merge glitch-free branch back into trunk
authorLennart Poettering <lennart@poettering.net>
Thu, 15 May 2008 23:34:41 +0000 (23:34 +0000)
committerLennart Poettering <lennart@poettering.net>
Thu, 15 May 2008 23:34:41 +0000 (23:34 +0000)
git-svn-id: file:///home/lennart/svn/public/pulseaudio/trunk@2445 fefdeb5f-60dc-0310-8127-8f9354f1896f

189 files changed:
PROTOCOL
configure.ac
doxygen/doxygen.conf.in
src/Makefile.am
src/daemon/caps.c
src/daemon/caps.h
src/daemon/cmdline.c
src/daemon/cpulimit.c
src/daemon/daemon-conf.c
src/daemon/daemon-conf.h
src/daemon/daemon.conf.in
src/daemon/main.c
src/map-file
src/modules/alsa-util.c
src/modules/alsa-util.h
src/modules/module-alsa-sink.c
src/modules/module-alsa-source.c
src/modules/module-combine.c
src/modules/module-default-device-restore.c
src/modules/module-device-restore.c [new file with mode: 0644]
src/modules/module-esound-sink.c
src/modules/module-hal-detect.c
src/modules/module-jack-sink.c
src/modules/module-jack-source.c
src/modules/module-ladspa-sink.c
src/modules/module-match.c
src/modules/module-null-sink.c
src/modules/module-oss.c
src/modules/module-pipe-sink.c
src/modules/module-pipe-source.c
src/modules/module-protocol-stub.c
src/modules/module-remap-sink.c
src/modules/module-rescue-streams.c
src/modules/module-sine.c
src/modules/module-suspend-on-idle.c
src/modules/module-tunnel.c
src/modules/module-volume-restore.c
src/modules/module-x11-bell.c
src/modules/module-zeroconf-publish.c
src/modules/oss-util.c
src/modules/oss-util.h
src/modules/rtp/module-rtp-recv.c
src/modules/rtp/module-rtp-send.c
src/modules/rtp/rtp.c
src/modules/rtp/rtp.h
src/modules/rtp/sap.c
src/modules/rtp/sap.h
src/modules/rtp/sdp.c
src/pulse/browser.c
src/pulse/cdecl.h
src/pulse/channelmap.h
src/pulse/client-conf-x11.c
src/pulse/client-conf.c
src/pulse/context.c
src/pulse/context.h
src/pulse/def.h
src/pulse/gccmacro.h [moved from src/pulsecore/gccmacro.h with 87% similarity]
src/pulse/internal.h
src/pulse/introspect.c
src/pulse/introspect.h
src/pulse/mainloop-api.c
src/pulse/mainloop-signal.c
src/pulse/mainloop-signal.h
src/pulse/operation.c
src/pulse/proplist.c
src/pulse/proplist.h
src/pulse/sample.c
src/pulse/sample.h
src/pulse/scache.c
src/pulse/scache.h
src/pulse/simple.h
src/pulse/stream.c
src/pulse/stream.h
src/pulse/subscribe.c
src/pulse/timeval.c
src/pulse/timeval.h
src/pulse/utf8.h
src/pulse/util.h
src/pulse/version.h.in
src/pulse/volume.c
src/pulse/volume.h
src/pulse/xmalloc.c
src/pulsecore/asyncmsgq.c
src/pulsecore/asyncmsgq.h
src/pulsecore/asyncq.c
src/pulsecore/asyncq.h
src/pulsecore/cli-command.c
src/pulsecore/cli-command.h
src/pulsecore/cli-text.c
src/pulsecore/cli.c
src/pulsecore/client.c
src/pulsecore/client.h
src/pulsecore/core-def.h [deleted file]
src/pulsecore/core-scache.c
src/pulsecore/core-scache.h
src/pulsecore/core-util.c
src/pulsecore/core-util.h
src/pulsecore/core.c
src/pulsecore/core.h
src/pulsecore/envelope.c
src/pulsecore/envelope.h
src/pulsecore/fdsem.c
src/pulsecore/fdsem.h
src/pulsecore/flist.h
src/pulsecore/hook-list.h
src/pulsecore/ioline.c
src/pulsecore/ioline.h
src/pulsecore/log.c
src/pulsecore/log.h
src/pulsecore/ltdl-helper.c
src/pulsecore/macro.h
src/pulsecore/mcalign.c
src/pulsecore/mcalign.h
src/pulsecore/memblock.c
src/pulsecore/memblock.h
src/pulsecore/memblockq.c
src/pulsecore/memblockq.h
src/pulsecore/memchunk.c
src/pulsecore/memchunk.h
src/pulsecore/module.c
src/pulsecore/module.h
src/pulsecore/namereg.c
src/pulsecore/namereg.h
src/pulsecore/native-common.h
src/pulsecore/pid.c
src/pulsecore/play-memblockq.c
src/pulsecore/play-memblockq.h
src/pulsecore/play-memchunk.c
src/pulsecore/play-memchunk.h
src/pulsecore/protocol-cli.c
src/pulsecore/protocol-esound.c
src/pulsecore/protocol-http.c
src/pulsecore/protocol-native.c
src/pulsecore/protocol-simple.c
src/pulsecore/pstream.c
src/pulsecore/pstream.h
src/pulsecore/refcnt.h
src/pulsecore/resampler.c
src/pulsecore/resampler.h
src/pulsecore/rtclock.c
src/pulsecore/rtclock.h
src/pulsecore/rtpoll.c
src/pulsecore/rtpoll.h
src/pulsecore/sample-util.c
src/pulsecore/sample-util.h
src/pulsecore/shm.c
src/pulsecore/shm.h
src/pulsecore/shmasyncq.c [new file with mode: 0644]
src/pulsecore/shmasyncq.h [new file with mode: 0644]
src/pulsecore/sink-input.c
src/pulsecore/sink-input.h
src/pulsecore/sink.c
src/pulsecore/sink.h
src/pulsecore/socket-client.c
src/pulsecore/socket-client.h
src/pulsecore/sound-file-stream.c
src/pulsecore/source-output.c
src/pulsecore/source-output.h
src/pulsecore/source.c
src/pulsecore/source.h
src/pulsecore/strbuf.h
src/pulsecore/tagstruct.c
src/pulsecore/tagstruct.h
src/pulsecore/thread-mq.c
src/pulsecore/thread-mq.h
src/pulsecore/time-smoother.c
src/pulsecore/time-smoother.h
src/pulsecore/tokenizer.c
src/tests/asyncq-test.c
src/tests/channelmap-test.c
src/tests/cpulimit-test.c
src/tests/interpol-test.c
src/tests/mainloop-test.c
src/tests/mcalign-test.c
src/tests/memblockq-test.c
src/tests/pacat-simple.c
src/tests/parec-simple.c
src/tests/proplist-test.c
src/tests/rtpoll-test.c
src/tests/rtstutter.c [new file with mode: 0644]
src/tests/smoother-test.c
src/tests/stripnul.c [new file with mode: 0644]
src/tests/strlist-test.c
src/tests/thread-mainloop-test.c
src/tests/voltest.c
src/utils/pacat.c
src/utils/pacmd.c
src/utils/pactl.c
src/utils/padsp.c

index 497fa47..74c08b4 100644 (file)
--- a/PROTOCOL
+++ b/PROTOCOL
@@ -78,3 +78,49 @@ New opcodes for notifications:
  PA_COMMAND_CAPTURE_STREAM_SUSPENDED
  PA_COMMAND_PLAYBACK_STREAM_MOVED
  PA_COMMAND_CAPTURE_STREAM_MOVED
+
+### v13, implemented  by >= 0.9.11
+
+New fields for PA_COMMAND_CREATE_PLAYBACK_STREAM, PA_COMMAND_CREATE_RECORD_STREAM request at the end:
+
+ peak_detect (bool)
+
+Replace field "name" for PA_COMMAND_CREATE_PLAYBACK_STREAM, PA_COMMAND_CREATE_RECORD_STREAM at the end:
+
+ proplist
+
+Replace field "name" for PA_COMMAND_SET_CLIENT_NAME request at the end:
+
+ proplist
+
+On response of PA_COMMAND_SET_CLIENT_NAME:
+
+ client_index
+
+New proplist field for sink, source, sink input, source output introspection opcodes and at the end:
+
+ proplist
+
+New opcodes for proplist modifications
+
+  PA_COMMAND_UPDATE_RECORD_STREAM_PROPLIST
+  PA_COMMAND_UPDATE_PLAYBACK_STREAM_PROPLIST
+  PA_COMMAND_UPDATE_CLIENT_PROPLIST
+  PA_COMMAND_REMOVE_RECORD_STREAM_PROPLIST
+  PA_COMMAND_REMOVE_PLAYBACK_STREAM_PROPLIST
+  PA_COMMAND_REMOVE_CLIENT_PROPLIST
+
+New field for PA_COMMAND_PLAY_SAMPLE:
+
+  proplist
+
+New field for PA_COMMAND_PLAY_SAMPLE response:
+
+  idx
+
+New field for PA_COMMAND_CREATE_PLAYBACK_STREAM at the end:
+
+  start_muted
+
+Buffer attributes for PA_COMMAND_CREATE_PLAYBACK_STREAM and
+PA_COMMAND_CREATE_RECORD_STREAM may now be 0 for default values.
index 4704869..ca7565d 100644 (file)
@@ -26,7 +26,7 @@ AC_PREREQ(2.60)
 
 m4_define(PA_MAJOR, [0])
 m4_define(PA_MINOR, [9])
-m4_define(PA_MICRO, [10])
+m4_define(PA_MICRO, [11])
 
 AC_INIT([pulseaudio], PA_MAJOR.PA_MINOR.PA_MICRO,[mzchyfrnhqvb (at) 0pointer (dot) net])
 AC_CONFIG_SRCDIR([src/daemon/main.c])
@@ -37,7 +37,7 @@ AC_SUBST(PA_MAJORMINOR, "PA_MAJOR.PA_MINOR")
 AC_SUBST(PACKAGE_URL, [http://pulseaudio.org/])
 
 AC_SUBST(PA_API_VERSION, 11)
-AC_SUBST(PA_PROTOCOL_VERSION, 12)
+AC_SUBST(PA_PROTOCOL_VERSION, 13)
 
 # The stable ABI for client applications, for the version info x:y:z
 # always will hold y=z
@@ -339,6 +339,7 @@ AC_CHECK_TYPES(ssize_t, , [AC_DEFINE([ssize_t], [signed long],
 AC_TYPE_OFF_T
 AC_TYPE_SIGNAL
 AC_TYPE_UID_T
+AC_CHECK_DECLS(environ)
 
 AC_CHECK_DEFINE([SIGXCPU], [signal.h], [
 HAVE_SIGXCPU=1
@@ -376,6 +377,9 @@ AC_SEARCH_LIBS([connect], [socket])
 # build, disabling its ability to make dlls.
 AC_CHECK_FUNCS([getopt_long], [], [AC_CHECK_LIB([iberty], [getopt_long])])
 
+AC_CHECK_LIB(gdbm, gdbm_open)
+AC_CHECK_HEADERS(gdbm.h)
+
 #### Check for functions ####
 
 # ISO
@@ -591,7 +595,7 @@ AC_ARG_ENABLE([alsa],
         [alsa=auto])
 
 if test "x${alsa}" != xno ; then
-    PKG_CHECK_MODULES(ASOUNDLIB, [ alsa >= 1.0.0 ],
+    PKG_CHECK_MODULES(ASOUNDLIB, [ alsa >= 1.0.16 ],
         [
             HAVE_ALSA=1
             AC_DEFINE([HAVE_ALSA], 1, [Have ALSA?])
index 81923a9..7ad5d2f 100644 (file)
@@ -417,7 +417,7 @@ WARN_LOGFILE           =
 # directories like "/usr/src/myproject". Separate the files or directories 
 # with spaces.
 
-INPUT                  = ../src/pulse/context.h ../src/pulse/stream.h ../src/pulse/pulseaudio.h ../src/pulse/sample.h ../src/pulse/def.h ../src/pulse/subscribe.h ../src/pulse/introspect.h ../src/pulse/scache.h ../src/pulse/mainloop-api.h ../src/pulse/glib-mainloop.h ../src/pulse/mainloop.h ../src/pulse/mainloop-signal.h ../src/pulse/error.h ../src/pulse/operation.h ../src/pulse/simple.h ../src/pulse/version.h ../src/pulse/volume.h ../src/pulse/channelmap.h ../src/pulse/thread-mainloop.h ../src/pulse/xmalloc.h ../src/pulse/utf8.h ../src/pulse/util.h ../src/pulse/timeval.h ../src/pulse/browser.h
+INPUT                  = ../src/pulse/context.h ../src/pulse/stream.h ../src/pulse/pulseaudio.h ../src/pulse/sample.h ../src/pulse/def.h ../src/pulse/subscribe.h ../src/pulse/introspect.h ../src/pulse/scache.h ../src/pulse/mainloop-api.h ../src/pulse/glib-mainloop.h ../src/pulse/mainloop.h ../src/pulse/mainloop-signal.h ../src/pulse/error.h ../src/pulse/operation.h ../src/pulse/simple.h ../src/pulse/version.h ../src/pulse/volume.h ../src/pulse/channelmap.h ../src/pulse/thread-mainloop.h ../src/pulse/xmalloc.h ../src/pulse/utf8.h ../src/pulse/util.h ../src/pulse/timeval.h ../src/pulse/proplist.h ../src/pulse/gccmacro.h
 
 # If the value of the INPUT tag contains directories, you can use the 
 # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
index 916a06a..799e7b2 100644 (file)
@@ -131,8 +131,7 @@ pulseaudio_SOURCES = \
                daemon/daemon-conf.c daemon/daemon-conf.h \
                daemon/dumpmodules.c daemon/dumpmodules.h \
                daemon/ltdl-bind-now.c daemon/ltdl-bind-now.h \
-               daemon/main.c \
-               pulsecore/gccmacro.h
+               daemon/main.c
 
 pulseaudio_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) $(LIBSAMPLERATE_CFLAGS) $(LIBSNDFILE_CFLAGS) $(CAP_CFLAGS) $(LIBOIL_CFLAGS) $(DBUS_CFLAGS)
 pulseaudio_CPPFLAGS = $(AM_CPPFLAGS)
@@ -226,7 +225,7 @@ pabrowse_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
 #         Test programs           #
 ###################################
 
-check_PROGRAMS = \
+noinst_PROGRAMS = \
                mainloop-test \
                mcalign-test \
                pacat-simple \
@@ -255,16 +254,18 @@ check_PROGRAMS = \
                mix-test \
                remix-test \
                envelope-test \
-               proplist-test
+               proplist-test \
+               rtstutter \
+               stripnul
 
 if HAVE_SIGXCPU
-check_PROGRAMS += \
+noinst_PROGRAMS += \
                cpulimit-test \
                cpulimit-test2
 endif
 
 if HAVE_GLIB20
-check_PROGRAMS += \
+noinst_PROGRAMS += \
                mainloop-test-glib
 endif
 
@@ -426,10 +427,20 @@ envelope_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS)
 envelope_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS)
 
 proplist_test_SOURCES = tests/proplist-test.c
-proplist_test_LDADD = $(AM_LDADD) libpulse.la
+proplist_test_LDADD = $(AM_LDADD) libpulsecore.la
 proplist_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS)
 proplist_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS)
 
+rtstutter_SOURCES = tests/rtstutter.c
+rtstutter_LDADD = $(AM_LDADD) libpulsecore.la
+rtstutter_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS)
+rtstutter_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS)
+
+stripnul_SOURCES = tests/stripnul.c
+stripnul_LDADD = $(AM_LDADD) libpulsecore.la
+stripnul_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS)
+stripnul_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS)
+
 ###################################
 #         Client library          #
 ###################################
@@ -458,7 +469,8 @@ pulseinclude_HEADERS = \
                pulse/version.h \
                pulse/volume.h \
                pulse/xmalloc.h \
-               pulse/proplist.h
+               pulse/proplist.h \
+               pulse/gccmacro.h
 
 if HAVE_AVAHI
 pulseinclude_HEADERS += \
@@ -517,7 +529,6 @@ libpulse_la_SOURCES += \
                pulsecore/conf-parser.c pulsecore/conf-parser.h \
                pulsecore/core-util.c pulsecore/core-util.h \
                pulsecore/dynarray.c pulsecore/dynarray.h \
-               pulsecore/gccmacro.h \
                pulsecore/hashmap.c pulsecore/hashmap.h \
                pulsecore/idxset.c pulsecore/idxset.h \
                pulsecore/inet_ntop.c pulsecore/inet_ntop.h \
@@ -550,6 +561,8 @@ libpulse_la_SOURCES += \
                pulsecore/object.c pulsecore/object.h \
                pulsecore/msgobject.c pulsecore/msgobject.h \
                pulsecore/once.c pulsecore/once.h \
+               pulsecore/rtclock.c pulsecore/rtclock.h \
+               pulsecore/time-smoother.c pulsecore/time-smoother.h \
                $(PA_THREAD_OBJS)
 
 if OS_IS_WIN32
@@ -649,7 +662,6 @@ noinst_HEADERS = \
                pulsecore/cli-text.h \
                pulsecore/client.h \
                pulsecore/core.h \
-               pulsecore/core-def.h \
                pulsecore/core-scache.h \
                pulsecore/core-subscribe.h \
                pulsecore/conf-parser.h \
@@ -708,7 +720,8 @@ libpulsecore_la_SOURCES = \
                pulse/utf8.c pulse/utf8.h \
                pulse/util.c pulse/util.h \
                pulse/volume.c pulse/volume.h \
-               pulse/xmalloc.c pulse/xmalloc.h
+               pulse/xmalloc.c pulse/xmalloc.h \
+               pulse/proplist.c pulse/proplist.h
 
 # Pure core stuff (some are shared in libpulse though).
 libpulsecore_la_SOURCES += \
@@ -998,6 +1011,7 @@ modlibexec_LTLIBRARIES += \
                module-null-sink.la \
                module-detect.la \
                module-volume-restore.la \
+               module-device-restore.la \
                module-default-device-restore.la \
                module-rescue-streams.la \
                module-suspend-on-idle.la \
@@ -1168,6 +1182,7 @@ SYMDEF_FILES = \
                modules/module-jack-sink-symdef.h \
                modules/module-jack-source-symdef.h \
                modules/module-volume-restore-symdef.h \
+               modules/module-device-restore-symdef.h \
                modules/module-default-device-restore-symdef.h \
                modules/module-rescue-streams-symdef.h \
                modules/module-suspend-on-idle-symdef.h \
@@ -1296,7 +1311,7 @@ module_remap_sink_la_LDFLAGS = -module -avoid-version
 module_remap_sink_la_LIBADD = $(AM_LIBADD) libpulsecore.la
 
 module_ladspa_sink_la_SOURCES = modules/module-ladspa-sink.c modules/ladspa.h
-module_ladspa_sink_la_CFLAGS = -DLADSPA_PATH=\"$(libdir)/ladspa:/usr/local/lib/ladspa:/usr/lib/ladspa\" $(AM_CFLAGS)
+module_ladspa_sink_la_CFLAGS = -DLADSPA_PATH=\"$(libdir)/ladspa:/usr/local/lib/ladspa:/usr/lib/ladspa:/usr/local/lib64/ladspa:/usr/lib64/ladspa\" $(AM_CFLAGS)
 module_ladspa_sink_la_LDFLAGS = -module -avoid-version
 module_ladspa_sink_la_LIBADD = $(AM_LIBADD) $(LIBLTDL) libpulsecore.la
 
@@ -1408,6 +1423,12 @@ module_volume_restore_la_LDFLAGS = -module -avoid-version
 module_volume_restore_la_LIBADD = $(AM_LIBADD) libpulsecore.la
 module_volume_restore_la_CFLAGS = $(AM_CFLAGS)
 
+# Device volume restore module
+module_device_restore_la_SOURCES = modules/module-device-restore.c
+module_device_restore_la_LDFLAGS = -module -avoid-version
+module_device_restore_la_LIBADD = $(AM_LIBADD) libpulsecore.la -lgdbm
+module_device_restore_la_CFLAGS = $(AM_CFLAGS)
+
 # Default sink/source restore module
 module_default_device_restore_la_SOURCES = modules/module-default-device-restore.c
 module_default_device_restore_la_LDFLAGS = -module -avoid-version
index d78e968..e936d6b 100644 (file)
@@ -85,31 +85,21 @@ void pa_drop_root(void) {
 #if defined(HAVE_SYS_CAPABILITY_H) && defined(HAVE_SYS_PRCTL_H)
 
 /* Limit permitted capabilities set to CAPSYS_NICE */
-int pa_limit_caps(void) {
-    int r = -1;
+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);
+    pa_assert_se(cap_set_proc(caps) == 0);
 
-    cap_clear(caps);
-    cap_set_flag(caps, CAP_EFFECTIVE, 1, &nice_cap, CAP_SET);
-    cap_set_flag(caps, CAP_PERMITTED, 1, &nice_cap, CAP_SET);
-
-    if (cap_set_proc(caps) < 0)
-        goto fail;
-
-    if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0)
-        goto fail;
+    pa_assert_se(prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == 0);
 
     pa_log_info("Dropped capabilities successfully.");
 
-    r = 1;
-
-fail:
-    cap_free(caps);
-
-    return r;
+    pa_assert_se(cap_free(caps) == 0);
 }
 
 /* Drop all capabilities, effectively becoming a normal user */
@@ -119,9 +109,9 @@ void pa_drop_caps(void) {
     pa_assert_se(prctl(PR_SET_KEEPCAPS, 0, 0, 0, 0) == 0);
 
     pa_assert_se(caps = cap_init());
-    cap_clear(caps);
+    pa_assert_se(cap_clear(caps) == 0);
     pa_assert_se(cap_set_proc(caps) == 0);
-    cap_free(caps);
+    pa_assert_se(cap_free(caps) == 0);
 }
 
 #else
index 91c8841..5b21f12 100644 (file)
@@ -26,6 +26,6 @@
 
 void pa_drop_root(void);
 void pa_drop_caps(void);
-int pa_limit_caps(void);
+void pa_limit_caps(void);
 
 #endif
index f1e1282..97c75f3 100644 (file)
@@ -293,8 +293,7 @@ int pa_cmdline_parse(pa_daemon_conf *conf, int argc, char *const argv [], int *d
                 break;
 
             case 'n':
-                pa_xfree(conf->default_script_file);
-                conf->default_script_file = NULL;
+                conf->load_default_script_file = FALSE;
                 break;
 
             case ARG_LOG_TARGET:
index 620a93a..579b91e 100644 (file)
@@ -82,7 +82,7 @@ static pa_io_event *io_event = NULL;
 static struct sigaction sigaction_prev;
 
 /* Nonzero after pa_cpu_limit_init() */
-static int installed = 0;
+static pa_bool_t installed = FALSE;
 
 /* The current state of operation */
 static enum  {
@@ -210,7 +210,7 @@ int pa_cpu_limit_init(pa_mainloop_api *m) {
         return -1;
     }
 
-    installed = 1;
+    installed = TRUE;
 
     reset_cpu_time(CPUTIME_INTERVAL_SOFT);
 
@@ -231,7 +231,7 @@ void pa_cpu_limit_done(void) {
 
     if (installed) {
         pa_assert_se(sigaction(SIGXCPU, &sigaction_prev, NULL) >= 0);
-        installed = 0;
+        installed = FALSE;
     }
 }
 
index c98c021..f9ad7ec 100644 (file)
@@ -33,6 +33,7 @@
 #include <sched.h>
 
 #include <pulse/xmalloc.h>
+#include <pulse/timeval.h>
 
 #include <pulsecore/core-error.h>
 #include <pulsecore/core-util.h>
@@ -45,6 +46,8 @@
 
 #define DEFAULT_SCRIPT_FILE PA_DEFAULT_CONFIG_DIR PA_PATH_SEP "default.pa"
 #define DEFAULT_SCRIPT_FILE_USER PA_PATH_SEP "default.pa"
+#define DEFAULT_SYSTEM_SCRIPT_FILE PA_DEFAULT_CONFIG_DIR PA_PATH_SEP "system.pa"
+
 #define DEFAULT_CONFIG_FILE PA_DEFAULT_CONFIG_DIR PA_PATH_SEP "daemon.conf"
 #define DEFAULT_CONFIG_FILE_USER PA_PATH_SEP "daemon.conf"
 
@@ -67,6 +70,7 @@ static const pa_daemon_conf default_conf = {
     .auto_log_target = 1,
     .script_commands = NULL,
     .dl_search_path = NULL,
+    .load_default_script_file = TRUE,
     .default_script_file = NULL,
     .log_target = PA_LOG_SYSLOG,
     .log_level = PA_LOG_NOTICE,
@@ -81,34 +85,43 @@ static const pa_daemon_conf default_conf = {
     .default_fragment_size_msec = 25,
     .default_sample_spec = { .format = PA_SAMPLE_S16NE, .rate = 44100, .channels = 2 }
 #ifdef HAVE_SYS_RESOURCE_H
-    , .rlimit_as = { .value = 0, .is_set = FALSE },
-    .rlimit_core = { .value = 0, .is_set = FALSE },
+   ,.rlimit_fsize = { .value = 0, .is_set = FALSE },
     .rlimit_data = { .value = 0, .is_set = FALSE },
-    .rlimit_fsize = { .value = 0, .is_set = FALSE },
-    .rlimit_nofile = { .value = 256, .is_set = TRUE },
-    .rlimit_stack = { .value = 0, .is_set = FALSE }
+    .rlimit_stack = { .value = 0, .is_set = FALSE },
+    .rlimit_core = { .value = 0, .is_set = FALSE },
+    .rlimit_rss = { .value = 0, .is_set = FALSE }
 #ifdef RLIMIT_NPROC
-    , .rlimit_nproc = { .value = 0, .is_set = FALSE }
+   ,.rlimit_nproc = { .value = 0, .is_set = FALSE }
 #endif
+   ,.rlimit_nofile = { .value = 256, .is_set = TRUE }
 #ifdef RLIMIT_MEMLOCK
-    , .rlimit_memlock = { .value = 0, .is_set = FALSE }
+   ,.rlimit_memlock = { .value = 0, .is_set = FALSE }
+#endif
+   ,.rlimit_as = { .value = 0, .is_set = FALSE }
+#ifdef RLIMIT_LOCKS
+   ,.rlimit_locks = { .value = 0, .is_set = FALSE }
+#endif
+#ifdef RLIMIT_SIGPENDING
+   ,.rlimit_sigpending = { .value = 0, .is_set = FALSE }
+#endif
+#ifdef RLIMIT_MSGQUEUE
+   ,.rlimit_msgqueue = { .value = 0, .is_set = FALSE }
 #endif
 #ifdef RLIMIT_NICE
-    , .rlimit_nice = { .value = 31, .is_set = TRUE }     /* nice level of -11 */
+   ,.rlimit_nice = { .value = 31, .is_set = TRUE }     /* nice level of -11 */
 #endif
 #ifdef RLIMIT_RTPRIO
-    , .rlimit_rtprio = { .value = 9, .is_set = TRUE }      /* One below JACK's default for the server */
+   ,.rlimit_rtprio = { .value = 9, .is_set = TRUE }    /* One below JACK's default for the server */
+#endif
+#ifdef RLIMIT_RTTIME
+   ,.rlimit_rttime = { .value = PA_USEC_PER_SEC, .is_set = TRUE }
 #endif
 #endif
 };
 
 pa_daemon_conf* pa_daemon_conf_new(void) {
-    FILE *f;
     pa_daemon_conf *c = pa_xnewdup(pa_daemon_conf, &default_conf, 1);
 
-    if ((f = pa_open_config_file(DEFAULT_SCRIPT_FILE, DEFAULT_SCRIPT_FILE_USER, ENV_SCRIPT_FILE, &c->default_script_file, "r")))
-        fclose(f);
-
     c->dl_search_path = pa_xstrdup(PA_DLSEARCHPATH);
     return c;
 }
@@ -412,25 +425,39 @@ int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) {
         { "default-fragment-size-msec", parse_fragment_size_msec, NULL },
         { "nice-level",                 parse_nice_level,         NULL },
         { "disable-remixing",           pa_config_parse_bool,     NULL },
+        { "load-default-script-file",   pa_config_parse_bool,     NULL },
 #ifdef HAVE_SYS_RESOURCE_H
-        { "rlimit-as",                  parse_rlimit,             NULL },
-        { "rlimit-core",                parse_rlimit,             NULL },
-        { "rlimit-data",                parse_rlimit,             NULL },
         { "rlimit-fsize",               parse_rlimit,             NULL },
-        { "rlimit-nofile",              parse_rlimit,             NULL },
+        { "rlimit-data",                parse_rlimit,             NULL },
         { "rlimit-stack",               parse_rlimit,             NULL },
+        { "rlimit-core",                parse_rlimit,             NULL },
+        { "rlimit-rss",                 parse_rlimit,             NULL },
+        { "rlimit-nofile",              parse_rlimit,             NULL },
+        { "rlimit-as",                  parse_rlimit,             NULL },
 #ifdef RLIMIT_NPROC
         { "rlimit-nproc",               parse_rlimit,             NULL },
 #endif
 #ifdef RLIMIT_MEMLOCK
         { "rlimit-memlock",             parse_rlimit,             NULL },
 #endif
+#ifdef RLIMIT_LOCKS
+        { "rlimit-locks",               parse_rlimit,             NULL },
+#endif
+#ifdef RLIMIT_SIGPENDING
+        { "rlimit-sigpending",          parse_rlimit,             NULL },
+#endif
+#ifdef RLIMIT_MSGQUEUE
+        { "rlimit-msgqueue",            parse_rlimit,             NULL },
+#endif
 #ifdef RLIMIT_NICE
         { "rlimit-nice",                parse_rlimit,             NULL },
 #endif
 #ifdef RLIMIT_RTPRIO
         { "rlimit-rtprio",              parse_rlimit,             NULL },
 #endif
+#ifdef RLIMIT_RTTIME
+        { "rlimit-rttime",              parse_rlimit,             NULL },
+#endif
 #endif
         { NULL,                         NULL,                     NULL },
     };
@@ -461,33 +488,66 @@ int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) {
     table[23].data = c;
     table[24].data = c;
     table[25].data = &c->disable_remixing;
+    table[26].data = &c->load_default_script_file;
 #ifdef HAVE_SYS_RESOURCE_H
-    table[26].data = &c->rlimit_as;
-    table[27].data = &c->rlimit_core;
+    table[27].data = &c->rlimit_fsize;
     table[28].data = &c->rlimit_data;
-    table[29].data = &c->rlimit_fsize;
-    table[30].data = &c->rlimit_nofile;
-    table[31].data = &c->rlimit_stack;
+    table[29].data = &c->rlimit_stack;
+    table[30].data = &c->rlimit_as;
+    table[31].data = &c->rlimit_core;
+    table[32].data = &c->rlimit_nofile;
+    table[33].data = &c->rlimit_as;
 #ifdef RLIMIT_NPROC
-    table[32].data = &c->rlimit_nproc;
+    table[34].data = &c->rlimit_nproc;
 #endif
+
 #ifdef RLIMIT_MEMLOCK
 #ifndef RLIMIT_NPROC
 #error "Houston, we have a numbering problem!"
 #endif
-    table[33].data = &c->rlimit_memlock;
+    table[35].data = &c->rlimit_memlock;
 #endif
-#ifdef RLIMIT_NICE
+
+#ifdef RLIMIT_LOCKS
 #ifndef RLIMIT_MEMLOCK
 #error "Houston, we have a numbering problem!"
 #endif
-    table[34].data = &c->rlimit_nice;
+    table[36].data = &c->rlimit_locks;
+#endif
+
+#ifdef RLIMIT_SIGPENDING
+#ifndef RLIMIT_LOCKS
+#error "Houston, we have a numbering problem!"
+#endif
+    table[37].data = &c->rlimit_sigpending;
+#endif
+
+#ifdef RLIMIT_MSGQUEUE
+#ifndef RLIMIT_SIGPENDING
+#error "Houston, we have a numbering problem!"
+#endif
+    table[38].data = &c->rlimit_msgqueue;
+#endif
+
+#ifdef RLIMIT_NICE
+#ifndef RLIMIT_MSGQUEUE
+#error "Houston, we have a numbering problem!"
+#endif
+    table[39].data = &c->rlimit_nice;
 #endif
+
 #ifdef RLIMIT_RTPRIO
 #ifndef RLIMIT_NICE
 #error "Houston, we have a numbering problem!"
 #endif
-    table[35].data = &c->rlimit_rtprio;
+    table[40].data = &c->rlimit_rtprio;
+#endif
+
+#ifdef RLIMIT_RTTIME
+#ifndef RLIMIT_RTTIME
+#error "Houston, we have a numbering problem!"
+#endif
+    table[41].data = &c->rlimit_rttime;
 #endif
 #endif
 
@@ -496,10 +556,10 @@ int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) {
 
     f = filename ?
         fopen(c->config_file = pa_xstrdup(filename), "r") :
-        pa_open_config_file(DEFAULT_CONFIG_FILE, DEFAULT_CONFIG_FILE_USER, ENV_CONFIG_FILE, &c->config_file, "r");
+        pa_open_config_file(DEFAULT_CONFIG_FILE, DEFAULT_CONFIG_FILE_USER, ENV_CONFIG_FILE, &c->config_file);
 
     if (!f && errno != ENOENT) {
-        pa_log_warn("Failed to open configuration file '%s': %s", c->config_file, pa_cstrerror(errno));
+        pa_log_warn("Failed to open configuration file: %s", pa_cstrerror(errno));
         goto finish;
     }
 
@@ -514,6 +574,7 @@ finish:
 
 int pa_daemon_conf_env(pa_daemon_conf *c) {
     char *e;
+    pa_assert(c);
 
     if ((e = getenv(ENV_DL_SEARCH_PATH))) {
         pa_xfree(c->dl_search_path);
@@ -527,6 +588,35 @@ int pa_daemon_conf_env(pa_daemon_conf *c) {
     return 0;
 }
 
+const char *pa_daemon_conf_get_default_script_file(pa_daemon_conf *c) {
+    pa_assert(c);
+
+    if (!c->default_script_file) {
+        if (c->system_instance)
+            c->default_script_file = pa_find_config_file(DEFAULT_SYSTEM_SCRIPT_FILE, NULL, ENV_SCRIPT_FILE);
+        else
+            c->default_script_file = pa_find_config_file(DEFAULT_SCRIPT_FILE, DEFAULT_SCRIPT_FILE_USER, ENV_SCRIPT_FILE);
+    }
+
+    return c->default_script_file;
+}
+
+FILE *pa_daemon_conf_open_default_script_file(pa_daemon_conf *c) {
+    FILE *f;
+    pa_assert(c);
+
+    if (!c->default_script_file) {
+        if (c->system_instance)
+            f = pa_open_config_file(DEFAULT_SYSTEM_SCRIPT_FILE, NULL, ENV_SCRIPT_FILE, &c->default_script_file);
+        else
+            f = pa_open_config_file(DEFAULT_SCRIPT_FILE, DEFAULT_SCRIPT_FILE_USER, ENV_SCRIPT_FILE, &c->default_script_file);
+    } else
+        f = fopen(c->default_script_file, "r");
+
+    return f;
+}
+
+
 static const char* const log_level_to_string[] = {
     [PA_LOG_DEBUG] = "debug",
     [PA_LOG_INFO] = "info",
@@ -561,8 +651,9 @@ char *pa_daemon_conf_dump(pa_daemon_conf *c) {
     pa_strbuf_printf(s, "exit-idle-time = %i\n", c->exit_idle_time);
     pa_strbuf_printf(s, "module-idle-time = %i\n", c->module_idle_time);
     pa_strbuf_printf(s, "scache-idle-time = %i\n", c->scache_idle_time);
-    pa_strbuf_printf(s, "dl-search-path = %s\n", c->dl_search_path ? c->dl_search_path : "");
-    pa_strbuf_printf(s, "default-script-file = %s\n", c->default_script_file);
+    pa_strbuf_printf(s, "dl-search-path = %s\n", pa_strempty(c->dl_search_path));
+    pa_strbuf_printf(s, "default-script-file = %s\n", pa_strempty(pa_daemon_conf_get_default_script_file(c)));
+    pa_strbuf_printf(s, "load-default-script-file = %s\n", pa_yes_no(c->load_default_script_file));
     pa_strbuf_printf(s, "log-target = %s\n", c->auto_log_target ? "auto" : (c->log_target == PA_LOG_SYSLOG ? "syslog" : "stderr"));
     pa_strbuf_printf(s, "log-level = %s\n", log_level_to_string[c->log_level]);
     pa_strbuf_printf(s, "resample-method = %s\n", pa_resample_method_to_string(c->resample_method));
@@ -573,23 +664,36 @@ char *pa_daemon_conf_dump(pa_daemon_conf *c) {
     pa_strbuf_printf(s, "default-fragments = %u\n", c->default_n_fragments);
     pa_strbuf_printf(s, "default-fragment-size-msec = %u\n", c->default_fragment_size_msec);
 #ifdef HAVE_SYS_RESOURCE_H
-    pa_strbuf_printf(s, "rlimit-as = %li\n", c->rlimit_as.is_set ? (long int) c->rlimit_as.value : -1);
-    pa_strbuf_printf(s, "rlimit-core = %li\n", c->rlimit_core.is_set ? (long int) c->rlimit_core.value : -1);
-    pa_strbuf_printf(s, "rlimit-data = %li\n", c->rlimit_data.is_set ? (long int) c->rlimit_data.value : -1);
     pa_strbuf_printf(s, "rlimit-fsize = %li\n", c->rlimit_fsize.is_set ? (long int) c->rlimit_fsize.value : -1);
-    pa_strbuf_printf(s, "rlimit-nofile = %li\n", c->rlimit_nofile.is_set ? (long int) c->rlimit_nofile.value : -1);
+    pa_strbuf_printf(s, "rlimit-data = %li\n", c->rlimit_data.is_set ? (long int) c->rlimit_data.value : -1);
     pa_strbuf_printf(s, "rlimit-stack = %li\n", c->rlimit_stack.is_set ? (long int) c->rlimit_stack.value : -1);
+    pa_strbuf_printf(s, "rlimit-core = %li\n", c->rlimit_core.is_set ? (long int) c->rlimit_core.value : -1);
+    pa_strbuf_printf(s, "rlimit-as = %li\n", c->rlimit_as.is_set ? (long int) c->rlimit_as.value : -1);
+    pa_strbuf_printf(s, "rlimit-rss = %li\n", c->rlimit_rss.is_set ? (long int) c->rlimit_rss.value : -1);
 #ifdef RLIMIT_NPROC
     pa_strbuf_printf(s, "rlimit-nproc = %li\n", c->rlimit_nproc.is_set ? (long int) c->rlimit_nproc.value : -1);
 #endif
+    pa_strbuf_printf(s, "rlimit-nofile = %li\n", c->rlimit_nofile.is_set ? (long int) c->rlimit_nofile.value : -1);
 #ifdef RLIMIT_MEMLOCK
     pa_strbuf_printf(s, "rlimit-memlock = %li\n", c->rlimit_memlock.is_set ? (long int) c->rlimit_memlock.value : -1);
 #endif
+#ifdef RLIMIT_LOCKS
+    pa_strbuf_printf(s, "rlimit-locks = %li\n", c->rlimit_locks.is_set ? (long int) c->rlimit_locks.value : -1);
+#endif
+#ifdef RLIMIT_SIGPENDING
+    pa_strbuf_printf(s, "rlimit-sigpending = %li\n", c->rlimit_sigpending.is_set ? (long int) c->rlimit_sigpending.value : -1);
+#endif
+#ifdef RLIMIT_MSGQUEUE
+    pa_strbuf_printf(s, "rlimit-msgqueue = %li\n", c->rlimit_msgqueue.is_set ? (long int) c->rlimit_msgqueue.value : -1);
+#endif
 #ifdef RLIMIT_NICE
-    pa_strbuf_printf(s, "rlimit-nice = %li\n", c->rlimit_memlock.is_set ? (long int) c->rlimit_nice.value : -1);
+    pa_strbuf_printf(s, "rlimit-nice = %li\n", c->rlimit_nice.is_set ? (long int) c->rlimit_nice.value : -1);
 #endif
 #ifdef RLIMIT_RTPRIO
-    pa_strbuf_printf(s, "rlimit-rtprio = %li\n", c->rlimit_memlock.is_set ? (long int) c->rlimit_rtprio.value : -1);
+    pa_strbuf_printf(s, "rlimit-rtprio = %li\n", c->rlimit_rtprio.is_set ? (long int) c->rlimit_rtprio.value : -1);
+#endif
+#ifdef RLIMIT_RTTIME
+    pa_strbuf_printf(s, "rlimit-rttime = %li\n", c->rlimit_rttime.is_set ? (long int) c->rlimit_rttime.value : -1);
 #endif
 #endif
 
index 3dcafbf..03a7566 100644 (file)
@@ -27,6 +27,7 @@
 
 #include <pulsecore/log.h>
 #include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
 #include <pulse/sample.h>
 
 #ifdef HAVE_SYS_RESOURCE_H
@@ -65,7 +66,8 @@ typedef struct pa_daemon_conf {
         system_instance,
         no_cpu_limit,
         disable_shm,
-        disable_remixing;
+        disable_remixing,
+        load_default_script_file;
     int exit_idle_time,
         module_idle_time,
         scache_idle_time,
@@ -79,19 +81,31 @@ typedef struct pa_daemon_conf {
     char *config_file;
 
 #ifdef HAVE_SYS_RESOURCE_H
-    pa_rlimit rlimit_as, rlimit_core, rlimit_data, rlimit_fsize, rlimit_nofile, rlimit_stack;
+    pa_rlimit rlimit_fsize, rlimit_data, rlimit_stack, rlimit_core, rlimit_rss, rlimit_nofile, rlimit_as;
 #ifdef RLIMIT_NPROC
     pa_rlimit rlimit_nproc;
 #endif
 #ifdef RLIMIT_MEMLOCK
     pa_rlimit rlimit_memlock;
 #endif
+#ifdef RLIMIT_LOCKS
+    pa_rlimit rlimit_locks;
+#endif
+#ifdef RLIMIT_SIGPENDING
+    pa_rlimit rlimit_sigpending;
+#endif
+#ifdef RLIMIT_MSGQUEUE
+    pa_rlimit rlimit_msgqueue;
+#endif
 #ifdef RLIMIT_NICE
     pa_rlimit rlimit_nice;
 #endif
 #ifdef RLIMIT_RTPRIO
     pa_rlimit rlimit_rtprio;
 #endif
+#ifdef RLIMIT_RTTIME
+    pa_rlimit rlimit_rttime;
+#endif
 #endif
 
     unsigned default_n_fragments, default_fragment_size_msec;
@@ -121,4 +135,7 @@ int pa_daemon_conf_set_log_target(pa_daemon_conf *c, const char *string);
 int pa_daemon_conf_set_log_level(pa_daemon_conf *c, const char *string);
 int pa_daemon_conf_set_resample_method(pa_daemon_conf *c, const char *string);
 
+const char *pa_daemon_conf_get_default_script_file(pa_daemon_conf *c);
+FILE *pa_daemon_conf_open_default_script_file(pa_daemon_conf *c);
+
 #endif
index e4cfb82..fd35c0f 100644 (file)
@@ -40,6 +40,7 @@
 
 ; dl-search-path = (depends on architecture)
 
+; load-defaul-script-file = yes
 ; default-script-file = @PA_DEFAULT_CONFIG_FILE@
 
 ; log-target = auto
 
 ; no-cpu-limit = no
 
-; rlimit-as = -1
-; rlimit-core = -1
-; rlimit-data = -1
 ; rlimit-fsize = -1
-; rlimit-nofile = 256
+; rlimit-data = -1
 ; rlimit-stack = -1
+; rlimit-core = -1
+; rlimit-as = -1
+; rlimit-rss = -1
 ; rlimit-nproc = -1
+; rlimit-nofile = 256
 ; rlimit-memlock = -1
+; rlimit-locks = -1
+; rlimit-sigpending = -1
+; rlimit-msgqueue = -1
 ; rlimit-nice = 31
 ; rlimit-rtprio = 9
+; rlimit-rtttime = 1000000
 
 ; default-sample-format = s16le
 ; default-sample-rate = 44100
index 6b0c81d..789d104 100644 (file)
@@ -115,7 +115,7 @@ static void message_cb(pa_mainloop_api*a, pa_time_event*e, PA_GCC_UNUSED const s
     MSG msg;
     struct timeval tvnext;
 
-    while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
+    while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
         if (msg.message == WM_QUIT)
             raise(SIGTERM);
         else {
@@ -164,8 +164,6 @@ static void signal_callback(pa_mainloop_api*m, PA_GCC_UNUSED pa_signal_event *e,
     }
 }
 
-#define set_env(key, value) putenv(pa_sprintf_malloc("%s=%s", (key), (value)))
-
 #if defined(HAVE_PWD_H) && defined(HAVE_GRP_H)
 
 static int change_user(void) {
@@ -241,14 +239,14 @@ static int change_user(void) {
         return -1;
     }
 
-    set_env("USER", PA_SYSTEM_USER);
-    set_env("USERNAME", PA_SYSTEM_USER);
-    set_env("LOGNAME", PA_SYSTEM_USER);
-    set_env("HOME", PA_SYSTEM_RUNTIME_PATH);
+    pa_set_env("USER", PA_SYSTEM_USER);
+    pa_set_env("USERNAME", PA_SYSTEM_USER);
+    pa_set_env("LOGNAME", PA_SYSTEM_USER);
+    pa_set_env("HOME", PA_SYSTEM_RUNTIME_PATH);
 
     /* Relevant for pa_runtime_path() */
-    set_env("PULSE_RUNTIME_PATH", PA_SYSTEM_RUNTIME_PATH);
-    set_env("PULSE_CONFIG_PATH", PA_SYSTEM_RUNTIME_PATH);
+    pa_set_env("PULSE_RUNTIME_PATH", PA_SYSTEM_RUNTIME_PATH);
+    pa_set_env("PULSE_CONFIG_PATH", PA_SYSTEM_RUNTIME_PATH);
 
     pa_log_info("Successfully dropped root privileges.");
 
@@ -264,23 +262,6 @@ static int change_user(void) {
 
 #endif /* HAVE_PWD_H && HAVE_GRP_H */
 
-static int create_runtime_dir(void) {
-    char fn[PATH_MAX];
-
-    pa_runtime_path(NULL, fn, sizeof(fn));
-
-    /* This function is called only when the daemon is started in
-     * per-user mode. We create the runtime directory somewhere in
-     * /tmp/ with the current UID/GID */
-
-    if (pa_make_secure_dir(fn, 0700, (uid_t)-1, (gid_t)-1) < 0) {
-        pa_log("Failed to create '%s': %s", fn, pa_cstrerror(errno));
-        return -1;
-    }
-
-    return 0;
-}
-
 #ifdef HAVE_SYS_RESOURCE_H
 
 static int set_one_rlimit(const pa_rlimit *r, int resource, const char *name) {
@@ -293,7 +274,7 @@ static int set_one_rlimit(const pa_rlimit *r, int resource, const char *name) {
     rl.rlim_cur = rl.rlim_max = r->value;
 
     if (setrlimit(resource, &rl) < 0) {
-        pa_log_warn("setrlimit(%s, (%u, %u)) failed: %s", name, (unsigned) r->value, (unsigned) r->value, pa_cstrerror(errno));
+        pa_log_info("setrlimit(%s, (%u, %u)) failed: %s", name, (unsigned) r->value, (unsigned) r->value, pa_cstrerror(errno));
         return -1;
     }
 
@@ -301,24 +282,37 @@ static int set_one_rlimit(const pa_rlimit *r, int resource, const char *name) {
 }
 
 static void set_all_rlimits(const pa_daemon_conf *conf) {
-    set_one_rlimit(&conf->rlimit_as, RLIMIT_AS, "RLIMIT_AS");
-    set_one_rlimit(&conf->rlimit_core, RLIMIT_CORE, "RLIMIT_CORE");
-    set_one_rlimit(&conf->rlimit_data, RLIMIT_DATA, "RLIMIT_DATA");
     set_one_rlimit(&conf->rlimit_fsize, RLIMIT_FSIZE, "RLIMIT_FSIZE");
-    set_one_rlimit(&conf->rlimit_nofile, RLIMIT_NOFILE, "RLIMIT_NOFILE");
+    set_one_rlimit(&conf->rlimit_data, RLIMIT_DATA, "RLIMIT_DATA");
     set_one_rlimit(&conf->rlimit_stack, RLIMIT_STACK, "RLIMIT_STACK");
+    set_one_rlimit(&conf->rlimit_core, RLIMIT_CORE, "RLIMIT_CORE");
+    set_one_rlimit(&conf->rlimit_rss, RLIMIT_RSS, "RLIMIT_RSS");
 #ifdef RLIMIT_NPROC
     set_one_rlimit(&conf->rlimit_nproc, RLIMIT_NPROC, "RLIMIT_NPROC");
 #endif
+    set_one_rlimit(&conf->rlimit_nofile, RLIMIT_NOFILE, "RLIMIT_NOFILE");
 #ifdef RLIMIT_MEMLOCK
     set_one_rlimit(&conf->rlimit_memlock, RLIMIT_MEMLOCK, "RLIMIT_MEMLOCK");
 #endif
+    set_one_rlimit(&conf->rlimit_as, RLIMIT_AS, "RLIMIT_AS");
+#ifdef RLIMIT_LOCKS
+    set_one_rlimit(&conf->rlimit_locks, RLIMIT_LOCKS, "RLIMIT_LOCKS");
+#endif
+#ifdef RLIMIT_SIGPENDING
+    set_one_rlimit(&conf->rlimit_sigpending, RLIMIT_SIGPENDING, "RLIMIT_SIGPENDING");
+#endif
+#ifdef RLIMIT_MSGQUEUE
+    set_one_rlimit(&conf->rlimit_msgqueue, RLIMIT_MSGQUEUE, "RLIMIT_MSGQUEUE");
+#endif
 #ifdef RLIMIT_NICE
     set_one_rlimit(&conf->rlimit_nice, RLIMIT_NICE, "RLIMIT_NICE");
 #endif
 #ifdef RLIMIT_RTPRIO
     set_one_rlimit(&conf->rlimit_rtprio, RLIMIT_RTPRIO, "RLIMIT_RTPRIO");
 #endif
+#ifdef RLIMIT_RTTIME
+    set_one_rlimit(&conf->rlimit_rttime, RLIMIT_RTTIME, "RLIMIT_RTTIME");
+#endif
 }
 #endif
 
@@ -329,19 +323,20 @@ int main(int argc, char *argv[]) {
     pa_mainloop *mainloop = NULL;
     char *s;
     int r = 0, retval = 1, d = 0;
-    int daemon_pipe[2] = { -1, -1 };
     pa_bool_t suid_root, real_root;
-    int valid_pid_file = 0;
+    pa_bool_t valid_pid_file = FALSE;
     gid_t gid = (gid_t) -1;
-    pa_bool_t allow_realtime, allow_high_priority;
     pa_bool_t ltdl_init = FALSE;
-
+    int passed_fd = -1;
+    const char *e;
+#ifdef HAVE_FORK
+    int daemon_pipe[2] = { -1, -1 };
+#endif
 #ifdef OS_IS_WIN32
-    pa_time_event *timer;
-    struct timeval tv;
+    pa_time_event *win32_timer;
+    struct timeval win32_tv;
 #endif
 
-
 #if defined(__linux__) && defined(__OPTIMIZE__)
     /*
        Disable lazy relocations to make usage of external libraries
@@ -355,7 +350,7 @@ int main(int argc, char *argv[]) {
         /* We have to execute ourselves, because the libc caches the
          * value of $LD_BIND_NOW on initialization. */
 
-        putenv(pa_xstrdup("LD_BIND_NOW=1"));
+        pa_set_env("LD_BIND_NOW", "1");
         pa_assert_se(rp = pa_readlink("/proc/self/exe"));
         pa_assert_se(execv(rp, argv) == 0);
     }
@@ -385,6 +380,18 @@ int main(int argc, char *argv[]) {
          * is just too risky tun let PA run as root all the time. */
     }
 
+    if ((e = getenv("PULSE_PASSED_FD"))) {
+        passed_fd = atoi(e);
+
+        if (passed_fd <= 2)
+            passed_fd = -1;
+    }
+
+    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. */
@@ -410,67 +417,66 @@ int main(int argc, char *argv[]) {
     pa_log_set_target(conf->auto_log_target ? PA_LOG_STDERR : conf->log_target, NULL);
 
     if (suid_root) {
+        pa_bool_t allow_realtime, allow_high_priority;
+
         /* Ok, we're suid root, so let's better not enable high prio
          * or RT by default */
 
         allow_high_priority = allow_realtime = FALSE;
 
+        if (conf->high_priority || conf->realtime_scheduling)
+            if (pa_own_uid_in_group(PA_REALTIME_GROUP, &gid) > 0) {
+                pa_log_info("We're in the group '"PA_REALTIME_GROUP"', allowing real-time and high-priority scheduling.");
+                allow_realtime = conf->realtime_scheduling;
+                allow_high_priority = conf->high_priority;
+            }
+
 #ifdef HAVE_POLKIT
-        if (conf->high_priority) {
+        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 privilige.");
+                pa_log_info("PolicyKit grants us acquire-high-priority privilege.");
                 allow_high_priority = TRUE;
             } else
-                pa_log_info("PolicyKit refuses acquire-high-priority privilige.");
+                pa_log_info("PolicyKit refuses acquire-high-priority privilege.");
         }
 
-        if (conf->realtime_scheduling) {
+        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 privilige.");
+                pa_log_info("PolicyKit grants us acquire-real-time privilege.");
                 allow_realtime = TRUE;
             } else
-                pa_log_info("PolicyKit refuses acquire-real-time privilige.");
+                pa_log_info("PolicyKit refuses acquire-real-time privilege.");
         }
 #endif
 
-        if ((conf->high_priority || conf->realtime_scheduling) && pa_own_uid_in_group(PA_REALTIME_GROUP, &gid) > 0) {
-            pa_log_info("We're in the group '"PA_REALTIME_GROUP"', allowing real-time and high-priority scheduling.");
-            allow_realtime = conf->realtime_scheduling;
-            allow_high_priority = conf->high_priority;
-        }
-
         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();
-            pa_drop_root();
-            suid_root = real_root = FALSE;
+            suid_root = FALSE;
 
             if (conf->high_priority || conf->realtime_scheduling)
                 pa_log_notice("Called SUID root and real-time/high-priority scheduling was requested in the configuration. However, we lack the necessary priviliges:\n"
                               "We are not in group '"PA_REALTIME_GROUP"' and PolicyKit refuse to grant us priviliges. Dropping SUID again.\n"
                               "For enabling real-time scheduling please acquire the appropriate PolicyKit priviliges, or become a member of '"PA_REALTIME_GROUP"', or increase the RLIMIT_NICE/RLIMIT_RTPRIO resource limits for this user.");
         }
-
-    } else {
-
-        /* OK, we're a normal user, so let's allow the user evrything
-         * he asks for, it's now the kernel's job to enforce limits,
-         * not ours anymore */
-        allow_high_priority = allow_realtime = TRUE;
     }
 
-    if (conf->high_priority && !allow_high_priority) {
-        pa_log_info("High-priority scheduling enabled in configuration but now allowed by policy. Disabling forcibly.");
-        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 */
 
-    if (conf->realtime_scheduling && !allow_realtime) {
-        pa_log_info("Real-time scheduling enabled in configuration but now allowed by policy. Disabling forcibly.");
-        conf->realtime_scheduling = FALSE;
-    }
+    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.");
 
     if (conf->high_priority && conf->cmd == PA_CMD_DAEMON)
         pa_raise_priority(conf->nice_level);
@@ -482,28 +488,38 @@ int main(int argc, char *argv[]) {
 
 #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. */
 
-            const pa_rlimit rl = { 9, TRUE };
+            if (getrlimit(RLIMIT_RTPRIO, &rl) >= 0) {
 
-            if (set_one_rlimit(&rl, RLIMIT_RTPRIO, "RLIMIT_RTPRIO") >= 0) {
-                pa_log_info("Successfully increased RLIMIT_RTPRIO, giving up CAP_NICE.");
-                drop = TRUE;
-            } else
-                pa_log_warn("RLIMIT_RTPRIO failed: %s", pa_cstrerror(errno));
+                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();
-            pa_drop_root();
-            suid_root = real_root = FALSE;
+            suid_root = FALSE;
         }
     }
 
+    if (conf->realtime_scheduling && !pa_can_realtime())
+        pa_log_warn("Real-time scheduling enabled in configuration but not allowed by policy.");
+
     LTDL_SET_PRELOADED_SYMBOLS();
     pa_ltdl_init();
     ltdl_init = TRUE;
@@ -605,7 +621,7 @@ int main(int argc, char *argv[]) {
 
 #ifdef HAVE_FORK
         if (pipe(daemon_pipe) < 0) {
-            pa_log("Failed to create pipe.");
+            pa_log("pipe failed: %s", pa_cstrerror(errno));
             goto finish;
         }
 
@@ -615,20 +631,24 @@ int main(int argc, char *argv[]) {
         }
 
         if (child != 0) {
+            ssize_t n;
             /* Father */
 
             pa_assert_se(pa_close(daemon_pipe[1]) == 0);
             daemon_pipe[1] = -1;
 
-            if (pa_loop_read(daemon_pipe[0], &retval, sizeof(retval), NULL) != sizeof(retval)) {
-                pa_log("read() failed: %s", pa_cstrerror(errno));
+            if ((n = pa_loop_read(daemon_pipe[0], &retval, sizeof(retval), NULL)) != sizeof(retval)) {
+
+                if (n < 0)
+                    pa_log("read() failed: %s", pa_cstrerror(errno));
+
                 retval = 1;
             }
 
             if (retval)
-                pa_log("daemon startup failed.");
+                pa_log("Daemon startup failed.");
             else
-                pa_log_info("daemon startup successful.");
+                pa_log_info("Daemon startup successful.");
 
             goto finish;
         }
@@ -652,9 +672,9 @@ int main(int argc, char *argv[]) {
         pa_close(1);
         pa_close(2);
 
-        open("/dev/null", O_RDONLY);
-        open("/dev/null", O_WRONLY);
-        open("/dev/null", O_WRONLY);
+        pa_assert_se(open("/dev/null", O_RDONLY) == 0);
+        pa_assert_se(open("/dev/null", O_WRONLY) == 1);
+        pa_assert_se(open("/dev/null", O_WRONLY) == 2);
 #else
         FreeConsole();
 #endif
@@ -677,39 +697,32 @@ int main(int argc, char *argv[]) {
 #endif
     }
 
+    pa_set_env("PULSE_INTERNAL", "1");
     pa_assert_se(chdir("/") == 0);
     umask(0022);
 
-    if (conf->system_instance) {
+    if (conf->system_instance)
         if (change_user() < 0)
             goto finish;
-    } else if (create_runtime_dir() < 0)
-        goto finish;
+
+    pa_log_info("This is PulseAudio " PACKAGE_VERSION);
+    pa_log_info("Page size is %lu bytes", (unsigned long) PA_PAGE_SIZE);
+    pa_log_info("Using runtime directory %s.", s = pa_get_runtime_dir());
+    pa_xfree(s);
 
     if (conf->use_pid_file) {
         if (pa_pid_file_create() < 0) {
             pa_log("pa_pid_file_create() failed.");
-#ifdef HAVE_FORK
-            if (conf->daemonize)
-                pa_loop_write(daemon_pipe[1], &retval, sizeof(retval), NULL);
-#endif
             goto finish;
         }
 
-        valid_pid_file = 1;
+        valid_pid_file = TRUE;
     }
 
-#ifdef HAVE_SYS_RESOURCE_H
-    set_all_rlimits(conf);
-#endif
-
 #ifdef SIGPIPE
     signal(SIGPIPE, SIG_IGN);
 #endif
 
-    pa_log_info("This is PulseAudio " PACKAGE_VERSION);
-    pa_log_info("Page size is %lu bytes", (unsigned long) PA_PAGE_SIZE);
-
     if (pa_rtclock_hrtimer())
         pa_log_info("Fresh high-resolution timers available! Bon appetit!");
     else
@@ -738,11 +751,11 @@ int main(int argc, char *argv[]) {
     c->realtime_priority = conf->realtime_priority;
     c->realtime_scheduling = !!conf->realtime_scheduling;
     c->disable_remixing = !!conf->disable_remixing;
+    c->running_as_daemon = !!conf->daemonize;
 
     pa_assert_se(pa_signal_init(pa_mainloop_get_api(mainloop)) == 0);
     pa_signal_new(SIGINT, signal_callback, c);
     pa_signal_new(SIGTERM, signal_callback, c);
-
 #ifdef SIGUSR1
     pa_signal_new(SIGUSR1, signal_callback, c);
 #endif
@@ -754,23 +767,27 @@ int main(int argc, char *argv[]) {
 #endif
 
 #ifdef OS_IS_WIN32
-    pa_assert_se(timer = pa_mainloop_get_api(mainloop)->time_new(pa_mainloop_get_api(mainloop), pa_gettimeofday(&tv), message_cb, NULL));
+    win32_timer = pa_mainloop_get_api(mainloop)->time_new(pa_mainloop_get_api(mainloop), pa_gettimeofday(&win32_tv), message_cb, NULL);
 #endif
 
-    if (conf->daemonize)
-        c->running_as_daemon = TRUE;
-
     oil_init();
 
     if (!conf->no_cpu_limit)
         pa_assert_se(pa_cpu_limit_init(pa_mainloop_get_api(mainloop)) == 0);
 
     buf = pa_strbuf_new();
-    if (conf->default_script_file)
-        r = pa_cli_command_execute_file(c, conf->default_script_file, buf, &conf->fail);
+    if (conf->load_default_script_file) {
+        FILE *f;
+
+        if ((f = pa_daemon_conf_open_default_script_file(conf))) {
+            r = pa_cli_command_execute_file_stream(c, f, buf, &conf->fail);
+            fclose(f);
+        }
+    }
 
     if (r >= 0)
         r = pa_cli_command_execute(c, conf->script_commands, buf, &conf->fail);
+
     pa_log_error("%s", s = pa_strbuf_tostring_free(buf));
     pa_xfree(s);
 
@@ -780,53 +797,55 @@ int main(int argc, char *argv[]) {
 
     if (r < 0 && conf->fail) {
         pa_log("Failed to initialize daemon.");
-#ifdef HAVE_FORK
-        if (conf->daemonize)
-            pa_loop_write(daemon_pipe[1], &retval, sizeof(retval), NULL);
-#endif
-    } else if (!c->modules || pa_idxset_size(c->modules) == 0) {
-        pa_log("daemon startup without any loaded modules, refusing to work.");
-#ifdef HAVE_FORK
-        if (conf->daemonize)
-            pa_loop_write(daemon_pipe[1], &retval, sizeof(retval), NULL);
-#endif
-    } else {
+        goto finish;
+    }
 
-        retval = 0;
+    if (!c->modules || pa_idxset_size(c->modules) == 0) {
+        pa_log("Daemon startup without any loaded modules, refusing to work.");
+        goto finish;
+    }
+
+    if (c->default_sink_name && !pa_namereg_get(c, c->default_sink_name, PA_NAMEREG_SINK, TRUE) && conf->fail) {
+        pa_log_error("Default sink name (%s) does not exist in name register.", c->default_sink_name);
+        goto finish;
+    }
 
-        if (c->default_sink_name &&
-            pa_namereg_get(c, c->default_sink_name, PA_NAMEREG_SINK, 1) == NULL) {
-            pa_log_error("%s : Default sink name (%s) does not exist in name register.", __FILE__, c->default_sink_name);
-            retval = !!conf->fail;
-        }
 
 #ifdef HAVE_FORK
-        if (conf->daemonize)
-            pa_loop_write(daemon_pipe[1], &retval, sizeof(retval), NULL);
+    if (conf->daemonize) {
+        int ok = 0;
+        pa_loop_write(daemon_pipe[1], &ok, sizeof(ok), NULL);
+    }
 #endif
 
-        if (!retval) {
-            pa_log_info("Daemon startup complete.");
-            if (pa_mainloop_run(mainloop, &retval) < 0)
-                retval = 1;
-            pa_log_info("Daemon shutdown initiated.");
-        }
-    }
+    pa_log_info("Daemon startup complete.");
+
+    retval = 0;
+    if (pa_mainloop_run(mainloop, &retval) < 0)
+        goto finish;
+
+    pa_log_info("Daemon shutdown initiated.");
+
+finish:
 
 #ifdef OS_IS_WIN32
-    pa_mainloop_get_api(mainloop)->time_free(timer);
+    if (win32_timer)
+        pa_mainloop_get_api(mainloop)->time_free(win32_timer);
 #endif
 
-    pa_core_unref(c);
+    if (c) {
+        pa_core_unref(c);
+        pa_log_info("Daemon terminated.");
+    }
 
     if (!conf->no_cpu_limit)
         pa_cpu_limit_done();
 
     pa_signal_done();
 
-    pa_log_info("Daemon terminated.");
-
-finish:
+#ifdef HAVE_FORK
+    pa_close_pipe(daemon_pipe);
+#endif
 
     if (mainloop)
         pa_mainloop_free(mainloop);
@@ -837,8 +856,6 @@ finish:
     if (valid_pid_file)
         pa_pid_file_remove();
 
-    pa_close_pipe(daemon_pipe);
-
 #ifdef OS_IS_WIN32
     WSACleanup();
 #endif
index ffa5d10..d918974 100644 (file)
@@ -30,6 +30,7 @@ pa_context_get_autoload_info_by_name;
 pa_context_get_autoload_info_list;
 pa_context_get_client_info;
 pa_context_get_client_info_list;
+pa_context_get_index;
 pa_context_get_module_info;
 pa_context_get_module_info_list;
 pa_context_get_protocol_version;
@@ -61,7 +62,11 @@ pa_context_move_sink_input_by_name;
 pa_context_move_source_output_by_index;
 pa_context_move_source_output_by_name;
 pa_context_new;
+pa_context_new_with_proplist;
 pa_context_play_sample;
+pa_context_play_sample_with_proplist;
+pa_context_proplist_remove;
+pa_context_proplist_update;
 pa_context_ref;
 pa_context_remove_autoload_by_index;
 pa_context_remove_autoload_by_name;
@@ -128,14 +133,19 @@ pa_operation_unref;
 pa_parse_sample_format;
 pa_path_get_filename;
 pa_proplist_free;
+pa_proplist_contains;
+pa_proplist_clear;
+pa_proplist_copy;
 pa_proplist_get;
 pa_proplist_gets;
 pa_proplist_iterate;
-pa_proplist_merge;
+pa_proplist_update;
 pa_proplist_new;
-pa_proplist_put;
-pa_proplist_puts;
-pa_proplist_remove;
+pa_proplist_set;
+pa_proplist_sets;
+pa_proplist_setf;
+pa_proplist_unset;
+pa_proplist_unset_many;
 pa_proplist_to_string;
 pa_sample_format_to_string;
 pa_sample_size;
@@ -174,10 +184,14 @@ pa_stream_get_sample_spec;
 pa_stream_get_state;
 pa_stream_get_time;
 pa_stream_get_timing_info;
+pa_stream_is_corked;
 pa_stream_is_suspended;
 pa_stream_new;
+pa_stream_new_with_proplist;
 pa_stream_peek;
 pa_stream_prebuf;
+pa_stream_proplist_remove;
+pa_stream_proplist_update;
 pa_stream_readable_size;
 pa_stream_ref;
 pa_stream_set_buffer_attr;
@@ -186,6 +200,7 @@ pa_stream_set_moved_callback;
 pa_stream_set_name;
 pa_stream_set_overflow_callback;
 pa_stream_set_read_callback;
+pa_stream_set_started_callback;
 pa_stream_set_state_callback;
 pa_stream_set_suspended_callback;
 pa_stream_set_underflow_callback;
@@ -221,6 +236,7 @@ pa_timeval_cmp;
 pa_timeval_diff;
 pa_timeval_load;
 pa_timeval_store;
+pa_timeval_sub;
 pa_usec_to_bytes;
 pa_utf8_filter;
 pa_utf8_to_locale;
index 6afec3b..d212abc 100644 (file)
@@ -27,6 +27,7 @@
 #endif
 
 #include <sys/types.h>
+#include <limits.h>
 #include <asoundlib.h>
 
 #include <pulse/sample.h>
@@ -35,6 +36,7 @@
 #include <pulsecore/log.h>
 #include <pulsecore/macro.h>
 #include <pulsecore/core-util.h>
+#include <pulsecore/atomic.h>
 
 #include "alsa-util.h"
 
@@ -290,16 +292,22 @@ int pa_alsa_set_hw_params(
         pa_sample_spec *ss,
         uint32_t *periods,
         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) {
 
     int ret = -1;
+    snd_pcm_uframes_t _period_size = *period_size;
+    unsigned int _periods = *periods;
     snd_pcm_uframes_t buffer_size;
     unsigned int r = ss->rate;
     unsigned int c = ss->channels;
     pa_sample_format_t f = ss->format;
     snd_pcm_hw_params_t *hwparams;
     pa_bool_t _use_mmap = use_mmap && *use_mmap;
+    pa_bool_t _use_tsched = use_tsched && *use_tsched;
+    int dir;
 
     pa_assert(pcm_handle);
     pa_assert(ss);
@@ -308,8 +316,6 @@ int pa_alsa_set_hw_params(
 
     snd_pcm_hw_params_alloca(&hwparams);
 
-    buffer_size = *periods * *period_size;
-
     if ((ret = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0)
         goto finish;
 
@@ -330,12 +336,19 @@ int pa_alsa_set_hw_params(
     } else if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
         goto finish;
 
+    if (!_use_mmap)
+        _use_tsched = FALSE;
+
     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)
         goto finish;
 
+    /* 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 (require_exact_channel_number) {
         if ((ret = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, c)) < 0)
             goto finish;
@@ -344,10 +357,32 @@ int pa_alsa_set_hw_params(
             goto finish;
     }
 
-    if ((*period_size > 0 && (ret = snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, period_size, NULL)) < 0) ||
-        (*periods > 0 && (ret = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams, &buffer_size)) < 0))
+    if (_use_tsched) {
+        _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 ((ret = snd_pcm_hw_params_set_periods_integer(pcm_handle, hwparams)) < 0)
         goto finish;
 
+    if (_periods > 0) {
+        dir = 1;
+        if ((ret = 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;
+        }
+    }
+
+    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)
         goto finish;
 
@@ -363,8 +398,8 @@ int pa_alsa_set_hw_params(
     if ((ret = snd_pcm_prepare(pcm_handle)) < 0)
         goto finish;
 
-    if ((ret = snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size)) < 0 ||
-        (ret = snd_pcm_hw_params_get_period_size(hwparams, period_size, NULL)) < 0)
+    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)
         goto finish;
 
     /* If the sample rate deviates too much, we need to resample */
@@ -373,14 +408,18 @@ int pa_alsa_set_hw_params(
     ss->channels = c;
     ss->format = f;
 
-    pa_assert(buffer_size > 0);
-    pa_assert(*period_size > 0);
-    *periods = buffer_size / *period_size;
-    pa_assert(*periods > 0);
+    pa_assert(_periods > 0);
+    pa_assert(_period_size > 0);
+
+    *periods = _periods;
+    *period_size = _period_size;
 
     if (use_mmap)
         *use_mmap = _use_mmap;
 
+    if (use_tsched)
+        *use_tsched = _use_tsched;
+
     ret = 0;
 
 finish:
@@ -388,7 +427,7 @@ finish:
     return ret;
 }
 
-int pa_alsa_set_sw_params(snd_pcm_t *pcm) {
+int pa_alsa_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min) {
     snd_pcm_sw_params_t *swparams;
     int err;
 
@@ -411,6 +450,11 @@ int pa_alsa_set_sw_params(snd_pcm_t *pcm) {
         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));
+        return err;
+    }
+
     if ((err = snd_pcm_sw_params(pcm, swparams)) < 0) {
         pa_log_warn("Unable to set sw params: %s\n", snd_strerror(err));
         return err;
@@ -477,7 +521,9 @@ snd_pcm_t *pa_alsa_open_by_device_id(
         int mode,
         uint32_t *nfrags,
         snd_pcm_uframes_t *period_size,
-        pa_bool_t *use_mmap) {
+        snd_pcm_uframes_t tsched_size,
+        pa_bool_t *use_mmap,
+        pa_bool_t *use_tsched) {
 
     int i;
     int direction = 1;
@@ -526,7 +572,11 @@ snd_pcm_t *pa_alsa_open_by_device_id(
         d = pa_sprintf_malloc("%s:%s", device_table[i].name, dev_id);
         pa_log_debug("Trying %s...", d);
 
-        if ((err = snd_pcm_open(&pcm_handle, d, mode, SND_PCM_NONBLOCK)) < 0) {
+        if ((err = snd_pcm_open(&pcm_handle, d, mode,
+                                SND_PCM_NONBLOCK|
+                                SND_PCM_NO_AUTO_RESAMPLE|
+                                SND_PCM_NO_AUTO_CHANNELS|
+                                SND_PCM_NO_AUTO_FORMAT)) < 0) {
             pa_log_info("Couldn't open PCM device %s: %s", d, snd_strerror(err));
             pa_xfree(d);
             continue;
@@ -536,7 +586,7 @@ snd_pcm_t *pa_alsa_open_by_device_id(
         try_ss.rate = ss->rate;
         try_ss.format = ss->format;
 
-        if ((err = pa_alsa_set_hw_params(pcm_handle, &try_ss, nfrags, period_size, use_mmap, TRUE)) < 0) {
+        if ((err = pa_alsa_set_hw_params(pcm_handle, &try_ss, nfrags, period_size, tsched_size, use_mmap, use_tsched, TRUE)) < 0) {
             pa_log_info("PCM device %s refused our hw parameters: %s", d, snd_strerror(err));
             pa_xfree(d);
             snd_pcm_close(pcm_handle);
@@ -550,11 +600,11 @@ snd_pcm_t *pa_alsa_open_by_device_id(
         return pcm_handle;
     }
 
-    /* OK, we didn't find any good device, so let's try the raw hw: stuff */
+    /* OK, we didn't find any good device, so let's try the raw plughw: stuff */
 
-    d = pa_sprintf_malloc("hw:%s", dev_id);
+    d = pa_sprintf_malloc("plughw:%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, use_mmap);
+    pcm_handle = pa_alsa_open_by_device_string(d, dev, ss, map, mode, nfrags, period_size, tsched_size, use_mmap, use_tsched);
     pa_xfree(d);
 
     return pcm_handle;
@@ -568,7 +618,9 @@ snd_pcm_t *pa_alsa_open_by_device_string(
         int mode,
         uint32_t *nfrags,
         snd_pcm_uframes_t *period_size,
-        pa_bool_t *use_mmap) {
+        snd_pcm_uframes_t tsched_size,
+        pa_bool_t *use_mmap,
+        pa_bool_t *use_tsched) {
 
     int err;
     char *d;
@@ -585,13 +637,16 @@ snd_pcm_t *pa_alsa_open_by_device_string(
 
     for (;;) {
 
-        if ((err = snd_pcm_open(&pcm_handle, d, mode, SND_PCM_NONBLOCK)) < 0) {
+        if ((err = snd_pcm_open(&pcm_handle, d, mode, 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", d, snd_strerror(err));
             pa_xfree(d);
             return NULL;
         }
 
-        if ((err = pa_alsa_set_hw_params(pcm_handle, ss, nfrags, period_size, use_mmap, FALSE)) < 0) {
+        if ((err = pa_alsa_set_hw_params(pcm_handle, ss, nfrags, period_size, tsched_size, use_mmap, use_tsched, FALSE)) < 0) {
 
             if (err == -EPERM) {
                 /* Hmm, some hw is very exotic, so we retry with plug, if without it didn't work */
@@ -616,8 +671,24 @@ snd_pcm_t *pa_alsa_open_by_device_string(
         *dev = d;
 
         if (ss->channels != map->channels) {
-            pa_assert_se(pa_channel_map_init_auto(map, ss->channels, PA_CHANNEL_MAP_AUX));
-            pa_channel_map_init_auto(map, ss->channels, PA_CHANNEL_MAP_ALSA);
+            if (!pa_channel_map_init_auto(map, ss->channels, PA_CHANNEL_MAP_ALSA)) {
+                unsigned c;
+                pa_channel_position_t pos;
+
+                pa_log_warn("Device has an unknown channel mapping. This is a limitation of ALSA. Synthesizing channel map.");
+
+                for (c = ss->channels; c > 0; c--)
+                    if (pa_channel_map_init_auto(map, c, PA_CHANNEL_MAP_ALSA))
+                        break;
+
+                pa_assert(c > 0);
+
+                pos = PA_CHANNEL_POSITION_AUX0;
+                for (; c < map->channels; c ++)
+                    map->map[c] = pos++;
+
+                map->channels = ss->channels;
+            }
         }
 
         return pcm_handle;
@@ -773,7 +844,7 @@ int pa_alsa_calc_mixer_map(snd_mixer_elem_t *elem, const pa_channel_map *channel
         }
 
         if ((is_mono && mono_used) || (!is_mono && alsa_channel_used[id])) {
-            pa_log_info("Channel map has duplicate channel '%s', failling back to software volume control.", pa_channel_position_to_string(channel_map->map[i]));
+            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;
         }
 
@@ -793,7 +864,275 @@ int pa_alsa_calc_mixer_map(snd_mixer_elem_t *elem, const pa_channel_map *channel
         }
     }
 
-    pa_log_info("All %u channels can be mapped to mixer channels. Using hardware volume control.", channel_map->channels);
+    pa_log_info("All %u channels can be mapped to mixer channels.", channel_map->channels);
 
     return 0;
 }
+
+void pa_alsa_0dB_playback(snd_mixer_elem_t *elem) {
+    long min, max, v;
+
+    pa_assert(elem);
+
+    /* Try to enable 0 dB if possible. If ALSA cannot do dB, then use
+     * raw volume levels and fix them to 75% */
+
+    if (snd_mixer_selem_set_playback_dB_all(elem, 0, -1) >= 0)
+        return;
+
+    if (snd_mixer_selem_set_playback_dB_all(elem, 0, 1) >= 0)
+        return;
+
+    if (snd_mixer_selem_get_playback_volume_range(elem, &min, &max) < 0)
+        return;
+
+    v = min + ((max - min) * 3) / 4; /* 75% */
+
+    if (v <= min)
+        v = max;
+
+    snd_mixer_selem_set_playback_volume_all(elem, v);
+}
+
+void pa_alsa_0dB_capture(snd_mixer_elem_t *elem) {
+    long min, max, v;
+
+    pa_assert(elem);
+
+    /* Try to enable 0 dB if possible. If ALSA cannot do dB, then use
+     * raw volume levels and fix them to 75% */
+
+    if (snd_mixer_selem_set_capture_dB_all(elem, 0, -1) >= 0)
+        return;
+
+    if (snd_mixer_selem_set_capture_dB_all(elem, 0, 1) >= 0)
+        return;
+
+    if (snd_mixer_selem_get_capture_volume_range(elem, &min, &max) < 0)
+        return;
+
+    v = min + ((max - min) * 3) / 4; /* 75% */
+
+    if (v <= min)
+        v = max;
+
+    snd_mixer_selem_set_capture_volume_all(elem, v);
+}
+
+void pa_alsa_dump(snd_pcm_t *pcm) {
+    int err;
+    snd_output_t *out;
+
+    pa_assert(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));
+    else {
+        char *s = NULL;
+        snd_output_buffer_string(out, &s);
+        pa_log_debug("snd_pcm_dump():\n%s", pa_strnull(s));
+    }
+
+    pa_assert_se(snd_output_close(out) == 0);
+}
+
+void pa_alsa_dump_status(snd_pcm_t *pcm) {
+    int err;
+    snd_output_t *out;
+    snd_pcm_status_t *status;
+
+    pa_assert(pcm);
+
+    snd_pcm_status_alloca(&status);
+
+    pa_assert_se(snd_output_buffer_open(&out) == 0);
+
+    pa_assert_se(snd_pcm_status(pcm, status) == 0);
+
+    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));
+    }
+
+    pa_assert_se(snd_output_close(out) == 0);
+}
+
+static void alsa_error_handler(const char *file, int line, const char *function, int err, const char *fmt,...) {
+    va_list ap;
+
+    va_start(ap, fmt);
+
+    pa_log_levelv_meta(PA_LOG_WARN, file, line, function, fmt, ap);
+
+    va_end(ap);
+}
+
+static pa_atomic_t n_error_handler_installed = PA_ATOMIC_INIT(0);
+
+void pa_alsa_redirect_errors_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) {
+    int r;
+
+    pa_assert_se((r = pa_atomic_dec(&n_error_handler_installed)) >= 1);
+
+    if (r == 1)
+        snd_lib_error_set_handler(NULL);
+}
+
+void pa_alsa_init_proplist(pa_proplist *p, snd_pcm_info_t *pcm_info) {
+
+    static const char * const alsa_class_table[SND_PCM_CLASS_LAST+1] = {
+        [SND_PCM_CLASS_GENERIC] = "generic",
+        [SND_PCM_CLASS_MULTI] = "multi",
+        [SND_PCM_CLASS_MODEM] = "modem",
+        [SND_PCM_CLASS_DIGITIZER] = "digitizer"
+    };
+    static const char * const class_table[SND_PCM_CLASS_LAST+1] = {
+        [SND_PCM_CLASS_GENERIC] = "sound",
+        [SND_PCM_CLASS_MULTI] = NULL,
+        [SND_PCM_CLASS_MODEM] = "modem",
+        [SND_PCM_CLASS_DIGITIZER] = NULL
+    };
+    static const char * const alsa_subclass_table[SND_PCM_SUBCLASS_LAST+1] = {
+        [SND_PCM_SUBCLASS_GENERIC_MIX] = "generic-mix",
+        [SND_PCM_SUBCLASS_MULTI_MIX] = "multi-mix"
+    };
+
+    snd_pcm_class_t class;
+    snd_pcm_subclass_t subclass;
+    const char *n, *id, *sdn;
+    char *cn = NULL, *lcn = NULL;
+    int card;
+
+    pa_assert(p);
+    pa_assert(pcm_info);
+
+    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_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 (alsa_subclass_table[subclass])
+            pa_proplist_sets(p, "alsa.subclass", alsa_subclass_table[subclass]);
+
+    if ((n = snd_pcm_info_get_name(pcm_info)))
+        pa_proplist_sets(p, "alsa.name", n);
+
+    if ((id = snd_pcm_info_get_id(pcm_info)))
+        pa_proplist_sets(p, "alsa.id", id);
+
+    pa_proplist_setf(p, "alsa.subdevice", "%u", snd_pcm_info_get_subdevice(pcm_info));
+    if ((sdn = snd_pcm_info_get_subdevice_name(pcm_info)))
+        pa_proplist_sets(p, "alsa.subdevice_name", sdn);
+
+    pa_proplist_setf(p, "alsa.device", "%u", snd_pcm_info_get_device(pcm_info));
+
+    if ((card = snd_pcm_info_get_card(pcm_info)) >= 0) {
+        pa_proplist_setf(p, "alsa.card", "%i", card);
+
+        if (snd_card_get_name(card, &cn) >= 0)
+            pa_proplist_sets(p, "alsa.card_name", cn);
+
+        if (snd_card_get_longname(card, &lcn) >= 0)
+            pa_proplist_sets(p, "alsa.long_card_name", lcn);
+    }
+
+    if (cn && n)
+        pa_proplist_setf(p, PA_PROP_DEVICE_DESCRIPTION, "%s - %s", cn, n);
+    else if (cn)
+        pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, cn);
+    else if (n)
+        pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, n);
+
+    free(lcn);
+    free(cn);
+}
+
+int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents) {
+    snd_pcm_state_t state;
+    int err;
+
+    pa_assert(pcm);
+
+    if (revents & POLLERR)
+        pa_log_warn("Got POLLERR from ALSA");
+    if (revents & POLLNVAL)
+        pa_log_warn("Got POLLNVAL from ALSA");
+    if (revents & POLLHUP)
+        pa_log_warn("Got POLLHUP from ALSA");
+
+    state = snd_pcm_state(pcm);
+    pa_log_warn("PCM state is %s", snd_pcm_state_name(state));
+
+    /* Try to recover from this error */
+
+    switch (state) {
+
+        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));
+                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));
+                return -1;
+            }
+            break;
+
+        default:
+
+            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));
+                return -1;
+            }
+            break;
+    }
+
+    return 0;
+}
+
+pa_rtpoll_item* pa_alsa_build_pollfd(snd_pcm_t *pcm, pa_rtpoll *rtpoll) {
+    int n, err;
+    struct pollfd *pollfd;
+    pa_rtpoll_item *item;
+
+    pa_assert(pcm);
+
+    if ((n = snd_pcm_poll_descriptors_count(pcm)) < 0) {
+        pa_log("snd_pcm_poll_descriptors_count() failed: %s", snd_strerror(n));
+        return NULL;
+    }
+
+    item = pa_rtpoll_item_new(rtpoll, PA_RTPOLL_NEVER, n);
+    pollfd = pa_rtpoll_item_get_pollfd(item, NULL);
+
+    if ((err = snd_pcm_poll_descriptors(pcm, pollfd, n)) < 0) {
+        pa_log("snd_pcm_poll_descriptors() failed: %s", snd_strerror(err));
+        pa_rtpoll_item_free(item);
+        return NULL;
+    }
+
+    return item;
+}
index 53d9a2f..442c264 100644 (file)
 
 #include <pulse/sample.h>
 #include <pulse/mainloop-api.h>
-
 #include <pulse/channelmap.h>
+#include <pulse/proplist.h>
+
+#include <pulsecore/rtpoll.h>
 
 typedef struct pa_alsa_fdlist pa_alsa_fdlist;
 
@@ -43,10 +45,12 @@ int pa_alsa_set_hw_params(
         pa_sample_spec *ss,
         uint32_t *periods,
         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);
 
-int pa_alsa_set_sw_params(snd_pcm_t *pcm);
+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);
@@ -59,7 +63,9 @@ snd_pcm_t *pa_alsa_open_by_device_id(
         int mode,
         uint32_t *nfrags,
         snd_pcm_uframes_t *period_size,
-        pa_bool_t *use_mmap);
+        snd_pcm_uframes_t tsched_size,
+        pa_bool_t *use_mmap,
+        pa_bool_t *use_tsched);
 
 snd_pcm_t *pa_alsa_open_by_device_string(
         const char *device,
@@ -69,8 +75,25 @@ snd_pcm_t *pa_alsa_open_by_device_string(
         int mode,
         uint32_t *nfrags,
         snd_pcm_uframes_t *period_size,
-        pa_bool_t *use_mmap);
+        snd_pcm_uframes_t tsched_size,
+        pa_bool_t *use_mmap,
+        pa_bool_t *use_tsched);
 
 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);
 
+void pa_alsa_0dB_playback(snd_mixer_elem_t *elem);
+void pa_alsa_0dB_capture(snd_mixer_elem_t *elem);
+
+void pa_alsa_dump(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_init_proplist(pa_proplist *p, snd_pcm_info_t *pcm_info);
+
+int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents);
+
+pa_rtpoll_item* pa_alsa_build_pollfd(snd_pcm_t *pcm, pa_rtpoll *rtpoll);
+
 #endif
index 14aef7c..95a72fd 100644 (file)
@@ -3,7 +3,7 @@
 /***
   This file is part of PulseAudio.
 
-  Copyright 2004-2006 Lennart Poettering
+  Copyright 2004-2008 Lennart Poettering
   Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
 
   PulseAudio is free software; you can redistribute it and/or modify
@@ -32,6 +32,7 @@
 
 #include <pulse/xmalloc.h>
 #include <pulse/util.h>
+#include <pulse/timeval.h>
 
 #include <pulsecore/core.h>
 #include <pulsecore/module.h>
@@ -46,6 +47,8 @@
 #include <pulsecore/core-error.h>
 #include <pulsecore/thread-mq.h>
 #include <pulsecore/rtpoll.h>
+#include <pulsecore/rtclock.h>
+#include <pulsecore/time-smoother.h>
 
 #include "alsa-util.h"
 #include "module-alsa-sink-symdef.h"
@@ -57,16 +60,42 @@ PA_MODULE_LOAD_ONCE(FALSE);
 PA_MODULE_USAGE(
         "sink_name=<name for the sink> "
         "device=<ALSA device> "
-        "device_id=<ALSA device id> "
+        "device_id=<ALSA card index> "
         "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?>");
+        "mmap=<enable memory mapping?> "
+        "tsched=<enable system timer based scheduling mode?> "
+        "tsched_buffer_size=<buffer size when using timer based scheduling> "
+        "tsched_buffer_watermark=<lower fill watermark> "
+        "mixer_reset=<reset hw volume and mute settings to sane defaults when falling back to software?>");
+
+static const char* const valid_modargs[] = {
+    "sink_name",
+    "device",
+    "device_id",
+    "format",
+    "rate",
+    "channels",
+    "channel_map",
+    "fragments",
+    "fragment_size",
+    "mmap",
+    "tsched",
+    "tsched_buffer_size",
+    "tsched_buffer_watermark",
+    "mixer_reset",
+    NULL
+};
 
 #define DEFAULT_DEVICE "default"
+#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC)            /* 2s */
+#define DEFAULT_TSCHED_WATERMARK_USEC (20*PA_USEC_PER_MSEC)       /* 20ms */
+#define TSCHED_MIN_SLEEP_USEC (3*PA_USEC_PER_MSEC)                /* 3ms */
+#define TSCHED_MIN_WAKEUP_USEC (3*PA_USEC_PER_MSEC)               /* 3ms */
 
 struct userdata {
     pa_core *core;
@@ -83,227 +112,404 @@ struct userdata {
     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;
 
-    size_t frame_size, fragment_size, hwbuf_size;
+    size_t frame_size, fragment_size, hwbuf_size, tsched_watermark;
     unsigned nfragments;
     pa_memchunk memchunk;
 
     char *device_name;
 
-    pa_bool_t use_mmap;
+    pa_bool_t use_mmap, use_tsched;
 
-    pa_bool_t first;
+    pa_bool_t first, after_rewind;
 
     pa_rtpoll_item *alsa_rtpoll_item;
 
     snd_mixer_selem_channel_id_t mixer_map[SND_MIXER_SCHN_LAST];
-};
 
-static const char* const valid_modargs[] = {
-    "device",
-    "device_id",
-    "sink_name",
-    "format",
-    "channels",
-    "rate",
-    "fragments",
-    "fragment_size",
-    "channel_map",
-    "mmap",
-    NULL
+    pa_smoother *smoother;
+    int64_t frame_index;
+    uint64_t since_start;
+
+    snd_pcm_sframes_t hwbuf_unused_frames;
 };
 
-static int mmap_write(struct userdata *u) {
+static void fix_tsched_watermark(struct userdata *u) {
+    size_t max_use;
+    size_t min_sleep, min_wakeup;
+    pa_assert(u);
+
+    max_use = u->hwbuf_size - u->hwbuf_unused_frames * u->frame_size;
+
+    min_sleep = pa_usec_to_bytes(TSCHED_MIN_SLEEP_USEC, &u->sink->sample_spec);
+    min_wakeup = pa_usec_to_bytes(TSCHED_MIN_WAKEUP_USEC, &u->sink->sample_spec);
+
+    if (min_sleep > max_use/2)
+        min_sleep = pa_frame_align(max_use/2, &u->sink->sample_spec);
+    if (min_sleep < u->frame_size)
+        min_sleep = u->frame_size;
+
+    if (min_wakeup > max_use/2)
+        min_wakeup = pa_frame_align(max_use/2, &u->sink->sample_spec);
+    if (min_wakeup < u->frame_size)
+        min_wakeup = u->frame_size;
+
+    if (u->tsched_watermark > max_use-min_sleep)
+        u->tsched_watermark = max_use-min_sleep;
+
+    if (u->tsched_watermark < min_wakeup)
+        u->tsched_watermark = min_wakeup;
+}
+
+static void hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*process_usec) {
+    pa_usec_t usec, wm;
+
+    pa_assert(sleep_usec);
+    pa_assert(process_usec);
+
+    pa_assert(u);
+
+    usec = pa_sink_get_requested_latency_within_thread(u->sink);
+
+    if (usec == (pa_usec_t) -1)
+        usec = pa_bytes_to_usec(u->hwbuf_size, &u->sink->sample_spec);
+
+/*     pa_log_debug("hw buffer time: %u ms", (unsigned) (usec / PA_USEC_PER_MSEC)); */
+
+    wm = pa_bytes_to_usec(u->tsched_watermark, &u->sink->sample_spec);
+
+    if (usec >= wm) {
+        *sleep_usec = usec - wm;
+        *process_usec = wm;
+    } else
+        *process_usec = *sleep_usec = usec / 2;
+
+/*     pa_log_debug("after watermark: %u ms", (unsigned) (*sleep_usec / PA_USEC_PER_MSEC)); */
+}
+
+static int try_recover(struct userdata *u, const char *call, int err) {
+    pa_assert(u);
+    pa_assert(call);
+    pa_assert(err < 0);
+
+    pa_log_debug("%s: %s", call, snd_strerror(err));
+
+    pa_assert(err != -EAGAIN);
+
+    if (err == -EPIPE)
+        pa_log_debug("%s: Buffer underrun!", call);
+
+    if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0) {
+        u->first = TRUE;
+        u->since_start = 0;
+        return 0;
+    }
+
+    pa_log("%s: %s", call, snd_strerror(err));
+    return -1;
+}
+
+static size_t check_left_to_play(struct userdata *u, snd_pcm_sframes_t n) {
+    size_t left_to_play;
+
+    if (n*u->frame_size < u->hwbuf_size)
+        left_to_play = u->hwbuf_size - (n*u->frame_size);
+    else
+        left_to_play = 0;
+
+    if (left_to_play > 0) {
+/*         pa_log_debug("%0.2f ms left to play", (double) pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) / PA_USEC_PER_MSEC); */
+    } else if (!u->first && !u->after_rewind) {
+        pa_log_info("Underrun!");
+
+        if (u->use_tsched) {
+            size_t old_watermark = u->tsched_watermark;
+
+            u->tsched_watermark *= 2;
+            fix_tsched_watermark(u);
+
+            if (old_watermark != u->tsched_watermark)
+                pa_log_notice("Increasing wakeup watermark to %0.2f ms",
+                              (double) pa_bytes_to_usec(u->tsched_watermark, &u->sink->sample_spec) / PA_USEC_PER_MSEC);
+        }
+    }
+
+    return left_to_play;
+}
+
+static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec) {
     int work_done = 0;
+    pa_usec_t max_sleep_usec, process_usec;
+    size_t left_to_play;
 
     pa_assert(u);
     pa_sink_assert_ref(u->sink);
 
+    if (u->use_tsched)
+        hw_sleep_time(u, &max_sleep_usec, &process_usec);
+
     for (;;) {
-        pa_memchunk chunk;
-        void *p;
         snd_pcm_sframes_t n;
-        int err;
-        const snd_pcm_channel_area_t *areas;
-        snd_pcm_uframes_t offset, frames;
+        int r;
 
-        if ((n = snd_pcm_avail_update(u->pcm_handle)) < 0) {
+        snd_pcm_hwsync(u->pcm_handle);
 
-            if (n == -EPIPE) {
-                pa_log_debug("snd_pcm_avail_update: Buffer underrun!");
-                u->first = TRUE;
-            }
+        /* First we determine how many samples are missing to fill the
+         * buffer up to 100% */
 
-            if ((err = snd_pcm_recover(u->pcm_handle, n, 1)) == 0)
-                continue;
+        if (PA_UNLIKELY((n = snd_pcm_avail_update(u->pcm_handle)) < 0)) {
 
-            if (err == -EAGAIN)
-                return work_done;
+            if ((r = try_recover(u, "snd_pcm_avail_update", n)) == 0)
+                continue;
 
-            pa_log("snd_pcm_avail_update: %s", snd_strerror(err));
-            return -1;
+            return r;
         }
 
-/*         pa_log("Got request for %i samples", (int) n); */
+        left_to_play = check_left_to_play(u, n);
 
-        if (n <= 0)
-            return work_done;
+        if (u->use_tsched)
 
-        frames = n;
+            /* We won't fill up the playback buffer before at least
+            * half the sleep time is over because otherwise we might
+            * ask for more data from the clients then they expect. We
+            * need to guarantee that clients only have to keep around
+            * a single hw buffer length. */
 
-        if ((err = snd_pcm_mmap_begin(u->pcm_handle, &areas, &offset, &frames)) < 0) {
+            if (pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) > max_sleep_usec/2)
+                break;
 
-            if (err == -EPIPE) {
-                pa_log_debug("snd_pcm_mmap_begin: Buffer underrun!");
-                u->first = TRUE;
-            }
+        if (PA_UNLIKELY(n <= u->hwbuf_unused_frames))
+            break;
 
-            if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0)
-                continue;
+        n -= u->hwbuf_unused_frames;
 
-            if (err == -EAGAIN)
-                return work_done;
+/*         pa_log_debug("Filling up"); */
 
-            pa_log("Failed to write data to DSP: %s", snd_strerror(err));
-            return -1;
-        }
+        for (;;) {
+            pa_memchunk chunk;
+            void *p;
+            int err;
+            const snd_pcm_channel_area_t *areas;
+            snd_pcm_uframes_t offset, frames = (snd_pcm_uframes_t) n;
 
-        /* Check these are multiples of 8 bit */
-        pa_assert((areas[0].first & 7) == 0);
-        pa_assert((areas[0].step & 7)== 0);
+/*             pa_log_debug("%lu frames to write", (unsigned long) frames); */
 
-        /* We assume a single interleaved memory buffer */
-        pa_assert((areas[0].first >> 3) == 0);
-        pa_assert((areas[0].step >> 3) == u->frame_size);
+            if (PA_UNLIKELY((err = snd_pcm_mmap_begin(u->pcm_handle, &areas, &offset, &frames)) < 0)) {
 
-        p = (uint8_t*) areas[0].addr + (offset * u->frame_size);
+                if ((r = try_recover(u, "snd_pcm_mmap_begin", err)) == 0)
+                    continue;
 
-        chunk.memblock = pa_memblock_new_fixed(u->core->mempool, p, frames * u->frame_size, 1);
-        chunk.length = pa_memblock_get_length(chunk.memblock);
-        chunk.index = 0;
+                return r;
+            }
 
-        pa_sink_render_into_full(u->sink, &chunk);
+            /* Make sure that if these memblocks need to be copied they will fit into one slot */
+            if (frames > pa_mempool_block_size_max(u->sink->core->mempool)/u->frame_size)
+                frames = pa_mempool_block_size_max(u->sink->core->mempool)/u->frame_size;
 
-        /* FIXME: Maybe we can do something to keep this memory block
-         * a little bit longer around? */
-        pa_memblock_unref_fixed(chunk.memblock);
+            /* Check these are multiples of 8 bit */
+            pa_assert((areas[0].first & 7) == 0);
+            pa_assert((areas[0].step & 7)== 0);
 
-        if ((err = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0) {
+            /* We assume a single interleaved memory buffer */
+            pa_assert((areas[0].first >> 3) == 0);
+            pa_assert((areas[0].step >> 3) == u->frame_size);
 
-            if (err == -EPIPE) {
-                pa_log_debug("snd_pcm_mmap_commit: Buffer underrun!");
-                u->first = TRUE;
-            }
+            p = (uint8_t*) areas[0].addr + (offset * u->frame_size);
 
-            if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0)
-                continue;
+            chunk.memblock = pa_memblock_new_fixed(u->core->mempool, p, frames * u->frame_size, TRUE);
+            chunk.length = pa_memblock_get_length(chunk.memblock);
+            chunk.index = 0;
 
-            if (err == -EAGAIN)
-                return work_done;
+            pa_sink_render_into_full(u->sink, &chunk);
 
-            pa_log("Failed to write data to DSP: %s", snd_strerror(err));
-            return -1;
-        }
+            /* FIXME: Maybe we can do something to keep this memory block
+             * a little bit longer around? */
+            pa_memblock_unref_fixed(chunk.memblock);
 
-        work_done = 1;
+            if (PA_UNLIKELY((err = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0)) {
 
-        if (frames >= (snd_pcm_uframes_t) n)
-            return work_done;
+                if ((r = try_recover(u, "snd_pcm_mmap_commit", err)) == 0)
+                    continue;
 
-/*         pa_log("wrote %i samples", (int) frames); */
+                return r;
+            }
+
+            work_done = 1;
+
+            u->frame_index += frames;
+            u->since_start += frames * u->frame_size;
+
+/*             pa_log_debug("wrote %lu frames", (unsigned long) frames); */
+
+            if (frames >= (snd_pcm_uframes_t) n)
+                break;
+
+            n -= frames;
+        }
     }
+
+    *sleep_usec = pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) - process_usec;
+    return work_done;
 }
 
-static int unix_write(struct userdata *u) {
-    snd_pcm_status_t *status;
+static int unix_write(struct userdata *u, pa_usec_t *sleep_usec) {
     int work_done = 0;
-
-    snd_pcm_status_alloca(&status);
+    pa_usec_t max_sleep_usec, process_usec;
+    size_t left_to_play;
 
     pa_assert(u);
     pa_sink_assert_ref(u->sink);
 
+    if (u->use_tsched)
+        hw_sleep_time(u, &max_sleep_usec, &process_usec);
+
     for (;;) {
-        void *p;
-        snd_pcm_sframes_t t;
-        ssize_t l;
-        int err;
+        snd_pcm_sframes_t n;
+        int r;
 
-        if ((err = snd_pcm_status(u->pcm_handle, status)) < 0) {
-            pa_log("Failed to query DSP status data: %s", snd_strerror(err));
-            return -1;
+        snd_pcm_hwsync(u->pcm_handle);
+
+        if (PA_UNLIKELY((n = snd_pcm_avail_update(u->pcm_handle)) < 0)) {
+
+            if ((r = try_recover(u, "snd_pcm_avail_update", n)) == 0)
+                continue;
+
+            return r;
         }
 
-        if (snd_pcm_status_get_avail_max(status)*u->frame_size >= u->hwbuf_size)
-            pa_log_debug("Buffer underrun!");
+        left_to_play = check_left_to_play(u, n);
 
-        l = snd_pcm_status_get_avail(status) * u->frame_size;
+        if (u->use_tsched)
 
-/*         pa_log("%u bytes to write", l); */
+            /* We won't fill up the playback buffer before at least
+            * half the sleep time is over because otherwise we might
+            * ask for more data from the clients then they expect. We
+            * need to guarantee that clients only have to keep around
+            * a single hw buffer length. */
 
-        if (l <= 0)
-            return work_done;
+            if (pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) > max_sleep_usec/2)
+                break;
 
-        if (u->memchunk.length <= 0)
-            pa_sink_render(u->sink, l, &u->memchunk);
+        if (PA_UNLIKELY(n <= u->hwbuf_unused_frames))
+            break;
 
-        pa_assert(u->memchunk.length > 0);
+        n -= u->hwbuf_unused_frames;
 
-        p = pa_memblock_acquire(u->memchunk.memblock);
-        t = snd_pcm_writei(u->pcm_handle, (const uint8_t*) p + u->memchunk.index, u->memchunk.length / u->frame_size);
-        pa_memblock_release(u->memchunk.memblock);
+        for (;;) {
+            snd_pcm_sframes_t frames;
+            void *p;
 
-/*         pa_log("wrote %i bytes of %u (%u)", t*u->frame_size, u->memchunk.length, l); */
+/*         pa_log_debug("%lu frames to write", (unsigned long) frames); */
 
-        pa_assert(t != 0);
+            if (u->memchunk.length <= 0)
+                pa_sink_render(u->sink, n * u->frame_size, &u->memchunk);
 
-        if (t < 0) {
+            pa_assert(u->memchunk.length > 0);
 
-            if ((t = snd_pcm_recover(u->pcm_handle, t, 1)) == 0)
-                continue;
+            frames = u->memchunk.length / u->frame_size;
+
+            if (frames > n)
+                frames = n;
+
+            p = pa_memblock_acquire(u->memchunk.memblock);
+            frames = snd_pcm_writei(u->pcm_handle, (const uint8_t*) p + u->memchunk.index, frames);
+            pa_memblock_release(u->memchunk.memblock);
 
-            if (t == -EAGAIN) {
-                pa_log_debug("EAGAIN");
-                return work_done;
-            } else {
-                pa_log("Failed to write data to DSP: %s", snd_strerror(t));
-                return -1;
+            pa_assert(frames != 0);
+
+            if (PA_UNLIKELY(frames < 0)) {
+
+                if ((r = try_recover(u, "snd_pcm_writei", n)) == 0)
+                    continue;
+
+                return r;
             }
-        }
 
-        u->memchunk.index += t * u->frame_size;
-        u->memchunk.length -= t * u->frame_size;
+            u->memchunk.index += frames * u->frame_size;
+            u->memchunk.length -= frames * u->frame_size;
 
-        if (u->memchunk.length <= 0) {
-            pa_memblock_unref(u->memchunk.memblock);
-            pa_memchunk_reset(&u->memchunk);
-        }
+            if (u->memchunk.length <= 0) {
+                pa_memblock_unref(u->memchunk.memblock);
+                pa_memchunk_reset(&u->memchunk);
+            }
+
+            work_done = 1;
+
+            u->frame_index += frames;
+            u->since_start += frames * u->frame_size;
 
-        work_done = 1;
+/*         pa_log_debug("wrote %lu frames", (unsigned long) frames); */
 
-        if (t * u->frame_size >= (unsigned) l)
-            return work_done;
+            if (frames >= n)
+                break;
+
+            n -= frames;
+        }
     }
+
+    *sleep_usec = pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) - process_usec;
+    return work_done;
 }
 
-static pa_usec_t sink_get_latency(struct userdata *u) {
-    pa_usec_t r = 0;
-    snd_pcm_status_t *status;
-    snd_pcm_sframes_t frames = 0;
+static void update_smoother(struct userdata *u) {
+    snd_pcm_sframes_t delay  = 0;
+    int64_t frames;
     int err;
+    pa_usec_t now1, now2;
+/*     struct timeval timestamp; */
+    snd_pcm_status_t *status;
 
     snd_pcm_status_alloca(&status);
 
     pa_assert(u);
     pa_assert(u->pcm_handle);
 
-    if ((err = snd_pcm_status(u->pcm_handle, status)) < 0)
-        pa_log("Failed to get delay: %s", snd_strerror(err));
-    else
-        frames = snd_pcm_status_get_delay(status);
+    /* Let's update the time smoother */
+
+    snd_pcm_hwsync(u->pcm_handle);
+    snd_pcm_avail_update(u->pcm_handle);
+
+/*     if (PA_UNLIKELY((err = snd_pcm_status(u->pcm_handle, status)) < 0)) { */
+/*         pa_log("Failed to query DSP status data: %s", snd_strerror(err)); */
+/*         return; */
+/*     } */
+
+/*     delay = snd_pcm_status_get_delay(status); */
+
+    if (PA_UNLIKELY((err = snd_pcm_delay(u->pcm_handle, &delay)) < 0)) {
+        pa_log("Failed to query DSP status data: %s", snd_strerror(err));
+        return;
+    }
+
+    frames = u->frame_index - delay;
+
+/*     pa_log_debug("frame_index = %llu, delay = %llu, p = %llu", (unsigned long long) u->frame_index, (unsigned long long) delay, (unsigned long long) frames); */
+
+/*     snd_pcm_status_get_tstamp(status, &timestamp); */
+/*     pa_rtclock_from_wallclock(&timestamp); */
+/*     now1 = pa_timeval_load(&timestamp); */
+
+    now1 = pa_rtclock_usec();
+    now2 = pa_bytes_to_usec(frames * u->frame_size, &u->sink->sample_spec);
+    pa_smoother_put(u->smoother, now1, now2);
+}
+
+static pa_usec_t sink_get_latency(struct userdata *u) {
+    pa_usec_t r = 0;
+    int64_t delay;
+    pa_usec_t now1, now2;
+
+    pa_assert(u);
+
+    now1 = pa_rtclock_usec();
+    now2 = pa_smoother_get(u->smoother, now1);
+
+    delay = (int64_t) pa_bytes_to_usec(u->frame_index * u->frame_size, &u->sink->sample_spec) - (int64_t) now2;
 
-    if (frames > 0)
-        r = pa_bytes_to_usec(frames * u->frame_size, &u->sink->sample_spec);
+    if (delay > 0)
+        r = (pa_usec_t) delay;
 
     if (u->memchunk.memblock)
         r += pa_bytes_to_usec(u->memchunk.length, &u->sink->sample_spec);
@@ -312,28 +518,14 @@ static pa_usec_t sink_get_latency(struct userdata *u) {
 }
 
 static int build_pollfd(struct userdata *u) {
-    int err;
-    struct pollfd *pollfd;
-    int n;
-
     pa_assert(u);
     pa_assert(u->pcm_handle);
 
-    if ((n = snd_pcm_poll_descriptors_count(u->pcm_handle)) < 0) {
-        pa_log("snd_pcm_poll_descriptors_count() failed: %s", snd_strerror(n));
-        return -1;
-    }
-
     if (u->alsa_rtpoll_item)
         pa_rtpoll_item_free(u->alsa_rtpoll_item);
 
-    u->alsa_rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, n);
-    pollfd = pa_rtpoll_item_get_pollfd(u->alsa_rtpoll_item, NULL);
-
-    if ((err = snd_pcm_poll_descriptors(u->pcm_handle, pollfd, n)) < 0) {
-        pa_log("snd_pcm_poll_descriptors() failed: %s", snd_strerror(err));
+    if (!(u->alsa_rtpoll_item = pa_alsa_build_pollfd(u->pcm_handle, u->rtpoll)))
         return -1;
-    }
 
     return 0;
 }
@@ -342,6 +534,8 @@ static int suspend(struct userdata *u) {
     pa_assert(u);
     pa_assert(u->pcm_handle);
 
+    pa_smoother_pause(u->smoother, pa_rtclock_usec());
+
     /* Let's suspend */
     snd_pcm_drain(u->pcm_handle);
     snd_pcm_close(u->pcm_handle);
@@ -357,10 +551,64 @@ static int suspend(struct userdata *u) {
     return 0;
 }
 
+static int update_sw_params(struct userdata *u) {
+    snd_pcm_uframes_t avail_min;
+    int err;
+
+    pa_assert(u);
+
+    /* Use the full buffer if noone asked us for anything specific */
+    u->hwbuf_unused_frames = 0;
+
+    if (u->use_tsched) {
+        pa_usec_t latency;
+
+        if ((latency = pa_sink_get_requested_latency_within_thread(u->sink)) != (pa_usec_t) -1) {
+            size_t b;
+
+            pa_log_debug("latency set to %0.2f", (double) latency / PA_USEC_PER_MSEC);
+
+            b = pa_usec_to_bytes(latency, &u->sink->sample_spec);
+
+            /* We need at least one sample in our buffer */
+
+            if (PA_UNLIKELY(b < u->frame_size))
+                b = u->frame_size;
+
+            u->hwbuf_unused_frames =
+                PA_LIKELY(b < u->hwbuf_size) ?
+                ((u->hwbuf_size - b) / u->frame_size) : 0;
+
+            fix_tsched_watermark(u);
+        }
+    }
+
+    pa_log_debug("hwbuf_unused_frames=%lu", (unsigned long) u->hwbuf_unused_frames);
+
+    /* We need at last one frame in the used part of the buffer */
+    avail_min = u->hwbuf_unused_frames + 1;
+
+    if (u->use_tsched) {
+        pa_usec_t sleep_usec, process_usec;
+
+        hw_sleep_time(u, &sleep_usec, &process_usec);
+        avail_min += pa_usec_to_bytes(sleep_usec, &u->sink->sample_spec);
+    }
+
+    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));
+        return err;
+    }
+
+    return 0;
+}
+
 static int unsuspend(struct userdata *u) {
     pa_sample_spec ss;
     int err;
-    pa_bool_t b;
+    pa_bool_t b, d;
     unsigned nfrags;
     snd_pcm_uframes_t period_size;
 
@@ -379,13 +627,14 @@ static int unsuspend(struct userdata *u) {
     nfrags = u->nfragments;
     period_size = u->fragment_size / u->frame_size;
     b = u->use_mmap;
+    d = u->use_tsched;
 
-    if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &nfrags, &period_size, &b, TRUE)) < 0) {
+    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));
         goto fail;
     }
 
-    if (b != u->use_mmap) {
+    if (b != u->use_mmap || d != u->use_tsched) {
         pa_log_warn("Resume failed, couldn't get original access mode.");
         goto fail;
     }
@@ -400,10 +649,8 @@ static int unsuspend(struct userdata *u) {
         goto fail;
     }
 
-    if ((err = pa_alsa_set_sw_params(u->pcm_handle)) < 0) {
-        pa_log("Failed to set software parameters: %s", snd_strerror(err));
+    if (update_sw_params(u) < 0)
         goto fail;
-    }
 
     if (build_pollfd(u) < 0)
         goto fail;
@@ -411,6 +658,7 @@ static int unsuspend(struct userdata *u) {
     /* FIXME: We need to reload the volume somehow */
 
     u->first = TRUE;
+    u->since_start = 0;
 
     pa_log_info("Resumed successfully...");
 
@@ -446,7 +694,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
             switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) {
 
                 case PA_SINK_SUSPENDED:
-                    pa_assert(PA_SINK_OPENED(u->sink->thread_info.state));
+                    pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state));
 
                     if (suspend(u) < 0)
                         return -1;
@@ -505,18 +753,24 @@ static int sink_get_volume_cb(pa_sink *s) {
     pa_assert(u->mixer_elem);
 
     for (i = 0; i < s->sample_spec.channels; i++) {
-        long set_vol, vol;
+        long alsa_vol;
 
         pa_assert(snd_mixer_selem_has_playback_channel(u->mixer_elem, u->mixer_map[i]));
 
-        if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, u->mixer_map[i], &vol)) < 0)
-            goto fail;
+        if (u->hw_dB_supported) {
 
-        set_vol = (long) roundf(((float) s->volume.values[i] * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min;
+            if ((err = snd_mixer_selem_get_playback_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol)) >= 0) {
+                s->volume.values[i] = pa_sw_volume_from_dB(alsa_vol / 100.0);
+                continue;
+            }
+
+            u->hw_dB_supported = FALSE;
+        }
+
+        if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0)
+            goto fail;
 
-        /* Try to avoid superfluous volume changes */
-        if (set_vol != vol)
-            s->volume.values[i] = (pa_volume_t) roundf(((float) (vol - u->hw_volume_min) * PA_VOLUME_NORM) / (u->hw_volume_max - u->hw_volume_min));
+        s->volume.values[i] = (pa_volume_t) roundf(((float) (alsa_vol - u->hw_volume_min) * PA_VOLUME_NORM) / (u->hw_volume_max - u->hw_volume_min));
     }
 
     return 0;
@@ -524,8 +778,6 @@ static int sink_get_volume_cb(pa_sink *s) {
 fail:
     pa_log_error("Unable to read volume: %s", snd_strerror(err));
 
-    s->get_volume = NULL;
-    s->set_volume = NULL;
     return -1;
 }
 
@@ -543,15 +795,32 @@ static int sink_set_volume_cb(pa_sink *s) {
 
         pa_assert(snd_mixer_selem_has_playback_channel(u->mixer_elem, u->mixer_map[i]));
 
-        vol = s->volume.values[i];
+        vol = PA_MIN(s->volume.values[i], PA_VOLUME_NORM);
 
-        if (vol > PA_VOLUME_NORM)
-            vol = PA_VOLUME_NORM;
+        if (u->hw_dB_supported) {
+            alsa_vol = (long) (pa_sw_volume_to_dB(vol) * 100);
+            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) {
+
+                if (snd_mixer_selem_get_playback_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol) >= 0)
+                    s->volume.values[i] = pa_sw_volume_from_dB(alsa_vol / 100.0);
+
+                continue;
+            }
+
+            u->hw_dB_supported = FALSE;
+
+        }
 
         alsa_vol = (long) roundf(((float) vol * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min;
+        alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_volume_min, u->hw_volume_max);
 
         if ((err = snd_mixer_selem_set_playback_volume(u->mixer_elem, u->mixer_map[i], alsa_vol)) < 0)
             goto fail;
+
+        if (snd_mixer_selem_get_playback_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol) >= 0)
+            s->volume.values[i] = (pa_volume_t) roundf(((float) (alsa_vol - u->hw_volume_min) * PA_VOLUME_NORM) / (u->hw_volume_max - u->hw_volume_min));
     }
 
     return 0;
@@ -559,8 +828,6 @@ static int sink_set_volume_cb(pa_sink *s) {
 fail:
     pa_log_error("Unable to set volume: %s", snd_strerror(err));
 
-    s->get_volume = NULL;
-    s->set_volume = NULL;
     return -1;
 }
 
@@ -573,9 +840,6 @@ static int sink_get_mute_cb(pa_sink *s) {
 
     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));
-
-        s->get_mute = NULL;
-        s->set_mute = NULL;
         return -1;
     }
 
@@ -593,12 +857,90 @@ static int sink_set_mute_cb(pa_sink *s) {
 
     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 -1;
+    }
+
+    return 0;
+}
 
-        s->get_mute = NULL;
-        s->set_mute = NULL;
+static void sink_update_requested_latency_cb(pa_sink *s) {
+    struct userdata *u = s->userdata;
+    snd_pcm_sframes_t before;
+    pa_assert(u);
+
+    if (!u->pcm_handle)
+        return;
+
+    before = u->hwbuf_unused_frames;
+    update_sw_params(u);
+
+    /* 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
+    current fill level. Thus, let's do a full rewind once, to clear
+    things up. */
+
+    if (u->hwbuf_unused_frames > before) {
+        pa_log_debug("Requesting rewind due to latency change.");
+        pa_sink_request_rewind(s, 0);
+    }
+}
+
+static int process_rewind(struct userdata *u) {
+    snd_pcm_sframes_t unused;
+    size_t rewind_nbytes, unused_nbytes, limit_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;
+
+    pa_assert(rewind_nbytes > 0);
+    pa_log_debug("Requested to rewind %lu bytes.", (unsigned long) rewind_nbytes);
+
+    snd_pcm_hwsync(u->pcm_handle);
+    if ((unused = snd_pcm_avail_update(u->pcm_handle)) < 0) {
+        pa_log("snd_pcm_avail_update() failed: %s", snd_strerror(unused));
         return -1;
     }
 
+    unused_nbytes = u->tsched_watermark + (size_t) unused * u->frame_size;
+
+    if (u->hwbuf_size > unused_nbytes)
+        limit_nbytes = u->hwbuf_size - unused_nbytes;
+    else
+        limit_nbytes = 0;
+
+    if (rewind_nbytes > limit_nbytes)
+        rewind_nbytes = limit_nbytes;
+
+    if (rewind_nbytes > 0) {
+        snd_pcm_sframes_t in_frames, out_frames;
+
+        pa_log_debug("Limited to %lu bytes.", (unsigned long) rewind_nbytes);
+
+        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, in_frames)) < 0) {
+            pa_log("snd_pcm_rewind() failed: %s", snd_strerror(out_frames));
+            return -1;
+        }
+        pa_log_debug("after: %lu", (unsigned long) out_frames);
+
+        rewind_nbytes = out_frames * u->frame_size;
+
+        if (rewind_nbytes <= 0)
+            pa_log_info("Tried rewind, but was apparently not possible.");
+        else {
+            u->frame_index -= out_frames;
+            pa_log_debug("Rewound %lu bytes.", (unsigned long) rewind_nbytes);
+            pa_sink_process_rewind(u->sink, rewind_nbytes);
+
+            u->after_rewind = TRUE;
+        }
+    } else
+        pa_log_debug("Mhmm, actually there is nothing to rewind.");
+
     return 0;
 }
 
@@ -618,25 +960,77 @@ static void thread_func(void *userdata) {
     for (;;) {
         int ret;
 
+/*         pa_log_debug("loop"); */
+
         /* Render some data and write it to the dsp */
-        if (PA_SINK_OPENED(u->sink->thread_info.state)) {
-            int work_done = 0;
+        if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) {
+            int work_done;
+            pa_usec_t sleep_usec;
 
-            if (u->use_mmap) {
-                if ((work_done = mmap_write(u)) < 0)
-                    goto fail;
-            } else {
-                if ((work_done = unix_write(u)) < 0)
+            if (u->sink->thread_info.rewind_nbytes > 0)
+                if (process_rewind(u) < 0)
                     goto fail;
+
+            if (u->use_mmap)
+                work_done = mmap_write(u, &sleep_usec);
+            else
+                work_done = unix_write(u, &sleep_usec);
+
+            if (work_done < 0)
+                goto fail;
+
+/*             pa_log_debug("work_done = %i", work_done); */
+
+            if (work_done) {
+
+                if (u->first) {
+                    pa_log_info("Starting playback.");
+                    snd_pcm_start(u->pcm_handle);
+
+                    pa_smoother_resume(u->smoother, pa_rtclock_usec());
+                }
+
+                update_smoother(u);
             }
 
-            if (work_done && u->first) {
-                pa_log_info("Starting playback.");
-                snd_pcm_start(u->pcm_handle);
-                u->first = FALSE;
-                continue;
+            if (u->use_tsched) {
+                pa_usec_t cusec;
+
+                if (u->since_start <= u->hwbuf_size) {
+
+                    /* 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
+                     * we artificially decrease the sleep time until
+                     * we have filled the buffer at least once
+                     * completely.*/
+
+                    /*pa_log_debug("Cutting sleep time for the initial iterations by half.");*/
+                    sleep_usec /= 2;
+                }
+
+                /* OK, the playback buffer is now full, let's
+                 * calculate when to wake up next */
+/*                 pa_log_debug("Waking up in %0.2fms (sound card clock).", (double) sleep_usec / PA_USEC_PER_MSEC); */
+
+                /* Convert from the sound card time domain to the
+                 * system time domain */
+                cusec = pa_smoother_translate(u->smoother, pa_rtclock_usec(), sleep_usec);
+
+/*                 pa_log_debug("Waking up in %0.2fms (system clock).", (double) cusec / PA_USEC_PER_MSEC); */
+
+                /* We don't trust the conversion, so we wake up whatever comes first */
+                pa_rtpoll_set_timer_relative(u->rtpoll, PA_MIN(sleep_usec, cusec));
             }
-        }
+
+            u->first = FALSE;
+            u->after_rewind = FALSE;
+
+        } else if (u->use_tsched)
+
+            /* OK, we're in an invalid state, let's disable our timers */
+            pa_rtpoll_set_timer_disabled(u->rtpoll);
 
         /* Hmm, nothing to do. Let's sleep */
         if ((ret = pa_rtpoll_run(u->rtpoll, 1)) < 0)
@@ -646,7 +1040,7 @@ static void thread_func(void *userdata) {
             goto finish;
 
         /* Tell ALSA about this and process its response */
-        if (PA_SINK_OPENED(u->sink->thread_info.state)) {
+        if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) {
             struct pollfd *pollfd;
             unsigned short revents = 0;
             int err;
@@ -660,43 +1054,15 @@ static void thread_func(void *userdata) {
             }
 
             if (revents & (POLLERR|POLLNVAL|POLLHUP)) {
+                if (pa_alsa_recover_from_poll(u->pcm_handle, revents) < 0)
+                    goto fail;
 
-                if (revents & POLLERR)
-                    pa_log_warn("Got POLLERR from ALSA");
-                if (revents & POLLNVAL)
-                    pa_log_warn("Got POLLNVAL from ALSA");
-                if (revents & POLLHUP)
-                    pa_log_warn("Got POLLHUP from ALSA");
-
-                /* Try to recover from this error */
-
-                switch (snd_pcm_state(u->pcm_handle)) {
-
-                    case SND_PCM_STATE_XRUN:
-                        if ((err = snd_pcm_recover(u->pcm_handle, -EPIPE, 1)) != 0) {
-                            pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and XRUN: %s", snd_strerror(err));
-                            goto fail;
-                        }
-                        break;
-
-                    case SND_PCM_STATE_SUSPENDED:
-                        if ((err = snd_pcm_recover(u->pcm_handle, -ESTRPIPE, 1)) != 0) {
-                            pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and SUSPENDED: %s", snd_strerror(err));
-                            goto fail;
-                        }
-                        break;
-
-                    default:
-
-                        snd_pcm_drop(u->pcm_handle);
-
-                        if ((err = snd_pcm_prepare(u->pcm_handle)) < 0) {
-                            pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP with snd_pcm_prepare(): %s", snd_strerror(err));
-                            goto fail;
-                        }
-                        break;
-                }
+                u->first = TRUE;
+                u->since_start = 0;
             }
+
+            if (revents && u->use_tsched)
+                pa_log_debug("Wakeup from ALSA! (%i)", revents);
         }
     }
 
@@ -717,21 +1083,24 @@ int pa__init(pa_module*m) {
     const char *dev_id;
     pa_sample_spec ss;
     pa_channel_map map;
-    uint32_t nfrags, frag_size;
-    snd_pcm_uframes_t period_size;
+    uint32_t nfrags, hwbuf_size, frag_size, tsched_size, tsched_watermark;
+    snd_pcm_uframes_t period_frames, tsched_frames;
     size_t frame_size;
     snd_pcm_info_t *pcm_info = NULL;
     int err;
-    char *t;
     const char *name;
     char *name_buf = NULL;
-    int namereg_fail;
-    pa_bool_t use_mmap = TRUE, b;
+    pa_bool_t namereg_fail;
+    pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, mixer_reset = TRUE;
+    pa_usec_t usec;
+    pa_sink_new_data data;
 
     snd_pcm_info_alloca(&pcm_info);
 
     pa_assert(m);
 
+    pa_alsa_redirect_errors_inc();
+
     if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
         pa_log("Failed to parse module arguments");
         goto fail;
@@ -746,35 +1115,66 @@ int pa__init(pa_module*m) {
     frame_size = pa_frame_size(&ss);
 
     nfrags = m->core->default_n_fragments;
-    frag_size = pa_usec_to_bytes(m->core->default_fragment_size_msec*1000, &ss);
+    frag_size = pa_usec_to_bytes(m->core->default_fragment_size_msec*PA_USEC_PER_MSEC, &ss);
     if (frag_size <= 0)
         frag_size = frame_size;
+    tsched_size = pa_usec_to_bytes(DEFAULT_TSCHED_BUFFER_USEC, &ss);
+    tsched_watermark = pa_usec_to_bytes(DEFAULT_TSCHED_WATERMARK_USEC, &ss);
 
-    if (pa_modargs_get_value_u32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_u32(ma, "fragment_size", &frag_size) < 0) {
+    if (pa_modargs_get_value_u32(ma, "fragments", &nfrags) < 0 ||
+        pa_modargs_get_value_u32(ma, "fragment_size", &frag_size) < 0 ||
+        pa_modargs_get_value_u32(ma, "tsched_buffer_size", &tsched_size) < 0 ||
+        pa_modargs_get_value_u32(ma, "tsched_buffer_watermark", &tsched_watermark) < 0) {
         pa_log("Failed to parse buffer metrics");
         goto fail;
     }
-    period_size = frag_size/frame_size;
+
+    hwbuf_size = frag_size * nfrags;
+    period_frames = frag_size/frame_size;
+    tsched_frames = tsched_size/frame_size;
 
     if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) {
         pa_log("Failed to parse mmap argument.");
         goto fail;
     }
 
+    if (pa_modargs_get_value_boolean(ma, "tsched", &use_tsched) < 0) {
+        pa_log("Failed to parse timer_scheduling argument.");
+        goto fail;
+    }
+
+    if (use_tsched && !pa_rtclock_hrtimer()) {
+        pa_log("Disabling timer-based scheduling because high-resolution timers are not available from the kernel.");
+        use_tsched = FALSE;
+    }
+
+    if (pa_modargs_get_value_boolean(ma, "mixer_reset", &mixer_reset) < 0) {
+        pa_log("Failed to parse mixer_reset argument.");
+        goto fail;
+    }
+
     u = pa_xnew0(struct userdata, 1);
     u->core = m->core;
     u->module = m;
     m->userdata = u;
     u->use_mmap = use_mmap;
+    u->use_tsched = use_tsched;
     u->first = TRUE;
-    pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
+    u->since_start = 0;
+    u->after_rewind = FALSE;
     u->rtpoll = pa_rtpoll_new();
+    pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll);
     u->alsa_rtpoll_item = NULL;
-    pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
+
+    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);
 
     snd_config_update_free_global();
 
     b = use_mmap;
+    d = use_tsched;
 
     if ((dev_id = pa_modargs_get_value(ma, "device_id", NULL))) {
 
@@ -783,8 +1183,8 @@ int pa__init(pa_module*m) {
                       &u->device_name,
                       &ss, &map,
                       SND_PCM_STREAM_PLAYBACK,
-                      &nfrags, &period_size,
-                      &b)))
+                      &nfrags, &period_frames, tsched_frames,
+                      &b, &d)))
 
             goto fail;
 
@@ -795,8 +1195,8 @@ int pa__init(pa_module*m) {
                       &u->device_name,
                       &ss, &map,
                       SND_PCM_STREAM_PLAYBACK,
-                      &nfrags, &period_size,
-                      &b)))
+                      &nfrags, &period_frames, tsched_frames,
+                      &b, &d)))
             goto fail;
 
     }
@@ -806,22 +1206,25 @@ int pa__init(pa_module*m) {
 
     if (use_mmap && !b) {
         pa_log_info("Device doesn't support mmap(), falling back to UNIX read/write mode.");
-        u->use_mmap = use_mmap = b;
+        u->use_mmap = use_mmap = FALSE;
+    }
+
+    if (use_tsched && (!b || !d)) {
+        pa_log_info("Cannot enabled timer-based scheduling, falling back to sound IRQ scheduling.");
+        u->use_tsched = use_tsched = FALSE;
     }
 
     if (u->use_mmap)
         pa_log_info("Successfully enabled mmap() mode.");
 
+    if (u->use_tsched)
+        pa_log_info("Successfully enabled timer-based scheduling mode.");
+
     if ((err = snd_pcm_info(u->pcm_handle, pcm_info)) < 0) {
         pa_log("Error fetching PCM info: %s", snd_strerror(err));
         goto fail;
     }
 
-    if ((err = pa_alsa_set_sw_params(u->pcm_handle)) < 0) {
-        pa_log("Failed to set software parameters: %s", snd_strerror(err));
-        goto fail;
-    }
-
     /* ALSA might tweak the sample spec, so recalculate the frame size */
     frame_size = pa_frame_size(&ss);
 
@@ -833,13 +1236,24 @@ int pa__init(pa_module*m) {
         if (pa_alsa_prepare_mixer(u->mixer_handle, u->device_name) >= 0)
             found = TRUE;
         else {
-            char *md = pa_sprintf_malloc("hw:%s", dev_id);
+            snd_pcm_info_t *info;
+
+            snd_pcm_info_alloca(&info);
 
-            if (strcmp(u->device_name, md))
-                if (pa_alsa_prepare_mixer(u->mixer_handle, md) >= 0)
-                    found = TRUE;
+            if (snd_pcm_info(u->pcm_handle, info) >= 0) {
+                char *md;
+                int card;
 
-            pa_xfree(md);
+                if ((card = snd_pcm_info_get_card(info)) >= 0) {
+
+                    md = pa_sprintf_malloc("hw:%i", card);
+
+                    if (strcmp(u->device_name, md))
+                        if (pa_alsa_prepare_mixer(u->mixer_handle, md) >= 0)
+                            found = TRUE;
+                    pa_xfree(md);
+                }
+            }
         }
 
         if (found)
@@ -853,13 +1267,28 @@ int pa__init(pa_module*m) {
     }
 
     if ((name = pa_modargs_get_value(ma, "sink_name", NULL)))
-        namereg_fail = 1;
+        namereg_fail = TRUE;
     else {
         name = name_buf = pa_sprintf_malloc("alsa_output.%s", u->device_name);
-        namereg_fail = 0;
+        namereg_fail = FALSE;
     }
 
-    u->sink = pa_sink_new(m->core, __FILE__, name, namereg_fail, &ss, &map);
+    pa_sink_new_data_init(&data);
+    data.driver = __FILE__;
+    data.module = m;
+    pa_sink_new_data_set_name(&data, name);
+    data.namereg_fail = namereg_fail;
+    pa_sink_new_data_set_sample_spec(&data, &ss);
+    pa_sink_new_data_set_channel_map(&data, &map);
+
+    pa_alsa_init_proplist(data.proplist, pcm_info);
+    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->device_name);
+    pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (period_frames * frame_size * nfrags));
+    pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_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"));
+
+    u->sink = pa_sink_new(m->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY);
+    pa_sink_new_data_done(&data);
     pa_xfree(name_buf);
 
     if (!u->sink) {
@@ -868,26 +1297,41 @@ int pa__init(pa_module*m) {
     }
 
     u->sink->parent.process_msg = sink_process_msg;
+    u->sink->update_requested_latency = sink_update_requested_latency_cb;
     u->sink->userdata = u;
 
-    pa_sink_set_module(u->sink, m);
     pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
     pa_sink_set_rtpoll(u->sink, u->rtpoll);
-    pa_sink_set_description(u->sink, t = pa_sprintf_malloc(
-                                    "ALSA PCM on %s (%s)%s",
-                                    u->device_name,
-                                    snd_pcm_info_get_name(pcm_info),
-                                    use_mmap ? " via DMA" : ""));
-    pa_xfree(t);
-
-    u->sink->flags = PA_SINK_HARDWARE|PA_SINK_LATENCY;
 
     u->frame_size = frame_size;
-    u->fragment_size = frag_size = period_size * frame_size;
+    u->fragment_size = frag_size = period_frames * frame_size;
     u->nfragments = nfrags;
     u->hwbuf_size = u->fragment_size * nfrags;
-
-    pa_log_info("Using %u fragments of size %lu bytes.", nfrags, (long unsigned) u->fragment_size);
+    u->hwbuf_unused_frames = 0;
+    u->tsched_watermark = tsched_watermark;
+    u->frame_index = 0;
+    u->hw_dB_supported = FALSE;
+    u->hw_dB_min = u->hw_dB_max = 0;
+    u->hw_volume_min = u->hw_volume_max = 0;
+
+    if (use_tsched)
+        fix_tsched_watermark(u);
+
+    u->sink->thread_info.max_rewind = use_tsched ? u->hwbuf_size : 0;
+    u->sink->max_latency = pa_bytes_to_usec(u->hwbuf_size, &ss);
+    if (!use_tsched)
+        u->sink->min_latency = u->sink->max_latency;
+
+    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 (use_tsched)
+        pa_log_info("Time scheduling watermark is %0.2fms",
+                    (double) pa_bytes_to_usec(u->tsched_watermark, &ss) / PA_USEC_PER_MSEC);
+
+    if (update_sw_params(u) < 0)
+        goto fail;
 
     pa_memchunk_reset(&u->memchunk);
 
@@ -895,17 +1339,74 @@ int pa__init(pa_module*m) {
         pa_assert(u->mixer_elem);
 
         if (snd_mixer_selem_has_playback_volume(u->mixer_elem))
-            if (pa_alsa_calc_mixer_map(u->mixer_elem, &map, u->mixer_map, TRUE) >= 0) {
-                u->sink->get_volume = sink_get_volume_cb;
-                u->sink->set_volume = sink_set_volume_cb;
-                snd_mixer_selem_get_playback_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max);
-                u->sink->flags |= PA_SINK_HW_VOLUME_CTRL;
+
+            if (pa_alsa_calc_mixer_map(u->mixer_elem, &map, u->mixer_map, TRUE) >= 0 &&
+                snd_mixer_selem_get_playback_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max) >= 0) {
+
+                pa_bool_t suitable = TRUE;
+
+                pa_log_info("Volume ranges from %li to %li.", u->hw_volume_min, u->hw_volume_max);
+
+                if (u->hw_volume_min > u->hw_volume_max) {
+
+                    pa_log_info("Minimal volume %li larger than maximum volume %li. Strange stuff Falling back to software volume control.", u->hw_volume_min, u->hw_volume_max);
+                    suitable = FALSE;
+
+                } else if (u->hw_volume_max - u->hw_volume_min < 3) {
+
+                    pa_log_info("Device has less than 4 volume levels. Falling back to software volume control.");
+                    suitable = FALSE;
+
+                } else if (snd_mixer_selem_get_playback_dB_range(u->mixer_elem, &u->hw_dB_min, &u->hw_dB_max) >= 0) {
+
+                    /* u->hw_dB_max = 0; u->hw_dB_min = -3000; Use this to make valgrind shut up */
+
+                    pa_log_info("Volume ranges from %0.2f dB to %0.2f dB.", u->hw_dB_min/100.0, u->hw_dB_max/100.0);
+
+                    /* Let's see if this thing actually is useful for muting */
+                    if (u->hw_dB_min > -6000) {
+                        pa_log_info("Device cannot attenuate for more than -60 dB (only %0.2f dB supported), falling back to software volume control.", ((double) u->hw_dB_min) / 100);
+
+                        suitable = FALSE;
+                    } else if (u->hw_dB_max < 0) {
+
+                        pa_log_info("Device is still attenuated at maximum volume setting (%0.2f dB is maximum). Strange stuff. Falling back to software volume control.", ((double) u->hw_dB_max) / 100);
+                        suitable = FALSE;
+
+                    } else if (u->hw_dB_min >= u->hw_dB_max) {
+
+                        pa_log_info("Minimal dB (%0.2f) larger or equal to maximum dB (%0.2f). Strange stuff. Falling back to software volume control.", ((double) u->hw_dB_min) / 100, ((double) u->hw_dB_max) / 100);
+                        suitable = FALSE;
+
+                    } else {
+
+                        if (u->hw_dB_max > 0) {
+                            /* dB > 0 means overamplification, and clipping, we don't want that here */
+                            pa_log_info("Device can do overamplification for %0.2f dB. Limiting to 0 db", ((double) u->hw_dB_max) / 100);
+                            u->hw_dB_max = 0;
+                        }
+
+                        u->hw_dB_supported = TRUE;
+                    }
+                }
+
+                if (suitable) {
+                    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");
+
+                } else if (mixer_reset) {
+                    pa_log_info("Using software volume control. Trying to reset sound card to 0 dB.");
+                    pa_alsa_0dB_playback(u->mixer_elem);
+                } else
+                    pa_log_info("Using software volume control. Leaving hw mixer controls untouched.");
             }
 
         if (snd_mixer_selem_has_playback_switch(u->mixer_elem)) {
             u->sink->get_mute = sink_get_mute_cb;
             u->sink->set_mute = sink_set_mute_cb;
-            u->sink->flags |= PA_SINK_HW_VOLUME_CTRL;
+            u->sink->flags |= PA_SINK_HW_MUTE_CTRL;
         }
 
         u->mixer_fdl = pa_alsa_fdlist_new();
@@ -920,16 +1421,29 @@ int pa__init(pa_module*m) {
     } else
         u->mixer_fdl = NULL;
 
+    pa_alsa_dump(u->pcm_handle);
+
     if (!(u->thread = pa_thread_new(thread_func, u))) {
         pa_log("Failed to create thread.");
         goto fail;
     }
 
     /* Get initial mixer settings */
-    if (u->sink->get_volume)
-        u->sink->get_volume(u->sink);
-    if (u->sink->get_mute)
-        u->sink->get_mute(u->sink);
+    if (data.volume_is_set) {
+        if (u->sink->set_volume)
+            u->sink->set_volume(u->sink);
+    } else {
+        if (u->sink->get_volume)
+            u->sink->get_volume(u->sink);
+    }
+
+    if (data.muted_is_set) {
+        if (u->sink->set_mute)
+            u->sink->set_mute(u->sink);
+    } else {
+        if (u->sink->get_mute)
+            u->sink->get_mute(u->sink);
+    }
 
     pa_sink_put(u->sink);
 
@@ -952,8 +1466,10 @@ void pa__done(pa_module*m) {
 
     pa_assert(m);
 
-    if (!(u = m->userdata))
+    if (!(u = m->userdata)) {
+        pa_alsa_redirect_errors_dec();
         return;
+    }
 
     if (u->sink)
         pa_sink_unlink(u->sink);
@@ -988,8 +1504,13 @@ void pa__done(pa_module*m) {
         snd_pcm_close(u->pcm_handle);
     }
 
+    if (u->smoother)
+        pa_smoother_free(u->smoother);
+
     pa_xfree(u->device_name);
     pa_xfree(u);
 
     snd_config_update_free_global();
+
+    pa_alsa_redirect_errors_dec();
 }
index 23a2f92..e309010 100644 (file)
@@ -3,7 +3,7 @@
 /***
   This file is part of PulseAudio.
 
-  Copyright 2004-2006 Lennart Poettering
+  Copyright 2004-2008 Lennart Poettering
   Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
 
   PulseAudio is free software; you can redistribute it and/or modify
@@ -32,6 +32,7 @@
 
 #include <pulse/xmalloc.h>
 #include <pulse/util.h>
+#include <pulse/timeval.h>
 
 #include <pulsecore/core-error.h>
 #include <pulsecore/core.h>
@@ -47,6 +48,8 @@
 #include <pulsecore/core-error.h>
 #include <pulsecore/thread-mq.h>
 #include <pulsecore/rtpoll.h>
+#include <pulsecore/time-smoother.h>
+#include <pulsecore/rtclock.h>
 
 #include "alsa-util.h"
 #include "module-alsa-source-symdef.h"
@@ -58,16 +61,42 @@ PA_MODULE_LOAD_ONCE(FALSE);
 PA_MODULE_USAGE(
         "source_name=<name for the source> "
         "device=<ALSA device> "
-        "device_id=<ALSA device id> "
+        "device_id=<ALSA card index> "
         "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?>");
+        "mmap=<enable memory mapping?> "
+        "tsched=<enable system timer based scheduling mode?> "
+        "tsched_buffer_size=<buffer size when using timer based scheduling> "
+        "tsched_buffer_watermark=<upper fill watermark> "
+        "mixer_reset=<reset hw volume and mute settings to sane defaults when falling back to software?>");
+
+static const char* const valid_modargs[] = {
+    "source_name",
+    "device",
+    "device_id",
+    "format",
+    "rate",
+    "channels",
+    "channel_map",
+    "fragments",
+    "fragment_size",
+    "mmap",
+    "tsched",
+    "tsched_buffer_size",
+    "tsched_buffer_watermark",
+    "mixer_reset",
+    NULL
+};
 
 #define DEFAULT_DEVICE "default"
+#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC)       /* 2s */
+#define DEFAULT_TSCHED_WATERMARK_USEC (20*PA_USEC_PER_MSEC)  /* 20ms */
+#define TSCHED_MIN_SLEEP_USEC (3*PA_USEC_PER_MSEC)           /* 3ms */
+#define TSCHED_MIN_WAKEUP_USEC (3*PA_USEC_PER_MSEC)          /* 3ms */
 
 struct userdata {
     pa_core *core;
@@ -84,244 +113,364 @@ struct userdata {
     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;
 
-    size_t frame_size, fragment_size, hwbuf_size;
+    size_t frame_size, fragment_size, hwbuf_size, tsched_watermark;
     unsigned nfragments;
 
     char *device_name;
 
-    pa_bool_t use_mmap;
+    pa_bool_t use_mmap, use_tsched;
 
     pa_rtpoll_item *alsa_rtpoll_item;
 
     snd_mixer_selem_channel_id_t mixer_map[SND_MIXER_SCHN_LAST];
-};
 
-static const char* const valid_modargs[] = {
-    "device",
-    "device_id",
-    "source_name",
-    "channels",
-    "rate",
-    "format",
-    "fragments",
-    "fragment_size",
-    "channel_map",
-    "mmap",
-    NULL
+    pa_smoother *smoother;
+    int64_t frame_index;
+
+    snd_pcm_sframes_t hwbuf_unused_frames;
 };
 
-static int mmap_read(struct userdata *u) {
+static void fix_tsched_watermark(struct userdata *u) {
+    size_t max_use;
+    size_t min_sleep, min_wakeup;
+    pa_assert(u);
+
+    max_use = u->hwbuf_size - u->hwbuf_unused_frames * u->frame_size;
+
+    min_sleep = pa_usec_to_bytes(TSCHED_MIN_SLEEP_USEC, &u->source->sample_spec);
+    min_wakeup = pa_usec_to_bytes(TSCHED_MIN_WAKEUP_USEC, &u->source->sample_spec);
+
+    if (min_sleep > max_use/2)
+        min_sleep = pa_frame_align(max_use/2, &u->source->sample_spec);
+    if (min_sleep < u->frame_size)
+        min_sleep = u->frame_size;
+
+    if (min_wakeup > max_use/2)
+        min_wakeup = pa_frame_align(max_use/2, &u->source->sample_spec);
+    if (min_wakeup < u->frame_size)
+        min_wakeup = u->frame_size;
+
+    if (u->tsched_watermark > max_use-min_sleep)
+        u->tsched_watermark = max_use-min_sleep;
+
+    if (u->tsched_watermark < min_wakeup)
+        u->tsched_watermark = min_wakeup;
+}
+
+static pa_usec_t hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*process_usec) {
+    pa_usec_t wm, usec;
+
+    pa_assert(u);
+
+    usec = pa_source_get_requested_latency_within_thread(u->source);
+
+    if (usec == (pa_usec_t) -1)
+        usec = pa_bytes_to_usec(u->hwbuf_size, &u->source->sample_spec);
+
+/*     pa_log_debug("hw buffer time: %u ms", (unsigned) (usec / PA_USEC_PER_MSEC)); */
+
+    wm = pa_bytes_to_usec(u->tsched_watermark, &u->source->sample_spec);
+
+    if (usec >= wm) {
+        *sleep_usec = usec - wm;
+        *process_usec = wm;
+    } else
+        *process_usec = *sleep_usec = usec /= 2;
+
+/*     pa_log_debug("after watermark: %u ms", (unsigned) (*sleep_usec / PA_USEC_PER_MSEC)); */
+
+    return usec;
+}
+
+static int try_recover(struct userdata *u, const char *call, int err) {
+    pa_assert(u);
+    pa_assert(call);
+    pa_assert(err < 0);
+
+    pa_log_debug("%s: %s", call, snd_strerror(err));
+
+    pa_assert(err != -EAGAIN);
+
+    if (err == -EPIPE)
+        pa_log_debug("%s: Buffer overrun!", call);
+
+    if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0) {
+        snd_pcm_start(u->pcm_handle);
+        return 0;
+    }
+
+    pa_log("%s: %s", call, snd_strerror(err));
+    return -1;
+}
+
+static size_t check_left_to_record(struct userdata *u, snd_pcm_sframes_t n) {
+    size_t left_to_record;
+
+    if (n*u->frame_size < u->hwbuf_size)
+        left_to_record = u->hwbuf_size - (n*u->frame_size);
+    else
+        left_to_record = 0;
+
+    if (left_to_record > 0) {
+/*         pa_log_debug("%0.2f ms left to record", (double) pa_bytes_to_usec(left_to_record, &u->source->sample_spec) / PA_USEC_PER_MSEC); */
+    } else {
+        pa_log_info("Overrun!");
+
+        if (u->use_tsched) {
+            size_t old_watermark = u->tsched_watermark;
+
+            u->tsched_watermark *= 2;
+            fix_tsched_watermark(u);
+
+            if (old_watermark != u->tsched_watermark)
+                pa_log_notice("Increasing wakeup watermark to %0.2f ms",
+                              (double) pa_bytes_to_usec(u->tsched_watermark, &u->source->sample_spec) / PA_USEC_PER_MSEC);
+        }
+    }
+
+    return left_to_record;
+}
+
+static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec) {
     int work_done = 0;
+    pa_usec_t max_sleep_usec, process_usec;
+    size_t left_to_record;
 
     pa_assert(u);
     pa_source_assert_ref(u->source);
 
+    if (u->use_tsched)
+        hw_sleep_time(u, &max_sleep_usec, &process_usec);
+
     for (;;) {
         snd_pcm_sframes_t n;
-        int err;
-        const snd_pcm_channel_area_t *areas;
-        snd_pcm_uframes_t offset, frames;
-        pa_memchunk chunk;
-        void *p;
+        int r;
 
-        if ((n = snd_pcm_avail_update(u->pcm_handle)) < 0) {
+        snd_pcm_hwsync(u->pcm_handle);
 
-            if (n == -EPIPE)
-                pa_log_debug("snd_pcm_avail_update: Buffer underrun!");
+        if (PA_UNLIKELY((n = snd_pcm_avail_update(u->pcm_handle)) < 0)) {
 
-            if ((err = snd_pcm_recover(u->pcm_handle, n, 1)) == 0)
+            if ((r = try_recover(u, "snd_pcm_avail_update", n)) == 0)
                 continue;
 
-            if (err == -EAGAIN)
-                return work_done;
-
-            pa_log("snd_pcm_avail_update: %s", snd_strerror(err));
-            return -1;
+            return r;
         }
 
-/*         pa_log("Got request for %i samples", (int) n); */
+        left_to_record = check_left_to_record(u, n);
 
-        if (n <= 0)
-            return work_done;
+        if (u->use_tsched)
+            if (pa_bytes_to_usec(left_to_record, &u->source->sample_spec) > max_sleep_usec/2)
+                break;
 
-        frames = n;
+        if (PA_UNLIKELY(n <= 0))
+            break;
 
-        if ((err = snd_pcm_mmap_begin(u->pcm_handle, &areas, &offset, &frames)) < 0) {
+        for (;;) {
+            int err;
+            const snd_pcm_channel_area_t *areas;
+            snd_pcm_uframes_t offset, frames = (snd_pcm_uframes_t) n;
+            pa_memchunk chunk;
+            void *p;
 
-            if (err == -EPIPE)
-                pa_log_debug("snd_pcm_mmap_begin: Buffer underrun!");
+/*             pa_log_debug("%lu frames to read", (unsigned long) frames); */
 
-            if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0)
-                continue;
+            if (PA_UNLIKELY((err = snd_pcm_mmap_begin(u->pcm_handle, &areas, &offset, &frames)) < 0)) {
 
-            if (err == -EAGAIN)
-                return work_done;
+                if ((r = try_recover(u, "snd_pcm_mmap_begin", err)) == 0)
+                    continue;
 
-            pa_log("Failed to write data to DSP: %s", snd_strerror(err));
-            return -1;
-        }
+                return r;
+            }
 
-        /* Check these are multiples of 8 bit */
-        pa_assert((areas[0].first & 7) == 0);
-        pa_assert((areas[0].step & 7)== 0);
+            /* Make sure that if these memblocks need to be copied they will fit into one slot */
+            if (frames > pa_mempool_block_size_max(u->source->core->mempool)/u->frame_size)
+                frames = pa_mempool_block_size_max(u->source->core->mempool)/u->frame_size;
 
-        /* We assume a single interleaved memory buffer */
-        pa_assert((areas[0].first >> 3) == 0);
-        pa_assert((areas[0].step >> 3) == u->frame_size);
+            /* Check these are multiples of 8 bit */
+            pa_assert((areas[0].first & 7) == 0);
+            pa_assert((areas[0].step & 7)== 0);
 
-        p = (uint8_t*) areas[0].addr + (offset * u->frame_size);
+            /* We assume a single interleaved memory buffer */
+            pa_assert((areas[0].first >> 3) == 0);
+            pa_assert((areas[0].step >> 3) == u->frame_size);
 
-        chunk.memblock = pa_memblock_new_fixed(u->core->mempool, p, frames * u->frame_size, 1);
-        chunk.length = pa_memblock_get_length(chunk.memblock);
-        chunk.index = 0;
+            p = (uint8_t*) areas[0].addr + (offset * u->frame_size);
 
-        pa_source_post(u->source, &chunk);
+            chunk.memblock = pa_memblock_new_fixed(u->core->mempool, p, frames * u->frame_size, TRUE);
+            chunk.length = pa_memblock_get_length(chunk.memblock);
+            chunk.index = 0;
 
-        /* FIXME: Maybe we can do something to keep this memory block
-         * a little bit longer around? */
-        pa_memblock_unref_fixed(chunk.memblock);
+            pa_source_post(u->source, &chunk);
+            pa_memblock_unref_fixed(chunk.memblock);
 
-        if ((err = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0) {
+            if (PA_UNLIKELY((err = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0)) {
 
-            if (err == -EPIPE)
-                pa_log_debug("snd_pcm_mmap_commit: Buffer underrun!");
+                if ((r = try_recover(u, "snd_pcm_mmap_commit", err)) == 0)
+                    continue;
 
-            if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0)
-                continue;
+                return r;
+            }
 
-            if (err == -EAGAIN)
-                return work_done;
+            work_done = 1;
 
-            pa_log("Failed to write data to DSP: %s", snd_strerror(err));
-            return -1;
-        }
+            u->frame_index += frames;
+
+/*             pa_log_debug("read %lu frames", (unsigned long) frames); */
 
-        work_done = 1;
+            if (frames >= (snd_pcm_uframes_t) n)
+                break;
 
-/*         pa_log("wrote %i samples", (int) frames); */
+            n -= frames;
+        }
     }
+
+    *sleep_usec = pa_bytes_to_usec(left_to_record, &u->source->sample_spec) - process_usec;
+    return work_done;
 }
 
-static int unix_read(struct userdata *u) {
-    snd_pcm_status_t *status;
+static int unix_read(struct userdata *u, pa_usec_t *sleep_usec) {
     int work_done = 0;
-
-    snd_pcm_status_alloca(&status);
+    pa_usec_t max_sleep_usec, process_usec;
+    size_t left_to_record;
 
     pa_assert(u);
     pa_source_assert_ref(u->source);
 
+    if (u->use_tsched)
+        hw_sleep_time(u, &max_sleep_usec, &process_usec);
+
     for (;;) {
-        void *p;
-        snd_pcm_sframes_t t, k;
-        ssize_t l;
-        int err;
-        pa_memchunk chunk;
-
-        if ((err = snd_pcm_status(u->pcm_handle, status)) < 0) {
-            pa_log("Failed to query DSP status data: %s", snd_strerror(err));
-            return -1;
+        snd_pcm_sframes_t n;
+        int r;
+
+        snd_pcm_hwsync(u->pcm_handle);
+
+        if (PA_UNLIKELY((n = snd_pcm_avail_update(u->pcm_handle)) < 0)) {
+
+            if ((r = try_recover(u, "snd_pcm_avail_update", n)) == 0)
+                continue;
+
+            return r;
         }
 
-        if (snd_pcm_status_get_avail_max(status)*u->frame_size >= u->hwbuf_size)
-            pa_log_debug("Buffer overrun!");
+        left_to_record = check_left_to_record(u, n);
 
-        l = snd_pcm_status_get_avail(status) * u->frame_size;
+        if (u->use_tsched)
+            if (pa_bytes_to_usec(left_to_record, &u->source->sample_spec) > max_sleep_usec/2)
+                break;
 
-        if (l <= 0)
+        if (PA_UNLIKELY(n <= 0))
             return work_done;
 
-        chunk.memblock = pa_memblock_new(u->core->mempool, (size_t) -1);
+        for (;;) {
+            void *p;
+            snd_pcm_sframes_t frames;
+            pa_memchunk chunk;
 
-        k = pa_memblock_get_length(chunk.memblock);
+            chunk.memblock = pa_memblock_new(u->core->mempool, (size_t) -1);
 
-        if (k > l)
-            k = l;
+            frames = pa_memblock_get_length(chunk.memblock) / u->frame_size;
 
-        k = (k/u->frame_size)*u->frame_size;
+            if (frames > n)
+                frames = n;
 
-        p = pa_memblock_acquire(chunk.memblock);
-        t = snd_pcm_readi(u->pcm_handle, (uint8_t*) p, k / u->frame_size);
-        pa_memblock_release(chunk.memblock);
+/*             pa_log_debug("%lu frames to read", (unsigned long) n); */
 
-/*                     pa_log("wrote %i bytes of %u (%u)", t*u->frame_size, u->memchunk.length, l);   */
+            p = pa_memblock_acquire(chunk.memblock);
+            frames = snd_pcm_readi(u->pcm_handle, (uint8_t*) p, frames);
+            pa_memblock_release(chunk.memblock);
 
-        pa_assert(t != 0);
+            pa_assert(frames != 0);
 
-        if (t < 0) {
-            pa_memblock_unref(chunk.memblock);
+            if (PA_UNLIKELY(frames < 0)) {
+                pa_memblock_unref(chunk.memblock);
 
-            if ((t = snd_pcm_recover(u->pcm_handle, t, 1)) == 0)
-                continue;
+                if ((r = try_recover(u, "snd_pcm_readi", n)) == 0)
+                    continue;
 
-            if (t == -EAGAIN) {
-                pa_log_debug("EAGAIN");
-                return work_done;
-            } else {
-                pa_log("Failed to read data from DSP: %s", snd_strerror(t));
-                return -1;
+                return r;
             }
+
+            chunk.index = 0;
+            chunk.length = frames * u->frame_size;
+
+            pa_source_post(u->source, &chunk);
+            pa_memblock_unref(chunk.memblock);
+
+            work_done = 1;
+
+            u->frame_index += frames;
+
+/*             pa_log_debug("read %lu frames", (unsigned long) frames); */
+
+            if (frames >= n)
+                break;
+
+            n -= frames;
         }
+    }
 
-        chunk.index = 0;
-        chunk.length = t * u->frame_size;
+    *sleep_usec = pa_bytes_to_usec(left_to_record, &u->source->sample_spec) - process_usec;
+    return work_done;
+}
 
-        pa_source_post(u->source, &chunk);
-        pa_memblock_unref(chunk.memblock);
+static void update_smoother(struct userdata *u) {
+    snd_pcm_sframes_t delay = 0;
+    int64_t frames;
+    int err;
+    pa_usec_t now1, now2;
 
-        work_done = 1;
+    pa_assert(u);
+    pa_assert(u->pcm_handle);
 
-        if (t * u->frame_size >= (unsigned) l)
-            return work_done;
+    /* Let's update the time smoother */
+
+    snd_pcm_hwsync(u->pcm_handle);
+    snd_pcm_avail_update(u->pcm_handle);
+
+    if (PA_UNLIKELY((err = snd_pcm_delay(u->pcm_handle, &delay)) < 0)) {
+        pa_log_warn("Failed to get delay: %s", snd_strerror(err));
+        return;
     }
+
+    frames = u->frame_index + delay;
+
+    now1 = pa_rtclock_usec();
+    now2 = pa_bytes_to_usec(frames * u->frame_size, &u->source->sample_spec);
+
+    pa_smoother_put(u->smoother, now1, now2);
 }
 
 static pa_usec_t source_get_latency(struct userdata *u) {
     pa_usec_t r = 0;
-    snd_pcm_status_t *status;
-    snd_pcm_sframes_t frames = 0;
-    int err;
-
-    snd_pcm_status_alloca(&status);
+    int64_t delay;
+    pa_usec_t now1, now2;
 
     pa_assert(u);
-    pa_assert(u->pcm_handle);
 
-    if ((err = snd_pcm_status(u->pcm_handle, status)) < 0)
-        pa_log("Failed to get delay: %s", snd_strerror(err));
-    else
-        frames = snd_pcm_status_get_delay(status);
+    now1 = pa_rtclock_usec();
+    now2 = pa_smoother_get(u->smoother, now1);
+
+    delay = (int64_t) now2 - pa_bytes_to_usec(u->frame_index * u->frame_size, &u->source->sample_spec);
 
-    if (frames > 0)
-        r = pa_bytes_to_usec(frames * u->frame_size, &u->source->sample_spec);
+    if (delay > 0)
+        r = (pa_usec_t) delay;
 
     return r;
 }
 
 static int build_pollfd(struct userdata *u) {
-    int err;
-    struct pollfd *pollfd;
-    int n;
-
     pa_assert(u);
     pa_assert(u->pcm_handle);
 
-    if ((n = snd_pcm_poll_descriptors_count(u->pcm_handle)) < 0) {
-        pa_log("snd_pcm_poll_descriptors_count() failed: %s", snd_strerror(n));
-        return -1;
-    }
-
     if (u->alsa_rtpoll_item)
         pa_rtpoll_item_free(u->alsa_rtpoll_item);
 
-    u->alsa_rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, n);
-    pollfd = pa_rtpoll_item_get_pollfd(u->alsa_rtpoll_item, NULL);
-
-    if ((err = snd_pcm_poll_descriptors(u->pcm_handle, pollfd, n)) < 0) {
-        pa_log("snd_pcm_poll_descriptors() failed: %s", snd_strerror(err));
+    if (!(u->alsa_rtpoll_item = pa_alsa_build_pollfd(u->pcm_handle, u->rtpoll)))
         return -1;
-    }
 
     return 0;
 }
@@ -330,6 +479,8 @@ static int suspend(struct userdata *u) {
     pa_assert(u);
     pa_assert(u->pcm_handle);
 
+    pa_smoother_pause(u->smoother, pa_rtclock_usec());
+
     /* Let's suspend */
     snd_pcm_close(u->pcm_handle);
     u->pcm_handle = NULL;
@@ -344,10 +495,63 @@ static int suspend(struct userdata *u) {
     return 0;
 }
 
+static int update_sw_params(struct userdata *u) {
+    snd_pcm_uframes_t avail_min;
+    int err;
+
+    pa_assert(u);
+
+    /* Use the full buffer if noone asked us for anything specific */
+    u->hwbuf_unused_frames = 0;
+
+    if (u->use_tsched) {
+        pa_usec_t latency;
+
+        if ((latency = pa_source_get_requested_latency_within_thread(u->source)) != (pa_usec_t) -1) {
+            size_t b;
+
+            pa_log_debug("latency set to %0.2f", (double) latency / PA_USEC_PER_MSEC);
+
+            b = pa_usec_to_bytes(latency, &u->source->sample_spec);
+
+            /* We need at least one sample in our buffer */
+
+            if (PA_UNLIKELY(b < u->frame_size))
+                b = u->frame_size;
+
+            u->hwbuf_unused_frames =
+                PA_LIKELY(b < u->hwbuf_size) ?
+                ((u->hwbuf_size - b) / u->frame_size) : 0;
+
+            fix_tsched_watermark(u);
+        }
+    }
+
+    pa_log_debug("hwbuf_unused_frames=%lu", (unsigned long) u->hwbuf_unused_frames);
+
+    avail_min = 1;
+
+    if (u->use_tsched) {
+        pa_usec_t sleep_usec, process_usec;
+
+        hw_sleep_time(u, &sleep_usec, &process_usec);
+        avail_min += pa_usec_to_bytes(sleep_usec, &u->source->sample_spec);
+    }
+
+    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));
+        return err;
+    }
+
+    return 0;
+}
+
 static int unsuspend(struct userdata *u) {
     pa_sample_spec ss;
     int err;
-    pa_bool_t b;
+    pa_bool_t b, d;
     unsigned nfrags;
     snd_pcm_uframes_t period_size;
 
@@ -366,13 +570,14 @@ static int unsuspend(struct userdata *u) {
     nfrags = u->nfragments;
     period_size = u->fragment_size / u->frame_size;
     b = u->use_mmap;
+    d = u->use_tsched;
 
-    if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &nfrags, &period_size, &b, TRUE)) < 0) {
+    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));
         goto fail;
     }
 
-    if (b != u->use_mmap) {
+    if (b != u->use_mmap || d != u->use_tsched) {
         pa_log_warn("Resume failed, couldn't get original access mode.");
         goto fail;
     }
@@ -387,18 +592,17 @@ static int unsuspend(struct userdata *u) {
         goto fail;
     }
 
-    if ((err = pa_alsa_set_sw_params(u->pcm_handle)) < 0) {
-        pa_log("Failed to set software parameters: %s", snd_strerror(err));
+    if (update_sw_params(u) < 0)
         goto fail;
-    }
 
     if (build_pollfd(u) < 0)
         goto fail;
 
-    snd_pcm_start(u->pcm_handle);
-
     /* FIXME: We need to reload the volume somehow */
 
+    snd_pcm_start(u->pcm_handle);
+    pa_smoother_resume(u->smoother, pa_rtclock_usec());
+
     pa_log_info("Resumed successfully...");
 
     return 0;
@@ -433,7 +637,7 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off
             switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) {
 
                 case PA_SOURCE_SUSPENDED:
-                    pa_assert(PA_SOURCE_OPENED(u->source->thread_info.state));
+                    pa_assert(PA_SOURCE_IS_OPENED(u->source->thread_info.state));
 
                     if (suspend(u) < 0)
                         return -1;
@@ -494,18 +698,24 @@ static int source_get_volume_cb(pa_source *s) {
     pa_assert(u->mixer_elem);
 
     for (i = 0; i < s->sample_spec.channels; i++) {
-        long set_vol, vol;
+        long alsa_vol;
 
         pa_assert(snd_mixer_selem_has_capture_channel(u->mixer_elem, u->mixer_map[i]));
 
-        if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, u->mixer_map[i], &vol)) < 0)
-            goto fail;
+        if (u->hw_dB_supported) {
+
+            if ((err = snd_mixer_selem_get_capture_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol)) >= 0) {
+                s->volume.values[i] = pa_sw_volume_from_dB(alsa_vol / 100.0);
+                continue;
+            }
 
-        set_vol = (long) roundf(((float) s->volume.values[i] * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min;
+            u->hw_dB_supported = FALSE;
+        }
+
+        if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0)
+            goto fail;
 
-        /* Try to avoid superfluous volume changes */
-        if (set_vol != vol)
-            s->volume.values[i] = (pa_volume_t) roundf(((float) (vol - u->hw_volume_min) * PA_VOLUME_NORM) / (u->hw_volume_max - u->hw_volume_min));
+        s->volume.values[i] = (pa_volume_t) roundf(((float) (alsa_vol - u->hw_volume_min) * PA_VOLUME_NORM) / (u->hw_volume_max - u->hw_volume_min));
     }
 
     return 0;
@@ -513,8 +723,6 @@ static int source_get_volume_cb(pa_source *s) {
 fail:
     pa_log_error("Unable to read volume: %s", snd_strerror(err));
 
-    s->get_volume = NULL;
-    s->set_volume = NULL;
     return -1;
 }
 
@@ -532,15 +740,32 @@ static int source_set_volume_cb(pa_source *s) {
 
         pa_assert(snd_mixer_selem_has_capture_channel(u->mixer_elem, u->mixer_map[i]));
 
-        vol = s->volume.values[i];
+        vol = PA_MIN(s->volume.values[i], PA_VOLUME_NORM);
+
+        if (u->hw_dB_supported) {
+            alsa_vol = (long) (pa_sw_volume_to_dB(vol) * 100);
+            alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_dB_min, u->hw_dB_max);
+
 
-        if (vol > PA_VOLUME_NORM)
-            vol = PA_VOLUME_NORM;
+            if ((err = snd_mixer_selem_set_capture_dB(u->mixer_elem, u->mixer_map[i], alsa_vol, -1)) >= 0) {
+
+                if (snd_mixer_selem_get_capture_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol) >= 0)
+                    s->volume.values[i] = pa_sw_volume_from_dB(alsa_vol / 100.0);
+
+                continue;
+            }
+
+            u->hw_dB_supported = FALSE;
+        }
 
         alsa_vol = (long) roundf(((float) vol * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min;
+        alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_volume_min, u->hw_volume_max);
 
         if ((err = snd_mixer_selem_set_capture_volume(u->mixer_elem, u->mixer_map[i], alsa_vol)) < 0)
             goto fail;
+
+        if (snd_mixer_selem_get_capture_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol) >= 0)
+            s->volume.values[i] = (pa_volume_t) roundf(((float) (alsa_vol - u->hw_volume_min) * PA_VOLUME_NORM) / (u->hw_volume_max - u->hw_volume_min));
     }
 
     return 0;
@@ -548,8 +773,6 @@ static int source_set_volume_cb(pa_source *s) {
 fail:
     pa_log_error("Unable to set volume: %s", snd_strerror(err));
 
-    s->get_volume = NULL;
-    s->set_volume = NULL;
     return -1;
 }
 
@@ -562,9 +785,6 @@ static int source_get_mute_cb(pa_source *s) {
 
     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));
-
-        s->get_mute = NULL;
-        s->set_mute = NULL;
         return -1;
     }
 
@@ -582,15 +802,22 @@ static int source_set_mute_cb(pa_source *s) {
 
     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));
-
-        s->get_mute = NULL;
-        s->set_mute = NULL;
         return -1;
     }
 
     return 0;
 }
 
+static void source_update_requested_latency_cb(pa_source *s) {
+    struct userdata *u = s->userdata;
+    pa_assert(u);
+
+    if (!u->pcm_handle)
+        return;
+
+    update_sw_params(u);
+}
+
 static void thread_func(void *userdata) {
     struct userdata *u = userdata;
 
@@ -607,18 +834,47 @@ static void thread_func(void *userdata) {
     for (;;) {
         int ret;
 
+/*         pa_log_debug("loop"); */
+
         /* Read some data and pass it to the sources */
-        if (PA_SOURCE_OPENED(u->source->thread_info.state)) {
+        if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) {
+            int work_done = 0;
+            pa_usec_t sleep_usec;
 
-            if (u->use_mmap) {
-                if (mmap_read(u) < 0)
-                    goto fail;
+            if (u->use_mmap)
+                work_done = mmap_read(u, &sleep_usec);
+            else
+                work_done = unix_read(u, &sleep_usec);
 
-            } else {
-                if (unix_read(u) < 0)
-                    goto fail;
+            if (work_done < 0)
+                goto fail;
+
+/*             pa_log_debug("work_done = %i", work_done); */
+
+            if (work_done)
+                update_smoother(u);
+
+            if (u->use_tsched) {
+                pa_usec_t cusec;
+
+                /* OK, the capture buffer is now empty, let's
+                 * calculate when to wake up next */
+
+/*                 pa_log_debug("Waking up in %0.2fms (sound card clock).", (double) sleep_usec / PA_USEC_PER_MSEC); */
+
+                /* Convert from the sound card time domain to the
+                 * system time domain */
+                cusec = pa_smoother_translate(u->smoother, pa_rtclock_usec(), sleep_usec);
+
+/*                 pa_log_debug("Waking up in %0.2fms (system clock).", (double) cusec / PA_USEC_PER_MSEC); */
+
+                /* We don't trust the conversion, so we wake up whatever comes first */
+                pa_rtpoll_set_timer_relative(u->rtpoll, PA_MIN(sleep_usec, cusec));
             }
-        }
+        } else if (u->use_tsched)
+
+            /* OK, we're in an invalid state, let's disable our timers */
+            pa_rtpoll_set_timer_disabled(u->rtpoll);
 
         /* Hmm, nothing to do. Let's sleep */
         if ((ret = pa_rtpoll_run(u->rtpoll, 1)) < 0)
@@ -628,7 +884,7 @@ static void thread_func(void *userdata) {
             goto finish;
 
         /* Tell ALSA about this and process its response */
-        if (PA_SOURCE_OPENED(u->source->thread_info.state)) {
+        if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) {
             struct pollfd *pollfd;
             unsigned short revents = 0;
             int err;
@@ -642,43 +898,14 @@ static void thread_func(void *userdata) {
             }
 
             if (revents & (POLLERR|POLLNVAL|POLLHUP)) {
+                if (pa_alsa_recover_from_poll(u->pcm_handle, revents) < 0)
+                    goto fail;
 
-                if (revents & POLLERR)
-                    pa_log_warn("Got POLLERR from ALSA");
-                if (revents & POLLNVAL)
-                    pa_log_warn("Got POLLNVAL from ALSA");
-                if (revents & POLLHUP)
-                    pa_log_warn("Got POLLHUP from ALSA");
-
-                /* Try to recover from this error */
-
-                switch (snd_pcm_state(u->pcm_handle)) {
-
-                    case SND_PCM_STATE_XRUN:
-                        if ((err = snd_pcm_recover(u->pcm_handle, -EPIPE, 1)) != 0) {
-                            pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and XRUN: %s", snd_strerror(err));
-                            goto fail;
-                        }
-                        break;
-
-                    case SND_PCM_STATE_SUSPENDED:
-                        if ((err = snd_pcm_recover(u->pcm_handle, -ESTRPIPE, 1)) != 0) {
-                            pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and SUSPENDED: %s", snd_strerror(err));
-                            goto fail;
-                        }
-                        break;
-
-                    default:
-
-                        snd_pcm_drop(u->pcm_handle);
-
-                        if ((err = snd_pcm_prepare(u->pcm_handle)) < 0) {
-                            pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP with snd_pcm_prepare(): %s", snd_strerror(err));
-                            goto fail;
-                        }
-                        break;
-                }
+                snd_pcm_start(u->pcm_handle);
             }
+
+            if (revents && u->use_tsched)
+                pa_log_debug("Wakeup from ALSA! (%i)", revents);
         }
     }
 
@@ -699,21 +926,23 @@ int pa__init(pa_module*m) {
     const char *dev_id;
     pa_sample_spec ss;
     pa_channel_map map;
-    uint32_t nfrags, frag_size;
-    snd_pcm_uframes_t period_size;
+    uint32_t nfrags, hwbuf_size, frag_size, tsched_size, tsched_watermark;
+    snd_pcm_uframes_t period_frames, tsched_frames;
     size_t frame_size;
     snd_pcm_info_t *pcm_info = NULL;
     int err;
-    char *t;
     const char *name;
     char *name_buf = NULL;
-    int namereg_fail;
-    pa_bool_t use_mmap = TRUE, b;
+    pa_bool_t namereg_fail;
+    pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, mixer_reset = TRUE;
+    pa_source_new_data data;
 
     snd_pcm_info_alloca(&pcm_info);
 
     pa_assert(m);
 
+    pa_alsa_redirect_errors_inc();
+
     if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
         pa_log("Failed to parse module arguments");
         goto fail;
@@ -728,34 +957,61 @@ int pa__init(pa_module*m) {
     frame_size = pa_frame_size(&ss);
 
     nfrags = m->core->default_n_fragments;
-    frag_size = pa_usec_to_bytes(m->core->default_fragment_size_msec*1000, &ss);
+    frag_size = pa_usec_to_bytes(m->core->default_fragment_size_msec*PA_USEC_PER_MSEC, &ss);
     if (frag_size <= 0)
         frag_size = frame_size;
+    tsched_size = pa_usec_to_bytes(DEFAULT_TSCHED_BUFFER_USEC, &ss);
+    tsched_watermark = pa_usec_to_bytes(DEFAULT_TSCHED_WATERMARK_USEC, &ss);
 
-    if (pa_modargs_get_value_u32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_u32(ma, "fragment_size", &frag_size) < 0) {
+    if (pa_modargs_get_value_u32(ma, "fragments", &nfrags) < 0 ||
+        pa_modargs_get_value_u32(ma, "fragment_size", &frag_size) < 0 ||
+        pa_modargs_get_value_u32(ma, "tsched_buffer_size", &tsched_size) < 0 ||
+        pa_modargs_get_value_u32(ma, "tsched_buffer_watermark", &tsched_watermark) < 0) {
         pa_log("Failed to parse buffer metrics");
         goto fail;
     }
-    period_size = frag_size/frame_size;
+
+    hwbuf_size = frag_size * nfrags;
+    period_frames = frag_size/frame_size;
+    tsched_frames = tsched_size/frame_size;
 
     if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) {
         pa_log("Failed to parse mmap argument.");
         goto fail;
     }
 
+    if (pa_modargs_get_value_boolean(ma, "tsched", &use_tsched) < 0) {
+        pa_log("Failed to parse timer_scheduling argument.");
+        goto fail;
+    }
+
+    if (use_tsched && !pa_rtclock_hrtimer()) {
+        pa_log("Disabling timer-based scheduling because high-resolution timers are not available from the kernel.");
+        use_tsched = FALSE;
+    }
+
+    if (pa_modargs_get_value_boolean(ma, "mixer_reset", &mixer_reset) < 0) {
+        pa_log("Failed to parse mixer_reset argument.");
+        goto fail;
+    }
+
     u = pa_xnew0(struct userdata, 1);
     u->core = m->core;
     u->module = m;
     m->userdata = u;
     u->use_mmap = use_mmap;
-    pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
+    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;
-    pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
+
+    u->smoother = pa_smoother_new(DEFAULT_TSCHED_WATERMARK_USEC, DEFAULT_TSCHED_WATERMARK_USEC, TRUE, 5);
+    pa_smoother_set_time_offset(u->smoother, pa_rtclock_usec());
 
     snd_config_update_free_global();
 
     b = use_mmap;
+    d = use_tsched;
 
     if ((dev_id = pa_modargs_get_value(ma, "device_id", NULL))) {
 
@@ -764,8 +1020,8 @@ int pa__init(pa_module*m) {
                       &u->device_name,
                       &ss, &map,
                       SND_PCM_STREAM_CAPTURE,
-                      &nfrags, &period_size,
-                      &b)))
+                      &nfrags, &period_frames, tsched_frames,
+                      &b, &d)))
             goto fail;
 
     } else {
@@ -775,8 +1031,8 @@ int pa__init(pa_module*m) {
                       &u->device_name,
                       &ss, &map,
                       SND_PCM_STREAM_CAPTURE,
-                      &nfrags, &period_size,
-                      &b)))
+                      &nfrags, &period_frames, tsched_frames,
+                      &b, &d)))
             goto fail;
     }
 
@@ -785,22 +1041,25 @@ int pa__init(pa_module*m) {
 
     if (use_mmap && !b) {
         pa_log_info("Device doesn't support mmap(), falling back to UNIX read/write mode.");
-        u->use_mmap = use_mmap = b;
+        u->use_mmap = use_mmap = FALSE;
+    }
+
+    if (use_tsched && (!b || !d)) {
+        pa_log_info("Cannot enabled timer-based scheduling, falling back to sound IRQ scheduling.");
+        u->use_tsched = use_tsched = FALSE;
     }
 
     if (u->use_mmap)
         pa_log_info("Successfully enabled mmap() mode.");
 
+    if (u->use_tsched)
+        pa_log_info("Successfully enabled timer-based scheduling mode.");
+
     if ((err = snd_pcm_info(u->pcm_handle, pcm_info)) < 0) {
         pa_log("Error fetching PCM info: %s", snd_strerror(err));
         goto fail;
     }
 
-    if ((err = pa_alsa_set_sw_params(u->pcm_handle)) < 0) {
-        pa_log("Failed to set software parameters: %s", snd_strerror(err));
-        goto fail;
-    }
-
     /* ALSA might tweak the sample spec, so recalculate the frame size */
     frame_size = pa_frame_size(&ss);
 
@@ -812,13 +1071,24 @@ int pa__init(pa_module*m) {
         if (pa_alsa_prepare_mixer(u->mixer_handle, u->device_name) >= 0)
             found = TRUE;
         else {
-            char *md = pa_sprintf_malloc("hw:%s", dev_id);
+            snd_pcm_info_t* info;
 
-            if (strcmp(u->device_name, md))
-                if (pa_alsa_prepare_mixer(u->mixer_handle, md) >= 0)
-                    found = TRUE;
+            snd_pcm_info_alloca(&info);
 
-            pa_xfree(md);
+            if (snd_pcm_info(u->pcm_handle, info) >= 0) {
+                char *md;
+                int card;
+
+                if ((card = snd_pcm_info_get_card(info)) >= 0) {
+
+                    md = pa_sprintf_malloc("hw:%i", card);
+
+                    if (strcmp(u->device_name, md))
+                        if (pa_alsa_prepare_mixer(u->mixer_handle, md) >= 0)
+                            found = TRUE;
+                    pa_xfree(md);
+                }
+            }
         }
 
         if (found)
@@ -832,13 +1102,28 @@ int pa__init(pa_module*m) {
     }
 
     if ((name = pa_modargs_get_value(ma, "source_name", NULL)))
-        namereg_fail = 1;
+        namereg_fail = TRUE;
     else {
         name = name_buf = pa_sprintf_malloc("alsa_input.%s", u->device_name);
-        namereg_fail = 0;
+        namereg_fail = FALSE;
     }
 
-    u->source = pa_source_new(m->core, __FILE__, name, namereg_fail, &ss, &map);
+    pa_source_new_data_init(&data);
+    data.driver = __FILE__;
+    data.module = m;
+    pa_source_new_data_set_name(&data, name);
+    data.namereg_fail = namereg_fail;
+    pa_source_new_data_set_sample_spec(&data, &ss);
+    pa_source_new_data_set_channel_map(&data, &map);
+
+    pa_alsa_init_proplist(data.proplist, pcm_info);
+    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->device_name);
+    pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (period_frames * frame_size * nfrags));
+    pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_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"));
+
+    u->source = pa_source_new(m->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY);
+    pa_source_new_data_done(&data);
     pa_xfree(name_buf);
 
     if (!u->source) {
@@ -847,42 +1132,104 @@ int pa__init(pa_module*m) {
     }
 
     u->source->parent.process_msg = source_process_msg;
+    u->source->update_requested_latency = source_update_requested_latency_cb;
     u->source->userdata = u;
 
-    pa_source_set_module(u->source, m);
     pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
     pa_source_set_rtpoll(u->source, u->rtpoll);
-    pa_source_set_description(u->source, t = pa_sprintf_malloc(
-                                      "ALSA PCM on %s (%s)%s",
-                                      u->device_name,
-                                      snd_pcm_info_get_name(pcm_info),
-                                      use_mmap ? " via DMA" : ""));
-    pa_xfree(t);
-
-    u->source->flags = PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY;
 
     u->frame_size = frame_size;
-    u->fragment_size = frag_size = period_size * frame_size;
+    u->fragment_size = frag_size = period_frames * frame_size;
     u->nfragments = nfrags;
     u->hwbuf_size = u->fragment_size * nfrags;
+    u->hwbuf_unused_frames = 0;
+    u->tsched_watermark = tsched_watermark;
+    u->frame_index = 0;
+    u->hw_dB_supported = FALSE;
+    u->hw_dB_min = u->hw_dB_max = 0;
+    u->hw_volume_min = u->hw_volume_max = 0;
+
+    if (use_tsched)
+        fix_tsched_watermark(u);
+
+    u->source->max_latency = pa_bytes_to_usec(u->hwbuf_size, &ss);
+    if (!use_tsched)
+        u->source->min_latency = u->source->max_latency;
+
+    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_log_info("Using %u fragments of size %lu bytes.", nfrags, (long unsigned) u->fragment_size);
+    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);
+
+    if (update_sw_params(u) < 0)
+        goto fail;
 
     if (u->mixer_handle) {
         pa_assert(u->mixer_elem);
 
         if (snd_mixer_selem_has_capture_volume(u->mixer_elem))
-            if (pa_alsa_calc_mixer_map(u->mixer_elem, &map, u->mixer_map, FALSE) >= 0) {
-                u->source->get_volume = source_get_volume_cb;
-                u->source->set_volume = source_set_volume_cb;
-                snd_mixer_selem_get_capture_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max);
-                u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL;
+            if (pa_alsa_calc_mixer_map(u->mixer_elem, &map, u->mixer_map, FALSE) >= 0 &&
+                snd_mixer_selem_get_capture_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max) >= 0) {
+
+                pa_bool_t suitable = TRUE;
+
+                pa_log_info("Volume ranges from %li to %li.", u->hw_volume_min, u->hw_volume_max);
+
+                if (u->hw_volume_min > u->hw_volume_max) {
+
+                    pa_log_info("Minimal volume %li larger than maximum volume %li. Strange stuff Falling back to software volume control.", u->hw_volume_min, u->hw_volume_max);
+                    suitable = FALSE;
+
+                } else if (u->hw_volume_max - u->hw_volume_min < 3) {
+
+                    pa_log_info("Device has less than 4 volume levels. Falling back to software volume control.");
+                    suitable = FALSE;
+
+                } else if (snd_mixer_selem_get_capture_dB_range(u->mixer_elem, &u->hw_dB_min, &u->hw_dB_max) >= 0) {
+
+                    pa_log_info("Volume ranges from %0.2f dB to %0.2f dB.", u->hw_dB_min/100.0, u->hw_dB_max/100.0);
+
+                    /* Let's see if this thing actually is useful for muting */
+                    if (u->hw_dB_min > -6000) {
+                        pa_log_info("Device cannot attenuate for more than -60 dB (only %0.2f dB supported), falling back to software volume control.", ((double) u->hw_dB_min) / 100);
+
+                        suitable = FALSE;
+                    } else if (u->hw_dB_max < 0) {
+
+                        pa_log_info("Device is still attenuated at maximum volume setting (%0.2f dB is maximum). Strange stuff. Falling back to software volume control.", ((double) u->hw_dB_max) / 100);
+                        suitable = FALSE;
+
+                    } else if (u->hw_dB_min >= u->hw_dB_max) {
+
+                        pa_log_info("Minimal dB (%0.2f) larger or equal to maximum dB (%0.2f). Strange stuff. Falling back to software volume control.", ((double) u->hw_dB_min) / 100, ((double) u->hw_dB_max) / 100);
+                        suitable = FALSE;
+
+                    } else
+                        u->hw_dB_supported = TRUE;
+                }
+
+                if (suitable) {
+                    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");
+
+                } else if (mixer_reset) {
+                    pa_log_info("Using software volume control. Trying to reset sound card to 0 dB.");
+                    pa_alsa_0dB_capture(u->mixer_elem);
+                } else
+                    pa_log_info("Using software volume control. Leaving hw mixer controls untouched.");
+
             }
 
+
         if (snd_mixer_selem_has_capture_switch(u->mixer_elem)) {
             u->source->get_mute = source_get_mute_cb;
             u->source->set_mute = source_set_mute_cb;
-            u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL;
+            u->source->flags |= PA_SOURCE_HW_MUTE_CTRL;
         }
 
         u->mixer_fdl = pa_alsa_fdlist_new();
@@ -897,15 +1244,28 @@ int pa__init(pa_module*m) {
     } else
         u->mixer_fdl = NULL;
 
+    pa_alsa_dump(u->pcm_handle);
+
     if (!(u->thread = pa_thread_new(thread_func, u))) {
         pa_log("Failed to create thread.");
         goto fail;
     }
     /* Get initial mixer settings */
-    if (u->source->get_volume)
-        u->source->get_volume(u->source);
-    if (u->source->get_mute)
-        u->source->get_mute(u->source);
+    if (data.volume_is_set) {
+        if (u->source->set_volume)
+            u->source->set_volume(u->source);
+    } else {
+        if (u->source->get_volume)
+            u->source->get_volume(u->source);
+    }
+
+    if (data.muted_is_set) {
+        if (u->source->set_mute)
+            u->source->set_mute(u->source);
+    } else {
+        if (u->source->get_mute)
+            u->source->get_mute(u->source);
+    }
 
     pa_source_put(u->source);
 
@@ -928,8 +1288,10 @@ void pa__done(pa_module*m) {
 
     pa_assert(m);
 
-    if (!(u = m->userdata))
+    if (!(u = m->userdata)) {
+        pa_alsa_redirect_errors_dec();
         return;
+    }
 
     if (u->source)
         pa_source_unlink(u->source);
@@ -961,8 +1323,12 @@ void pa__done(pa_module*m) {
         snd_pcm_close(u->pcm_handle);
     }
 
+    if (u->smoother)
+        pa_smoother_free(u->smoother);
+
     pa_xfree(u->device_name);
     pa_xfree(u);
 
     snd_config_update_free_global();
+    pa_alsa_redirect_errors_dec();
 }
index 996cd4f..fc8be18 100644 (file)
@@ -66,7 +66,7 @@ PA_MODULE_USAGE(
         "channel_map=<channel map>");
 
 #define DEFAULT_SINK_NAME "combined"
-#define MEMBLOCKQ_MAXLENGTH (1024*170)
+#define MEMBLOCKQ_MAXLENGTH (1024*1024*16)
 
 #define DEFAULT_ADJUST_TIME 10
 
@@ -139,7 +139,7 @@ enum {
 };
 
 enum {
-    SINK_INPUT_MESSAGE_POST = PA_SINK_INPUT_MESSAGE_MAX
+    SINK_INPUT_MESSAGE_POST = PA_SINK_INPUT_MESSAGE_MAX,
 };
 
 static void output_free(struct output *o);
@@ -162,13 +162,13 @@ static void adjust_rates(struct userdata *u) {
     if (!u->master)
         return;
 
-    if (!PA_SINK_OPENED(pa_sink_get_state(u->sink)))
+    if (!PA_SINK_IS_OPENED(pa_sink_get_state(u->sink)))
         return;
 
     for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) {
         pa_usec_t sink_latency;
 
-        if (!o->sink_input || !PA_SINK_OPENED(pa_sink_get_state(o->sink)))
+        if (!o->sink_input || !PA_SINK_IS_OPENED(pa_sink_get_state(o->sink)))
             continue;
 
         sink_latency = pa_sink_get_latency(o->sink);
@@ -194,7 +194,7 @@ static void adjust_rates(struct userdata *u) {
     for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) {
         uint32_t r = base_rate;
 
-        if (!o->sink_input || !PA_SINK_OPENED(pa_sink_get_state(o->sink)))
+        if (!o->sink_input || !PA_SINK_IS_OPENED(pa_sink_get_state(o->sink)))
             continue;
 
         if (o->total_latency < target_latency)
@@ -203,10 +203,10 @@ static void adjust_rates(struct userdata *u) {
             r += (uint32_t) (((((double) o->total_latency - target_latency))/u->adjust_time)*r/PA_USEC_PER_SEC);
 
         if (r < (uint32_t) (base_rate*0.9) || r > (uint32_t) (base_rate*1.1)) {
-            pa_log_warn("[%s] sample rates too different, not adjusting (%u vs. %u).", o->sink_input->name, base_rate, r);
+            pa_log_warn("[%s] sample rates too different, not adjusting (%u vs. %u).", pa_proplist_gets(o->sink_input->proplist, PA_PROP_MEDIA_NAME), base_rate, r);
             pa_sink_input_set_rate(o->sink_input, base_rate);
         } else {
-            pa_log_info("[%s] new rate is %u Hz; ratio is %0.3f; latency is %0.0f usec.", o->sink_input->name, r, (double) r / base_rate, (float) o->total_latency);
+            pa_log_info("[%s] new rate is %u Hz; ratio is %0.3f; latency is %0.0f usec.", pa_proplist_gets(o->sink_input->proplist, PA_PROP_MEDIA_NAME), r, (double) r / base_rate, (float) o->total_latency);
             pa_sink_input_set_rate(o->sink_input, r);
         }
     }
@@ -250,10 +250,18 @@ static void thread_func(void *userdata) {
         if (u->sink->thread_info.state == PA_SINK_RUNNING && !u->thread_info.active_outputs) {
             struct timeval now;
 
+            /* Just rewind if necessary, since we are in NULL mode, we
+             * don't have to pass this on */
+            pa_sink_process_rewind(u->sink, u->sink->thread_info.rewind_nbytes);
+            u->sink->thread_info.rewind_nbytes = 0;
+
             pa_rtclock_get(&now);
 
             if (!u->thread_info.in_null_mode || pa_timeval_cmp(&u->thread_info.timestamp, &now) <= 0) {
-                pa_sink_skip(u->sink, u->block_size);
+                pa_memchunk chunk;
+
+                pa_sink_render_full(u->sink, u->block_size, &chunk);
+                pa_memblock_unref(chunk.memblock);
 
                 if (!u->thread_info.in_null_mode)
                     u->thread_info.timestamp = now;
@@ -354,27 +362,20 @@ static void request_memblock(struct output *o, size_t length) {
 }
 
 /* Called from I/O thread context */
-static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
+static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) {
     struct output *o;
 
     pa_sink_input_assert_ref(i);
     pa_assert_se(o = i->userdata);
 
     /* If necessary, get some new data */
-    request_memblock(o, length);
-
-    return pa_memblockq_peek(o->memblockq, chunk);
-}
-
-/* Called from I/O thread context */
-static void sink_input_drop_cb(pa_sink_input *i, size_t length) {
-    struct output *o;
+    request_memblock(o, nbytes);
 
-    pa_sink_input_assert_ref(i);
-    pa_assert(length > 0);
-    pa_assert_se(o = i->userdata);
+    if (pa_memblockq_peek(o->memblockq, chunk) < 0)
+        return -1;
 
-    pa_memblockq_drop(o->memblockq, length);
+    pa_memblockq_drop(o->memblockq, chunk->length);
+    return 0;
 }
 
 /* Called from I/O thread context */
@@ -386,7 +387,7 @@ static void sink_input_attach_cb(pa_sink_input *i) {
 
     /* Set up the queue from the sink thread to us */
     pa_assert(!o->inq_rtpoll_item);
-    o->inq_rtpoll_item = pa_rtpoll_item_new_asyncmsgq(
+    o->inq_rtpoll_item = pa_rtpoll_item_new_asyncmsgq_read(
             i->sink->rtpoll,
             PA_RTPOLL_LATE,  /* This one is not that important, since we check for data in _peek() anyway. */
             o->inq);
@@ -434,12 +435,13 @@ static int sink_input_process_msg(pa_msgobject *obj, int code, void *data, int64
 
         case SINK_INPUT_MESSAGE_POST:
 
-            if (PA_SINK_OPENED(o->sink_input->sink->thread_info.state))
+            if (PA_SINK_IS_OPENED(o->sink_input->sink->thread_info.state))
                 pa_memblockq_push_align(o->memblockq, chunk);
             else
                 pa_memblockq_flush(o->memblockq);
 
             break;
+
     }
 
     return pa_sink_input_process_msg(obj, code, data, offset, chunk);
@@ -472,7 +474,7 @@ static void enable_output(struct output *o) {
 
         pa_sink_input_put(o->sink_input);
 
-        if (o->userdata->sink && PA_SINK_LINKED(pa_sink_get_state(o->userdata->sink)))
+        if (o->userdata->sink && PA_SINK_IS_LINKED(pa_sink_get_state(o->userdata->sink)))
             pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_ADD_OUTPUT, o, 0, NULL);
     }
 }
@@ -505,7 +507,7 @@ static void unsuspend(struct userdata *u) {
 
         pa_sink_suspend(o->sink, FALSE);
 
-        if (PA_SINK_OPENED(pa_sink_get_state(o->sink)))
+        if (PA_SINK_IS_OPENED(pa_sink_get_state(o->sink)))
             enable_output(o);
     }
 
@@ -526,7 +528,7 @@ static int sink_set_state(pa_sink *sink, pa_sink_state_t state) {
 
     switch (state) {
         case PA_SINK_SUSPENDED:
-            pa_assert(PA_SINK_OPENED(pa_sink_get_state(u->sink)));
+            pa_assert(PA_SINK_IS_OPENED(pa_sink_get_state(u->sink)));
 
             suspend(u);
             break;
@@ -585,7 +587,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
 
             /* Create pa_asyncmsgq to the sink thread */
 
-            op->outq_rtpoll_item = pa_rtpoll_item_new_asyncmsgq(
+            op->outq_rtpoll_item = pa_rtpoll_item_new_asyncmsgq_read(
                     u->rtpoll,
                     PA_RTPOLL_EARLY-1,  /* This item is very important */
                     op->outq);
@@ -616,35 +618,35 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
 }
 
 /* Called from main context */
-static pa_usec_t sink_get_latency_cb(pa_sink *s) {
-    struct userdata *u;
+/* static pa_usec_t sink_get_latency_cb(pa_sink *s) { */
+/*     struct userdata *u; */
 
-    pa_sink_assert_ref(s);
-    pa_assert_se(u = s->userdata);
+/*     pa_sink_assert_ref(s); */
+/*     pa_assert_se(u = s->userdata); */
 
-    if (u->master) {
-        /* If we have a master sink, we just return the latency of it
-         * and add our own buffering on top */
+/*     if (u->master) { */
+/*         /\* If we have a master sink, we just return the latency of it */
+/*          * and add our own buffering on top *\/ */
 
-        if (!u->master->sink_input)
-            return 0;
+/*         if (!u->master->sink_input) */
+/*             return 0; */
 
-        return
-            pa_sink_input_get_latency(u->master->sink_input) +
-            pa_sink_get_latency(u->master->sink);
+/*         return */
+/*             pa_sink_input_get_latency(u->master->sink_input) + */
+/*             pa_sink_get_latency(u->master->sink); */
 
-    } else {
-        pa_usec_t usec = 0;
+/*     } else { */
+/*         pa_usec_t usec = 0; */
 
-        /* We have no master, hence let's ask our own thread which
-         * implements the NULL sink */
+/*         /\* We have no master, hence let's ask our own thread which */
+/*          * implements the NULL sink *\/ */
 
-        if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0)
-            return 0;
+/*         if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) */
+/*             return 0; */
 
-        return usec;
-    }
-}
+/*         return usec; */
+/*     } */
+/* } */
 
 static void update_description(struct userdata *u) {
     int first = 1;
@@ -665,10 +667,10 @@ static void update_description(struct userdata *u) {
         char *e;
 
         if (first) {
-            e = pa_sprintf_malloc("%s %s", t, o->sink->description);
+            e = pa_sprintf_malloc("%s %s", t, pa_strnull(pa_proplist_gets(o->sink->proplist, PA_PROP_DEVICE_DESCRIPTION)));
             first = 0;
         } else
-            e = pa_sprintf_malloc("%s, %s", t, o->sink->description);
+            e = pa_sprintf_malloc("%s, %s", t, pa_strnull(pa_proplist_gets(o->sink->proplist, PA_PROP_DEVICE_DESCRIPTION)));
 
         pa_xfree(t);
         t = e;
@@ -698,7 +700,7 @@ static void pick_master(struct userdata *u, struct output *except) {
     if (u->master &&
         u->master != except &&
         u->master->sink_input &&
-        PA_SINK_OPENED(pa_sink_get_state(u->master->sink))) {
+        PA_SINK_IS_OPENED(pa_sink_get_state(u->master->sink))) {
         update_master(u, u->master);
         return;
     }
@@ -706,7 +708,7 @@ static void pick_master(struct userdata *u, struct output *except) {
     for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx))
         if (o != except &&
             o->sink_input &&
-            PA_SINK_OPENED(pa_sink_get_state(o->sink))) {
+            PA_SINK_IS_OPENED(pa_sink_get_state(o->sink))) {
             update_master(u, o);
             return;
         }
@@ -723,12 +725,12 @@ static int output_create_sink_input(struct output *o) {
     if (o->sink_input)
         return 0;
 
-    t = pa_sprintf_malloc("Simultaneous output on %s", o->sink->description);
+    t = pa_sprintf_malloc("Simultaneous output on %s", pa_strnull(pa_proplist_gets(o->sink->proplist, PA_PROP_DEVICE_DESCRIPTION)));
 
     pa_sink_input_new_data_init(&data);
     data.sink = o->sink;
     data.driver = __FILE__;
-    data.name = t;
+    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, t);
     pa_sink_input_new_data_set_sample_spec(&data, &o->userdata->sink->sample_spec);
     pa_sink_input_new_data_set_channel_map(&data, &o->userdata->sink->channel_map);
     data.module = o->userdata->module;
@@ -736,14 +738,15 @@ static int output_create_sink_input(struct output *o) {
 
     o->sink_input = pa_sink_input_new(o->userdata->core, &data, PA_SINK_INPUT_VARIABLE_RATE|PA_SINK_INPUT_DONT_MOVE);
 
+    pa_sink_input_new_data_done(&data);
+
     pa_xfree(t);
 
     if (!o->sink_input)
         return -1;
 
     o->sink_input->parent.process_msg = sink_input_process_msg;
-    o->sink_input->peek = sink_input_peek_cb;
-    o->sink_input->drop = sink_input_drop_cb;
+    o->sink_input->pop = sink_input_pop_cb;
     o->sink_input->attach = sink_input_attach_cb;
     o->sink_input->detach = sink_input_detach_cb;
     o->sink_input->kill = sink_input_kill_cb;
@@ -775,26 +778,27 @@ static struct output *output_new(struct userdata *u, pa_sink *sink) {
             pa_frame_size(&u->sink->sample_spec),
             1,
             0,
+            0,
             NULL);
 
     pa_assert_se(pa_idxset_put(u->outputs, o, NULL) == 0);
 
-    if (u->sink && PA_SINK_LINKED(pa_sink_get_state(u->sink)))
+    if (u->sink && PA_SINK_IS_LINKED(pa_sink_get_state(u->sink)))
         pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_ADD_OUTPUT, o, 0, NULL);
     else {
         /* If the sink is not yet started, we need to do the activation ourselves */
         PA_LLIST_PREPEND(struct output, u->thread_info.active_outputs, o);
 
-        o->outq_rtpoll_item = pa_rtpoll_item_new_asyncmsgq(
+        o->outq_rtpoll_item = pa_rtpoll_item_new_asyncmsgq_read(
                 u->rtpoll,
                 PA_RTPOLL_EARLY-1,  /* This item is very important */
                 o->outq);
     }
 
-    if (PA_SINK_OPENED(pa_sink_get_state(u->sink)) || pa_sink_get_state(u->sink) == PA_SINK_INIT) {
+    if (PA_SINK_IS_OPENED(pa_sink_get_state(u->sink)) || pa_sink_get_state(u->sink) == PA_SINK_INIT) {
         pa_sink_suspend(sink, FALSE);
 
-        if (PA_SINK_OPENED(pa_sink_get_state(sink)))
+        if (PA_SINK_IS_OPENED(pa_sink_get_state(sink)))
             if (output_create_sink_input(o) < 0)
                 goto fail;
     }
@@ -897,7 +901,7 @@ static pa_hook_result_t sink_state_changed_hook_cb(pa_core *c, pa_sink *s, struc
 
     state = pa_sink_get_state(s);
 
-    if (PA_SINK_OPENED(state) && PA_SINK_OPENED(pa_sink_get_state(u->sink)) && !o->sink_input) {
+    if (PA_SINK_IS_OPENED(state) && PA_SINK_IS_OPENED(pa_sink_get_state(u->sink)) && !o->sink_input) {
         enable_output(o);
         pick_master(u, NULL);
     }
@@ -920,6 +924,7 @@ int pa__init(pa_module*m) {
     pa_channel_map map;
     struct output *o;
     uint32_t idx;
+    pa_sink_new_data data;
 
     pa_assert(m);
 
@@ -943,8 +948,8 @@ int pa__init(pa_module*m) {
     u->master = NULL;
     u->time_event = NULL;
     u->adjust_time = DEFAULT_ADJUST_TIME;
-    pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
     u->rtpoll = pa_rtpoll_new();
+    pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll);
     u->thread = NULL;
     u->resample_method = resample_method;
     u->outputs = pa_idxset_new(NULL, NULL);
@@ -953,7 +958,6 @@ int pa__init(pa_module*m) {
     PA_LLIST_HEAD_INIT(struct output, u->thread_info.active_outputs);
     pa_atomic_store(&u->thread_info.running, FALSE);
     u->thread_info.in_null_mode = FALSE;
-    pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
 
     if (pa_modargs_get_value_u32(ma, "adjust_time", &u->adjust_time) < 0) {
         pa_log("Failed to parse adjust_time value");
@@ -1003,19 +1007,28 @@ int pa__init(pa_module*m) {
         goto fail;
     }
 
-    if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) {
+    pa_sink_new_data_init(&data);
+    data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME));
+    data.namereg_fail = FALSE;
+    data.driver = __FILE__;
+    data.module = m;
+    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");
+
+    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->get_latency = sink_get_latency_cb;
+/*     u->sink->get_latency = sink_get_latency_cb; */
     u->sink->set_state = sink_set_state;
     u->sink->userdata = u;
 
-    u->sink->flags = PA_SINK_LATENCY;
-    pa_sink_set_module(u->sink, m);
-    pa_sink_set_description(u->sink, "Simultaneous output");
     pa_sink_set_rtpoll(u->sink, u->rtpoll);
     pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
 
@@ -1075,7 +1088,7 @@ int pa__init(pa_module*m) {
             }
         }
 
-        u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_NEW_POST], (pa_hook_cb_t) sink_new_hook_cb, u);
+        u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], (pa_hook_cb_t) sink_new_hook_cb, u);
     }
 
     u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], (pa_hook_cb_t) sink_unlink_hook_cb, u);
index b550ae7..a7fc3a3 100644 (file)
@@ -3,7 +3,7 @@
 /***
   This file is part of PulseAudio.
 
-  Copyright 2006 Lennart Poettering
+  Copyright 2006-2008 Lennart Poettering
 
   PulseAudio is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published
 #include <config.h>
 #endif
 
+#include <errno.h>
+#include <stdio.h>
+
+#include <pulse/timeval.h>
+
 #include <pulsecore/core-util.h>
 #include <pulsecore/module.h>
 #include <pulsecore/log.h>
 #include <pulsecore/namereg.h>
+#include <pulsecore/core-error.h>
 
 #include "module-default-device-restore-symdef.h"
 
@@ -39,15 +45,24 @@ PA_MODULE_LOAD_ONCE(TRUE);
 
 #define DEFAULT_SINK_FILE "default-sink"
 #define DEFAULT_SOURCE_FILE "default-source"
+#define DEFAULT_SAVE_INTERVAL 5
 
-int pa__init(pa_module *m) {
+struct userdata {
+    pa_core *core;
+    pa_subscription *subscription;
+    pa_time_event *time_event;
+    char *sink_filename, *source_filename;
+    pa_bool_t modified;
+};
+
+static void load(struct userdata *u) {
     FILE *f;
 
     /* We never overwrite manually configured settings */
 
-    if (m->core->default_sink_name)
+    if (u->core->default_sink_name)
         pa_log_info("Manually configured default sink, not overwriting.");
-    else if ((f = pa_open_config_file(NULL, DEFAULT_SINK_FILE, NULL, NULL, "r"))) {
+    else if ((f = fopen(u->sink_filename, "r"))) {
         char ln[256] = "";
 
         fgets(ln, sizeof(ln)-1, f);
@@ -55,17 +70,19 @@ int pa__init(pa_module *m) {
         fclose(f);
 
         if (!ln[0])
-            pa_log_debug("No previous default sink setting, ignoring.");
-        else if (pa_namereg_get(m->core, ln, PA_NAMEREG_SINK, 1)) {
-            pa_namereg_set_default(m->core, ln, PA_NAMEREG_SINK);
-            pa_log_debug("Restored default sink '%s'.", ln);
+            pa_log_info("No previous default sink setting, ignoring.");
+        else if (pa_namereg_get(u->core, ln, PA_NAMEREG_SINK, TRUE)) {
+            pa_namereg_set_default(u->core, ln, PA_NAMEREG_SINK);
+            pa_log_info("Restored default sink '%s'.", ln);
         } else
             pa_log_info("Saved default sink '%s' not existant, not restoring default sink setting.", ln);
-    }
 
-    if (m->core->default_source_name)
+    } else if (errno != ENOENT)
+        pa_log("Failed to load default sink: %s", pa_cstrerror(errno));
+
+    if (u->core->default_source_name)
         pa_log_info("Manually configured default source, not overwriting.");
-    else if ((f = pa_open_config_file(NULL, DEFAULT_SOURCE_FILE, NULL, NULL, "r"))) {
+    else if ((f = fopen(u->source_filename, "r"))) {
         char ln[256] = "";
 
         fgets(ln, sizeof(ln)-1, f);
@@ -73,29 +90,114 @@ int pa__init(pa_module *m) {
         fclose(f);
 
         if (!ln[0])
-            pa_log_debug("No previous default source setting, ignoring.");
-        else if (pa_namereg_get(m->core, ln, PA_NAMEREG_SOURCE, 1)) {
-            pa_namereg_set_default(m->core, ln, PA_NAMEREG_SOURCE);
-            pa_log_debug("Restored default source '%s'.", ln);
+            pa_log_info("No previous default source setting, ignoring.");
+        else if (pa_namereg_get(u->core, ln, PA_NAMEREG_SOURCE, TRUE)) {
+            pa_namereg_set_default(u->core, ln, PA_NAMEREG_SOURCE);
+            pa_log_info("Restored default source '%s'.", ln);
         } else
             pa_log_info("Saved default source '%s' not existant, not restoring default source setting.", ln);
-    }
 
-    return 0;
+    } else if (errno != ENOENT)
+            pa_log("Failed to load default sink: %s", pa_cstrerror(errno));
 }
 
-void pa__done(pa_module*m) {
+static void save(struct userdata *u) {
     FILE *f;
 
-    if ((f = pa_open_config_file(NULL, DEFAULT_SINK_FILE, NULL, NULL, "w"))) {
-        const char *n = pa_namereg_get_default_sink_name(m->core);
-        fprintf(f, "%s\n", n ? n : "");
-        fclose(f);
+    if (!u->modified)
+        return;
+
+    if (u->sink_filename) {
+        if ((f = fopen(u->sink_filename, "w"))) {
+            const char *n = pa_namereg_get_default_sink_name(u->core);
+            fprintf(f, "%s\n", pa_strempty(n));
+            fclose(f);
+        } else
+            pa_log("Failed to save default sink: %s", pa_cstrerror(errno));
     }
 
-    if ((f = pa_open_config_file(NULL, DEFAULT_SOURCE_FILE, NULL, NULL, "w"))) {
-        const char *n = pa_namereg_get_default_source_name(m->core);
-        fprintf(f, "%s\n", n ? n : "");
-        fclose(f);
+    if (u->source_filename) {
+        if ((f = fopen(u->source_filename, "w"))) {
+            const char *n = pa_namereg_get_default_source_name(u->core);
+            fprintf(f, "%s\n", pa_strempty(n));
+            fclose(f);
+        } else
+            pa_log("Failed to save default source: %s", pa_cstrerror(errno));
+    }
+
+    u->modified = FALSE;
+}
+
+static void time_cb(pa_mainloop_api *a, pa_time_event *e, const struct timeval *tv, void *userdata) {
+    struct userdata *u = userdata;
+
+    pa_assert(u);
+    save(u);
+
+    if (u->time_event) {
+        u->core->mainloop->time_free(u->time_event);
+        u->time_event = NULL;
+    }
+}
+
+static void subscribe_cb(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
+    struct userdata *u = userdata;
+
+    pa_assert(u);
+
+    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);
     }
 }
+
+int pa__init(pa_module *m) {
+    struct userdata *u;
+
+    pa_assert(u);
+
+    u = pa_xnew0(struct userdata, 1);
+    u->core = m->core;
+
+    if (!(u->sink_filename = pa_runtime_path(DEFAULT_SINK_FILE)))
+        goto fail;
+
+    if (!(u->source_filename = pa_runtime_path(DEFAULT_SOURCE_FILE)))
+        goto fail;
+
+    load(u);
+
+    u->subscription = pa_subscription_new(u->core, PA_SUBSCRIPTION_MASK_SERVER, subscribe_cb, u);
+
+    return 0;
+
+fail:
+    pa__done(m);
+
+    return -1;
+}
+
+void pa__done(pa_module*m) {
+    struct userdata *u;
+
+    pa_assert(m);
+
+    if (!(u = m->userdata))
+        return;
+
+    save(u);
+
+    if (u->subscription)
+        pa_subscription_free(u->subscription);
+
+    if (u->time_event)
+        m->core->mainloop->time_free(u->time_event);
+
+    pa_xfree(u->sink_filename);
+    pa_xfree(u->source_filename);
+    pa_xfree(u);
+}
diff --git a/src/modules/module-device-restore.c b/src/modules/module-device-restore.c
new file mode 100644 (file)
index 0000000..0a41b84
--- /dev/null
@@ -0,0 +1,349 @@
+/* $Id$ */
+
+/***
+  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 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <gdbm.h>
+
+#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-device-restore-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("Automatically restore the volume/mute state of devices");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(TRUE);
+
+#define SAVE_INTERVAL 10
+
+static const char* const valid_modargs[] = {
+    NULL,
+};
+
+struct userdata {
+    pa_core *core;
+    pa_subscription *subscription;
+    pa_hook_slot *sink_fixate_hook_slot, *source_fixate_hook_slot;
+    pa_time_event *save_time_event;
+    GDBM_FILE gdbm_file;
+};
+
+struct entry {
+    pa_cvolume volume;
+    int muted;
+};
+
+static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, 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_log_info("Synced.");
+}
+
+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;
+    char *name;
+    datum key, data;
+
+    pa_assert(c);
+    pa_assert(u);
+
+    if (t != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW) &&
+        t != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE) &&
+        t != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW) &&
+        t != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE))
+        return;
+
+    if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK) {
+        pa_sink *sink;
+
+        if (!(sink = pa_idxset_get_by_index(c->sinks, idx)))
+            return;
+
+        name = pa_sprintf_malloc("sink:%s", sink->name);
+        entry.volume = *pa_sink_get_volume(sink);
+        entry.muted = pa_sink_get_mute(sink);
+
+    } else {
+        pa_source *source;
+
+        pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE);
+
+        if (!(source = pa_idxset_get_by_index(c->sources, idx)))
+            return;
+
+        name = pa_sprintf_malloc("source:%s", source->name);
+        entry.volume = *pa_source_get_volume(source);
+        entry.muted = pa_source_get_mute(source);
+    }
+
+    key.dptr = name;
+    key.dsize = strlen(name);
+
+    data = gdbm_fetch(u->gdbm_file, key);
+
+    if (data.dptr) {
+
+        if (data.dsize == sizeof(struct entry)) {
+            struct entry *old = (struct entry*) data.dptr;
+
+            if (pa_cvolume_valid(&old->volume)) {
+
+                if (pa_cvolume_equal(&old->volume, &entry.volume) &&
+                    !old->muted == !entry.muted) {
+
+                    pa_xfree(data.dptr);
+                    pa_xfree(name);
+                    return;
+                }
+            } else
+                pa_log_warn("Invalid volume stored in database for device %s", name);
+
+        } else
+            pa_log_warn("Database contains entry for device %s of wrong size %lu != %lu", name, (unsigned long) data.dsize, (unsigned long) sizeof(struct entry));
+
+        pa_xfree(data.dptr);
+    }
+
+    data.dptr = (void*) &entry;
+    data.dsize = sizeof(entry);
+
+    pa_log_info("Storing volume/mute for device %s.", name);
+
+    gdbm_store(u->gdbm_file, key, data, GDBM_REPLACE);
+
+    if (!u->save_time_event) {
+        struct timeval tv;
+        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);
+    }
+
+    pa_xfree(name);
+}
+
+static struct entry* read_entry(struct userdata *u, char *name) {
+    datum key, data;
+    struct entry *e;
+
+    pa_assert(u);
+    pa_assert(name);
+
+    key.dptr = name;
+    key.dsize = strlen(name);
+
+    data = gdbm_fetch(u->gdbm_file, key);
+
+    if (!data.dptr)
+        goto fail;
+
+    if (data.dsize != sizeof(struct entry)) {
+        pa_log_warn("Database contains entry for device %s of wrong size %lu != %lu", name, (unsigned long) data.dsize, (unsigned long) sizeof(struct entry));
+        goto fail;
+    }
+
+    e = (struct entry*) data.dptr;
+
+    if (!(pa_cvolume_valid(&e->volume))) {
+        pa_log_warn("Invalid volume stored in database for device %s", name);
+        goto fail;
+    }
+
+    return e;
+
+fail:
+
+    pa_xfree(data.dptr);
+    return NULL;
+}
+
+
+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(new_data);
+
+    name = pa_sprintf_malloc("sink:%s", new_data->name);
+
+    if ((e = read_entry(u, name))) {
+
+        if (e->volume.channels == new_data->sample_spec.channels) {
+            pa_log_info("Restoring volume for sink %s.", new_data->name);
+            pa_sink_new_data_set_volume(new_data, &e->volume);
+        }
+
+        pa_log_info("Restoring mute state for sink %s.", new_data->name);
+        pa_sink_new_data_set_muted(new_data, e->muted);
+        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(new_data);
+
+    name = pa_sprintf_malloc("source:%s", new_data->name);
+
+    if ((e = read_entry(u, name))) {
+
+        if (e->volume.channels == new_data->sample_spec.channels) {
+            pa_log_info("Restoring volume for source %s.", new_data->name);
+            pa_source_new_data_set_volume(new_data, &e->volume);
+        }
+
+        pa_log_info("Restoring mute state for source %s.", new_data->name);
+        pa_source_new_data_set_muted(new_data, e->muted);
+        pa_xfree(e);
+    }
+
+    pa_xfree(name);
+
+    return PA_HOOK_OK;
+}
+
+int pa__init(pa_module*m) {
+    pa_modargs *ma = NULL;
+    struct userdata *u;
+    char *fname, *runtime_dir;
+    char hn[256];
+    pa_sink *sink;
+    pa_source *source;
+    uint32_t idx;
+
+    pa_assert(m);
+
+    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+        pa_log("Failed to parse module arguments");
+        goto fail;
+    }
+
+    u = pa_xnew(struct userdata, 1);
+    u->core = m->core;
+    u->save_time_event = NULL;
+
+    u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SOURCE, subscribe_callback, u);
+
+    u->sink_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_FIXATE], (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_cb_t) source_fixate_hook_callback, u);
+
+    m->userdata = u;
+
+    if (!pa_get_host_name(hn, sizeof(hn)))
+        goto fail;
+
+    if (!(runtime_dir = pa_get_runtime_dir()))
+        goto fail;
+
+    fname = pa_sprintf_malloc("%s/device-volumes.%s.gdbm", runtime_dir, hn);
+    pa_xfree(runtime_dir);
+
+    if (!(u->gdbm_file = gdbm_open(fname, 0, GDBM_WRCREAT, 0600, NULL))) {
+        pa_log("Failed to open volume database '%s': %s", fname, gdbm_strerror(gdbm_errno));
+        pa_xfree(fname);
+        goto fail;
+    }
+
+    pa_log_info("Sucessfully opened database file '%s'.", fname);
+    pa_xfree(fname);
+
+    for (sink = pa_idxset_first(m->core->sinks, &idx); sink; sink = pa_idxset_next(m->core->sinks, &idx))
+        subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW, sink->index, u);
+
+    for (source = pa_idxset_first(m->core->sources, &idx); source; source = pa_idxset_next(m->core->sources, &idx))
+        subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW, source->index, 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->subscription)
+        pa_subscription_free(u->subscription);
+
+    if (u->sink_fixate_hook_slot)
+        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->save_time_event)
+        u->core->mainloop->time_free(u->save_time_event);
+
+    if (u->gdbm_file)
+        gdbm_close(u->gdbm_file);
+
+    pa_xfree(u);
+}
index f9bea63..87b87c3 100644 (file)
@@ -143,7 +143,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
             switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) {
 
                 case PA_SINK_SUSPENDED:
-                    pa_assert(PA_SINK_OPENED(u->sink->thread_info.state));
+                    pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state));
 
                     pa_smoother_pause(u->smoother, pa_rtclock_usec());
                     break;
@@ -211,7 +211,7 @@ static void thread_func(void *userdata) {
             pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
 
             /* Render some data and write it to the fifo */
-            if (PA_SINK_OPENED(u->sink->thread_info.state) && pollfd->revents) {
+            if (PA_SINK_IS_OPENED(u->sink->thread_info.state) && pollfd->revents) {
                 pa_usec_t usec;
                 int64_t n;
 
@@ -294,7 +294,7 @@ static void thread_func(void *userdata) {
             }
 
             /* Hmm, nothing to do. Let's sleep */
-            pollfd->events = PA_SINK_OPENED(u->sink->thread_info.state)  ? POLLOUT : 0;
+            pollfd->events = PA_SINK_IS_OPENED(u->sink->thread_info.state) ? POLLOUT : 0;
         }
 
         if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0)
@@ -502,12 +502,11 @@ static void on_connection(PA_GCC_UNUSED pa_socket_client *c, pa_iochannel*io, vo
 
 int pa__init(pa_module*m) {
     struct userdata *u = NULL;
-    const char *p;
     pa_sample_spec ss;
     pa_modargs *ma = NULL;
-    char *t;
     const char *espeaker;
     uint32_t key;
+    pa_sink_new_data data;
 
     pa_assert(m);
 
@@ -533,13 +532,12 @@ 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);
+    u->smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC*2, TRUE, 10);
     pa_memchunk_reset(&u->memchunk);
     u->offset = 0;
 
-    pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
     u->rtpoll = pa_rtpoll_new();
-    pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
+    pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll);
     u->rtpoll_item = NULL;
 
     u->format =
@@ -554,30 +552,38 @@ int pa__init(pa_module*m) {
     u->state = STATE_AUTH;
     u->latency = 0;
 
-    if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL))) {
+    if (!(espeaker = getenv("ESPEAKER")))
+        espeaker = ESD_UNIX_SOCKET_NAME;
+
+    espeaker = pa_modargs_get_value(ma, "server", espeaker);
+
+    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, espeaker);
+    pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Esound sink '%s'", espeaker);
+
+    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->flags = PA_SINK_LATENCY|PA_SINK_NETWORK;
 
-    pa_sink_set_module(u->sink, m);
     pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
     pa_sink_set_rtpoll(u->sink, u->rtpoll);
 
-    if (!(espeaker = getenv("ESPEAKER")))
-        espeaker = ESD_UNIX_SOCKET_NAME;
-
-    if (!(u->client = pa_socket_client_new_string(u->core->mainloop, p = pa_modargs_get_value(ma, "server", espeaker), ESD_DEFAULT_PORT))) {
+    if (!(u->client = pa_socket_client_new_string(u->core->mainloop, espeaker, ESD_DEFAULT_PORT))) {
         pa_log("Failed to connect to server.");
         goto fail;
     }
 
-    pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Esound sink '%s'", p));
-    pa_xfree(t);
-
     pa_socket_client_set_callback(u->client, on_connection, u);
 
     /* Prepare the initial request */
index 832bc73..44b31a5 100644 (file)
@@ -372,7 +372,7 @@ static int hal_device_add_all(struct userdata *u, const char *capability) {
                 pa_log_debug("Not loaded device %s", udis[i]);
             else {
                 if (d->sink_name)
-                    pa_scache_play_item_by_name(u->core, "pulse-coldplug", d->sink_name, PA_VOLUME_NORM, 0);
+                    pa_scache_play_item_by_name(u->core, "pulse-coldplug", d->sink_name, FALSE, PA_VOLUME_NORM, NULL, NULL);
                 count++;
             }
         }
@@ -412,7 +412,7 @@ static void device_added_time_cb(pa_mainloop_api *ea, pa_time_event *ev, const s
                 pa_log_debug("Not loaded device %s", td->udi);
             else {
                 if (d->sink_name)
-                    pa_scache_play_item_by_name(td->u->core, "pulse-hotplug", d->sink_name, PA_VOLUME_NORM, 0);
+                    pa_scache_play_item_by_name(td->u->core, "pulse-hotplug", d->sink_name, FALSE, PA_VOLUME_NORM, NULL, NULL);
             }
         }
     }
@@ -575,7 +575,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo
                         if (prev_suspended && !suspend) {
                             /* resume */
                             if (pa_sink_suspend(sink, 0) >= 0)
-                                pa_scache_play_item_by_name(u->core, "pulse-access", d->sink_name, PA_VOLUME_NORM, 0);
+                                pa_scache_play_item_by_name(u->core, "pulse-access", d->sink_name, FALSE, PA_VOLUME_NORM, NULL, NULL);
                             else
                                 d->acl_race_fix = 1;
 
@@ -643,7 +643,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo
                     if (prev_suspended) {
                         /* resume */
                         if (pa_sink_suspend(sink, 0) >= 0)
-                            pa_scache_play_item_by_name(u->core, "pulse-access", d->sink_name, PA_VOLUME_NORM, 0);
+                            pa_scache_play_item_by_name(u->core, "pulse-access", d->sink_name, FALSE, PA_VOLUME_NORM, NULL, NULL);
                     }
                 }
             }
index a42aa9e..1ef5d23 100644 (file)
@@ -53,7 +53,7 @@
 
 /* General overview:
  *
- * Because JACK has a very unflexible event loop management, which
+ * 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
@@ -276,7 +276,7 @@ int pa__init(pa_module*m) {
     pa_bool_t do_connect = TRUE;
     unsigned i;
     const char **ports = NULL, **p;
-    char *t;
+    pa_sink_new_data data;
 
     pa_assert(m);
 
@@ -300,9 +300,8 @@ int pa__init(pa_module*m) {
     u->module = m;
     m->userdata = u;
     u->saved_frame_time_valid = FALSE;
-    pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
     u->rtpoll = pa_rtpoll_new();
-    pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
+    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);
@@ -312,7 +311,7 @@ int pa__init(pa_module*m) {
      * 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(u->rtpoll, PA_RTPOLL_EARLY-1, u->jack_msgq);
+    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.");
@@ -355,20 +354,31 @@ int pa__init(pa_module*m) {
         }
     }
 
-    if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) {
-        pa_log("failed to create sink.");
+    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));
+
+    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;
-    u->sink->flags = PA_SINK_LATENCY;
 
-    pa_sink_set_module(u->sink, m);
     pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
     pa_sink_set_rtpoll(u->sink, u->rtpoll);
-    pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Jack sink (%s)", jack_get_client_name(u->client)));
-    pa_xfree(t);
 
     jack_set_process_callback(u->client, jack_process, u);
     jack_on_shutdown(u->client, jack_shutdown, u);
index 4ee08bf..fa2ec5e 100644 (file)
@@ -253,7 +253,7 @@ int pa__init(pa_module*m) {
     pa_bool_t do_connect = TRUE;
     unsigned i;
     const char **ports = NULL, **p;
-    char *t;
+    pa_source_new_data data;
 
     pa_assert(m);
 
@@ -278,12 +278,11 @@ int pa__init(pa_module*m) {
     m->userdata = u;
     u->saved_frame_time_valid = FALSE;
 
-    pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
     u->rtpoll = pa_rtpoll_new();
-    pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
+    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(u->rtpoll, PA_RTPOLL_EARLY-1, u->jack_msgq);
+    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.");
@@ -326,20 +325,31 @@ int pa__init(pa_module*m) {
         }
     }
 
-    if (!(u->source = pa_source_new(m->core, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, &map))) {
-        pa_log("failed to create source.");
+    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));
+
+    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;
-    u->source->flags = PA_SOURCE_LATENCY;
 
-    pa_source_set_module(u->source, m);
     pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
     pa_source_set_rtpoll(u->source, u->rtpoll);
-    pa_source_set_description(u->source, t = pa_sprintf_malloc("Jack source (%s)", jack_get_client_name(u->client)));
-    pa_xfree(t);
 
     jack_set_process_callback(u->client, jack_process, u);
     jack_on_shutdown(u->client, jack_shutdown, u);
index fcfeffd..245efcb 100644 (file)
@@ -3,7 +3,7 @@
 /***
   This file is part of PulseAudio.
 
-  Copyright 2004-2006 Lennart Poettering
+  Copyright 2004-2008 Lennart Poettering
 
   PulseAudio is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published
@@ -41,6 +41,7 @@
 #include <pulsecore/thread-mq.h>
 #include <pulsecore/rtpoll.h>
 #include <pulsecore/sample-util.h>
+#include <pulsecore/ltdl-helper.h>
 
 #include "module-ladspa-sink-symdef.h"
 #include "ladspa.h"
@@ -60,6 +61,8 @@ PA_MODULE_USAGE(
         "label=<ladspa plugin label> "
         "control=<comma seperated list of input control values>");
 
+#define MEMBLOCKQ_MAXLENGTH (16*1024*1024)
+
 struct userdata {
     pa_core *core;
     pa_module *module;
@@ -79,7 +82,7 @@ struct userdata {
        about control out ports. We connect them all to this single buffer. */
     LADSPA_Data control_out;
 
-    pa_memchunk memchunk;
+    pa_memblockq *memblockq;
 };
 
 static const char* const valid_modargs[] = {
@@ -104,10 +107,14 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
         case PA_SINK_MESSAGE_GET_LATENCY: {
             pa_usec_t usec = 0;
 
+            /* Get the latency of the master sink */
             if (PA_MSGOBJECT(u->master)->process_msg(PA_MSGOBJECT(u->master), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0)
                 usec = 0;
 
-            *((pa_usec_t*) data) = usec + pa_bytes_to_usec(u->memchunk.length, &u->sink->sample_spec);
+            /* Add the latency internal to our sink input on top */
+            usec += pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->master->sample_spec);
+
+            *((pa_usec_t*) data) = usec;
             return 0;
         }
     }
@@ -122,110 +129,143 @@ static int sink_set_state(pa_sink *s, pa_sink_state_t state) {
     pa_sink_assert_ref(s);
     pa_assert_se(u = s->userdata);
 
-    if (PA_SINK_LINKED(state) && u->sink_input && PA_SINK_INPUT_LINKED(pa_sink_input_get_state(u->sink_input)))
+    if (PA_SINK_IS_LINKED(state) &&
+        u->sink_input &&
+        PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
+
         pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED);
 
     return 0;
 }
 
 /* Called from I/O thread context */
-static int sink_input_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
-    struct userdata *u = PA_SINK_INPUT(o)->userdata;
+static void sink_request_rewind(pa_sink *s) {
+    struct userdata *u;
 
-    switch (code) {
-        case PA_SINK_INPUT_MESSAGE_GET_LATENCY:
-            *((pa_usec_t*) data) = pa_bytes_to_usec(u->memchunk.length, &u->sink_input->sample_spec);
+    pa_sink_assert_ref(s);
+    pa_assert_se(u = s->userdata);
 
-            /* Fall through, the default handler will add in the extra
-             * latency added by the resampler */
-            break;
-    }
+    /* Just hand this one over to the master sink */
+    pa_sink_input_request_rewind(u->sink_input, s->thread_info.rewind_nbytes + pa_memblockq_get_length(u->memblockq), TRUE, FALSE);
+}
 
-    return pa_sink_input_process_msg(o, code, data, offset, chunk);
+/* Called from I/O thread context */
+static void sink_update_requested_latency(pa_sink *s) {
+    struct userdata *u;
+
+    pa_sink_assert_ref(s);
+    pa_assert_se(u = s->userdata);
+
+    /* Just hand this one over to the master sink */
+    pa_sink_input_set_requested_latency_within_thread(
+            u->sink_input,
+            pa_sink_get_requested_latency_within_thread(s));
 }
 
 /* Called from I/O thread context */
-static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
+static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) {
     struct userdata *u;
+    float *src, *dst;
+    size_t fs;
+    unsigned n, c;
+    pa_memchunk tchunk;
 
     pa_sink_input_assert_ref(i);
+    pa_assert(chunk);
     pa_assert_se(u = i->userdata);
 
-    if (!u->memchunk.memblock) {
-        pa_memchunk tchunk;
-        float *src, *dst;
-        size_t fs;
-        unsigned n, c;
-
-        pa_sink_render(u->sink, length, &tchunk);
-
-        fs = pa_frame_size(&i->sample_spec);
-        n = tchunk.length / fs;
+    if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state))
+        return -1;
 
-        pa_assert(n > 0);
+    while (pa_memblockq_peek(u->memblockq, &tchunk) < 0) {
+        pa_memchunk nchunk;
 
-        u->memchunk.memblock = pa_memblock_new(i->sink->core->mempool, tchunk.length);
-        u->memchunk.index = 0;
-        u->memchunk.length = tchunk.length;
+        pa_sink_render(u->sink, nbytes, &nchunk);
+        pa_memblockq_push(u->memblockq, &nchunk);
+        pa_memblock_unref(nchunk.memblock);
+    }
 
-        src = (float*) ((uint8_t*) pa_memblock_acquire(tchunk.memblock) + tchunk.index);
-        dst = (float*) pa_memblock_acquire(u->memchunk.memblock);
+    pa_assert(tchunk.length > 0);
 
-        for (c = 0; c < u->channels; c++) {
-            unsigned j;
-            float *p, *q;
+    fs = pa_frame_size(&i->sample_spec);
+    n = PA_MIN(tchunk.length, u->block_size) / fs;
 
-            p = src + c;
-            q = u->input;
-            for (j = 0; j < n; j++, p += u->channels, q++)
-                *q = PA_CLAMP_UNLIKELY(*p, -1.0, 1.0);
+    pa_assert(n > 0);
 
-            u->descriptor->run(u->handle[c], n);
+    chunk->index = 0;
+    chunk->length = n*fs;
+    chunk->memblock = pa_memblock_new(i->sink->core->mempool, chunk->length);
 
-            q = u->output;
-            p = dst + c;
-            for (j = 0; j < n; j++, q++, p += u->channels)
-                *p = PA_CLAMP_UNLIKELY(*q, -1.0, 1.0);
-        }
+    pa_memblockq_drop(u->memblockq, chunk->length);
 
-        pa_memblock_release(tchunk.memblock);
-        pa_memblock_release(u->memchunk.memblock);
+    src = (float*) ((uint8_t*) pa_memblock_acquire(tchunk.memblock) + tchunk.index);
+    dst = (float*) pa_memblock_acquire(chunk->memblock);
 
-        pa_memblock_unref(tchunk.memblock);
+    for (c = 0; c < u->channels; c++) {
+        pa_sample_clamp(PA_SAMPLE_FLOAT32NE, u->input, sizeof(float), src+c, u->channels*sizeof(float), n);
+        u->descriptor->run(u->handle[c], n);
+        pa_sample_clamp(PA_SAMPLE_FLOAT32NE, dst+c, u->channels*sizeof(float), u->output, sizeof(float), n);
     }
 
-    pa_assert(u->memchunk.length > 0);
-    pa_assert(u->memchunk.memblock);
+    pa_memblock_release(tchunk.memblock);
+    pa_memblock_release(chunk->memblock);
 
-    *chunk = u->memchunk;
-    pa_memblock_ref(chunk->memblock);
+    pa_memblock_unref(tchunk.memblock);
 
     return 0;
 }
 
 /* Called from I/O thread context */
-static void sink_input_drop_cb(pa_sink_input *i, size_t length) {
+static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
     struct userdata *u;
 
     pa_sink_input_assert_ref(i);
     pa_assert_se(u = i->userdata);
-    pa_assert(length > 0);
+    pa_assert(nbytes > 0);
 
-    if (u->memchunk.memblock) {
+    if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state))
+        return;
 
-        if (length < u->memchunk.length) {
-            u->memchunk.index += length;
-            u->memchunk.length -= length;
-            return;
-        }
+    if (u->sink->thread_info.rewind_nbytes > 0) {
+        size_t max_rewrite, amount;
+
+        max_rewrite = nbytes + pa_memblockq_get_length(u->memblockq);
+        amount = PA_MIN(u->sink->thread_info.rewind_nbytes, max_rewrite);
+        u->sink->thread_info.rewind_nbytes = 0;
 
-        pa_memblock_unref(u->memchunk.memblock);
-        length -= u->memchunk.length;
-        pa_memchunk_reset(&u->memchunk);
+        if (amount > 0) {
+            unsigned c;
+
+            pa_memblockq_seek(u->memblockq, - (int64_t) amount, PA_SEEK_RELATIVE);
+            pa_sink_process_rewind(u->sink, amount);
+
+            pa_log_debug("Resetting plugin");
+
+            /* Reset the plugin */
+            if (u->descriptor->deactivate)
+                for (c = 0; c < u->channels; c++)
+                    u->descriptor->deactivate(u->handle[c]);
+            if (u->descriptor->activate)
+                for (c = 0; c < u->channels; c++)
+                    u->descriptor->activate(u->handle[c]);
+        }
     }
 
-    if (length > 0)
-        pa_sink_skip(u->sink, length);
+    pa_memblockq_rewind(u->memblockq, nbytes);
+}
+
+/* Called from I/O thread context */
+static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
+    struct userdata *u;
+
+    pa_sink_input_assert_ref(i);
+    pa_assert_se(u = i->userdata);
+
+    if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state))
+        return;
+
+    pa_memblockq_set_maxrewind(u->memblockq, nbytes);
+    pa_sink_set_max_rewind(u->sink, nbytes);
 }
 
 /* Called from I/O thread context */
@@ -235,7 +275,12 @@ static void sink_input_detach_cb(pa_sink_input *i) {
     pa_sink_input_assert_ref(i);
     pa_assert_se(u = i->userdata);
 
+    if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state))
+        return;
+
     pa_sink_detach_within_thread(u->sink);
+    pa_sink_set_asyncmsgq(u->sink, NULL);
+    pa_sink_set_rtpoll(u->sink, NULL);
 }
 
 /* Called from I/O thread context */
@@ -245,10 +290,15 @@ static void sink_input_attach_cb(pa_sink_input *i) {
     pa_sink_input_assert_ref(i);
     pa_assert_se(u = i->userdata);
 
+    if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state))
+        return;
+
     pa_sink_set_asyncmsgq(u->sink, i->sink->asyncmsgq);
     pa_sink_set_rtpoll(u->sink, i->sink->rtpoll);
-
     pa_sink_attach_within_thread(u->sink);
+
+    u->sink->max_latency = u->master->max_latency;
+    u->sink->min_latency = u->master->min_latency;
 }
 
 /* Called from main context */
@@ -258,25 +308,43 @@ static void sink_input_kill_cb(pa_sink_input *i) {
     pa_sink_input_assert_ref(i);
     pa_assert_se(u = i->userdata);
 
+    pa_sink_unlink(u->sink);
     pa_sink_input_unlink(u->sink_input);
-    pa_sink_input_unref(u->sink_input);
-    u->sink_input = NULL;
 
-    pa_sink_unlink(u->sink);
     pa_sink_unref(u->sink);
     u->sink = NULL;
+    pa_sink_input_unref(u->sink_input);
+    u->sink_input = NULL;
 
     pa_module_unload_request(u->module);
 }
 
+/* Called from IO thread context */
+static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) {
+    struct userdata *u;
+
+    pa_sink_input_assert_ref(i);
+    pa_assert_se(u = i->userdata);
+
+    /* If we are added for the first time, ask for a rewinding so that
+     * we are heard right-away. */
+    if (PA_SINK_INPUT_IS_LINKED(state) &&
+        i->thread_info.state == PA_SINK_INPUT_INIT) {
+        pa_log_debug("Requesting rewind due to state change.");
+        pa_sink_input_request_rewind(i, 0, FALSE, TRUE);
+    }
+}
+
 int pa__init(pa_module*m) {
     struct userdata *u;
     pa_sample_spec ss;
     pa_channel_map map;
     pa_modargs *ma;
     char *t;
+    const char *z;
     pa_sink *master;
-    pa_sink_input_new_data data;
+    pa_sink_input_new_data sink_input_data;
+    pa_sink_new_data sink_data;
     const char *plugin, *label;
     LADSPA_Descriptor_Function descriptor_func;
     const char *e, *cdata;
@@ -284,7 +352,6 @@ int pa__init(pa_module*m) {
     unsigned long input_port, output_port, p, j, n_control;
     unsigned c;
     pa_bool_t *use_default = NULL;
-    char *default_sink_name = NULL;
 
     pa_assert(m);
 
@@ -325,7 +392,9 @@ int pa__init(pa_module*m) {
     u->module = m;
     m->userdata = u;
     u->master = master;
-    pa_memchunk_reset(&u->memchunk);
+    u->sink = NULL;
+    u->sink_input = NULL;
+    u->memblockq = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0, pa_frame_size(&ss), 1, 1, 0, NULL);
 
     if (!(e = getenv("LADSPA_PATH")))
         e = LADSPA_PATH;
@@ -342,7 +411,7 @@ int pa__init(pa_module*m) {
         goto fail;
     }
 
-    if (!(descriptor_func = (LADSPA_Descriptor_Function) lt_dlsym(m->dl, "ladspa_descriptor"))) {
+    if (!(descriptor_func = (LADSPA_Descriptor_Function) pa_load_sym(m->dl, NULL, "ladspa_descriptor"))) {
         pa_log("LADSPA module lacks ladspa_descriptor() symbol.");
         goto fail;
     }
@@ -350,7 +419,7 @@ int pa__init(pa_module*m) {
     for (j = 0;; j++) {
 
         if (!(d = descriptor_func(j))) {
-            pa_log("Failed to find plugin label '%s' in plugin '%s'.", plugin, label);
+            pa_log("Failed to find plugin label '%s' in plugin '%s'.", label, plugin);
             goto fail;
         }
 
@@ -582,43 +651,66 @@ int pa__init(pa_module*m) {
         for (c = 0; c < u->channels; c++)
             d->activate(u->handle[c]);
 
-    default_sink_name = pa_sprintf_malloc("%s.ladspa", master->name);
-
     /* Create sink */
-    if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", default_sink_name), 0, &ss, &map))) {
+    pa_sink_new_data_init(&sink_data);
+    sink_data.driver = __FILE__;
+    sink_data.module = m;
+    if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL))))
+        sink_data.name = pa_sprintf_malloc("%s.ladspa", master->name);
+    sink_data.namereg_fail = FALSE;
+    pa_sink_new_data_set_sample_spec(&sink_data, &ss);
+    pa_sink_new_data_set_channel_map(&sink_data, &map);
+    z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION);
+    pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "LADSPA Plugin %s on %s", label, z ? z : master->name);
+    pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name);
+    pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter");
+    pa_proplist_sets(sink_data.proplist, "device.ladspa.module", plugin);
+    pa_proplist_sets(sink_data.proplist, "device.ladspa.label", d->Label);
+    pa_proplist_sets(sink_data.proplist, "device.ladspa.name", d->Name);
+    pa_proplist_sets(sink_data.proplist, "device.ladspa.maker", d->Maker);
+    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);
+    pa_sink_new_data_done(&sink_data);
+
+    if (!u->sink) {
         pa_log("Failed to create sink.");
         goto fail;
     }
 
     u->sink->parent.process_msg = sink_process_msg;
     u->sink->set_state = sink_set_state;
+    u->sink->update_requested_latency = sink_update_requested_latency;
+    u->sink->request_rewind = sink_request_rewind;
     u->sink->userdata = u;
-    u->sink->flags = PA_SINK_LATENCY;
 
-    pa_sink_set_module(u->sink, m);
-    pa_sink_set_description(u->sink, t = pa_sprintf_malloc("LADSPA plugin '%s' on '%s'", label, master->description));
-    pa_xfree(t);
     pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq);
     pa_sink_set_rtpoll(u->sink, master->rtpoll);
 
     /* Create sink input */
-    pa_sink_input_new_data_init(&data);
-    data.sink = u->master;
-    data.driver = __FILE__;
-    data.name = "LADSPA Stream";
-    pa_sink_input_new_data_set_sample_spec(&data, &ss);
-    pa_sink_input_new_data_set_channel_map(&data, &map);
-    data.module = m;
-
-    if (!(u->sink_input = pa_sink_input_new(m->core, &data, PA_SINK_INPUT_DONT_MOVE)))
+    pa_sink_input_new_data_init(&sink_input_data);
+    sink_input_data.driver = __FILE__;
+    sink_input_data.module = m;
+    sink_input_data.sink = u->master;
+    pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "LADSPA Stream");
+    pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
+    pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss);
+    pa_sink_input_new_data_set_channel_map(&sink_input_data, &map);
+
+    u->sink_input = pa_sink_input_new(m->core, &sink_input_data, PA_SINK_INPUT_DONT_MOVE);
+    pa_sink_input_new_data_done(&sink_input_data);
+
+    if (!u->sink_input)
         goto fail;
 
-    u->sink_input->parent.process_msg = sink_input_process_msg;
-    u->sink_input->peek = sink_input_peek_cb;
-    u->sink_input->drop = sink_input_drop_cb;
+    u->sink_input->pop = sink_input_pop_cb;
+    u->sink_input->process_rewind = sink_input_process_rewind_cb;
+    u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
     u->sink_input->kill = sink_input_kill_cb;
     u->sink_input->attach = sink_input_attach_cb;
     u->sink_input->detach = sink_input_detach_cb;
+    u->sink_input->state_change = sink_input_state_change_cb;
     u->sink_input->userdata = u;
 
     pa_sink_put(u->sink);
@@ -627,7 +719,6 @@ int pa__init(pa_module*m) {
     pa_modargs_free(ma);
 
     pa_xfree(use_default);
-    pa_xfree(default_sink_name);
 
     return 0;
 
@@ -636,7 +727,6 @@ fail:
         pa_modargs_free(ma);
 
     pa_xfree(use_default);
-    pa_xfree(default_sink_name);
 
     pa__done(m);
 
@@ -652,18 +742,15 @@ void pa__done(pa_module*m) {
     if (!(u = m->userdata))
         return;
 
-    if (u->sink_input) {
-        pa_sink_input_unlink(u->sink_input);
-        pa_sink_input_unref(u->sink_input);
-    }
-
     if (u->sink) {
         pa_sink_unlink(u->sink);
         pa_sink_unref(u->sink);
     }
 
-    if (u->memchunk.memblock)
-        pa_memblock_unref(u->memchunk.memblock);
+    if (u->sink_input) {
+        pa_sink_input_unlink(u->sink_input);
+        pa_sink_input_unref(u->sink_input);
+    }
 
     for (c = 0; c < u->channels; c++)
         if (u->handle[c]) {
@@ -675,6 +762,9 @@ void pa__done(pa_module*m) {
     if (u->output != u->input)
         pa_xfree(u->output);
 
+    if (u->memblockq)
+        pa_memblockq_free(u->memblockq);
+
     pa_xfree(u->input);
 
     pa_xfree(u->control);
index ed5f307..d026545 100644 (file)
@@ -82,12 +82,14 @@ static int load_rules(struct userdata *u, const char *filename) {
 
     pa_assert(u);
 
-    f = filename ?
-        fopen(fn = pa_xstrdup(filename), "r") :
-        pa_open_config_file(DEFAULT_MATCH_TABLE_FILE, DEFAULT_MATCH_TABLE_FILE_USER, NULL, &fn, "r");
+    if (filename)
+        f = fopen(fn = pa_xstrdup(filename), "r");
+    else
+        f = pa_open_config_file(DEFAULT_MATCH_TABLE_FILE, DEFAULT_MATCH_TABLE_FILE_USER, NULL, &fn);
 
     if (!f) {
-        pa_log("failed to open file '%s': %s", fn, pa_cstrerror(errno));
+        pa_xfree(fn);
+        pa_log("Failed to open file config file: %s", pa_cstrerror(errno));
         goto finish;
     }
 
@@ -166,6 +168,7 @@ static void callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, v
     struct userdata *u =  userdata;
     pa_sink_input *si;
     struct rule *r;
+    const char *n;
 
     pa_assert(c);
     pa_assert(u);
@@ -176,13 +179,13 @@ static void callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, v
     if (!(si = pa_idxset_get_by_index(c->sink_inputs, idx)))
         return;
 
-    if (!si->name)
+    if (!(n = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_NAME)))
         return;
 
     for (r = u->rules; r; r = r->next) {
-        if (!regexec(&r->regex, si->name, 0, NULL, 0)) {
+        if (!regexec(&r->regex, n, 0, NULL, 0)) {
             pa_cvolume cv;
-            pa_log_debug("changing volume of sink input '%s' to 0x%03x", si->name, r->volume);
+            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);
         }
index de35fff..aff244f 100644 (file)
@@ -3,7 +3,7 @@
 /***
   This file is part of PulseAudio.
 
-  Copyright 2004-2006 Lennart Poettering
+  Copyright 2004-2008 Lennart Poettering
 
   PulseAudio is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published
@@ -64,6 +64,7 @@ PA_MODULE_USAGE(
         "description=<description for the sink>");
 
 #define DEFAULT_SINK_NAME "null"
+#define MAX_LATENCY_USEC (PA_USEC_PER_SEC * 2)
 
 struct userdata {
     pa_core *core;
@@ -76,7 +77,8 @@ struct userdata {
 
     size_t block_size;
 
-    struct timeval timestamp;
+    pa_usec_t block_usec;
+    pa_usec_t timestamp;
 };
 
 static const char* const valid_modargs[] = {
@@ -96,26 +98,95 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
         case PA_SINK_MESSAGE_SET_STATE:
 
             if (PA_PTR_TO_UINT(data) == PA_SINK_RUNNING)
-                pa_rtclock_get(&u->timestamp);
+                u->timestamp = pa_rtclock_usec();
 
             break;
 
         case PA_SINK_MESSAGE_GET_LATENCY: {
-            struct timeval now;
+            pa_usec_t now;
 
-            pa_rtclock_get(&now);
+            now = pa_rtclock_usec();
+            *((pa_usec_t*) data) = u->timestamp > now ? u->timestamp - now : 0;
 
-            if (pa_timeval_cmp(&u->timestamp, &now) > 0)
-                *((pa_usec_t*) data) = 0;
-            else
-                *((pa_usec_t*) data) = pa_timeval_diff(&u->timestamp, &now);
-            break;
+            return 0;
         }
     }
 
     return pa_sink_process_msg(o, code, data, offset, chunk);
 }
 
+static void sink_update_requested_latency_cb(pa_sink *s) {
+    struct userdata *u;
+
+    pa_sink_assert_ref(s);
+    u = s->userdata;
+    pa_assert(u);
+
+    u->block_usec = pa_sink_get_requested_latency_within_thread(s);
+}
+
+static void process_rewind(struct userdata *u, pa_usec_t now) {
+    size_t rewind_nbytes, in_buffer;
+    pa_usec_t delay;
+
+    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;
+
+    pa_assert(rewind_nbytes > 0);
+    pa_log_debug("Requested to rewind %lu bytes.", (unsigned long) rewind_nbytes);
+
+    if (u->timestamp <= now)
+        return;
+
+    delay = u->timestamp - now;
+    in_buffer = pa_usec_to_bytes(delay, &u->sink->sample_spec);
+
+    if (in_buffer <= 0)
+        return;
+
+    if (rewind_nbytes > in_buffer)
+        rewind_nbytes = in_buffer;
+
+    pa_sink_process_rewind(u->sink, rewind_nbytes);
+    u->timestamp -= pa_bytes_to_usec(rewind_nbytes, &u->sink->sample_spec);
+
+    pa_log_debug("Rewound %lu bytes.", (unsigned long) rewind_nbytes);
+}
+
+static void process_render(struct userdata *u, pa_usec_t now) {
+    size_t nbytes;
+    size_t ate = 0;
+
+    pa_assert(u);
+
+    /* This is the configured latency. Sink inputs connected to us
+    might not have a single frame more than this value queued. Hence:
+    at maximum read this many bytes from the sink inputs. */
+
+    nbytes = pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec);
+
+    /* Fill the buffer up the the latency size */
+    while (u->timestamp < now + u->block_usec) {
+        pa_memchunk chunk;
+
+        pa_sink_render(u->sink, nbytes, &chunk);
+        pa_memblock_unref(chunk.memblock);
+
+        pa_log_debug("Ate %lu bytes.", (unsigned long) chunk.length);
+        u->timestamp += pa_bytes_to_usec(chunk.length, &u->sink->sample_spec);
+
+        ate += chunk.length;
+
+        if (ate >= nbytes)
+            break;
+    }
+
+    pa_log_debug("Ate in sum %lu bytes (of %lu)", (unsigned long) ate, (unsigned long) nbytes);
+}
+
 static void thread_func(void *userdata) {
     struct userdata *u = userdata;
 
@@ -126,28 +197,29 @@ static void thread_func(void *userdata) {
     pa_thread_mq_install(&u->thread_mq);
     pa_rtpoll_install(u->rtpoll);
 
-    pa_rtclock_get(&u->timestamp);
+    u->timestamp = pa_rtclock_usec();
 
     for (;;) {
         int ret;
 
         /* Render some data and drop it immediately */
         if (u->sink->thread_info.state == PA_SINK_RUNNING) {
-            struct timeval now;
+            pa_usec_t now;
 
-            pa_rtclock_get(&now);
+            now = pa_rtclock_usec();
 
-            if (pa_timeval_cmp(&u->timestamp, &now) <= 0) {
-                pa_sink_skip(u->sink, u->block_size);
-                pa_timeval_add(&u->timestamp, pa_bytes_to_usec(u->block_size, &u->sink->sample_spec));
-            }
+            if (u->sink->thread_info.rewind_nbytes > 0)
+                process_rewind(u, now);
 
-            pa_rtpoll_set_timer_absolute(u->rtpoll, &u->timestamp);
+            if (u->timestamp <= now)
+                process_render(u, now);
+
+            pa_rtpoll_set_timer_absolute(u->rtpoll, u->timestamp);
         } else
             pa_rtpoll_set_timer_disabled(u->rtpoll);
 
         /* Hmm, nothing to do. Let's sleep */
-        if ((ret = pa_rtpoll_run(u->rtpoll, 1)) < 0)
+        if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0)
             goto fail;
 
         if (ret == 0)
@@ -169,6 +241,7 @@ int pa__init(pa_module*m) {
     pa_sample_spec ss;
     pa_channel_map map;
     pa_modargs *ma = NULL;
+    pa_sink_new_data data;
 
     pa_assert(m);
 
@@ -187,27 +260,35 @@ int pa__init(pa_module*m) {
     u->core = m->core;
     u->module = m;
     m->userdata = u;
-    pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
     u->rtpoll = pa_rtpoll_new();
-    pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
+    pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll);
+
+    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_DESCRIPTION, pa_modargs_get_value(ma, "description", "Null Output"));
 
-    if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) {
-        pa_log("Failed to create sink.");
+    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 object.");
         goto fail;
     }
 
     u->sink->parent.process_msg = sink_process_msg;
+    u->sink->update_requested_latency = sink_update_requested_latency_cb;
     u->sink->userdata = u;
-    u->sink->flags = PA_SINK_LATENCY;
 
-    pa_sink_set_module(u->sink, m);
     pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
     pa_sink_set_rtpoll(u->sink, u->rtpoll);
-    pa_sink_set_description(u->sink, pa_modargs_get_value(ma, "description", "NULL sink"));
 
-    u->block_size = pa_bytes_per_second(&ss) / 20; /* 50 ms */
-    if (u->block_size <= 0)
-        u->block_size = pa_frame_size(&ss);
+    u->block_usec = u->sink->max_latency = MAX_LATENCY_USEC;
+
+    u->sink->thread_info.max_rewind = pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec);
 
     if (!(u->thread = pa_thread_new(thread_func, u))) {
         pa_log("Failed to create thread.");
index a7df8a0..cf7584d 100644 (file)
@@ -161,10 +161,10 @@ static void trigger(struct userdata *u, pa_bool_t quick) {
 
      pa_log_debug("trigger");
 
-    if (u->source && PA_SOURCE_OPENED(u->source->thread_info.state))
+    if (u->source && PA_SOURCE_IS_OPENED(u->source->thread_info.state))
         enable_bits |= PCM_ENABLE_INPUT;
 
-    if (u->sink && PA_SINK_OPENED(u->sink->thread_info.state))
+    if (u->sink && PA_SINK_IS_OPENED(u->sink->thread_info.state))
         enable_bits |= PCM_ENABLE_OUTPUT;
 
     pa_log_debug("trigger: %i", enable_bits);
@@ -202,7 +202,7 @@ static void trigger(struct userdata *u, pa_bool_t quick) {
              * register the fd as ready.
              */
 
-            if (u->source && PA_SOURCE_OPENED(u->source->thread_info.state)) {
+            if (u->source && PA_SOURCE_IS_OPENED(u->source->thread_info.state)) {
                 uint8_t *buf = pa_xnew(uint8_t, u->in_fragment_size);
                 pa_read(u->fd, buf, u->in_fragment_size, NULL);
                 pa_xfree(buf);
@@ -641,7 +641,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
             switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) {
 
                 case PA_SINK_SUSPENDED:
-                    pa_assert(PA_SINK_OPENED(u->sink->thread_info.state));
+                    pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state));
 
                     if (!u->source || u->source_suspended) {
                         if (suspend(u) < 0)
@@ -658,7 +658,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
 
                     if (u->sink->thread_info.state == PA_SINK_INIT) {
                         do_trigger = TRUE;
-                        quick = u->source && PA_SOURCE_OPENED(u->source->thread_info.state);
+                        quick = u->source && PA_SOURCE_IS_OPENED(u->source->thread_info.state);
                     }
 
                     if (u->sink->thread_info.state == PA_SINK_SUSPENDED) {
@@ -721,7 +721,7 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off
 
             switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) {
                 case PA_SOURCE_SUSPENDED:
-                    pa_assert(PA_SOURCE_OPENED(u->source->thread_info.state));
+                    pa_assert(PA_SOURCE_IS_OPENED(u->source->thread_info.state));
 
                     if (!u->sink || u->sink_suspended) {
                         if (suspend(u) < 0)
@@ -738,7 +738,7 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off
 
                     if (u->source->thread_info.state == PA_SOURCE_INIT) {
                         do_trigger = TRUE;
-                        quick = u->sink && PA_SINK_OPENED(u->sink->thread_info.state);
+                        quick = u->sink && PA_SINK_IS_OPENED(u->sink->thread_info.state);
                     }
 
                     if (u->source->thread_info.state == PA_SOURCE_SUSPENDED) {
@@ -877,7 +877,7 @@ static void thread_func(void *userdata) {
 
         /* Render some data and write it to the dsp */
 
-        if (u->sink && PA_SINK_OPENED(u->sink->thread_info.state) && ((revents & POLLOUT) || u->use_mmap || u->use_getospace)) {
+        if (u->sink && PA_SINK_IS_OPENED(u->sink->thread_info.state) && ((revents & POLLOUT) || u->use_mmap || u->use_getospace)) {
 
             if (u->use_mmap) {
 
@@ -985,7 +985,7 @@ static void thread_func(void *userdata) {
 
         /* Try to read some data and pass it on to the source driver. */
 
-        if (u->source && PA_SOURCE_OPENED(u->source->thread_info.state) && ((revents & POLLIN) || u->use_mmap || u->use_getispace)) {
+        if (u->source && PA_SOURCE_IS_OPENED(u->source->thread_info.state) && ((revents & POLLIN) || u->use_mmap || u->use_getispace)) {
 
             if (u->use_mmap) {
 
@@ -1095,8 +1095,8 @@ static void thread_func(void *userdata) {
 
             pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
             pollfd->events =
-                ((u->source && PA_SOURCE_OPENED(u->source->thread_info.state)) ? POLLIN : 0) |
-                ((u->sink && PA_SINK_OPENED(u->sink->thread_info.state)) ? POLLOUT : 0);
+                ((u->source && PA_SOURCE_IS_OPENED(u->source->thread_info.state)) ? POLLIN : 0) |
+                ((u->sink && PA_SINK_IS_OPENED(u->sink->thread_info.state)) ? POLLOUT : 0);
         }
 
         /* Hmm, nothing to do. Let's sleep */
@@ -1143,9 +1143,11 @@ int pa__init(pa_module*m) {
     pa_sample_spec ss;
     pa_channel_map map;
     pa_modargs *ma = NULL;
-    char hwdesc[64], *t;
+    char hwdesc[64];
     const char *name;
-    int namereg_fail;
+    pa_bool_t namereg_fail;
+    pa_sink_new_data sink_new_data;
+    pa_source_new_data source_new_data;
 
     pa_assert(m);
 
@@ -1226,17 +1228,16 @@ int pa__init(pa_module*m) {
     m->userdata = u;
     u->fd = fd;
     u->mixer_fd = -1;
-    u->use_getospace = u->use_getispace = 1;
-    u->use_getodelay = 1;
+    u->use_getospace = u->use_getispace = TRUE;
+    u->use_getodelay = TRUE;
     u->mode = mode;
     u->frame_size = pa_frame_size(&ss);
     u->device_name = pa_xstrdup(dev);
     u->in_nfrags = u->out_nfrags = u->nfrags = nfrags;
     u->out_fragment_size = u->in_fragment_size = u->frag_size = frag_size;
     u->use_mmap = use_mmap;
-    pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
     u->rtpoll = pa_rtpoll_new();
-    pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
+    pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll);
     u->rtpoll_item = NULL;
     build_pollfd(u);
 
@@ -1244,14 +1245,14 @@ int pa__init(pa_module*m) {
         pa_log_info("Input -- %u fragments of size %u.", info.fragstotal, info.fragsize);
         u->in_fragment_size = info.fragsize;
         u->in_nfrags = info.fragstotal;
-        u->use_getispace = 1;
+        u->use_getispace = TRUE;
     }
 
     if (ioctl(fd, SNDCTL_DSP_GETOSPACE, &info) >= 0) {
         pa_log_info("Output -- %u fragments of size %u.", info.fragstotal, info.fragsize);
         u->out_fragment_size = info.fragsize;
         u->out_nfrags = info.fragstotal;
-        u->use_getospace = 1;
+        u->use_getospace = TRUE;
     }
 
     u->in_hwbuf_size = u->in_nfrags * u->in_fragment_size;
@@ -1263,21 +1264,37 @@ int pa__init(pa_module*m) {
         if (use_mmap) {
             if ((u->in_mmap = mmap(NULL, u->in_hwbuf_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
                 pa_log_warn("mmap(PROT_READ) failed, reverting to non-mmap mode: %s", pa_cstrerror(errno));
-                use_mmap = u->use_mmap = 0;
+                use_mmap = u->use_mmap = FALSE;
                 u->in_mmap = NULL;
             } else
                 pa_log_debug("Successfully mmap()ed input buffer.");
         }
 
         if ((name = pa_modargs_get_value(ma, "source_name", NULL)))
-            namereg_fail = 1;
+            namereg_fail = TRUE;
         else {
             name = name_buf = pa_sprintf_malloc("oss_input.%s", pa_path_get_filename(dev));
-            namereg_fail = 0;
+            namereg_fail = FALSE;
         }
 
-        u->source = pa_source_new(m->core, __FILE__, name, namereg_fail, &ss, &map);
+        pa_source_new_data_init(&source_new_data);
+        source_new_data.driver = __FILE__;
+        source_new_data.module = m;
+        pa_source_new_data_set_name(&source_new_data, name);
+        source_new_data.namereg_fail = namereg_fail;
+        pa_source_new_data_set_sample_spec(&source_new_data, &ss);
+        pa_source_new_data_set_channel_map(&source_new_data, &map);
+        pa_proplist_sets(source_new_data.proplist, PA_PROP_DEVICE_STRING, dev);
+        pa_proplist_sets(source_new_data.proplist, PA_PROP_DEVICE_API, "oss");
+        pa_proplist_sets(source_new_data.proplist, PA_PROP_DEVICE_DESCRIPTION, hwdesc[0] ? hwdesc : dev);
+        pa_proplist_sets(source_new_data.proplist, PA_PROP_DEVICE_ACCESS_MODE, use_mmap ? "mmap" : "serial");
+        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));
+
+        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);
+
         if (!u->source) {
             pa_log("Failed to create source object");
             goto fail;
@@ -1286,18 +1303,8 @@ int pa__init(pa_module*m) {
         u->source->parent.process_msg = source_process_msg;
         u->source->userdata = u;
 
-        pa_source_set_module(u->source, m);
         pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
         pa_source_set_rtpoll(u->source, u->rtpoll);
-        pa_source_set_description(u->source, t = pa_sprintf_malloc(
-                                          "OSS PCM on %s%s%s%s%s",
-                                          dev,
-                                          hwdesc[0] ? " (" : "",
-                                          hwdesc[0] ? hwdesc : "",
-                                          hwdesc[0] ? ")" : "",
-                                          use_mmap ? " via DMA" : ""));
-        pa_xfree(t);
-        u->source->flags = PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY;
         u->source->refresh_volume = TRUE;
 
         if (use_mmap)
@@ -1315,7 +1322,7 @@ int pa__init(pa_module*m) {
                     goto go_on;
                 } else {
                     pa_log_warn("mmap(PROT_WRITE) failed, reverting to non-mmap mode: %s", pa_cstrerror(errno));
-                    u->use_mmap = (use_mmap = FALSE);
+                    u->use_mmap = use_mmap = FALSE;
                     u->out_mmap = NULL;
                 }
             } else {
@@ -1325,14 +1332,30 @@ int pa__init(pa_module*m) {
         }
 
         if ((name = pa_modargs_get_value(ma, "sink_name", NULL)))
-            namereg_fail = 1;
+            namereg_fail = TRUE;
         else {
             name = name_buf = pa_sprintf_malloc("oss_output.%s", pa_path_get_filename(dev));
-            namereg_fail = 0;
+            namereg_fail = FALSE;
         }
 
-        u->sink = pa_sink_new(m->core, __FILE__, name, namereg_fail, &ss, &map);
+        pa_sink_new_data_init(&sink_new_data);
+        sink_new_data.driver = __FILE__;
+        sink_new_data.module = m;
+        pa_sink_new_data_set_name(&sink_new_data, name);
+        sink_new_data.namereg_fail = namereg_fail;
+        pa_sink_new_data_set_sample_spec(&sink_new_data, &ss);
+        pa_sink_new_data_set_channel_map(&sink_new_data, &map);
+        pa_proplist_sets(sink_new_data.proplist, PA_PROP_DEVICE_STRING, dev);
+        pa_proplist_sets(sink_new_data.proplist, PA_PROP_DEVICE_API, "oss");
+        pa_proplist_sets(sink_new_data.proplist, PA_PROP_DEVICE_DESCRIPTION, hwdesc[0] ? hwdesc : dev);
+        pa_proplist_sets(sink_new_data.proplist, PA_PROP_DEVICE_ACCESS_MODE, use_mmap ? "mmap" : "serial");
+        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));
+
+        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);
+
         if (!u->sink) {
             pa_log("Failed to create sink object");
             goto fail;
@@ -1341,18 +1364,8 @@ int pa__init(pa_module*m) {
         u->sink->parent.process_msg = sink_process_msg;
         u->sink->userdata = u;
 
-        pa_sink_set_module(u->sink, m);
         pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
         pa_sink_set_rtpoll(u->sink, u->rtpoll);
-        pa_sink_set_description(u->sink, t = pa_sprintf_malloc(
-                                        "OSS PCM on %s%s%s%s%s",
-                                        dev,
-                                        hwdesc[0] ? " (" : "",
-                                        hwdesc[0] ? hwdesc : "",
-                                        hwdesc[0] ? ")" : "",
-                                        use_mmap ? " via DMA" : ""));
-        pa_xfree(t);
-        u->sink->flags = PA_SINK_HARDWARE|PA_SINK_LATENCY;
         u->sink->refresh_volume = TRUE;
 
         if (use_mmap)
@@ -1360,7 +1373,7 @@ int pa__init(pa_module*m) {
     }
 
     if ((u->mixer_fd = pa_oss_open_mixer_for_device(u->device_name)) >= 0) {
-        int do_close = 1;
+        pa_bool_t do_close = TRUE;
         u->mixer_devmask = 0;
 
         if (ioctl(fd, SOUND_MIXER_READ_DEVMASK, &u->mixer_devmask) < 0)
@@ -1372,7 +1385,7 @@ int pa__init(pa_module*m) {
                 u->sink->flags |= PA_SINK_HW_VOLUME_CTRL;
                 u->sink->get_volume = sink_get_volume;
                 u->sink->set_volume = sink_set_volume;
-                do_close = 0;
+                do_close = FALSE;
             }
 
             if (u->source && (u->mixer_devmask & (SOUND_MASK_RECLEV|SOUND_MASK_IGAIN))) {
@@ -1380,7 +1393,7 @@ int pa__init(pa_module*m) {
                 u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL;
                 u->source->get_volume = source_get_volume;
                 u->source->set_volume = source_set_volume;
-                do_close = 0;
+                do_close = FALSE;
             }
         }
 
@@ -1402,10 +1415,25 @@ go_on:
     }
 
     /* Read mixer settings */
-    if (u->sink && u->sink->get_volume)
-        sink_get_volume(u->sink);
-    if (u->source && u->source->get_volume)
-        source_get_volume(u->source);
+    if (u->sink) {
+        if (sink_new_data.volume_is_set) {
+            if (u->sink->set_volume)
+                u->sink->set_volume(u->sink);
+        } else {
+            if (u->sink->get_volume)
+                u->sink->get_volume(u->sink);
+        }
+    }
+
+    if (u->source) {
+        if (source_new_data.volume_is_set) {
+            if (u->source->set_volume)
+                u->source->set_volume(u->source);
+        } else {
+            if (u->source->get_volume)
+                u->source->get_volume(u->source);
+        }
+    }
 
     if (u->sink)
         pa_sink_put(u->sink);
index e720c8a..cc64892 100644 (file)
@@ -62,7 +62,7 @@ PA_MODULE_USAGE(
         "rate=<sample rate>"
         "channel_map=<channel map>");
 
-#define DEFAULT_FILE_NAME "/tmp/music.output"
+#define DEFAULT_FILE_NAME "fifo_output"
 #define DEFAULT_SINK_NAME "fifo_output"
 
 struct userdata {
@@ -80,6 +80,8 @@ struct userdata {
     pa_memchunk memchunk;
 
     pa_rtpoll_item *rtpoll_item;
+
+    int write_type;
 };
 
 static const char* const valid_modargs[] = {
@@ -109,16 +111,64 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
             n += u->memchunk.length;
 
             *((pa_usec_t*) data) = pa_bytes_to_usec(n, &u->sink->sample_spec);
-            break;
+            return 0;
         }
     }
 
     return pa_sink_process_msg(o, code, data, offset, chunk);
 }
 
+static void process_rewind(struct userdata *u) {
+    pa_assert(u);
+
+    pa_log_debug("Rewind requested but not supported by pipe sink. Ignoring.");
+    u->sink->thread_info.rewind_nbytes = 0;
+}
+
+static int process_render(struct userdata *u) {
+    pa_assert(u);
+
+    if (u->memchunk.length <= 0)
+        pa_sink_render(u->sink, PIPE_BUF, &u->memchunk);
+
+    pa_assert(u->memchunk.length > 0);
+
+    for (;;) {
+        ssize_t l;
+        void *p;
+
+        p = pa_memblock_acquire(u->memchunk.memblock);
+        l = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, &u->write_type);
+        pa_memblock_release(u->memchunk.memblock);
+
+        pa_assert(l != 0);
+
+        if (l < 0) {
+
+            if (errno == EINTR)
+                continue;
+            else if (errno != EAGAIN) {
+                pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno));
+                return -1;
+            }
+
+        } else {
+
+            u->memchunk.index += l;
+            u->memchunk.length -= l;
+
+            if (u->memchunk.length <= 0) {
+                pa_memblock_unref(u->memchunk.memblock);
+                pa_memchunk_reset(&u->memchunk);
+            }
+        }
+
+        return 0;
+    }
+}
+
 static void thread_func(void *userdata) {
     struct userdata *u = userdata;
-    int write_type = 0;
 
     pa_assert(u);
 
@@ -134,39 +184,14 @@ static void thread_func(void *userdata) {
         pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
 
         /* Render some data and write it to the fifo */
-        if (u->sink->thread_info.state == PA_SINK_RUNNING && pollfd->revents) {
-            ssize_t l;
-            void *p;
-
-            if (u->memchunk.length <= 0)
-                pa_sink_render(u->sink, PIPE_BUF, &u->memchunk);
+        if (u->sink->thread_info.state == PA_SINK_RUNNING) {
 
-            pa_assert(u->memchunk.length > 0);
+            if (u->sink->thread_info.rewind_nbytes > 0)
+                process_rewind(u);
 
-            p = pa_memblock_acquire(u->memchunk.memblock);
-            l = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, &write_type);
-            pa_memblock_release(u->memchunk.memblock);
-
-            pa_assert(l != 0);
-
-            if (l < 0) {
-
-                if (errno == EINTR)
-                    continue;
-                else if (errno != EAGAIN) {
-                    pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno));
+            if (pollfd->revents) {
+                if (process_render(u) < 0)
                     goto fail;
-                }
-
-            } else {
-
-                u->memchunk.index += l;
-                u->memchunk.length -= l;
-
-                if (u->memchunk.length <= 0) {
-                    pa_memblock_unref(u->memchunk.memblock);
-                    pa_memchunk_reset(&u->memchunk);
-                }
 
                 pollfd->revents = 0;
             }
@@ -205,8 +230,8 @@ int pa__init(pa_module*m) {
     pa_sample_spec ss;
     pa_channel_map map;
     pa_modargs *ma;
-    char *t;
     struct pollfd *pollfd;
+    pa_sink_new_data data;
 
     pa_assert(m);
 
@@ -226,11 +251,11 @@ int pa__init(pa_module*m) {
     u->module = m;
     m->userdata = u;
     pa_memchunk_reset(&u->memchunk);
-    pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
     u->rtpoll = pa_rtpoll_new();
-    pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
+    pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll);
+    u->write_type = 0;
 
-    u->filename = pa_xstrdup(pa_modargs_get_value(ma, "file", DEFAULT_FILE_NAME));
+    u->filename = pa_runtime_path(pa_modargs_get_value(ma, "file", DEFAULT_FILE_NAME));
 
     mkfifo(u->filename, 0666);
     if ((u->fd = open(u->filename, O_RDWR|O_NOCTTY)) < 0) {
@@ -251,20 +276,28 @@ int pa__init(pa_module*m) {
         goto fail;
     }
 
-    if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) {
+    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_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->filename);
+    pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Unix FIFO sink %s", u->filename);
+    pa_sink_new_data_set_sample_spec(&data, &ss);
+    pa_sink_new_data_set_channel_map(&data, &map);
+
+    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;
-    u->sink->flags = PA_SINK_LATENCY;
 
-    pa_sink_set_module(u->sink, m);
     pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
     pa_sink_set_rtpoll(u->sink, u->rtpoll);
-    pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Unix FIFO sink '%s'", u->filename));
-    pa_xfree(t);
 
     u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);
     pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
index 0293564..83eb4f8 100644 (file)
@@ -153,13 +153,14 @@ static void thread_func(void *userdata) {
         /* Hmm, nothing to do. Let's sleep */
         pollfd->events = u->source->thread_info.state == PA_SOURCE_RUNNING ? POLLIN : 0;
 
-        if ((ret = pa_rtpoll_run(u->rtpoll, 1)) < 0)
+        if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0)
             goto fail;
 
         if (ret == 0)
             goto finish;
 
         pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
+
         if (pollfd->revents & ~POLLIN) {
             pa_log("FIFO shutdown.");
             goto fail;
@@ -182,8 +183,8 @@ int pa__init(pa_module*m) {
     pa_sample_spec ss;
     pa_channel_map map;
     pa_modargs *ma;
-    char *t;
     struct pollfd *pollfd;
+    pa_source_new_data data;
 
     pa_assert(m);
 
@@ -203,11 +204,10 @@ int pa__init(pa_module*m) {
     u->module = m;
     m->userdata = u;
     pa_memchunk_reset(&u->memchunk);
-    pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
     u->rtpoll = pa_rtpoll_new();
-    pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
+    pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll);
 
-    u->filename = pa_xstrdup(pa_modargs_get_value(ma, "file", DEFAULT_FILE_NAME));
+    u->filename = pa_runtime_path(pa_modargs_get_value(ma, "file", DEFAULT_FILE_NAME));
 
     mkfifo(u->filename, 0666);
     if ((u->fd = open(u->filename, O_RDWR|O_NOCTTY)) < 0) {
@@ -228,19 +228,27 @@ int pa__init(pa_module*m) {
         goto fail;
     }
 
-    if (!(u->source = pa_source_new(m->core, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, &map))) {
+    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_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->filename);
+    pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Unix FIFO source %s", u->filename);
+    pa_source_new_data_set_sample_spec(&data, &ss);
+    pa_source_new_data_set_channel_map(&data, &map);
+
+    u->source = pa_source_new(m->core, &data, 0);
+    pa_source_new_data_done(&data);
+
+    if (!u->source) {
         pa_log("Failed to create source.");
         goto fail;
     }
 
     u->source->userdata = u;
-    u->source->flags = 0;
 
-    pa_source_set_module(u->source, m);
     pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
     pa_source_set_rtpoll(u->source, u->rtpoll);
-    pa_source_set_description(u->source, t = pa_sprintf_malloc("Unix FIFO source '%s'", u->filename));
-    pa_xfree(t);
 
     u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);
     pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
index 600201b..8bcc19b 100644 (file)
@@ -215,15 +215,6 @@ int pa__init(pa_module*m) {
 #else
     pa_socket_server *s;
     int r;
-    char tmp[PATH_MAX];
-
-#if defined(USE_PROTOCOL_ESOUND)
-#if defined(USE_PER_USER_ESOUND_SOCKET)
-    char esdsocketpath[PATH_MAX];
-#else
-    const char esdsocketpath[] = "/tmp/.esd/socket";
-#endif
-#endif
 #endif
 
     pa_assert(m);
@@ -255,27 +246,28 @@ int pa__init(pa_module*m) {
         goto fail;
 
     if (s_ipv4)
-        if (!(u->protocol_ipv4 = protocol_new(m->core, s_ipv4, m, ma)))
-            pa_socket_server_unref(s_ipv4);
-
+        u->protocol_ipv4 = protocol_new(m->core, s_ipv4, m, ma);
     if (s_ipv6)
-        if (!(u->protocol_ipv6 = protocol_new(m->core, s_ipv6, m, ma)))
-            pa_socket_server_unref(s_ipv6);
+        u->protocol_ipv6 = protocol_new(m->core, s_ipv6, m, ma);
 
     if (!u->protocol_ipv4 && !u->protocol_ipv6)
         goto fail;
 
+    if (s_ipv6)
+        pa_socket_server_unref(s_ipv6);
+    if (s_ipv6)
+        pa_socket_server_unref(s_ipv4);
+
 #else
 
 #if defined(USE_PROTOCOL_ESOUND)
 
 #if defined(USE_PER_USER_ESOUND_SOCKET)
-    snprintf(esdsocketpath, sizeof(esdsocketpath), "/tmp/.esd-%lu/socket", (unsigned long) getuid());
+    u->socket_path = pa_sprintf_malloc("/tmp/.esd-%lu/socket", (unsigned long) getuid());
+#else
+    u->socket_path = pa_xstrdup("/tmp/.esd/socket");
 #endif
 
-    pa_runtime_path(pa_modargs_get_value(ma, "socket", esdsocketpath), tmp, sizeof(tmp));
-    u->socket_path = pa_xstrdup(tmp);
-
     /* This socket doesn't reside in our own runtime dir but in
      * /tmp/.esd/, hence we have to create the dir first */
 
@@ -285,24 +277,26 @@ int pa__init(pa_module*m) {
     }
 
 #else
-    pa_runtime_path(pa_modargs_get_value(ma, "socket", UNIX_SOCKET), tmp, sizeof(tmp));
-    u->socket_path = pa_xstrdup(tmp);
-#endif
-
-    if ((r = pa_unix_socket_remove_stale(tmp)) < 0) {
-        pa_log("Failed to remove stale UNIX socket '%s': %s", tmp, pa_cstrerror(errno));
+    if (!(u->socket_path = pa_runtime_path(pa_modargs_get_value(ma, "socket", UNIX_SOCKET)))) {
+        pa_log("Failed to generate socket path.");
         goto fail;
     }
+#endif
 
-    if (r)
-        pa_log("Removed stale UNIX socket '%s'.", tmp);
+    if ((r = pa_unix_socket_remove_stale(u->socket_path)) < 0) {
+        pa_log("Failed to remove stale UNIX socket '%s': %s", u->socket_path, pa_cstrerror(errno));
+        goto fail;
+    } else if (r > 0)
+        pa_log_info("Removed stale UNIX socket '%s'.", u->socket_path);
 
-    if (!(s = pa_socket_server_new_unix(m->core->mainloop, tmp)))
+    if (!(s = pa_socket_server_new_unix(m->core->mainloop, u->socket_path)))
         goto fail;
 
     if (!(u->protocol_unix = protocol_new(m->core, s, m, ma)))
         goto fail;
 
+    pa_socket_server_unref(s);
+
 #endif
 
     m->userdata = u;
@@ -325,23 +319,21 @@ fail:
 #else
         if (u->protocol_unix)
             protocol_free(u->protocol_unix);
-
-        if (u->socket_path)
-            pa_xfree(u->socket_path);
+        pa_xfree(u->socket_path);
 #endif
 
         pa_xfree(u);
-    } else {
+    }
+
 #if defined(USE_TCP_SOCKETS)
-        if (s_ipv4)
-            pa_socket_server_unref(s_ipv4);
-        if (s_ipv6)
-            pa_socket_server_unref(s_ipv6);
+    if (s_ipv4)
+        pa_socket_server_unref(s_ipv4);
+    if (s_ipv6)
+        pa_socket_server_unref(s_ipv6);
 #else
-        if (s)
-            pa_socket_server_unref(s);
+    if (s)
+        pa_socket_server_unref(s);
 #endif
-    }
 
     goto finish;
 }
@@ -362,7 +354,7 @@ void pa__done(pa_module*m) {
     if (u->protocol_unix)
         protocol_free(u->protocol_unix);
 
-#if defined(USE_PROTOCOL_ESOUND)
+#if defined(USE_PROTOCOL_ESOUND) && !defined(USE_PER_USER_ESOUND_SOCKET)
     if (u->socket_path) {
         char *p = pa_parent_dir(u->socket_path);
         rmdir(p);
index 39a9245..0b9825e 100644 (file)
@@ -3,7 +3,7 @@
 /***
   This file is part of PulseAudio.
 
-  Copyright 2004-2006 Lennart Poettering
+  Copyright 2004-2008 Lennart Poettering
 
   PulseAudio is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published
@@ -59,8 +59,6 @@ struct userdata {
 
     pa_sink *sink, *master;
     pa_sink_input *sink_input;
-
-    pa_memchunk memchunk;
 };
 
 static const char* const valid_modargs[] = {
@@ -83,10 +81,14 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
         case PA_SINK_MESSAGE_GET_LATENCY: {
             pa_usec_t usec = 0;
 
+            /* Get the latency of the master sink */
             if (PA_MSGOBJECT(u->master)->process_msg(PA_MSGOBJECT(u->master), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0)
                 usec = 0;
 
-            *((pa_usec_t*) data) = usec + pa_bytes_to_usec(u->memchunk.length, &u->sink->sample_spec);
+            /* Add the latency internal to our sink input on top */
+            usec += pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->master->sample_spec);
+
+            *((pa_usec_t*) data) = usec;
             return 0;
         }
     }
@@ -101,67 +103,86 @@ static int sink_set_state(pa_sink *s, pa_sink_state_t state) {
     pa_sink_assert_ref(s);
     pa_assert_se(u = s->userdata);
 
-    if (PA_SINK_LINKED(state) && u->sink_input && PA_SINK_INPUT_LINKED(pa_sink_input_get_state(u->sink_input)))
+    if (PA_SINK_IS_LINKED(state) &&
+        u->sink_input &&
+        PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
+
         pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED);
 
     return 0;
 }
 
 /* Called from I/O thread context */
-static int sink_input_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
-    struct userdata *u = PA_SINK_INPUT(o)->userdata;
+static void sink_request_rewind(pa_sink *s) {
+    struct userdata *u;
 
-    switch (code) {
-        case PA_SINK_INPUT_MESSAGE_GET_LATENCY:
-            *((pa_usec_t*) data) = pa_bytes_to_usec(u->memchunk.length, &u->sink_input->sample_spec);
+    pa_sink_assert_ref(s);
+    pa_assert_se(u = s->userdata);
 
-            /* Fall through, the default handler will add in the extra
-             * latency added by the resampler */
-            break;
-    }
+    pa_sink_input_request_rewind(u->sink_input, s->thread_info.rewind_nbytes, TRUE, FALSE);
+}
+
+/* Called from I/O thread context */
+static void sink_update_requested_latency(pa_sink *s) {
+    struct userdata *u;
 
-    return pa_sink_input_process_msg(o, code, data, offset, chunk);
+    pa_sink_assert_ref(s);
+    pa_assert_se(u = s->userdata);
+
+    /* Just hand this one over to the master sink */
+    pa_sink_input_set_requested_latency_within_thread(
+            u->sink_input,
+            pa_sink_get_requested_latency_within_thread(s));
 }
 
 /* Called from I/O thread context */
-static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
+static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) {
     struct userdata *u;
 
     pa_sink_input_assert_ref(i);
+    pa_assert(chunk);
     pa_assert_se(u = i->userdata);
 
-    if (!u->memchunk.memblock)
-        pa_sink_render(u->sink, length, &u->memchunk);
+    if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state))
+        return -1;
 
-    pa_assert(u->memchunk.memblock);
-    *chunk = u->memchunk;
-    pa_memblock_ref(chunk->memblock);
+    pa_sink_render(u->sink, nbytes, chunk);
     return 0;
 }
 
 /* Called from I/O thread context */
-static void sink_input_drop_cb(pa_sink_input *i, size_t length) {
+static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
     struct userdata *u;
 
     pa_sink_input_assert_ref(i);
     pa_assert_se(u = i->userdata);
-    pa_assert(length > 0);
+    pa_assert(nbytes > 0);
 
-    if (u->memchunk.memblock) {
+    if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state))
+        return;
 
-        if (length < u->memchunk.length) {
-            u->memchunk.index += length;
-            u->memchunk.length -= length;
-            return;
-        }
+    if (u->sink->thread_info.rewind_nbytes > 0) {
+        size_t amount;
+
+        amount = PA_MIN(u->sink->thread_info.rewind_nbytes, nbytes);
+        u->sink->thread_info.rewind_nbytes = 0;
 
-        pa_memblock_unref(u->memchunk.memblock);
-        length -= u->memchunk.length;
-        pa_memchunk_reset(&u->memchunk);
+        if (amount > 0)
+            pa_sink_process_rewind(u->sink, amount);
     }
+}
+
+/* Called from I/O thread context */
+static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
+    struct userdata *u;
 
-    if (length > 0)
-        pa_sink_skip(u->sink, length);
+    pa_sink_input_assert_ref(i);
+    pa_assert_se(u = i->userdata);
+
+    if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state))
+        return;
+
+    pa_sink_set_max_rewind(u->sink, nbytes);
 }
 
 /* Called from I/O thread context */
@@ -171,7 +192,12 @@ static void sink_input_detach_cb(pa_sink_input *i) {
     pa_sink_input_assert_ref(i);
     pa_assert_se(u = i->userdata);
 
+    if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state))
+        return;
+
     pa_sink_detach_within_thread(u->sink);
+    pa_sink_set_asyncmsgq(u->sink, NULL);
+    pa_sink_set_rtpoll(u->sink, NULL);
 }
 
 /* Called from I/O thread context */
@@ -181,10 +207,15 @@ static void sink_input_attach_cb(pa_sink_input *i) {
     pa_sink_input_assert_ref(i);
     pa_assert_se(u = i->userdata);
 
+    if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state))
+        return;
+
     pa_sink_set_asyncmsgq(u->sink, i->sink->asyncmsgq);
     pa_sink_set_rtpoll(u->sink, i->sink->rtpoll);
-
     pa_sink_attach_within_thread(u->sink);
+
+    u->sink->max_latency = u->master->max_latency;
+    u->sink->min_latency = u->master->min_latency;
 }
 
 /* Called from main context */
@@ -194,26 +225,42 @@ static void sink_input_kill_cb(pa_sink_input *i) {
     pa_sink_input_assert_ref(i);
     pa_assert_se(u = i->userdata);
 
+    pa_sink_unlink(u->sink);
     pa_sink_input_unlink(u->sink_input);
-    pa_sink_input_unref(u->sink_input);
-    u->sink_input = NULL;
 
-    pa_sink_unlink(u->sink);
     pa_sink_unref(u->sink);
     u->sink = NULL;
+    pa_sink_input_unref(u->sink_input);
+    u->sink_input = NULL;
 
     pa_module_unload_request(u->module);
 }
 
+/* Called from IO thread context */
+static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) {
+    struct userdata *u;
+
+    pa_sink_input_assert_ref(i);
+    pa_assert_se(u = i->userdata);
+
+    /* If we are added for the first time, ask for a rewinding so that
+     * we are heard right-away. */
+    if (PA_SINK_INPUT_IS_LINKED(state) &&
+        i->thread_info.state == PA_SINK_INPUT_INIT) {
+        pa_log_debug("Requesting rewind due to state change.");
+        pa_sink_input_request_rewind(i, 0, FALSE, TRUE);
+    }
+}
+
 int pa__init(pa_module*m) {
     struct userdata *u;
     pa_sample_spec ss;
     pa_channel_map sink_map, stream_map;
     pa_modargs *ma;
-    char *t;
+    const char *k;
     pa_sink *master;
-    pa_sink_input_new_data data;
-    char *default_sink_name = NULL;
+    pa_sink_input_new_data sink_input_data;
+    pa_sink_new_data sink_data;
 
     pa_assert(m);
 
@@ -245,57 +292,76 @@ int pa__init(pa_module*m) {
         goto fail;
     }
 
+    if (pa_channel_map_equal(&stream_map, &master->channel_map))
+        pa_log_warn("No remapping configured, proceeding nonetheless!");
+
     u = pa_xnew0(struct userdata, 1);
     u->core = m->core;
     u->module = m;
     m->userdata = u;
     u->master = master;
-    pa_memchunk_reset(&u->memchunk);
-
-    default_sink_name = pa_sprintf_malloc("%s.remapped", master->name);
+    u->sink = NULL;
+    u->sink_input = NULL;
 
     /* Create sink */
-    if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", default_sink_name), 0, &ss, &sink_map))) {
+    pa_sink_new_data_init(&sink_data);
+    sink_data.driver = __FILE__;
+    sink_data.module = m;
+    if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL))))
+        sink_data.name = pa_sprintf_malloc("%s.remapped", master->name);
+    pa_sink_new_data_set_sample_spec(&sink_data, &ss);
+    pa_sink_new_data_set_channel_map(&sink_data, &sink_map);
+    k = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION);
+    pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Remapped %s", k ? k : master->name);
+    pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name);
+    pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter");
+
+    u->sink = pa_sink_new(m->core, &sink_data, PA_SINK_LATENCY);
+    pa_sink_new_data_done(&sink_data);
+
+    if (!u->sink) {
         pa_log("Failed to create sink.");
         goto fail;
     }
 
     u->sink->parent.process_msg = sink_process_msg;
     u->sink->set_state = sink_set_state;
+    u->sink->update_requested_latency = sink_update_requested_latency;
+    u->sink->request_rewind = sink_request_rewind;
     u->sink->userdata = u;
-    u->sink->flags = PA_SINK_LATENCY;
 
-    pa_sink_set_module(u->sink, m);
-    pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Remapped %s", master->description));
-    pa_xfree(t);
     pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq);
     pa_sink_set_rtpoll(u->sink, master->rtpoll);
 
     /* Create sink input */
-    pa_sink_input_new_data_init(&data);
-    data.sink = u->master;
-    data.driver = __FILE__;
-    data.name = "Remapped Stream";
-    pa_sink_input_new_data_set_sample_spec(&data, &ss);
-    pa_sink_input_new_data_set_channel_map(&data, &stream_map);
-    data.module = m;
-
-    if (!(u->sink_input = pa_sink_input_new(m->core, &data, PA_SINK_INPUT_DONT_MOVE)))
+    pa_sink_input_new_data_init(&sink_input_data);
+    sink_input_data.driver = __FILE__;
+    sink_input_data.module = m;
+    sink_input_data.sink = u->master;
+    pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Remapped Stream");
+    pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
+    pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss);
+    pa_sink_input_new_data_set_channel_map(&sink_input_data, &stream_map);
+
+    u->sink_input = pa_sink_input_new(m->core, &sink_input_data, PA_SINK_INPUT_DONT_MOVE);
+    pa_sink_input_new_data_done(&sink_input_data);
+
+    if (!u->sink_input)
         goto fail;
 
-    u->sink_input->parent.process_msg = sink_input_process_msg;
-    u->sink_input->peek = sink_input_peek_cb;
-    u->sink_input->drop = sink_input_drop_cb;
+    u->sink_input->pop = sink_input_pop_cb;
+    u->sink_input->process_rewind = sink_input_process_rewind_cb;
+    u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
     u->sink_input->kill = sink_input_kill_cb;
     u->sink_input->attach = sink_input_attach_cb;
     u->sink_input->detach = sink_input_detach_cb;
+    u->sink_input->state_change = sink_input_state_change_cb;
     u->sink_input->userdata = u;
 
     pa_sink_put(u->sink);
     pa_sink_input_put(u->sink_input);
 
     pa_modargs_free(ma);
-    pa_xfree(default_sink_name);
 
     return 0;
 
@@ -305,8 +371,6 @@ fail:
 
     pa__done(m);
 
-    pa_xfree(default_sink_name);
-
     return -1;
 }
 
@@ -318,18 +382,15 @@ void pa__done(pa_module*m) {
     if (!(u = m->userdata))
         return;
 
-    if (u->sink_input) {
-        pa_sink_input_unlink(u->sink_input);
-        pa_sink_input_unref(u->sink_input);
-    }
-
     if (u->sink) {
         pa_sink_unlink(u->sink);
         pa_sink_unref(u->sink);
     }
 
-    if (u->memchunk.memblock)
-        pa_memblock_unref(u->memchunk.memblock);
+    if (u->sink_input) {
+        pa_sink_input_unlink(u->sink_input);
+        pa_sink_input_unref(u->sink_input);
+    }
 
     pa_xfree(u);
 }
index 12957c9..7241a99 100644 (file)
@@ -75,12 +75,12 @@ static pa_hook_result_t sink_hook_callback(pa_core *c, pa_sink *sink, void* user
     }
 
     while ((i = pa_idxset_first(sink->inputs, NULL))) {
-        if (pa_sink_input_move_to(i, target, 1) < 0) {
-            pa_log_warn("Failed to move sink input %u \"%s\" to %s.", i->index, i->name, target->name);
+        if (pa_sink_input_move_to(i, target) < 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_log_info("Sucessfully moved sink input %u \"%s\" to %s.", i->index, i->name, target->name);
+        pa_log_info("Sucessfully moved sink input %u \"%s\" to %s.", i->index, pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME), target->name);
     }
 
 
@@ -116,11 +116,11 @@ static pa_hook_result_t source_hook_callback(pa_core *c, pa_source *source, void
 
     while ((o = pa_idxset_first(source->outputs, NULL))) {
         if (pa_source_output_move_to(o, target) < 0) {
-            pa_log_warn("Failed to move source output %u \"%s\" to %s.", o->index, o->name, target->name);
+            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, o->name, target->name);
+        pa_log_info("Sucessfully moved source output %u \"%s\" to %s.", o->index, pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME), target->name);
     }
 
 
index 41d9a51..3d91705 100644 (file)
@@ -59,44 +59,43 @@ static const char* const valid_modargs[] = {
     NULL,
 };
 
-static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
+static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) {
     struct userdata *u;
 
-    pa_assert(i);
-    u = i->userdata;
-    pa_assert(u);
+    pa_sink_input_assert_ref(i);
+    pa_assert_se(u = i->userdata);
     pa_assert(chunk);
 
     chunk->memblock = pa_memblock_ref(u->memblock);
-    chunk->index = u->peek_index;
     chunk->length = pa_memblock_get_length(u->memblock) - u->peek_index;
+    chunk->index = u->peek_index;
+
+    u->peek_index = 0;
 
     return 0;
 }
 
-static void sink_input_drop_cb(pa_sink_input *i, size_t length) {
-    struct userdata *u;
+static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
     size_t l;
+    struct userdata *u;
 
-    pa_assert(i);
-    u = i->userdata;
-    pa_assert(u);
-    pa_assert(length > 0);
-
-    u->peek_index += length;
+    pa_sink_input_assert_ref(i);
+    pa_assert_se(u = i->userdata);
 
     l = pa_memblock_get_length(u->memblock);
+    nbytes %= l;
 
-    while (u->peek_index >= l)
-        u->peek_index -= l;
+    if (u->peek_index >= nbytes)
+        u->peek_index -= nbytes;
+    else
+        u->peek_index = l + u->peek_index - nbytes;
 }
 
 static void sink_input_kill_cb(pa_sink_input *i) {
     struct userdata *u;
 
-    pa_assert(i);
-    u = i->userdata;
-    pa_assert(u);
+    pa_sink_input_assert_ref(i);
+    pa_assert_se(u = i->userdata);
 
     pa_sink_input_unlink(u->sink_input);
     pa_sink_input_unref(u->sink_input);
@@ -105,6 +104,20 @@ static void sink_input_kill_cb(pa_sink_input *i) {
     pa_module_unload_request(u->module);
 }
 
+/* Called from IO thread context */
+static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) {
+    struct userdata *u;
+
+    pa_sink_input_assert_ref(i);
+    pa_assert_se(u = i->userdata);
+
+    /* If we are added for the first time, ask for a rewinding so that
+     * we are heard right-away. */
+    if (PA_SINK_INPUT_IS_LINKED(state) &&
+        i->thread_info.state == PA_SINK_INPUT_INIT)
+        pa_sink_input_request_rewind(i, 0, FALSE, TRUE);
+}
+
 static void calc_sine(float *f, size_t l, float freq) {
     size_t i;
 
@@ -120,7 +133,6 @@ int pa__init(pa_module*m) {
     pa_sink *sink;
     pa_sample_spec ss;
     uint32_t frequency;
-    char t[256];
     void *p;
     pa_sink_input_new_data data;
 
@@ -156,21 +168,25 @@ int pa__init(pa_module*m) {
     calc_sine(p, pa_memblock_get_length(u->memblock), frequency);
     pa_memblock_release(u->memblock);
 
-    pa_snprintf(t, sizeof(t), "Sine Generator at %u Hz", frequency);
-
     pa_sink_input_new_data_init(&data);
     data.sink = sink;
     data.driver = __FILE__;
-    data.name = t;
+    pa_proplist_setf(data.proplist, PA_PROP_MEDIA_NAME, "%u Hz Sine", frequency);
+    pa_proplist_sets(data.proplist, PA_PROP_MEDIA_ROLE, "abstract");
+    pa_proplist_setf(data.proplist, "sine.hz", "%u", frequency);
     pa_sink_input_new_data_set_sample_spec(&data, &ss);
     data.module = m;
 
-    if (!(u->sink_input = pa_sink_input_new(m->core, &data, 0)))
+    u->sink_input = pa_sink_input_new(m->core, &data, 0);
+    pa_sink_input_new_data_done(&data);
+
+    if (!u->sink_input)
         goto fail;
 
-    u->sink_input->peek = sink_input_peek_cb;
-    u->sink_input->drop = sink_input_drop_cb;
+    u->sink_input->pop = sink_input_pop_cb;
+    u->sink_input->process_rewind = sink_input_process_rewind_cb;
     u->sink_input->kill = sink_input_kill_cb;
+    u->sink_input->state_change = sink_input_state_change_cb;
     u->sink_input->userdata = u;
 
     pa_sink_input_put(u->sink_input);
index 4c260d7..a398597 100644 (file)
@@ -317,7 +317,7 @@ static pa_hook_result_t device_state_changed_hook_cb(pa_core *c, pa_object *o, s
 
         if (pa_sink_used_by(s) <= 0) {
 
-            if (PA_SINK_OPENED(state))
+            if (PA_SINK_IS_OPENED(state))
                 restart(d);
 
         }
@@ -328,7 +328,7 @@ static pa_hook_result_t device_state_changed_hook_cb(pa_core *c, pa_object *o, s
 
         if (pa_source_used_by(s) <= 0) {
 
-            if (PA_SOURCE_OPENED(state))
+            if (PA_SOURCE_IS_OPENED(state))
                 restart(d);
         }
     }
@@ -367,8 +367,8 @@ int pa__init(pa_module*m) {
     for (source = pa_idxset_first(m->core->sources, &idx); source; source = pa_idxset_next(m->core->sources, &idx))
         device_new_hook_cb(m->core, PA_OBJECT(source), u);
 
-    u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_NEW_POST], (pa_hook_cb_t) device_new_hook_cb, u);
-    u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_NEW_POST], (pa_hook_cb_t) device_new_hook_cb, u);
+    u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], (pa_hook_cb_t) device_new_hook_cb, u);
+    u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], (pa_hook_cb_t) device_new_hook_cb, u);
     u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK_POST], (pa_hook_cb_t) device_unlink_hook_cb, u);
     u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK_POST], (pa_hook_cb_t) device_unlink_hook_cb, u);
     u->sink_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED], (pa_hook_cb_t) device_state_changed_hook_cb, u);
index 62dac5d..7a87fd8 100644 (file)
@@ -303,7 +303,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
 
             /* First, change the state, because otherwide pa_sink_render() would fail */
             if ((r = pa_sink_process_msg(o, code, data, offset, chunk)) >= 0)
-                if (PA_SINK_OPENED((pa_sink_state_t) PA_PTR_TO_UINT(data)))
+                if (PA_SINK_IS_OPENED((pa_sink_state_t) PA_PTR_TO_UINT(data)))
                     send_data(u);
 
             return r;
@@ -314,7 +314,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
             pa_assert(offset > 0);
             u->requested_bytes += (size_t) offset;
 
-            if (PA_SINK_OPENED(u->sink->thread_info.state))
+            if (PA_SINK_IS_OPENED(u->sink->thread_info.state))
                 send_data(u);
 
             return 0;
@@ -343,7 +343,7 @@ static int sink_set_state(pa_sink *s, pa_sink_state_t state) {
     switch ((pa_sink_state_t) state) {
 
         case PA_SINK_SUSPENDED:
-            pa_assert(PA_SINK_OPENED(s->state));
+            pa_assert(PA_SINK_IS_OPENED(s->state));
             stream_cork(u, TRUE);
             break;
 
@@ -369,7 +369,7 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off
     switch (code) {
         case SOURCE_MESSAGE_POST:
 
-            if (PA_SOURCE_OPENED(u->source->thread_info.state))
+            if (PA_SOURCE_IS_OPENED(u->source->thread_info.state))
                 pa_source_post(u->source, chunk);
             return 0;
     }
@@ -385,7 +385,7 @@ static int source_set_state(pa_source *s, pa_source_state_t state) {
     switch ((pa_source_state_t) state) {
 
         case PA_SOURCE_SUSPENDED:
-            pa_assert(PA_SOURCE_OPENED(s->state));
+            pa_assert(PA_SOURCE_IS_OPENED(s->state));
             stream_cork(u, TRUE);
             break;
 
@@ -577,29 +577,29 @@ static void timeout_callback(pa_mainloop_api *m, pa_time_event*e, PA_GCC_UNUSED
 }
 
 #ifdef TUNNEL_SINK
-static pa_usec_t sink_get_latency(pa_sink *s) {
-    pa_usec_t t, c;
-    struct userdata *u = s->userdata;
+/* static pa_usec_t sink_get_latency(pa_sink *s) { */
+/*     pa_usec_t t, c; */
+/*     struct userdata *u = s->userdata; */
 
-    pa_sink_assert_ref(s);
+/*     pa_sink_assert_ref(s); */
 
-    c = pa_bytes_to_usec(u->counter, &s->sample_spec);
-    t = pa_smoother_get(u->smoother, pa_rtclock_usec());
+/*     c = pa_bytes_to_usec(u->counter, &s->sample_spec); */
+/*     t = pa_smoother_get(u->smoother, pa_rtclock_usec()); */
 
-    return c > t ? c - t : 0;
-}
+/*     return c > t ? c - t : 0; */
+/* } */
 #else
-static pa_usec_t source_get_latency(pa_source *s) {
-    pa_usec_t t, c;
-    struct userdata *u = s->userdata;
+/* static pa_usec_t source_get_latency(pa_source *s) { */
+/*     pa_usec_t t, c; */
+/*     struct userdata *u = s->userdata; */
 
-    pa_source_assert_ref(s);
+/*     pa_source_assert_ref(s); */
 
-    c = pa_bytes_to_usec(u->counter, &s->sample_spec);
-    t = pa_smoother_get(u->smoother, pa_rtclock_usec());
+/*     c = pa_bytes_to_usec(u->counter, &s->sample_spec); */
+/*     t = pa_smoother_get(u->smoother, pa_rtclock_usec()); */
 
-    return t > c ? t - c : 0;
-}
+/*     return t > c ? t - c : 0; */
+/* } */
 #endif
 
 static void update_description(struct userdata *u) {
@@ -1066,7 +1066,7 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t
     pa_tagstruct_putu32(reply, PA_INVALID_INDEX);
     pa_tagstruct_puts(reply, u->sink_name);
     pa_tagstruct_putu32(reply, u->maxlength);
-    pa_tagstruct_put_boolean(reply, !PA_SINK_OPENED(pa_sink_get_state(u->sink)));
+    pa_tagstruct_put_boolean(reply, !PA_SINK_IS_OPENED(pa_sink_get_state(u->sink)));
     pa_tagstruct_putu32(reply, u->tlength);
     pa_tagstruct_putu32(reply, u->prebuf);
     pa_tagstruct_putu32(reply, u->minreq);
@@ -1082,7 +1082,7 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t
     pa_tagstruct_putu32(reply, PA_INVALID_INDEX);
     pa_tagstruct_puts(reply, u->source_name);
     pa_tagstruct_putu32(reply, u->maxlength);
-    pa_tagstruct_put_boolean(reply, !PA_SOURCE_OPENED(pa_source_get_state(u->source)));
+    pa_tagstruct_put_boolean(reply, !PA_SOURCE_IS_OPENED(pa_source_get_state(u->source)));
     pa_tagstruct_putu32(reply, u->fragsize);
 #endif
 
@@ -1294,6 +1294,11 @@ int pa__init(pa_module*m) {
     pa_sample_spec ss;
     pa_channel_map map;
     char *t, *dn = NULL;
+#ifdef TUNNEL_SINK
+    pa_sink_new_data data;
+#else
+    pa_source_new_data data;
+#endif
 
     pa_assert(m);
 
@@ -1318,15 +1323,14 @@ 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);
+    u->smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC*2, TRUE, 10);
     u->ctag = 1;
     u->device_index = u->channel = PA_INVALID_INDEX;
     u->auth_cookie_in_property = FALSE;
     u->time_event = NULL;
 
-    pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
     u->rtpoll = pa_rtpoll_new();
-    pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
+    pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll);
 
     if (load_key(u, pa_modargs_get_value(ma, "cookie", NULL)) < 0)
         goto fail;
@@ -1354,7 +1358,18 @@ int pa__init(pa_module*m) {
     if (!(dn = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL))))
         dn = pa_sprintf_malloc("tunnel.%s", u->server_name);
 
-    if (!(u->sink = pa_sink_new(m->core, __FILE__, dn, 1, &ss, &map))) {
+    pa_sink_new_data_init(&data);
+    data.driver = __FILE__;
+    data.module = m;
+    data.namereg_fail = TRUE;
+    pa_sink_new_data_set_name(&data, dn);
+    pa_sink_new_data_set_sample_spec(&data, &ss);
+    pa_sink_new_data_set_channel_map(&data, &map);
+
+    u->sink = pa_sink_new(m->core, &data, PA_SINK_NETWORK|PA_SINK_LATENCY|PA_SINK_HW_VOLUME_CTRL);
+    pa_sink_new_data_done(&data);
+
+    if (!u->sink) {
         pa_log("Failed to create sink.");
         goto fail;
     }
@@ -1362,14 +1377,12 @@ int pa__init(pa_module*m) {
     u->sink->parent.process_msg = sink_process_msg;
     u->sink->userdata = u;
     u->sink->set_state = sink_set_state;
-    u->sink->get_latency = sink_get_latency;
+/*     u->sink->get_latency = sink_get_latency; */
     u->sink->get_volume = sink_get_volume;
     u->sink->get_mute = sink_get_mute;
     u->sink->set_volume = sink_set_volume;
     u->sink->set_mute = sink_set_mute;
-    u->sink->flags = PA_SINK_NETWORK|PA_SINK_LATENCY|PA_SINK_HW_VOLUME_CTRL;
 
-    pa_sink_set_module(u->sink, m);
     pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
     pa_sink_set_rtpoll(u->sink, u->rtpoll);
     pa_sink_set_description(u->sink, t = pa_sprintf_malloc("%s%s%s", u->sink_name ? u->sink_name : "", u->sink_name ? " on " : "", u->server_name));
@@ -1380,7 +1393,18 @@ int pa__init(pa_module*m) {
     if (!(dn = pa_xstrdup(pa_modargs_get_value(ma, "source_name", NULL))))
         dn = pa_sprintf_malloc("tunnel.%s", u->server_name);
 
-    if (!(u->source = pa_source_new(m->core, __FILE__, dn, 1, &ss, &map))) {
+    pa_source_new_data_init(&data);
+    data.driver = __FILE__;
+    data.module = m;
+    data.namereg_fail = TRUE;
+    pa_source_new_data_set_name(&data, dn);
+    pa_source_new_data_set_sample_spec(&data, &ss);
+    pa_source_new_data_set_channel_map(&data, &map);
+
+    u->source = pa_source_new(m->core, &data, PA_SOURCE_NETWORK|PA_SOURCE_LATENCY);
+    pa_source_new_data_done(&data);
+
+    if (!u->source) {
         pa_log("Failed to create source.");
         goto fail;
     }
@@ -1388,10 +1412,8 @@ int pa__init(pa_module*m) {
     u->source->parent.process_msg = source_process_msg;
     u->source->userdata = u;
     u->source->set_state = source_set_state;
-    u->source->get_latency = source_get_latency;
-    u->source->flags = PA_SOURCE_NETWORK|PA_SOURCE_LATENCY;
+/*     u->source->get_latency = source_get_latency; */
 
-    pa_source_set_module(u->source, m);
     pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
     pa_source_set_rtpoll(u->source, u->rtpoll);
     pa_source_set_description(u->source, t = pa_sprintf_malloc("%s%s%s", u->source_name ? u->source_name : "", u->source_name ? " on " : "", u->server_name));
index 192a2a7..336bcac 100644 (file)
@@ -115,7 +115,7 @@ static pa_cvolume* parse_volume(const char *s, pa_cvolume *v) {
 
         k = strtol(p, &p, 0);
 
-        if (k < PA_VOLUME_MUTED)
+        if (k < (long) PA_VOLUME_MUTED)
             return NULL;
 
         v->values[i] = (pa_volume_t) k;
@@ -134,16 +134,12 @@ static int load_rules(struct userdata *u) {
     char buf_name[256], buf_volume[256], buf_sink[256], buf_source[256];
     char *ln = buf_name;
 
-    f = u->table_file ?
-        fopen(u->table_file, "r") :
-        pa_open_config_file(NULL, DEFAULT_VOLUME_TABLE_FILE, NULL, &u->table_file, "r");
-
-    if (!f) {
+    if (!(f = fopen(u->table_file, "r"))) {
         if (errno == ENOENT) {
-            pa_log_info("starting with empty ruleset.");
+            pa_log_info("Starting with empty ruleset.");
             ret = 0;
         } else
-            pa_log("failed to open file '%s': %s", u->table_file, pa_cstrerror(errno));
+            pa_log("Failed to open file '%s': %s", u->table_file, pa_cstrerror(errno));
 
         goto finish;
     }
@@ -236,11 +232,7 @@ static int save_rules(struct userdata *u) {
 
     pa_log_info("Saving rules...");
 
-    f = u->table_file ?
-        fopen(u->table_file, "w") :
-        pa_open_config_file(NULL, DEFAULT_VOLUME_TABLE_FILE, NULL, &u->table_file, "w");
-
-    if (!f) {
+    if (!(f = fopen(u->table_file, "w"))) {
         pa_log("Failed to open file '%s': %s", u->table_file, pa_cstrerror(errno));
         goto finish;
     }
@@ -280,10 +272,10 @@ finish:
 static char* client_name(pa_client *c) {
     char *t, *e;
 
-    if (!c->name || !c->driver)
+    if (!pa_proplist_gets(c->proplist, PA_PROP_APPLICATION_NAME) || !c->driver)
         return NULL;
 
-    t = pa_sprintf_malloc("%s$%s", c->driver, c->name);
+    t = pa_sprintf_malloc("%s$%s", c->driver, pa_proplist_gets(c->proplist, PA_PROP_APPLICATION_NAME));
     t[strcspn(t, "\n\r#")] = 0;
 
     if (!*t) {
@@ -496,7 +488,7 @@ int pa__init(pa_module*m) {
     u = pa_xnew(struct userdata, 1);
     u->core = m->core;
     u->hashmap = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
-    u->table_file = pa_xstrdup(pa_modargs_get_value(ma, "table", NULL));
+    u->table_file = pa_runtime_path(pa_modargs_get_value(ma, "table", DEFAULT_VOLUME_TABLE_FILE));
     u->modified = FALSE;
     u->subscription = NULL;
     u->sink_input_new_hook_slot = u->sink_input_fixate_hook_slot = u->source_output_new_hook_slot = NULL;
index 87c6849..761b82a 100644 (file)
@@ -81,7 +81,7 @@ static int x11_event_callback(pa_x11_wrapper *w, XEvent *e, void *userdata) {
 
     bne = (XkbBellNotifyEvent*) e;
 
-    if (pa_scache_play_item_by_name(u->core, u->scache_item, u->sink_name, (bne->percent*PA_VOLUME_NORM)/100, 1) < 0) {
+    if (pa_scache_play_item_by_name(u->core, u->scache_item, u->sink_name, TRUE, (bne->percent*PA_VOLUME_NORM)/100, 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);
     }
index 46969a2..6ed8e3d 100644 (file)
@@ -115,7 +115,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 = sink->description;
+        *ret_description = pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION));
         *ret_subtype = sink->flags & PA_SINK_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL;
 
     } else if (pa_source_isinstance(s->device)) {
@@ -124,7 +124,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 = source->description;
+        *ret_description = pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION));
         *ret_subtype = source->monitor_of ? SUBTYPE_MONITOR : (source->flags & PA_SOURCE_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL);
 
     } else
@@ -304,10 +304,10 @@ static struct service *get_service(struct userdata *u, pa_object *device) {
     s->device = device;
 
     if (pa_sink_isinstance(device)) {
-        if (!(n = PA_SINK(device)->description))
+        if (!(n = pa_proplist_gets(PA_SINK(device)->proplist, PA_PROP_DEVICE_DESCRIPTION)))
             n = PA_SINK(device)->name;
     } else {
-        if (!(n = PA_SOURCE(device)->description))
+        if (!(n = pa_proplist_gets(PA_SOURCE(device)->proplist, PA_PROP_DEVICE_DESCRIPTION)))
             n = PA_SOURCE(device)->name;
     }
 
@@ -578,11 +578,11 @@ int pa__init(pa_module*m) {
 
     u->services = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
 
-    u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_NEW_POST], (pa_hook_cb_t) device_new_or_changed_cb, u);
-    u->sink_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_DESCRIPTION_CHANGED], (pa_hook_cb_t) device_new_or_changed_cb, u);
+    u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], (pa_hook_cb_t) device_new_or_changed_cb, u);
+    u->sink_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PROPLIST_CHANGED], (pa_hook_cb_t) device_new_or_changed_cb, u);
     u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], (pa_hook_cb_t) device_unlink_cb, u);
-    u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_NEW_POST], (pa_hook_cb_t) device_new_or_changed_cb, u);
-    u->source_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_DESCRIPTION_CHANGED], (pa_hook_cb_t) device_new_or_changed_cb, u);
+    u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], (pa_hook_cb_t) device_new_or_changed_cb, u);
+    u->source_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED], (pa_hook_cb_t) device_new_or_changed_cb, u);
     u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], (pa_hook_cb_t) device_unlink_cb, u);
 
     u->main_entry_group = NULL;
index 9598fee..e29f0ed 100644 (file)
@@ -251,7 +251,7 @@ int pa_oss_set_fragments(int fd, int nfrags, int frag_size) {
     return 0;
 }
 
-int pa_oss_get_volume(int fd, int mixer, const pa_sample_spec *ss, pa_cvolume *volume) {
+int pa_oss_get_volume(int fd, unsigned long mixer, const pa_sample_spec *ss, pa_cvolume *volume) {
     char cv[PA_CVOLUME_SNPRINT_MAX];
     unsigned vol;
 
@@ -273,7 +273,7 @@ int pa_oss_get_volume(int fd, int mixer, const pa_sample_spec *ss, pa_cvolume *v
     return 0;
 }
 
-int pa_oss_set_volume(int fd, long mixer, const pa_sample_spec *ss, const pa_cvolume *volume) {
+int pa_oss_set_volume(int fd, unsigned long mixer, const pa_sample_spec *ss, const pa_cvolume *volume) {
     char cv[PA_CVOLUME_SNPRINT_MAX];
     unsigned vol;
     pa_volume_t l, r;
index 259a622..8fea805 100644 (file)
@@ -33,8 +33,8 @@ int pa_oss_auto_format(int fd, pa_sample_spec *ss);
 
 int pa_oss_set_fragments(int fd, int frags, int frag_size);
 
-int pa_oss_set_volume(int fd, long mixer, const pa_sample_spec *ss, const pa_cvolume *volume);
-int pa_oss_get_volume(int fd, int mixer, const pa_sample_spec *ss, pa_cvolume *volume);
+int pa_oss_set_volume(int fd, unsigned long mixer, const pa_sample_spec *ss, const pa_cvolume *volume);
+int pa_oss_get_volume(int fd, unsigned long mixer, const pa_sample_spec *ss, pa_cvolume *volume);
 
 int pa_oss_get_hw_description(const char *dev, char *name, size_t l);
 
index d8e7a78..cff5cf8 100644 (file)
@@ -51,6 +51,7 @@
 #include <pulsecore/atomic.h>
 #include <pulsecore/rtclock.h>
 #include <pulsecore/atomic.h>
+#include <pulsecore/time-smoother.h>
 
 #include "module-rtp-recv-symdef.h"
 
@@ -69,9 +70,11 @@ PA_MODULE_USAGE(
 
 #define SAP_PORT 9875
 #define DEFAULT_SAP_ADDRESS "224.0.0.56"
-#define MEMBLOCKQ_MAXLENGTH (1024*170)
+#define MEMBLOCKQ_MAXLENGTH (1024*1024*40)
 #define MAX_SESSIONS 16
 #define DEATH_TIMEOUT 20
+#define RATE_UPDATE_INTERVAL (5*PA_USEC_PER_SEC)
+#define LATENCY_USEC (500*PA_USEC_PER_MSEC)
 
 static const char* const valid_modargs[] = {
     "sink",
@@ -97,6 +100,12 @@ struct session {
     pa_rtpoll_item *rtpoll_item;
 
     pa_atomic_t timestamp;
+
+    pa_smoother *smoother;
+    pa_usec_t intended_latency;
+    pa_usec_t sink_latency;
+
+    pa_usec_t last_rate_update;
 };
 
 struct userdata {
@@ -133,21 +142,37 @@ static int sink_input_process_msg(pa_msgobject *o, int code, void *data, int64_t
 }
 
 /* Called from I/O thread context */
-static int sink_input_peek(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
+static int sink_input_pop_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
     struct session *s;
     pa_sink_input_assert_ref(i);
     pa_assert_se(s = i->userdata);
 
-    return pa_memblockq_peek(s->memblockq, chunk);
+    if (pa_memblockq_peek(s->memblockq, chunk) < 0)
+        return -1;
+
+    pa_memblockq_drop(s->memblockq, chunk->length);
+
+    return 0;
 }
 
 /* Called from I/O thread context */
-static void sink_input_drop(pa_sink_input *i, size_t length) {
+static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
     struct session *s;
+
     pa_sink_input_assert_ref(i);
     pa_assert_se(s = i->userdata);
 
-    pa_memblockq_drop(s->memblockq, length);
+    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) {
+    struct session *s;
+
+    pa_sink_input_assert_ref(i);
+    pa_assert_se(s = i->userdata);
+
+    pa_memblockq_set_maxrewind(s->memblockq, nbytes);
 }
 
 /* Called from main context */
@@ -215,20 +240,82 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) {
 
     pa_memblockq_seek(s->memblockq, delta * s->rtp_context.frame_size, PA_SEEK_RELATIVE);
 
+    pa_rtclock_get(&now);
+
+    pa_smoother_put(s->smoother, pa_timeval_load(&now), pa_bytes_to_usec(pa_memblockq_get_write_index(s->memblockq), &s->sink_input->sample_spec));
+
     if (pa_memblockq_push(s->memblockq, &chunk) < 0) {
-        /* queue overflow, let's flush it and try again */
-        pa_memblockq_flush(s->memblockq);
-        pa_memblockq_push(s->memblockq, &chunk);
+        pa_log_warn("Queue overrun");
+        pa_memblockq_seek(s->memblockq, chunk.length, PA_SEEK_RELATIVE);
     }
 
-    /* The next timestamp we expect */
-    s->offset = s->rtp_context.timestamp + (chunk.length / s->rtp_context.frame_size);
+    pa_log("blocks in q: %u", pa_memblockq_get_nblocks(s->memblockq));
 
     pa_memblock_unref(chunk.memblock);
 
-    pa_rtclock_get(&now);
+    /* The next timestamp we expect */
+    s->offset = s->rtp_context.timestamp + (chunk.length / s->rtp_context.frame_size);
+
     pa_atomic_store(&s->timestamp, now.tv_sec);
 
+    if (s->last_rate_update + RATE_UPDATE_INTERVAL < pa_timeval_load(&now)) {
+        pa_usec_t wi, ri, render_delay, sink_delay = 0, latency, fix;
+        unsigned fix_samples;
+
+        pa_log("Updating sample rate");
+
+        wi = pa_smoother_get(s->smoother, pa_timeval_load(&now));
+        ri = pa_bytes_to_usec(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;
+
+        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)
+            ri -= render_delay+sink_delay;
+        else
+            ri = 0;
+
+        if (wi < ri)
+            latency = 0;
+        else
+            latency = wi - ri;
+
+        pa_log_debug("Write index deviates by %0.2f ms, expected %0.2f ms", (double) latency/PA_USEC_PER_MSEC, (double)  s->intended_latency/PA_USEC_PER_MSEC);
+
+        /* Calculate deviation */
+        if (latency < s->intended_latency)
+            fix = s->intended_latency - latency;
+        else
+            fix = latency - s->intended_latency;
+
+        /* How many samples is this per second? */
+        fix_samples = fix * s->sink_input->thread_info.sample_spec.rate / RATE_UPDATE_INTERVAL;
+
+        /* Check if deviation is in bounds */
+        if (fix_samples > s->sink_input->sample_spec.rate*.20)
+            pa_log_debug("Hmmm, rate fix is too large (%lu Hz), not applying.", (unsigned long) fix_samples);
+
+        /* 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_resampler_set_input_rate(s->sink_input->thread_info.resampler, s->sink_input->sample_spec.rate);
+
+        pa_log_debug("Updated sampling rate to %lu Hz.", (unsigned long) s->sink_input->sample_spec.rate);
+
+        s->last_rate_update = pa_timeval_load(&now);
+    }
+
+    if (pa_memblockq_is_readable(s->memblockq) &&
+        s->sink_input->thread_info.underrun_for > 0) {
+        pa_log_debug("Requesting rewind due to end of underrun");
+        pa_sink_input_request_rewind(s->sink_input, 0, FALSE, TRUE);
+    }
+
     return 1;
 }
 
@@ -314,10 +401,9 @@ fail:
 
 static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_info) {
     struct session *s = NULL;
-    char *c;
     pa_sink *sink;
     int fd = -1;
-    pa_memblock *silence;
+    pa_memchunk silence;
     pa_sink_input_new_data data;
     struct timeval now;
 
@@ -329,37 +415,46 @@ static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_in
         goto fail;
     }
 
-    if (!(sink = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, 1))) {
+    if (!(sink = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, TRUE))) {
         pa_log("Sink does not exist.");
         goto fail;
     }
 
+    pa_rtclock_get(&now);
+
     s = pa_xnew0(struct session, 1);
     s->userdata = u;
     s->first_packet = FALSE;
     s->sdp_info = *sdp_info;
     s->rtpoll_item = NULL;
-
-    pa_rtclock_get(&now);
+    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->last_rate_update = pa_timeval_load(&now);
     pa_atomic_store(&s->timestamp, now.tv_sec);
 
     if ((fd = mcast_socket((const struct sockaddr*) &sdp_info->sa, sdp_info->salen)) < 0)
         goto fail;
 
-    c = pa_sprintf_malloc("RTP Stream%s%s%s",
-                          sdp_info->session_name ? " (" : "",
-                          sdp_info->session_name ? sdp_info->session_name : "",
-                          sdp_info->session_name ? ")" : "");
-
     pa_sink_input_new_data_init(&data);
     data.sink = sink;
     data.driver = __FILE__;
-    data.name = c;
+    pa_proplist_sets(data.proplist, PA_PROP_MEDIA_ROLE, "stream");
+    pa_proplist_setf(data.proplist, PA_PROP_MEDIA_NAME,
+                     "RTP Stream%s%s%s",
+                     sdp_info->session_name ? " (" : "",
+                     sdp_info->session_name ? sdp_info->session_name : "",
+                     sdp_info->session_name ? ")" : "");
+
+    if (sdp_info->session_name)
+        pa_proplist_sets(data.proplist, "rtp.session", sdp_info->session_name);
+    pa_proplist_sets(data.proplist, "rtp.origin", sdp_info->origin);
+    pa_proplist_setf(data.proplist, "rtp.payload", "%u", (unsigned) sdp_info->payload);
     data.module = u->module;
     pa_sink_input_new_data_set_sample_spec(&data, &sdp_info->sample_spec);
 
     s->sink_input = pa_sink_input_new(u->module->core, &data, 0);
-    pa_xfree(c);
+    pa_sink_input_new_data_done(&data);
 
     if (!s->sink_input) {
         pa_log("Failed to create sink input.");
@@ -369,27 +464,31 @@ static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_in
     s->sink_input->userdata = s;
 
     s->sink_input->parent.process_msg = sink_input_process_msg;
-    s->sink_input->peek = sink_input_peek;
-    s->sink_input->drop = sink_input_drop;
+    s->sink_input->pop = sink_input_pop_cb;
+    s->sink_input->process_rewind = sink_input_process_rewind_cb;
+    s->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
     s->sink_input->kill = sink_input_kill;
     s->sink_input->attach = sink_input_attach;
     s->sink_input->detach = sink_input_detach;
 
-    silence = pa_silence_memblock_new(
-            s->userdata->module->core->mempool,
-            &s->sink_input->sample_spec,
-            pa_frame_align(pa_bytes_per_second(&s->sink_input->sample_spec)/128, &s->sink_input->sample_spec));
+    pa_sink_input_get_silence(s->sink_input, &silence);
+
+    s->sink_latency = pa_sink_input_set_requested_latency(s->sink_input, s->intended_latency/2);
+
+    if (s->intended_latency < s->sink_latency*2)
+        s->intended_latency = s->sink_latency*2;
 
     s->memblockq = pa_memblockq_new(
             0,
             MEMBLOCKQ_MAXLENGTH,
             MEMBLOCKQ_MAXLENGTH,
             pa_frame_size(&s->sink_input->sample_spec),
-            pa_bytes_per_second(&s->sink_input->sample_spec)/10+1,
+            pa_usec_to_bytes(s->intended_latency - s->sink_latency, &s->sink_input->sample_spec),
+            0,
             0,
-            silence);
+            &silence);
 
-    pa_memblock_unref(silence);
+    pa_memblock_unref(silence.memblock);
 
     pa_rtp_context_init_recv(&s->rtp_context, fd, pa_frame_size(&s->sdp_info.sample_spec));
 
@@ -429,12 +528,14 @@ static void session_free(struct session *s) {
     pa_sdp_info_destroy(&s->sdp_info);
     pa_rtp_context_destroy(&s->rtp_context);
 
+    pa_smoother_free(s->smoother);
+
     pa_xfree(s);
 }
 
 static void sap_event_cb(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event_flags_t flags, void *userdata) {
     struct userdata *u = userdata;
-    int goodbye;
+    pa_bool_t goodbye = FALSE;
     pa_sdp_info info;
     struct session *s;
 
index 95ff15d..3a526c1 100644 (file)
@@ -288,14 +288,20 @@ int pa__init(pa_module*m) {
     pa_make_fd_cloexec(sap_fd);
 
     pa_source_output_new_data_init(&data);
-    data.name = "RTP Monitor Stream";
+    pa_proplist_sets(data.proplist, PA_PROP_MEDIA_NAME, "RTP Monitor Stream");
+    pa_proplist_sets(data.proplist, "rtp.destination", dest);
+    pa_proplist_setf(data.proplist, "rtp.mtu", "%lu", (unsigned long) mtu);
+    pa_proplist_setf(data.proplist, "rtp.port", "%lu", (unsigned long) port);
     data.driver = __FILE__;
     data.module = m;
     data.source = s;
     pa_source_output_new_data_set_sample_spec(&data, &ss);
     pa_source_output_new_data_set_channel_map(&data, &cm);
 
-    if (!(o = pa_source_output_new(m->core, &data, 0))) {
+    o = pa_source_output_new(m->core, &data, 0);
+    pa_source_output_new_data_done(&data);
+
+    if (!o) {
         pa_log("failed to create source output.");
         goto fail;
     }
@@ -318,6 +324,7 @@ int pa__init(pa_module*m) {
             pa_frame_size(&ss),
             1,
             0,
+            0,
             NULL);
 
     u->mtu = mtu;
index 997fcc3..5c29984 100644 (file)
@@ -55,6 +55,8 @@ pa_rtp_context* pa_rtp_context_init_send(pa_rtp_context *c, int fd, uint32_t ssr
     c->payload = payload & 127;
     c->frame_size = frame_size;
 
+    pa_memchunk_reset(&c->memchunk);
+
     return c;
 }
 
@@ -152,6 +154,8 @@ pa_rtp_context* pa_rtp_context_init_recv(pa_rtp_context *c, int fd, size_t frame
 
     c->fd = fd;
     c->frame_size = frame_size;
+
+    pa_memchunk_reset(&c->memchunk);
     return c;
 }
 
@@ -173,12 +177,28 @@ int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) {
         goto fail;
     }
 
-    if (!size)
+    if (size <= 0)
         return 0;
 
-    chunk->memblock = pa_memblock_new(pool, size);
+    if (c->memchunk.length < (unsigned) size) {
+        size_t l;
+
+        if (c->memchunk.memblock)
+            pa_memblock_unref(c->memchunk.memblock);
+
+        l = PA_MAX((size_t) size, pa_mempool_block_size_max(pool));
+
+        c->memchunk.memblock = pa_memblock_new(pool, l);
+        c->memchunk.index = 0;
+        c->memchunk.length = pa_memblock_get_length(c->memchunk.memblock);
+    }
+
+    pa_assert(c->memchunk.length >= (size_t) size);
 
-    iov.iov_base = pa_memblock_acquire(chunk->memblock);
+    chunk->memblock = pa_memblock_ref(c->memchunk.memblock);
+    chunk->index = c->memchunk.index;
+
+    iov.iov_base = (uint8_t*) pa_memblock_acquire(chunk->memblock) + chunk->index;
     iov.iov_len = size;
 
     m.msg_name = NULL;
@@ -236,14 +256,22 @@ int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) {
         goto fail;
     }
 
-    chunk->index = 12 + cc*4;
-    chunk->length = size - chunk->index;
+    chunk->index += 12 + cc*4;
+    chunk->length = size - 12 + cc*4;
 
     if (chunk->length % c->frame_size != 0) {
         pa_log_warn("Bad RTP packet size.");
         goto fail;
     }
 
+    c->memchunk.index = chunk->index + chunk->length;
+    c->memchunk.length = pa_memblock_get_length(c->memchunk.memblock) - c->memchunk.index;
+
+    if (c->memchunk.length <= 0) {
+        pa_memblock_unref(c->memchunk.memblock);
+        pa_memchunk_reset(&c->memchunk);
+    }
+
     return 0;
 
 fail:
@@ -329,7 +357,10 @@ int pa_rtp_sample_spec_valid(const pa_sample_spec *ss) {
 void pa_rtp_context_destroy(pa_rtp_context *c) {
     pa_assert(c);
 
-    pa_close(c->fd);
+    pa_assert_se(pa_close(c->fd) == 0);
+
+    if (c->memchunk.memblock)
+        pa_memblock_unref(c->memchunk.memblock);
 }
 
 const char* pa_rtp_format_to_string(pa_sample_format_t f) {
@@ -361,4 +392,3 @@ pa_sample_format_t pa_rtp_string_to_format(const char *s) {
     else
         return PA_SAMPLE_INVALID;
 }
-
index ad7175c..a366d7a 100644 (file)
@@ -37,6 +37,8 @@ typedef struct pa_rtp_context {
     uint32_t ssrc;
     uint8_t payload;
     size_t frame_size;
+
+    pa_memchunk memchunk;
 } pa_rtp_context;
 
 pa_rtp_context* pa_rtp_context_init_send(pa_rtp_context *c, int fd, uint32_t ssrc, uint8_t payload, size_t frame_size);
index ed7eb0b..123bc49 100644 (file)
@@ -71,7 +71,7 @@ void pa_sap_context_destroy(pa_sap_context *c) {
     pa_xfree(c->sdp_data);
 }
 
-int pa_sap_send(pa_sap_context *c, int goodbye) {
+int pa_sap_send(pa_sap_context *c, pa_bool_t goodbye) {
     uint32_t header;
     struct sockaddr_storage sa_buf;
     struct sockaddr *sa = (struct sockaddr*) &sa_buf;
@@ -127,7 +127,7 @@ pa_sap_context* pa_sap_context_init_recv(pa_sap_context *c, int fd) {
     return c;
 }
 
-int pa_sap_recv(pa_sap_context *c, int *goodbye) {
+int pa_sap_recv(pa_sap_context *c, pa_bool_t *goodbye) {
     struct msghdr m;
     struct iovec iov;
     int size, k;
index f906a32..db096d6 100644 (file)
@@ -40,9 +40,9 @@ typedef struct pa_sap_context {
 pa_sap_context* pa_sap_context_init_send(pa_sap_context *c, int fd, char *sdp_data);
 void pa_sap_context_destroy(pa_sap_context *c);
 
-int pa_sap_send(pa_sap_context *c, int goodbye);
+int pa_sap_send(pa_sap_context *c, pa_bool_t goodbye);
 
 pa_sap_context* pa_sap_context_init_recv(pa_sap_context *c, int fd);
-int pa_sap_recv(pa_sap_context *c, int *goodbye);
+int pa_sap_recv(pa_sap_context *c, pa_bool_t *goodbye);
 
 #endif
index 50ac157..9265a20 100644 (file)
@@ -117,7 +117,7 @@ static pa_sample_spec *parse_sdp_sample_spec(pa_sample_spec *ss, char *c) {
 
 pa_sdp_info *pa_sdp_parse(const char *t, pa_sdp_info *i, int is_goodbye) {
     uint16_t port = 0;
-    int ss_valid = 0;
+    pa_bool_t ss_valid = FALSE;
 
     pa_assert(t);
     pa_assert(i);
@@ -202,7 +202,7 @@ pa_sdp_info *pa_sdp_parse(const char *t, pa_sdp_info *i, int is_goodbye) {
                     i->payload = (uint8_t) _payload;
 
                     if (pa_rtp_sample_spec_from_payload(i->payload, &i->sample_spec))
-                        ss_valid = 1;
+                        ss_valid = TRUE;
                 }
             }
         } else if (pa_startswith(t, "a=rtpmap:")) {
@@ -222,7 +222,7 @@ pa_sdp_info *pa_sdp_parse(const char *t, pa_sdp_info *i, int is_goodbye) {
                         c[strcspn(c, "\n")] = 0;
 
                         if (parse_sdp_sample_spec(&i->sample_spec, c))
-                            ss_valid = 1;
+                            ss_valid = TRUE;
                     }
                 }
             }
index 55e0b2c..5e4aa87 100644 (file)
@@ -313,10 +313,15 @@ static void client_callback(AvahiClient *s, AvahiClientState state, void *userda
 
 static void browser_free(pa_browser *b);
 
+
+PA_WARN_REFERENCE(pa_browser_new, "libpulse-browse is being phased out.");
+
 pa_browser *pa_browser_new(pa_mainloop_api *mainloop) {
     return pa_browser_new_full(mainloop, PA_BROWSE_FOR_SERVERS|PA_BROWSE_FOR_SINKS|PA_BROWSE_FOR_SOURCES, NULL);
 }
 
+PA_WARN_REFERENCE(pa_browser_new_full, "libpulse-browse is being phased out.");
+
 pa_browser *pa_browser_new_full(pa_mainloop_api *mainloop, pa_browse_flags_t flags, const char **error_string) {
     pa_browser *b;
     int error;
@@ -420,6 +425,8 @@ static void browser_free(pa_browser *b) {
     pa_xfree(b);
 }
 
+PA_WARN_REFERENCE(pa_browser_ref, "libpulse-browse is being phased out.");
+
 pa_browser *pa_browser_ref(pa_browser *b) {
     pa_assert(b);
     pa_assert(PA_REFCNT_VALUE(b) >= 1);
@@ -428,6 +435,8 @@ pa_browser *pa_browser_ref(pa_browser *b) {
     return b;
 }
 
+PA_WARN_REFERENCE(pa_browser_unref, "libpulse-browse is being phased out.");
+
 void pa_browser_unref(pa_browser *b) {
     pa_assert(b);
     pa_assert(PA_REFCNT_VALUE(b) >= 1);
@@ -436,6 +445,8 @@ void pa_browser_unref(pa_browser *b) {
         browser_free(b);
 }
 
+PA_WARN_REFERENCE(pa_browser_set_callback, "libpulse-browse is being phased out.");
+
 void pa_browser_set_callback(pa_browser *b, pa_browse_cb_t cb, void *userdata) {
     pa_assert(b);
     pa_assert(PA_REFCNT_VALUE(b) >= 1);
@@ -444,6 +455,8 @@ void pa_browser_set_callback(pa_browser *b, pa_browse_cb_t cb, void *userdata) {
     b->userdata = userdata;
 }
 
+PA_WARN_REFERENCE(pa_browser_set_error_callback, "libpulse-browse is being phased out.");
+
 void pa_browser_set_error_callback(pa_browser *b, pa_browser_error_cb_t cb, void *userdata) {
     pa_assert(b);
     pa_assert(PA_REFCNT_VALUE(b) >= 1);
index e1f23d2..922ad27 100644 (file)
 
 #endif
 
-#ifndef PA_GCC_PURE
-#ifdef __GNUCC__
-#define PA_GCC_PURE __attribute__ ((pure))
-#else
-/** This function's return value depends only the arguments list and global state **/
-#define PA_GCC_PURE
-#endif
-#endif
-
-#ifndef PA_GCC_CONST
-#ifdef __GNUCC__
-#define PA_GCC_CONST __attribute__ ((pure))
-#else
-/** This function's return value depends only the arguments list (stricter version of PA_GCC_CONST) **/
-#define PA_GCC_CONST
-#endif
-#endif
-
 #endif
index a05e191..00d3eb0 100644 (file)
@@ -27,6 +27,7 @@
 
 #include <pulse/sample.h>
 #include <pulse/cdecl.h>
+#include <pulse/gccmacro.h>
 
 /** \page channelmap Channel Maps
  *
@@ -183,7 +184,7 @@ const char* pa_channel_position_to_pretty_string(pa_channel_position_t pos);
 /** Make a humand 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 into a channel map structure. \since 0.8.1 */
+/** Parse a channel position list into a channel map structure. */
 pa_channel_map *pa_channel_map_parse(pa_channel_map *map, const char *s);
 
 /** Compare two channel maps. Return 1 if both match. */
index e240ba8..49df4b6 100644 (file)
@@ -46,7 +46,10 @@ int pa_client_conf_from_x11(pa_client_conf *c, const char *dname) {
 
     pa_assert(c);
 
-    if (!dname && (!(dname = getenv("DISPLAY")) || *dname == '\0'))
+    if (!dname && !(dname = getenv("DISPLAY")))
+        goto finish;
+
+    if (*dname == 0)
         goto finish;
 
     if (!(d = XOpenDisplay(dname))) {
@@ -80,7 +83,7 @@ int pa_client_conf_from_x11(pa_client_conf *c, const char *dname) {
         pa_assert(sizeof(cookie) == sizeof(c->cookie));
         memcpy(c->cookie, cookie, sizeof(cookie));
 
-        c->cookie_valid = 1;
+        c->cookie_valid = TRUE;
 
         pa_xfree(c->cookie_file);
         c->cookie_file = NULL;
index c054f66..75f4418 100644 (file)
@@ -112,13 +112,20 @@ int pa_client_conf_load(pa_client_conf *c, const char *filename) {
     table[6].data = &c->cookie_file;
     table[7].data = &c->disable_shm;
 
-    f = filename ?
-        fopen((fn = pa_xstrdup(filename)), "r") :
-        pa_open_config_file(DEFAULT_CLIENT_CONFIG_FILE, DEFAULT_CLIENT_CONFIG_FILE_USER, ENV_CLIENT_CONFIG_FILE, &fn, "r");
+    if (filename) {
 
-    if (!f && errno != EINTR) {
-        pa_log_warn("Failed to open configuration file '%s': %s", fn, pa_cstrerror(errno));
-        goto finish;
+        if (!(f = fopen(filename, "r"))) {
+            pa_log("Failed to open configuration file '%s': %s", fn, pa_cstrerror(errno));
+            goto finish;
+        }
+
+        fn = pa_xstrdup(fn);
+
+    } else {
+
+        if (!(f = pa_open_config_file(DEFAULT_CLIENT_CONFIG_FILE, DEFAULT_CLIENT_CONFIG_FILE_USER, ENV_CLIENT_CONFIG_FILE, &fn)))
+            if (errno != ENOENT)
+                goto finish;
     }
 
     r = f ? pa_config_parse(fn, f, table, NULL) : 0;
@@ -126,7 +133,6 @@ int pa_client_conf_load(pa_client_conf *c, const char *filename) {
     if (!r)
         r = pa_client_conf_load_cookie(c);
 
-
 finish:
     pa_xfree(fn);
 
index 7243a29..f9f021a 100644 (file)
@@ -3,7 +3,7 @@
 /***
   This file is part of PulseAudio.
 
-  Copyright 2004-2006 Lennart Poettering
+  Copyright 2004-2008 Lennart Poettering
   Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
 
   PulseAudio is free software; you can redistribute it and/or modify
@@ -35,6 +35,7 @@
 #include <errno.h>
 #include <signal.h>
 #include <limits.h>
+#include <locale.h>
 
 #ifdef HAVE_SYS_WAIT_H
 #include <sys/wait.h>
@@ -52,6 +53,8 @@
 
 #include <pulse/version.h>
 #include <pulse/xmalloc.h>
+#include <pulse/utf8.h>
+#include <pulse/util.h>
 
 #include <pulsecore/winsock.h>
 #include <pulsecore/core-error.h>
@@ -90,6 +93,7 @@ static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = {
     [PA_COMMAND_RECORD_STREAM_MOVED] = pa_command_stream_moved,
     [PA_COMMAND_PLAYBACK_STREAM_SUSPENDED] = pa_command_stream_suspended,
     [PA_COMMAND_RECORD_STREAM_SUSPENDED] = pa_command_stream_suspended,
+    [PA_COMMAND_STARTED] = pa_command_stream_started,
     [PA_COMMAND_SUBSCRIBE_EVENT] = pa_command_subscribe_event
 };
 
@@ -97,10 +101,12 @@ static void unlock_autospawn_lock_file(pa_context *c) {
     pa_assert(c);
 
     if (c->autospawn_lock_fd >= 0) {
-        char lf[PATH_MAX];
-        pa_runtime_path(AUTOSPAWN_LOCK, lf, sizeof(lf));
+        char *lf;
 
+        lf = pa_runtime_path(AUTOSPAWN_LOCK);
         pa_unlock_lockfile(lf, c->autospawn_lock_fd);
+        pa_xfree(lf);
+
         c->autospawn_lock_fd = -1;
     }
 }
@@ -108,20 +114,42 @@ static void unlock_autospawn_lock_file(pa_context *c) {
 static void context_free(pa_context *c);
 
 pa_context *pa_context_new(pa_mainloop_api *mainloop, const char *name) {
+    return pa_context_new_with_proplist(mainloop, name, NULL);
+}
+
+static void reset_callbacks(pa_context *c) {
+    pa_assert(c);
+
+    c->state_callback = NULL;
+    c->state_userdata = NULL;
+
+    c->subscribe_callback = NULL;
+    c->subscribe_userdata = NULL;
+}
+
+pa_context *pa_context_new_with_proplist(pa_mainloop_api *mainloop, const char *name, pa_proplist *p) {
     pa_context *c;
 
     pa_assert(mainloop);
-    pa_assert(name);
+
+    if (!name && !pa_proplist_contains(p, PA_PROP_APPLICATION_NAME))
+        return NULL;
 
     c = pa_xnew(pa_context, 1);
     PA_REFCNT_INIT(c);
-    c->name = pa_xstrdup(name);
+
+    c->proplist = p ? pa_proplist_copy(p) : pa_proplist_new();
+
+    if (name)
+        pa_proplist_sets(c->proplist, PA_PROP_APPLICATION_NAME, name);
+
     c->mainloop = mainloop;
     c->client = NULL;
     c->pstream = NULL;
     c->pdispatch = NULL;
     c->playback_streams = pa_dynarray_new();
     c->record_streams = pa_dynarray_new();
+    c->client_index = PA_INVALID_INDEX;
 
     PA_LLIST_HEAD_INIT(pa_stream, c->streams);
     PA_LLIST_HEAD_INIT(pa_operation, c->operations);
@@ -131,18 +159,14 @@ pa_context *pa_context_new(pa_mainloop_api *mainloop, const char *name) {
     c->ctag = 0;
     c->csyncid = 0;
 
-    c->state_callback = NULL;
-    c->state_userdata = NULL;
-
-    c->subscribe_callback = NULL;
-    c->subscribe_userdata = NULL;
+    reset_callbacks(c);
 
-    c->is_local = -1;
+    c->is_local = FALSE;
     c->server_list = NULL;
     c->server = NULL;
     c->autospawn_lock_fd = -1;
     memset(&c->spawn_api, 0, sizeof(c->spawn_api));
-    c->do_autospawn = 0;
+    c->do_autospawn = FALSE;
 
 #ifndef MSG_NOSIGNAL
 #ifdef SIGPIPE
@@ -171,26 +195,48 @@ pa_context *pa_context_new(pa_mainloop_api *mainloop, const char *name) {
     return c;
 }
 
-static void context_free(pa_context *c) {
+static void context_unlink(pa_context *c) {
+    pa_stream *s;
+
     pa_assert(c);
 
-    unlock_autospawn_lock_file(c);
+    s = c->streams ? pa_stream_ref(c->streams) : NULL;
+    while (s) {
+        pa_stream *n = s->next ? pa_stream_ref(s->next) : NULL;
+        pa_stream_set_state(s, c->state == PA_CONTEXT_FAILED ? PA_STREAM_FAILED : PA_STREAM_TERMINATED);
+        pa_stream_unref(s);
+        s = n;
+    }
 
     while (c->operations)
         pa_operation_cancel(c->operations);
 
-    while (c->streams)
-        pa_stream_set_state(c->streams, PA_STREAM_TERMINATED);
-
-    if (c->client)
-        pa_socket_client_unref(c->client);
-    if (c->pdispatch)
+    if (c->pdispatch) {
         pa_pdispatch_unref(c->pdispatch);
+        c->pdispatch = NULL;
+    }
+
     if (c->pstream) {
         pa_pstream_unlink(c->pstream);
         pa_pstream_unref(c->pstream);
+        c->pstream = NULL;
     }
 
+    if (c->client) {
+        pa_socket_client_unref(c->client);
+        c->client = NULL;
+    }
+
+    reset_callbacks(c);
+}
+
+static void context_free(pa_context *c) {
+    pa_assert(c);
+
+    context_unlink(c);
+
+    unlock_autospawn_lock_file(c);
+
     if (c->record_streams)
         pa_dynarray_free(c->record_streams, NULL, NULL);
     if (c->playback_streams)
@@ -204,7 +250,9 @@ static void context_free(pa_context *c) {
 
     pa_strlist_free(c->server_list);
 
-    pa_xfree(c->name);
+    if (c->proplist)
+        pa_proplist_free(c->proplist);
+
     pa_xfree(c->server);
     pa_xfree(c);
 }
@@ -235,46 +283,16 @@ void pa_context_set_state(pa_context *c, pa_context_state_t st) {
     pa_context_ref(c);
 
     c->state = st;
+
     if (c->state_callback)
         c->state_callback(c, c->state_userdata);
 
-    if (st == PA_CONTEXT_FAILED || st == PA_CONTEXT_TERMINATED) {
-        pa_stream *s;
-
-        s = c->streams ? pa_stream_ref(c->streams) : NULL;
-        while (s) {
-            pa_stream *n = s->next ? pa_stream_ref(s->next) : NULL;
-            pa_stream_set_state(s, st == PA_CONTEXT_FAILED ? PA_STREAM_FAILED : PA_STREAM_TERMINATED);
-            pa_stream_unref(s);
-            s = n;
-        }
-
-        if (c->pdispatch)
-            pa_pdispatch_unref(c->pdispatch);
-        c->pdispatch = NULL;
-
-        if (c->pstream) {
-            pa_pstream_unlink(c->pstream);
-            pa_pstream_unref(c->pstream);
-        }
-        c->pstream = NULL;
-
-        if (c->client)
-            pa_socket_client_unref(c->client);
-        c->client = NULL;
-    }
+    if (st == PA_CONTEXT_FAILED || st == PA_CONTEXT_TERMINATED)
+        context_unlink(c);
 
     pa_context_unref(c);
 }
 
-void pa_context_fail(pa_context *c, int error) {
-    pa_assert(c);
-    pa_assert(PA_REFCNT_VALUE(c) >= 1);
-
-    pa_context_set_error(c, error);
-    pa_context_set_state(c, PA_CONTEXT_FAILED);
-}
-
 int pa_context_set_error(pa_context *c, int error) {
     pa_assert(error >= 0);
     pa_assert(error < PA_ERR_MAX);
@@ -285,6 +303,14 @@ int pa_context_set_error(pa_context *c, int error) {
     return error;
 }
 
+void pa_context_fail(pa_context *c, int error) {
+    pa_assert(c);
+    pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+    pa_context_set_error(c, error);
+    pa_context_set_state(c, PA_CONTEXT_FAILED);
+}
+
 static void pstream_die_callback(pa_pstream *p, void *userdata) {
     pa_context *c = userdata;
 
@@ -341,25 +367,41 @@ static void pstream_memblock_callback(pa_pstream *p, uint32_t channel, int64_t o
     pa_context_unref(c);
 }
 
-int pa_context_handle_error(pa_context *c, uint32_t command, pa_tagstruct *t) {
+int pa_context_handle_error(pa_context *c, uint32_t command, pa_tagstruct *t, pa_bool_t fail) {
+    uint32_t err;
     pa_assert(c);
     pa_assert(PA_REFCNT_VALUE(c) >= 1);
 
     if (command == PA_COMMAND_ERROR) {
         pa_assert(t);
 
-        if (pa_tagstruct_getu32(t, &c->error) < 0) {
+        if (pa_tagstruct_getu32(t, &err) < 0) {
             pa_context_fail(c, PA_ERR_PROTOCOL);
             return -1;
-
         }
+
     } else if (command == PA_COMMAND_TIMEOUT)
-        c->error = PA_ERR_TIMEOUT;
+        err = PA_ERR_TIMEOUT;
     else {
         pa_context_fail(c, PA_ERR_PROTOCOL);
         return -1;
     }
 
+    if (err == PA_OK) {
+        pa_context_fail(c, PA_ERR_PROTOCOL);
+        return -1;
+    }
+
+    if (err >= PA_ERR_MAX)
+        err = PA_ERR_UNKNOWN;
+
+    if (fail) {
+        pa_context_fail(c, err);
+        return -1;
+    }
+
+    pa_context_set_error(c, err);
+
     return 0;
 }
 
@@ -373,11 +415,7 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t
     pa_context_ref(c);
 
     if (command != PA_COMMAND_REPLY) {
-
-        if (pa_context_handle_error(c, command, t) < 0)
-            pa_context_fail(c, PA_ERR_PROTOCOL);
-
-        pa_context_fail(c, c->error);
+        pa_context_handle_error(c, command, t, TRUE);
         goto finish;
     }
 
@@ -400,7 +438,7 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t
             /* Enable shared memory support if possible */
             if (c->version >= 10 &&
                 pa_mempool_is_shared(c->mempool) &&
-                c->is_local > 0) {
+                c->is_local) {
 
                 /* Only enable SHM if both sides are owned by the same
                  * user. This is a security measure because otherwise
@@ -410,12 +448,18 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t
                 const pa_creds *creds;
                 if ((creds = pa_pdispatch_creds(pd)))
                     if (getuid() == creds->uid)
-                        pa_pstream_use_shm(c->pstream, 1);
+                        pa_pstream_enable_shm(c->pstream, TRUE);
 #endif
             }
 
             reply = pa_tagstruct_command(c, PA_COMMAND_SET_CLIENT_NAME, &tag);
-            pa_tagstruct_puts(reply, c->name);
+
+            if (c->version >= 13) {
+                pa_init_proplist(c->proplist);
+                pa_tagstruct_put_proplist(reply, c->proplist);
+            } else
+                pa_tagstruct_puts(reply, pa_proplist_gets(c->proplist, PA_PROP_APPLICATION_NAME));
+
             pa_pstream_send_tagstruct(c->pstream, reply);
             pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, setup_complete_callback, c, NULL);
 
@@ -424,11 +468,19 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t
         }
 
         case PA_CONTEXT_SETTING_NAME :
+
+            if ((c->version >= 13 && (pa_tagstruct_getu32(t, &c->client_index) < 0 ||
+                                      c->client_index == PA_INVALID_INDEX)) ||
+                !pa_tagstruct_eof(t)) {
+                pa_context_fail(c, PA_ERR_PROTOCOL);
+                goto finish;
+            }
+
             pa_context_set_state(c, PA_CONTEXT_READY);
             break;
 
         default:
-            pa_assert(0);
+            pa_assert_not_reached();
     }
 
 finish:
@@ -455,7 +507,7 @@ static void setup_context(pa_context *c, pa_iochannel *io) {
     c->pdispatch = pa_pdispatch_new(c->mainloop, command_table, PA_COMMAND_MAX);
 
     if (!c->conf->cookie_valid)
-        pa_log_warn("No cookie loaded. Attempting to connect without.");
+        pa_log_info("No cookie loaded. Attempting to connect without.");
 
     t = pa_tagstruct_command(c, PA_COMMAND_AUTH, &tag);
     pa_tagstruct_putu32(t, PA_PROTOCOL_VERSION);
@@ -494,10 +546,13 @@ static int context_connect_spawn(pa_context *c) {
     int fds[2] = { -1, -1} ;
     pa_iochannel *io;
 
+    if (getuid() == 0)
+        return -1;
+
     pa_context_ref(c);
 
     if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0) {
-        pa_log("socketpair(): %s", pa_cstrerror(errno));
+        pa_log_error("socketpair(): %s", pa_cstrerror(errno));
         pa_context_fail(c, PA_ERR_INTERNAL);
         goto fail;
     }
@@ -511,7 +566,7 @@ static int context_connect_spawn(pa_context *c) {
         c->spawn_api.prefork();
 
     if ((pid = fork()) < 0) {
-        pa_log("fork(): %s", pa_cstrerror(errno));
+        pa_log_error("fork(): %s", pa_cstrerror(errno));
         pa_context_fail(c, PA_ERR_INTERNAL);
 
         if (c->spawn_api.postfork)
@@ -526,9 +581,13 @@ static int context_connect_spawn(pa_context *c) {
 #define MAX_ARGS 64
         const char * argv[MAX_ARGS+1];
         int n;
+        char *f;
+
+        pa_close_all(fds[1], -1);
 
-        /* Not required, since fds[0] has CLOEXEC enabled anyway */
-        pa_assert_se(pa_close(fds[0]) == 0);
+        f = pa_sprintf_malloc("%i", fds[1]);
+        pa_set_env("PULSE_PASSED_FD", f);
+        pa_xfree(f);
 
         if (c->spawn_api.atfork)
             c->spawn_api.atfork();
@@ -561,6 +620,8 @@ static int context_connect_spawn(pa_context *c) {
 
     /* Parent */
 
+    pa_assert_se(pa_close(fds[1]) == 0);
+
     r = waitpid(pid, &status, 0);
 
     if (c->spawn_api.postfork)
@@ -575,14 +636,12 @@ static int context_connect_spawn(pa_context *c) {
         goto fail;
     }
 
-    pa_assert_se(pa_close(fds[1]) == 0);
+    c->is_local = TRUE;
 
-    c->is_local = 1;
+    unlock_autospawn_lock_file(c);
 
     io = pa_iochannel_new(c->mainloop, fds[0], fds[0]);
-
     setup_context(c, io);
-    unlock_autospawn_lock_file(c);
 
     pa_context_unref(c);
 
@@ -634,7 +693,7 @@ static int try_next_connection(pa_context *c) {
         if (!(c->client = pa_socket_client_new_string(c->mainloop, u, PA_NATIVE_DEFAULT_PORT)))
             continue;
 
-        c->is_local = pa_socket_client_is_local(c->client);
+        c->is_local = !!pa_socket_client_is_local(c->client);
         pa_socket_client_set_callback(c->client, on_connection, c);
         break;
     }
@@ -649,6 +708,7 @@ finish:
 
 static void on_connection(pa_socket_client *client, pa_iochannel*io, void *userdata) {
     pa_context *c = userdata;
+    int saved_errno = errno;
 
     pa_assert(client);
     pa_assert(c);
@@ -661,7 +721,9 @@ static void on_connection(pa_socket_client *client, pa_iochannel*io, void *userd
 
     if (!io) {
         /* Try the item in the list */
-        if (errno == ECONNREFUSED || errno == ETIMEDOUT || errno == EHOSTUNREACH) {
+        if (saved_errno == ECONNREFUSED ||
+            saved_errno == ETIMEDOUT ||
+            saved_errno == EHOSTUNREACH) {
             try_next_connection(c);
             goto finish;
         }
@@ -677,6 +739,25 @@ finish:
     pa_context_unref(c);
 }
 
+
+static char *get_legacy_runtime_dir(void) {
+    char *p, u[128];
+    struct stat st;
+
+    if (!pa_get_user_name(u, sizeof(u)))
+        return NULL;
+
+    p = pa_sprintf_malloc("/tmp/pulse-%s", u);
+
+    if (stat(p, &st) < 0)
+        return NULL;
+
+    if (st.st_uid != getuid())
+        return NULL;
+
+    return p;
+}
+
 int pa_context_connect(
         pa_context *c,
         const char *server,
@@ -705,8 +786,8 @@ int pa_context_connect(
             goto finish;
         }
     } else {
-        char *d;
-        char ufn[PATH_MAX];
+        char *d, *ufn;
+        static char *legacy_dir;
 
         /* Prepend in reverse order */
 
@@ -726,25 +807,34 @@ int pa_context_connect(
         c->server_list = pa_strlist_prepend(c->server_list, "tcp4:localhost");
 
         /* The system wide instance */
-        c->server_list = pa_strlist_prepend(c->server_list, PA_SYSTEM_RUNTIME_PATH "/" PA_NATIVE_DEFAULT_UNIX_SOCKET);
+        c->server_list = pa_strlist_prepend(c->server_list, PA_SYSTEM_RUNTIME_PATH PA_PATH_SEP PA_NATIVE_DEFAULT_UNIX_SOCKET);
+
+        /* The old per-user instance path. This is supported only to easy upgrades */
+        if ((legacy_dir = get_legacy_runtime_dir())) {
+            char *p = pa_sprintf_malloc("%s" PA_PATH_SEP PA_NATIVE_DEFAULT_UNIX_SOCKET, legacy_dir);
+            c->server_list = pa_strlist_prepend(c->server_list, p);
+            pa_xfree(p);
+            pa_xfree(legacy_dir);
+        }
 
         /* The per-user instance */
-        c->server_list = pa_strlist_prepend(c->server_list, pa_runtime_path(PA_NATIVE_DEFAULT_UNIX_SOCKET, ufn, sizeof(ufn)));
+        c->server_list = pa_strlist_prepend(c->server_list, ufn = pa_runtime_path(PA_NATIVE_DEFAULT_UNIX_SOCKET));
+        pa_xfree(ufn);
 
         /* Wrap the connection attempts in a single transaction for sane autospawn locking */
         if (!(flags & PA_CONTEXT_NOAUTOSPAWN) && c->conf->autospawn) {
-            char lf[PATH_MAX];
+            char *lf;
 
-            pa_runtime_path(AUTOSPAWN_LOCK, lf, sizeof(lf));
-            pa_make_secure_parent_dir(lf, 0700, (uid_t)-1, (gid_t)-1);
+            lf = pa_runtime_path(AUTOSPAWN_LOCK);
             pa_assert(c->autospawn_lock_fd <= 0);
             c->autospawn_lock_fd = pa_lock_lockfile(lf);
+            pa_xfree(lf);
 
             if (api)
                 c->spawn_api = *api;
-            c->do_autospawn = 1;
-        }
 
+            c->do_autospawn = TRUE;
+        }
     }
 
     pa_context_set_state(c, PA_CONTEXT_CONNECTING);
@@ -760,7 +850,8 @@ void pa_context_disconnect(pa_context *c) {
     pa_assert(c);
     pa_assert(PA_REFCNT_VALUE(c) >= 1);
 
-    pa_context_set_state(c, PA_CONTEXT_TERMINATED);
+    if (PA_CONTEXT_IS_GOOD(c->state))
+        pa_context_set_state(c, PA_CONTEXT_TERMINATED);
 }
 
 pa_context_state_t pa_context_get_state(pa_context *c) {
@@ -781,6 +872,9 @@ void pa_context_set_state_callback(pa_context *c, pa_context_notify_cb_t cb, voi
     pa_assert(c);
     pa_assert(PA_REFCNT_VALUE(c) >= 1);
 
+    if (c->state == PA_CONTEXT_TERMINATED || c->state == PA_CONTEXT_FAILED)
+        return;
+
     c->state_callback = cb;
     c->state_userdata = userdata;
 }
@@ -789,11 +883,7 @@ int pa_context_is_pending(pa_context *c) {
     pa_assert(c);
     pa_assert(PA_REFCNT_VALUE(c) >= 1);
 
-    PA_CHECK_VALIDITY(c,
-                      c->state == PA_CONTEXT_CONNECTING ||
-                      c->state == PA_CONTEXT_AUTHORIZING ||
-                      c->state == PA_CONTEXT_SETTING_NAME ||
-                      c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+    PA_CHECK_VALIDITY(c, PA_CONTEXT_IS_GOOD(c->state), PA_ERR_BADSTATE);
 
     return (c->pstream && pa_pstream_is_pending(c->pstream)) ||
         (c->pdispatch && pa_pdispatch_is_pending(c->pdispatch)) ||
@@ -870,7 +960,7 @@ void pa_context_simple_ack_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_U
         goto finish;
 
     if (command != PA_COMMAND_REPLY) {
-        if (pa_context_handle_error(o->context, command, t) < 0)
+        if (pa_context_handle_error(o->context, command, t, FALSE) < 0)
             goto finish;
 
         success = 0;
@@ -889,7 +979,7 @@ finish:
     pa_operation_unref(o);
 }
 
-pa_operation* pa_context_exit_daemon(pa_context *c, pa_context_success_cb_t cb, void *userdata) {
+pa_operation* pa_context_send_simple_command(pa_context *c, uint32_t command, pa_pdispatch_cb_t internal_cb, pa_operation_cb_t cb, void *userdata) {
     pa_tagstruct *t;
     pa_operation *o;
     uint32_t tag;
@@ -899,32 +989,20 @@ pa_operation* pa_context_exit_daemon(pa_context *c, pa_context_success_cb_t cb,
 
     PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
 
-    o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+    o = pa_operation_new(c, NULL, cb, userdata);
 
-    t = pa_tagstruct_command(c, PA_COMMAND_EXIT, &tag);
+    t = pa_tagstruct_command(c, command, &tag);
     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);
+    pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, internal_cb, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
 
     return o;
 }
 
-pa_operation* pa_context_send_simple_command(pa_context *c, uint32_t command, pa_pdispatch_cb_t internal_cb, pa_operation_cb_t cb, void *userdata) {
-    pa_tagstruct *t;
-    pa_operation *o;
-    uint32_t tag;
-
+pa_operation* pa_context_exit_daemon(pa_context *c, pa_context_success_cb_t cb, void *userdata) {
     pa_assert(c);
     pa_assert(PA_REFCNT_VALUE(c) >= 1);
 
-    PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
-
-    o = pa_operation_new(c, NULL, cb, userdata);
-
-    t = pa_tagstruct_command(c, command, &tag);
-    pa_pstream_send_tagstruct(c->pstream, t);
-    pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, internal_cb, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
-
-    return o;
+    return pa_context_send_simple_command(c, PA_COMMAND_EXIT, pa_context_simple_ack_callback, (pa_operation_cb_t) cb, userdata);
 }
 
 pa_operation* pa_context_set_default_sink(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata) {
@@ -938,7 +1016,6 @@ pa_operation* pa_context_set_default_sink(pa_context *c, const char *name, pa_co
     PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
 
     o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
-
     t = pa_tagstruct_command(c, PA_COMMAND_SET_DEFAULT_SINK, &tag);
     pa_tagstruct_puts(t, name);
     pa_pstream_send_tagstruct(c->pstream, t);
@@ -958,7 +1035,6 @@ pa_operation* pa_context_set_default_source(pa_context *c, const char *name, pa_
     PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
 
     o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
-
     t = pa_tagstruct_command(c, PA_COMMAND_SET_DEFAULT_SOURCE, &tag);
     pa_tagstruct_puts(t, name);
     pa_pstream_send_tagstruct(c->pstream, t);
@@ -971,15 +1047,13 @@ int pa_context_is_local(pa_context *c) {
     pa_assert(c);
     pa_assert(PA_REFCNT_VALUE(c) >= 1);
 
-    PA_CHECK_VALIDITY(c, c->is_local >= 0, PA_ERR_BADSTATE);
+    PA_CHECK_VALIDITY_RETURN_ANY(c, PA_CONTEXT_IS_GOOD(c->state), PA_ERR_BADSTATE, -1);
 
-    return c->is_local;
+    return !!c->is_local;
 }
 
 pa_operation* pa_context_set_name(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata) {
-    pa_tagstruct *t;
     pa_operation *o;
-    uint32_t tag;
 
     pa_assert(c);
     pa_assert(PA_REFCNT_VALUE(c) >= 1);
@@ -987,12 +1061,22 @@ pa_operation* pa_context_set_name(pa_context *c, const char *name, pa_context_su
 
     PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
 
-    o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+    if (c->version >= 13) {
+        pa_proplist *p = pa_proplist_new();
 
-    t = pa_tagstruct_command(c, PA_COMMAND_SET_CLIENT_NAME, &tag);
-    pa_tagstruct_puts(t, name);
-    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);
+        pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, name);
+        o = pa_context_proplist_update(c, PA_UPDATE_REPLACE, p, cb, userdata);
+        pa_proplist_free(p);
+    } else {
+        pa_tagstruct *t;
+        uint32_t tag;
+
+        o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+        t = pa_tagstruct_command(c, PA_COMMAND_SET_CLIENT_NAME, &tag);
+        pa_tagstruct_puts(t, name);
+        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;
 }
@@ -1024,6 +1108,8 @@ uint32_t pa_context_get_server_protocol_version(pa_context *c) {
     pa_assert(c);
     pa_assert(PA_REFCNT_VALUE(c) >= 1);
 
+    PA_CHECK_VALIDITY_RETURN_ANY(c, PA_CONTEXT_IS_GOOD(c->state), PA_ERR_BADSTATE, PA_INVALID_INDEX);
+
     return c->version;
 }
 
@@ -1039,3 +1125,153 @@ pa_tagstruct *pa_tagstruct_command(pa_context *c, uint32_t command, uint32_t *ta
 
     return t;
 }
+
+uint32_t pa_context_get_index(pa_context *c) {
+    pa_assert(c);
+    pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+    PA_CHECK_VALIDITY_RETURN_ANY(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE, PA_INVALID_INDEX);
+    PA_CHECK_VALIDITY_RETURN_ANY(c, c->version >= 13, PA_ERR_NOTSUPPORTED, PA_INVALID_INDEX);
+
+    return c->client_index;
+}
+
+pa_operation *pa_context_proplist_update(pa_context *c, pa_update_mode_t mode, pa_proplist *p, 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, mode == PA_UPDATE_SET || mode == PA_UPDATE_MERGE || mode == PA_UPDATE_REPLACE, PA_ERR_INVALID);
+    PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+    PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 13, PA_ERR_NOTSUPPORTED);
+
+    o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+    t = pa_tagstruct_command(c, PA_COMMAND_UPDATE_CLIENT_PROPLIST, &tag);
+    pa_tagstruct_putu32(t, (uint32_t) mode);
+    pa_tagstruct_put_proplist(t, p);
+
+    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);
+
+    /* Please note that we don't update c->proplist here, because we
+     * don't export that field */
+
+    return o;
+}
+
+pa_operation *pa_context_proplist_remove(pa_context *c, const char *const keys[], pa_context_success_cb_t cb, void *userdata) {
+    pa_operation *o;
+    pa_tagstruct *t;
+    uint32_t tag;
+    const char * const *k;
+
+    pa_assert(c);
+    pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+    PA_CHECK_VALIDITY_RETURN_NULL(c, keys && keys[0], PA_ERR_INVALID);
+    PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+    PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 13, PA_ERR_NOTSUPPORTED);
+
+    o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+    t = pa_tagstruct_command(c, PA_COMMAND_REMOVE_CLIENT_PROPLIST, &tag);
+
+    for (k = keys; *k; k++)
+        pa_tagstruct_puts(t, *k);
+
+    pa_tagstruct_puts(t, NULL);
+
+    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);
+
+    /* Please note that we don't update c->proplist here, because we
+     * don't export that field */
+
+    return o;
+}
+
+void pa_init_proplist(pa_proplist *p) {
+    int a, b;
+#ifndef HAVE_DECL_ENVIRON
+    extern char **environ;
+#endif
+    char **e;
+
+    pa_assert(p);
+
+    for (e = environ; *e; e++) {
+
+        if (pa_startswith(*e, "PULSE_PROP_")) {
+            size_t kl = strcspn(*e+11, "=");
+            char *k;
+
+            if ((*e)[11+kl] != '=')
+                continue;
+
+            if (!pa_utf8_valid(*e+11+kl+1))
+                continue;
+
+            k = pa_xstrndup(*e+11, kl);
+
+            if (pa_proplist_contains(p, k)) {
+                pa_xfree(k);
+                continue;
+            }
+
+            pa_proplist_sets(p, k, *e+11+kl+1);
+            pa_xfree(k);
+        }
+    }
+
+    if (!pa_proplist_contains(p, PA_PROP_APPLICATION_PROCESS_ID)) {
+        char t[32];
+        pa_snprintf(t, sizeof(t), "%lu", (unsigned long) getpid());
+        pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_ID, t);
+    }
+
+    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);
+        }
+    }
+
+    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);
+        }
+    }
+
+    a = pa_proplist_contains(p, PA_PROP_APPLICATION_PROCESS_BINARY);
+    b = pa_proplist_contains(p, PA_PROP_APPLICATION_NAME);
+
+    if (!a || !b) {
+        char t[PATH_MAX];
+        if (pa_get_binary_name(t, sizeof(t))) {
+            char *c = pa_utf8_filter(t);
+
+            if (!a)
+                pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_BINARY, c);
+            if (!b)
+                pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, c);
+
+            pa_xfree(c);
+        }
+    }
+
+    if (!pa_proplist_contains(p, PA_PROP_APPLICATION_LANGUAGE)) {
+        const char *l;
+
+        if ((l = setlocale(LC_MESSAGES, NULL)))
+            pa_proplist_sets(p, PA_PROP_APPLICATION_LANGUAGE, l);
+    }
+}
index 1de3aba..143508f 100644 (file)
@@ -30,6 +30,7 @@
 #include <pulse/mainloop-api.h>
 #include <pulse/cdecl.h>
 #include <pulse/operation.h>
+#include <pulse/proplist.h>
 
 /** \page async Asynchronous API
  *
@@ -166,9 +167,15 @@ typedef void (*pa_context_notify_cb_t)(pa_context *c, void *userdata);
 typedef void (*pa_context_success_cb_t) (pa_context *c, int success, void *userdata);
 
 /** Instantiate a new connection context with an abstract mainloop API
- * and an application name */
+ * and an application name. It is recommended to use pa_context_new_with_proplist()
+ * instead and specify some initial properties.*/
 pa_context *pa_context_new(pa_mainloop_api *mainloop, const char *name);
 
+/** Instantiate a new connection context with an abstract mainloop API
+ * and an application name, and specify the the initial client property
+ * list. \since 0.9.11 */
+pa_context *pa_context_new_with_proplist(pa_mainloop_api *mainloop, const char *name, pa_proplist *proplist);
+
 /** Decrease the reference counter of the context by one */
 void pa_context_unref(pa_context *c);
 
@@ -207,27 +214,42 @@ pa_operation* pa_context_drain(pa_context *c, pa_context_notify_cb_t cb, void *u
  * returning a success notification */
 pa_operation* pa_context_exit_daemon(pa_context *c, pa_context_success_cb_t cb, void *userdata);
 
-/** Set the name of the default sink. \since 0.4 */
+/** Set the name of the default sink. */
 pa_operation* pa_context_set_default_sink(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata);
 
-/** Set the name of the default source. \since 0.4 */
+/** Set the name of the default source. */
 pa_operation* pa_context_set_default_source(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata);
 
-/** Returns 1 when the connection is to a local daemon. Returns negative when no connection has been made yet. \since 0.5 */
+/** Returns 1 when the connection is to a local daemon. Returns negative when no connection has been made yet. */
 int pa_context_is_local(pa_context *c);
 
-/** Set a different application name for context on the server. \since 0.5 */
+/** Set a different application name for context on the server. */
 pa_operation* pa_context_set_name(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata);
 
-/** Return the server name this context is connected to. \since 0.7 */
+/** Return the server name this context is connected to. */
 const char* pa_context_get_server(pa_context *c);
 
-/** Return the protocol version of the library. \since 0.8 */
+/** Return the protocol version of the library. */
 uint32_t pa_context_get_protocol_version(pa_context *c);
 
-/** Return the protocol version of the connected server. \since 0.8 */
+/** Return the protocol version of the connected server. */
 uint32_t pa_context_get_server_protocol_version(pa_context *c);
 
+/* Update the property list of the client, adding new entries. Please
+ * note that it is highly recommended to set as much properties
+ * initially via pa_context_new_with_proplist() as possible instead a
+ * posteriori with this function, since that information may then be
+ * used to route streams of the client to the right device. \since 0.9.11 */
+pa_operation *pa_context_proplist_update(pa_context *c, pa_update_mode_t mode, pa_proplist *p, pa_context_success_cb_t cb, void *userdata);
+
+/* Update the property list of the client, remove entries. \since 0.9.11 */
+pa_operation *pa_context_proplist_remove(pa_context *c, const char *const keys[], pa_context_success_cb_t cb, void *userdata);
+
+/** Return the client index this context is
+ * identified in the server with. This is useful for usage with the
+ * introspection functions, such as pa_context_get_client_info(). \since 0.9.11 */
+uint32_t pa_context_get_index(pa_context *s);
+
 PA_C_DECL_END
 
 #endif
index dabbc5e..1072232 100644 (file)
@@ -48,6 +48,15 @@ typedef enum pa_context_state {
     PA_CONTEXT_TERMINATED      /**< The connection was terminated cleanly */
 } pa_context_state_t;
 
+/** Return non-zero if the passed state is one of the connected states */
+static inline int PA_CONTEXT_IS_GOOD(pa_context_state_t x) {
+    return
+        x == PA_CONTEXT_CONNECTING ||
+        x == PA_CONTEXT_AUTHORIZING ||
+        x == PA_CONTEXT_SETTING_NAME ||
+        x == PA_CONTEXT_READY;
+}
+
 /** The state of a stream */
 typedef enum pa_stream_state {
     PA_STREAM_UNCONNECTED, /**< The stream is not yet connected to any sink or source */
@@ -57,6 +66,13 @@ typedef enum pa_stream_state {
     PA_STREAM_TERMINATED    /**< The stream has been terminated cleanly */
 } pa_stream_state_t;
 
+/** Return non-zero if the passed state is one of the connected states */
+static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x) {
+    return
+        x == PA_STREAM_CREATING ||
+        x == PA_STREAM_READY;
+}
+
 /** The state of an operation */
 typedef enum pa_operation_state {
     PA_OPERATION_RUNNING,      /**< The operation is still running */
@@ -67,7 +83,7 @@ typedef enum pa_operation_state {
 /** An invalid index */
 #define PA_INVALID_INDEX ((uint32_t) -1)
 
-/** Some special flags for contexts. \since 0.8 */
+/** Some special flags for contexts. */
 typedef enum pa_context_flags {
     PA_CONTEXT_NOAUTOSPAWN = 1 /**< Disabled autospawning of the PulseAudio daemon if required */
 } pa_context_flags_t;
@@ -80,7 +96,7 @@ typedef enum pa_stream_direction {
     PA_STREAM_UPLOAD         /**< Sample upload stream */
 } pa_stream_direction_t;
 
-/** Some special flags for stream connections. \since 0.6 */
+/** Some special flags for stream connections. */
 typedef enum pa_stream_flags {
     PA_STREAM_START_CORKED = 1,       /**< Create the stream corked, requiring an explicit pa_stream_cork() call to uncork it. */
     PA_STREAM_INTERPOLATE_TIMING = 2, /**< Interpolate the latency for
@@ -209,15 +225,73 @@ typedef enum pa_stream_flags {
                                      * least PA 0.9.8. It is ignored
                                      * on older servers. \since
                                      * 0.9.8 */
+    PA_STREAM_PEAK_DETECT = 2048, /**< Find peaks instead of
+                                   * resampling. \since 0.9.11 */
+
+    PA_STREAM_START_MUTED = 4096,  /**< Create in muted state. \since 0.9.11 */
+
+
+    PA_STREAM_ADJUST_LATENCY = 8192, /**< Try to adjust the latency of
+                                      * the sink/source based on the
+                                      * requested buffer metrics and
+                                      * adjust buffer metrics
+                                      * accordingly. \since 0.9.11 */
 } pa_stream_flags_t;
 
+
+/** English is an evil language \since 0.9.11 */
+#define PA_STREAM_NOT_MONOTONIC PA_STREAM_NOT_MONOTONOUS
+
 /** Playback and record buffer metrics */
 typedef struct pa_buffer_attr {
-    uint32_t maxlength;      /**< Maximum length of the buffer */
-    uint32_t tlength;        /**< Playback only: target length of the buffer. The server tries to assure that at least tlength bytes are always available in the buffer */
-    uint32_t prebuf;         /**< Playback only: pre-buffering. The server does not start with playback before at least prebug bytes are available in the buffer */
-    uint32_t minreq;         /**< Playback only: minimum request. The server does not request less than minreq bytes from the client, instead waints until the buffer is free enough to request more bytes at once */
-    uint32_t fragsize;       /**< Recording only: fragment size. The server sends data in blocks of fragsize bytes size. Large values deminish interactivity with other operations on the connection context but decrease control overhead. */
+    uint32_t maxlength;      /**< Maximum length of the
+                              * buffer. Setting this to 0 will
+                              * initialize this to the maximum value
+                              * supported by server, which is
+                              * recommended. */
+    uint32_t tlength;        /**< Playback only: target length of the
+                              * buffer. The server tries to assure
+                              * that at least tlength bytes are always
+                              * available in the buffer. It is
+                              * recommended to set this to 0, which
+                              * will initialize this to a value that
+                              * is deemed sensible by the
+                              * server. However, this value will
+                              * default to something like 2s, i.e. for
+                              * applications that have specific
+                              * latency requirements this value should
+                              * be set to the maximum latency that the
+                              * application can deal with.  */
+    uint32_t prebuf;         /**< Playback only: pre-buffering. The
+                              * server does not start with playback
+                              * before at least prebug bytes are
+                              * available in the buffer. It is
+                              * recommended to set this to 0, which
+                              * will initialize this to the same value
+                              * as tlength, whatever that may be. */
+    uint32_t minreq;         /**< Playback only: minimum request. The
+                              * server does not request less than
+                              * minreq bytes from the client, instead
+                              * waits until the buffer is free enough
+                              * to request more bytes at once. It is
+                              * recommended to set this to 0, which
+                              * will initialize this to a value that
+                              * is deemed sensible by the server. */
+    uint32_t fragsize;       /**< Recording only: fragment size. The
+                              * server sends data in blocks of
+                              * fragsize bytes size. Large values
+                              * deminish interactivity with other
+                              * operations on the connection context
+                              * but decrease control overhead. It is
+                              * recommended to set this to 0, which
+                              * will initialize this to a value that
+                              * is deemed sensible by the
+                              * server. However, this value will
+                              * default to something like 2s, i.e. for
+                              * applications that have specific
+                              * latency requirements this value should
+                              * be set to the maximum latency that the
+                              * application can deal with. */
 } pa_buffer_attr;
 
 /** Error values as used by pa_context_errno(). Use pa_strerror() to convert these values to human readable strings */
@@ -239,9 +313,10 @@ enum {
     PA_ERR_MODINITFAILED,          /**< Module initialization failed */
     PA_ERR_BADSTATE,               /**< Bad state */
     PA_ERR_NODATA,                 /**< No data */
-    PA_ERR_VERSION,                /**< Incompatible protocol version \since 0.8 */
-    PA_ERR_TOOLARGE,               /**< Data too large \since 0.8.1 */
+    PA_ERR_VERSION,                /**< Incompatible protocol version */
+    PA_ERR_TOOLARGE,               /**< Data too large */
     PA_ERR_NOTSUPPORTED,           /**< Operation not supported \since 0.9.5 */
+    PA_ERR_UNKNOWN,                /**< The error code was unknown to the client */
     PA_ERR_MAX                     /**< Not really an error but the first invalid error code */
 };
 
@@ -255,9 +330,9 @@ typedef enum pa_subscription_mask {
     PA_SUBSCRIPTION_MASK_MODULE = 16,            /**< Module events */
     PA_SUBSCRIPTION_MASK_CLIENT = 32,            /**< Client events */
     PA_SUBSCRIPTION_MASK_SAMPLE_CACHE = 64,      /**< Sample cache events */
-    PA_SUBSCRIPTION_MASK_SERVER = 128,           /**< Other global server changes. \since 0.4 */
-    PA_SUBSCRIPTION_MASK_AUTOLOAD = 256,         /**< Autoload table events. \since 0.5 */
-    PA_SUBSCRIPTION_MASK_ALL = 511               /**< Catch all events \since 0.8 */
+    PA_SUBSCRIPTION_MASK_SERVER = 128,           /**< Other global server changes. */
+    PA_SUBSCRIPTION_MASK_AUTOLOAD = 256,         /**< Autoload table events. */
+    PA_SUBSCRIPTION_MASK_ALL = 511               /**< Catch all events */
 } pa_subscription_mask_t;
 
 /** Subscription event types, as used by pa_context_subscribe() */
@@ -269,8 +344,8 @@ typedef enum pa_subscription_event_type {
     PA_SUBSCRIPTION_EVENT_MODULE = 4,         /**< Event type: Module */
     PA_SUBSCRIPTION_EVENT_CLIENT = 5,         /**< Event type: Client */
     PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE = 6,   /**< Event type: Sample cache item */
-    PA_SUBSCRIPTION_EVENT_SERVER = 7,         /**< Event type: Global server change, only occuring with PA_SUBSCRIPTION_EVENT_CHANGE. \since 0.4  */
-    PA_SUBSCRIPTION_EVENT_AUTOLOAD = 8,       /**< Event type: Autoload table changes. \since 0.5 */
+    PA_SUBSCRIPTION_EVENT_SERVER = 7,         /**< Event type: Global server change, only occuring with PA_SUBSCRIPTION_EVENT_CHANGE. */
+    PA_SUBSCRIPTION_EVENT_AUTOLOAD = 8,       /**< Event type: Autoload table changes. */
     PA_SUBSCRIPTION_EVENT_FACILITY_MASK = 15, /**< A mask to extract the event type from an event value */
 
     PA_SUBSCRIPTION_EVENT_NEW = 0,            /**< A new object was created */
@@ -297,7 +372,9 @@ typedef enum pa_subscription_event_type {
  * source_usec+buffer_usec+transport_usec-sink_usec. (Take care of
  * sign issues!) When connected to a monitor source sink_usec contains
  * the latency of the owning sink. The two latency estimations
- * described here are implemented in pa_stream_get_latency().*/
+ * described here are implemented in pa_stream_get_latency(). Please
+ * note that this structure can be extended as part of evolutionary
+ * API updates at any time in any new release.*/
 typedef struct pa_timing_info {
     struct timeval timestamp; /**< The time when this timing info structure was current */
     int synchronized_clocks;  /**< Non-zero if the local and the
@@ -306,14 +383,21 @@ typedef struct pa_timing_info {
                                * detected transport_usec becomes much
                                * more reliable. However, the code that
                                * detects synchronized clocks is very
-                               * limited und unreliable itself. \since
-                               * 0.5 */
+                               * limited und unreliable itself. */
 
     pa_usec_t sink_usec;      /**< Time in usecs a sample takes to be played on the sink. For playback streams and record streams connected to a monitor source. */
-    pa_usec_t source_usec;    /**< Time in usecs a sample takes from being recorded to being delivered to the application. Only for record streams. \since 0.5*/
-    pa_usec_t transport_usec; /**< Estimated time in usecs a sample takes to be transferred to/from the daemon. For both playback and record streams. \since 0.5 */
-
-    int playing;              /**< Non-zero when the stream is currently playing. Only for playback streams. */
+    pa_usec_t source_usec;    /**< Time in usecs a sample takes from being recorded to being delivered to the application. Only for record streams. */
+    pa_usec_t transport_usec; /**< Estimated time in usecs a sample takes to be transferred to/from the daemon. For both playback and record streams. */
+
+    int playing;              /**< Non-zero when the stream is
+                               * currently not underrun and data is
+                               * being passed on to the device. Only
+                               * for playback streams. This field does
+                               * not say whether the data is actually
+                               * already being played. To determine
+                               * this check whether since_underrun
+                               * (converted to usec) is larger than
+                               * sink_usec.*/
 
     int write_index_corrupt;  /**< Non-zero if write_index is not
                                * up-to-date because a local write
@@ -322,20 +406,19 @@ typedef struct pa_timing_info {
                                * info was current . Only write
                                * commands with SEEK_RELATIVE_ON_READ
                                * and SEEK_RELATIVE_END can corrupt
-                               * write_index. \since 0.8 */
+                               * write_index. */
     int64_t write_index;      /**< Current write index into the
                                * playback buffer in bytes. Think twice before
                                * using this for seeking purposes: it
                                * might be out of date a the time you
                                * want to use it. Consider using
-                               * PA_SEEK_RELATIVE instead. \since
-                               * 0.8 */
+                               * PA_SEEK_RELATIVE instead.  */
 
     int read_index_corrupt;   /**< Non-zero if read_index is not
                                * up-to-date because a local pause or
                                * flush request that corrupted it has
                                * been issued in the time since this
-                               * latency info was current. \since 0.8  */
+                               * latency info was current. */
 
     int64_t read_index;       /**< Current read index into the
                                * playback buffer in bytes. Think twice before
@@ -343,7 +426,20 @@ typedef struct pa_timing_info {
                                * might be out of date a the time you
                                * want to use it. Consider using
                                * PA_SEEK_RELATIVE_ON_READ
-                               * instead. \since 0.8 */
+                               * instead. */
+
+    pa_usec_t configured_sink_usec;   /**< The static configured latency for
+                                * the sink. \since 0.9.11 */
+    pa_usec_t configured_source_usec; /**< The static configured latency for
+                                * the source. \since 0.9.11 */
+
+    int64_t since_underrun;    /**< Bytes that were handed to the sink
+                                  since the last underrun happened, or
+                                  since playback started again after
+                                  the last underrun. playing will tell
+                                  you which case it is. \since
+                                  0.9.11 */
+
 } pa_timing_info;
 
 /** A structure for the spawn api. This may be used to integrate auto
@@ -352,7 +448,7 @@ typedef struct pa_timing_info {
  * waitpid() is used on the child's PID. The spawn routine will not
  * block or ignore SIGCHLD signals, since this cannot be done in a
  * thread compatible way. You might have to do this in
- * prefork/postfork. \since 0.4 */
+ * prefork/postfork. */
 typedef struct pa_spawn_api {
     void (*prefork)(void);     /**< Is called just before the fork in the parent process. May be NULL. */
     void (*postfork)(void);    /**< Is called immediately after the fork in the parent process. May be NULL.*/
@@ -365,7 +461,7 @@ typedef struct pa_spawn_api {
                                 * passed to the new process. */
 } pa_spawn_api;
 
-/** Seek type for pa_stream_write(). \since 0.8*/
+/** Seek type for pa_stream_write(). */
 typedef enum pa_seek_mode {
     PA_SEEK_RELATIVE = 0,           /**< Seek relatively to the write index */
     PA_SEEK_ABSOLUTE = 1,           /**< Seek relatively to the start of the buffer queue */
@@ -373,20 +469,24 @@ typedef enum pa_seek_mode {
     PA_SEEK_RELATIVE_END = 3        /**< Seek relatively to the current end of the buffer queue. */
 } pa_seek_mode_t;
 
-/** Special sink flags. \since 0.8  */
+/** Special sink flags. */
 typedef enum pa_sink_flags {
     PA_SINK_HW_VOLUME_CTRL = 1,   /**< Supports hardware volume control */
     PA_SINK_LATENCY = 2,          /**< Supports latency querying */
     PA_SINK_HARDWARE = 4,         /**< Is a hardware sink of some kind, in contrast to "virtual"/software sinks \since 0.9.3 */
-    PA_SINK_NETWORK = 8           /**< Is a networked sink of some kind. \since 0.9.7 */
+    PA_SINK_NETWORK = 8,          /**< Is a networked sink of some kind. \since 0.9.7 */
+    PA_SINK_HW_MUTE_CTRL = 16,    /**< Supports hardware mute control \since 0.9.11 */
+    PA_SINK_DECIBEL_VOLUME = 32   /**< Volume can be translated to dB with pa_sw_volume_to_dB() \since 0.9.11 */
 } pa_sink_flags_t;
 
-/** Special source flags. \since 0.8  */
+/** Special source flags.  */
 typedef enum pa_source_flags {
     PA_SOURCE_HW_VOLUME_CTRL = 1,  /**< Supports hardware volume control */
     PA_SOURCE_LATENCY = 2,         /**< Supports latency querying */
     PA_SOURCE_HARDWARE = 4,        /**< Is a hardware source of some kind, in contrast to "virtual"/software source \since 0.9.3 */
-    PA_SOURCE_NETWORK = 8          /**< Is a networked sink of some kind. \since 0.9.7 */
+    PA_SOURCE_NETWORK = 8,         /**< Is a networked sink of some kind. \since 0.9.7 */
+    PA_SOURCE_HW_MUTE_CTRL = 16,   /**< Supports hardware mute control \since 0.9.11 */
+    PA_SOURCE_DECIBEL_VOLUME = 32  /**< Volume can be translated to dB with pa_sw_volume_to_dB() \since 0.9.11 */
 } pa_source_flags_t;
 
 /** A generic free() like callback prototype */
similarity index 87%
rename from src/pulsecore/gccmacro.h
rename to src/pulse/gccmacro.h
index f94a8c4..032c3ba 100644 (file)
 #endif
 #endif
 
-#ifndef PA_LIKELY
+#ifndef PA_GCC_DEPRECATED
 #ifdef __GNUC__
-#define PA_LIKELY(x) (__builtin_expect(!!(x),1))
-#define PA_UNLIKELY(x) (__builtin_expect((x),0))
+#define PA_GCC_DEPRECATED __attribute__ ((deprecated))
 #else
-#define PA_LIKELY(x) (x)
-#define PA_UNLIKELY(x) (x)
+/** This function is deprecated **/
+#define PA_GCC_DEPRECATED
+#endif
+#endif
+
+#ifndef PA_GCC_PACKED
+#ifdef __GNUCC__
+#define PA_GCC_PACKED __attribute__ ((packed))
+#else
+/** Structure shall be packed in memory **/
+#define PA_GCC_PACKED
 #endif
 #endif
 
index 873f136..d346e94 100644 (file)
@@ -42,6 +42,7 @@
 #include <pulsecore/memblockq.h>
 #include <pulsecore/hashmap.h>
 #include <pulsecore/refcnt.h>
+#include <pulsecore/time-smoother.h>
 
 #include "client-conf.h"
 
@@ -50,7 +51,7 @@
 struct pa_context {
     PA_REFCNT_DECLARE;
 
-    char *name;
+    pa_proplist *proplist;
     pa_mainloop_api* mainloop;
 
     pa_socket_client *client;
@@ -69,14 +70,13 @@ struct pa_context {
 
     pa_context_notify_cb_t state_callback;
     void *state_userdata;
-
     pa_context_subscribe_cb_t subscribe_callback;
     void *subscribe_userdata;
 
     pa_mempool *mempool;
 
-    int is_local;
-    int do_autospawn;
+    pa_bool_t is_local;
+    pa_bool_t do_autospawn;
     int autospawn_lock_fd;
     pa_spawn_api spawn_api;
 
@@ -85,38 +85,43 @@ struct pa_context {
     char *server;
 
     pa_client_conf *conf;
+
+    uint32_t client_index;
 };
 
-#define PA_MAX_WRITE_INDEX_CORRECTIONS 10
+#define PA_MAX_WRITE_INDEX_CORRECTIONS 32
 
 typedef struct pa_index_correction {
     uint32_t tag;
-    int valid;
     int64_t value;
-    int absolute, corrupt;
+    pa_bool_t valid:1;
+    pa_bool_t absolute:1;
+    pa_bool_t corrupt:1;
 } pa_index_correction;
 
 struct pa_stream {
     PA_REFCNT_DECLARE;
+    PA_LLIST_FIELDS(pa_stream);
+
     pa_context *context;
     pa_mainloop_api *mainloop;
-    PA_LLIST_FIELDS(pa_stream);
 
-    char *name;
-    pa_bool_t manual_buffer_attr;
-    pa_buffer_attr buffer_attr;
+    pa_stream_direction_t direction;
+    pa_stream_state_t state;
+    pa_stream_flags_t flags;
+
     pa_sample_spec sample_spec;
     pa_channel_map channel_map;
-    pa_stream_flags_t flags;
+
+    pa_proplist *proplist;
+
     uint32_t channel;
+    pa_bool_t channel_valid;
     uint32_t syncid;
-    int channel_valid;
     uint32_t stream_index;
-    pa_stream_direction_t direction;
-    pa_stream_state_t state;
-    pa_bool_t buffer_attr_not_ready, timing_info_not_ready;
 
     uint32_t requested_bytes;
+    pa_buffer_attr buffer_attr;
 
     uint32_t device_index;
     char *device_name;
@@ -126,11 +131,11 @@ struct pa_stream {
     void *peek_data;
     pa_memblockq *record_memblockq;
 
-    int corked;
+    pa_bool_t corked;
 
     /* Store latest latency info */
     pa_timing_info timing_info;
-    int timing_info_valid;
+    pa_bool_t timing_info_valid;
 
     /* Use to make sure that time advances monotonically */
     pa_usec_t previous_time;
@@ -145,10 +150,9 @@ struct pa_stream {
 
     /* Latency interpolation stuff */
     pa_time_event *auto_timing_update_event;
-    int auto_timing_update_requested;
+    pa_bool_t auto_timing_update_requested;
 
-    pa_usec_t cached_time;
-    int cached_time_valid;
+    pa_smoother *smoother;
 
     /* Callbacks */
     pa_stream_notify_cb_t state_callback;
@@ -167,6 +171,8 @@ struct pa_stream {
     void *moved_userdata;
     pa_stream_notify_cb_t suspended_callback;
     void *suspended_userdata;
+    pa_stream_notify_cb_t started_callback;
+    void *started_userdata;
 };
 
 typedef void (*pa_operation_cb_t)(void);
@@ -192,7 +198,7 @@ void pa_command_subscribe_event(pa_pdispatch *pd, uint32_t command, uint32_t tag
 void pa_command_overflow_or_underflow(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
 void pa_command_stream_suspended(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
 void pa_command_stream_moved(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
-
+void pa_command_stream_started(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);
 
@@ -204,7 +210,7 @@ void pa_stream_simple_ack_callback(pa_pdispatch *pd, uint32_t command, uint32_t
 void pa_context_fail(pa_context *c, int error);
 int pa_context_set_error(pa_context *c, int error);
 void pa_context_set_state(pa_context *c, pa_context_state_t st);
-int pa_context_handle_error(pa_context *c, uint32_t command, pa_tagstruct *t);
+int pa_context_handle_error(pa_context *c, uint32_t command, pa_tagstruct *t, pa_bool_t fail);
 pa_operation* pa_context_send_simple_command(pa_context *c, uint32_t command, void (*internal_callback)(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata), void (*cb)(void), void *userdata);
 
 void pa_stream_set_state(pa_stream *s, pa_stream_state_t st);
@@ -226,5 +232,6 @@ pa_tagstruct *pa_tagstruct_command(pa_context *c, uint32_t command, uint32_t *ta
 
 #define PA_CHECK_VALIDITY_RETURN_NULL(context, expression, error) PA_CHECK_VALIDITY_RETURN_ANY(context, expression, error, NULL)
 
+void pa_init_proplist(pa_proplist *p);
 
 #endif
index 6610a72..857e82b 100644 (file)
@@ -27,8 +27,8 @@
 #endif
 
 #include <pulse/context.h>
+#include <pulse/gccmacro.h>
 
-#include <pulsecore/gccmacro.h>
 #include <pulsecore/macro.h>
 #include <pulsecore/pstream-util.h>
 
@@ -52,7 +52,7 @@ static void context_stat_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNU
         goto finish;
 
     if (command != PA_COMMAND_REPLY) {
-        if (pa_context_handle_error(o->context, command, t) < 0)
+        if (pa_context_handle_error(o->context, command, t, FALSE) < 0)
             goto finish;
 
         p = NULL;
@@ -95,7 +95,7 @@ static void context_get_server_info_callback(pa_pdispatch *pd, uint32_t command,
         goto finish;
 
     if (command != PA_COMMAND_REPLY) {
-        if (pa_context_handle_error(o->context, command, t) < 0)
+        if (pa_context_handle_error(o->context, command, t, FALSE) < 0)
             goto finish;
 
         p = NULL;
@@ -140,7 +140,7 @@ static void context_get_sink_info_callback(pa_pdispatch *pd, uint32_t command, P
         goto finish;
 
     if (command != PA_COMMAND_REPLY) {
-        if (pa_context_handle_error(o->context, command, t) < 0)
+        if (pa_context_handle_error(o->context, command, t, FALSE) < 0)
             goto finish;
 
         eol = -1;
@@ -149,7 +149,10 @@ static void context_get_sink_info_callback(pa_pdispatch *pd, uint32_t command, P
 
         while (!pa_tagstruct_eof(t)) {
             pa_sink_info i;
+            pa_bool_t mute = FALSE;
+
             memset(&i, 0, sizeof(i));
+            i.proplist = pa_proplist_new();
 
             if (pa_tagstruct_getu32(t, &i.index) < 0 ||
                 pa_tagstruct_gets(t, &i.name) < 0 ||
@@ -158,23 +161,30 @@ static void context_get_sink_info_callback(pa_pdispatch *pd, uint32_t command, P
                 pa_tagstruct_get_channel_map(t, &i.channel_map) < 0 ||
                 pa_tagstruct_getu32(t, &i.owner_module) < 0 ||
                 pa_tagstruct_get_cvolume(t, &i.volume) < 0 ||
-                pa_tagstruct_get_boolean(t, &i.mute) < 0 ||
+                pa_tagstruct_get_boolean(t, &mute) < 0 ||
                 pa_tagstruct_getu32(t, &i.monitor_source) < 0 ||
                 pa_tagstruct_gets(t, &i.monitor_source_name) < 0 ||
                 pa_tagstruct_get_usec(t, &i.latency) < 0 ||
                 pa_tagstruct_gets(t, &i.driver) < 0 ||
-                pa_tagstruct_getu32(t, &flags) < 0) {
+                pa_tagstruct_getu32(t, &flags) < 0 ||
+                (o->context->version >= 13 &&
+                 (pa_tagstruct_get_proplist(t, i.proplist) < 0 ||
+                  pa_tagstruct_get_usec(t, &i.configured_latency) < 0))) {
 
                 pa_context_fail(o->context, PA_ERR_PROTOCOL);
+                pa_proplist_free(i.proplist);
                 goto finish;
             }
 
+            i.mute = (int) mute;
             i.flags = (pa_sink_flags_t) flags;
 
             if (o->callback) {
                 pa_sink_info_cb_t cb = (pa_sink_info_cb_t) o->callback;
                 cb(o->context, &i, 0, o->userdata);
             }
+
+            pa_proplist_free(i.proplist);
         }
     }
 
@@ -251,7 +261,7 @@ static void context_get_source_info_callback(pa_pdispatch *pd, uint32_t command,
         goto finish;
 
     if (command != PA_COMMAND_REPLY) {
-        if (pa_context_handle_error(o->context, command, t) < 0)
+        if (pa_context_handle_error(o->context, command, t, FALSE) < 0)
             goto finish;
 
         eol = -1;
@@ -260,7 +270,10 @@ static void context_get_source_info_callback(pa_pdispatch *pd, uint32_t command,
         while (!pa_tagstruct_eof(t)) {
             pa_source_info i;
             uint32_t flags;
+            pa_bool_t mute = FALSE;
+
             memset(&i, 0, sizeof(i));
+            i.proplist = pa_proplist_new();
 
             if (pa_tagstruct_getu32(t, &i.index) < 0 ||
                 pa_tagstruct_gets(t, &i.name) < 0 ||
@@ -269,23 +282,30 @@ static void context_get_source_info_callback(pa_pdispatch *pd, uint32_t command,
                 pa_tagstruct_get_channel_map(t, &i.channel_map) < 0 ||
                 pa_tagstruct_getu32(t, &i.owner_module) < 0 ||
                 pa_tagstruct_get_cvolume(t, &i.volume) < 0 ||
-                pa_tagstruct_get_boolean(t, &i.mute) < 0 ||
+                pa_tagstruct_get_boolean(t, &mute) < 0 ||
                 pa_tagstruct_getu32(t, &i.monitor_of_sink) < 0 ||
                 pa_tagstruct_gets(t, &i.monitor_of_sink_name) < 0 ||
                 pa_tagstruct_get_usec(t, &i.latency) < 0 ||
                 pa_tagstruct_gets(t, &i.driver) < 0 ||
-                pa_tagstruct_getu32(t, &flags) < 0) {
+                pa_tagstruct_getu32(t, &flags) < 0 ||
+                (o->context->version >= 13 &&
+                 (pa_tagstruct_get_proplist(t, i.proplist) < 0 ||
+                  pa_tagstruct_get_usec(t, &i.configured_latency) < 0))) {
 
                 pa_context_fail(o->context, PA_ERR_PROTOCOL);
+                pa_proplist_free(i.proplist);
                 goto finish;
             }
 
+            i.mute = (int) mute;
             i.flags = (pa_source_flags_t) flags;
 
             if (o->callback) {
                 pa_source_info_cb_t cb = (pa_source_info_cb_t) o->callback;
                 cb(o->context, &i, 0, o->userdata);
             }
+
+            pa_proplist_free(i.proplist);
         }
     }
 
@@ -362,7 +382,7 @@ static void context_get_client_info_callback(pa_pdispatch *pd, uint32_t command,
         goto finish;
 
     if (command != PA_COMMAND_REPLY) {
-        if (pa_context_handle_error(o->context, command, t) < 0)
+        if (pa_context_handle_error(o->context, command, t, FALSE) < 0)
             goto finish;
 
         eol = -1;
@@ -370,13 +390,18 @@ 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));
+            i.proplist = pa_proplist_new();
 
             if (pa_tagstruct_getu32(t, &i.index) < 0 ||
                 pa_tagstruct_gets(t, &i.name) < 0 ||
                 pa_tagstruct_getu32(t, &i.owner_module) < 0 ||
-                pa_tagstruct_gets(t, &i.driver) < 0 ) {
+                pa_tagstruct_gets(t, &i.driver) < 0 ||
+                (o->context->version >= 13 && pa_tagstruct_get_proplist(t, i.proplist) < 0)) {
+
                 pa_context_fail(o->context, PA_ERR_PROTOCOL);
+                pa_proplist_free(i.proplist);
                 goto finish;
             }
 
@@ -384,6 +409,8 @@ static void context_get_client_info_callback(pa_pdispatch *pd, uint32_t command,
                 pa_client_info_cb_t cb = (pa_client_info_cb_t) o->callback;
                 cb(o->context, &i, 0, o->userdata);
             }
+
+            pa_proplist_free(i.proplist);
         }
     }
 
@@ -437,7 +464,7 @@ static void context_get_module_info_callback(pa_pdispatch *pd, uint32_t command,
         goto finish;
 
     if (command != PA_COMMAND_REPLY) {
-        if (pa_context_handle_error(o->context, command, t) < 0)
+        if (pa_context_handle_error(o->context, command, t, FALSE) < 0)
             goto finish;
 
         eol = -1;
@@ -445,17 +472,20 @@ static void context_get_module_info_callback(pa_pdispatch *pd, uint32_t command,
 
         while (!pa_tagstruct_eof(t)) {
             pa_module_info i;
+            pa_bool_t auto_unload = FALSE;
             memset(&i, 0, sizeof(i));
 
             if (pa_tagstruct_getu32(t, &i.index) < 0 ||
                 pa_tagstruct_gets(t, &i.name) < 0 ||
                 pa_tagstruct_gets(t, &i.argument) < 0 ||
                 pa_tagstruct_getu32(t, &i.n_used) < 0 ||
-                pa_tagstruct_get_boolean(t, &i.auto_unload) < 0) {
+                pa_tagstruct_get_boolean(t, &auto_unload) < 0) {
                 pa_context_fail(o->context, PA_ERR_PROTOCOL);
                 goto finish;
             }
 
+            i.auto_unload = (int) auto_unload;
+
             if (o->callback) {
                 pa_module_info_cb_t cb = (pa_module_info_cb_t) o->callback;
                 cb(o->context, &i, 0, o->userdata);
@@ -513,7 +543,7 @@ static void context_get_sink_input_info_callback(pa_pdispatch *pd, uint32_t comm
         goto finish;
 
     if (command != PA_COMMAND_REPLY) {
-        if (pa_context_handle_error(o->context, command, t) < 0)
+        if (pa_context_handle_error(o->context, command, t, FALSE) < 0)
             goto finish;
 
         eol = -1;
@@ -521,7 +551,10 @@ static void context_get_sink_input_info_callback(pa_pdispatch *pd, uint32_t comm
 
         while (!pa_tagstruct_eof(t)) {
             pa_sink_input_info i;
+            pa_bool_t mute = FALSE;
+
             memset(&i, 0, sizeof(i));
+            i.proplist = pa_proplist_new();
 
             if (pa_tagstruct_getu32(t, &i.index) < 0 ||
                 pa_tagstruct_gets(t, &i.name) < 0 ||
@@ -535,16 +568,22 @@ static void context_get_sink_input_info_callback(pa_pdispatch *pd, uint32_t comm
                 pa_tagstruct_get_usec(t, &i.sink_usec) < 0 ||
                 pa_tagstruct_gets(t, &i.resample_method) < 0 ||
                 pa_tagstruct_gets(t, &i.driver) < 0 ||
-                (o->context->version >= 11 && pa_tagstruct_get_boolean(t, &i.mute) < 0)) {
+                (o->context->version >= 11 && pa_tagstruct_get_boolean(t, &mute) < 0) ||
+                (o->context->version >= 13 && pa_tagstruct_get_proplist(t, i.proplist) < 0)) {
 
                 pa_context_fail(o->context, PA_ERR_PROTOCOL);
+                pa_proplist_free(i.proplist);
                 goto finish;
             }
 
+            i.mute = (int) mute;
+
             if (o->callback) {
                 pa_sink_input_info_cb_t cb = (pa_sink_input_info_cb_t) o->callback;
                 cb(o->context, &i, 0, o->userdata);
             }
+
+            pa_proplist_free(i.proplist);
         }
     }
 
@@ -598,7 +637,7 @@ static void context_get_source_output_info_callback(pa_pdispatch *pd, uint32_t c
         goto finish;
 
     if (command != PA_COMMAND_REPLY) {
-        if (pa_context_handle_error(o->context, command, t) < 0)
+        if (pa_context_handle_error(o->context, command, t, FALSE) < 0)
             goto finish;
 
         eol = -1;
@@ -608,6 +647,7 @@ static void context_get_source_output_info_callback(pa_pdispatch *pd, uint32_t c
             pa_source_output_info i;
 
             memset(&i, 0, sizeof(i));
+            i.proplist = pa_proplist_new();
 
             if (pa_tagstruct_getu32(t, &i.index) < 0 ||
                 pa_tagstruct_gets(t, &i.name) < 0 ||
@@ -619,9 +659,11 @@ static void context_get_source_output_info_callback(pa_pdispatch *pd, uint32_t c
                 pa_tagstruct_get_usec(t, &i.buffer_usec) < 0 ||
                 pa_tagstruct_get_usec(t, &i.source_usec) < 0 ||
                 pa_tagstruct_gets(t, &i.resample_method) < 0 ||
-                pa_tagstruct_gets(t, &i.driver) < 0) {
+                pa_tagstruct_gets(t, &i.driver) < 0 ||
+                (o->context->version >= 13 && pa_tagstruct_get_proplist(t, i.proplist) < 0)) {
 
                 pa_context_fail(o->context, PA_ERR_PROTOCOL);
+                pa_proplist_free(i.proplist);
                 goto finish;
             }
 
@@ -629,6 +671,8 @@ static void context_get_source_output_info_callback(pa_pdispatch *pd, uint32_t c
                 pa_source_output_info_cb_t cb = (pa_source_output_info_cb_t) o->callback;
                 cb(o->context, &i, 0, o->userdata);
             }
+
+            pa_proplist_free(i.proplist);
         }
     }
 
@@ -923,7 +967,7 @@ static void context_get_sample_info_callback(pa_pdispatch *pd, uint32_t command,
         goto finish;
 
     if (command != PA_COMMAND_REPLY) {
-        if (pa_context_handle_error(o->context, command, t) < 0)
+        if (pa_context_handle_error(o->context, command, t, FALSE) < 0)
             goto finish;
 
         eol = -1;
@@ -931,8 +975,10 @@ static void context_get_sample_info_callback(pa_pdispatch *pd, uint32_t command,
 
         while (!pa_tagstruct_eof(t)) {
             pa_sample_info i;
+            pa_bool_t lazy = FALSE;
 
             memset(&i, 0, sizeof(i));
+            i.proplist = pa_proplist_new();
 
             if (pa_tagstruct_getu32(t, &i.index) < 0 ||
                 pa_tagstruct_gets(t, &i.name) < 0 ||
@@ -941,17 +987,22 @@ static void context_get_sample_info_callback(pa_pdispatch *pd, uint32_t command,
                 pa_tagstruct_get_sample_spec(t, &i.sample_spec) < 0 ||
                 pa_tagstruct_get_channel_map(t, &i.channel_map) < 0 ||
                 pa_tagstruct_getu32(t, &i.bytes) < 0 ||
-                pa_tagstruct_get_boolean(t, &i.lazy) < 0 ||
-                pa_tagstruct_gets(t, &i.filename) < 0) {
+                pa_tagstruct_get_boolean(t, &lazy) < 0 ||
+                pa_tagstruct_gets(t, &i.filename) < 0 ||
+                (o->context->version >= 13 && pa_tagstruct_get_proplist(t, i.proplist) < 0)) {
 
                 pa_context_fail(o->context, PA_ERR_PROTOCOL);
                 goto finish;
             }
 
+            i.lazy = (int) lazy;
+
             if (o->callback) {
                 pa_sample_info_cb_t cb = (pa_sample_info_cb_t) o->callback;
                 cb(o->context, &i, 0, o->userdata);
             }
+
+            pa_proplist_free(i.proplist);
         }
     }
 
@@ -1060,7 +1111,7 @@ static void context_index_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UN
         goto finish;
 
     if (command != PA_COMMAND_REPLY) {
-        if (pa_context_handle_error(o->context, command, t) < 0)
+        if (pa_context_handle_error(o->context, command, t, FALSE) < 0)
             goto finish;
 
         idx = PA_INVALID_INDEX;
@@ -1121,7 +1172,7 @@ static void context_get_autoload_info_callback(pa_pdispatch *pd, uint32_t comman
         goto finish;
 
     if (command != PA_COMMAND_REPLY) {
-        if (pa_context_handle_error(o->context, command, t) < 0)
+        if (pa_context_handle_error(o->context, command, t, FALSE) < 0)
             goto finish;
 
         eol = -1;
@@ -1158,6 +1209,8 @@ finish:
     pa_operation_unref(o);
 }
 
+PA_WARN_REFERENCE(pa_context_get_autoload_info_by_name, "Autoload will no longer be implemented by future versions of the PulseAudio server.");
+
 pa_operation* pa_context_get_autoload_info_by_name(pa_context *c, const char *name, pa_autoload_type_t type, pa_autoload_info_cb_t cb, void *userdata) {
     pa_tagstruct *t;
     pa_operation *o;
@@ -1182,6 +1235,8 @@ pa_operation* pa_context_get_autoload_info_by_name(pa_context *c, const char *na
     return o;
 }
 
+PA_WARN_REFERENCE(pa_context_get_autoload_info_by_index, "Autoload will no longer be implemented by future versions of the PulseAudio server.");
+
 pa_operation* pa_context_get_autoload_info_by_index(pa_context *c, uint32_t idx, pa_autoload_info_cb_t cb, void *userdata) {
     pa_tagstruct *t;
     pa_operation *o;
@@ -1204,10 +1259,15 @@ pa_operation* pa_context_get_autoload_info_by_index(pa_context *c, uint32_t idx,
     return o;
 }
 
+
+PA_WARN_REFERENCE(pa_context_get_autoload_info_list, "Autoload will no longer be implemented by future versions of the PulseAudio server.");
+
 pa_operation* pa_context_get_autoload_info_list(pa_context *c, pa_autoload_info_cb_t cb, void *userdata) {
     return pa_context_send_simple_command(c, PA_COMMAND_GET_AUTOLOAD_INFO_LIST, context_get_autoload_info_callback, (pa_operation_cb_t) cb, userdata);
 }
 
+PA_WARN_REFERENCE(pa_context_add_autoload, "Autoload will no longer be implemented by future versions of the PulseAudio server.");
+
 pa_operation* pa_context_add_autoload(pa_context *c, const char *name, pa_autoload_type_t type, const char *module, const char*argument, pa_context_index_cb_t cb, void* userdata) {
     pa_operation *o;
     pa_tagstruct *t;
@@ -1234,6 +1294,8 @@ pa_operation* pa_context_add_autoload(pa_context *c, const char *name, pa_autolo
     return o;
 }
 
+PA_WARN_REFERENCE(pa_context_remove_autoload_by_name, "Autoload will no longer be implemented by future versions of the PulseAudio server.");
+
 pa_operation* pa_context_remove_autoload_by_name(pa_context *c, const char *name, pa_autoload_type_t type, pa_context_success_cb_t cb, void* userdata) {
     pa_operation *o;
     pa_tagstruct *t;
@@ -1257,6 +1319,8 @@ pa_operation* pa_context_remove_autoload_by_name(pa_context *c, const char *name
     return o;
 }
 
+PA_WARN_REFERENCE(pa_context_remove_autoload_by_index, "Autoload will no longer be implemented by future versions of the PulseAudio server.");
+
 pa_operation* pa_context_remove_autoload_by_index(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void* userdata) {
     pa_operation *o;
     pa_tagstruct *t;
index c148ee5..d185a3a 100644 (file)
 #include <pulse/operation.h>
 #include <pulse/context.h>
 #include <pulse/cdecl.h>
+#include <pulse/gccmacro.h>
 #include <pulse/channelmap.h>
 #include <pulse/volume.h>
+#include <pulse/proplist.h>
 
 /** \page introspect Server Query and Control
  *
 
 PA_C_DECL_BEGIN
 
-/** Stores information about sinks */
+#define PA_PORT_DIGITAL "spdif"
+#define PA_PORT_ANALOG_STEREO "analog-stereo"
+#define PA_PORT_ANALOG_5_1 "analog-5-1"
+#define PA_PORT_ANALOG_4_0 "analog-4-0"
+
+/** @{ \name Sinks */
+
+/** 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. */
 typedef struct pa_sink_info {
     const char *name;                  /**< Name of the sink */
     uint32_t index;                    /**< Index of the sink */
     const char *description;           /**< Description of this sink */
     pa_sample_spec sample_spec;        /**< Sample spec of this sink */
-    pa_channel_map channel_map;        /**< Channel map \since 0.8 */
+    pa_channel_map channel_map;        /**< Channel map */
     uint32_t owner_module;             /**< Index of the owning module of this sink, or PA_INVALID_INDEX */
     pa_cvolume volume;                 /**< Volume of the sink */
-    int mute;                          /**< Mute switch of the sink \since 0.8 */
+    int mute;                          /**< Mute switch of the sink */
     uint32_t monitor_source;           /**< Index of the monitor source connected to this sink */
     const char *monitor_source_name;   /**< The name of the monitor source */
-    pa_usec_t latency;                 /**< Length of filled playback buffer of this sink */
-    const char *driver;                /**< Driver name. \since 0.8 */
-    pa_sink_flags_t flags;             /**< Flags \since 0.8 */
+    pa_usec_t latency;                 /**< Length of queued audio in the output buffer. */
+    const char *driver;                /**< Driver name. */
+    pa_sink_flags_t flags;             /**< Flags */
+    pa_proplist *proplist;             /**< Property list \since 0.9.11 */
+    pa_usec_t configured_latency;      /**< The latency this device has been configured to. \since 0.9.11 */
 } pa_sink_info;
 
 /** Callback prototype for pa_context_get_sink_info_by_name() and friends */
@@ -235,21 +248,47 @@ pa_operation* pa_context_get_sink_info_by_index(pa_context *c, uint32_t id, pa_s
 /** Get the complete sink list */
 pa_operation* pa_context_get_sink_info_list(pa_context *c, pa_sink_info_cb_t cb, void *userdata);
 
-/** Stores information about sources */
+/** Set the volume of a sink device specified by its index */
+pa_operation* pa_context_set_sink_volume_by_index(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata);
+
+/** Set the volume of a sink device specified by its name */
+pa_operation* pa_context_set_sink_volume_by_name(pa_context *c, const char *name, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata);
+
+/** Set the mute switch of a sink device specified by its index */
+pa_operation* pa_context_set_sink_mute_by_index(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata);
+
+/** Set the mute switch of a sink device specified by its name */
+pa_operation* pa_context_set_sink_mute_by_name(pa_context *c, const char *name, int mute, pa_context_success_cb_t cb, void *userdata);
+
+/** Suspend/Resume a sink. \since 0.9.7 */
+pa_operation* pa_context_suspend_sink_by_name(pa_context *c, char *sink_name, int suspend, pa_context_success_cb_t cb, void* userdata);
+
+/** 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);
+
+/** @} */
+
+/** @{ \name Sources */
+
+/** 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. */
 typedef struct pa_source_info {
-    const char *name ;                  /**< Name of the source */
+    const char *name                  /**< Name of the source */
     uint32_t index;                     /**< Index of the source */
     const char *description;            /**< Description of this source */
     pa_sample_spec sample_spec;         /**< Sample spec of this source */
-    pa_channel_map channel_map;         /**< Channel map \since 0.8 */
+    pa_channel_map channel_map;         /**< Channel map */
     uint32_t owner_module;              /**< Owning module index, or PA_INVALID_INDEX */
-    pa_cvolume volume;                  /**< Volume of the source \since 0.8 */
-    int mute;                           /**< Mute switch of the sink \since 0.8 */
+    pa_cvolume volume;                  /**< Volume of the source */
+    int mute;                           /**< Mute switch of the sink */
     uint32_t monitor_of_sink;           /**< If this is a monitor source the index of the owning sink, otherwise PA_INVALID_INDEX */
     const char *monitor_of_sink_name;   /**< Name of the owning sink, or PA_INVALID_INDEX */
-    pa_usec_t latency;                  /**< Length of filled record buffer of this source. \since 0.5 */
-    const char *driver;                 /**< Driver name \since 0.8 */
-    pa_source_flags_t flags;            /**< Flags \since 0.8 */
+    pa_usec_t latency;                  /**< Length of filled record buffer of this source. */
+    const char *driver;                 /**< Driver name */
+    pa_source_flags_t flags;            /**< Flags */
+    pa_proplist *proplist;              /**< Property list \since 0.9.11 */
+    pa_usec_t configured_latency;       /**< The latency this device has been configured to. \since 0.9.11 */
 } pa_source_info;
 
 /** Callback prototype for pa_context_get_source_info_by_name() and friends */
@@ -264,16 +303,34 @@ pa_operation* pa_context_get_source_info_by_index(pa_context *c, uint32_t id, pa
 /** Get the complete source list */
 pa_operation* pa_context_get_source_info_list(pa_context *c, pa_source_info_cb_t cb, void *userdata);
 
-/** Server information */
+/** Set the volume of a source device specified by its index */
+pa_operation* pa_context_set_source_volume_by_index(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata);
+
+/** Set the volume of a source device specified by its name */
+pa_operation* pa_context_set_source_volume_by_name(pa_context *c, const char *name, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata);
+
+/** Set the mute switch of a source device specified by its index */
+pa_operation* pa_context_set_source_mute_by_index(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata);
+
+/** 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);
+
+/** @} */
+
+/** @{ \name Server */
+
+/** Server information. Please note that this structure can be
+ * extended as part of evolutionary API updates at any time in any new
+ * release. */
 typedef struct pa_server_info {
     const char *user_name;              /**< User name of the daemon process */
     const char *host_name;              /**< Host name the daemon is running on */
     const char *server_version;         /**< Version string of the daemon */
     const char *server_name;            /**< Server package name (usually "pulseaudio") */
     pa_sample_spec sample_spec;         /**< Default sample specification */
-    const char *default_sink_name;      /**< Name of default sink. \since 0.4 */
-    const char *default_source_name;    /**< Name of default sink. \since 0.4*/
-    uint32_t cookie;                    /**< A random cookie for identifying this instance of PulseAudio. \since 0.8 */
+    const char *default_sink_name;      /**< Name of default sink. */
+    const char *default_source_name;    /**< Name of default sink. */
+    uint32_t cookie;                    /**< A random cookie for identifying this instance of PulseAudio. */
 } pa_server_info;
 
 /** Callback prototype for pa_context_get_server_info() */
@@ -282,7 +339,13 @@ typedef void (*pa_server_info_cb_t) (pa_context *c, const pa_server_info*i, void
 /** Get some information about the server */
 pa_operation* pa_context_get_server_info(pa_context *c, pa_server_info_cb_t cb, void *userdata);
 
-/** Stores information about modules */
+/** @} */
+
+/** @{ \name Modules */
+
+/** Stores information about modules. Please note that this structure
+ * can be extended as part of evolutionary API updates at any time in
+ * any new release. */
 typedef struct pa_module_info {
     uint32_t index;                     /**< Index of the module */
     const char*name,                    /**< Name of the module */
@@ -300,12 +363,28 @@ pa_operation* pa_context_get_module_info(pa_context *c, uint32_t idx, pa_module_
 /** Get the complete list of currently loaded modules */
 pa_operation* pa_context_get_module_info_list(pa_context *c, pa_module_info_cb_t cb, void *userdata);
 
-/** Stores information about clients */
+/** Callback prototype for pa_context_load_module() */
+typedef void (*pa_context_index_cb_t)(pa_context *c, uint32_t idx, void *userdata);
+
+/** Load a module. */
+pa_operation* pa_context_load_module(pa_context *c, const char*name, const char *argument, pa_context_index_cb_t cb, void *userdata);
+
+/** Unload a module. */
+pa_operation* pa_context_unload_module(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata);
+
+/** @} */
+
+/** @{ \name Clients */
+
+/** Stores information about clients. Please note that this structure
+ * can be extended as part of evolutionary API updates at any time in
+ * any new release. */
 typedef struct pa_client_info {
     uint32_t index;                      /**< Index of this client */
     const char *name;                    /**< Name of this client */
     uint32_t owner_module;               /**< Index of the owning module, or PA_INVALID_INDEX */
-    const char *driver;                  /**< Driver name \since 0.8 */
+    const char *driver;                  /**< Driver name */
+    pa_proplist *proplist;               /**< Property list \since 0.9.11 */
 } pa_client_info;
 
 /** Callback prototype for pa_context_get_client_info() and firends*/
@@ -317,7 +396,16 @@ pa_operation* pa_context_get_client_info(pa_context *c, uint32_t idx, pa_client_
 /** Get the complete client list */
 pa_operation* pa_context_get_client_info_list(pa_context *c, pa_client_info_cb_t cb, void *userdata);
 
-/** Stores information about sink inputs */
+/** Kill a client. */
+pa_operation* pa_context_kill_client(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata);
+
+/** @} */
+
+/** @{ \name Sink Inputs */
+
+/** Stores information about sink inputs. Please note that this structure
+ * can be extended as part of evolutionary API updates at any time in
+ * any new release. */
 typedef struct pa_sink_input_info {
     uint32_t index;                      /**< Index of the sink input */
     const char *name;                    /**< Name of the sink input */
@@ -329,9 +417,10 @@ 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. \since 0.7 */
-    const char *driver;                  /**< Driver name \since 0.8 */
+    const char *resample_method;         /**< Thre 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*/
@@ -343,7 +432,28 @@ pa_operation* pa_context_get_sink_input_info(pa_context *c, uint32_t idx, pa_sin
 /** Get the complete sink input list */
 pa_operation* pa_context_get_sink_input_info_list(pa_context *c, pa_sink_input_info_cb_t cb, void *userdata);
 
-/** Stores information about source outputs */
+/** Move the specified sink input to a different sink. \since 0.9.5 */
+pa_operation* pa_context_move_sink_input_by_name(pa_context *c, uint32_t idx, char *sink_name, pa_context_success_cb_t cb, void* userdata);
+
+/** Move the specified sink input to a different sink. \since 0.9.5 */
+pa_operation* pa_context_move_sink_input_by_index(pa_context *c, uint32_t idx, uint32_t sink_idx, pa_context_success_cb_t cb, void* userdata);
+
+/** Set the volume of a sink input stream */
+pa_operation* pa_context_set_sink_input_volume(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata);
+
+/** Set the mute switch of a sink input stream \since 0.9.7 */
+pa_operation* pa_context_set_sink_input_mute(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata);
+
+/** Kill a sink input. */
+pa_operation* pa_context_kill_sink_input(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata);
+
+/** @} */
+
+/** @{ \name Source Outputs */
+
+/** Stores information about source outputs. Please note that this structure
+ * can be extended as part of evolutionary API updates at any time in
+ * any new release. */
 typedef struct pa_source_output_info {
     uint32_t index;                      /**< Index of the sink input */
     const char *name;                    /**< Name of the sink input */
@@ -352,10 +462,11 @@ typedef struct pa_source_output_info {
     uint32_t source;                     /**< Index of the connected source */
     pa_sample_spec sample_spec;          /**< The sample specification of the source output */
     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. \since 0.5 */
-    pa_usec_t source_usec;               /**< Latency of the source device, see pa_latency_info for details. \since 0.5 */
-    const char *resample_method;         /**< Thre resampling method used by this source output. \since 0.7 */
-    const char *driver;                  /**< Driver name \since 0.8 */
+    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 *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*/
@@ -367,43 +478,34 @@ pa_operation* pa_context_get_source_output_info(pa_context *c, uint32_t idx, pa_
 /** Get the complete list of source outputs */
 pa_operation* pa_context_get_source_output_info_list(pa_context *c, pa_source_output_info_cb_t cb, void *userdata);
 
-/** Set the volume of a sink device specified by its index */
-pa_operation* pa_context_set_sink_volume_by_index(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata);
-
-/** Set the volume of a sink device specified by its name */
-pa_operation* pa_context_set_sink_volume_by_name(pa_context *c, const char *name, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata);
-
-/** Set the mute switch of a sink device specified by its index \since 0.8 */
-pa_operation* pa_context_set_sink_mute_by_index(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata);
-
-/** Set the mute switch of a sink device specified by its name \since 0.8 */
-pa_operation* pa_context_set_sink_mute_by_name(pa_context *c, const char *name, int mute, pa_context_success_cb_t cb, void *userdata);
+/** Move the specified source output to a different source. \since 0.9.5 */
+pa_operation* pa_context_move_source_output_by_name(pa_context *c, uint32_t idx, char *source_name, pa_context_success_cb_t cb, void* userdata);
 
-/** Set the volume of a sink input stream */
-pa_operation* pa_context_set_sink_input_volume(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata);
+/** Move the specified source output to a different source. \since 0.9.5 */
+pa_operation* pa_context_move_source_output_by_index(pa_context *c, uint32_t idx, uint32_t source_idx, pa_context_success_cb_t cb, void* userdata);
 
-/** Set the mute switch of a sink input stream \since 0.9.7 */
-pa_operation* pa_context_set_sink_input_mute(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata);
+/** Suspend/Resume a source. \since 0.9.7 */
+pa_operation* pa_context_suspend_source_by_name(pa_context *c, char *source_name, int suspend, pa_context_success_cb_t cb, void* userdata);
 
-/** Set the volume of a source device specified by its index \since 0.8 */
-pa_operation* pa_context_set_source_volume_by_index(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata);
+/** Suspend/Resume a source. If idx is PA_INVALID_INDEX all sources will be suspended. \since 0.9.7 */
+pa_operation* pa_context_suspend_source_by_index(pa_context *c, uint32_t idx, int suspend, pa_context_success_cb_t cb, void* userdata);
 
-/** Set the volume of a source device specified by its name \since 0.8 */
-pa_operation* pa_context_set_source_volume_by_name(pa_context *c, const char *name, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata);
+/** Kill a source output. */
+pa_operation* pa_context_kill_source_output(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata);
 
-/** Set the mute switch of a source device specified by its index \since 0.8 */
-pa_operation* pa_context_set_source_mute_by_index(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata);
+/** @} */
 
-/** Set the mute switch of a source device specified by its name \since 0.8 */
-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);
+/** @{ \name Statistics */
 
-/** Memory block statistics */
+/** Memory block statistics. Please note that this structure
+ * can be extended as part of evolutionary API updates at any time in
+ * 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_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. \since 0.4 */
+    uint32_t scache_size;              /**< Total size of all sample cache entries. */
 } pa_stat_info;
 
 /** Callback prototype for pa_context_stat() */
@@ -412,7 +514,13 @@ typedef void (*pa_stat_info_cb_t) (pa_context *c, const pa_stat_info *i, void *u
 /** Get daemon memory block statistics */
 pa_operation* pa_context_stat(pa_context *c, pa_stat_info_cb_t cb, void *userdata);
 
-/** Stores information about sample cache entries */
+/** @} */
+
+/** @{ \name Cached Samples */
+
+/** Stores information about sample cache entries. Please note that this structure
+ * can be extended as part of evolutionary API updates at any time in
+ * any new release. */
 typedef struct pa_sample_info {
     uint32_t index;                       /**< Index of this entry */
     const char *name;                     /**< Name of this entry */
@@ -420,9 +528,10 @@ typedef struct pa_sample_info {
     pa_sample_spec sample_spec;           /**< Sample specification of the sample */
     pa_channel_map channel_map;           /**< The channel map */
     pa_usec_t duration;                   /**< Duration of this entry */
-    uint32_t bytes;                       /**< Length of this sample in bytes. \since 0.4 */
-    int lazy;                             /**< Non-zero when this is a lazy cache entry. \since 0.5 */
-    const char *filename;                 /**< In case this is a lazy cache entry, the filename for the sound file to be loaded on demand. \since 0.5 */
+    uint32_t bytes;                       /**< Length of this sample in bytes. */
+    int lazy;                             /**< Non-zero when this is a lazy cache entry. */
+    const char *filename;                 /**< In case this is a lazy cache entry, the filename for the sound file to be loaded on demand. */
+    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 */
@@ -437,31 +546,21 @@ pa_operation* pa_context_get_sample_info_by_index(pa_context *c, uint32_t idx, p
 /** Get the complete list of samples stored in the daemon. */
 pa_operation* pa_context_get_sample_info_list(pa_context *c, pa_sample_info_cb_t cb, void *userdata);
 
-/** Kill a client. \since 0.5 */
-pa_operation* pa_context_kill_client(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata);
+/** @} */
 
-/** Kill a sink input. \since 0.5 */
-pa_operation* pa_context_kill_sink_input(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata);
+/** \cond fulldocs */
 
-/** Kill a source output. \since 0.5 */
-pa_operation* pa_context_kill_source_output(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata);
-
-/** Callback prototype for pa_context_load_module() and pa_context_add_autoload() */
-typedef void (*pa_context_index_cb_t)(pa_context *c, uint32_t idx, void *userdata);
-
-/** Load a module. \since 0.5 */
-pa_operation* pa_context_load_module(pa_context *c, const char*name, const char *argument, pa_context_index_cb_t cb, void *userdata);
-
-/** Unload a module. \since 0.5 */
-pa_operation* pa_context_unload_module(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata);
+/** @{ \name Autoload Entries */
 
-/** Type of an autoload entry. \since 0.5 */
+/** Type of an autoload entry. */
 typedef enum pa_autoload_type {
     PA_AUTOLOAD_SINK = 0,
     PA_AUTOLOAD_SOURCE = 1
 } pa_autoload_type_t;
 
-/** Stores information about autoload entries. \since 0.5 */
+/** Stores information about autoload entries. Please note that this structure
+ * can be extended as part of evolutionary API updates at any time in
+ * any new release. */
 typedef struct pa_autoload_info {
     uint32_t index;               /**< Index of this autoload entry */
     const char *name;             /**< Name of the sink or source */
@@ -473,47 +572,27 @@ typedef struct pa_autoload_info {
 /** Callback prototype for pa_context_get_autoload_info_by_name() and firends */
 typedef void (*pa_autoload_info_cb_t)(pa_context *c, const pa_autoload_info *i, int eol, void *userdata);
 
-/** Get info about a specific autoload entry. \since 0.6 */
-pa_operation* pa_context_get_autoload_info_by_name(pa_context *c, const char *name, pa_autoload_type_t type, pa_autoload_info_cb_t cb, void *userdata);
+/** Get info about a specific autoload entry. */
+pa_operation* pa_context_get_autoload_info_by_name(pa_context *c, const char *name, pa_autoload_type_t type, pa_autoload_info_cb_t cb, void *userdata) PA_GCC_DEPRECATED;
 
-/** Get info about a specific autoload entry. \since 0.6 */
-pa_operation* pa_context_get_autoload_info_by_index(pa_context *c, uint32_t idx, pa_autoload_info_cb_t cb, void *userdata);
+/** Get info about a specific autoload entry. */
+pa_operation* pa_context_get_autoload_info_by_index(pa_context *c, uint32_t idx, pa_autoload_info_cb_t cb, void *userdata) PA_GCC_DEPRECATED;
 
-/** Get the complete list of autoload entries. \since 0.5 */
-pa_operation* pa_context_get_autoload_info_list(pa_context *c, pa_autoload_info_cb_t cb, void *userdata);
+/** Get the complete list of autoload entries. */
+pa_operation* pa_context_get_autoload_info_list(pa_context *c, pa_autoload_info_cb_t cb, void *userdata) PA_GCC_DEPRECATED;
 
-/** Add a new autoload entry. \since 0.5 */
-pa_operation* pa_context_add_autoload(pa_context *c, const char *name, pa_autoload_type_t type, const char *module, const char*argument, pa_context_index_cb_t, void* userdata);
+/** Add a new autoload entry. */
+pa_operation* pa_context_add_autoload(pa_context *c, const char *name, pa_autoload_type_t type, const char *module, const char*argument, pa_context_index_cb_t, void* userdata) PA_GCC_DEPRECATED;
 
-/** Remove an autoload entry. \since 0.6 */
-pa_operation* pa_context_remove_autoload_by_name(pa_context *c, const char *name, pa_autoload_type_t type, pa_context_success_cb_t cb, void* userdata);
+/** Remove an autoload entry. */
+pa_operation* pa_context_remove_autoload_by_name(pa_context *c, const char *name, pa_autoload_type_t type, pa_context_success_cb_t cb, void* userdata) PA_GCC_DEPRECATED;
 
-/** Remove an autoload entry. \since 0.6 */
-pa_operation* pa_context_remove_autoload_by_index(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void* userdata);
+/** Remove an autoload entry. */
+pa_operation* pa_context_remove_autoload_by_index(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void* userdata) PA_GCC_DEPRECATED;
 
-/** Move the specified sink input to a different sink. \since 0.9.5 */
-pa_operation* pa_context_move_sink_input_by_name(pa_context *c, uint32_t idx, char *sink_name, pa_context_success_cb_t cb, void* userdata);
-
-/** Move the specified sink input to a different sink. \since 0.9.5 */
-pa_operation* pa_context_move_sink_input_by_index(pa_context *c, uint32_t idx, uint32_t sink_idx, pa_context_success_cb_t cb, void* userdata);
-
-/** Move the specified source output to a different source. \since 0.9.5 */
-pa_operation* pa_context_move_source_output_by_name(pa_context *c, uint32_t idx, char *source_name, pa_context_success_cb_t cb, void* userdata);
+/** @} */
 
-/** Move the specified source output to a different source. \since 0.9.5 */
-pa_operation* pa_context_move_source_output_by_index(pa_context *c, uint32_t idx, uint32_t source_idx, pa_context_success_cb_t cb, void* userdata);
-
-/** Suspend/Resume a sink. \since 0.9.7 */
-pa_operation* pa_context_suspend_sink_by_name(pa_context *c, char *sink_name, int suspend, pa_context_success_cb_t cb, void* userdata);
-
-/** 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);
-
-/** Suspend/Resume a source. \since 0.9.7 */
-pa_operation* pa_context_suspend_source_by_name(pa_context *c, char *source_name, int suspend, pa_context_success_cb_t cb, void* userdata);
-
-/** Suspend/Resume a source. If idx is PA_INVALID_INDEX all sources will be suspended. \since 0.9.7 */
-pa_operation* pa_context_suspend_source_by_index(pa_context *c, uint32_t idx, int suspend, pa_context_success_cb_t cb, void* userdata);
+/** \endcond */
 
 PA_C_DECL_END
 
index b2ed343..dda5129 100644 (file)
@@ -28,8 +28,8 @@
 #include <stdlib.h>
 
 #include <pulse/xmalloc.h>
+#include <pulse/gccmacro.h>
 
-#include <pulsecore/gccmacro.h>
 #include <pulsecore/macro.h>
 
 #include "mainloop-api.h"
@@ -75,4 +75,3 @@ void pa_mainloop_api_once(pa_mainloop_api* m, void (*callback)(pa_mainloop_api *
     pa_assert_se(e = m->defer_new(m, once_callback, i));
     m->defer_set_destroy(e, free_callback);
 }
-
index e41ed14..91c6bf6 100644 (file)
@@ -3,7 +3,7 @@
 /***
   This file is part of PulseAudio.
 
-  Copyright 2004-2006 Lennart Poettering
+  Copyright 2004-2008 Lennart Poettering
   Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
 
   PulseAudio is free software; you can redistribute it and/or modify
 #endif
 
 #include <pulse/xmalloc.h>
+#include <pulse/gccmacro.h>
 
 #include <pulsecore/core-error.h>
 #include <pulsecore/core-util.h>
 #include <pulsecore/log.h>
-#include <pulsecore/gccmacro.h>
 #include <pulsecore/macro.h>
 
 #include "mainloop-signal.h"
@@ -55,9 +55,9 @@ struct pa_signal_event {
 #else
     void (*saved_handler)(int sig);
 #endif
-    void (*callback) (pa_mainloop_api*a, pa_signal_event *e, int sig, void *userdata);
     void *userdata;
-    void (*destroy_callback) (pa_mainloop_api*a, pa_signal_event*e, void *userdata);
+    pa_signal_cb_t callback;
+    pa_signal_destroy_cb_t destroy_callback;
     pa_signal_event *previous, *next;
 };
 
@@ -74,6 +74,7 @@ static void signal_handler(int sig) {
 #ifndef HAVE_SIGACTION
     signal(sig, signal_handler);
 #endif
+
     pa_write(signal_pipe[1], &sig, sizeof(sig), NULL);
 
     errno = saved_errno;
@@ -142,23 +143,21 @@ int pa_signal_init(pa_mainloop_api *a) {
 }
 
 void pa_signal_done(void) {
-    pa_assert(api);
-    pa_assert(signal_pipe[0] >= 0);
-    pa_assert(signal_pipe[1] >= 0);
-    pa_assert(io_event);
-
     while (signals)
         pa_signal_free(signals);
 
-    api->io_free(io_event);
-    io_event = NULL;
+    if (io_event) {
+        pa_assert(api);
+        api->io_free(io_event);
+        io_event = NULL;
+    }
 
     pa_close_pipe(signal_pipe);
 
     api = NULL;
 }
 
-pa_signal_event* pa_signal_new(int sig, void (*_callback) (pa_mainloop_api *api, pa_signal_event*e, int sig, void *userdata), void *userdata) {
+pa_signal_event* pa_signal_new(int sig, pa_signal_cb_t _callback, void *userdata) {
     pa_signal_event *e = NULL;
 
 #ifdef HAVE_SIGACTION
@@ -223,7 +222,7 @@ void pa_signal_free(pa_signal_event *e) {
     pa_xfree(e);
 }
 
-void pa_signal_set_destroy(pa_signal_event *e, void (*_callback) (pa_mainloop_api *api, pa_signal_event*e, void *userdata)) {
+void pa_signal_set_destroy(pa_signal_event *e, pa_signal_destroy_cb_t _callback) {
     pa_assert(e);
 
     e->destroy_callback = _callback;
index 50aa99c..bdb0f73 100644 (file)
@@ -6,7 +6,7 @@
 /***
   This file is part of PulseAudio.
 
-  Copyright 2004-2006 Lennart Poettering
+  Copyright 2004-2008 Lennart Poettering
   Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
 
   PulseAudio is free software; you can redistribute it and/or modify
@@ -39,23 +39,27 @@ PA_C_DECL_BEGIN
  * signals. However, you may hook signal support into an abstract main loop via the routines defined herein.
  */
 
+/** An opaque UNIX signal event source object */
+typedef struct pa_signal_event pa_signal_event;
+
+typedef void (*pa_signal_cb_t) (pa_mainloop_api *api, pa_signal_event*e, int sig, void *userdata);
+
+typedef void (*pa_signal_destroy_cb_t) (pa_mainloop_api *api, pa_signal_event*e, void *userdata);
+
 /** Initialize the UNIX signal subsystem and bind it to the specified main loop */
 int pa_signal_init(pa_mainloop_api *api);
 
 /** Cleanup the signal subsystem */
 void pa_signal_done(void);
 
-/** An opaque UNIX signal event source object */
-typedef struct pa_signal_event pa_signal_event;
-
 /** Create a new UNIX signal event source object */
-pa_signal_event* pa_signal_new(int sig, void (*callback) (pa_mainloop_api *api, pa_signal_event*e, int sig, void *userdata), void *userdata);
+pa_signal_event* pa_signal_new(int sig, pa_signal_cb_t callback, void *userdata);
 
 /** Free a UNIX signal event source object */
 void pa_signal_free(pa_signal_event *e);
 
 /** Set a function that is called when the signal event source is destroyed. Use this to free the userdata argument if required */
-void pa_signal_set_destroy(pa_signal_event *e, void (*callback) (pa_mainloop_api *api, pa_signal_event*e, void *userdata));
+void pa_signal_set_destroy(pa_signal_event *e, pa_signal_destroy_cb_t callback);
 
 PA_C_DECL_END
 
index 5d2da5b..6b5c142 100644 (file)
@@ -77,6 +77,23 @@ void pa_operation_unref(pa_operation *o) {
     }
 }
 
+static void operation_unlink(pa_operation *o) {
+    pa_assert(o);
+
+    if (o->context) {
+        pa_assert(PA_REFCNT_VALUE(o) >= 2);
+
+        PA_LLIST_REMOVE(pa_operation, o->context->operations, o);
+        pa_operation_unref(o);
+
+        o->context = NULL;
+    }
+
+    o->stream = NULL;
+    o->callback = NULL;
+    o->userdata = NULL;
+}
+
 static void operation_set_state(pa_operation *o, pa_operation_state_t st) {
     pa_assert(o);
     pa_assert(PA_REFCNT_VALUE(o) >= 1);
@@ -88,20 +105,8 @@ static void operation_set_state(pa_operation *o, pa_operation_state_t st) {
 
     o->state = st;
 
-    if ((o->state == PA_OPERATION_DONE) || (o->state == PA_OPERATION_CANCELED)) {
-
-        if (o->context) {
-            pa_assert(PA_REFCNT_VALUE(o) >= 2);
-
-            PA_LLIST_REMOVE(pa_operation, o->context->operations, o);
-            pa_operation_unref(o);
-        }
-
-        o->context = NULL;
-        o->stream = NULL;
-        o->callback = NULL;
-        o->userdata = NULL;
-    }
+    if ((o->state == PA_OPERATION_DONE) || (o->state == PA_OPERATION_CANCELED))
+        operation_unlink(o);
 
     pa_operation_unref(o);
 }
index c27c9d8..33bd274 100644 (file)
@@ -69,16 +69,14 @@ pa_proplist* pa_proplist_new(void) {
 }
 
 void pa_proplist_free(pa_proplist* p) {
-    struct property *prop;
-
-    while ((prop = pa_hashmap_steal_first(MAKE_HASHMAP(p))))
-        property_free(prop);
+    pa_assert(p);
 
+    pa_proplist_clear(p);
     pa_hashmap_free(MAKE_HASHMAP(p), NULL, NULL);
 }
 
 /** Will accept only valid UTF-8 */
-int pa_proplist_puts(pa_proplist *p, const char *key, const char *value) {
+int pa_proplist_sets(pa_proplist *p, const char *key, const char *value) {
     struct property *prop;
     pa_bool_t add = FALSE;
 
@@ -104,7 +102,29 @@ int pa_proplist_puts(pa_proplist *p, const char *key, const char *value) {
     return 0;
 }
 
-int pa_proplist_put(pa_proplist *p, const char *key, const void *data, size_t nbytes) {
+/** Will accept only valid UTF-8 */
+int pa_proplist_setf(pa_proplist *p, const char *key, const char *format, ...) {
+    va_list ap;
+    int r;
+    char *t;
+
+    pa_assert(p);
+    pa_assert(key);
+
+    if (!property_name_valid(key) || !pa_utf8_valid(format))
+        return -1;
+
+    va_start(ap, format);
+    t = pa_vsprintf_malloc(format, ap);
+    va_end(ap);
+
+    r = pa_proplist_sets(p, key, t);
+
+    pa_xfree(t);
+    return r;
+}
+
+int pa_proplist_set(pa_proplist *p, const char *key, const void *data, size_t nbytes) {
     struct property *prop;
     pa_bool_t add = FALSE;
 
@@ -175,18 +195,27 @@ int pa_proplist_get(pa_proplist *p, const char *key, const void **data, size_t *
     return 0;
 }
 
-void pa_proplist_merge(pa_proplist *p, pa_proplist *other) {
+void pa_proplist_update(pa_proplist *p, pa_update_mode_t mode, pa_proplist *other) {
     struct property *prop;
     void *state = NULL;
 
     pa_assert(p);
+    pa_assert(mode == PA_UPDATE_SET || mode == PA_UPDATE_MERGE || mode == PA_UPDATE_REPLACE);
     pa_assert(other);
 
-    while ((prop = pa_hashmap_iterate(MAKE_HASHMAP(other), &state, NULL)))
-        pa_assert_se(pa_proplist_put(p, prop->key, prop->value, prop->nbytes) == 0);
+    if (mode == PA_UPDATE_SET)
+        pa_proplist_clear(p);
+
+    while ((prop = pa_hashmap_iterate(MAKE_HASHMAP(other), &state, NULL))) {
+
+        if (mode == PA_UPDATE_MERGE && pa_proplist_contains(p, prop->key))
+            continue;
+
+        pa_assert_se(pa_proplist_set(p, prop->key, prop->value, prop->nbytes) == 0);
+    }
 }
 
-int pa_proplist_remove(pa_proplist *p, const char *key) {
+int pa_proplist_unset(pa_proplist *p, const char *key) {
     struct property *prop;
 
     pa_assert(p);
@@ -196,12 +225,30 @@ int pa_proplist_remove(pa_proplist *p, const char *key) {
         return -1;
 
     if (!(prop = pa_hashmap_remove(MAKE_HASHMAP(p), key)))
-        return -1;
+        return -2;
 
     property_free(prop);
     return 0;
 }
 
+int pa_proplist_unset_many(pa_proplist *p, const char * const keys[]) {
+    const char * const * k;
+    int n = 0;
+
+    pa_assert(p);
+    pa_assert(keys);
+
+    for (k = keys; *k; k++)
+        if (!property_name_valid(*k))
+            return -1;
+
+    for (k = keys; *k; k++)
+        if (pa_proplist_unset(p, *k) >= 0)
+            n++;
+
+    return n;
+}
+
 const char *pa_proplist_iterate(pa_proplist *p, void **state) {
     struct property *prop;
 
@@ -255,3 +302,22 @@ int pa_proplist_contains(pa_proplist *p, const char *key) {
 
     return 1;
 }
+
+void pa_proplist_clear(pa_proplist *p) {
+    struct property *prop;
+    pa_assert(p);
+
+    while ((prop = pa_hashmap_steal_first(MAKE_HASHMAP(p))))
+        property_free(prop);
+}
+
+pa_proplist* pa_proplist_copy(pa_proplist *template) {
+    pa_proplist *p;
+
+    pa_assert_se(p = pa_proplist_new());
+
+    if (template)
+        pa_proplist_update(p, PA_UPDATE_REPLACE, template);
+
+    return p;
+}
index c4cf9ac..f433ec6 100644 (file)
   USA.
 ***/
 
-#include <pulsecore/macro.h>
+#include <pulse/cdecl.h>
+#include <pulse/gccmacro.h>
+
+PA_C_DECL_BEGIN
 
 /* Defined properties:
  *
- *    x11.xid
- *    x11.display
- *    x11.x_pointer
- *    x11.y_pointer
- *    x11.button
- *    media.name
- *    media.title
- *    media.artist
- *    media.language
+ *    media.name                    "Guns'N'Roses: Civil War"
+ *    media.title                   "Civil War"
+ *    media.artist                  "Guns'N'Roses"
+ *    media.language                "de_DE"
  *    media.filename
  *    media.icon
  *    media.icon_name
- *    media.role                    video, music, game, event, phone, production
- *    application.name
+ *    media.role                    video, music, game, event, phone, production, filter, abstract, stream
+ *    event.id                      button-click, session-login
+ *    event.x11.display
+ *    event.x11.xid
+ *    event.x11.x_pointer
+ *    event.x11.y_pointer
+ *    event.x11.button
+ *    application.name              "Rhythmbox Media Player"
+ *    application.id                "org.gnome.rhythmbox"
  *    application.version
  *    application.icon
  *    application.icon_name
+ *    application.process.id
+ *    application.process.binary
+ *    application.process.user
+ *    application.process.host
+ *    device.string
+ *    device.api                     oss, alsa, sunaudio
+ *    device.description
+ *    device.bus_path
+ *    device.serial
+ *    device.vendor_product_id
+ *    device.class                   sound, modem, monitor, filter
+ *    device.form_factor             laptop-speakers, external-speakers, telephone, tv-capture, webcam-capture, microphone-capture, headset
+ *    device.connector               isa, pci, usb, firewire, bluetooth
+ *    device.access_mode             mmap, mmap_rewrite, serial
+ *    device.master_device
+ *    device.buffer_size
  */
 
-#define PA_PROP_X11_XID                  "x11.xid"
-#define PA_PROP_X11_DISPLAY              "x11.display"
-#define PA_PROP_X11_X_POINTER            "x11.x_pointer"
-#define PA_PROP_X11_Y_POINTER            "x11.y_pointer"
-#define PA_PROP_X11_BUTTON               "x11.button"
-#define PA_PROP_MEDIA_NAME               "media.name"
-#define PA_PROP_MEDIA_TITLE              "media.title"
-#define PA_PROP_MEDIA_ARTIST             "media.artist"
-#define PA_PROP_MEDIA_LANGUAGE           "media.language"
-#define PA_PROP_MEDIA_FILENAME           "media.filename"
-#define PA_PROP_MEDIA_ICON               "media.icon"
-#define PA_PROP_MEDIA_ICON_NAME          "media.icon_name"
-#define PA_PROP_MEDIA_ROLE               "media.role"
-#define PA_PROP_APPLICATION_NAME         "application.name"
-#define PA_PROP_APPLICATION_VERSION      "application.version"
-#define PA_PROP_APPLICATION_ICON         "application.icon"
-#define PA_PROP_APPLICATION_ICON_NAME    "application.icon_name"
-
+#define PA_PROP_MEDIA_NAME                     "media.name"
+#define PA_PROP_MEDIA_TITLE                    "media.title"
+#define PA_PROP_MEDIA_ARTIST                   "media.artist"
+#define PA_PROP_MEDIA_LANGUAGE                 "media.language"
+#define PA_PROP_MEDIA_FILENAME                 "media.filename"
+#define PA_PROP_MEDIA_ICON                     "media.icon"
+#define PA_PROP_MEDIA_ICON_NAME                "media.icon_name"
+#define PA_PROP_MEDIA_ROLE                     "media.role"
+#define PA_PROP_EVENT_ID                       "event.id"
+#define PA_PROP_EVENT_X11_DISPLAY              "event.x11.display"
+#define PA_PROP_EVENT_X11_XID                  "event.x11.xid"
+#define PA_PROP_EVENT_MOUSE_X                  "event.mouse.x"
+#define PA_PROP_EVENT_MOUSE_Y                  "event.mouse.y"
+#define PA_PROP_EVENT_MOUSE_BUTTON             "event.mouse.button"
+#define PA_PROP_APPLICATION_NAME               "application.name"
+#define PA_PROP_APPLICATION_ID                 "application.id"
+#define PA_PROP_APPLICATION_VERSION            "application.version"
+#define PA_PROP_APPLICATION_ICON               "application.icon"
+#define PA_PROP_APPLICATION_ICON_NAME          "application.icon_name"
+#define PA_PROP_APPLICATION_LANGUAGE           "application.language"
+#define PA_PROP_APPLICATION_PROCESS_ID         "application.process.id"
+#define PA_PROP_APPLICATION_PROCESS_BINARY     "application.process.binary"
+#define PA_PROP_APPLICATION_PROCESS_USER       "application.process.user"
+#define PA_PROP_APPLICATION_PROCESS_HOST       "application.process.host"
+#define PA_PROP_DEVICE_STRING                  "device.string"
+#define PA_PROP_DEVICE_API                     "device.api"
+#define PA_PROP_DEVICE_DESCRIPTION             "device.description"
+#define PA_PROP_DEVICE_BUS_PATH                "device.bus_path"
+#define PA_PROP_DEVICE_SERIAL                  "device.serial"
+#define PA_PROP_DEVICE_VENDOR_PRODUCT_ID       "device.vendor_product_id"
+#define PA_PROP_DEVICE_CLASS                   "device.class"
+#define PA_PROP_DEVICE_FORM_FACTOR             "device.form_factor"
+#define PA_PROP_DEVICE_CONNECTOR               "device.connector"
+#define PA_PROP_DEVICE_ACCESS_MODE             "device.access_mode"
+#define PA_PROP_DEVICE_MASTER_DEVICE           "device.master_device"
+#define PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE   "device.buffering.buffer_size"
+#define PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE "device.buffering.fragment_size"
+
+/** A property list object. Basically a dictionary with UTF-8 strings
+ * as keys and arbitrary data as values. \since 0.9.11 */
 typedef struct pa_proplist pa_proplist;
 
+/** Allocate a property list. \since 0.9.11 */
 pa_proplist* pa_proplist_new(void);
-void pa_proplist_free(pa_proplist* p);
 
-/** Will accept only valid UTF-8 */
-int pa_proplist_puts(pa_proplist *p, const char *key, const char *value);
-int pa_proplist_put(pa_proplist *p, const char *key, const void *data, size_t nbytes);
+/** Free the property list. \since 0.9.11 */
+void pa_proplist_free(pa_proplist* p);
 
-/* Will return NULL if the data is not valid UTF-8 */
+/** 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. \since 0.9.11 */
+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 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);
+
+/** Append a new arbitrary data entry to the property list, possibly
+ * overwriting an already existing entry with the same key. An
+ * internal copy of the data passed is made. \since 0.9.11 */
+int pa_proplist_set(pa_proplist *p, const char *key, const void *data, size_t nbytes);
+
+/* Return a string entry for the specified key. Will return NULL if
+ * the data is not valid UTF-8. Will return a NUL-terminated string in
+ * an internally allocated buffer. The caller should make a copy of
+ * the data before accessing the property list again. \since 0.9.11 */
 const char *pa_proplist_gets(pa_proplist *p, const char *key);
-int pa_proplist_get(pa_proplist *p, const char *key, const void **data, size_t *nbytes);
 
-void pa_proplist_merge(pa_proplist *p, pa_proplist *other);
-int pa_proplist_remove(pa_proplist *p, const char *key);
+/** Return the the value for the specified key. Will return a
+ * NUL-terminated string for string entries. The pointer returned will
+ * point to an internally allocated buffer. The caller should make a
+ * copy of the data before the property list is accessed again. \since
+ * 0.9.11 */
+int pa_proplist_get(pa_proplist *p, const char *key, const void **data, size_t *nbytes);
 
+/** 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 any of the old data around */
+    PA_UPDATE_MERGE, /*< Merge new property list into the existing one, not replacing any old entries if they share a common key with the new property list. */
+    PA_UPDATE_REPLACE /*< Merge new property list into the existing one, replacing all old entries that share a common key with  the new property list. */
+} pa_update_mode_t;
+
+/** Merge property list "other" into "p", adhering the merge mode as
+ * specified in "mode". \since 0.9.11 */
+void pa_proplist_update(pa_proplist *p, pa_update_mode_t mode, pa_proplist *other);
+
+/** Removes a single entry from the property list, identified be the
+ * specified key name. \since 0.9.11 */
+int pa_proplist_unset(pa_proplist *p, const char *key);
+
+/** Similar to pa_proplist_remove() but takes an array of keys to
+ * remove. The array should be terminated by a NULL pointer. Return -1
+ * on failure, otherwise the number of entries actually removed (which
+ * might even be 0, if there where no matching entries to
+ * remove). \since 0.9.11 */
+int pa_proplist_unset_many(pa_proplist *p, const char * const keys[]);
+
+/** Iterate through the property list. The user should allocate a
+ * state variable of type void* and initialize it with NULL. A pointer
+ * to this variable should then be passed to pa_proplist_iterate()
+ * which should be called in a loop until it returns NULL which
+ * signifies EOL. The property list should not be modified during
+ * iteration through the list. On each invication this function will
+ * return the key string for the next entry. The keys in the property
+ * list do not have any particular order. \since 0.9.11 */
 const char *pa_proplist_iterate(pa_proplist *p, void **state);
 
+/** Format the property list nicely as a human readable string. Call pa_xfree() on the result. \since
+ * 0.9.11 */
 char *pa_proplist_to_string(pa_proplist *p);
 
+/** Returns 1 if an entry for the specified key is existant in the
+ * property list. \since 0.9.11 */
 int pa_proplist_contains(pa_proplist *p, const char *key);
 
+/** Remove all entries from the property list object. \since 0.9.11 */
+void pa_proplist_clear(pa_proplist *p);
+
+/** Allocate a new property list and copy over every single entry from
+ * the specific list. \since 0.9.11 */
+pa_proplist* pa_proplist_copy(pa_proplist *t);
+
+PA_C_DECL_END
+
 #endif
index 27c0df0..43340f2 100644 (file)
@@ -32,6 +32,7 @@
 
 #include <pulsecore/core-util.h>
 #include <pulsecore/macro.h>
+#include <pulse/timeval.h>
 
 #include "sample.h"
 
@@ -70,13 +71,13 @@ size_t pa_bytes_per_second(const pa_sample_spec *spec) {
 pa_usec_t pa_bytes_to_usec(uint64_t length, const pa_sample_spec *spec) {
     pa_assert(spec);
 
-    return (pa_usec_t) (((double) length/pa_frame_size(spec)*1000000)/spec->rate);
+    return (((pa_usec_t) (length / pa_frame_size(spec)) * PA_USEC_PER_SEC) / spec->rate);
 }
 
 size_t pa_usec_to_bytes(pa_usec_t t, const pa_sample_spec *spec) {
     pa_assert(spec);
 
-    return (size_t) (((double) t * spec->rate / 1000000))*pa_frame_size(spec);
+    return (size_t) (((t * spec->rate) / PA_USEC_PER_SEC)) * pa_frame_size(spec);
 }
 
 int pa_sample_spec_valid(const pa_sample_spec *spec) {
@@ -97,7 +98,10 @@ int pa_sample_spec_equal(const pa_sample_spec*a, const pa_sample_spec*b) {
     pa_assert(a);
     pa_assert(b);
 
-    return (a->format == b->format) && (a->rate == b->rate) && (a->channels == b->channels);
+    return
+        (a->format == b->format) &&
+        (a->rate == b->rate) &&
+        (a->channels == b->channels);
 }
 
 const char *pa_sample_format_to_string(pa_sample_format_t f) {
index f0b839f..dedd72d 100644 (file)
@@ -30,6 +30,7 @@
 #include <sys/param.h>
 #include <math.h>
 
+#include <pulse/gccmacro.h>
 #include <pulse/cdecl.h>
 
 /** \page sample Sample Format Specifications
@@ -172,7 +173,7 @@ typedef struct pa_sample_spec {
     uint8_t channels;              /**< Audio channels. (1 for mono, 2 for stereo, ...) */
 } pa_sample_spec;
 
-/** Type for usec specifications (unsigned). May be either 32 or 64 bit, depending on the architecture */
+/** Type for usec specifications (unsigned). Always 64 bit. */
 typedef uint64_t pa_usec_t;
 
 /** Return the amount of bytes playback of a second of audio with the specified sample type takes */
index 186b0a3..e43a0b9 100644 (file)
@@ -49,12 +49,22 @@ int pa_stream_connect_upload(pa_stream *s, size_t length) {
     pa_stream_ref(s);
 
     s->direction = PA_STREAM_UPLOAD;
+    s->flags = 0;
 
     t = pa_tagstruct_command(s->context, PA_COMMAND_CREATE_UPLOAD_STREAM, &tag);
-    pa_tagstruct_puts(t, s->name);
+
+    if (s->context->version < 13)
+        pa_tagstruct_puts(t, pa_proplist_gets(s->proplist, PA_PROP_MEDIA_NAME));
+
     pa_tagstruct_put_sample_spec(t, &s->sample_spec);
     pa_tagstruct_put_channel_map(t, &s->channel_map);
     pa_tagstruct_putu32(t, length);
+
+    if (s->context->version >= 13) {
+        pa_init_proplist(s->proplist);
+        pa_tagstruct_put_proplist(t, s->proplist);
+    }
+
     pa_pstream_send_tagstruct(s->context->pstream, t);
     pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_create_stream_callback, s, NULL);
 
@@ -85,6 +95,73 @@ int pa_stream_finish_upload(pa_stream *s) {
     return 0;
 }
 
+static void play_sample_ack_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+    pa_operation *o = userdata;
+    int success = 1;
+    uint32_t idx = PA_INVALID_INDEX;
+
+    pa_assert(pd);
+    pa_assert(o);
+    pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+    if (!o->context)
+        goto finish;
+
+    if (command != PA_COMMAND_REPLY) {
+        if (pa_context_handle_error(o->context, command, t, FALSE) < 0)
+            goto finish;
+
+        success = 0;
+    } else if ((o->context->version >= 13 && pa_tagstruct_getu32(t, &idx) < 0) ||
+               !pa_tagstruct_eof(t)) {
+        pa_context_fail(o->context, PA_ERR_PROTOCOL);
+        goto finish;
+    } else if (o->context->version >= 13 && idx == PA_INVALID_INDEX)
+        success = 0;
+
+    if (o->callback) {
+        pa_context_success_cb_t cb = (pa_context_success_cb_t) o->callback;
+        cb(o->context, success, o->userdata);
+    }
+
+finish:
+    pa_operation_done(o);
+    pa_operation_unref(o);
+}
+
+static void play_sample_with_proplist_ack_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+    pa_operation *o = userdata;
+    uint32_t idx;
+
+    pa_assert(pd);
+    pa_assert(o);
+    pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+    if (!o->context)
+        goto finish;
+
+    if (command != PA_COMMAND_REPLY) {
+        if (pa_context_handle_error(o->context, command, t, FALSE) < 0)
+            goto finish;
+
+        idx = PA_INVALID_INDEX;
+    } else if (pa_tagstruct_getu32(t, &idx) < 0 ||
+               !pa_tagstruct_eof(t)) {
+        pa_context_fail(o->context, PA_ERR_PROTOCOL);
+        goto finish;
+    }
+
+    if (o->callback) {
+        pa_context_play_sample_cb_t cb = (pa_context_play_sample_cb_t) o->callback;
+        cb(o->context, idx, o->userdata);
+    }
+
+finish:
+    pa_operation_done(o);
+    pa_operation_unref(o);
+}
+
+
 pa_operation *pa_context_play_sample(pa_context *c, const char *name, const char *dev, pa_volume_t volume, pa_context_success_cb_t cb, void *userdata) {
     pa_operation *o;
     pa_tagstruct *t;
@@ -107,8 +184,47 @@ pa_operation *pa_context_play_sample(pa_context *c, const char *name, const char
     pa_tagstruct_puts(t, dev);
     pa_tagstruct_putu32(t, volume);
     pa_tagstruct_puts(t, name);
+
+    if (c->version >= 13) {
+        pa_proplist *p = pa_proplist_new();
+        pa_tagstruct_put_proplist(t, p);
+        pa_proplist_free(p);
+    }
+
     pa_pstream_send_tagstruct(c->pstream, t);
-    pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+    pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, play_sample_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+    return o;
+}
+
+pa_operation *pa_context_play_sample_with_proplist(pa_context *c, const char *name, const char *dev, pa_volume_t volume, pa_proplist *p, pa_context_play_sample_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, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+    PA_CHECK_VALIDITY_RETURN_NULL(c, name && *name, PA_ERR_INVALID);
+    PA_CHECK_VALIDITY_RETURN_NULL(c, !dev || *dev, PA_ERR_INVALID);
+    PA_CHECK_VALIDITY_RETURN_NULL(c, p, PA_ERR_INVALID);
+    PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 13, PA_ERR_NOTSUPPORTED);
+
+    o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+    if (!dev)
+        dev = c->conf->default_sink;
+
+    t = pa_tagstruct_command(c, PA_COMMAND_PLAY_SAMPLE, &tag);
+    pa_tagstruct_putu32(t, PA_INVALID_INDEX);
+    pa_tagstruct_puts(t, dev);
+    pa_tagstruct_putu32(t, volume);
+    pa_tagstruct_puts(t, name);
+    pa_tagstruct_put_proplist(t, p);
+
+    pa_pstream_send_tagstruct(c->pstream, t);
+    pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, play_sample_with_proplist_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
 
     return o;
 }
@@ -128,9 +244,9 @@ pa_operation* pa_context_remove_sample(pa_context *c, const char *name, pa_conte
 
     t = pa_tagstruct_command(c, PA_COMMAND_REMOVE_SAMPLE, &tag);
     pa_tagstruct_puts(t, name);
+
     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;
 }
-
index 31fd895..46d86a1 100644 (file)
 
 PA_C_DECL_BEGIN
 
+/** Callback prototype for pa_context_play_sample_with_proplist(). The
+ * idx value is the index of the sink input object, or
+ * PA_INVALID_INDEX on failure. \since 0.9.11 */
+typedef void (*pa_context_play_sample_cb_t)(pa_context *c, uint32_t idx, void *userdata);
+
 /** Make this stream a sample upload stream */
 int pa_stream_connect_upload(pa_stream *s, size_t length);
 
-/** Finish the sample upload, the stream name will become the sample name. You cancel a samp
- * le upload by issuing pa_stream_disconnect() */
+/** Finish the sample upload, the stream name will become the sample
+ * name. You cancel a sample upload by issuing
+ * pa_stream_disconnect() */
 int pa_stream_finish_upload(pa_stream *s);
 
-/** Play a sample from the sample cache to the specified device. If the latter is NULL use the default sink. Returns an operation object */
+/** Remove a sample from the sample cache. Returns an operation object which may be used to cancel the operation while it is running */
+pa_operation* pa_context_remove_sample(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata);
+
+/** Play a sample from the sample cache to the specified device. If
+ * the latter is NULL use the default sink. Returns an operation
+ * object */
 pa_operation* pa_context_play_sample(
         pa_context *c               /**< Context */,
         const char *name            /**< Name of the sample to play */,
@@ -95,8 +106,18 @@ pa_operation* pa_context_play_sample(
         pa_context_success_cb_t cb  /**< Call this function after successfully starting playback, or NULL */,
         void *userdata              /**< Userdata to pass to the callback */);
 
-/** Remove a sample from the sample cache. Returns an operation object which may be used to cancel the operation while it is running */
-pa_operation* pa_context_remove_sample(pa_context *c, const char *name, pa_context_success_cb_t, void *userdata);
+/** Play a sample from the sample cache to the specified device,
+ * allowing specification of a property list for the playback
+ * stream. If the latter is NULL use the default sink. Returns an
+ * operation object. \since 0.9.11 */
+pa_operation* pa_context_play_sample_with_proplist(
+        pa_context *c                   /**< Context */,
+        const char *name                /**< Name of the sample to play */,
+        const char *dev                 /**< Sink to play this sample on */,
+        pa_volume_t volume              /**< Volume to play this sample with */ ,
+        pa_proplist *proplist           /**< Property list for this sound. The property list of the cached entry will be merged into this property list */,
+        pa_context_play_sample_cb_t cb  /**< Call this function after successfully starting playback, or NULL */,
+        void *userdata                  /**< Userdata to pass to the callback */);
 
 PA_C_DECL_END
 
index 0ddd57e..7fca6ac 100644 (file)
@@ -138,10 +138,10 @@ int pa_simple_drain(pa_simple *s, int *error);
 /** Read some data from the server */
 int pa_simple_read(pa_simple *s, void*data, size_t bytes, int *error);
 
-/** Return the playback latency. \since 0.5 */
+/** Return the playback latency. */
 pa_usec_t pa_simple_get_latency(pa_simple *s, int *error);
 
-/** Flush the playback buffer. \since 0.5 */
+/** Flush the playback buffer. */
 int pa_simple_flush(pa_simple *s, int *error);
 
 PA_C_DECL_END
index c44323f..4268fd6 100644 (file)
 #include <pulsecore/log.h>
 #include <pulsecore/hashmap.h>
 #include <pulsecore/macro.h>
+#include <pulsecore/rtclock.h>
 
 #include "internal.h"
 
-#define LATENCY_IPOL_INTERVAL_USEC (100000L)
+#define LATENCY_IPOL_INTERVAL_USEC (333*PA_USEC_PER_MSEC)
+
+#define SMOOTHER_ADJUST_TIME (1000*PA_USEC_PER_MSEC)
+#define SMOOTHER_HISTORY_TIME (5000*PA_USEC_PER_MSEC)
+#define SMOOTHER_MIN_HISTORY (4)
 
 pa_stream *pa_stream_new(pa_context *c, const char *name, const pa_sample_spec *ss, const pa_channel_map *map) {
+    return pa_stream_new_with_proplist(c, name, ss, map, NULL);
+}
+
+static void reset_callbacks(pa_stream *s) {
+    s->read_callback = NULL;
+    s->read_userdata = NULL;
+    s->write_callback = NULL;
+    s->write_userdata = NULL;
+    s->state_callback = NULL;
+    s->state_userdata = NULL;
+    s->overflow_callback = NULL;
+    s->overflow_userdata = NULL;
+    s->underflow_callback = NULL;
+    s->underflow_userdata = NULL;
+    s->latency_update_callback = NULL;
+    s->latency_update_userdata = NULL;
+    s->moved_callback = NULL;
+    s->moved_userdata = NULL;
+    s->suspended_callback = NULL;
+    s->suspended_userdata = NULL;
+    s->started_callback = NULL;
+    s->started_userdata = NULL;
+}
+
+pa_stream *pa_stream_new_with_proplist(
+        pa_context *c,
+        const char *name,
+        const pa_sample_spec *ss,
+        const pa_channel_map *map,
+        pa_proplist *p) {
+
     pa_stream *s;
     int i;
     pa_channel_map tmap;
@@ -54,6 +90,7 @@ pa_stream *pa_stream_new(pa_context *c, const char *name, const pa_sample_spec *
     PA_CHECK_VALIDITY_RETURN_NULL(c, ss && pa_sample_spec_valid(ss), PA_ERR_INVALID);
     PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 12 || (ss->format != PA_SAMPLE_S32LE || ss->format != PA_SAMPLE_S32NE), PA_ERR_NOTSUPPORTED);
     PA_CHECK_VALIDITY_RETURN_NULL(c, !map || (pa_channel_map_valid(map) && map->channels == ss->channels), PA_ERR_INVALID);
+    PA_CHECK_VALIDITY_RETURN_NULL(c, name || (p && pa_proplist_contains(p, PA_PROP_MEDIA_NAME)), PA_ERR_INVALID);
 
     if (!map)
         PA_CHECK_VALIDITY_RETURN_NULL(c, map = pa_channel_map_init_auto(&tmap, ss->channels, PA_CHANNEL_MAP_DEFAULT), PA_ERR_INVALID);
@@ -63,67 +100,53 @@ pa_stream *pa_stream_new(pa_context *c, const char *name, const pa_sample_spec *
     s->context = c;
     s->mainloop = c->mainloop;
 
-    s->buffer_attr_not_ready = s->timing_info_not_ready = FALSE;
-
-    s->read_callback = NULL;
-    s->read_userdata = NULL;
-    s->write_callback = NULL;
-    s->write_userdata = NULL;
-    s->state_callback = NULL;
-    s->state_userdata = NULL;
-    s->overflow_callback = NULL;
-    s->overflow_userdata = NULL;
-    s->underflow_callback = NULL;
-    s->underflow_userdata = NULL;
-    s->latency_update_callback = NULL;
-    s->latency_update_userdata = NULL;
-    s->moved_callback = NULL;
-    s->moved_userdata = NULL;
-    s->suspended_callback = NULL;
-    s->suspended_userdata = NULL;
-
     s->direction = PA_STREAM_NODIRECTION;
-    s->name = pa_xstrdup(name);
+    s->state = PA_STREAM_UNCONNECTED;
+    s->flags = 0;
+
     s->sample_spec = *ss;
     s->channel_map = *map;
-    s->flags = 0;
+
+    s->proplist = p ? pa_proplist_copy(p) : pa_proplist_new();
+    if (name)
+        pa_proplist_sets(s->proplist, PA_PROP_MEDIA_NAME, name);
 
     s->channel = 0;
-    s->channel_valid = 0;
+    s->channel_valid = FALSE;
     s->syncid = c->csyncid++;
     s->stream_index = PA_INVALID_INDEX;
-    s->requested_bytes = 0;
-    s->state = PA_STREAM_UNCONNECTED;
 
-    s->manual_buffer_attr = FALSE;
+    s->requested_bytes = 0;
     memset(&s->buffer_attr, 0, sizeof(s->buffer_attr));
 
     s->device_index = PA_INVALID_INDEX;
     s->device_name = NULL;
     s->suspended = FALSE;
 
-    s->peek_memchunk.index = 0;
-    s->peek_memchunk.length = 0;
-    s->peek_memchunk.memblock = 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;
+
     s->previous_time = 0;
-    s->timing_info_valid = 0;
+
     s->read_index_not_before = 0;
     s->write_index_not_before = 0;
-
     for (i = 0; i < PA_MAX_WRITE_INDEX_CORRECTIONS; i++)
         s->write_index_corrections[i].valid = 0;
     s->current_write_index_correction = 0;
 
-    s->corked = 0;
+    s->auto_timing_update_event = NULL;
+    s->auto_timing_update_requested = FALSE;
 
-    s->cached_time_valid = 0;
+    reset_callbacks(s);
 
-    s->auto_timing_update_event = NULL;
-    s->auto_timing_update_requested = 0;
+    s->smoother = NULL;
 
     /* Refcounting is strictly one-way: from the "bigger" to the "smaller" object. */
     PA_LLIST_PREPEND(pa_stream, c->streams, s);
@@ -132,16 +155,51 @@ pa_stream *pa_stream_new(pa_context *c, const char *name, const pa_sample_spec *
     return s;
 }
 
-static void stream_free(pa_stream *s) {
+static void stream_unlink(pa_stream *s) {
+    pa_operation *o, *n;
     pa_assert(s);
-    pa_assert(!s->context);
-    pa_assert(!s->channel_valid);
+
+    if (!s->context)
+        return;
+
+    /* Detach from context */
+
+    /* Unref all operatio object that point to us */
+    for (o = s->context->operations; o; o = n) {
+        n = o->next;
+
+        if (o->stream == s)
+            pa_operation_cancel(o);
+    }
+
+    /* Drop all outstanding replies for this stream */
+    if (s->context->pdispatch)
+        pa_pdispatch_unregister_reply(s->context->pdispatch, s);
+
+    if (s->channel_valid) {
+        pa_dynarray_put((s->direction == PA_STREAM_PLAYBACK) ? s->context->playback_streams : s->context->record_streams, s->channel, NULL);
+        s->channel = 0;
+        s->channel_valid = FALSE;
+    }
+
+    PA_LLIST_REMOVE(pa_stream, s->context->streams, s);
+    pa_stream_unref(s);
+
+    s->context = NULL;
 
     if (s->auto_timing_update_event) {
         pa_assert(s->mainloop);
         s->mainloop->time_free(s->auto_timing_update_event);
     }
 
+    reset_callbacks(s);
+}
+
+static void stream_free(pa_stream *s) {
+    pa_assert(s);
+
+    stream_unlink(s);
+
     if (s->peek_memchunk.memblock) {
         if (s->peek_data)
             pa_memblock_release(s->peek_memchunk.memblock);
@@ -151,7 +209,12 @@ static void stream_free(pa_stream *s) {
     if (s->record_memblockq)
         pa_memblockq_free(s->record_memblockq);
 
-    pa_xfree(s->name);
+    if (s->proplist)
+        pa_proplist_free(s->proplist);
+
+    if (s->smoother)
+        pa_smoother_free(s->smoother);
+
     pa_xfree(s->device_name);
     pa_xfree(s);
 }
@@ -205,46 +268,41 @@ void pa_stream_set_state(pa_stream *s, pa_stream_state_t st) {
     pa_stream_ref(s);
 
     s->state = st;
+
     if (s->state_callback)
         s->state_callback(s, s->state_userdata);
 
-    if ((st == PA_STREAM_FAILED || st == PA_STREAM_TERMINATED) && s->context) {
-
-        /* Detach from context */
-        pa_operation *o, *n;
-
-        /* Unref all operatio object that point to us */
-        for (o = s->context->operations; o; o = n) {
-            n = o->next;
-
-            if (o->stream == s)
-                pa_operation_cancel(o);
-        }
+    if ((st == PA_STREAM_FAILED || st == PA_STREAM_TERMINATED))
+        stream_unlink(s);
 
-        /* Drop all outstanding replies for this stream */
-        if (s->context->pdispatch)
-            pa_pdispatch_unregister_reply(s->context->pdispatch, s);
+    pa_stream_unref(s);
+}
 
-        if (s->channel_valid)
-            pa_dynarray_put((s->direction == PA_STREAM_PLAYBACK) ? s->context->playback_streams : s->context->record_streams, s->channel, NULL);
+static void request_auto_timing_update(pa_stream *s, pa_bool_t force) {
+    pa_assert(s);
+    pa_assert(PA_REFCNT_VALUE(s) >= 1);
 
-        PA_LLIST_REMOVE(pa_stream, s->context->streams, s);
-        pa_stream_unref(s);
+    if (!(s->flags & PA_STREAM_AUTO_TIMING_UPDATE))
+        return;
 
-        s->channel = 0;
-        s->channel_valid = 0;
+    if (s->state == PA_STREAM_READY &&
+        (force || !s->auto_timing_update_requested)) {
+        pa_operation *o;
 
-        s->context = NULL;
+/*         pa_log("automatically requesting new timing data"); */
 
-        s->read_callback = NULL;
-        s->write_callback = NULL;
-        s->state_callback = NULL;
-        s->overflow_callback = NULL;
-        s->underflow_callback = NULL;
-        s->latency_update_callback = NULL;
+        if ((o = pa_stream_update_timing_info(s, NULL, NULL))) {
+            pa_operation_unref(o);
+            s->auto_timing_update_requested = TRUE;
+        }
     }
 
-    pa_stream_unref(s);
+    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);
+    }
 }
 
 void pa_command_stream_killed(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
@@ -269,6 +327,9 @@ void pa_command_stream_killed(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED
     if (!(s = pa_dynarray_get(command == PA_COMMAND_PLAYBACK_STREAM_KILLED ? c->playback_streams : c->record_streams, channel)))
         goto finish;
 
+    if (s->state != PA_STREAM_READY)
+        goto finish;
+
     pa_context_set_error(c, PA_ERR_KILLED);
     pa_stream_set_state(s, PA_STREAM_FAILED);
 
@@ -281,8 +342,10 @@ void pa_command_stream_moved(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED u
     pa_stream *s;
     uint32_t channel;
     const char *dn;
-    int suspended;
+    pa_bool_t suspended;
     uint32_t di;
+    pa_usec_t usec;
+    uint32_t maxlength = 0, fragsize = 0, minreq = 0, tlength = 0, prebuf = 0;
 
     pa_assert(pd);
     pa_assert(command == PA_COMMAND_PLAYBACK_STREAM_MOVED || command == PA_COMMAND_RECORD_STREAM_MOVED);
@@ -300,8 +363,33 @@ void pa_command_stream_moved(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED u
     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_tagstruct_eof(t)) {
+        pa_tagstruct_get_boolean(t, &suspended) < 0) {
+        pa_context_fail(c, PA_ERR_PROTOCOL);
+        goto finish;
+    }
+
+    if (c->version >= 13) {
+
+        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;
     }
@@ -314,12 +402,30 @@ void pa_command_stream_moved(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED u
     if (!(s = pa_dynarray_get(command == PA_COMMAND_PLAYBACK_STREAM_MOVED ? c->playback_streams : c->record_streams, channel)))
         goto finish;
 
+    if (s->state != PA_STREAM_READY)
+        goto finish;
+
+    if (c->version >= 13) {
+        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;
+    }
+
     pa_xfree(s->device_name);
     s->device_name = pa_xstrdup(dn);
     s->device_index = di;
 
     s->suspended = suspended;
 
+    request_auto_timing_update(s, TRUE);
+
     if (s->moved_callback)
         s->moved_callback(s, s->moved_userdata);
 
@@ -331,7 +437,7 @@ void pa_command_stream_suspended(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUS
     pa_context *c = userdata;
     pa_stream *s;
     uint32_t channel;
-    int suspended;
+    pa_bool_t suspended;
 
     pa_assert(pd);
     pa_assert(command == PA_COMMAND_PLAYBACK_STREAM_SUSPENDED || command == PA_COMMAND_RECORD_STREAM_SUSPENDED);
@@ -356,8 +462,23 @@ void pa_command_stream_suspended(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUS
     if (!(s = pa_dynarray_get(command == PA_COMMAND_PLAYBACK_STREAM_SUSPENDED ? c->playback_streams : c->record_streams, channel)))
         goto finish;
 
+    if (s->state != PA_STREAM_READY)
+        goto finish;
+
     s->suspended = suspended;
 
+    if (s->smoother) {
+        pa_usec_t x = pa_rtclock_usec();
+
+        if (s->timing_info_valid)
+            x -= s->timing_info.transport_usec;
+
+        if (s->suspended || s->corked)
+            pa_smoother_pause(s->smoother, x);
+    }
+
+    request_auto_timing_update(s, TRUE);
+
     if (s->suspended_callback)
         s->suspended_callback(s, s->suspended_userdata);
 
@@ -365,6 +486,45 @@ finish:
     pa_context_unref(c);
 }
 
+void pa_command_stream_started(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_assert(pd);
+    pa_assert(command == PA_COMMAND_STARTED);
+    pa_assert(t);
+    pa_assert(c);
+    pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+    pa_context_ref(c);
+
+    if (c->version < 13) {
+        pa_context_fail(c, PA_ERR_PROTOCOL);
+        goto finish;
+    }
+
+    if (pa_tagstruct_getu32(t, &channel) < 0 ||
+        !pa_tagstruct_eof(t)) {
+        pa_context_fail(c, PA_ERR_PROTOCOL);
+        goto finish;
+    }
+
+    if (!(s = pa_dynarray_get(c->playback_streams, channel)))
+        goto finish;
+
+    if (s->state != PA_STREAM_READY)
+        goto finish;
+
+    request_auto_timing_update(s, TRUE);
+
+    if (s->started_callback)
+        s->started_callback(s, s->suspended_userdata);
+
+finish:
+    pa_context_unref(c);
+}
+
 void pa_command_request(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
     pa_stream *s;
     pa_context *c = userdata;
@@ -388,12 +548,13 @@ void pa_command_request(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32
     if (!(s = pa_dynarray_get(c->playback_streams, channel)))
         goto finish;
 
-    if (s->state == PA_STREAM_READY) {
-        s->requested_bytes += bytes;
+    if (s->state != PA_STREAM_READY)
+        goto finish;
 
-        if (s->requested_bytes > 0 && s->write_callback)
-            s->write_callback(s, s->requested_bytes, s->write_userdata);
-    }
+    s->requested_bytes += bytes;
+
+    if (s->requested_bytes > 0 && s->write_callback)
+        s->write_callback(s, s->requested_bytes, s->write_userdata);
 
 finish:
     pa_context_unref(c);
@@ -421,6 +582,21 @@ void pa_command_overflow_or_underflow(pa_pdispatch *pd, uint32_t command, PA_GCC
     if (!(s = pa_dynarray_get(c->playback_streams, channel)))
         goto finish;
 
+    if (s->state != PA_STREAM_READY)
+        goto finish;
+
+    if (s->smoother)
+        if (s->direction == PA_STREAM_PLAYBACK && s->buffer_attr.prebuf > 0) {
+            pa_usec_t x = pa_rtclock_usec();
+
+            if (s->timing_info_valid)
+                x -= s->timing_info.transport_usec;
+
+            pa_smoother_pause(s->smoother, x);
+        }
+
+    request_auto_timing_update(s, TRUE);
+
     if (s->state == PA_STREAM_READY) {
 
         if (command == PA_COMMAND_OVERFLOW) {
@@ -436,34 +612,7 @@ void pa_command_overflow_or_underflow(pa_pdispatch *pd, uint32_t command, PA_GCC
     pa_context_unref(c);
 }
 
-static void request_auto_timing_update(pa_stream *s, int force) {
-    pa_assert(s);
-    pa_assert(PA_REFCNT_VALUE(s) >= 1);
-
-    if (!(s->flags & PA_STREAM_AUTO_TIMING_UPDATE))
-        return;
-
-    if (s->state == PA_STREAM_READY &&
-        (force || !s->auto_timing_update_requested)) {
-        pa_operation *o;
-
-/*         pa_log("automatically requesting new timing data");   */
-
-        if ((o = pa_stream_update_timing_info(s, NULL, NULL))) {
-            pa_operation_unref(o);
-            s->auto_timing_update_requested = 1;
-        }
-    }
-
-    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);
-    }
-}
-
-static void invalidate_indexes(pa_stream *s, int r, int w) {
+static void invalidate_indexes(pa_stream *s, pa_bool_t r, pa_bool_t w) {
     pa_assert(s);
     pa_assert(PA_REFCNT_VALUE(s) >= 1);
 
@@ -490,11 +639,7 @@ static void invalidate_indexes(pa_stream *s, int r, int w) {
 /*         pa_log("read_index invalidated"); */
     }
 
-    if ((s->direction == PA_STREAM_PLAYBACK && r) ||
-        (s->direction == PA_STREAM_RECORD && w))
-        s->cached_time_valid = 0;
-
-    request_auto_timing_update(s, 1);
+    request_auto_timing_update(s, TRUE);
 }
 
 static void auto_timing_update_callback(PA_GCC_UNUSED pa_mainloop_api *m, PA_GCC_UNUSED pa_time_event *e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) {
@@ -503,10 +648,8 @@ static void auto_timing_update_callback(PA_GCC_UNUSED pa_mainloop_api *m, PA_GCC
     pa_assert(s);
     pa_assert(PA_REFCNT_VALUE(s) >= 1);
 
-/*     pa_log("time event");    */
-
     pa_stream_ref(s);
-    request_auto_timing_update(s, 0);
+    request_auto_timing_update(s, FALSE);
     pa_stream_unref(s);
 }
 
@@ -515,9 +658,6 @@ static void create_stream_complete(pa_stream *s) {
     pa_assert(PA_REFCNT_VALUE(s) >= 1);
     pa_assert(s->state == PA_STREAM_CREATING);
 
-    if (s->buffer_attr_not_ready || s->timing_info_not_ready)
-        return;
-
     pa_stream_set_state(s, PA_STREAM_READY);
 
     if (s->requested_bytes > 0 && s->write_callback)
@@ -529,18 +669,36 @@ static void create_stream_complete(pa_stream *s) {
         tv.tv_usec += LATENCY_IPOL_INTERVAL_USEC; /* every 100 ms */
         pa_assert(!s->auto_timing_update_event);
         s->auto_timing_update_event = s->mainloop->time_new(s->mainloop, &tv, &auto_timing_update_callback, s);
+
+        request_auto_timing_update(s, TRUE);
     }
 }
 
-static void automatic_buffer_attr(pa_buffer_attr *attr, pa_sample_spec *ss) {
+static void automatic_buffer_attr(pa_stream *s, pa_buffer_attr *attr, const pa_sample_spec *ss) {
+    pa_assert(s);
     pa_assert(attr);
     pa_assert(ss);
 
-    attr->tlength = pa_bytes_per_second(ss)/2;
-    attr->maxlength = (attr->tlength*3)/2;
-    attr->minreq = attr->tlength/50;
-    attr->prebuf = attr->tlength - attr->minreq;
-    attr->fragsize = attr->tlength/50;
+    if (s->context->version >= 13)
+        return;
+
+    /* Version older than 0.9.10 didn't do server side buffer_attr
+     * selection, hence we have to fake it on the client side */
+
+    if (!attr->maxlength <= 0)
+        attr->maxlength = 4*1024*1024; /* 4MB is the maximum queue length PulseAudio <= 0.9.9 supported. */
+
+    if (!attr->tlength <= 0)
+        attr->tlength = pa_bytes_per_second(ss)*2; /* 2s of buffering */
+
+    if (!attr->minreq <= 0)
+        attr->minreq = (2*attr->tlength)/10; /* Ask for more data when there are only 200ms left in the playback buffer */
+
+    if (!attr->prebuf)
+        attr->prebuf = attr->tlength; /* Start to play only when the playback is fully filled up once */
+
+    if (!attr->fragsize)
+        attr->fragsize  = attr->tlength; /* Pass data to the app only when the buffer is filled up once */
 }
 
 void pa_create_stream_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
@@ -554,7 +712,7 @@ void pa_create_stream_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED
     pa_stream_ref(s);
 
     if (command != PA_COMMAND_REPLY) {
-        if (pa_context_handle_error(s->context, command, t) < 0)
+        if (pa_context_handle_error(s->context, command, t, FALSE) < 0)
             goto finish;
 
         pa_stream_set_state(s, PA_STREAM_FAILED);
@@ -562,7 +720,8 @@ void pa_create_stream_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED
     }
 
     if (pa_tagstruct_getu32(t, &s->channel) < 0 ||
-        ((s->direction != PA_STREAM_UPLOAD) && pa_tagstruct_getu32(t, &s->stream_index) < 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)) {
         pa_context_fail(s->context, PA_ERR_PROTOCOL);
         goto finish;
@@ -590,7 +749,7 @@ void pa_create_stream_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED
         pa_sample_spec ss;
         pa_channel_map cm;
         const char *dn = NULL;
-        int suspended;
+        pa_bool_t suspended;
 
         if (pa_tagstruct_get_sample_spec(t, &ss) < 0 ||
             pa_tagstruct_get_channel_map(t, &cm) < 0 ||
@@ -616,26 +775,22 @@ void pa_create_stream_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED
         s->device_name = pa_xstrdup(dn);
         s->suspended = suspended;
 
-        if (!s->manual_buffer_attr && pa_bytes_per_second(&ss) != pa_bytes_per_second(&s->sample_spec)) {
-            pa_buffer_attr attr;
-            pa_operation *o;
-
-            automatic_buffer_attr(&attr, &ss);
-
-            /* If we need to update the buffer metrics, we wait for
-             * the the OK for that call before we go to
-             * PA_STREAM_READY */
+        s->channel_map = cm;
+        s->sample_spec = ss;
+    }
 
-            s->state = PA_STREAM_READY;
-            pa_assert_se(o = pa_stream_set_buffer_attr(s, &attr, NULL, NULL));
-            pa_operation_unref(o);
-            s->state = PA_STREAM_CREATING;
+    if (s->context->version >= 13 && s->direction != PA_STREAM_UPLOAD) {
+        pa_usec_t usec;
 
-            s->buffer_attr_not_ready = TRUE;
+        if (pa_tagstruct_get_usec(t, &usec) < 0) {
+            pa_context_fail(s->context, PA_ERR_PROTOCOL);
+            goto finish;
         }
 
-        s->channel_map = cm;
-        s->sample_spec = ss;
+        if (s->direction == PA_STREAM_RECORD)
+            s->timing_info.configured_source_usec = usec;
+        else
+            s->timing_info.configured_sink_usec = usec;
     }
 
     if (!pa_tagstruct_eof(t)) {
@@ -653,25 +808,13 @@ void pa_create_stream_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED
                 pa_frame_size(&s->sample_spec),
                 1,
                 0,
+                0,
                 NULL);
     }
 
-    s->channel_valid = 1;
+    s->channel_valid = TRUE;
     pa_dynarray_put((s->direction == PA_STREAM_RECORD) ? s->context->record_streams : s->context->playback_streams, s->channel, s);
 
-    if (s->direction != PA_STREAM_UPLOAD && s->flags & PA_STREAM_AUTO_TIMING_UPDATE) {
-
-        /* If automatic timing updates are active, we wait for the
-         * first timing update before going to PA_STREAM_READY
-         * state */
-
-        s->state = PA_STREAM_READY;
-        request_auto_timing_update(s, 1);
-        s->state = PA_STREAM_CREATING;
-
-        s->timing_info_not_ready = TRUE;
-    }
-
     create_stream_complete(s);
 
 finish:
@@ -692,19 +835,33 @@ static int create_stream(
 
     pa_assert(s);
     pa_assert(PA_REFCNT_VALUE(s) >= 1);
+    pa_assert(direction == PA_STREAM_PLAYBACK || direction == PA_STREAM_RECORD);
 
     PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_UNCONNECTED, PA_ERR_BADSTATE);
-    PA_CHECK_VALIDITY(s->context, !(flags & ~((direction != PA_STREAM_UPLOAD ?
-                                               PA_STREAM_START_CORKED|
-                                               PA_STREAM_INTERPOLATE_TIMING|
-                                               PA_STREAM_NOT_MONOTONOUS|
-                                               PA_STREAM_AUTO_TIMING_UPDATE|
-                                               PA_STREAM_NO_REMAP_CHANNELS|
-                                               PA_STREAM_NO_REMIX_CHANNELS|
-                                               PA_STREAM_FIX_FORMAT|
-                                               PA_STREAM_FIX_RATE|
-                                               PA_STREAM_FIX_CHANNELS|
-                                               PA_STREAM_DONT_MOVE : 0))), PA_ERR_INVALID);
+    PA_CHECK_VALIDITY(s->context, !(flags & ~(PA_STREAM_START_CORKED|
+                                              PA_STREAM_INTERPOLATE_TIMING|
+                                              PA_STREAM_NOT_MONOTONOUS|
+                                              PA_STREAM_AUTO_TIMING_UPDATE|
+                                              PA_STREAM_NO_REMAP_CHANNELS|
+                                              PA_STREAM_NO_REMIX_CHANNELS|
+                                              PA_STREAM_FIX_FORMAT|
+                                              PA_STREAM_FIX_RATE|
+                                              PA_STREAM_FIX_CHANNELS|
+                                              PA_STREAM_DONT_MOVE|
+                                              PA_STREAM_VARIABLE_RATE|
+                                              PA_STREAM_PEAK_DETECT|
+                                              PA_STREAM_START_MUTED|
+                                              PA_STREAM_ADJUST_LATENCY)), PA_ERR_INVALID);
+
+    PA_CHECK_VALIDITY(s->context, s->context->version >= 12 || !(flags & PA_STREAM_VARIABLE_RATE), PA_ERR_NOTSUPPORTED);
+    PA_CHECK_VALIDITY(s->context, s->context->version >= 13 || !(flags & PA_STREAM_PEAK_DETECT), PA_ERR_NOTSUPPORTED);
+    /* 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
+     * client development easier */
+
+    PA_CHECK_VALIDITY(s->context, direction != PA_STREAM_PLAYBACK || !(flags & (PA_STREAM_START_MUTED)), PA_ERR_INVALID);
+    PA_CHECK_VALIDITY(s->context, direction != PA_STREAM_RECORD || !(flags & (PA_STREAM_PEAK_DETECT)), PA_ERR_INVALID);
     PA_CHECK_VALIDITY(s->context, !volume || volume->channels == s->sample_spec.channels, PA_ERR_INVALID);
     PA_CHECK_VALIDITY(s->context, !sync_stream || (direction == PA_STREAM_PLAYBACK && sync_stream->direction == PA_STREAM_PLAYBACK), PA_ERR_INVALID);
 
@@ -716,17 +873,21 @@ static int create_stream(
     if (sync_stream)
         s->syncid = sync_stream->syncid;
 
-    if (attr) {
+    if (attr)
         s->buffer_attr = *attr;
-        s->manual_buffer_attr = TRUE;
-    } else {
-        /* half a second, with minimum request of 10 ms */
-        s->buffer_attr.tlength = pa_bytes_per_second(&s->sample_spec)/2;
-        s->buffer_attr.maxlength = (s->buffer_attr.tlength*3)/2;
-        s->buffer_attr.minreq = s->buffer_attr.tlength/50;
-        s->buffer_attr.prebuf = s->buffer_attr.tlength - s->buffer_attr.minreq;
-        s->buffer_attr.fragsize = s->buffer_attr.tlength/50;
-        s->manual_buffer_attr = FALSE;
+    automatic_buffer_attr(s, &s->buffer_attr, &s->sample_spec);
+
+    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_MONOTONOUS), SMOOTHER_MIN_HISTORY);
+
+        x = pa_rtclock_usec();
+        pa_smoother_set_time_offset(s->smoother, x);
+        pa_smoother_pause(s->smoother, x);
     }
 
     if (!dev)
@@ -737,9 +898,11 @@ static int create_stream(
             s->direction == PA_STREAM_PLAYBACK ? PA_COMMAND_CREATE_PLAYBACK_STREAM : PA_COMMAND_CREATE_RECORD_STREAM,
             &tag);
 
+    if (s->context->version < 13)
+        pa_tagstruct_puts(t, pa_proplist_gets(s->proplist, PA_PROP_MEDIA_NAME));
+
     pa_tagstruct_put(
             t,
-            PA_TAG_STRING, s->name,
             PA_TAG_SAMPLE_SPEC, &s->sample_spec,
             PA_TAG_CHANNEL_MAP, &s->channel_map,
             PA_TAG_U32, PA_INVALID_INDEX,
@@ -766,7 +929,7 @@ static int create_stream(
     } else
         pa_tagstruct_putu32(t, s->buffer_attr.fragsize);
 
-    if (s->context->version >= 12 && s->direction != PA_STREAM_UPLOAD) {
+    if (s->context->version >= 12) {
         pa_tagstruct_put(
                 t,
                 PA_TAG_BOOLEAN, flags & PA_STREAM_NO_REMAP_CHANNELS,
@@ -779,6 +942,20 @@ static int create_stream(
                 PA_TAG_INVALID);
     }
 
+    if (s->context->version >= 13) {
+
+        if (s->direction == PA_STREAM_PLAYBACK)
+            pa_tagstruct_put_boolean(t, flags & PA_STREAM_START_MUTED);
+        else
+            pa_tagstruct_put_boolean(t, flags & PA_STREAM_PEAK_DETECT);
+
+        pa_tagstruct_put(
+                t,
+                PA_TAG_BOOLEAN, flags & PA_STREAM_ADJUST_LATENCY,
+                PA_TAG_PROPLIST, s->proplist,
+                PA_TAG_INVALID);
+    }
+
     pa_pstream_send_tagstruct(s->context->pstream, t);
     pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_create_stream_callback, s, NULL);
 
@@ -823,6 +1000,10 @@ int pa_stream_write(
         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);
@@ -836,21 +1017,42 @@ int pa_stream_write(
     if (length <= 0)
         return 0;
 
-    if (free_cb)
-        chunk.memblock = pa_memblock_new_user(s->context->mempool, (void*) data, length, free_cb, 1);
-    else {
-        void *tdata;
-        chunk.memblock = pa_memblock_new(s->context->mempool, length);
-        tdata = pa_memblock_acquire(chunk.memblock);
-        memcpy(tdata, data, length);
-        pa_memblock_release(chunk.memblock);
-    }
+    t_seek = seek;
+    t_offset = offset;
+    t_length = length;
+    t_data = data;
+
+    while (t_length > 0) {
+
+        chunk.index = 0;
+
+        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;
+
+            chunk.length = PA_MIN(t_length, pa_mempool_block_size_max(s->context->mempool));
+            chunk.memblock = pa_memblock_new(s->context->mempool, chunk.length);
+
+            d = pa_memblock_acquire(chunk.memblock);
+            memcpy(d, t_data, chunk.length);
+            pa_memblock_release(chunk.memblock);
+        }
 
-    chunk.index = 0;
-    chunk.length = length;
+        pa_pstream_send_memblock(s->context->pstream, s->channel, t_offset, t_seek, &chunk);
 
-    pa_pstream_send_memblock(s->context->pstream, s->channel, offset, seek, &chunk);
-    pa_memblock_unref(chunk.memblock);
+        t_offset = 0;
+        t_seek = PA_SEEK_RELATIVE;
+
+        t_data = (const uint8_t*) t_data + chunk.length;
+        t_length -= chunk.length;
+
+        pa_memblock_unref(chunk.memblock);
+    }
+
+    if (free_cb && pa_pstream_get_shm(s->context->pstream))
+        free_cb((void*) data);
 
     if (length < s->requested_bytes)
         s->requested_bytes -= length;
@@ -863,31 +1065,31 @@ int pa_stream_write(
         if (s->write_index_corrections[s->current_write_index_correction].valid) {
 
             if (seek == PA_SEEK_ABSOLUTE) {
-                s->write_index_corrections[s->current_write_index_correction].corrupt = 0;
-                s->write_index_corrections[s->current_write_index_correction].absolute = 1;
+                s->write_index_corrections[s->current_write_index_correction].corrupt = FALSE;
+                s->write_index_corrections[s->current_write_index_correction].absolute = TRUE;
                 s->write_index_corrections[s->current_write_index_correction].value = offset + length;
             } else if (seek == PA_SEEK_RELATIVE) {
                 if (!s->write_index_corrections[s->current_write_index_correction].corrupt)
                     s->write_index_corrections[s->current_write_index_correction].value += offset + length;
             } else
-                s->write_index_corrections[s->current_write_index_correction].corrupt = 1;
+                s->write_index_corrections[s->current_write_index_correction].corrupt = TRUE;
         }
 
         /* Update the write index in the already available latency data */
         if (s->timing_info_valid) {
 
             if (seek == PA_SEEK_ABSOLUTE) {
-                s->timing_info.write_index_corrupt = 0;
+                s->timing_info.write_index_corrupt = FALSE;
                 s->timing_info.write_index = offset + length;
             } else if (seek == PA_SEEK_RELATIVE) {
                 if (!s->timing_info.write_index_corrupt)
                     s->timing_info.write_index += offset + length;
             } else
-                s->timing_info.write_index_corrupt = 1;
+                s->timing_info.write_index_corrupt = TRUE;
         }
 
         if (!s->timing_info_valid || s->timing_info.write_index_corrupt)
-            request_auto_timing_update(s, 1);
+            request_auto_timing_update(s, TRUE);
     }
 
     return 0;
@@ -936,9 +1138,7 @@ int pa_stream_drop(pa_stream *s) {
     pa_assert(s->peek_data);
     pa_memblock_release(s->peek_memchunk.memblock);
     pa_memblock_unref(s->peek_memchunk.memblock);
-    s->peek_memchunk.length = 0;
-    s->peek_memchunk.index = 0;
-    s->peek_memchunk.memblock = NULL;
+    pa_memchunk_reset(&s->peek_memchunk);
 
     return 0;
 }
@@ -984,10 +1184,71 @@ pa_operation * pa_stream_drain(pa_stream *s, pa_stream_success_cb_t cb, void *us
     return o;
 }
 
+static pa_usec_t calc_time(pa_stream *s, pa_bool_t ignore_transport) {
+    pa_usec_t usec;
+
+    pa_assert(s);
+    pa_assert(PA_REFCNT_VALUE(s) >= 1);
+    pa_assert(s->state == PA_STREAM_READY);
+    pa_assert(s->direction != PA_STREAM_UPLOAD);
+    pa_assert(s->timing_info_valid);
+    pa_assert(s->direction != PA_STREAM_PLAYBACK || !s->timing_info.read_index_corrupt);
+    pa_assert(s->direction != PA_STREAM_RECORD || !s->timing_info.write_index_corrupt);
+
+    if (s->direction == PA_STREAM_PLAYBACK) {
+        /* The last byte that was written into the output device
+         * had this time value associated */
+        usec = pa_bytes_to_usec(s->timing_info.read_index < 0 ? 0 : (uint64_t) s->timing_info.read_index, &s->sample_spec);
+
+        if (!s->corked && !s->suspended) {
+
+            if (!ignore_transport)
+                /* Because the latency info took a little time to come
+                 * to us, we assume that the real output time is actually
+                 * a little ahead */
+                usec += s->timing_info.transport_usec;
+
+            /* However, the output device usually maintains a buffer
+               too, hence the real sample currently played is a little
+               back  */
+            if (s->timing_info.sink_usec >= usec)
+                usec = 0;
+            else
+                usec -= s->timing_info.sink_usec;
+        }
+
+    } else if (s->direction == PA_STREAM_RECORD) {
+        /* The last byte written into the server side queue had
+         * this time value associated */
+        usec = pa_bytes_to_usec(s->timing_info.write_index < 0 ? 0 : (uint64_t) s->timing_info.write_index, &s->sample_spec);
+
+        if (!s->corked && !s->suspended) {
+
+            if (!ignore_transport)
+                /* Add transport latency */
+                usec += s->timing_info.transport_usec;
+
+            /* Add latency of data in device buffer */
+            usec += s->timing_info.source_usec;
+
+            /* If this is a monitor source, we need to correct the
+             * time by the playback device buffer */
+            if (s->timing_info.sink_usec >= usec)
+                usec = 0;
+            else
+                usec -= s->timing_info.sink_usec;
+        }
+    }
+
+    return usec;
+}
+
 static void stream_get_timing_info_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
     pa_operation *o = userdata;
     struct timeval local, remote, now;
     pa_timing_info *i;
+    pa_bool_t playing = FALSE;
+    uint64_t underrun_for = 0, playing_for = 0;
 
     pa_assert(pd);
     pa_assert(o);
@@ -1000,29 +1261,48 @@ static void stream_get_timing_info_callback(pa_pdispatch *pd, uint32_t command,
 
 /*     pa_log("pre corrupt w:%u r:%u\n", !o->stream->timing_info_valid || i->write_index_corrupt,!o->stream->timing_info_valid || i->read_index_corrupt); */
 
-    o->stream->timing_info_valid = 0;
-    i->write_index_corrupt = 0;
-    i->read_index_corrupt = 0;
+    o->stream->timing_info_valid = FALSE;
+    i->write_index_corrupt = FALSE;
+    i->read_index_corrupt = FALSE;
 
 /*     pa_log("timing update %u\n", tag); */
 
     if (command != PA_COMMAND_REPLY) {
-        if (pa_context_handle_error(o->context, command, t) < 0)
+        if (pa_context_handle_error(o->context, command, t, FALSE) < 0)
             goto finish;
 
-    } else if (pa_tagstruct_get_usec(t, &i->sink_usec) < 0 ||
-               pa_tagstruct_get_usec(t, &i->source_usec) < 0 ||
-               pa_tagstruct_get_boolean(t, &i->playing) < 0 ||
-               pa_tagstruct_get_timeval(t, &local) < 0 ||
-               pa_tagstruct_get_timeval(t, &remote) < 0 ||
-               pa_tagstruct_gets64(t, &i->write_index) < 0 ||
-               pa_tagstruct_gets64(t, &i->read_index) < 0 ||
-               !pa_tagstruct_eof(t)) {
-        pa_context_fail(o->context, PA_ERR_PROTOCOL);
-        goto finish;
-
     } else {
-        o->stream->timing_info_valid = 1;
+
+        if (pa_tagstruct_get_usec(t, &i->sink_usec) < 0 ||
+            pa_tagstruct_get_usec(t, &i->source_usec) < 0 ||
+            pa_tagstruct_get_boolean(t, &playing) < 0 ||
+            pa_tagstruct_get_timeval(t, &local) < 0 ||
+            pa_tagstruct_get_timeval(t, &remote) < 0 ||
+            pa_tagstruct_gets64(t, &i->write_index) < 0 ||
+            pa_tagstruct_gets64(t, &i->read_index) < 0) {
+
+            pa_context_fail(o->context, PA_ERR_PROTOCOL);
+            goto finish;
+        }
+
+        if (o->context->version >= 13 &&
+            o->stream->direction == PA_STREAM_PLAYBACK)
+            if (pa_tagstruct_getu64(t, &underrun_for) < 0 ||
+                pa_tagstruct_getu64(t, &playing_for) < 0) {
+
+                pa_context_fail(o->context, PA_ERR_PROTOCOL);
+                goto finish;
+            }
+
+
+        if (!pa_tagstruct_eof(t)) {
+            pa_context_fail(o->context, PA_ERR_PROTOCOL);
+            goto finish;
+        }
+
+        o->stream->timing_info_valid = TRUE;
+        i->playing = (int) playing;
+        i->since_underrun = playing ? playing_for : underrun_for;
 
         pa_gettimeofday(&now);
 
@@ -1035,22 +1315,22 @@ static void stream_get_timing_info_callback(pa_pdispatch *pd, uint32_t command,
             else
                 i->transport_usec = pa_timeval_diff(&now, &remote);
 
-            i->synchronized_clocks = 1;
+            i->synchronized_clocks = TRUE;
             i->timestamp = remote;
         } else {
             /* clocks are not synchronized, let's estimate latency then */
             i->transport_usec = pa_timeval_diff(&now, &local)/2;
-            i->synchronized_clocks = 0;
+            i->synchronized_clocks = FALSE;
             i->timestamp = local;
             pa_timeval_add(&i->timestamp, i->transport_usec);
         }
 
         /* Invalidate read and write indexes if necessary */
         if (tag < o->stream->read_index_not_before)
-            i->read_index_corrupt = 1;
+            i->read_index_corrupt = TRUE;
 
         if (tag < o->stream->write_index_not_before)
-            i->write_index_corrupt = 1;
+            i->write_index_corrupt = TRUE;
 
         if (o->stream->direction == PA_STREAM_PLAYBACK) {
             /* Write index correction */
@@ -1076,11 +1356,11 @@ static void stream_get_timing_info_callback(pa_pdispatch *pd, uint32_t command,
                 if (o->stream->write_index_corrections[j].corrupt) {
                     /* A corrupting seek was made */
                     i->write_index = 0;
-                    i->write_index_corrupt = 1;
+                    i->write_index_corrupt = TRUE;
                 } else if (o->stream->write_index_corrections[j].absolute) {
                     /* An absolute seek was made */
                     i->write_index = o->stream->write_index_corrections[j].value;
-                    i->write_index_corrupt = 0;
+                    i->write_index_corrupt = FALSE;
                 } else if (!i->write_index_corrupt) {
                     /* A relative seek was made */
                     i->write_index += o->stream->write_index_corrections[j].value;
@@ -1095,31 +1375,57 @@ static void stream_get_timing_info_callback(pa_pdispatch *pd, uint32_t command,
                 i->read_index -= pa_memblockq_get_length(o->stream->record_memblockq);
         }
 
-        o->stream->cached_time_valid = 0;
-    }
-
-    o->stream->auto_timing_update_requested = 0;
 /*     pa_log("post corrupt w:%u r:%u\n", i->write_index_corrupt || !o->stream->timing_info_valid, i->read_index_corrupt || !o->stream->timing_info_valid); */
 
-    /* Clear old correction entries */
-    if (o->stream->direction == PA_STREAM_PLAYBACK) {
-        int n;
+        /* Clear old correction entries */
+        if (o->stream->direction == PA_STREAM_PLAYBACK) {
+            int n;
 
-        for (n = 0; n < PA_MAX_WRITE_INDEX_CORRECTIONS; n++) {
-            if (!o->stream->write_index_corrections[n].valid)
-                continue;
+            for (n = 0; n < PA_MAX_WRITE_INDEX_CORRECTIONS; n++) {
+                if (!o->stream->write_index_corrections[n].valid)
+                    continue;
 
-            if (o->stream->write_index_corrections[n].tag <= tag)
-                o->stream->write_index_corrections[n].valid = 0;
+                if (o->stream->write_index_corrections[n].tag <= tag)
+                    o->stream->write_index_corrections[n].valid = FALSE;
+            }
         }
-    }
 
-    /* First, let's complete the initialization, if necessary. */
-    if (o->stream->state == PA_STREAM_CREATING) {
-        o->stream->timing_info_not_ready = FALSE;
-        create_stream_complete(o->stream);
+        /* Update smoother */
+        if (o->stream->smoother) {
+            pa_usec_t u, x;
+
+            u = x = pa_rtclock_usec() - i->transport_usec;
+
+            if (o->stream->direction == PA_STREAM_PLAYBACK &&
+                o->context->version >= 13) {
+                pa_usec_t su;
+
+                /* If we weren't playing then it will take some time
+                 * until the audio will actually come out through the
+                 * speakers. Since we follow that timing here, we need
+                 * to try to fix this up */
+
+                su = pa_bytes_to_usec(i->since_underrun, &o->stream->sample_spec);
+
+                if (su < i->sink_usec)
+                    x += i->sink_usec - su;
+            }
+
+            if (!i->playing)
+                pa_smoother_pause(o->stream->smoother, x);
+
+            /* Update the smoother */
+            if ((o->stream->direction == PA_STREAM_PLAYBACK && !i->read_index_corrupt) ||
+                (o->stream->direction == PA_STREAM_RECORD && !i->write_index_corrupt))
+                pa_smoother_put(o->stream->smoother, u, calc_time(o->stream, TRUE));
+
+            if (i->playing)
+                pa_smoother_resume(o->stream->smoother, x);
+        }
     }
 
+    o->stream->auto_timing_update_requested = FALSE;
+
     if (o->stream->latency_update_callback)
         o->stream->latency_update_callback(o->stream, o->stream->latency_update_userdata);
 
@@ -1168,15 +1474,15 @@ pa_operation* pa_stream_update_timing_info(pa_stream *s, pa_stream_success_cb_t
 
     if (s->direction == PA_STREAM_PLAYBACK) {
         /* Fill in initial correction data */
-        o->stream->current_write_index_correction = cidx;
-        o->stream->write_index_corrections[cidx].valid = 1;
-        o->stream->write_index_corrections[cidx].tag = tag;
-        o->stream->write_index_corrections[cidx].absolute = 0;
-        o->stream->write_index_corrections[cidx].value = 0;
-        o->stream->write_index_corrections[cidx].corrupt = 0;
-    }
 
-/*     pa_log("requesting update %u\n", tag); */
+        s->current_write_index_correction = cidx;
+
+        s->write_index_corrections[cidx].valid = TRUE;
+        s->write_index_corrections[cidx].absolute = FALSE;
+        s->write_index_corrections[cidx].corrupt = FALSE;
+        s->write_index_corrections[cidx].tag = tag;
+        s->write_index_corrections[cidx].value = 0;
+    }
 
     return o;
 }
@@ -1191,7 +1497,7 @@ void pa_stream_disconnect_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UN
     pa_stream_ref(s);
 
     if (command != PA_COMMAND_REPLY) {
-        if (pa_context_handle_error(s->context, command, t) < 0)
+        if (pa_context_handle_error(s->context, command, t, FALSE) < 0)
             goto finish;
 
         pa_stream_set_state(s, PA_STREAM_FAILED);
@@ -1236,6 +1542,9 @@ void pa_stream_set_read_callback(pa_stream *s, pa_stream_request_cb_t cb, void *
     pa_assert(s);
     pa_assert(PA_REFCNT_VALUE(s) >= 1);
 
+    if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED)
+        return;
+
     s->read_callback = cb;
     s->read_userdata = userdata;
 }
@@ -1244,6 +1553,9 @@ void pa_stream_set_write_callback(pa_stream *s, pa_stream_request_cb_t cb, void
     pa_assert(s);
     pa_assert(PA_REFCNT_VALUE(s) >= 1);
 
+    if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED)
+        return;
+
     s->write_callback = cb;
     s->write_userdata = userdata;
 }
@@ -1252,6 +1564,9 @@ void pa_stream_set_state_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *
     pa_assert(s);
     pa_assert(PA_REFCNT_VALUE(s) >= 1);
 
+    if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED)
+        return;
+
     s->state_callback = cb;
     s->state_userdata = userdata;
 }
@@ -1260,6 +1575,9 @@ void pa_stream_set_overflow_callback(pa_stream *s, pa_stream_notify_cb_t cb, voi
     pa_assert(s);
     pa_assert(PA_REFCNT_VALUE(s) >= 1);
 
+    if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED)
+        return;
+
     s->overflow_callback = cb;
     s->overflow_userdata = userdata;
 }
@@ -1268,6 +1586,9 @@ void pa_stream_set_underflow_callback(pa_stream *s, pa_stream_notify_cb_t cb, vo
     pa_assert(s);
     pa_assert(PA_REFCNT_VALUE(s) >= 1);
 
+    if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED)
+        return;
+
     s->underflow_callback = cb;
     s->underflow_userdata = userdata;
 }
@@ -1276,6 +1597,9 @@ void pa_stream_set_latency_update_callback(pa_stream *s, pa_stream_notify_cb_t c
     pa_assert(s);
     pa_assert(PA_REFCNT_VALUE(s) >= 1);
 
+    if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED)
+        return;
+
     s->latency_update_callback = cb;
     s->latency_update_userdata = userdata;
 }
@@ -1284,6 +1608,9 @@ void pa_stream_set_moved_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *
     pa_assert(s);
     pa_assert(PA_REFCNT_VALUE(s) >= 1);
 
+    if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED)
+        return;
+
     s->moved_callback = cb;
     s->moved_userdata = userdata;
 }
@@ -1292,10 +1619,24 @@ void pa_stream_set_suspended_callback(pa_stream *s, pa_stream_notify_cb_t cb, vo
     pa_assert(s);
     pa_assert(PA_REFCNT_VALUE(s) >= 1);
 
+    if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED)
+        return;
+
     s->suspended_callback = cb;
     s->suspended_userdata = userdata;
 }
 
+void pa_stream_set_started_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) {
+    pa_assert(s);
+    pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+    if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED)
+        return;
+
+    s->started_callback = cb;
+    s->started_userdata = userdata;
+}
+
 void pa_stream_simple_ack_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
     pa_operation *o = userdata;
     int success = 1;
@@ -1308,7 +1649,7 @@ void pa_stream_simple_ack_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UN
         goto finish;
 
     if (command != PA_COMMAND_REPLY) {
-        if (pa_context_handle_error(o->context, command, t) < 0)
+        if (pa_context_handle_error(o->context, command, t, FALSE) < 0)
             goto finish;
 
         success = 0;
@@ -1351,8 +1692,18 @@ pa_operation* pa_stream_cork(pa_stream *s, int b, pa_stream_success_cb_t cb, voi
     pa_pstream_send_tagstruct(s->context->pstream, t);
     pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
 
+    if (s->smoother) {
+        pa_usec_t x = pa_rtclock_usec();
+
+        if (s->timing_info_valid)
+            x += s->timing_info.transport_usec;
+
+        if (s->suspended || s->corked)
+            pa_smoother_pause(s->smoother, x);
+    }
+
     if (s->direction == PA_STREAM_PLAYBACK)
-        invalidate_indexes(s, 1, 0);
+        invalidate_indexes(s, TRUE, FALSE);
 
     return o;
 }
@@ -1383,23 +1734,34 @@ pa_operation* pa_stream_flush(pa_stream *s, pa_stream_success_cb_t cb, void *use
     pa_assert(s);
     pa_assert(PA_REFCNT_VALUE(s) >= 1);
 
+    PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
     PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
 
     if ((o = stream_send_simple_command(s, s->direction == PA_STREAM_PLAYBACK ? PA_COMMAND_FLUSH_PLAYBACK_STREAM : PA_COMMAND_FLUSH_RECORD_STREAM, cb, userdata))) {
 
         if (s->direction == PA_STREAM_PLAYBACK) {
             if (s->write_index_corrections[s->current_write_index_correction].valid)
-                s->write_index_corrections[s->current_write_index_correction].corrupt = 1;
+                s->write_index_corrections[s->current_write_index_correction].corrupt = TRUE;
 
             if (s->timing_info_valid)
-                s->timing_info.write_index_corrupt = 1;
+                s->timing_info.write_index_corrupt = TRUE;
 
             if (s->buffer_attr.prebuf > 0)
-                invalidate_indexes(s, 1, 0);
+                invalidate_indexes(s, TRUE, FALSE);
             else
-                request_auto_timing_update(s, 1);
+                request_auto_timing_update(s, TRUE);
+
+            if (s->smoother && s->buffer_attr.prebuf > 0) {
+                pa_usec_t x = pa_rtclock_usec();
+
+                if (s->timing_info_valid)
+                    x += s->timing_info.transport_usec;
+
+                pa_smoother_pause(s->smoother, x);
+            }
+
         } else
-            invalidate_indexes(s, 0, 1);
+            invalidate_indexes(s, FALSE, TRUE);
     }
 
     return o;
@@ -1411,11 +1773,12 @@ pa_operation* pa_stream_prebuf(pa_stream *s, pa_stream_success_cb_t cb, void *us
     pa_assert(s);
     pa_assert(PA_REFCNT_VALUE(s) >= 1);
 
+    PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
     PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE);
     PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->buffer_attr.prebuf > 0, PA_ERR_BADSTATE);
 
     if ((o = stream_send_simple_command(s, PA_COMMAND_PREBUF_PLAYBACK_STREAM, cb, userdata)))
-        invalidate_indexes(s, 1, 0);
+        invalidate_indexes(s, TRUE, FALSE);
 
     return o;
 }
@@ -1426,19 +1789,18 @@ pa_operation* pa_stream_trigger(pa_stream *s, pa_stream_success_cb_t cb, void *u
     pa_assert(s);
     pa_assert(PA_REFCNT_VALUE(s) >= 1);
 
+    PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
     PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE);
     PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->buffer_attr.prebuf > 0, PA_ERR_BADSTATE);
 
     if ((o = stream_send_simple_command(s, PA_COMMAND_TRIGGER_PLAYBACK_STREAM, cb, userdata)))
-        invalidate_indexes(s, 1, 0);
+        invalidate_indexes(s, TRUE, FALSE);
 
     return o;
 }
 
 pa_operation* pa_stream_set_name(pa_stream *s, const char *name, pa_stream_success_cb_t cb, void *userdata) {
     pa_operation *o;
-    pa_tagstruct *t;
-    uint32_t tag;
 
     pa_assert(s);
     pa_assert(PA_REFCNT_VALUE(s) >= 1);
@@ -1447,22 +1809,32 @@ pa_operation* pa_stream_set_name(pa_stream *s, const char *name, pa_stream_succe
     PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
     PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
 
-    o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata);
+    if (s->context->version >= 13) {
+        pa_proplist *p = pa_proplist_new();
 
-    t = pa_tagstruct_command(
-            s->context,
-            s->direction == PA_STREAM_RECORD ? PA_COMMAND_SET_RECORD_STREAM_NAME : PA_COMMAND_SET_PLAYBACK_STREAM_NAME,
-            &tag);
-    pa_tagstruct_putu32(t, s->channel);
-    pa_tagstruct_puts(t, name);
-    pa_pstream_send_tagstruct(s->context->pstream, t);
-    pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+        pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, name);
+        o = pa_stream_proplist_update(s, PA_UPDATE_REPLACE, p, cb, userdata);
+        pa_proplist_free(p);
+    } else {
+        pa_tagstruct *t;
+        uint32_t tag;
+
+        o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata);
+        t = pa_tagstruct_command(
+                s->context,
+                s->direction == PA_STREAM_RECORD ? PA_COMMAND_SET_RECORD_STREAM_NAME : PA_COMMAND_SET_PLAYBACK_STREAM_NAME,
+                &tag);
+        pa_tagstruct_putu32(t, s->channel);
+        pa_tagstruct_puts(t, name);
+        pa_pstream_send_tagstruct(s->context->pstream, t);
+        pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+    }
 
     return o;
 }
 
 int pa_stream_get_time(pa_stream *s, pa_usec_t *r_usec) {
-    pa_usec_t usec = 0;
+    pa_usec_t usec;
 
     pa_assert(s);
     pa_assert(PA_REFCNT_VALUE(s) >= 1);
@@ -1473,65 +1845,10 @@ int pa_stream_get_time(pa_stream *s, pa_usec_t *r_usec) {
     PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_PLAYBACK || !s->timing_info.read_index_corrupt, PA_ERR_NODATA);
     PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_RECORD || !s->timing_info.write_index_corrupt, PA_ERR_NODATA);
 
-    if (s->cached_time_valid)
-        /* We alredy calculated the time value for this timing info, so let's reuse it */
-        usec = s->cached_time;
-    else {
-        if (s->direction == PA_STREAM_PLAYBACK) {
-            /* The last byte that was written into the output device
-             * had this time value associated */
-            usec = pa_bytes_to_usec(s->timing_info.read_index < 0 ? 0 : (uint64_t) s->timing_info.read_index, &s->sample_spec);
-
-            if (!s->corked) {
-                /* Because the latency info took a little time to come
-                 * to us, we assume that the real output time is actually
-                 * a little ahead */
-                usec += s->timing_info.transport_usec;
-
-                /* However, the output device usually maintains a buffer
-                   too, hence the real sample currently played is a little
-                   back  */
-                if (s->timing_info.sink_usec >= usec)
-                    usec = 0;
-                else
-                    usec -= s->timing_info.sink_usec;
-            }
-
-        } else if (s->direction == PA_STREAM_RECORD) {
-            /* The last byte written into the server side queue had
-             * this time value associated */
-            usec = pa_bytes_to_usec(s->timing_info.write_index < 0 ? 0 : (uint64_t) s->timing_info.write_index, &s->sample_spec);
-
-            if (!s->corked) {
-                /* Add transport latency */
-                usec += s->timing_info.transport_usec;
-
-                /* Add latency of data in device buffer */
-                usec += s->timing_info.source_usec;
-
-                /* If this is a monitor source, we need to correct the
-                 * time by the playback device buffer */
-                if (s->timing_info.sink_usec >= usec)
-                    usec = 0;
-                else
-                    usec -= s->timing_info.sink_usec;
-            }
-        }
-
-        s->cached_time = usec;
-        s->cached_time_valid = 1;
-    }
-
-    /* Interpolate if requested */
-    if (s->flags & PA_STREAM_INTERPOLATE_TIMING) {
-
-        /* We just add the time that passed since the latency info was
-         * current */
-        if (!s->corked && s->timing_info.playing) {
-            struct timeval now;
-            usec += pa_timeval_diff(pa_gettimeofday(&now), &s->timing_info.timestamp);
-        }
-    }
+    if (s->smoother)
+        usec = pa_smoother_get(s->smoother, pa_rtclock_usec());
+    else
+        usec = calc_time(s, FALSE);
 
     /* Make sure the time runs monotonically */
     if (!(s->flags & PA_STREAM_NOT_MONOTONOUS)) {
@@ -1632,7 +1949,7 @@ const pa_buffer_attr* pa_stream_get_buffer_attr(pa_stream *s) {
 
     PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
     PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
-    PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->context->version >= 9, PA_ERR_NODATA);
+    PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->context->version >= 9, PA_ERR_NOTSUPPORTED);
 
     return &s->buffer_attr;
 }
@@ -1649,7 +1966,7 @@ static void stream_set_buffer_attr_callback(pa_pdispatch *pd, uint32_t command,
         goto finish;
 
     if (command != PA_COMMAND_REPLY) {
-        if (pa_context_handle_error(o->context, command, t) < 0)
+        if (pa_context_handle_error(o->context, command, t, FALSE) < 0)
             goto finish;
 
         success = 0;
@@ -1675,13 +1992,6 @@ static void stream_set_buffer_attr_callback(pa_pdispatch *pd, uint32_t command,
             pa_context_fail(o->context, PA_ERR_PROTOCOL);
             goto finish;
         }
-
-        o->stream->manual_buffer_attr = TRUE;
-    }
-
-    if (o->stream->state == PA_STREAM_CREATING) {
-        o->stream->buffer_attr_not_ready = FALSE;
-        create_stream_complete(o->stream);
     }
 
     if (o->callback) {
@@ -1728,6 +2038,9 @@ pa_operation* pa_stream_set_buffer_attr(pa_stream *s, const pa_buffer_attr *attr
     else
         pa_tagstruct_putu32(t, attr->fragsize);
 
+    if (s->context->version >= 13)
+        pa_tagstruct_put_boolean(t, !!(s->flags & PA_STREAM_ADJUST_LATENCY));
+
     pa_pstream_send_tagstruct(s->context->pstream, t);
     pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, stream_set_buffer_attr_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
 
@@ -1769,6 +2082,16 @@ int pa_stream_is_suspended(pa_stream *s) {
     return s->suspended;
 }
 
+int pa_stream_is_corked(pa_stream *s) {
+    pa_assert(s);
+    pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+    PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
+    PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
+
+    return s->corked;
+}
+
 static void stream_update_sample_rate_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
     pa_operation *o = userdata;
     int success = 1;
@@ -1781,7 +2104,7 @@ static void stream_update_sample_rate_callback(pa_pdispatch *pd, uint32_t comman
         goto finish;
 
     if (command != PA_COMMAND_REPLY) {
-        if (pa_context_handle_error(o->context, command, t) < 0)
+        if (pa_context_handle_error(o->context, command, t, FALSE) < 0)
             goto finish;
 
         success = 0;
@@ -1835,5 +2158,72 @@ pa_operation *pa_stream_update_sample_rate(pa_stream *s, uint32_t rate, pa_strea
     pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, stream_update_sample_rate_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
 
     return o;
+}
+
+pa_operation *pa_stream_proplist_update(pa_stream *s, pa_update_mode_t mode, pa_proplist *p, pa_stream_success_cb_t cb, void *userdata) {
+    pa_operation *o;
+    pa_tagstruct *t;
+    uint32_t tag;
+
+    pa_assert(s);
+    pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+    PA_CHECK_VALIDITY_RETURN_NULL(s->context, mode == PA_UPDATE_SET || mode == PA_UPDATE_MERGE || mode == PA_UPDATE_REPLACE, PA_ERR_INVALID);
+    PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
+    PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
+    PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->context->version >= 13, PA_ERR_NOTSUPPORTED);
+
+    o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata);
+
+    t = pa_tagstruct_command(
+            s->context,
+            s->direction == PA_STREAM_RECORD ? PA_COMMAND_UPDATE_RECORD_STREAM_PROPLIST : PA_COMMAND_UPDATE_PLAYBACK_STREAM_PROPLIST,
+            &tag);
+    pa_tagstruct_putu32(t, s->channel);
+    pa_tagstruct_putu32(t, (uint32_t) mode);
+    pa_tagstruct_put_proplist(t, p);
+
+    pa_pstream_send_tagstruct(s->context->pstream, t);
+    pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+    /* Please note that we don't update s->proplist here, because we
+     * don't export that field */
+
+    return o;
+}
+
+pa_operation *pa_stream_proplist_remove(pa_stream *s, const char *const keys[], pa_stream_success_cb_t cb, void *userdata) {
+    pa_operation *o;
+    pa_tagstruct *t;
+    uint32_t tag;
+    const char * const*k;
+
+    pa_assert(s);
+    pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+    PA_CHECK_VALIDITY_RETURN_NULL(s->context, keys && keys[0], PA_ERR_INVALID);
+    PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
+    PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
+    PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->context->version >= 13, PA_ERR_NOTSUPPORTED);
 
+    o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata);
+
+    t = pa_tagstruct_command(
+            s->context,
+            s->direction == PA_STREAM_RECORD ? PA_COMMAND_REMOVE_RECORD_STREAM_PROPLIST : PA_COMMAND_REMOVE_PLAYBACK_STREAM_PROPLIST,
+            &tag);
+    pa_tagstruct_putu32(t, s->channel);
+
+    for (k = keys; *k; k++)
+        pa_tagstruct_puts(t, *k);
+
+    pa_tagstruct_puts(t, NULL);
+
+    pa_pstream_send_tagstruct(s->context->pstream, t);
+    pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+    /* Please note that we don't update s->proplist here, because we
+     * don't export that field */
+
+    return o;
 }
index 8547322..ebb45f2 100644 (file)
@@ -276,13 +276,25 @@ typedef void (*pa_stream_request_cb_t)(pa_stream *p, size_t bytes, void *userdat
 /** A generic notification callback */
 typedef void (*pa_stream_notify_cb_t)(pa_stream *p, void *userdata);
 
-/** Create a new, unconnected stream with the specified name and sample type */
+/** Create a new, unconnected stream with the specified name and
+ * sample type. It is recommended to use pa_stream_new_with_proplist()
+ * instead and specify some initial properties. */
 pa_stream* pa_stream_new(
         pa_context *c                     /**< The context to create this stream in */,
         const char *name                  /**< A name for this stream */,
         const pa_sample_spec *ss          /**< The desired sample format */,
         const pa_channel_map *map         /**< The desired channel map, or NULL for default */);
 
+/** Create a new, unconnected stream with the specified name and
+ * sample type, and specify the the initial stream property
+ * list. \since 0.9.11 */
+pa_stream* pa_stream_new_with_proplist(
+        pa_context *c                     /**< The context to create this stream in */,
+        const char *name                  /**< A name for this stream */,
+        const pa_sample_spec *ss          /**< The desired sample format */,
+        const pa_channel_map *map         /**< The desired channel map, or NULL for default */,
+        pa_proplist *p                    /**< The initial property list */);
+
 /** Decrease the reference counter by one */
 void pa_stream_unref(pa_stream *s);
 
@@ -327,6 +339,10 @@ const char *pa_stream_get_device_name(pa_stream *s);
  * server is older than 0.9.8. \since 0.9.8 */
 int pa_stream_is_suspended(pa_stream *s);
 
+/** Return 1 if the this stream has been corked. This will return 0 if
+ * not, and negative on error. \since 0.9.11 */
+int pa_stream_is_corked(pa_stream *s);
+
 /** Connect the stream to a sink */
 int pa_stream_connect_playback(
         pa_stream *s                  /**< The stream to connect to a sink */,
@@ -356,7 +372,7 @@ int pa_stream_disconnect(pa_stream *s);
 int pa_stream_write(
         pa_stream *p             /**< The stream to use */,
         const void *data         /**< The data to write */,
-        size_t bytes             /**< The length of the data to write in bytes*/,
+        size_t nbytes            /**< The length of the data to write in bytes*/,
         pa_free_cb_t free_cb     /**< A cleanup routine for the data or NULL to request an internal copy */,
         int64_t offset,          /**< Offset for seeking, must be 0 for upload streams */
         pa_seek_mode_t seek      /**< Seek mode, must be PA_SEEK_RELATIVE for upload streams */);
@@ -365,20 +381,20 @@ int pa_stream_write(
  * 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
- * buffer. If no data is available will return a NULL pointer  \since 0.8 */
+ * buffer. If no data is available will return a NULL pointer */
 int pa_stream_peek(
         pa_stream *p                 /**< The stream to use */,
         const void **data            /**< Pointer to pointer that will point to data */,
-        size_t *bytes                /**< The length of the data read in bytes */);
+        size_t *nbytes               /**< The length of the data read in bytes */);
 
 /** Remove the current fragment on record streams. It is invalid to do this without first
- * calling pa_stream_peek(). \since 0.8 */
+ * calling pa_stream_peek(). */
 int pa_stream_drop(pa_stream *p);
 
 /** Return the number of bytes that may be written using pa_stream_write() */
 size_t pa_stream_writable_size(pa_stream *p);
 
-/** Return the number of bytes that may be read using pa_stream_read() \since 0.8 */
+/** Return the number of bytes that may be read using pa_stream_read()*/
 size_t pa_stream_readable_size(pa_stream *p);
 
 /** Drain a playback stream. Use this for notification when the buffer is empty */
@@ -398,18 +414,25 @@ void pa_stream_set_state_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *
 void pa_stream_set_write_callback(pa_stream *p, pa_stream_request_cb_t cb, void *userdata);
 
 /** Set the callback function that is called when new data is available from the stream.
- * Return the number of bytes read. \since 0.8 */
+ * Return the number of bytes read.*/
 void pa_stream_set_read_callback(pa_stream *p, pa_stream_request_cb_t cb, void *userdata);
 
-/** Set the callback function that is called when a buffer overflow happens. (Only for playback streams) \since 0.8 */
+/** Set the callback function that is called when a buffer overflow happens. (Only for playback streams) */
 void pa_stream_set_overflow_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata);
 
-/** Set the callback function that is called when a buffer underflow happens. (Only for playback streams) \since 0.8 */
+/** Set the callback function that is called when a buffer underflow happens. (Only for playback streams) */
 void pa_stream_set_underflow_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata);
 
+/** Set the callback function that is called when a the server starts
+ * playback after an underrun or on initial startup. This only informs
+ * that audio is flowing again, it is no indication that audio startet
+ * to reach the speakers already. (Only for playback streams). \since
+ * 0.9.11 */
+void pa_stream_set_started_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata);
+
 /** Set the callback function that is called whenever a latency
  * information update happens. Useful on PA_STREAM_AUTO_TIMING_UPDATE
- * streams only. (Only for playback streams) \since 0.8.2 */
+ * streams only. (Only for playback streams) */
 void pa_stream_set_latency_update_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata);
 
 /** Set the callback function that is called whenever the stream is
@@ -429,24 +452,25 @@ void pa_stream_set_moved_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *
  * 0.9.8. \since 0.9.8 */
 void pa_stream_set_suspended_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. \since 0.3 */
+/** Pause (or resume) playback of this stream temporarily. Available on both playback and recording streams. */
 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
- * better off using the parameter delta of pa_stream_write() instead of this
- * function. Available on both playback and recording streams. \since 0.3 */
+ * better off using the parameter delta of pa_stream_write() instead
+ * of this function. Available on both playback and recording
+ * streams. */
 pa_operation* pa_stream_flush(pa_stream *s, pa_stream_success_cb_t cb, void *userdata);
 
 /** Reenable prebuffering as specified in the pa_buffer_attr
- * structure. Available for playback streams only. \since 0.6 */
+ * structure. Available for playback streams only. */
 pa_operation* pa_stream_prebuf(pa_stream *s, pa_stream_success_cb_t cb, void *userdata);
 
 /** Request immediate start of playback on this stream. This disables
- * prebuffering as specified in the pa_buffer_attr
- * structure, temporarily. Available for playback streams only. \since 0.3 */
+ * prebuffering as specified in the pa_buffer_attr structure,
+ * 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. \since 0.5 */
+/** 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
@@ -463,13 +487,13 @@ pa_operation* pa_stream_set_name(pa_stream *s, const char *name, pa_stream_succe
  * be disabled by using PA_STREAM_NOT_MONOTONOUS. 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'. \since 0.6 */
+ * able to deal with time going 'backwards'. */
 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. \since 0.6 */
+ * played. In this case *negative is set to 1. */
 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
@@ -481,13 +505,13 @@ int pa_stream_get_latency(pa_stream *s, pa_usec_t *r_usec, int *negative);
  * 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. \since 0.8 */
+ * recieved. */
 const pa_timing_info* pa_stream_get_timing_info(pa_stream *s);
 
-/** Return a pointer to the stream's sample specification. \since 0.6 */
+/** Return a pointer to the stream's sample specification. */
 const pa_sample_spec* pa_stream_get_sample_spec(pa_stream *s);
 
-/** Return a pointer to the stream's channel map. \since 0.8 */
+/** Return a pointer to the stream's channel map. */
 const pa_channel_map* pa_stream_get_channel_map(pa_stream *s);
 
 /** Return the buffer metrics of the stream. Only valid after the
@@ -510,6 +534,18 @@ pa_operation *pa_stream_set_buffer_attr(pa_stream *s, const pa_buffer_attr *attr
  * is at least PulseAudio 0.9.8. \since 0.9.8 */
 pa_operation *pa_stream_update_sample_rate(pa_stream *s, uint32_t rate, pa_stream_success_cb_t cb, void *userdata);
 
+/* Update the property list of the sink input/source output of this
+ * stream, adding new entries. Please note that it is highly
+ * recommended to set as much properties initially via
+ * pa_stream_new_with_proplist() as possible instead a posteriori with
+ * this function, since that information may then be used to route
+ * this stream to the right device. \since 0.9.11 */
+pa_operation *pa_stream_proplist_update(pa_stream *s, pa_update_mode_t mode, pa_proplist *p, pa_stream_success_cb_t cb, void *userdata);
+
+/* Update the property list of the sink input/source output of this
+ * stream, remove entries. \since 0.9.11 */
+pa_operation *pa_stream_proplist_remove(pa_stream *s, const char *const keys[], pa_stream_success_cb_t cb, void *userdata);
+
 PA_C_DECL_END
 
 #endif
index 580038c..0c5686b 100644 (file)
@@ -27,7 +27,8 @@
 
 #include <stdio.h>
 
-#include <pulsecore/gccmacro.h>
+#include <pulse/gccmacro.h>
+
 #include <pulsecore/macro.h>
 #include <pulsecore/pstream-util.h>
 
@@ -87,6 +88,9 @@ void pa_context_set_subscribe_callback(pa_context *c, pa_context_subscribe_cb_t
     pa_assert(c);
     pa_assert(PA_REFCNT_VALUE(c) >= 1);
 
+    if (c->state == PA_CONTEXT_TERMINATED || c->state == PA_CONTEXT_FAILED)
+        return;
+
     c->subscribe_callback = cb;
     c->subscribe_userdata = userdata;
 }
index 70ceb71..180e015 100644 (file)
@@ -148,6 +148,24 @@ struct timeval* pa_timeval_add(struct timeval *tv, pa_usec_t v) {
     return tv;
 }
 
+struct timeval* pa_timeval_sub(struct timeval *tv, pa_usec_t v) {
+    unsigned long secs;
+    pa_assert(tv);
+
+    secs = (unsigned long) (v/PA_USEC_PER_SEC);
+    tv->tv_sec -= secs;
+    v -= ((pa_usec_t) secs) * PA_USEC_PER_SEC;
+
+    if (tv->tv_usec >= (suseconds_t) v)
+        tv->tv_usec -= (suseconds_t) v;
+    else {
+        tv->tv_sec --;
+        tv->tv_usec = tv->tv_usec + PA_USEC_PER_SEC - v;
+    }
+
+    return tv;
+}
+
 struct timeval* pa_timeval_store(struct timeval *tv, pa_usec_t v) {
     pa_assert(tv);
 
index 65a0e51..09d5397 100644 (file)
@@ -26,6 +26,7 @@
 ***/
 
 #include <pulse/cdecl.h>
+#include <pulse/gccmacro.h>
 #include <pulse/sample.h>
 
 /** \file
@@ -37,6 +38,8 @@ PA_C_DECL_BEGIN
 #define PA_USEC_PER_SEC 1000000
 #define PA_NSEC_PER_SEC 1000000000
 #define PA_USEC_PER_MSEC 1000
+#define PA_NSEC_PER_MSEC 1000000
+#define PA_NSEC_PER_USEC 1000
 
 struct timeval;
 
@@ -54,7 +57,10 @@ int pa_timeval_cmp(const struct timeval *a, const struct timeval *b) PA_GCC_PURE
 pa_usec_t pa_timeval_age(const struct timeval *tv);
 
 /** Add the specified time inmicroseconds to the specified timeval structure */
-struct timeval* pa_timeval_add(struct timeval *tv, pa_usec_t v) PA_GCC_PURE;
+struct timeval* pa_timeval_add(struct timeval *tv, pa_usec_t v);
+
+/** Subtract the specified time inmicroseconds to the specified timeval structure. \since 0.9.11 */
+struct timeval* pa_timeval_sub(struct timeval *tv, pa_usec_t v);
 
 /** Store the specified uec value in the timeval struct. \since 0.9.7 */
 struct timeval* pa_timeval_store(struct timeval *tv, pa_usec_t v);
index 1e08047..840c74e 100644 (file)
@@ -26,6 +26,7 @@
 ***/
 
 #include <pulse/cdecl.h>
+#include <pulse/gccmacro.h>
 
 /** \file
  * UTF8 Validation functions
index 764678e..666ccce 100644 (file)
@@ -28,6 +28,7 @@
 #include <stddef.h>
 
 #include <pulse/cdecl.h>
+#include <pulse/gccmacro.h>
 
 /** \file
  * Assorted utility functions */
index 20c7a9c..dc0f8e3 100644 (file)
@@ -8,17 +8,17 @@
 
   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 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
@@ -39,7 +39,8 @@ a macro and not a function, so it is impossible to get the pointer of
 it. */
 #define pa_get_headers_version() ("@PACKAGE_VERSION@")
 
-/** Return the version of the library the current application is linked to. */
+/** Return the version of the library the current application is
+ * linked to. */
 const char* pa_get_library_version(void);
 
 /** The current API version. Version 6 relates to Polypaudio
@@ -47,8 +48,8 @@ const char* pa_get_library_version(void);
  * PA_API_VERSION undefined.  */
 #define PA_API_VERSION @PA_API_VERSION@
 
-/** The current protocol version. Version 8 relates to Polypaudio 0.8/PulseAudio 0.9.
- * \since 0.8 */
+/** The current protocol version. Version 8 relates to Polypaudio
+ * 0.8/PulseAudio 0.9. */
 #define PA_PROTOCOL_VERSION @PA_PROTOCOL_VERSION@
 
 PA_C_DECL_END
index 3688b84..33ab1c5 100644 (file)
@@ -80,10 +80,10 @@ pa_volume_t pa_sw_volume_multiply(pa_volume_t a, pa_volume_t b) {
     return pa_sw_volume_from_linear(pa_sw_volume_to_linear(a)* pa_sw_volume_to_linear(b));
 }
 
-#define USER_DECIBEL_RANGE 30
+#define USER_DECIBEL_RANGE 60
 
 pa_volume_t pa_sw_volume_from_dB(double dB) {
-    if (dB <= -USER_DECIBEL_RANGE)
+    if (isinf(dB) < 0 || dB <= -USER_DECIBEL_RANGE)
         return PA_VOLUME_MUTED;
 
     return (pa_volume_t) ((dB/USER_DECIBEL_RANGE+1)*PA_VOLUME_NORM);
index 22e5b8a..e7ceb0d 100644 (file)
@@ -26,7 +26,9 @@
 ***/
 
 #include <inttypes.h>
+
 #include <pulse/cdecl.h>
+#include <pulse/gccmacro.h>
 #include <pulse/sample.h>
 
 /** \page volume Volume Control
@@ -101,10 +103,10 @@ PA_C_DECL_BEGIN
 typedef uint32_t pa_volume_t;
 
 /** Normal volume (100%) */
-#define PA_VOLUME_NORM (0x10000)
+#define PA_VOLUME_NORM ((pa_volume_t) 0x10000)
 
 /** Muted volume (0%) */
-#define PA_VOLUME_MUTED (0)
+#define PA_VOLUME_MUTED ((pa_volume_t) 0)
 
 /** A structure encapsulating a per-channel volume */
 typedef struct pa_cvolume {
@@ -149,25 +151,25 @@ int pa_cvolume_channels_equal_to(const pa_cvolume *a, pa_volume_t v) PA_GCC_PURE
 pa_volume_t pa_sw_volume_multiply(pa_volume_t a, pa_volume_t b) PA_GCC_CONST;
 
 /** Multiply to per-channel volumes and return the result in *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) PA_GCC_PURE;
+pa_cvolume *pa_sw_cvolume_multiply(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! \since 0.4 */
+/** Convert a decibel value to a volume. 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! \since 0.4 */
+/** Convert a volume to a decibel value. 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! \since 0.8 */
+/** Convert a linear factor to a volume. This is only valid for software volumes! */
 pa_volume_t pa_sw_volume_from_linear(double v) PA_GCC_CONST;
 
-/** Convert a volume to a linear factor. This is only valid for software volumes! \since 0.8 */
+/** Convert a volume to a linear factor. This is only valid for software volumes! */
 double pa_sw_volume_to_linear(pa_volume_t v) PA_GCC_CONST;
 
 #ifdef INFINITY
-#define PA_DECIBEL_MININFTY (-INFINITY)
+#define PA_DECIBEL_MININFTY ((double) -INFINITY)
 #else
-/** This value is used as minus infinity when using pa_volume_{to,from}_dB(). \since 0.4 */
-#define PA_DECIBEL_MININFTY (-200)
+/** This value is used as minus infinity when using pa_volume_{to,from}_dB(). */
+#define PA_DECIBEL_MININFTY ((double) -200)
 #endif
 
 PA_C_DECL_END
index 5348dda..a64761b 100644 (file)
 #include <signal.h>
 #include <unistd.h>
 #include <string.h>
+#include <errno.h>
 
+#include <pulse/gccmacro.h>
 #include <pulsecore/core-util.h>
-#include <pulsecore/gccmacro.h>
 #include <pulsecore/macro.h>
 
 #include "xmalloc.h"
@@ -123,8 +124,12 @@ char *pa_xstrndup(const char *s, size_t l) {
 }
 
 void pa_xfree(void *p) {
+    int saved_errno;
+
     if (!p)
         return;
 
+    saved_errno = errno;
     free(p);
+    errno = saved_errno;
 }
index 96b43a7..eba1c2c 100644 (file)
@@ -136,7 +136,7 @@ void pa_asyncmsgq_post(pa_asyncmsgq *a, pa_msgobject *object, int code, const vo
 
     /* This mutex makes the queue multiple-writer safe. This lock is only used on the writing side */
     pa_mutex_lock(a->mutex);
-    pa_assert_se(pa_asyncq_push(a->asyncq, i, 1) == 0);
+    pa_asyncq_post(a->asyncq, i);
     pa_mutex_unlock(a->mutex);
 }
 
@@ -163,7 +163,7 @@ int pa_asyncmsgq_send(pa_asyncmsgq *a, pa_msgobject *object, int code, const voi
 
     /* Thus mutex makes the queue multiple-writer safe. This lock is only used on the writing side */
     pa_mutex_lock(a->mutex);
-    pa_assert_se(pa_asyncq_push(a->asyncq, &i, 1) == 0);
+    pa_assert_se(pa_asyncq_push(a->asyncq, &i, TRUE) == 0);
     pa_mutex_unlock(a->mutex);
 
     pa_semaphore_wait(i.semaphore);
@@ -174,7 +174,7 @@ int pa_asyncmsgq_send(pa_asyncmsgq *a, pa_msgobject *object, int code, const voi
     return i.ret;
 }
 
-int pa_asyncmsgq_get(pa_asyncmsgq *a, pa_msgobject **object, int *code, void **userdata, int64_t *offset, pa_memchunk *chunk, int wait) {
+int pa_asyncmsgq_get(pa_asyncmsgq *a, pa_msgobject **object, int *code, void **userdata, int64_t *offset, pa_memchunk *chunk, pa_bool_t wait) {
     pa_assert(PA_REFCNT_VALUE(a) > 0);
     pa_assert(!a->current);
 
@@ -276,22 +276,40 @@ int pa_asyncmsgq_process_one(pa_asyncmsgq *a) {
     return 1;
 }
 
-int pa_asyncmsgq_get_fd(pa_asyncmsgq *a) {
+int pa_asyncmsgq_read_fd(pa_asyncmsgq *a) {
     pa_assert(PA_REFCNT_VALUE(a) > 0);
 
-    return pa_asyncq_get_fd(a->asyncq);
+    return pa_asyncq_read_fd(a->asyncq);
 }
 
-int pa_asyncmsgq_before_poll(pa_asyncmsgq *a) {
+int pa_asyncmsgq_read_before_poll(pa_asyncmsgq *a) {
     pa_assert(PA_REFCNT_VALUE(a) > 0);
 
-    return pa_asyncq_before_poll(a->asyncq);
+    return pa_asyncq_read_before_poll(a->asyncq);
 }
 
-void pa_asyncmsgq_after_poll(pa_asyncmsgq *a) {
+void pa_asyncmsgq_read_after_poll(pa_asyncmsgq *a) {
     pa_assert(PA_REFCNT_VALUE(a) > 0);
 
-    pa_asyncq_after_poll(a->asyncq);
+    pa_asyncq_read_after_poll(a->asyncq);
+}
+
+int pa_asyncmsgq_write_fd(pa_asyncmsgq *a) {
+    pa_assert(PA_REFCNT_VALUE(a) > 0);
+
+    return pa_asyncq_write_fd(a->asyncq);
+}
+
+void pa_asyncmsgq_write_before_poll(pa_asyncmsgq *a) {
+    pa_assert(PA_REFCNT_VALUE(a) > 0);
+
+    pa_asyncq_write_before_poll(a->asyncq);
+}
+
+void pa_asyncmsgq_write_after_poll(pa_asyncmsgq *a) {
+    pa_assert(PA_REFCNT_VALUE(a) > 0);
+
+    pa_asyncq_write_after_poll(a->asyncq);
 }
 
 int pa_asyncmsgq_dispatch(pa_msgobject *object, int code, void *userdata, int64_t offset, pa_memchunk *memchunk) {
index 5d3867b..93f1ce8 100644 (file)
@@ -56,20 +56,26 @@ typedef struct pa_asyncmsgq pa_asyncmsgq;
 
 pa_asyncmsgq* pa_asyncmsgq_new(unsigned size);
 pa_asyncmsgq* pa_asyncmsgq_ref(pa_asyncmsgq *q);
+
 void pa_asyncmsgq_unref(pa_asyncmsgq* q);
 
 void pa_asyncmsgq_post(pa_asyncmsgq *q, pa_msgobject *object, int code, const void *userdata, int64_t offset, const pa_memchunk *memchunk, pa_free_cb_t userdata_free_cb);
 int pa_asyncmsgq_send(pa_asyncmsgq *q, pa_msgobject *object, int code, const void *userdata, int64_t offset, const pa_memchunk *memchunk);
 
-int pa_asyncmsgq_get(pa_asyncmsgq *q, pa_msgobject **object, int *code, void **userdata, int64_t *offset, pa_memchunk *memchunk, int wait);
+int pa_asyncmsgq_get(pa_asyncmsgq *q, pa_msgobject **object, int *code, void **userdata, int64_t *offset, pa_memchunk *memchunk, pa_bool_t wait);
 int pa_asyncmsgq_dispatch(pa_msgobject *object, int code, void *userdata, int64_t offset, pa_memchunk *memchunk);
 void pa_asyncmsgq_done(pa_asyncmsgq *q, int ret);
 int pa_asyncmsgq_wait_for(pa_asyncmsgq *a, int code);
 int pa_asyncmsgq_process_one(pa_asyncmsgq *a);
 
-/* Just for the reading side */
-int pa_asyncmsgq_get_fd(pa_asyncmsgq *q);
-int pa_asyncmsgq_before_poll(pa_asyncmsgq *a);
-void pa_asyncmsgq_after_poll(pa_asyncmsgq *a);
+/* For the reading side */
+int pa_asyncmsgq_read_fd(pa_asyncmsgq *q);
+int pa_asyncmsgq_read_before_poll(pa_asyncmsgq *a);
+void pa_asyncmsgq_read_after_poll(pa_asyncmsgq *a);
+
+/* For the write side */
+int pa_asyncmsgq_write_fd(pa_asyncmsgq *q);
+void pa_asyncmsgq_write_before_poll(pa_asyncmsgq *a);
+void pa_asyncmsgq_write_after_poll(pa_asyncmsgq *a);
 
 #endif
index 75b15c0..8e0dfbc 100644 (file)
@@ -3,7 +3,7 @@
 /***
   This file is part of PulseAudio.
 
-  Copyright 2006 Lennart Poettering
+  Copyright 2006-2008 Lennart Poettering
 
   PulseAudio is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as
 #include <pulsecore/thread.h>
 #include <pulsecore/macro.h>
 #include <pulsecore/core-util.h>
+#include <pulsecore/llist.h>
+#include <pulsecore/flist.h>
 #include <pulse/xmalloc.h>
 
 #include "asyncq.h"
 #include "fdsem.h"
 
-#define ASYNCQ_SIZE 128
+#define ASYNCQ_SIZE 256
 
-/* For debugging purposes we can define _Y to put and extra thread
+/* For debugging purposes we can define _Y to put an extra thread
  * yield between each operation. */
 
 /* #define PROFILE */
 #define _Y do { } while(0)
 #endif
 
+struct localq {
+    void *data;
+    PA_LLIST_FIELDS(struct localq);
+};
+
 struct pa_asyncq {
     unsigned size;
     unsigned read_idx;
     unsigned write_idx;
     pa_fdsem *read_fdsem, *write_fdsem;
+
+    PA_LLIST_HEAD(struct localq, localq);
+    struct localq *last_localq;
+    pa_bool_t waiting_for_post;
 };
 
-#define PA_ASYNCQ_CELLS(x) ((pa_atomic_ptr_t*) ((uint8_t*) (x) + PA_ALIGN(sizeof(struct pa_asyncq))))
+PA_STATIC_FLIST_DECLARE(localq, 0, pa_xfree);
 
-static int is_power_of_two(unsigned size) {
-    return !(size & (size - 1));
-}
+#define PA_ASYNCQ_CELLS(x) ((pa_atomic_ptr_t*) ((uint8_t*) (x) + PA_ALIGN(sizeof(struct pa_asyncq))))
 
 static int reduce(pa_asyncq *l, int value) {
     return value & (unsigned) (l->size - 1);
@@ -74,12 +83,16 @@ pa_asyncq *pa_asyncq_new(unsigned size) {
     if (!size)
         size = ASYNCQ_SIZE;
 
-    pa_assert(is_power_of_two(size));
+    pa_assert(pa_is_power_of_two(size));
 
     l = pa_xmalloc0(PA_ALIGN(sizeof(pa_asyncq)) + (sizeof(pa_atomic_ptr_t) * size));
 
     l->size = size;
 
+    PA_LLIST_HEAD_INIT(struct localq, l->localq);
+    l->last_localq = NULL;
+    l->waiting_for_post = FALSE;
+
     if (!(l->read_fdsem = pa_fdsem_new())) {
         pa_xfree(l);
         return NULL;
@@ -95,6 +108,7 @@ pa_asyncq *pa_asyncq_new(unsigned size) {
 }
 
 void pa_asyncq_free(pa_asyncq *l, pa_free_cb_t free_cb) {
+    struct localq *q;
     pa_assert(l);
 
     if (free_cb) {
@@ -104,12 +118,22 @@ void pa_asyncq_free(pa_asyncq *l, pa_free_cb_t free_cb) {
             free_cb(p);
     }
 
+    while ((q = l->localq)) {
+        if (free_cb)
+            free_cb(q->data);
+
+        PA_LLIST_REMOVE(struct localq, l->localq, q);
+
+        if (pa_flist_push(PA_STATIC_FLIST_GET(localq), q) < 0)
+            pa_xfree(q);
+    }
+
     pa_fdsem_free(l->read_fdsem);
     pa_fdsem_free(l->write_fdsem);
     pa_xfree(l);
 }
 
-int pa_asyncq_push(pa_asyncq*l, void *p, int wait) {
+static int push(pa_asyncq*l, void *p, pa_bool_t wait) {
     int idx;
     pa_atomic_ptr_t *cells;
 
@@ -141,7 +165,63 @@ int pa_asyncq_push(pa_asyncq*l, void *p, int wait) {
     return 0;
 }
 
-void* pa_asyncq_pop(pa_asyncq*l, int wait) {
+static pa_bool_t flush_postq(pa_asyncq *l) {
+    struct localq *q;
+
+    pa_assert(l);
+
+    while ((q = l->last_localq)) {
+
+        if (push(l, q->data, FALSE) < 0)
+            return FALSE;
+
+        l->last_localq = q->prev;
+
+        PA_LLIST_REMOVE(struct localq, l->localq, q);
+
+        if (pa_flist_push(PA_STATIC_FLIST_GET(localq), q) < 0)
+            pa_xfree(q);
+    }
+
+    return TRUE;
+}
+
+int pa_asyncq_push(pa_asyncq*l, void *p, pa_bool_t wait) {
+    pa_assert(l);
+
+    if (!flush_postq(l))
+        return -1;
+
+    return push(l, p, wait);
+}
+
+void pa_asyncq_post(pa_asyncq*l, void *p) {
+    struct localq *q;
+
+    pa_assert(l);
+    pa_assert(p);
+
+    if (pa_asyncq_push(l, p, FALSE) >= 0)
+        return;
+
+    /* OK, we couldn't push anything in the queue. So let's queue it
+     * locally and push it later */
+
+    pa_log("q overrun, queuing locally");
+
+    if (!(q = pa_flist_pop(PA_STATIC_FLIST_GET(localq))))
+        q = pa_xnew(struct localq, 1);
+
+    q->data = p;
+    PA_LLIST_PREPEND(struct localq, l->localq, q);
+
+    if (!l->last_localq)
+        l->last_localq = q;
+
+    return;
+}
+
+void* pa_asyncq_pop(pa_asyncq*l, pa_bool_t wait) {
     int idx;
     void *ret;
     pa_atomic_ptr_t *cells;
@@ -178,13 +258,13 @@ void* pa_asyncq_pop(pa_asyncq*l, int wait) {
     return ret;
 }
 
-int pa_asyncq_get_fd(pa_asyncq *q) {
+int pa_asyncq_read_fd(pa_asyncq *q) {
     pa_assert(q);
 
     return pa_fdsem_get(q->write_fdsem);
 }
 
-int pa_asyncq_before_poll(pa_asyncq *l) {
+int pa_asyncq_read_before_poll(pa_asyncq *l) {
     int idx;
     pa_atomic_ptr_t *cells;
 
@@ -206,8 +286,38 @@ int pa_asyncq_before_poll(pa_asyncq *l) {
     return 0;
 }
 
-void pa_asyncq_after_poll(pa_asyncq *l) {
+void pa_asyncq_read_after_poll(pa_asyncq *l) {
     pa_assert(l);
 
     pa_fdsem_after_poll(l->write_fdsem);
 }
+
+int pa_asyncq_write_fd(pa_asyncq *q) {
+    pa_assert(q);
+
+    return pa_fdsem_get(q->read_fdsem);
+}
+
+void pa_asyncq_write_before_poll(pa_asyncq *l) {
+    pa_assert(l);
+
+    for (;;) {
+
+        if (flush_postq(l))
+            break;
+
+        if (pa_fdsem_before_poll(l->read_fdsem) >= 0) {
+            l->waiting_for_post = TRUE;
+            break;
+        }
+    }
+}
+
+void pa_asyncq_write_after_poll(pa_asyncq *l) {
+    pa_assert(l);
+
+    if (l->waiting_for_post) {
+        pa_fdsem_after_poll(l->read_fdsem);
+        l->waiting_for_post = FALSE;
+    }
+}
index 53d4586..4cdf8cd 100644 (file)
@@ -26,6 +26,7 @@
 
 #include <sys/types.h>
 #include <pulse/def.h>
+#include <pulsecore/macro.h>
 
 /* A simple, asynchronous, lock-free (if requested also wait-free)
  * queue. Not multiple-reader/multiple-writer safe. If that is
@@ -46,11 +47,21 @@ typedef struct pa_asyncq pa_asyncq;
 pa_asyncq* pa_asyncq_new(unsigned size);
 void pa_asyncq_free(pa_asyncq* q, pa_free_cb_t free_cb);
 
-void* pa_asyncq_pop(pa_asyncq *q, int wait);
-int pa_asyncq_push(pa_asyncq *q, void *p, int wait);
+void* pa_asyncq_pop(pa_asyncq *q, pa_bool_t wait);
+int pa_asyncq_push(pa_asyncq *q, void *p, pa_bool_t wait);
 
-int pa_asyncq_get_fd(pa_asyncq *q);
-int pa_asyncq_before_poll(pa_asyncq *a);
-void pa_asyncq_after_poll(pa_asyncq *a);
+/* Similar to pa_asyncq_push(), but if the queue is full, postpone it
+ * locally and delay until pa_asyncq_before_poll_post() */
+void pa_asyncq_post(pa_asyncq*l, void *p);
+
+/* For the reading side */
+int pa_asyncq_read_fd(pa_asyncq *q);
+int pa_asyncq_read_before_poll(pa_asyncq *a);
+void pa_asyncq_read_after_poll(pa_asyncq *a);
+
+/* For the writing side */
+int pa_asyncq_write_fd(pa_asyncq *q);
+void pa_asyncq_write_before_poll(pa_asyncq *a);
+void pa_asyncq_write_after_poll(pa_asyncq *a);
 
 #endif
index 423c3f2..925a2e8 100644 (file)
@@ -90,6 +90,7 @@ static int pa_cli_command_stat(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_b
 static int pa_cli_command_info(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
 static int pa_cli_command_load(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
 static int pa_cli_command_unload(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_describe(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
 static int pa_cli_command_sink_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
 static int pa_cli_command_sink_input_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
 static int pa_cli_command_source_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
@@ -136,6 +137,7 @@ static const struct command commands[] = {
     { "list",                    pa_cli_command_info,               NULL,                           1 },
     { "load-module",             pa_cli_command_load,               "Load a module (args: name, arguments)", 3},
     { "unload-module",           pa_cli_command_unload,             "Unload a module (args: index)", 2},
+    { "describe-module",         pa_cli_command_describe,           "Describe a module (arg: name)", 2},
     { "set-sink-volume",         pa_cli_command_sink_volume,        "Set the volume of a sink (args: index|name, volume)", 3},
     { "set-sink-input-volume",   pa_cli_command_sink_input_volume,  "Set the volume of a sink input (args: index, volume)", 3},
     { "set-source-volume",       pa_cli_command_source_volume,      "Set the volume of a source (args: index|name, volume)", 3},
@@ -155,10 +157,10 @@ static const struct command commands[] = {
     { "load-sample-dir-lazy",    pa_cli_command_scache_load_dir,    "Lazily load all files in a directory into the sample cache (args: pathname)", 2},
     { "play-file",               pa_cli_command_play_file,          "Play a sound file (args: filename, sink|index)", 3},
     { "list-autoload",           pa_cli_command_autoload_list,      "List autoload entries", 1},
-    { "add-autoload-sink",       pa_cli_command_autoload_add,       "Add autoload entry for a sink (args: sink, module name, arguments)", 4},
-    { "add-autoload-source",     pa_cli_command_autoload_add,       "Add autoload entry for a source (args: source, module name, arguments)", 4},
-    { "remove-autoload-sink",    pa_cli_command_autoload_remove,    "Remove autoload entry for a sink (args: name)", 2},
-    { "remove-autoload-source",  pa_cli_command_autoload_remove,    "Remove autoload entry for a source (args: name)", 2},
+    { "add-autoload-sink",       pa_cli_command_autoload_add,       NULL /*"Add autoload entry for a sink (args: sink, module name, arguments)"*/, 4},
+    { "add-autoload-source",     pa_cli_command_autoload_add,       NULL /*"Add autoload entry for a source (args: source, module name, arguments)"*/, 4},
+    { "remove-autoload-sink",    pa_cli_command_autoload_remove,    NULL /*"Remove autoload entry for a sink (args: name)"*/, 2},
+    { "remove-autoload-source",  pa_cli_command_autoload_remove,    NULL /*"Remove autoload entry for a source (args: name)"*/, 2},
     { "dump",                    pa_cli_command_dump,               "Dump daemon configuration", 1},
     { "list-props",              pa_cli_command_list_props,         NULL, 1},
     { "move-sink-input",         pa_cli_command_move_sink_input,    "Move sink input to another sink (args: index, sink)", 3},
@@ -367,7 +369,7 @@ static int pa_cli_command_info(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_b
     pa_cli_command_sink_inputs(c, t, buf, fail);
     pa_cli_command_source_outputs(c, t, buf, fail);
     pa_cli_command_scache_list(c, t, buf, fail);
-    pa_cli_command_autoload_list(c, t, buf, fail);
+/*     pa_cli_command_autoload_list(c, t, buf, fail); */
     return 0;
 }
 
@@ -419,6 +421,45 @@ static int pa_cli_command_unload(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa
     return 0;
 }
 
+static int pa_cli_command_describe(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) {
+    const char *name;
+    pa_modinfo *i;
+
+    pa_core_assert_ref(c);
+    pa_assert(t);
+    pa_assert(buf);
+    pa_assert(fail);
+
+    if (!(name = pa_tokenizer_get(t, 1))) {
+        pa_strbuf_puts(buf, "You need to specify the module name.\n");
+        return -1;
+    }
+
+    if ((i = pa_modinfo_get_by_name(name))) {
+
+        pa_strbuf_printf(buf, "Name: %s\n", name);
+
+        if (!i->description && !i->version && !i->author && !i->usage)
+            pa_strbuf_printf(buf, "No module information available\n");
+        else {
+            if (i->version)
+                pa_strbuf_printf(buf, "Version: %s\n", i->version);
+            if (i->description)
+                pa_strbuf_printf(buf, "Description: %s\n", i->description);
+            if (i->author)
+                pa_strbuf_printf(buf, "Author: %s\n", i->author);
+            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));
+        }
+
+        pa_modinfo_free(i);
+    } else
+        pa_strbuf_puts(buf, "Failed to open module.\n");
+
+    return 0;
+}
+
 static int pa_cli_command_sink_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) {
     const char *n, *v;
     pa_sink *sink;
@@ -436,7 +477,7 @@ static int pa_cli_command_sink_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *bu
     }
 
     if (!(v = pa_tokenizer_get(t, 2))) {
-        pa_strbuf_puts(buf, "You need to specify a volume >= 0. (0 is muted, 0x100 is normal volume)\n");
+        pa_strbuf_puts(buf, "You need to specify a volume >= 0. (0 is muted, 0x10000 is normal volume)\n");
         return -1;
     }
 
@@ -478,7 +519,7 @@ static int pa_cli_command_sink_input_volume(pa_core *c, pa_tokenizer *t, pa_strb
     }
 
     if (!(v = pa_tokenizer_get(t, 2))) {
-        pa_strbuf_puts(buf, "You need to specify a volume >= 0. (0 is muted, 0x100 is normal volume)\n");
+        pa_strbuf_puts(buf, "You need to specify a volume >= 0. (0 is muted, 0x10000 is normal volume)\n");
         return -1;
     }
 
@@ -514,7 +555,7 @@ static int pa_cli_command_source_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *
     }
 
     if (!(v = pa_tokenizer_get(t, 2))) {
-        pa_strbuf_puts(buf, "You need to specify a volume >= 0. (0 is muted, 0x100 is normal volume)\n");
+        pa_strbuf_puts(buf, "You need to specify a volume >= 0. (0 is muted, 0x10000 is normal volume)\n");
         return -1;
     }
 
@@ -553,7 +594,7 @@ static int pa_cli_command_sink_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf,
         return -1;
     }
 
-    if (pa_atoi(m, &mute) < 0) {
+    if ((mute = pa_parse_boolean(m)) < 0) {
         pa_strbuf_puts(buf, "Failed to parse mute switch.\n");
         return -1;
     }
@@ -587,7 +628,7 @@ static int pa_cli_command_source_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *bu
         return -1;
     }
 
-    if (pa_atoi(m, &mute) < 0) {
+    if ((mute = pa_parse_boolean(m)) < 0) {
         pa_strbuf_puts(buf, "Failed to parse mute switch.\n");
         return -1;
     }
@@ -623,11 +664,11 @@ static int pa_cli_command_sink_input_mute(pa_core *c, pa_tokenizer *t, pa_strbuf
     }
 
     if (!(v = pa_tokenizer_get(t, 2))) {
-        pa_strbuf_puts(buf, "You need to specify a volume >= 0. (0 is muted, 0x100 is normal volume)\n");
+        pa_strbuf_puts(buf, "You need to specify a mute switch setting (0/1).\n");
         return -1;
     }
 
-    if (pa_atoi(v, &mute) < 0) {
+    if ((mute = pa_parse_boolean(v)) < 0) {
         pa_strbuf_puts(buf, "Failed to parse mute switch.\n");
         return -1;
     }
@@ -780,6 +821,7 @@ static int pa_cli_command_scache_list(pa_core *c, pa_tokenizer *t, pa_strbuf *bu
 static int pa_cli_command_scache_play(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) {
     const char *n, *sink_name;
     pa_sink *sink;
+    uint32_t idx;
 
     pa_core_assert_ref(c);
     pa_assert(t);
@@ -796,11 +838,13 @@ static int pa_cli_command_scache_play(pa_core *c, pa_tokenizer *t, pa_strbuf *bu
         return -1;
     }
 
-    if (pa_scache_play_item(c, n, sink, PA_VOLUME_NORM) < 0) {
+    if (pa_scache_play_item(c, n, sink, PA_VOLUME_NORM, NULL, &idx) < 0) {
         pa_strbuf_puts(buf, "Failed to play sample.\n");
         return -1;
     }
 
+    pa_strbuf_printf(buf, "Playing on sink input #%i\n", idx);
+
     return 0;
 }
 
@@ -902,6 +946,8 @@ static int pa_cli_command_autoload_add(pa_core *c, pa_tokenizer *t, pa_strbuf *b
     pa_assert(buf);
     pa_assert(fail);
 
+    pa_log_warn("Autoload will no longer be implemented by future versions of the PulseAudio server.");
+
     if (!(a = pa_tokenizer_get(t, 1)) || !(b = pa_tokenizer_get(t, 2))) {
         pa_strbuf_puts(buf, "You need to specify a device name, a filename or a module name and optionally module arguments\n");
         return -1;
@@ -920,6 +966,8 @@ static int pa_cli_command_autoload_remove(pa_core *c, pa_tokenizer *t, pa_strbuf
     pa_assert(buf);
     pa_assert(fail);
 
+    pa_log_warn("Autoload will no longer be implemented by future versions of the PulseAudio server.");
+
     if (!(name = pa_tokenizer_get(t, 1))) {
         pa_strbuf_puts(buf, "You need to specify a device name\n");
         return -1;
@@ -941,6 +989,8 @@ static int pa_cli_command_autoload_list(pa_core *c, pa_tokenizer *t, pa_strbuf *
     pa_assert(buf);
     pa_assert(fail);
 
+    pa_log_warn("Autoload will no longer be implemented by future versions of the PulseAudio server.");
+
     pa_assert_se(s = pa_autoload_list_to_string(c));
     pa_strbuf_puts(buf, s);
     pa_xfree(s);
@@ -1005,7 +1055,7 @@ static int pa_cli_command_move_sink_input(pa_core *c, pa_tokenizer *t, pa_strbuf
         return -1;
     }
 
-    if (pa_sink_input_move_to(si, sink, 0) < 0) {
+    if (pa_sink_input_move_to(si, sink) < 0) {
         pa_strbuf_puts(buf, "Moved failed.\n");
         return -1;
     }
@@ -1075,7 +1125,7 @@ static int pa_cli_command_suspend_sink(pa_core *c, pa_tokenizer *t, pa_strbuf *b
         return -1;
     }
 
-    if (pa_atoi(m, &suspend) < 0) {
+    if ((suspend = pa_parse_boolean(m)) < 0) {
         pa_strbuf_puts(buf, "Failed to parse suspend switch.\n");
         return -1;
     }
@@ -1109,7 +1159,7 @@ static int pa_cli_command_suspend_source(pa_core *c, pa_tokenizer *t, pa_strbuf
         return -1;
     }
 
-    if (pa_atoi(m, &suspend) < 0) {
+    if ((suspend = pa_parse_boolean(m)) < 0) {
         pa_strbuf_puts(buf, "Failed to parse suspend switch.\n");
         return -1;
     }
@@ -1138,7 +1188,7 @@ static int pa_cli_command_suspend(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, p
         return -1;
     }
 
-    if (pa_atoi(m, &suspend) < 0) {
+    if ((suspend = pa_parse_boolean(m)) < 0) {
         pa_strbuf_puts(buf, "Failed to parse suspend switch.\n");
         return -1;
     }
@@ -1202,7 +1252,8 @@ static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_b
         }
 
         pa_strbuf_printf(buf, "set-sink-volume %s 0x%03x\n", sink->name, pa_cvolume_avg(pa_sink_get_volume(sink)));
-        pa_strbuf_printf(buf, "set-sink-mute %s %d\n", sink->name, pa_sink_get_mute(sink));
+        pa_strbuf_printf(buf, "set-sink-mute %s %s\n", sink->name, pa_yes_no(pa_sink_get_mute(sink)));
+        pa_strbuf_printf(buf, "suspend-sink %s %s\n", sink->name, pa_yes_no(pa_sink_get_state(sink) == PA_SINK_SUSPENDED));
     }
 
     for (source = pa_idxset_first(c->sources, &idx); source; source = pa_idxset_next(c->sources, &idx)) {
@@ -1215,7 +1266,8 @@ static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_b
         }
 
         pa_strbuf_printf(buf, "set-source-volume %s 0x%03x\n", source->name, pa_cvolume_avg(pa_source_get_volume(source)));
-        pa_strbuf_printf(buf, "set-source-mute %s %d\n", source->name, pa_source_get_mute(source));
+        pa_strbuf_printf(buf, "set-source-mute %s %s\n", source->name, pa_yes_no(pa_source_get_mute(source)));
+        pa_strbuf_printf(buf, "suspend-source %s %s\n", source->name, pa_yes_no(pa_source_get_state(source) == PA_SOURCE_SUSPENDED));
     }
 
 
@@ -1390,16 +1442,45 @@ int pa_cli_command_execute_line(pa_core *c, const char *s, pa_strbuf *buf, pa_bo
     return pa_cli_command_execute_line_stateful(c, s, buf, fail, NULL);
 }
 
-int pa_cli_command_execute_file(pa_core *c, const char *fn, pa_strbuf *buf, pa_bool_t *fail) {
+int pa_cli_command_execute_file_stream(pa_core *c, FILE *f, pa_strbuf *buf, pa_bool_t *fail) {
     char line[1024];
-    FILE *f = NULL;
     int ifstate = IFSTATE_NONE;
     int ret = -1;
+    pa_bool_t _fail = TRUE;
+
+    pa_assert(c);
+    pa_assert(f);
+    pa_assert(buf);
+
+    if (!fail)
+        fail = &_fail;
+
+    while (fgets(line, sizeof(line), f)) {
+        pa_strip_nl(line);
+
+        if (pa_cli_command_execute_line_stateful(c, line, buf, fail, &ifstate) < 0 && *fail)
+            goto fail;
+    }
+
+    ret = 0;
+
+fail:
+
+    return ret;
+}
+
+int pa_cli_command_execute_file(pa_core *c, const char *fn, pa_strbuf *buf, pa_bool_t *fail) {
+    FILE *f = NULL;
+    int ret = -1;
+    pa_bool_t _fail = TRUE;
 
     pa_assert(c);
     pa_assert(fn);
     pa_assert(buf);
 
+    if (!fail)
+        fail = &_fail;
+
     if (!(f = fopen(fn, "r"))) {
         pa_strbuf_printf(buf, "open('%s') failed: %s\n", fn, pa_cstrerror(errno));
         if (!*fail)
@@ -1407,13 +1488,7 @@ int pa_cli_command_execute_file(pa_core *c, const char *fn, pa_strbuf *buf, pa_b
         goto fail;
     }
 
-    while (fgets(line, sizeof(line), f)) {
-        char *e = line + strcspn(line, linebreak);
-        *e = 0;
-
-        if (pa_cli_command_execute_line_stateful(c, line, buf, fail, &ifstate) < 0 && *fail)
-            goto fail;
-    }
+    ret = pa_cli_command_execute_file_stream(c, f, buf, fail);
 
     ret = 0;
 
@@ -1427,11 +1502,15 @@ fail:
 int pa_cli_command_execute(pa_core *c, const char *s, pa_strbuf *buf, pa_bool_t *fail) {
     const char *p;
     int ifstate = IFSTATE_NONE;
+    pa_bool_t _fail = TRUE;
 
     pa_assert(c);
     pa_assert(s);
     pa_assert(buf);
 
+    if (!fail)
+        fail = &_fail;
+
     p = s;
     while (*p) {
         size_t l = strcspn(p, linebreak);
index c90c8e0..2a92344 100644 (file)
@@ -36,6 +36,9 @@ int pa_cli_command_execute_line(pa_core *c, const char *s, pa_strbuf *buf, pa_bo
 /* Execute a whole file of CLI commands */
 int pa_cli_command_execute_file(pa_core *c, const char *fn, pa_strbuf *buf, pa_bool_t *fail);
 
+/* Execute a whole file of CLI commands */
+int pa_cli_command_execute_file_stream(pa_core *c, FILE *f, pa_strbuf *buf, pa_bool_t *fail);
+
 /* Split the specified string into lines and run pa_cli_command_execute_line() for each. */
 int pa_cli_command_execute(pa_core *c, const char *s, pa_strbuf *buf, pa_bool_t *fail);
 
index b64cafe..029a708 100644 (file)
@@ -29,6 +29,7 @@
 
 #include <pulse/volume.h>
 #include <pulse/xmalloc.h>
+#include <pulse/timeval.h>
 
 #include <pulsecore/module.h>
 #include <pulsecore/client.h>
@@ -41,6 +42,7 @@
 #include <pulsecore/core-scache.h>
 #include <pulsecore/autoload.h>
 #include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
 
 #include "cli-text.h"
 
@@ -56,12 +58,12 @@ char *pa_module_list_to_string(pa_core *c) {
 
     for (m = pa_idxset_first(c->modules, &idx); m; m = pa_idxset_next(c->modules, &idx)) {
         pa_strbuf_printf(s, "    index: %u\n"
-            "\tname: <%s>\n"
-            "\targument: <%s>\n"
-            "\tused: %i\n"
-            "\tauto unload: %s\n",
-            m->index, m->name, m->argument ? m->argument : "", m->n_used,
-            m->auto_unload ? "yes" : "no");
+                         "\tname: <%s>\n"
+                         "\targument: <%s>\n"
+                         "\tused: %i\n"
+                         "\tauto unload: %s\n",
+                         m->index, m->name, m->argument ? m->argument : "", m->n_used,
+                         pa_yes_no(m->auto_unload));
     }
 
     return pa_strbuf_tostring_free(s);
@@ -78,10 +80,20 @@ char *pa_client_list_to_string(pa_core *c) {
     pa_strbuf_printf(s, "%u client(s) logged in.\n", pa_idxset_size(c->clients));
 
     for (client = pa_idxset_first(c->clients, &idx); client; client = pa_idxset_next(c->clients, &idx)) {
-        pa_strbuf_printf(s, "    index: %u\n\tname: <%s>\n\tdriver: <%s>\n", client->index, client->name, client->driver);
+        char *t;
+        pa_strbuf_printf(
+                s,
+                "    index: %u\n"
+                "\tdriver: <%s>\n",
+                client->index,
+                client->driver);
+
+        if (client->module)
+            pa_strbuf_printf(s, "\towner module: %u\n", client->module->index);
 
-        if (client->owner)
-            pa_strbuf_printf(s, "\towner module: <%u>\n", client->owner->index);
+        t = pa_proplist_to_string(client->proplist);
+        pa_strbuf_printf(s, "\tproperties:\n%s", t);
+        pa_xfree(t);
     }
 
     return pa_strbuf_tostring_free(s);
@@ -92,6 +104,7 @@ char *pa_sink_list_to_string(pa_core *c) {
     pa_sink *sink;
     uint32_t idx = PA_IDXSET_INVALID;
     static const char* const state_table[] = {
+        [PA_SINK_INIT] = "INIT",
         [PA_SINK_RUNNING] = "RUNNING",
         [PA_SINK_SUSPENDED] = "SUSPENDED",
         [PA_SINK_IDLE] = "IDLE",
@@ -104,35 +117,39 @@ char *pa_sink_list_to_string(pa_core *c) {
     pa_strbuf_printf(s, "%u sink(s) available.\n", pa_idxset_size(c->sinks));
 
     for (sink = pa_idxset_first(c->sinks, &idx); sink; sink = pa_idxset_next(c->sinks, &idx)) {
-        char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+        char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], *t;
 
         pa_strbuf_printf(
             s,
             "  %c index: %u\n"
             "\tname: <%s>\n"
             "\tdriver: <%s>\n"
-            "\tflags: %s%s%s%s\n"
+            "\tflags: %s%s%s%s%s%s\n"
             "\tstate: %s\n"
-            "\tvolume: <%s>\n"
-            "\tmute: <%i>\n"
-            "\tlatency: <%0.0f usec>\n"
-            "\tmonitor source: <%u>\n"
-            "\tsample spec: <%s>\n"
-            "\tchannel map: <%s>\n"
-            "\tused by: <%u>\n"
-            "\tlinked by: <%u>\n",
+            "\tvolume: %s\n"
+            "\tmuted: %s\n"
+            "\tcurrent latency: %0.2f ms\n"
+            "\tconfigured latency: %0.2f ms; range is %0.2f .. %0.2f ms\n"
+            "\tmonitor source: %u\n"
+            "\tsample spec: %s\n"
+            "\tchannel map: %s\n"
+            "\tused by: %u\n"
+            "\tlinked by: %u\n",
             c->default_sink_name && !strcmp(sink->name, c->default_sink_name) ? '*' : ' ',
             sink->index,
             sink->name,
             sink->driver,
-            sink->flags & PA_SINK_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "",
-            sink->flags & PA_SINK_LATENCY ? "LATENCY " : "",
             sink->flags & PA_SINK_HARDWARE ? "HARDWARE " : "",
             sink->flags & PA_SINK_NETWORK ? "NETWORK " : "",
+            sink->flags & PA_SINK_HW_MUTE_CTRL ? "HW_MUTE_CTRL " : "",
+            sink->flags & PA_SINK_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "",
+            sink->flags & PA_SINK_DECIBEL_VOLUME ? "DECIBEL_VOLUME " : "",
+            sink->flags & PA_SINK_LATENCY ? "LATENCY " : "",
             state_table[pa_sink_get_state(sink)],
             pa_cvolume_snprint(cv, sizeof(cv), pa_sink_get_volume(sink)),
-            !!pa_sink_get_mute(sink),
-            (double) pa_sink_get_latency(sink),
+            pa_yes_no(pa_sink_get_mute(sink)),
+            (double) pa_sink_get_latency(sink) / PA_USEC_PER_MSEC,
+            (double) pa_sink_get_requested_latency(sink) / PA_USEC_PER_MSEC, (double) sink->min_latency / PA_USEC_PER_MSEC, (double) sink->max_latency / PA_USEC_PER_MSEC,
             sink->monitor_source ? sink->monitor_source->index : PA_INVALID_INDEX,
             pa_sample_spec_snprint(ss, sizeof(ss), &sink->sample_spec),
             pa_channel_map_snprint(cm, sizeof(cm), &sink->channel_map),
@@ -140,9 +157,11 @@ char *pa_sink_list_to_string(pa_core *c) {
             pa_sink_linked_by(sink));
 
         if (sink->module)
-            pa_strbuf_printf(s, "\tmodule: <%u>\n", sink->module->index);
-        if (sink->description)
-            pa_strbuf_printf(s, "\tdescription: <%s>\n", sink->description);
+            pa_strbuf_printf(s, "\tmodule: %u\n", sink->module->index);
+
+        t = pa_proplist_to_string(sink->proplist);
+        pa_strbuf_printf(s, "\tproperties:\n%s", t);
+        pa_xfree(t);
     }
 
     return pa_strbuf_tostring_free(s);
@@ -153,6 +172,7 @@ char *pa_source_list_to_string(pa_core *c) {
     pa_source *source;
     uint32_t idx = PA_IDXSET_INVALID;
     static const char* const state_table[] = {
+        [PA_SOURCE_INIT] = "INIT",
         [PA_SOURCE_RUNNING] = "RUNNING",
         [PA_SOURCE_SUSPENDED] = "SUSPENDED",
         [PA_SOURCE_IDLE] = "IDLE",
@@ -165,46 +185,51 @@ char *pa_source_list_to_string(pa_core *c) {
     pa_strbuf_printf(s, "%u source(s) available.\n", pa_idxset_size(c->sources));
 
     for (source = pa_idxset_first(c->sources, &idx); source; source = pa_idxset_next(c->sources, &idx)) {
-        char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX];
-
+        char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], *t;
 
         pa_strbuf_printf(
             s,
             "  %c index: %u\n"
             "\tname: <%s>\n"
             "\tdriver: <%s>\n"
-            "\tflags: %s%s%s%s\n"
+            "\tflags: %s%s%s%s%s%s\n"
             "\tstate: %s\n"
-            "\tvolume: <%s>\n"
-            "\tmute: <%u>\n"
-            "\tlatency: <%0.0f usec>\n"
-            "\tsample spec: <%s>\n"
-            "\tchannel map: <%s>\n"
-            "\tused by: <%u>\n"
-            "\tlinked by: <%u>\n",
+            "\tvolume: %s\n"
+            "\tmuted: %s\n"
+            "\tcurrent latency: %0.2f ms\n"
+            "\tconfigured latency: %0.2f ms; range is %0.2f .. %0.2f ms\n"
+            "\tsample spec: %s\n"
+            "\tchannel map: %s\n"
+            "\tused by: %u\n"
+            "\tlinked by: %u\n",
             c->default_source_name && !strcmp(source->name, c->default_source_name) ? '*' : ' ',
             source->index,
             source->name,
             source->driver,
-            source->flags & PA_SOURCE_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "",
-            source->flags & PA_SOURCE_LATENCY ? "LATENCY " : "",
             source->flags & PA_SOURCE_HARDWARE ? "HARDWARE " : "",
             source->flags & PA_SOURCE_NETWORK ? "NETWORK " : "",
+            source->flags & PA_SOURCE_HW_MUTE_CTRL ? "HW_MUTE_CTRL " : "",
+            source->flags & PA_SOURCE_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "",
+            source->flags & PA_SOURCE_DECIBEL_VOLUME ? "DECIBEL_VOLUME " : "",
+            source->flags & PA_SOURCE_LATENCY ? "LATENCY " : "",
             state_table[pa_source_get_state(source)],
             pa_cvolume_snprint(cv, sizeof(cv), pa_source_get_volume(source)),
-            !!pa_source_get_mute(source),
-            (double) pa_source_get_latency(source),
+            pa_yes_no(pa_source_get_mute(source)),
+            (double) pa_source_get_latency(source) / PA_USEC_PER_MSEC,
+            (double) pa_source_get_requested_latency(source) / PA_USEC_PER_MSEC, (double) source->min_latency / PA_USEC_PER_MSEC, (double) source->max_latency / PA_USEC_PER_MSEC,
             pa_sample_spec_snprint(ss, sizeof(ss), &source->sample_spec),
             pa_channel_map_snprint(cm, sizeof(cm), &source->channel_map),
             pa_source_used_by(source),
             pa_source_linked_by(source));
 
         if (source->monitor_of)
-            pa_strbuf_printf(s, "\tmonitor_of: <%u>\n", source->monitor_of->index);
+            pa_strbuf_printf(s, "\tmonitor_of: %u\n", source->monitor_of->index);
         if (source->module)
-            pa_strbuf_printf(s, "\tmodule: <%u>\n", source->module->index);
-        if (source->description)
-            pa_strbuf_printf(s, "\tdescription: <%s>\n", source->description);
+            pa_strbuf_printf(s, "\tmodule: %u\n", source->module->index);
+
+        t = pa_proplist_to_string(source->proplist);
+        pa_strbuf_printf(s, "\tproperties:\n%s", t);
+        pa_xfree(t);
     }
 
     return pa_strbuf_tostring_free(s);
@@ -216,6 +241,7 @@ char *pa_source_output_list_to_string(pa_core *c) {
     pa_source_output *o;
     uint32_t idx = PA_IDXSET_INVALID;
     static const char* const state_table[] = {
+        [PA_SOURCE_OUTPUT_INIT] = "INIT",
         [PA_SOURCE_OUTPUT_RUNNING] = "RUNNING",
         [PA_SOURCE_OUTPUT_CORKED] = "CORKED",
         [PA_SOURCE_OUTPUT_UNLINKED] = "UNLINKED"
@@ -227,27 +253,33 @@ char *pa_source_output_list_to_string(pa_core *c) {
     pa_strbuf_printf(s, "%u source outputs(s) available.\n", pa_idxset_size(c->source_outputs));
 
     for (o = pa_idxset_first(c->source_outputs, &idx); o; o = pa_idxset_next(c->source_outputs, &idx)) {
-        char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+        char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], *t, clt[28];
+        pa_usec_t cl;
+
+        if ((cl = pa_source_output_get_requested_latency(o)) == (pa_usec_t) -1)
+            pa_snprintf(clt, sizeof(clt), "n/a");
+        else
+            pa_snprintf(clt, sizeof(clt), "%0.2f ms", (double) cl / PA_USEC_PER_MSEC);
 
         pa_assert(o->source);
 
         pa_strbuf_printf(
             s,
             "    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"
-            "\tsource: <%u> '%s'\n"
-            "\tlatency: <%0.0f usec>\n"
-            "\tsample spec: <%s>\n"
-            "\tchannel map: <%s>\n"
+            "\tsource: %u <%s>\n"
+            "\tcurrent latency: %0.2f ms\n"
+            "\trequested latency: %s\n"
+            "\tsample spec: %s\n"
+            "\tchannel map: %s\n"
             "\tresample method: %s\n",
             o->index,
-            o->name,
             o->driver,
             o->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE ? "VARIABLE_RATE " : "",
             o->flags & PA_SOURCE_OUTPUT_DONT_MOVE ? "DONT_MOVE " : "",
+            o->flags & PA_SOURCE_OUTPUT_START_CORKED ? "START_CORKED " : "",
             o->flags & PA_SOURCE_OUTPUT_NO_REMAP ? "NO_REMAP " : "",
             o->flags & PA_SOURCE_OUTPUT_NO_REMIX ? "NO_REMIX " : "",
             o->flags & PA_SOURCE_OUTPUT_FIX_FORMAT ? "FIX_FORMAT " : "",
@@ -255,14 +287,19 @@ char *pa_source_output_list_to_string(pa_core *c) {
             o->flags & PA_SOURCE_OUTPUT_FIX_CHANNELS ? "FIX_CHANNELS " : "",
             state_table[pa_source_output_get_state(o)],
             o->source->index, o->source->name,
-            (double) pa_source_output_get_latency(o),
+            (double) pa_source_output_get_latency(o) / PA_USEC_PER_MSEC,
+            clt,
             pa_sample_spec_snprint(ss, sizeof(ss), &o->sample_spec),
             pa_channel_map_snprint(cm, sizeof(cm), &o->channel_map),
             pa_resample_method_to_string(pa_source_output_get_resample_method(o)));
         if (o->module)
-            pa_strbuf_printf(s, "\towner module: <%u>\n", o->module->index);
+            pa_strbuf_printf(s, "\towner module: %u\n", o->module->index);
         if (o->client)
-            pa_strbuf_printf(s, "\tclient: <%u> '%s'\n", o->client->index, o->client->name);
+            pa_strbuf_printf(s, "\tclient: %u <%s>\n", o->client->index, pa_strnull(pa_proplist_gets(o->client->proplist, PA_PROP_APPLICATION_NAME)));
+
+        t = pa_proplist_to_string(o->proplist);
+        pa_strbuf_printf(s, "\tproperties:\n%s", t);
+        pa_xfree(t);
     }
 
     return pa_strbuf_tostring_free(s);
@@ -273,6 +310,7 @@ char *pa_sink_input_list_to_string(pa_core *c) {
     pa_sink_input *i;
     uint32_t idx = PA_IDXSET_INVALID;
     static const char* const state_table[] = {
+        [PA_SINK_INPUT_INIT] = "INIT",
         [PA_SINK_INPUT_RUNNING] = "RUNNING",
         [PA_SINK_INPUT_DRAINED] = "DRAINED",
         [PA_SINK_INPUT_CORKED] = "CORKED",
@@ -285,29 +323,35 @@ char *pa_sink_input_list_to_string(pa_core *c) {
     pa_strbuf_printf(s, "%u sink input(s) available.\n", pa_idxset_size(c->sink_inputs));
 
     for (i = pa_idxset_first(c->sink_inputs, &idx); i; i = pa_idxset_next(c->sink_inputs, &idx)) {
-        char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+        char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], *t, clt[28];
+        pa_usec_t cl;
+
+        if ((cl = pa_sink_input_get_requested_latency(i)) == (pa_usec_t) -1)
+            pa_snprintf(clt, sizeof(clt), "n/a");
+        else
+            pa_snprintf(clt, sizeof(clt), "%0.2f ms", (double) cl / PA_USEC_PER_MSEC);
 
         pa_assert(i->sink);
 
         pa_strbuf_printf(
             s,
             "    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"
-            "\tsink: <%u> '%s'\n"
-            "\tvolume: <%s>\n"
-            "\tmute: <%i>\n"
-            "\tlatency: <%0.0f usec>\n"
-            "\tsample spec: <%s>\n"
-            "\tchannel map: <%s>\n"
+            "\tsink: %u <%s>\n"
+            "\tvolume: %s\n"
+            "\tmuted: %s\n"
+            "\tcurrent latency: %0.2f ms\n"
+            "\trequested latency: %s\n"
+            "\tsample spec: %s\n"
+            "\tchannel map: %s\n"
             "\tresample method: %s\n",
             i->index,
-            i->name,
             i->driver,
             i->flags & PA_SINK_INPUT_VARIABLE_RATE ? "VARIABLE_RATE " : "",
             i->flags & PA_SINK_INPUT_DONT_MOVE ? "DONT_MOVE " : "",
+            i->flags & PA_SINK_INPUT_START_CORKED ? "START_CORKED " : "",
             i->flags & PA_SINK_INPUT_NO_REMAP ? "NO_REMAP " : "",
             i->flags & PA_SINK_INPUT_NO_REMIX ? "NO_REMIX " : "",
             i->flags & PA_SINK_INPUT_FIX_FORMAT ? "FIX_FORMAT " : "",
@@ -316,16 +360,21 @@ char *pa_sink_input_list_to_string(pa_core *c) {
             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_sink_input_get_mute(i),
-            (double) pa_sink_input_get_latency(i),
+            pa_yes_no(pa_sink_input_get_mute(i)),
+            (double) pa_sink_input_get_latency(i) / PA_USEC_PER_MSEC,
+            clt,
             pa_sample_spec_snprint(ss, sizeof(ss), &i->sample_spec),
             pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map),
             pa_resample_method_to_string(pa_sink_input_get_resample_method(i)));
 
         if (i->module)
-            pa_strbuf_printf(s, "\tmodule: <%u>\n", i->module->index);
+            pa_strbuf_printf(s, "\tmodule: %u\n", i->module->index);
         if (i->client)
-            pa_strbuf_printf(s, "\tclient: <%u> '%s'\n", i->client->index, i->client->name);
+            pa_strbuf_printf(s, "\tclient: %u <%s>\n", i->client->index, pa_strnull(pa_proplist_gets(i->client->proplist, PA_PROP_APPLICATION_NAME)));
+
+        t = pa_proplist_to_string(i->proplist);
+        pa_strbuf_printf(s, "\tproperties:\n%s", t);
+        pa_xfree(t);
     }
 
     return pa_strbuf_tostring_free(s);
@@ -345,7 +394,7 @@ char *pa_scache_list_to_string(pa_core *c) {
 
         for (e = pa_idxset_first(c->scache, &idx); e; e = pa_idxset_next(c->scache, &idx)) {
             double l = 0;
-            char ss[PA_SAMPLE_SPEC_SNPRINT_MAX] = "n/a", cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX] = "n/a";
+            char ss[PA_SAMPLE_SPEC_SNPRINT_MAX] = "n/a", cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX] = "n/a", *t;
 
             if (e->memchunk.memblock) {
                 pa_sample_spec_snprint(ss, sizeof(ss), &e->sample_spec);
@@ -356,14 +405,14 @@ char *pa_scache_list_to_string(pa_core *c) {
             pa_strbuf_printf(
                 s,
                 "    name: <%s>\n"
-                "\tindex: <%u>\n"
-                "\tsample spec: <%s>\n"
-                "\tchannel map: <%s>\n"
-                "\tlength: <%lu>\n"
-                "\tduration: <%0.1fs>\n"
-                "\tvolume: <%s>\n"
+                "\tindex: %u\n"
+                "\tsample spec: %s\n"
+                "\tchannel map: %s\n"
+                "\tlength: %lu\n"
+                "\tduration: %0.1f s\n"
+                "\tvolume: %s\n"
                 "\tlazy: %s\n"
-                "\tfilename: %s\n",
+                "\tfilename: <%s>\n",
                 e->name,
                 e->index,
                 ss,
@@ -371,8 +420,12 @@ char *pa_scache_list_to_string(pa_core *c) {
                 (long unsigned)(e->memchunk.memblock ? e->memchunk.length : 0),
                 l,
                 pa_cvolume_snprint(cv, sizeof(cv), &e->volume),
-                e->lazy ? "yes" : "no",
+                pa_yes_no(e->lazy),
                 e->filename ? e->filename : "n/a");
+
+            t = pa_proplist_to_string(e->proplist);
+            pa_strbuf_printf(s, "\tproperties:\n%s", t);
+            pa_xfree(t);
         }
     }
 
@@ -393,7 +446,12 @@ char *pa_autoload_list_to_string(pa_core *c) {
 
         while ((e = pa_hashmap_iterate(c->autoload_hashmap, &state, NULL))) {
             pa_strbuf_printf(
-                s, "    name: <%s>\n\ttype: <%s>\n\tindex: <%u>\n\tmodule_name: <%s>\n\targuments: <%s>\n",
+                s,
+                "    name: <%s>\n"
+                "\ttype: %s\n"
+                "\tindex: %u\n"
+                "\tmodule_name: <%s>\n"
+                "\targuments: <%s>\n",
                 e->name,
                 e->type == PA_NAMEREG_SOURCE ? "source" : "sink",
                 e->index,
index 85e0863..47712d3 100644 (file)
@@ -82,7 +82,7 @@ pa_cli* pa_cli_new(pa_core *core, pa_iochannel *io, pa_module *m) {
     pa_assert_se(c->client = pa_client_new(core, __FILE__, cname));
     c->client->kill = client_kill;
     c->client->userdata = c;
-    c->client->owner = m;
+    c->client->module = m;
 
     pa_ioline_set_callback(c->line, line_callback, c);
     pa_ioline_puts(c->line, "Welcome to PulseAudio! Use \"help\" for usage information.\n"PROMPT);
index 319b838..4eca4e2 100644 (file)
@@ -35,6 +35,7 @@
 #include <pulsecore/core-subscribe.h>
 #include <pulsecore/log.h>
 #include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
 
 #include "client.h"
 
@@ -44,17 +45,19 @@ pa_client *pa_client_new(pa_core *core, const char *driver, const char *name) {
     pa_core_assert_ref(core);
 
     c = pa_xnew(pa_client, 1);
-    c->name = pa_xstrdup(name);
-    c->driver = pa_xstrdup(driver);
-    c->owner = NULL;
     c->core = core;
+    c->proplist = pa_proplist_new();
+    if (name)
+        pa_proplist_sets(c->proplist, PA_PROP_APPLICATION_NAME, name);
+    c->driver = pa_xstrdup(driver);
+    c->module = NULL;
 
     c->kill = NULL;
     c->userdata = NULL;
 
     pa_assert_se(pa_idxset_put(core->clients, c, &c->index) >= 0);
 
-    pa_log_info("Created %u \"%s\"", c->index, c->name);
+    pa_log_info("Created %u \"%s\"", c->index, pa_strnull(name));
     pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_NEW, c->index);
 
     pa_core_check_quit(core);
@@ -70,9 +73,9 @@ void pa_client_free(pa_client *c) {
 
     pa_core_check_quit(c->core);
 
-    pa_log_info("Freed %u \"%s\"", c->index, c->name);
+    pa_log_info("Freed %u \"%s\"", c->index, pa_strnull(pa_proplist_gets(c->proplist, PA_PROP_APPLICATION_NAME)));
     pa_subscription_post(c->core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_REMOVE, c->index);
-    pa_xfree(c->name);
+    pa_proplist_free(c->proplist);
     pa_xfree(c->driver);
     pa_xfree(c);
 }
@@ -91,10 +94,7 @@ void pa_client_kill(pa_client *c) {
 void pa_client_set_name(pa_client *c, const char *name) {
     pa_assert(c);
 
-    pa_log_info("Client %u changed name from \"%s\" to \"%s\"", c->index, c->name, name);
-
-    pa_xfree(c->name);
-    c->name = pa_xstrdup(name);
-
+    pa_log_info("Client %u changed name from \"%s\" to \"%s\"", c->index, pa_strnull(pa_proplist_gets(c->proplist, PA_PROP_APPLICATION_NAME)), name);
+    pa_proplist_sets(c->proplist, PA_PROP_APPLICATION_NAME, name);
     pa_subscription_post(c->core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_CHANGE, c->index);
 }
index 6d09b99..bff057e 100644 (file)
@@ -28,6 +28,7 @@
 
 typedef struct pa_client pa_client;
 
+#include <pulse/proplist.h>
 #include <pulsecore/core.h>
 #include <pulsecore/module.h>
 
@@ -37,11 +38,12 @@ typedef struct pa_client pa_client;
 
 struct pa_client {
     uint32_t index;
-
-    pa_module *owner;
-    char *name, *driver;
     pa_core *core;
 
+    pa_proplist *proplist;
+    pa_module *module;
+    char *driver;
+
     void (*kill)(pa_client *c);
     void *userdata;
 };
diff --git a/src/pulsecore/core-def.h b/src/pulsecore/core-def.h
deleted file mode 100644 (file)
index 4bc0513..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-#ifndef foocoredefhfoo
-#define foocoredefhfoo
-
-/* $Id$ */
-
-/***
-  This file is part of PulseAudio.
-
-  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 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.
-***/
-
-/* FIXME: Remove this shit */
-
-#endif
index 46444a9..e554600 100644 (file)
@@ -3,7 +3,7 @@
 /***
   This file is part of PulseAudio.
 
-  Copyright 2004-2006 Lennart Poettering
+  Copyright 2004-2008 Lennart Poettering
   Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
 
   PulseAudio is free software; you can redistribute it and/or modify
@@ -63,7 +63,7 @@
 
 #include "core-scache.h"
 
-#define UNLOAD_POLL_TIME 2
+#define UNLOAD_POLL_TIME 5
 
 static void timeout_callback(pa_mainloop_api *m, pa_time_event*e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) {
     pa_core *c = userdata;
@@ -89,6 +89,8 @@ static void free_entry(pa_scache_entry *e) {
     pa_xfree(e->filename);
     if (e->memchunk.memblock)
         pa_memblock_unref(e->memchunk.memblock);
+    if (e->proplist)
+        pa_proplist_free(e->proplist);
     pa_xfree(e);
 }
 
@@ -103,6 +105,7 @@ static pa_scache_entry* scache_add_item(pa_core *c, const char *name) {
             pa_memblock_unref(e->memchunk.memblock);
 
         pa_xfree(e->filename);
+        pa_proplist_clear(e->proplist);
 
         pa_assert(e->core == c);
 
@@ -117,11 +120,10 @@ static pa_scache_entry* scache_add_item(pa_core *c, const char *name) {
 
         e->name = pa_xstrdup(name);
         e->core = c;
+        e->proplist = pa_proplist_new();
 
-        if (!c->scache) {
+        if (!c->scache)
             c->scache = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
-            pa_assert(c->scache);
-        }
 
         pa_idxset_put(c->scache, e, &e->index);
 
@@ -132,17 +134,27 @@ static pa_scache_entry* scache_add_item(pa_core *c, const char *name) {
     e->memchunk.memblock = NULL;
     e->memchunk.index = e->memchunk.length = 0;
     e->filename = NULL;
-    e->lazy = 0;
+    e->lazy = FALSE;
     e->last_used_time = 0;
 
     memset(&e->sample_spec, 0, sizeof(e->sample_spec));
     pa_channel_map_init(&e->channel_map);
     pa_cvolume_reset(&e->volume, PA_CHANNELS_MAX);
 
+    pa_proplist_sets(e->proplist, PA_PROP_MEDIA_ROLE, "event");
+
     return e;
 }
 
-int pa_scache_add_item(pa_core *c, const char *name, const pa_sample_spec *ss, const pa_channel_map *map, const pa_memchunk *chunk, uint32_t *idx) {
+int pa_scache_add_item(
+        pa_core *c,
+        const char *name,
+        const pa_sample_spec *ss,
+        const pa_channel_map *map,
+        const pa_memchunk *chunk,
+        pa_proplist *p,
+        uint32_t *idx) {
+
     pa_scache_entry *e;
     char st[PA_SAMPLE_SPEC_SNPRINT_MAX];
     pa_channel_map tmap;
@@ -178,6 +190,9 @@ int pa_scache_add_item(pa_core *c, const char *name, const pa_sample_spec *ss, c
         pa_memblock_ref(e->memchunk.memblock);
     }
 
+    if (p)
+        pa_proplist_update(e->proplist, PA_UPDATE_REPLACE, p);
+
     if (idx)
         *idx = e->index;
 
@@ -193,6 +208,7 @@ int pa_scache_add_file(pa_core *c, const char *name, const char *filename, uint3
     pa_channel_map map;
     pa_memchunk chunk;
     int r;
+    pa_proplist *p;
 
 #ifdef OS_IS_WIN32
     char buf[MAX_PATH];
@@ -208,8 +224,11 @@ int pa_scache_add_file(pa_core *c, const char *name, const char *filename, uint3
     if (pa_sound_file_load(c->mempool, filename, &ss, &map, &chunk) < 0)
         return -1;
 
-    r = pa_scache_add_item(c, name, &ss, &map, &chunk, idx);
+    p = pa_proplist_new();
+    pa_proplist_sets(p, PA_PROP_MEDIA_FILENAME, filename);
+    r = pa_scache_add_item(c, name, &ss, &map, &chunk, p, idx);
     pa_memblock_unref(chunk.memblock);
+    pa_proplist_free(p);
 
     return r;
 }
@@ -231,9 +250,11 @@ int pa_scache_add_file_lazy(pa_core *c, const char *name, const char *filename,
     if (!(e = scache_add_item(c, name)))
         return -1;
 
-    e->lazy = 1;
+    e->lazy = TRUE;
     e->filename = pa_xstrdup(filename);
 
+    pa_proplist_sets(e->proplist, PA_PROP_MEDIA_FILENAME, filename);
+
     if (!c->scache_auto_unload_event) {
         struct timeval ntv;
         pa_gettimeofday(&ntv);
@@ -285,10 +306,10 @@ void pa_scache_free(pa_core *c) {
         c->mainloop->time_free(c->scache_auto_unload_event);
 }
 
-int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t volume) {
+int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t volume, pa_proplist *p, uint32_t *sink_input_idx) {
     pa_scache_entry *e;
-    char *t;
     pa_cvolume r;
+    pa_proplist *merged;
 
     pa_assert(c);
     pa_assert(name);
@@ -312,17 +333,24 @@ int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t
 
     pa_log_debug("Playing sample \"%s\" on \"%s\"", name, sink->name);
 
-    t = pa_sprintf_malloc("sample:%s", name);
-
     pa_cvolume_set(&r, e->volume.channels, volume);
     pa_sw_cvolume_multiply(&r, &r, &e->volume);
 
-    if (pa_play_memchunk(sink, t, &e->sample_spec, &e->channel_map, &e->memchunk, &r) < 0) {
-        pa_xfree(t);
+    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, &r, merged, sink_input_idx) < 0) {
+        pa_proplist_free(merged);
         return -1;
     }
 
-    pa_xfree(t);
+    pa_proplist_free(merged);
 
     if (e->lazy)
         time(&e->last_used_time);
@@ -330,7 +358,7 @@ int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t
     return 0;
 }
 
-int pa_scache_play_item_by_name(pa_core *c, const char *name, const char*sink_name, pa_volume_t volume, int autoload) {
+int pa_scache_play_item_by_name(pa_core *c, const char *name, const char*sink_name, pa_bool_t autoload, pa_volume_t volume, pa_proplist *p, uint32_t *sink_input_idx) {
     pa_sink *sink;
 
     pa_assert(c);
@@ -339,10 +367,10 @@ int pa_scache_play_item_by_name(pa_core *c, const char *name, const char*sink_na
     if (!(sink = pa_namereg_get(c, sink_name, PA_NAMEREG_SINK, autoload)))
         return -1;
 
-    return pa_scache_play_item(c, name, sink, volume);
+    return pa_scache_play_item(c, name, sink, volume, p, sink_input_idx);
 }
 
-const char * pa_scache_get_name_by_id(pa_core *c, uint32_t id) {
+const char *pa_scache_get_name_by_id(pa_core *c, uint32_t id) {
     pa_scache_entry *e;
 
     pa_assert(c);
@@ -366,9 +394,10 @@ uint32_t pa_scache_get_id_by_name(pa_core *c, const char *name) {
     return e->index;
 }
 
-uint32_t pa_scache_total_size(pa_core *c) {
+size_t pa_scache_total_size(pa_core *c) {
     pa_scache_entry *e;
-    uint32_t idx, sum = 0;
+    uint32_t idx;
+    size_t sum = 0;
 
     pa_assert(c);
 
@@ -403,8 +432,7 @@ void pa_scache_unload_unused(pa_core *c) {
             continue;
 
         pa_memblock_unref(e->memchunk.memblock);
-        e->memchunk.memblock = NULL;
-        e->memchunk.index = e->memchunk.length = 0;
+        pa_memchunk_reset(&e->memchunk);
 
         pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index);
     }
@@ -467,8 +495,9 @@ int pa_scache_add_directory_lazy(pa_core *c, const char *pathname) {
             pa_snprintf(p, sizeof(p), "%s/%s", pathname, e->d_name);
             add_file(c, p);
         }
+
+        closedir(dir);
     }
 
-    closedir(dir);
     return 0;
 }
index ab7ec0e..31f3ff3 100644 (file)
 #include <pulsecore/memchunk.h>
 #include <pulsecore/sink.h>
 
-#define PA_SCACHE_ENTRY_SIZE_MAX (1024*1024*2)
+#define PA_SCACHE_ENTRY_SIZE_MAX (1024*1024*16)
 
 typedef struct pa_scache_entry {
-    pa_core *core;
     uint32_t index;
+    pa_core *core;
+
     char *name;
 
     pa_cvolume volume;
@@ -43,25 +44,27 @@ typedef struct pa_scache_entry {
 
     char *filename;
 
-    int lazy;
+    pa_bool_t lazy;
     time_t last_used_time;
+
+    pa_proplist *proplist;
 } pa_scache_entry;
 
-int pa_scache_add_item(pa_core *c, const char *name, const pa_sample_spec *ss, const pa_channel_map *map, const pa_memchunk *chunk, uint32_t *idx);
+int pa_scache_add_item(pa_core *c, const char *name, const pa_sample_spec *ss, const pa_channel_map *map, const pa_memchunk *chunk, pa_proplist *p, uint32_t *idx);
 int pa_scache_add_file(pa_core *c, const char *name, const char *filename, uint32_t *idx);
 int pa_scache_add_file_lazy(pa_core *c, const char *name, const char *filename, uint32_t *idx);
 
 int pa_scache_add_directory_lazy(pa_core *c, const char *pathname);
 
 int pa_scache_remove_item(pa_core *c, const char *name);
-int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t volume);
-int pa_scache_play_item_by_name(pa_core *c, const char *name, const char*sink_name, pa_volume_t volume, int autoload);
+int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t volume, pa_proplist *p, uint32_t *sink_input_idx);
+int pa_scache_play_item_by_name(pa_core *c, const char *name, const char*sink_name, pa_bool_t autoload, pa_volume_t volume, pa_proplist *p, uint32_t *sink_input_idx);
 void pa_scache_free(pa_core *c);
 
 const char *pa_scache_get_name_by_id(pa_core *c, uint32_t id);
 uint32_t pa_scache_get_id_by_name(pa_core *c, const char *name);
 
-uint32_t pa_scache_total_size(pa_core *c);
+size_t pa_scache_total_size(pa_core *c);
 
 void pa_scache_unload_unused(pa_core *c);
 
index 61d04c2..c8ea4f5 100644 (file)
@@ -41,6 +41,7 @@
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <sys/time.h>
+#include <dirent.h>
 
 #ifdef HAVE_STRTOF_L
 #include <locale.h>
 #define MSG_NOSIGNAL 0
 #endif
 
-#ifndef OS_IS_WIN32
-#define PA_USER_RUNTIME_PATH_PREFIX "/tmp/pulse-"
-#else
-#define PA_USER_RUNTIME_PATH_PREFIX "%TEMP%\\pulse-"
-#endif
-
 #ifdef OS_IS_WIN32
 
 #define PULSE_ROOTENV "PULSE_ROOT"
@@ -221,7 +216,7 @@ int pa_make_secure_dir(const char* dir, mode_t m, uid_t uid, gid_t gid) {
         goto fail;
     }
 #else
-    pa_log_warn("secure directory creation not supported on Win32.");
+    pa_log_warn("Secure directory creation not supported on Win32.");
 #endif
 
     return 0;
@@ -557,6 +552,82 @@ int pa_make_realtime(int rtprio) {
 #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;
+
+#if defined(HAVE_SYS_RESOURCE_H) && defined(RLIMIT_RTPRIO)
+    {
+        struct rlimit rl;
+
+        if (getrlimit(RLIMIT_RTPRIO, &rl) >= 0)
+            if (rl.rlim_cur > 0 || rl.rlim_cur == RLIM_INFINITY)
+                return TRUE;
+    }
+#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;
+
+            if (cap_get_flag(cap, CAP_SYS_NICE, CAP_EFFECTIVE, &flag) >= 0)
+                if (flag == CAP_SET) {
+                    cap_free(cap);
+                    return TRUE;
+                }
+
+            cap_free(cap);
+        }
+    }
+#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;
+
+#if defined(HAVE_SYS_RESOURCE_H) && defined(RLIMIT_RTPRIO)
+    {
+        struct rlimit rl;
+
+        if (getrlimit(RLIMIT_NICE, &rl) >= 0)
+            if (rl.rlim_cur >= 21 || rl.rlim_cur == RLIM_INFINITY)
+                return TRUE;
+    }
+#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;
+
+            if (cap_get_flag(cap, CAP_SYS_NICE, CAP_EFFECTIVE, &flag) >= 0)
+                if (flag == CAP_SET) {
+                    cap_free(cap);
+                    return TRUE;
+                }
+
+            cap_free(cap);
+        }
+    }
+#endif
+
+    return FALSE;
+}
+
 /* Raise the priority of the current process as much as possible that
  * is <= the specified nice level..*/
 int pa_raise_priority(int nice_level) {
@@ -612,6 +683,7 @@ void pa_reset_priority(void) {
 
 /* Try to parse a boolean string value.*/
 int pa_parse_boolean(const char *v) {
+    pa_assert(v);
 
     if (!strcmp(v, "1") || v[0] == 'y' || v[0] == 'Y' || v[0] == 't' || v[0] == 'T' || !strcasecmp(v, "on"))
         return 1;
@@ -1093,16 +1165,39 @@ int pa_unlock_lockfile(const char *fn, int fd) {
     return r;
 }
 
+char *pa_get_runtime_dir(void) {
+    const char *e;
+    char *d;
+
+    if ((e = getenv("PULSE_RUNTIME_PATH")))
+        d = pa_xstrdup(e);
+    else {
+        char h[PATH_MAX];
+
+        if (!pa_get_home_dir(h, sizeof(h))) {
+            pa_log_error("Failed to get home directory.");
+            return NULL;
+        }
+
+        d = pa_sprintf_malloc("%s" PA_PATH_SEP ".pulse", h);
+    }
+
+    if (pa_make_secure_dir(d, 0700, (pid_t) -1, (pid_t) -1) < 0)  {
+        pa_log_error("Failed to create secure directory: %s", pa_cstrerror(errno));
+        return NULL;
+    }
+
+    return d;
+}
+
 /* Try to open a configuration file. If "env" is specified, open the
  * value of the specified environment variable. Otherwise look for a
  * file "local" in the home directory or a file "global" in global
  * file system. If "result" is non-NULL, a pointer to a newly
  * allocated buffer containing the used configuration file is
  * stored there.*/
-FILE *pa_open_config_file(const char *global, const char *local, const char *env, char **result, const char *mode) {
+FILE *pa_open_config_file(const char *global, const char *local, const char *env, char **result) {
     const char *fn;
-    char h[PATH_MAX];
-
 #ifdef OS_IS_WIN32
     char buf[PATH_MAX];
 
@@ -1111,75 +1206,152 @@ FILE *pa_open_config_file(const char *global, const char *local, const char *env
 #endif
 
     if (env && (fn = getenv(env))) {
+        FILE *f;
+
 #ifdef OS_IS_WIN32
         if (!ExpandEnvironmentStrings(fn, buf, PATH_MAX))
             return NULL;
         fn = buf;
 #endif
 
-        if (result)
-            *result = pa_xstrdup(fn);
+        if ((f = fopen(fn, "r"))) {
+            if (result)
+                *result = pa_xstrdup(fn);
 
-        return fopen(fn, mode);
+            return f;
+        }
+
+        pa_log_warn("Failed to open configuration file '%s': %s", fn, pa_cstrerror(errno));
+        return NULL;
     }
 
     if (local) {
         const char *e;
-        char *lfn = NULL;
+        char *lfn;
+        char h[PATH_MAX];
+        FILE *f;
 
         if ((e = getenv("PULSE_CONFIG_PATH")))
-            fn = lfn = pa_sprintf_malloc("%s/%s", e, local);
-        else if (pa_get_home_dir(h, sizeof(h))) {
-            char *d;
+            fn = lfn = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", e, local);
+        else if (pa_get_home_dir(h, sizeof(h)))
+            fn = lfn = pa_sprintf_malloc("%s" PA_PATH_SEP ".pulse" PA_PATH_SEP "%s", h, local);
+
+#ifdef OS_IS_WIN32
+        if (!ExpandEnvironmentStrings(lfn, buf, PATH_MAX)) {
+            pa_xfree(lfn);
+            return NULL;
+        }
+        fn = buf;
+#endif
 
-            d = pa_sprintf_malloc("%s/.pulse", h);
-            mkdir(d, 0755);
-            pa_xfree(d);
+        if ((f = fopen(fn, "r"))) {
+            if (result)
+                *result = pa_xstrdup(fn);
 
-            fn = lfn = pa_sprintf_malloc("%s/.pulse/%s", h, local);
+            pa_xfree(lfn);
+            return f;
+        }
+
+        if (errno != ENOENT) {
+            pa_log_warn("Failed to open configuration file '%s': %s", fn, pa_cstrerror(errno));
+            pa_xfree(lfn);
+            return NULL;
         }
 
-        if (lfn) {
-            FILE *f;
+        pa_xfree(lfn);
+    }
+
+    if (global) {
+        FILE *f;
 
 #ifdef OS_IS_WIN32
-            if (!ExpandEnvironmentStrings(lfn, buf, PATH_MAX))
-                return NULL;
-            fn = buf;
+        if (!ExpandEnvironmentStrings(global, buf, PATH_MAX))
+            return NULL;
+        global = buf;
 #endif
 
-            f = fopen(fn, mode);
-            if (f != NULL) {
-                if (result)
-                    *result = pa_xstrdup(fn);
-                pa_xfree(lfn);
-                return f;
-            }
+        if ((f = fopen(global, "r"))) {
 
-            if (errno != ENOENT)
-                pa_log_warn("Failed to open configuration file '%s': %s", lfn, pa_cstrerror(errno));
+            if (result)
+                *result = pa_xstrdup(global);
 
-            pa_xfree(lfn);
+            return f;
         }
-    }
-
-    if (!global) {
-        if (result)
-            *result = NULL;
+    } else
         errno = ENOENT;
+
+    return NULL;
+}
+
+char *pa_find_config_file(const char *global, const char *local, const char *env) {
+    const char *fn;
+#ifdef OS_IS_WIN32
+    char buf[PATH_MAX];
+
+    if (!getenv(PULSE_ROOTENV))
+        pa_set_root(NULL);
+#endif
+
+    if (env && (fn = getenv(env))) {
+#ifdef OS_IS_WIN32
+        if (!ExpandEnvironmentStrings(fn, buf, PATH_MAX))
+            return NULL;
+        fn = buf;
+#endif
+
+        if (access(fn, R_OK) == 0)
+            return pa_xstrdup(fn);
+
+        pa_log_warn("Failed to access configuration file '%s': %s", fn, pa_cstrerror(errno));
         return NULL;
     }
 
+    if (local) {
+        const char *e;
+        char *lfn;
+        char h[PATH_MAX];
+
+        if ((e = getenv("PULSE_CONFIG_PATH")))
+            fn = lfn = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", e, local);
+        else if (pa_get_home_dir(h, sizeof(h)))
+            fn = lfn = pa_sprintf_malloc("%s" PA_PATH_SEP ".pulse" PA_PATH_SEP "%s", h, local);
+
 #ifdef OS_IS_WIN32
-    if (!ExpandEnvironmentStrings(global, buf, PATH_MAX))
-        return NULL;
-    global = buf;
+        if (!ExpandEnvironmentStrings(lfn, buf, PATH_MAX)) {
+            pa_xfree(lfn);
+            return NULL;
+        }
+        fn = buf;
+#endif
+
+        if (access(fn, R_OK) == 0) {
+            char *r = pa_xstrdup(fn);
+            pa_xfree(lfn);
+            return r;
+        }
+
+        if (errno != ENOENT) {
+            pa_log_warn("Failed to access configuration file '%s': %s", fn, pa_cstrerror(errno));
+            pa_xfree(lfn);
+            return NULL;
+        }
+
+        pa_xfree(lfn);
+    }
+
+    if (global) {
+#ifdef OS_IS_WIN32
+        if (!ExpandEnvironmentStrings(global, buf, PATH_MAX))
+            return NULL;
+        global = buf;
 #endif
 
-    if (result)
-        *result = pa_xstrdup(global);
+        if (access(fn, R_OK) == 0)
+            return pa_xstrdup(global);
+    } else
+        errno = ENOENT;
 
-    return fopen(global, mode);
+    return NULL;
 }
 
 /* Format the specified data as a hexademical string */
@@ -1270,45 +1442,51 @@ int pa_endswith(const char *s, const char *sfx) {
     return l1 >= l2 && strcmp(s+l1-l2, sfx) == 0;
 }
 
-/* if fn is null return the PulseAudio run time path in s (/tmp/pulse)
- * if fn is non-null and starts with / return fn in s
- * otherwise append fn to the run time path and return it in s */
-char *pa_runtime_path(const char *fn, char *s, size_t l) {
-    const char *e;
+pa_bool_t pa_is_path_absolute(const char *fn) {
+    pa_assert(fn);
 
 #ifndef OS_IS_WIN32
-    if (fn && *fn == '/')
+    return *fn == '/';
 #else
-    if (fn && strlen(fn) >= 3 && isalpha(fn[0]) && fn[1] == ':' && fn[2] == '\\')
+    return strlen(fn) >= 3 && isalpha(fn[0]) && fn[1] == ':' && fn[2] == '\\';
 #endif
-        return pa_strlcpy(s, fn, l);
+}
 
-    if ((e = getenv("PULSE_RUNTIME_PATH"))) {
+char *pa_make_path_absolute(const char *p) {
+    char *r;
+    char *cwd;
 
-        if (fn)
-            pa_snprintf(s, l, "%s%c%s", e, PA_PATH_SEP_CHAR, fn);
-        else
-            pa_snprintf(s, l, "%s", e);
+    pa_assert(p);
 
-    } else {
-        char u[256];
+    if (pa_is_path_absolute(p))
+        return pa_xstrdup(p);
 
-        if (fn)
-            pa_snprintf(s, l, "%s%s%c%s", PA_USER_RUNTIME_PATH_PREFIX, pa_get_user_name(u, sizeof(u)), PA_PATH_SEP_CHAR, fn);
-        else
-            pa_snprintf(s, l, "%s%s", PA_USER_RUNTIME_PATH_PREFIX, pa_get_user_name(u, sizeof(u)));
-    }
+    if (!(cwd = pa_getcwd()))
+        return pa_xstrdup(p);
 
+    r = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", cwd, p);
+    pa_xfree(cwd);
+    return r;
+}
 
-#ifdef OS_IS_WIN32
-    {
-        char buf[l];
-        strcpy(buf, s);
-        ExpandEnvironmentStrings(buf, s, l);
-    }
-#endif
+/* if fn is null return the PulseAudio run time path in s (~/.pulse)
+ * if fn is non-null and starts with / return fn
+ * otherwise append fn to the run time path and return it */
+char *pa_runtime_path(const char *fn) {
+    char *rtp;
 
-    return s;
+    if (pa_is_path_absolute(fn))
+        return pa_xstrdup(fn);
+
+    rtp = pa_get_runtime_dir();
+
+    if (fn) {
+        char *r;
+        r = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", rtp, fn);
+        pa_xfree(rtp);
+        return r;
+    } else
+        return rtp;
 }
 
 /* Convert the string s to a signed integer in *ret_i */
@@ -1414,12 +1592,28 @@ int pa_snprintf(char *str, size_t size, const char *format, ...) {
     pa_assert(format);
 
     va_start(ap, format);
-    ret = vsnprintf(str, size, format, ap);
+    ret = pa_vsnprintf(str, size, format, ap);
     va_end(ap);
 
+    return ret;
+}
+
+/* Same as vsnprintf, but guarantees NUL-termination on every platform */
+int pa_vsnprintf(char *str, size_t size, const char *format, va_list ap) {
+    int ret;
+
+    pa_assert(str);
+    pa_assert(size > 0);
+    pa_assert(format);
+
+    ret = vsnprintf(str, size, format, ap);
+
     str[size-1] = 0;
 
-    return ret;
+    if (ret < 0)
+        ret = strlen(str);
+
+    return PA_MIN((int) size-1, ret);
 }
 
 /* Truncate the specified string, but guarantee that the string
@@ -1455,23 +1649,6 @@ char *pa_getcwd(void) {
     }
 }
 
-char *pa_make_path_absolute(const char *p) {
-    char *r;
-    char *cwd;
-
-    pa_assert(p);
-
-    if (p[0] == '/')
-        return pa_xstrdup(p);
-
-    if (!(cwd = pa_getcwd()))
-        return pa_xstrdup(p);
-
-    r = pa_sprintf_malloc("%s/%s", cwd, p);
-    pa_xfree(cwd);
-    return r;
-}
-
 void *pa_will_need(const void *p, size_t l) {
 #ifdef RLIMIT_MEMLOCK
     struct rlimit rlim;
@@ -1577,3 +1754,249 @@ char *pa_readlink(const char *p) {
         l *= 2;
     }
 }
+
+int pa_close_all(int except_fd, ...) {
+    va_list ap;
+    int n = 0, i, r;
+    int *p;
+
+    va_start(ap, except_fd);
+
+    if (except_fd >= 0)
+        for (n = 1; va_arg(ap, int) >= 0; n++)
+            ;
+
+    va_end(ap);
+
+    p = pa_xnew(int, n+1);
+
+    va_start(ap, except_fd);
+
+    i = 0;
+    if (except_fd >= 0) {
+        p[i++] = except_fd;
+
+        while ((p[i++] = va_arg(ap, int)) >= 0)
+            ;
+    }
+    p[i] = -1;
+
+    va_end(ap);
+
+    r = pa_close_allv(p);
+    free(p);
+
+    return r;
+}
+
+int pa_close_allv(const int except_fds[]) {
+    struct rlimit rl;
+    int fd;
+    int saved_errno;
+
+#ifdef __linux__
+
+    DIR *d;
+
+    if ((d = opendir("/proc/self/fd"))) {
+
+        struct dirent *de;
+
+        while ((de = readdir(d))) {
+            long l;
+            char *e = NULL;
+            int i;
+
+            if (de->d_name[0] == '.')
+                continue;
+
+            errno = 0;
+            l = strtol(de->d_name, &e, 10);
+            if (errno != 0 || !e || *e) {
+                closedir(d);
+                errno = EINVAL;
+                return -1;
+            }
+
+            fd = (int) l;
+
+            if ((long) fd != l) {
+                closedir(d);
+                errno = EINVAL;
+                return -1;
+            }
+
+            if (fd <= 3)
+                continue;
+
+            if (fd == dirfd(d))
+                continue;
+
+            for (i = 0; except_fds[i] >= 0; i++)
+                if (except_fds[i] == fd)
+                    continue;
+
+            if (close(fd) < 0) {
+                saved_errno = errno;
+                closedir(d);
+                errno = saved_errno;
+
+                return -1;
+            }
+        }
+
+        closedir(d);
+        return 0;
+    }
+
+#endif
+
+    if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
+        return -1;
+
+    for (fd = 0; fd < (int) rl.rlim_max; fd++) {
+        int i;
+
+        if (fd <= 3)
+            continue;
+
+        for (i = 0; except_fds[i] >= 0; i++)
+            if (except_fds[i] == fd)
+                continue;
+
+        if (close(fd) < 0 && errno != EBADF)
+            return -1;
+    }
+
+    return 0;
+}
+
+int pa_unblock_sigs(int except, ...) {
+    va_list ap;
+    int n = 0, i, r;
+    int *p;
+
+    va_start(ap, except);
+
+    if (except >= 1)
+        for (n = 1; va_arg(ap, int) >= 0; n++)
+            ;
+
+    va_end(ap);
+
+    p = pa_xnew(int, n+1);
+
+    va_start(ap, except);
+
+    i = 0;
+    if (except >= 1) {
+        p[i++] = except;
+
+        while ((p[i++] = va_arg(ap, int)) >= 0)
+            ;
+    }
+    p[i] = -1;
+
+    va_end(ap);
+
+    r = pa_unblock_sigsv(p);
+    pa_xfree(p);
+
+    return r;
+}
+
+int pa_unblock_sigsv(const int except[]) {
+    int i;
+    sigset_t ss;
+
+    if (sigemptyset(&ss) < 0)
+        return -1;
+
+    for (i = 0; except[i] > 0; i++)
+        if (sigaddset(&ss, except[i]) < 0)
+            return -1;
+
+    return sigprocmask(SIG_SETMASK, &ss, NULL);
+}
+
+int pa_reset_sigs(int except, ...) {
+    va_list ap;
+    int n = 0, i, r;
+    int *p;
+
+    va_start(ap, except);
+
+    if (except >= 1)
+        for (n = 1; va_arg(ap, int) >= 0; n++)
+            ;
+
+    va_end(ap);
+
+    p = pa_xnew(int, n+1);
+
+    va_start(ap, except);
+
+    i = 0;
+    if (except >= 1) {
+        p[i++] = except;
+
+        while ((p[i++] = va_arg(ap, int)) >= 0)
+            ;
+    }
+    p[i] = -1;
+
+    va_end(ap);
+
+    r = pa_reset_sigsv(p);
+    pa_xfree(p);
+
+    return r;
+}
+
+int pa_reset_sigsv(const int except[]) {
+    int sig;
+
+    for (sig = 1; sig < _NSIG; sig++) {
+        int reset = 1;
+
+        switch (sig) {
+            case SIGKILL:
+            case SIGSTOP:
+                reset = 0;
+                break;
+
+            default: {
+                int i;
+
+                for (i = 0; except[i] > 0; i++) {
+                    if (sig == except[i]) {
+                        reset = 0;
+                        break;
+                    }
+                }
+            }
+        }
+
+        if (reset) {
+            struct sigaction sa;
+
+            memset(&sa, 0, sizeof(sa));
+            sa.sa_handler = SIG_DFL;
+
+            /* On Linux the first two RT signals are reserved by
+             * glibc, and sigaction() will return EINVAL for them. */
+            if ((sigaction(sig, &sa, NULL) < 0))
+                if (errno != EINVAL)
+                    return -1;
+        }
+    }
+
+    return 0;
+}
+
+void pa_set_env(const char *key, const char *value) {
+    pa_assert(key);
+    pa_assert(value);
+
+    putenv(pa_sprintf_malloc("%s=%s", key, value));
+}
index c8760a1..ec4cdc4 100644 (file)
 #include <stdarg.h>
 #include <stdio.h>
 
-#include <pulsecore/gccmacro.h>
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
+
+#include <pulse/gccmacro.h>
 #include <pulsecore/macro.h>
 
 struct timeval;
 
+/* These resource limits are pretty new on Linux, let's define them
+ * here manually, in case the kernel is newer than the glibc */
+#if !defined(RLIMIT_NICE) && defined(__linux__)
+#define RLIMIT_NICE 13
+#endif
+#if !defined(RLIMIT_RTPRIO) && defined(__linux__)
+#define RLIMIT_RTPRIO 14
+#endif
+#if !defined(RLIMIT_RTTIME) && defined(__linux__)
+#define RLIMIT_RTTIME 15
+#endif
+
 void pa_make_fd_nonblock(int fd);
 void pa_make_fd_cloexec(int fd);
 
@@ -61,12 +77,23 @@ 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) {
     return b ? "yes" : "no";
 }
 
+static inline const char *pa_strnull(const char *x) {
+    return x ? x : "(null)";
+}
+
+static inline const char *pa_strempty(const char *x) {
+    return x ? x : "";
+}
+
 char *pa_split(const char *c, const char*delimiters, const char **state);
 char *pa_split_spaces(const char *c, const char **state);
 
@@ -84,26 +111,30 @@ int pa_lock_fd(int fd, int b);
 int pa_lock_lockfile(const char *fn);
 int pa_unlock_lockfile(const char *fn, int fd);
 
-FILE *pa_open_config_file(const char *global, const char *local, const char *env, char **result, const char *mode);
-
 char *pa_hexstr(const uint8_t* d, size_t dlength, char *s, size_t slength);
 size_t pa_parsehex(const char *p, uint8_t *d, size_t dlength);
 
 int pa_startswith(const char *s, const char *pfx) PA_GCC_PURE;
 int pa_endswith(const char *s, const char *sfx) PA_GCC_PURE;
 
-char *pa_runtime_path(const char *fn, char *s, size_t l);
+FILE *pa_open_config_file(const char *global, const char *local, const char *env, char **result);
+char* pa_find_config_file(const char *global, const char *local, const char *env);
+
+char *pa_get_runtime_dir(void);
+char *pa_runtime_path(const char *fn);
 
 int pa_atoi(const char *s, int32_t *ret_i);
 int pa_atou(const char *s, uint32_t *ret_u);
 int pa_atof(const char *s, float *ret_f);
 
 int pa_snprintf(char *str, size_t size, const char *format, ...);
+int pa_vsnprintf(char *str, size_t size, const char *format, va_list ap);
 
 char *pa_truncate_utf8(char *c, size_t l);
 
 char *pa_getcwd(void);
 char *pa_make_path_absolute(const char *p);
+pa_bool_t pa_is_path_absolute(const char *p);
 
 void *pa_will_need(const void *p, size_t l);
 
@@ -125,8 +156,28 @@ static inline unsigned pa_make_power_of_two(unsigned n) {
     return n + 1;
 }
 
+static inline unsigned pa_ulog2(unsigned n) {
+    unsigned r = 0;
+
+    while (n) {
+        r++;
+        n = n >> 1;
+    }
+
+    return r;
+}
+
 void pa_close_pipe(int fds[2]);
 
 char *pa_readlink(const char *p);
 
+int pa_close_all(int except_fd, ...);
+int pa_close_allv(const int except_fds[]);
+int pa_unblock_sigs(int except, ...);
+int pa_unblock_sigsv(const int except[]);
+int pa_reset_sigs(int except, ...);
+int pa_reset_sigsv(const int except[]);
+
+void pa_set_env(const char *key, const char *value);
+
 #endif
index cf01850..3b758a3 100644 (file)
@@ -125,6 +125,7 @@ pa_core* pa_core_new(pa_mainloop_api *m, int shared) {
     c->subscription_event_last = NULL;
 
     c->mempool = pool;
+    pa_silence_cache_init(&c->silence_cache);
 
     c->quit_event = NULL;
 
@@ -188,6 +189,7 @@ static void core_free(pa_object *o) {
     pa_xfree(c->default_source_name);
     pa_xfree(c->default_sink_name);
 
+    pa_silence_cache_done(&c->silence_cache);
     pa_mempool_free(c->mempool);
 
     pa_property_cleanup(c);
index ce45e30..50c05b4 100644 (file)
@@ -35,6 +35,7 @@
 #include <pulsecore/llist.h>
 #include <pulsecore/hook-list.h>
 #include <pulsecore/asyncmsgq.h>
+#include <pulsecore/sample-util.h>
 
 typedef struct pa_core pa_core;
 
@@ -43,16 +44,20 @@ typedef struct pa_core pa_core;
 #include <pulsecore/msgobject.h>
 
 typedef enum pa_core_hook {
-    PA_CORE_HOOK_SINK_NEW_POST,
+    PA_CORE_HOOK_SINK_NEW,
+    PA_CORE_HOOK_SINK_FIXATE,
+    PA_CORE_HOOK_SINK_PUT,
     PA_CORE_HOOK_SINK_UNLINK,
     PA_CORE_HOOK_SINK_UNLINK_POST,
     PA_CORE_HOOK_SINK_STATE_CHANGED,
-    PA_CORE_HOOK_SINK_DESCRIPTION_CHANGED,
-    PA_CORE_HOOK_SOURCE_NEW_POST,
+    PA_CORE_HOOK_SINK_PROPLIST_CHANGED,
+    PA_CORE_HOOK_SOURCE_NEW,
+    PA_CORE_HOOK_SOURCE_FIXATE,
+    PA_CORE_HOOK_SOURCE_PUT,
     PA_CORE_HOOK_SOURCE_UNLINK,
     PA_CORE_HOOK_SOURCE_UNLINK_POST,
     PA_CORE_HOOK_SOURCE_STATE_CHANGED,
-    PA_CORE_HOOK_SOURCE_DESCRIPTION_CHANGED,
+    PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED,
     PA_CORE_HOOK_SINK_INPUT_NEW,
     PA_CORE_HOOK_SINK_INPUT_FIXATE,
     PA_CORE_HOOK_SINK_INPUT_PUT,
@@ -60,8 +65,8 @@ typedef enum pa_core_hook {
     PA_CORE_HOOK_SINK_INPUT_UNLINK_POST,
     PA_CORE_HOOK_SINK_INPUT_MOVE,
     PA_CORE_HOOK_SINK_INPUT_MOVE_POST,
-    PA_CORE_HOOK_SINK_INPUT_NAME_CHANGED,
     PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED,
+    PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED,
     PA_CORE_HOOK_SOURCE_OUTPUT_NEW,
     PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE,
     PA_CORE_HOOK_SOURCE_OUTPUT_PUT,
@@ -69,8 +74,8 @@ typedef enum pa_core_hook {
     PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST,
     PA_CORE_HOOK_SOURCE_OUTPUT_MOVE,
     PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_POST,
-    PA_CORE_HOOK_SOURCE_OUTPUT_NAME_CHANGED,
     PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED,
+    PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED,
     PA_CORE_HOOK_MAX
 } pa_core_hook_t;
 
@@ -108,6 +113,7 @@ struct pa_core {
     pa_subscription_event *subscription_event_last;
 
     pa_mempool *mempool;
+    pa_silence_cache silence_cache;
 
     int exit_idle_time, module_idle_time, scache_idle_time;
 
index 571f875..2f5da5a 100644 (file)
@@ -381,7 +381,7 @@ static void envelope_merge(pa_envelope *e, int v) {
                 break;
 
             if (e->points[v].n_points >= e->points[v].n_allocated) {
-                e->points[v].n_allocated = MAX(e->points[v].n_points*2, PA_ENVELOPE_POINTS_MAX);
+                e->points[v].n_allocated = PA_MAX(e->points[v].n_points*2, PA_ENVELOPE_POINTS_MAX);
 
                 e->points[v].x = pa_xrealloc(e->points[v].x, sizeof(size_t) * e->points[v].n_allocated);
                 e->points[v].y.i = pa_xrealloc(e->points[v].y.i, sizeof(int32_t) * e->points[v].n_allocated);
index 23be8f6..c54c137 100644 (file)
@@ -29,7 +29,7 @@
 
 #include <pulse/sample.h>
 
-#define PA_ENVELOPE_POINTS_MAX 4
+#define PA_ENVELOPE_POINTS_MAX 4U
 
 typedef struct pa_envelope pa_envelope;
 typedef struct pa_envelope_item pa_envelope_item;
index 59eec18..22d2a85 100644 (file)
@@ -78,21 +78,19 @@ struct pa_fdsem {
 #ifdef HAVE_EVENTFD
     int efd;
 #endif
-    pa_atomic_t waiting;
-    pa_atomic_t signalled;
-    pa_atomic_t in_pipe;
+
+    pa_fdsem_data *data;
 };
 
 pa_fdsem *pa_fdsem_new(void) {
     pa_fdsem *f;
 
-    f = pa_xnew(pa_fdsem, 1);
+    f = pa_xmalloc(PA_ALIGN(sizeof(pa_fdsem)) + PA_ALIGN(sizeof(pa_fdsem_data)));
 
 #ifdef HAVE_EVENTFD
     if ((f->efd = eventfd(0)) >= 0) {
         pa_make_fd_cloexec(f->efd);
         f->fds[0] = f->fds[1] = -1;
-
     } else
 #endif
     {
@@ -105,9 +103,57 @@ pa_fdsem *pa_fdsem_new(void) {
         pa_make_fd_cloexec(f->fds[1]);
     }
 
-    pa_atomic_store(&f->waiting, 0);
-    pa_atomic_store(&f->signalled, 0);
-    pa_atomic_store(&f->in_pipe, 0);
+    f->data = (pa_fdsem_data*) ((uint8_t*) f + PA_ALIGN(sizeof(pa_fdsem)));
+
+    pa_atomic_store(&f->data->waiting, 0);
+    pa_atomic_store(&f->data->signalled, 0);
+    pa_atomic_store(&f->data->in_pipe, 0);
+
+    return f;
+}
+
+pa_fdsem *pa_fdsem_open_shm(pa_fdsem_data *data, int event_fd) {
+    pa_fdsem *f = NULL;
+
+    pa_assert(data);
+    pa_assert(event_fd >= 0);
+
+#ifdef HAVE_EVENTFD
+    f = pa_xnew(pa_fdsem, 1);
+
+    f->efd = event_fd;
+    pa_make_fd_cloexec(f->efd);
+    f->fds[0] = f->fds[1] = -1;
+    f->data = data;
+#endif
+
+    return f;
+}
+
+pa_fdsem *pa_fdsem_new_shm(pa_fdsem_data *data, int* event_fd) {
+    pa_fdsem *f = NULL;
+
+    pa_assert(data);
+    pa_assert(event_fd);
+
+#ifdef HAVE_EVENTFD
+
+    f = pa_xnew(pa_fdsem, 1);
+
+    if ((f->efd = eventfd(0)) < 0) {
+        pa_xfree(f);
+        return NULL;
+    }
+
+    pa_make_fd_cloexec(f->efd);
+    f->fds[0] = f->fds[1] = -1;
+    f->data = data;
+
+    pa_atomic_store(&f->data->waiting, 0);
+    pa_atomic_store(&f->data->signalled, 0);
+    pa_atomic_store(&f->data->in_pipe, 0);
+
+#endif
 
     return f;
 }
@@ -128,7 +174,7 @@ static void flush(pa_fdsem *f) {
     ssize_t r;
     pa_assert(f);
 
-    if (pa_atomic_load(&f->in_pipe) <= 0)
+    if (pa_atomic_load(&f->data->in_pipe) <= 0)
         return;
 
     do {
@@ -151,19 +197,19 @@ static void flush(pa_fdsem *f) {
             continue;
         }
 
-    } while (pa_atomic_sub(&f->in_pipe, r) > r);
+    } while (pa_atomic_sub(&f->data->in_pipe, r) > r);
 }
 
 void pa_fdsem_post(pa_fdsem *f) {
     pa_assert(f);
 
-    if (pa_atomic_cmpxchg(&f->signalled, 0, 1)) {
+    if (pa_atomic_cmpxchg(&f->data->signalled, 0, 1)) {
 
-        if (pa_atomic_load(&f->waiting)) {
+        if (pa_atomic_load(&f->data->waiting)) {
             ssize_t r;
             char x = 'x';
 
-            pa_atomic_inc(&f->in_pipe);
+            pa_atomic_inc(&f->data->in_pipe);
 
             for (;;) {
 
@@ -194,12 +240,12 @@ void pa_fdsem_wait(pa_fdsem *f) {
 
     flush(f);
 
-    if (pa_atomic_cmpxchg(&f->signalled, 1, 0))
+    if (pa_atomic_cmpxchg(&f->data->signalled, 1, 0))
         return;
 
-    pa_atomic_inc(&f->waiting);
+    pa_atomic_inc(&f->data->waiting);
 
-    while (!pa_atomic_cmpxchg(&f->signalled, 1, 0)) {
+    while (!pa_atomic_cmpxchg(&f->data->signalled, 1, 0)) {
         char x[10];
         ssize_t r;
 
@@ -221,10 +267,10 @@ void pa_fdsem_wait(pa_fdsem *f) {
             continue;
         }
 
-        pa_atomic_sub(&f->in_pipe, r);
+        pa_atomic_sub(&f->data->in_pipe, r);
     }
 
-    pa_assert_se(pa_atomic_dec(&f->waiting) >= 1);
+    pa_assert_se(pa_atomic_dec(&f->data->waiting) >= 1);
 }
 
 int pa_fdsem_try(pa_fdsem *f) {
@@ -232,7 +278,7 @@ int pa_fdsem_try(pa_fdsem *f) {
 
     flush(f);
 
-    if (pa_atomic_cmpxchg(&f->signalled, 1, 0))
+    if (pa_atomic_cmpxchg(&f->data->signalled, 1, 0))
         return 1;
 
     return 0;
@@ -254,13 +300,13 @@ int pa_fdsem_before_poll(pa_fdsem *f) {
 
     flush(f);
 
-    if (pa_atomic_cmpxchg(&f->signalled, 1, 0))
+    if (pa_atomic_cmpxchg(&f->data->signalled, 1, 0))
         return -1;
 
-    pa_atomic_inc(&f->waiting);
+    pa_atomic_inc(&f->data->waiting);
 
-    if (pa_atomic_cmpxchg(&f->signalled, 1, 0)) {
-        pa_assert_se(pa_atomic_dec(&f->waiting) >= 1);
+    if (pa_atomic_cmpxchg(&f->data->signalled, 1, 0)) {
+        pa_assert_se(pa_atomic_dec(&f->data->waiting) >= 1);
         return -1;
     }
     return 0;
@@ -269,11 +315,11 @@ int pa_fdsem_before_poll(pa_fdsem *f) {
 int pa_fdsem_after_poll(pa_fdsem *f) {
     pa_assert(f);
 
-    pa_assert_se(pa_atomic_dec(&f->waiting) >= 1);
+    pa_assert_se(pa_atomic_dec(&f->data->waiting) >= 1);
 
     flush(f);
 
-    if (pa_atomic_cmpxchg(&f->signalled, 1, 0))
+    if (pa_atomic_cmpxchg(&f->data->signalled, 1, 0))
         return 1;
 
     return 0;
index f38ef20..f4f7b99 100644 (file)
 
 typedef struct pa_fdsem pa_fdsem;
 
+typedef struct pa_fdsem_data {
+    pa_atomic_t waiting;
+    pa_atomic_t signalled;
+    pa_atomic_t in_pipe;
+} pa_fdsem_data;
+
 pa_fdsem *pa_fdsem_new(void);
+pa_fdsem *pa_fdsem_open_shm(pa_fdsem_data *data, int event_fd);
+pa_fdsem *pa_fdsem_new_shm(pa_fdsem_data *data, int* event_fd);
 void pa_fdsem_free(pa_fdsem *f);
 
 void pa_fdsem_post(pa_fdsem *f);
index daf0fec..3d9a89a 100644 (file)
@@ -25,9 +25,9 @@
 ***/
 
 #include <pulse/def.h>
+#include <pulse/gccmacro.h>
 
 #include <pulsecore/once.h>
-#include <pulsecore/gccmacro.h>
 
 /* A multiple-reader multipler-write lock-free free list implementation */
 
index b3bd600..c288980 100644 (file)
   USA.
 ***/
 
-#include <pulsecore/llist.h>
 #include <pulse/xmalloc.h>
-#include <pulsecore/gccmacro.h>
+#include <pulse/gccmacro.h>
+
+#include <pulsecore/llist.h>
 
 typedef struct pa_hook_slot pa_hook_slot;
 typedef struct pa_hook pa_hook;
index 5fd2189..85bbadc 100644 (file)
@@ -49,7 +49,6 @@ struct pa_ioline {
     pa_iochannel *io;
     pa_defer_event *defer_event;
     pa_mainloop_api *mainloop;
-    int dead;
 
     char *wbuf;
     size_t wbuf_length, wbuf_index, wbuf_valid_length;
@@ -57,10 +56,11 @@ struct pa_ioline {
     char *rbuf;
     size_t rbuf_length, rbuf_index, rbuf_valid_length;
 
-    void (*callback)(pa_ioline*io, const char *s, void *userdata);
+    pa_ioline_cb_t callback;
     void *userdata;
 
-    int defer_close;
+    pa_bool_t dead:1;
+    pa_bool_t defer_close:1;
 };
 
 static void io_callback(pa_iochannel*io, void *userdata);
@@ -73,7 +73,6 @@ pa_ioline* pa_ioline_new(pa_iochannel *io) {
     l = pa_xnew(pa_ioline, 1);
     PA_REFCNT_INIT(l);
     l->io = io;
-    l->dead = 0;
 
     l->wbuf = NULL;
     l->wbuf_length = l->wbuf_index = l->wbuf_valid_length = 0;
@@ -89,7 +88,8 @@ pa_ioline* pa_ioline_new(pa_iochannel *io) {
     l->defer_event = l->mainloop->defer_new(l->mainloop, defer_callback, l);
     l->mainloop->defer_enable(l->defer_event, 0);
 
-    l->defer_close = 0;
+    l->dead = FALSE;
+    l->defer_close = FALSE;
 
     pa_iochannel_set_callback(io, io_callback, l);
 
@@ -130,7 +130,7 @@ void pa_ioline_close(pa_ioline *l) {
     pa_assert(l);
     pa_assert(PA_REFCNT_VALUE(l) >= 1);
 
-    l->dead = 1;
+    l->dead = TRUE;
 
     if (l->io) {
         pa_iochannel_free(l->io);
@@ -166,11 +166,13 @@ void pa_ioline_puts(pa_ioline *l, const char *c) {
         /* In case the allocated buffer is too small, enlarge it. */
         if (l->wbuf_valid_length + len > l->wbuf_length) {
             size_t n = l->wbuf_valid_length+len;
-            char *new = pa_xmalloc(n);
+            char *new = pa_xnew(char, n);
+
             if (l->wbuf) {
                 memcpy(new, l->wbuf+l->wbuf_index, l->wbuf_valid_length);
                 pa_xfree(l->wbuf);
             }
+
             l->wbuf = new;
             l->wbuf_length = n;
             l->wbuf_index = 0;
@@ -191,15 +193,18 @@ void pa_ioline_puts(pa_ioline *l, const char *c) {
     }
 }
 
-void pa_ioline_set_callback(pa_ioline*l, void (*callback)(pa_ioline*io, const char *s, void *userdata), void *userdata) {
+void pa_ioline_set_callback(pa_ioline*l, pa_ioline_cb_t callback, void *userdata) {
     pa_assert(l);
     pa_assert(PA_REFCNT_VALUE(l) >= 1);
 
+    if (l->dead)
+        return;
+
     l->callback = callback;
     l->userdata = userdata;
 }
 
-static void failure(pa_ioline *l, int process_leftover) {
+static void failure(pa_ioline *l, pa_bool_t process_leftover) {
     pa_assert(l);
     pa_assert(PA_REFCNT_VALUE(l) >= 1);
     pa_assert(!l->dead);
@@ -247,7 +252,7 @@ static void scan_for_lines(pa_ioline *l, size_t skip) {
             l->rbuf_index = 0;
 
         if (l->callback)
-            l->callback(l, p, l->userdata);
+            l->callback(l, pa_strip_nl(p), l->userdata);
 
         skip = 0;
     }
@@ -282,7 +287,7 @@ static int do_read(pa_ioline *l) {
                     memmove(l->rbuf, l->rbuf+l->rbuf_index, l->rbuf_valid_length);
             } else {
                 /* Enlarge the buffer */
-                char *new = pa_xmalloc(n);
+                char *new = pa_xnew(char, n);
                 if (l->rbuf_valid_length)
                     memcpy(new, l->rbuf+l->rbuf_index, l->rbuf_valid_length);
                 pa_xfree(l->rbuf);
@@ -299,11 +304,15 @@ static int do_read(pa_ioline *l) {
 
         /* Read some data */
         if ((r = pa_iochannel_read(l->io, l->rbuf+l->rbuf_index+l->rbuf_valid_length, len)) <= 0) {
+
+            if (r < 0 && errno == EAGAIN)
+                return 0;
+
             if (r < 0 && errno != ECONNRESET) {
                 pa_log("read(): %s", pa_cstrerror(errno));
-                failure(l, 0);
+                failure(l, FALSE);
             } else
-                failure(l, 1);
+                failure(l, TRUE);
 
             return -1;
         }
@@ -328,10 +337,13 @@ static int do_write(pa_ioline *l) {
 
         if ((r = pa_iochannel_write(l->io, l->wbuf+l->wbuf_index, l->wbuf_valid_length)) <= 0) {
 
+            if (r < 0 && errno == EAGAIN)
+                return 0;
+
             if (r < 0 && errno != EPIPE)
                 pa_log("write(): %s", pa_cstrerror(errno));
 
-            failure(l, 0);
+            failure(l, FALSE);
 
             return -1;
         }
@@ -363,7 +375,7 @@ static void do_work(pa_ioline *l) {
         do_write(l);
 
     if (l->defer_close && !l->wbuf_valid_length)
-        failure(l, 1);
+        failure(l, TRUE);
 
     pa_ioline_unref(l);
 }
@@ -393,7 +405,7 @@ void pa_ioline_defer_close(pa_ioline *l) {
     pa_assert(l);
     pa_assert(PA_REFCNT_VALUE(l) >= 1);
 
-    l->defer_close = 1;
+    l->defer_close = TRUE;
 
     if (!l->wbuf_valid_length)
         l->mainloop->defer_enable(l->defer_event, 1);
index 8475b79..f4edc7b 100644 (file)
@@ -33,6 +33,8 @@
 
 typedef struct pa_ioline pa_ioline;
 
+typedef void (*pa_ioline_cb_t)(pa_ioline*io, const char *s, void *userdata);
+
 pa_ioline* pa_ioline_new(pa_iochannel *io);
 void pa_ioline_unref(pa_ioline *l);
 pa_ioline* pa_ioline_ref(pa_ioline *l);
@@ -45,7 +47,7 @@ void pa_ioline_puts(pa_ioline *s, const char *c);
 void pa_ioline_printf(pa_ioline *s, const char *format, ...) PA_GCC_PRINTF_ATTR(2,3);
 
 /* Set the callback function that is called for every recieved line */
-void pa_ioline_set_callback(pa_ioline*io, void (*callback)(pa_ioline*io, const char *s, void *userdata), void *userdata);
+void pa_ioline_set_callback(pa_ioline*io, pa_ioline_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);
index c824e84..b5929ec 100644 (file)
@@ -30,6 +30,7 @@
 #include <stdio.h>
 #include <unistd.h>
 #include <string.h>
+#include <errno.h>
 
 #ifdef HAVE_SYSLOG_H
 #include <syslog.h>
@@ -108,7 +109,12 @@ void pa_log_levelv_meta(
         va_list ap) {
 
     const char *e;
-    char *text, *t, *n, *location;
+    char *t, *n;
+    int saved_errno = errno;
+
+    /* We don't use dynamic memory allocation here to minimize the hit
+     * in RT threads */
+    char text[1024], location[128];
 
     pa_assert(level < PA_LOG_LEVEL_MAX);
     pa_assert(format);
@@ -116,17 +122,19 @@ void pa_log_levelv_meta(
     if ((e = getenv(ENV_LOGLEVEL)))
         maximal_level = atoi(e);
 
-    if (level > maximal_level)
+    if (level > maximal_level) {
+        errno = saved_errno;
         return;
+    }
 
-    text = pa_vsprintf_malloc(format, ap);
+    pa_vsnprintf(text, sizeof(text), format, ap);
 
     if (getenv(ENV_LOGMETA) && file && line > 0 && func)
-        location = pa_sprintf_malloc("[%s:%i %s()] ", file, line, func);
+        pa_snprintf(location, sizeof(location), "[%s:%i %s()] ", file, line, func);
     else if (file)
-        location = pa_sprintf_malloc("%s: ", pa_path_get_filename(file));
+        pa_snprintf(location, sizeof(location), "%s: ", pa_path_get_filename(file));
     else
-        location = pa_xstrdup("");
+        location[0] = 0;
 
     if (!pa_utf8_valid(text))
         pa_log_level(level, __FILE__": invalid UTF-8 string following below:");
@@ -158,6 +166,8 @@ void pa_log_levelv_meta(
                 }
 #endif
 
+                /* We shouldn't be using dynamic allocation here to
+                 * minimize the hit in RT threads */
                 local_t = pa_utf8_to_locale(t);
                 if (!local_t)
                     fprintf(stderr, "%c: %s%s%s%s\n", level_to_char[level], location, prefix, t, suffix);
@@ -189,11 +199,10 @@ void pa_log_levelv_meta(
 #endif
 
             case PA_LOG_USER: {
-                char *x;
+                char x[1024];
 
-                x = pa_sprintf_malloc("%s%s", location, t);
+                pa_snprintf(x, sizeof(x), "%s%s", location, t);
                 user_log_func(level, x);
-                pa_xfree(x);
 
                 break;
             }
@@ -204,8 +213,7 @@ void pa_log_levelv_meta(
         }
     }
 
-    pa_xfree(text);
-    pa_xfree(location);
+    errno = saved_errno;
 }
 
 void pa_log_level_meta(
index b0711dc..765dd2e 100644 (file)
@@ -27,7 +27,7 @@
 
 #include <stdarg.h>
 #include <stdlib.h>
-#include <pulsecore/gccmacro.h>
+#include <pulse/gccmacro.h>
 
 /* A simple logging subsystem */
 
index 711396d..b83897a 100644 (file)
@@ -42,12 +42,14 @@ pa_void_func_t pa_load_sym(lt_dlhandle handle, const char *module, const char *s
     pa_void_func_t f;
 
     pa_assert(handle);
-    pa_assert(module);
     pa_assert(symbol);
 
-    if ((f = ((pa_void_func_t) (long) lt_dlsym(handle, symbol))))
+    if ((f = ((pa_void_func_t) (size_t) lt_dlsym(handle, symbol))))
         return f;
 
+    if (!module)
+        return NULL;
+
     /* As the .la files might have been cleansed from the system, we should
      * try with the ltdl prefix as well. */
 
@@ -57,7 +59,7 @@ pa_void_func_t pa_load_sym(lt_dlhandle handle, const char *module, const char *s
         if (!isalnum(*c))
             *c = '_';
 
-    f = (pa_void_func_t) (long) lt_dlsym(handle, sn);
+    f = (pa_void_func_t) (size_t) lt_dlsym(handle, sn);
     pa_xfree(sn);
 
     return f;
index ba53817..1d9eafd 100644 (file)
 #include <stdlib.h>
 
 #include <pulsecore/log.h>
-#include <pulsecore/gccmacro.h>
+#include <pulse/gccmacro.h>
 
 #ifndef PACKAGE
 #error "Please include config.h before including this file!"
 #endif
 
+#ifndef PA_LIKELY
+#ifdef __GNUC__
+#define PA_LIKELY(x) (__builtin_expect(!!(x),1))
+#define PA_UNLIKELY(x) (__builtin_expect((x),0))
+#else
+#define PA_LIKELY(x) (x)
+#define PA_UNLIKELY(x) (x)
+#endif
+#endif
+
 #if defined(PAGE_SIZE)
 #define PA_PAGE_SIZE ((size_t) PAGE_SIZE)
 #elif defined(PAGESIZE)
@@ -67,19 +77,53 @@ static inline size_t pa_page_align(size_t l) {
 
 #define PA_ELEMENTSOF(x) (sizeof(x)/sizeof((x)[0]))
 
-#ifndef MAX
-#define MAX(a, b) ((a) > (b) ? (a) : (b))
+/* The users of PA_MIN and PA_MAX should be aware that these macros on
+ * non-GCC executed code with side effects twice. It is thus
+ * considered misuse to use code with side effects as arguments to MIN
+ * and MAX. */
+
+#ifdef __GNUC__
+#define PA_MAX(a,b)                             \
+    __extension__ ({ typeof(a) _a = (a);        \
+            typeof(b) _b = (b);                 \
+            _a > _b ? _a : _b;                  \
+        })
+#else
+#define PA_MAX(a, b) ((a) > (b) ? (a) : (b))
 #endif
 
-#ifndef MIN
-#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#ifdef __GNUC__
+#define PA_MIN(a,b)                             \
+    __extension__ ({ typeof(a) _a = (a);        \
+            typeof(b) _b = (b);                 \
+            _a < _b ? _a : _b;                  \
+        })
+#else
+#define PA_MIN(a, b) ((a) < (b) ? (a) : (b))
 #endif
 
-#ifndef CLAMP
-#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
+#ifdef __GNUC__
+#define PA_CLAMP(x, low, high)                                          \
+    __extension__ ({ typeof(x) _x = (x);                                \
+            typeof(low) _low = (low);                                   \
+            typeof(high) _high = (high);                                \
+            ((_x > _high) ? _high : ((_x < _low) ? _low : _x));         \
+        })
+#else
+#define PA_CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
 #endif
 
+#ifdef __GNUC__
+#define PA_CLAMP_UNLIKELY(x, low, high)                                 \
+    __extension__ ({ typeof(x) _x = (x);                                \
+            typeof(low) _low = (low);                                   \
+            typeof(high) _high = (high);                                \
+            (PA_UNLIKELY(_x > _high) ? _high : (PA_UNLIKELY(_x < _low) ? _low : _x)); \
+        })
+#else
 #define PA_CLAMP_UNLIKELY(x, low, high) (PA_UNLIKELY((x) > (high)) ? (high) : (PA_UNLIKELY((x) < (low)) ? (low) : (x)))
+#endif
+
 /* We don't define a PA_CLAMP_LIKELY here, because it doesn't really
  * make sense: we cannot know if it is more likely that the values is
  * lower or greater than the boundaries.*/
@@ -166,8 +210,17 @@ typedef int pa_bool_t;
 #define PA_PATH_SEP_CHAR '/'
 #endif
 
-static inline const char *pa_strnull(const char *x) {
-    return x ? x : "(null)";
-}
+#ifdef __GNUC__
+
+#define PA_WARN_REFERENCE(sym, msg)                  \
+    __asm__(".section .gnu.warning." #sym);          \
+    __asm__(".asciz \"" msg "\"");                   \
+    __asm__(".previous")
+
+#else
+
+#define PA_WARN_REFERENCE(sym, msg)
+
+#endif
 
 #endif
index 8ca7c96..e12f84f 100644 (file)
@@ -197,7 +197,6 @@ int pa_mcalign_pop(pa_mcalign *m, pa_memchunk *c) {
 
     /* There's simply nothing */
     return -1;
-
 }
 
 size_t pa_mcalign_csize(pa_mcalign *m, size_t l) {
@@ -211,3 +210,11 @@ size_t pa_mcalign_csize(pa_mcalign *m, size_t l) {
 
     return (l/m->base)*m->base;
 }
+
+void pa_mcalign_flush(pa_mcalign *m) {
+    pa_memchunk chunk;
+    pa_assert(m);
+
+    while (pa_mcalign_pop(m, &chunk) >= 0)
+        pa_memblock_unref(chunk.memblock);
+}
index 6ff8f94..6c8b8d5 100644 (file)
@@ -79,4 +79,7 @@ int pa_mcalign_pop(pa_mcalign *m, pa_memchunk *c);
 /* If we pass l bytes in now, how many bytes would we get out? */
 size_t pa_mcalign_csize(pa_mcalign *m, size_t l);
 
+/* Flush what's still stored in the aligner */
+void pa_mcalign_flush(pa_mcalign *m);
+
 #endif
index 99b5a13..7005b44 100644 (file)
 
 #include "memblock.h"
 
-#define PA_MEMPOOL_SLOTS_MAX 128
-#define PA_MEMPOOL_SLOT_SIZE (16*1024)
+/* We can allocate 64*1024*1024 bytes at maximum. That's 64MB. Please
+ * note that the footprint is usually much smaller, since the data is
+ * stored in SHM and our OS does not commit the memory before we use
+ * it for the first time. */
+#define PA_MEMPOOL_SLOTS_MAX 1024
+#define PA_MEMPOOL_SLOT_SIZE (64*1024)
 
 #define PA_MEMEXPORT_SLOTS_MAX 128
 
@@ -59,7 +63,9 @@ struct pa_memblock {
     pa_mempool *pool;
 
     pa_memblock_type_t type;
-    int read_only; /* boolean */
+
+    pa_bool_t read_only:1;
+    pa_bool_t is_silence:1;
 
     pa_atomic_ptr_t data;
     size_t length;
@@ -125,11 +131,6 @@ struct pa_memexport {
     PA_LLIST_FIELDS(pa_memexport);
 };
 
-struct mempool_slot {
-    PA_LLIST_FIELDS(struct mempool_slot);
-    /* the actual data follows immediately hereafter */
-};
-
 struct pa_mempool {
     pa_semaphore *semaphore;
     pa_mutex *mutex;
@@ -202,7 +203,7 @@ pa_memblock *pa_memblock_new(pa_mempool *p, size_t length) {
     pa_memblock *b;
 
     pa_assert(p);
-    pa_assert(length > 0);
+    pa_assert(length);
 
     if (!(b = pa_memblock_new_pool(p, length)))
         b = memblock_new_appended(p, length);
@@ -215,18 +216,18 @@ static pa_memblock *memblock_new_appended(pa_mempool *p, size_t length) {
     pa_memblock *b;
 
     pa_assert(p);
-    pa_assert(length > 0);
+    pa_assert(length);
 
     /* If -1 is passed as length we choose the size for the caller. */
 
     if (length == (size_t) -1)
-        length = p->block_size - PA_ALIGN(sizeof(struct mempool_slot)) - PA_ALIGN(sizeof(pa_memblock));
+        length = p->block_size - PA_ALIGN(sizeof(pa_memblock));
 
     b = pa_xmalloc(PA_ALIGN(sizeof(pa_memblock)) + length);
     PA_REFCNT_INIT(b);
     b->pool = p;
     b->type = PA_MEMBLOCK_APPENDED;
-    b->read_only = 0;
+    b->read_only = b->is_silence = FALSE;
     pa_atomic_ptr_store(&b->data, (uint8_t*) b + PA_ALIGN(sizeof(pa_memblock)));
     b->length = length;
     pa_atomic_store(&b->n_acquired, 0);
@@ -252,7 +253,7 @@ static struct mempool_slot* mempool_allocate_slot(pa_mempool *p) {
             slot = (struct mempool_slot*) ((uint8_t*) p->memory.ptr + (p->block_size * idx));
 
         if (!slot) {
-            pa_log_debug("Pool full");
+            pa_log_info("Pool full");
             pa_atomic_inc(&p->stat.n_pool_full);
             return NULL;
         }
@@ -261,11 +262,9 @@ static struct mempool_slot* mempool_allocate_slot(pa_mempool *p) {
     return slot;
 }
 
-/* No lock necessary */
-static void* mempool_slot_data(struct mempool_slot *slot) {
-    pa_assert(slot);
-
-    return (uint8_t*) slot + PA_ALIGN(sizeof(struct mempool_slot));
+/* No lock necessary, totally redundant anyway */
+static inline void* mempool_slot_data(struct mempool_slot *slot) {
+    return slot;
 }
 
 /* No lock necessary */
@@ -294,7 +293,7 @@ pa_memblock *pa_memblock_new_pool(pa_mempool *p, size_t length) {
     struct mempool_slot *slot;
 
     pa_assert(p);
-    pa_assert(length > 0);
+    pa_assert(length);
 
     /* If -1 is passed as length we choose the size for the caller: we
      * take the largest size that fits in one of our slots. */
@@ -302,7 +301,7 @@ pa_memblock *pa_memblock_new_pool(pa_mempool *p, size_t length) {
     if (length == (size_t) -1)
         length = pa_mempool_block_size_max(p);
 
-    if (p->block_size - PA_ALIGN(sizeof(struct mempool_slot)) >= PA_ALIGN(sizeof(pa_memblock)) + length) {
+    if (p->block_size >= PA_ALIGN(sizeof(pa_memblock)) + length) {
 
         if (!(slot = mempool_allocate_slot(p)))
             return NULL;
@@ -311,7 +310,7 @@ pa_memblock *pa_memblock_new_pool(pa_mempool *p, size_t length) {
         b->type = PA_MEMBLOCK_POOL;
         pa_atomic_ptr_store(&b->data, (uint8_t*) b + PA_ALIGN(sizeof(pa_memblock)));
 
-    } else if (p->block_size - PA_ALIGN(sizeof(struct mempool_slot)) >= length) {
+    } else if (p->block_size >= length) {
 
         if (!(slot = mempool_allocate_slot(p)))
             return NULL;
@@ -323,14 +322,14 @@ pa_memblock *pa_memblock_new_pool(pa_mempool *p, size_t length) {
         pa_atomic_ptr_store(&b->data, mempool_slot_data(slot));
 
     } else {
-        pa_log_debug("Memory block too large for pool: %lu > %lu", (unsigned long) length, (unsigned long) (p->block_size - PA_ALIGN(sizeof(struct mempool_slot))));
+        pa_log_debug("Memory block too large for pool: %lu > %lu", (unsigned long) length, (unsigned long) p->block_size);
         pa_atomic_inc(&p->stat.n_too_large_for_pool);
         return NULL;
     }
 
     PA_REFCNT_INIT(b);
     b->pool = p;
-    b->read_only = 0;
+    b->read_only = b->is_silence = FALSE;
     b->length = length;
     pa_atomic_store(&b->n_acquired, 0);
     pa_atomic_store(&b->please_signal, 0);
@@ -340,13 +339,13 @@ pa_memblock *pa_memblock_new_pool(pa_mempool *p, size_t length) {
 }
 
 /* No lock necessary */
-pa_memblock *pa_memblock_new_fixed(pa_mempool *p, void *d, size_t length, int read_only) {
+pa_memblock *pa_memblock_new_fixed(pa_mempool *p, void *d, size_t length, pa_bool_t read_only) {
     pa_memblock *b;
 
     pa_assert(p);
     pa_assert(d);
     pa_assert(length != (size_t) -1);
-    pa_assert(length > 0);
+    pa_assert(length);
 
     if (!(b = pa_flist_pop(PA_STATIC_FLIST_GET(unused_memblocks))))
         b = pa_xnew(pa_memblock, 1);
@@ -354,6 +353,7 @@ pa_memblock *pa_memblock_new_fixed(pa_mempool *p, void *d, size_t length, int re
     b->pool = p;
     b->type = PA_MEMBLOCK_FIXED;
     b->read_only = read_only;
+    b->is_silence = FALSE;
     pa_atomic_ptr_store(&b->data, d);
     b->length = length;
     pa_atomic_store(&b->n_acquired, 0);
@@ -364,12 +364,12 @@ pa_memblock *pa_memblock_new_fixed(pa_mempool *p, void *d, size_t length, int re
 }
 
 /* No lock necessary */
-pa_memblock *pa_memblock_new_user(pa_mempool *p, void *d, size_t length, void (*free_cb)(void *p), int read_only) {
+pa_memblock *pa_memblock_new_user(pa_mempool *p, void *d, size_t length, pa_free_cb_t free_cb, pa_bool_t read_only) {
     pa_memblock *b;
 
     pa_assert(p);
     pa_assert(d);
-    pa_assert(length > 0);
+    pa_assert(length);
     pa_assert(length != (size_t) -1);
     pa_assert(free_cb);
 
@@ -379,6 +379,7 @@ pa_memblock *pa_memblock_new_user(pa_mempool *p, void *d, size_t length, void (*
     b->pool = p;
     b->type = PA_MEMBLOCK_USER;
     b->read_only = read_only;
+    b->is_silence = FALSE;
     pa_atomic_ptr_store(&b->data, d);
     b->length = length;
     pa_atomic_store(&b->n_acquired, 0);
@@ -391,7 +392,7 @@ pa_memblock *pa_memblock_new_user(pa_mempool *p, void *d, size_t length, void (*
 }
 
 /* No lock necessary */
-int pa_memblock_is_read_only(pa_memblock *b) {
+pa_bool_t pa_memblock_is_read_only(pa_memblock *b) {
     pa_assert(b);
     pa_assert(PA_REFCNT_VALUE(b) > 0);
 
@@ -399,13 +400,27 @@ int pa_memblock_is_read_only(pa_memblock *b) {
 }
 
 /* No lock necessary */
-int pa_memblock_ref_is_one(pa_memblock *b) {
-    int r;
+pa_bool_t pa_memblock_is_silence(pa_memblock *b) {
+    pa_assert(b);
+    pa_assert(PA_REFCNT_VALUE(b) > 0);
+
+    return b->is_silence;
+}
+
+/* No lock necessary */
+void pa_memblock_set_is_silence(pa_memblock *b, pa_bool_t v) {
+    pa_assert(b);
+    pa_assert(PA_REFCNT_VALUE(b) > 0);
 
+    b->is_silence = v;
+}
+
+/* No lock necessary */
+pa_bool_t pa_memblock_ref_is_one(pa_memblock *b) {
+    int r;
     pa_assert(b);
 
-    r = PA_REFCNT_VALUE(b);
-    pa_assert(r > 0);
+    pa_assert_se((r = PA_REFCNT_VALUE(b)) > 0);
 
     return r == 1;
 }
@@ -567,7 +582,7 @@ static void memblock_make_local(pa_memblock *b) {
 
     pa_atomic_dec(&b->pool->stat.n_allocated_by_type[b->type]);
 
-    if (b->length <= b->pool->block_size - PA_ALIGN(sizeof(struct mempool_slot))) {
+    if (b->length <= b->pool->block_size) {
         struct mempool_slot *slot;
 
         if ((slot = mempool_allocate_slot(b->pool))) {
@@ -579,7 +594,7 @@ static void memblock_make_local(pa_memblock *b) {
             pa_atomic_ptr_store(&b->data, new_data);
 
             b->type = PA_MEMBLOCK_POOL_EXTERNAL;
-            b->read_only = 0;
+            b->read_only = FALSE;
 
             goto finish;
         }
@@ -590,7 +605,7 @@ static void memblock_make_local(pa_memblock *b) {
     pa_atomic_ptr_store(&b->data, pa_xmemdup(pa_atomic_ptr_load(&b->data), b->length));
 
     b->type = PA_MEMBLOCK_USER;
-    b->read_only = 0;
+    b->read_only = FALSE;
 
 finish:
     pa_atomic_inc(&b->pool->stat.n_allocated_by_type[b->type]);
@@ -655,7 +670,7 @@ static void memblock_replace_import(pa_memblock *b) {
         pa_mutex_unlock(seg->import->mutex);
 }
 
-pa_mempool* pa_mempool_new(int shared) {
+pa_mempool* pa_mempool_new(pa_bool_t shared) {
     pa_mempool *p;
 
     p = pa_xnew(pa_mempool, 1);
@@ -669,8 +684,6 @@ pa_mempool* pa_mempool_new(int shared) {
 
     p->n_blocks = PA_MEMPOOL_SLOTS_MAX;
 
-    pa_assert(p->block_size > PA_ALIGN(sizeof(struct mempool_slot)));
-
     if (pa_shm_create_rw(&p->memory, p->n_blocks * p->block_size, shared, 0700) < 0) {
         pa_xfree(p);
         return NULL;
@@ -726,7 +739,7 @@ const pa_mempool_stat* pa_mempool_get_stat(pa_mempool *p) {
 size_t pa_mempool_block_size_max(pa_mempool *p) {
     pa_assert(p);
 
-    return p->block_size - PA_ALIGN(sizeof(struct mempool_slot)) - PA_ALIGN(sizeof(pa_memblock));
+    return p->block_size - PA_ALIGN(sizeof(pa_memblock));
 }
 
 /* No lock necessary */
@@ -743,9 +756,7 @@ void pa_mempool_vacuum(pa_mempool *p) {
             ;
 
     while ((slot = pa_flist_pop(list))) {
-        pa_shm_punch(&p->memory,
-                     (uint8_t*) slot - (uint8_t*) p->memory.ptr + PA_ALIGN(sizeof(struct mempool_slot)),
-                     p->block_size - PA_ALIGN(sizeof(struct mempool_slot)));
+        pa_shm_punch(&p->memory, (uint8_t*) slot - (uint8_t*) p->memory.ptr, p->block_size);
 
         while (pa_flist_push(p->free_slots, slot))
             ;
@@ -767,7 +778,7 @@ int pa_mempool_get_shm_id(pa_mempool *p, uint32_t *id) {
 }
 
 /* No lock necessary */
-int pa_mempool_is_shared(pa_mempool *p) {
+pa_bool_t pa_mempool_is_shared(pa_mempool *p) {
     pa_assert(p);
 
     return !!p->memory.shared;
@@ -886,7 +897,8 @@ pa_memblock* pa_memimport_get(pa_memimport *i, uint32_t block_id, uint32_t shm_i
     PA_REFCNT_INIT(b);
     b->pool = i->pool;
     b->type = PA_MEMBLOCK_IMPORTED;
-    b->read_only = 1;
+    b->read_only = TRUE;
+    b->is_silence = FALSE;
     pa_atomic_ptr_store(&b->data, (uint8_t*) seg->memory.ptr + offset);
     b->length = size;
     pa_atomic_store(&b->n_acquired, 0);
index c704014..8dc3f5a 100644 (file)
@@ -87,13 +87,13 @@ pa_memblock *pa_memblock_new(pa_mempool *, size_t length);
 pa_memblock *pa_memblock_new_pool(pa_mempool *, size_t length);
 
 /* Allocate a new memory block of type PA_MEMBLOCK_USER */
-pa_memblock *pa_memblock_new_user(pa_mempool *, void *data, size_t length, void (*free_cb)(void *p), int read_only);
+pa_memblock *pa_memblock_new_user(pa_mempool *, void *data, size_t length, pa_free_cb_t free_cb, pa_bool_t read_only);
 
 /* A special case of pa_memblock_new_user: take a memory buffer previously allocated with pa_xmalloc()  */
 #define pa_memblock_new_malloced(p,data,length) pa_memblock_new_user(p, data, length, pa_xfree, 0)
 
 /* Allocate a new memory block of type PA_MEMBLOCK_FIXED */
-pa_memblock *pa_memblock_new_fixed(pa_mempool *, void *data, size_t length, int read_only);
+pa_memblock *pa_memblock_new_fixed(pa_mempool *, void *data, size_t length, pa_bool_t read_only);
 
 void pa_memblock_unref(pa_memblock*b);
 pa_memblock* pa_memblock_ref(pa_memblock*b);
@@ -106,8 +106,11 @@ function is not multiple caller safe, i.e. needs to be locked
 manually if called from more than one thread at the same time.  */
 void pa_memblock_unref_fixed(pa_memblock*b);
 
-int pa_memblock_is_read_only(pa_memblock *b);
-int pa_memblock_ref_is_one(pa_memblock *b);
+pa_bool_t pa_memblock_is_read_only(pa_memblock *b);
+pa_bool_t pa_memblock_is_silence(pa_memblock *b);
+pa_bool_t pa_memblock_ref_is_one(pa_memblock *b);
+void pa_memblock_set_is_silence(pa_memblock *b, pa_bool_t v);
+
 void* pa_memblock_acquire(pa_memblock *b);
 void pa_memblock_release(pa_memblock *b);
 size_t pa_memblock_get_length(pa_memblock *b);
@@ -116,12 +119,12 @@ pa_mempool * pa_memblock_get_pool(pa_memblock *b);
 pa_memblock *pa_memblock_will_need(pa_memblock *b);
 
 /* The memory block manager */
-pa_mempool* pa_mempool_new(int shared);
+pa_mempool* pa_mempool_new(pa_bool_t shared);
 void pa_mempool_free(pa_mempool *p);
 const pa_mempool_stat* pa_mempool_get_stat(pa_mempool *p);
 void pa_mempool_vacuum(pa_mempool *p);
 int pa_mempool_get_shm_id(pa_mempool *p, uint32_t *id);
-int pa_mempool_is_shared(pa_mempool *p);
+pa_bool_t pa_mempool_is_shared(pa_mempool *p);
 size_t pa_mempool_block_size_max(pa_mempool *p);
 
 /* For recieving blocks from other nodes */
index 8247fea..c047e56 100644 (file)
@@ -50,11 +50,12 @@ PA_STATIC_FLIST_DECLARE(list_items, 0, pa_xfree);
 
 struct pa_memblockq {
     struct list_item *blocks, *blocks_tail;
+    struct list_item *current_read, *current_write;
     unsigned n_blocks;
-    size_t maxlength, tlength, base, prebuf, minreq;
+    size_t maxlength, tlength, base, prebuf, minreq, maxrewind;
     int64_t read_index, write_index;
     pa_bool_t in_prebuf;
-    pa_memblock *silence;
+    pa_memchunk silence;
     pa_mcalign *mcalign;
     int64_t missing;
     size_t requested;
@@ -67,7 +68,8 @@ pa_memblockq* pa_memblockq_new(
         size_t base,
         size_t prebuf,
         size_t minreq,
-        pa_memblock *silence) {
+        size_t maxrewind,
+        pa_memchunk *silence) {
 
     pa_memblockq* bq;
 
@@ -75,27 +77,34 @@ pa_memblockq* pa_memblockq_new(
 
     bq = pa_xnew(pa_memblockq, 1);
     bq->blocks = bq->blocks_tail = NULL;
+    bq->current_read = bq->current_write = NULL;
     bq->n_blocks = 0;
 
     bq->base = base;
     bq->read_index = bq->write_index = idx;
 
-    pa_log_debug("memblockq requested: maxlength=%lu, tlength=%lu, base=%lu, prebuf=%lu, minreq=%lu",
-        (unsigned long) maxlength, (unsigned long) tlength, (unsigned long) base, (unsigned long) prebuf, (unsigned long) minreq);
+    pa_log_debug("memblockq requested: maxlength=%lu, tlength=%lu, base=%lu, prebuf=%lu, minreq=%lu maxrewind=%lu",
+                 (unsigned long) maxlength, (unsigned long) tlength, (unsigned long) base, (unsigned long) prebuf, (unsigned long) minreq, (unsigned long) maxrewind);
 
-    bq->missing = bq->requested = bq->maxlength = bq->tlength = bq->prebuf = bq->minreq = 0;
+    bq->missing = bq->requested = bq->maxlength = bq->tlength = bq->prebuf = bq->minreq = bq->maxrewind = 0;
     bq->in_prebuf = TRUE;
 
     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_maxrewind(bq, maxrewind);
 
-    pa_log_debug("memblockq sanitized: maxlength=%lu, tlength=%lu, base=%lu, prebuf=%lu, minreq=%lu",
-        (unsigned long)bq->maxlength, (unsigned long)bq->tlength, (unsigned long)bq->base, (unsigned long)bq->prebuf, (unsigned long)bq->minreq);
+    pa_log_debug("memblockq sanitized: maxlength=%lu, tlength=%lu, base=%lu, prebuf=%lu, minreq=%lu maxrewind=%lu",
+                 (unsigned long) bq->maxlength, (unsigned long) bq->tlength, (unsigned long) bq->base, (unsigned long) bq->prebuf, (unsigned long) bq->minreq, (unsigned long) bq->maxrewind);
 
-    bq->silence = silence ? pa_memblock_ref(silence) : NULL;
-    bq->mcalign = NULL;
+    if (silence) {
+        bq->silence = *silence;
+        pa_memblock_ref(bq->silence.memblock);
+    } else
+        pa_memchunk_reset(&bq->silence);
+
+    bq->mcalign = pa_mcalign_new(bq->base);
 
     return bq;
 }
@@ -105,8 +114,8 @@ void pa_memblockq_free(pa_memblockq* bq) {
 
     pa_memblockq_flush(bq);
 
-    if (bq->silence)
-        pa_memblock_unref(bq->silence);
+    if (bq->silence.memblock)
+        pa_memblock_unref(bq->silence.memblock);
 
     if (bq->mcalign)
         pa_mcalign_free(bq->mcalign);
@@ -114,6 +123,62 @@ void pa_memblockq_free(pa_memblockq* bq) {
     pa_xfree(bq);
 }
 
+static void fix_current_read(pa_memblockq *bq) {
+    pa_assert(bq);
+
+    if (PA_UNLIKELY(!bq->blocks)) {
+        bq->current_read = NULL;
+        return;
+    }
+
+    if (PA_UNLIKELY(!bq->current_read))
+        bq->current_read = bq->blocks;
+
+    /* Scan left */
+    while (PA_UNLIKELY(bq->current_read->index > bq->read_index))
+
+        if (bq->current_read->prev)
+            bq->current_read = bq->current_read->prev;
+        else
+            break;
+
+    /* Scan right */
+    while (PA_LIKELY(bq->current_read != NULL) && PA_UNLIKELY(bq->current_read->index + (int64_t) bq->current_read->chunk.length <= bq->read_index))
+        bq->current_read = bq->current_read->next;
+
+    /* At this point current_read will either point at or left of the
+       next block to play. It may be NULL in case everything in
+       the queue was already played */
+}
+
+static void fix_current_write(pa_memblockq *bq) {
+    pa_assert(bq);
+
+    if (PA_UNLIKELY(!bq->blocks)) {
+        bq->current_write = NULL;
+        return;
+    }
+
+    if (PA_UNLIKELY(!bq->current_write))
+        bq->current_write = bq->blocks_tail;
+
+    /* Scan right */
+    while (PA_UNLIKELY(bq->current_write->index + (int64_t) bq->current_write->chunk.length <= bq->write_index))
+
+        if (bq->current_write->next)
+            bq->current_write = bq->current_write->next;
+        else
+            break;
+
+    /* Scan left */
+    while (PA_LIKELY(bq->current_write != NULL) && PA_UNLIKELY(bq->current_write->index > bq->write_index))
+        bq->current_write = bq->current_write->prev;
+
+    /* At this point current_write will either point at or right of
+       the next block to write data to. It may be NULL in case
+       everything in the queue is still to be played */
+}
+
 static void drop_block(pa_memblockq *bq, struct list_item *q) {
     pa_assert(bq);
     pa_assert(q);
@@ -122,13 +187,23 @@ static void drop_block(pa_memblockq *bq, struct list_item *q) {
 
     if (q->prev)
         q->prev->next = q->next;
-    else
+    else {
+        pa_assert(bq->blocks == q);
         bq->blocks = q->next;
+    }
 
     if (q->next)
         q->next->prev = q->prev;
-    else
+    else {
+        pa_assert(bq->blocks_tail == q);
         bq->blocks_tail = q->prev;
+    }
+
+    if (bq->current_write == q)
+        bq->current_write = q->prev;
+
+    if (bq->current_read == q)
+        bq->current_read = q->next;
 
     pa_memblock_unref(q->chunk.memblock);
 
@@ -138,6 +213,16 @@ static void drop_block(pa_memblockq *bq, struct list_item *q) {
     bq->n_blocks--;
 }
 
+static void drop_backlog(pa_memblockq *bq) {
+    int64_t boundary;
+    pa_assert(bq);
+
+    boundary = bq->read_index - bq->maxrewind;
+
+    while (bq->blocks && (bq->blocks->index + (int64_t) bq->blocks->chunk.length <= boundary))
+        drop_block(bq, bq->blocks);
+}
+
 static pa_bool_t can_push(pa_memblockq *bq, size_t l) {
     int64_t end;
 
@@ -152,10 +237,10 @@ static pa_bool_t can_push(pa_memblockq *bq, size_t l) {
             return TRUE;
     }
 
-    end = bq->blocks_tail ? bq->blocks_tail->index + bq->blocks_tail->chunk.length : 0;
+    end = bq->blocks_tail ? bq->blocks_tail->index + (int64_t) bq->blocks_tail->chunk.length : bq->write_index;
 
     /* Make sure that the list doesn't get too long */
-    if (bq->write_index + (int64_t)l > end)
+    if (bq->write_index + (int64_t) l > end)
         if (bq->write_index + l - bq->read_index > bq->maxlength)
             return FALSE;
 
@@ -182,28 +267,26 @@ int pa_memblockq_push(pa_memblockq* bq, const pa_memchunk *uchunk) {
     old = bq->write_index;
     chunk = *uchunk;
 
-    if (bq->read_index > bq->write_index) {
-
-        /* We currently have a buffer underflow, we need to drop some
-         * incoming data */
+    fix_current_write(bq);
+    q = bq->current_write;
 
-        size_t d = bq->read_index - bq->write_index;
+    /* First we advance the q pointer right of where we want to
+     * write to */
 
-        if (chunk.length > d) {
-            chunk.index += d;
-            chunk.length -= d;
-            bq->write_index += d;
-        } else {
-            /* We drop the incoming data completely */
-            bq->write_index += chunk.length;
-            goto finish;
-        }
+    if (q) {
+        while (bq->write_index + (int64_t) chunk.length > q->index)
+            if (q->next)
+                q = q->next;
+            else
+                break;
     }
 
+    if (!q)
+        q = bq->blocks_tail;
+
     /* We go from back to front to look for the right place to add
      * this new entry. Drop data we will overwrite on the way */
 
-    q = bq->blocks_tail;
     while (q) {
 
         if (bq->write_index >= q->index + (int64_t) q->chunk.length)
@@ -329,7 +412,7 @@ finish:
 
     delta = bq->write_index - old;
 
-    if (delta >= bq->requested) {
+    if (delta >= (int64_t) bq->requested) {
         delta -= bq->requested;
         bq->requested = 0;
     } else {
@@ -342,7 +425,16 @@ finish:
     return 0;
 }
 
-static pa_bool_t memblockq_check_prebuf(pa_memblockq *bq) {
+pa_bool_t pa_memblockq_prebuf_active(pa_memblockq *bq) {
+    pa_assert(bq);
+
+    if (bq->in_prebuf)
+        return pa_memblockq_get_length(bq) < bq->prebuf;
+    else
+        return bq->prebuf > 0 && bq->read_index >= bq->write_index;
+}
+
+static pa_bool_t update_prebuf(pa_memblockq *bq) {
     pa_assert(bq);
 
     if (bq->in_prebuf) {
@@ -364,34 +456,42 @@ static pa_bool_t memblockq_check_prebuf(pa_memblockq *bq) {
 }
 
 int pa_memblockq_peek(pa_memblockq* bq, pa_memchunk *chunk) {
+    int64_t d;
     pa_assert(bq);
     pa_assert(chunk);
 
     /* We need to pre-buffer */
-    if (memblockq_check_prebuf(bq))
+    if (update_prebuf(bq))
         return -1;
 
+    fix_current_read(bq);
+
     /* Do we need to spit out silence? */
-    if (!bq->blocks || bq->blocks->index > bq->read_index) {
+    if (!bq->current_read || bq->current_read->index > bq->read_index) {
 
         size_t length;
 
         /* How much silence shall we return? */
-        length = bq->blocks ? bq->blocks->index - bq->read_index : 0;
+        if (bq->current_read)
+            length = bq->current_read->index - bq->read_index;
+        else if (bq->write_index > bq->read_index)
+            length = (size_t) (bq->write_index - bq->read_index);
+        else
+            length = 0;
 
         /* We need to return silence, since no data is yet available */
-        if (bq->silence) {
-            chunk->memblock = pa_memblock_ref(bq->silence);
+        if (bq->silence.memblock) {
+            *chunk = bq->silence;
+            pa_memblock_ref(chunk->memblock);
 
-            if (!length || length > pa_memblock_get_length(chunk->memblock))
-                length = pa_memblock_get_length(chunk->memblock);
+            if (length > 0 && length < chunk->length)
+                chunk->length = length;
 
-            chunk->length = length;
         } else {
 
             /* If the memblockq is empty, return -1, otherwise return
              * the time to sleep */
-            if (!bq->blocks)
+            if (length <= 0)
                 return -1;
 
             chunk->memblock = NULL;
@@ -403,11 +503,14 @@ int pa_memblockq_peek(pa_memblockq* bq, pa_memchunk *chunk) {
     }
 
     /* Ok, let's pass real data to the caller */
-    pa_assert(bq->blocks->index == bq->read_index);
-
-    *chunk = bq->blocks->chunk;
+    *chunk = bq->current_read->chunk;
     pa_memblock_ref(chunk->memblock);
 
+    pa_assert(bq->read_index >= bq->current_read->index);
+    d = bq->read_index - bq->current_read->index;
+    chunk->index += d;
+    chunk->length -= d;
+
     return 0;
 }
 
@@ -421,45 +524,26 @@ void pa_memblockq_drop(pa_memblockq *bq, size_t length) {
     while (length > 0) {
 
         /* Do not drop any data when we are in prebuffering mode */
-        if (memblockq_check_prebuf(bq))
+        if (update_prebuf(bq))
             break;
 
-        if (bq->blocks) {
-            size_t d;
+        fix_current_read(bq);
 
-            pa_assert(bq->blocks->index >= bq->read_index);
+        if (bq->current_read) {
+            int64_t p, d;
 
-            d = (size_t) (bq->blocks->index - bq->read_index);
+            /* We go through this piece by piece to make sure we don't
+             * drop more than allowed by prebuf */
 
-            if (d >= length) {
-                /* The first block is too far in the future */
+            p = bq->current_read->index + bq->current_read->chunk.length;
+            pa_assert(p >= bq->read_index);
+            d = p - bq->read_index;
 
-                bq->read_index += length;
-                break;
-            } else {
+            if (d > (int64_t) length)
+                d = length;
 
-                length -= d;
-                bq->read_index += d;
-            }
-
-            pa_assert(bq->blocks->index == bq->read_index);
-
-            if (bq->blocks->chunk.length <= length) {
-                /* We need to drop the full block */
-
-                length -= bq->blocks->chunk.length;
-                bq->read_index += bq->blocks->chunk.length;
-
-                drop_block(bq, bq->blocks);
-            } else {
-                /* Only the start of this block needs to be dropped */
-
-                bq->blocks->chunk.index += length;
-                bq->blocks->chunk.length -= length;
-                bq->blocks->index += length;
-                bq->read_index += length;
-                break;
-            }
+            bq->read_index += d;
+            length -= d;
 
         } else {
 
@@ -469,20 +553,32 @@ void pa_memblockq_drop(pa_memblockq *bq, size_t length) {
         }
     }
 
+    drop_backlog(bq);
+
     delta = bq->read_index - old;
     bq->missing += delta;
 }
 
-int pa_memblockq_is_readable(pa_memblockq *bq) {
+void pa_memblockq_rewind(pa_memblockq *bq, size_t length) {
     pa_assert(bq);
+    pa_assert(length % bq->base == 0);
 
-    if (memblockq_check_prebuf(bq))
-        return 0;
+    /* This is kind of the inverse of pa_memblockq_drop() */
+
+    bq->read_index -= length;
+    bq->missing -= length;
+}
+
+pa_bool_t pa_memblockq_is_readable(pa_memblockq *bq) {
+    pa_assert(bq);
+
+    if (pa_memblockq_prebuf_active(bq))
+        return FALSE;
 
     if (pa_memblockq_get_length(bq) <= 0)
-        return 0;
+        return FALSE;
 
-    return 1;
+    return TRUE;
 }
 
 size_t pa_memblockq_get_length(pa_memblockq *bq) {
@@ -506,12 +602,6 @@ size_t pa_memblockq_missing(pa_memblockq *bq) {
     return l >= bq->minreq ? l : 0;
 }
 
-size_t pa_memblockq_get_minreq(pa_memblockq *bq) {
-    pa_assert(bq);
-
-    return bq->minreq;
-}
-
 void pa_memblockq_seek(pa_memblockq *bq, int64_t offset, pa_seek_mode_t seek) {
     int64_t old, delta;
     pa_assert(bq);
@@ -535,9 +625,11 @@ void pa_memblockq_seek(pa_memblockq *bq, int64_t offset, pa_seek_mode_t seek) {
             pa_assert_not_reached();
     }
 
+    drop_backlog(bq);
+
     delta = bq->write_index - old;
 
-    if (delta >= bq->requested) {
+    if (delta >= (int64_t) bq->requested) {
         delta -= bq->requested;
         bq->requested = 0;
     } else if (delta >= 0) {
@@ -552,10 +644,7 @@ void pa_memblockq_flush(pa_memblockq *bq) {
     int64_t old, delta;
     pa_assert(bq);
 
-    while (bq->blocks)
-        drop_block(bq, bq->blocks);
-
-    pa_assert(bq->n_blocks == 0);
+    pa_memblockq_silence(bq);
 
     old = bq->write_index;
     bq->write_index = bq->read_index;
@@ -564,7 +653,7 @@ void pa_memblockq_flush(pa_memblockq *bq) {
 
     delta = bq->write_index - old;
 
-    if (delta > bq->requested) {
+    if (delta >= (int64_t) bq->requested) {
         delta -= bq->requested;
         bq->requested = 0;
     } else if (delta >= 0) {
@@ -581,13 +670,21 @@ size_t pa_memblockq_get_tlength(pa_memblockq *bq) {
     return bq->tlength;
 }
 
+size_t pa_memblockq_get_minreq(pa_memblockq *bq) {
+    pa_assert(bq);
+
+    return bq->minreq;
+}
+
 int64_t pa_memblockq_get_read_index(pa_memblockq *bq) {
     pa_assert(bq);
+
     return bq->read_index;
 }
 
 int64_t pa_memblockq_get_write_index(pa_memblockq *bq) {
     pa_assert(bq);
+
     return bq->write_index;
 }
 
@@ -600,9 +697,6 @@ int pa_memblockq_push_align(pa_memblockq* bq, const pa_memchunk *chunk) {
     if (bq->base == 1)
         return pa_memblockq_push(bq, chunk);
 
-    if (!bq->mcalign)
-        bq->mcalign = pa_mcalign_new(bq->base);
-
     if (!can_push(bq, pa_mcalign_csize(bq->mcalign, chunk->length)))
         return -1;
 
@@ -613,23 +707,15 @@ int pa_memblockq_push_align(pa_memblockq* bq, const pa_memchunk *chunk) {
         r = pa_memblockq_push(bq, &rchunk);
         pa_memblock_unref(rchunk.memblock);
 
-        if (r < 0)
+        if (r < 0) {
+            pa_mcalign_flush(bq->mcalign);
             return -1;
+        }
     }
 
     return 0;
 }
 
-void pa_memblockq_shorten(pa_memblockq *bq, size_t length) {
-    size_t l;
-    pa_assert(bq);
-
-    l = pa_memblockq_get_length(bq);
-
-    if (l > length)
-        pa_memblockq_drop(bq, l - length);
-}
-
 void pa_memblockq_prebuf_disable(pa_memblockq *bq) {
     pa_assert(bq);
 
@@ -639,7 +725,7 @@ void pa_memblockq_prebuf_disable(pa_memblockq *bq) {
 void pa_memblockq_prebuf_force(pa_memblockq *bq) {
     pa_assert(bq);
 
-    if (!bq->in_prebuf && bq->prebuf > 0)
+    if (bq->prebuf > 0)
         bq->in_prebuf = TRUE;
 }
 
@@ -691,18 +777,17 @@ void pa_memblockq_set_tlength(pa_memblockq *bq, size_t tlength) {
     size_t old_tlength;
     pa_assert(bq);
 
-    old_tlength = bq->tlength;
-
     if (tlength <= 0)
         tlength = bq->maxlength;
 
+    old_tlength = bq->tlength;
     bq->tlength = ((tlength+bq->base-1)/bq->base)*bq->base;
 
     if (bq->tlength > bq->maxlength)
         bq->tlength = bq->maxlength;
 
-    if (bq->minreq > bq->tlength - bq->prebuf)
-        pa_memblockq_set_minreq(bq, bq->tlength - bq->prebuf);
+    if (bq->minreq > bq->tlength)
+        pa_memblockq_set_minreq(bq, bq->tlength);
 
     bq->missing += (int64_t) bq->tlength - (int64_t) old_tlength;
 }
@@ -710,8 +795,10 @@ void pa_memblockq_set_tlength(pa_memblockq *bq, size_t tlength) {
 void pa_memblockq_set_prebuf(pa_memblockq *bq, size_t prebuf) {
     pa_assert(bq);
 
-    bq->prebuf = (prebuf == (size_t) -1) ? bq->tlength/2 : prebuf;
-    bq->prebuf = ((bq->prebuf+bq->base-1)/bq->base)*bq->base;
+    if (prebuf == (size_t) -1)
+        prebuf = bq->tlength;
+
+    bq->prebuf = ((prebuf+bq->base-1)/bq->base)*bq->base;
 
     if (prebuf > 0 && bq->prebuf < bq->base)
         bq->prebuf = bq->base;
@@ -722,8 +809,8 @@ void pa_memblockq_set_prebuf(pa_memblockq *bq, size_t prebuf) {
     if (bq->prebuf <= 0 || pa_memblockq_get_length(bq) >= bq->prebuf)
         bq->in_prebuf = FALSE;
 
-    if (bq->minreq > bq->tlength - bq->prebuf)
-        pa_memblockq_set_minreq(bq, bq->tlength - bq->prebuf);
+    if (bq->minreq > bq->prebuf)
+        pa_memblockq_set_minreq(bq, bq->prebuf);
 }
 
 void pa_memblockq_set_minreq(pa_memblockq *bq, size_t minreq) {
@@ -731,9 +818,93 @@ void pa_memblockq_set_minreq(pa_memblockq *bq, size_t minreq) {
 
     bq->minreq = (minreq/bq->base)*bq->base;
 
-    if (bq->minreq > bq->tlength - bq->prebuf)
-        bq->minreq = bq->tlength - bq->prebuf;
+    if (bq->minreq > bq->tlength)
+        bq->minreq = bq->tlength;
+
+    if (bq->minreq > bq->prebuf)
+        bq->minreq = bq->prebuf;
 
     if (bq->minreq < bq->base)
         bq->minreq = bq->base;
 }
+
+void pa_memblockq_set_maxrewind(pa_memblockq *bq, size_t maxrewind) {
+    pa_assert(bq);
+
+    bq->maxrewind = (maxrewind/bq->base)*bq->base;
+}
+
+int pa_memblockq_splice(pa_memblockq *bq, pa_memblockq *source) {
+
+    pa_assert(bq);
+    pa_assert(source);
+
+    pa_memblockq_prebuf_disable(bq);
+
+    for (;;) {
+        pa_memchunk chunk;
+
+        if (pa_memblockq_peek(source, &chunk) < 0)
+            return 0;
+
+        pa_assert(chunk.length > 0);
+
+        if (chunk.memblock) {
+
+            if (pa_memblockq_push_align(bq, &chunk) < 0) {
+                pa_memblock_unref(chunk.memblock);
+                return -1;
+            }
+
+            pa_memblock_unref(chunk.memblock);
+        } else
+            pa_memblockq_seek(bq, chunk.length, PA_SEEK_RELATIVE);
+
+        pa_memblockq_drop(bq, chunk.length);
+    }
+}
+
+void pa_memblockq_willneed(pa_memblockq *bq) {
+    struct list_item *q;
+
+    pa_assert(bq);
+
+    fix_current_read(bq);
+
+    for (q = bq->current_read; q; q = q->next)
+        pa_memchunk_will_need(&q->chunk);
+}
+
+void pa_memblockq_set_silence(pa_memblockq *bq, pa_memchunk *silence) {
+    pa_assert(bq);
+
+    if (bq->silence.memblock)
+        pa_memblock_unref(bq->silence.memblock);
+
+    if (silence) {
+        bq->silence = *silence;
+        pa_memblock_ref(bq->silence.memblock);
+    } else
+        pa_memchunk_reset(&bq->silence);
+}
+
+pa_bool_t pa_memblockq_is_empty(pa_memblockq *bq) {
+    pa_assert(bq);
+
+    return !bq->blocks;
+}
+
+void pa_memblockq_silence(pa_memblockq *bq) {
+    pa_assert(bq);
+
+    while (bq->blocks)
+        drop_block(bq, bq->blocks);
+
+    pa_assert(bq->n_blocks == 0);
+}
+
+unsigned pa_memblockq_get_nblocks(pa_memblockq *bq) {
+    pa_assert(bq);
+
+    return bq->n_blocks;
+}
index 46637f1..7c38757 100644 (file)
@@ -62,7 +62,9 @@ typedef struct pa_memblockq pa_memblockq;
    - minreq:    pa_memblockq_missing() will only return values greater
                 than this value. Pass 0 for the default.
 
-   - silence:   return this memblock when reading unitialized data
+   - maxrewind: how many bytes of history to keep in the queue
+
+   - silence:   return this memchunk when reading unitialized data
 */
 pa_memblockq* pa_memblockq_new(
         int64_t idx,
@@ -71,7 +73,8 @@ pa_memblockq* pa_memblockq_new(
         size_t base,
         size_t prebuf,
         size_t minreq,
-        pa_memblock *silence);
+        size_t maxrewind,
+        pa_memchunk *silence);
 
 void pa_memblockq_free(pa_memblockq*bq);
 
@@ -95,7 +98,7 @@ int pa_memblockq_peek(pa_memblockq* bq, pa_memchunk *chunk);
 void pa_memblockq_drop(pa_memblockq *bq, size_t length);
 
 /* Test if the pa_memblockq is currently readable, that is, more data than base */
-int pa_memblockq_is_readable(pa_memblockq *bq);
+pa_bool_t pa_memblockq_is_readable(pa_memblockq *bq);
 
 /* Return the length of the queue in bytes */
 size_t pa_memblockq_get_length(pa_memblockq *bq);
@@ -107,6 +110,9 @@ size_t pa_memblockq_missing(pa_memblockq *bq);
  * this function, reset the internal counter to 0. */
 size_t pa_memblockq_pop_missing(pa_memblockq *bq);
 
+/* Directly moves the data from the source memblockq into bq */
+int pa_memblockq_splice(pa_memblockq *bq, pa_memblockq *source);
+
 /* Returns the minimal request value */
 size_t pa_memblockq_get_minreq(pa_memblockq *bq);
 
@@ -125,10 +131,8 @@ int64_t pa_memblockq_get_read_index(pa_memblockq *bq);
 /* Return the current write index */
 int64_t pa_memblockq_get_write_index(pa_memblockq *bq);
 
-/* Shorten the pa_memblockq to the specified length by dropping data
- * at the read end of the queue. The read index is increased until the
- * queue has the specified length */
-void pa_memblockq_shorten(pa_memblockq *bq, size_t length);
+/* Rewind the read index. If the history is shorter than the specified length we'll point to silence afterwards. */
+void pa_memblockq_rewind(pa_memblockq *bq, size_t length);
 
 /* Ignore prebuf for now */
 void pa_memblockq_prebuf_disable(pa_memblockq *bq);
@@ -142,10 +146,27 @@ size_t pa_memblockq_get_maxlength(pa_memblockq *bq);
 /* Return the prebuffer length in bytes */
 size_t pa_memblockq_get_prebuf(pa_memblockq *bq);
 
-/* Change metrics. */
-void pa_memblockq_set_maxlength(pa_memblockq *memblockq, size_t maxlength);
-void pa_memblockq_set_tlength(pa_memblockq *memblockq, size_t tlength);
-void pa_memblockq_set_prebuf(pa_memblockq *memblockq, size_t prebuf);
+/* Change metrics. Always call in order. */
+void pa_memblockq_set_maxlength(pa_memblockq *memblockq, size_t maxlength); /* might modify tlength, prebuf, minreq too */
+void pa_memblockq_set_tlength(pa_memblockq *memblockq, size_t tlength); /* might modify minreq, too */
+void pa_memblockq_set_prebuf(pa_memblockq *memblockq, size_t prebuf); /* might modify minreq, too */
 void pa_memblockq_set_minreq(pa_memblockq *memblockq, size_t minreq);
+void pa_memblockq_set_maxrewind(pa_memblockq *memblockq, size_t rewind); /* Set the maximum history size */
+void pa_memblockq_set_silence(pa_memblockq *memblockq, pa_memchunk *silence);
+
+/* 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);
+
+/* Check whether the memblockq is completely empty, i.e. no data
+ * neither left nor right of the read pointer, and hence no buffered
+ * data for the future nor data in the backlog. */
+pa_bool_t pa_memblockq_is_empty(pa_memblockq *bq);
+
+void pa_memblockq_silence(pa_memblockq *bq);
+
+/* Check whether we currently are in prebuf state */
+pa_bool_t pa_memblockq_prebuf_active(pa_memblockq *bq);
+
+unsigned pa_memblockq_get_nblocks(pa_memblockq *bq);
 
 #endif
index 4e73b63..16a9c14 100644 (file)
@@ -90,3 +90,23 @@ pa_memchunk *pa_memchunk_will_need(const pa_memchunk *c) {
 
     return (pa_memchunk*) c;
 }
+
+pa_memchunk* pa_memchunk_memcpy(pa_memchunk *dst, pa_memchunk *src) {
+    void *p, *q;
+
+    pa_assert(dst);
+    pa_assert(src);
+    pa_assert(dst->length == src->length);
+
+    p = pa_memblock_acquire(dst->memblock);
+    q = pa_memblock_acquire(src->memblock);
+
+    memmove((uint8_t*) p + dst->index,
+            (uint8_t*) q + src->index,
+            dst->length);
+
+    pa_memblock_release(dst->memblock);
+    pa_memblock_release(src->memblock);
+
+    return dst;
+}
index e6105ac..46a8240 100644 (file)
@@ -49,4 +49,7 @@ pa_memchunk* pa_memchunk_reset(pa_memchunk *c);
 /* Map a memory chunk back into memory if it was swapped out */
 pa_memchunk *pa_memchunk_will_need(const pa_memchunk *c);
 
+/* Copy the data in the src memchunk to the dst memchunk */
+pa_memchunk* pa_memchunk_memcpy(pa_memchunk *dst, pa_memchunk *src);
+
 #endif
index ae140ff..8e5bd2d 100644 (file)
@@ -109,8 +109,8 @@ pa_module* pa_module_load(pa_core *c, const char *name, const char *argument) {
     m->userdata = NULL;
     m->core = c;
     m->n_used = -1;
-    m->auto_unload = 0;
-    m->unload_requested = 0;
+    m->auto_unload = FALSE;
+    m->unload_requested = FALSE;
 
     if (m->init(m) < 0) {
         pa_log_error("Failed to load  module \"%s\" (argument: \"%s\"): initialization failed.", name, argument ? argument : "");
@@ -281,7 +281,7 @@ static void defer_cb(pa_mainloop_api*api, pa_defer_event *e, void *userdata) {
 void pa_module_unload_request(pa_module *m) {
     pa_assert(m);
 
-    m->unload_requested = 1;
+    m->unload_requested = TRUE;
 
     if (!m->core->module_defer_unload_event)
         m->core->module_defer_unload_event = m->core->mainloop->defer_new(m->core->mainloop, defer_cb, m->core);
index 25f122d..68c7238 100644 (file)
@@ -45,10 +45,10 @@ struct pa_module {
     void *userdata;
 
     int n_used;
-    int auto_unload;
+    pa_bool_t auto_unload;
     time_t last_used_time;
 
-    int unload_requested;
+    pa_bool_t unload_requested;
 };
 
 pa_module* pa_module_load(pa_core *c, const char *name, const char*argument);
index fe52038..1b0977d 100644 (file)
@@ -179,7 +179,7 @@ void pa_namereg_unregister(pa_core *c, const char *name) {
     pa_xfree(e);
 }
 
-void* pa_namereg_get(pa_core *c, const char *name, pa_namereg_type_t type, int autoload) {
+void* pa_namereg_get(pa_core *c, const char *name, pa_namereg_type_t type, pa_bool_t autoload) {
     struct namereg_entry *e;
     uint32_t idx;
     pa_assert(c);
index d0db9e8..0f5b4d4 100644 (file)
@@ -39,7 +39,7 @@ void pa_namereg_free(pa_core *c);
 
 const char *pa_namereg_register(pa_core *c, const char *name, pa_namereg_type_t type, void *data, int fail);
 void pa_namereg_unregister(pa_core *c, const char *name);
-void* pa_namereg_get(pa_core *c, const char *name, pa_namereg_type_t type, int autoload);
+void* pa_namereg_get(pa_core *c, const char *name, pa_namereg_type_t type, pa_bool_t autoload);
 int pa_namereg_set_default(pa_core*c, const char *name, pa_namereg_type_t type);
 
 const char *pa_namereg_get_default_sink_name(pa_core *c);
index 3ab2361..56f9037 100644 (file)
@@ -126,7 +126,7 @@ enum {
     PA_COMMAND_SUSPEND_SINK,
     PA_COMMAND_SUSPEND_SOURCE,
 
-    /* Supported since protocol v13 (0.9.8) */
+    /* Supported since protocol v12 (0.9.8) */
     PA_COMMAND_SET_PLAYBACK_STREAM_BUFFER_ATTR,
     PA_COMMAND_SET_RECORD_STREAM_BUFFER_ATTR,
 
@@ -139,6 +139,17 @@ enum {
     PA_COMMAND_PLAYBACK_STREAM_MOVED,
     PA_COMMAND_RECORD_STREAM_MOVED,
 
+    /* Supported since protocol v13 (0.9.10) */
+    PA_COMMAND_UPDATE_RECORD_STREAM_PROPLIST,
+    PA_COMMAND_UPDATE_PLAYBACK_STREAM_PROPLIST,
+    PA_COMMAND_UPDATE_CLIENT_PROPLIST,
+    PA_COMMAND_REMOVE_RECORD_STREAM_PROPLIST,
+    PA_COMMAND_REMOVE_PLAYBACK_STREAM_PROPLIST,
+    PA_COMMAND_REMOVE_CLIENT_PROPLIST,
+
+    /* SERVER->CLIENT */
+    PA_COMMAND_STARTED,
+
     PA_COMMAND_MAX
 };
 
index f3c9faa..2ff132b 100644 (file)
@@ -144,16 +144,16 @@ fail:
 int pa_pid_file_create(void) {
     int fd = -1;
     int ret = -1;
-    char fn[PATH_MAX];
     char t[20];
     pid_t pid;
     size_t l;
+    char *fn;
 
 #ifdef OS_IS_WIN32
     HANDLE process;
 #endif
 
-    pa_runtime_path("pid", fn, sizeof(fn));
+    fn = pa_runtime_path("pid");
 
     if ((fd = open_pid_file(fn, O_CREAT|O_RDWR)) < 0)
         goto fail;
@@ -200,17 +200,19 @@ fail:
         }
     }
 
+    pa_xfree(fn);
+
     return ret;
 }
 
 /* Remove the PID file, if it is ours */
 int pa_pid_file_remove(void) {
     int fd = -1;
-    char fn[PATH_MAX];
+    char *fn;
     int ret = -1;
     pid_t pid;
 
-    pa_runtime_path("pid", fn, sizeof(fn));
+    fn = pa_runtime_path("pid");
 
     if ((fd = open_pid_file(fn, O_RDWR)) < 0) {
         pa_log_warn("Failed to open PID file '%s': %s", fn, pa_cstrerror(errno));
@@ -254,6 +256,8 @@ fail:
         }
     }
 
+    pa_xfree(fn);
+
     return ret;
 }
 
@@ -272,7 +276,7 @@ int pa_pid_file_check_running(pid_t *pid, const char *binary_name) {
  * process. */
 int pa_pid_file_kill(int sig, pid_t *pid, const char *binary_name) {
     int fd = -1;
-    char fn[PATH_MAX];
+    char *fn;
     int ret = -1;
     pid_t _pid;
 #ifdef __linux__
@@ -281,7 +285,7 @@ int pa_pid_file_kill(int sig, pid_t *pid, const char *binary_name) {
     if (!pid)
         pid = &_pid;
 
-    pa_runtime_path("pid", fn, sizeof(fn));
+    fn = pa_runtime_path("pid");
 
     if ((fd = open_pid_file(fn, O_RDONLY)) < 0)
         goto fail;
@@ -296,7 +300,7 @@ int pa_pid_file_kill(int sig, pid_t *pid, const char *binary_name) {
         if ((e = pa_readlink(fn))) {
             char *f = pa_path_get_filename(e);
             if (strcmp(f, binary_name)
-#if defined(__OPTIMIZE__)
+#if !defined(__OPTIMIZE__)
                 /* libtool likes to rename our binary names ... */
                 && !(pa_startswith(f, "lt-") && strcmp(f+3, binary_name) == 0)
 #endif
@@ -319,6 +323,8 @@ fail:
     pa_xfree(e);
 #endif
 
+    pa_xfree(fn);
+
     return ret;
 
 }
index 5d3c2d3..2688f92 100644 (file)
@@ -3,7 +3,7 @@
 /***
   This file is part of PulseAudio.
 
-  Copyright 2006 Lennart Poettering
+  Copyright 2006-2008 Lennart Poettering
 
   PulseAudio is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published
 #include <string.h>
 
 #include <pulse/xmalloc.h>
+#include <pulse/gccmacro.h>
 
 #include <pulsecore/sink-input.h>
-#include <pulsecore/gccmacro.h>
 #include <pulsecore/thread-mq.h>
+#include <pulsecore/sample-util.h>
 
 #include "play-memblockq.h"
 
@@ -59,7 +60,6 @@ static void memblockq_stream_unlink(memblockq_stream *u) {
         return;
 
     pa_sink_input_unlink(u->sink_input);
-
     pa_sink_input_unref(u->sink_input);
     u->sink_input = NULL;
 
@@ -70,8 +70,6 @@ static void memblockq_stream_free(pa_object *o) {
     memblockq_stream *u = MEMBLOCKQ_STREAM(o);
     pa_assert(u);
 
-    memblockq_stream_unlink(u);
-
     if (u->memblockq)
         pa_memblockq_free(u->memblockq);
 
@@ -92,15 +90,34 @@ static int memblockq_stream_process_msg(pa_msgobject *o, int code, void*userdata
 }
 
 static void sink_input_kill_cb(pa_sink_input *i) {
+    memblockq_stream *u;
+
     pa_sink_input_assert_ref(i);
+    u = MEMBLOCKQ_STREAM(i->userdata);
+    memblockq_stream_assert_ref(u);
 
-    memblockq_stream_unlink(MEMBLOCKQ_STREAM(i->userdata));
+    memblockq_stream_unlink(u);
 }
 
-static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
+/* Called from IO thread context */
+static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) {
     memblockq_stream *u;
 
-    pa_assert(i);
+    pa_sink_input_assert_ref(i);
+    u = MEMBLOCKQ_STREAM(i->userdata);
+    memblockq_stream_assert_ref(u);
+
+    /* If we are added for the first time, ask for a rewinding so that
+     * we are heard right-away. */
+    if (PA_SINK_INPUT_IS_LINKED(state) &&
+        i->thread_info.state == PA_SINK_INPUT_INIT)
+        pa_sink_input_request_rewind(i, 0, FALSE, TRUE);
+}
+
+static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) {
+    memblockq_stream *u;
+
+    pa_sink_input_assert_ref(i);
     pa_assert(chunk);
     u = MEMBLOCKQ_STREAM(i->userdata);
     memblockq_stream_assert_ref(u);
@@ -109,36 +126,57 @@ static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chun
         return -1;
 
     if (pa_memblockq_peek(u->memblockq, chunk) < 0) {
-        pa_memblockq_free(u->memblockq);
-        u->memblockq = NULL;
-        pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u), MEMBLOCKQ_STREAM_MESSAGE_UNLINK, NULL, 0, NULL, NULL);
+
+        if (pa_sink_input_safe_to_remove(i)) {
+
+            pa_memblockq_free(u->memblockq);
+            u->memblockq = NULL;
+
+            pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u), MEMBLOCKQ_STREAM_MESSAGE_UNLINK, NULL, 0, NULL, NULL);
+        }
+
         return -1;
     }
 
+    pa_memblockq_drop(u->memblockq, chunk->length);
+
     return 0;
 }
 
-static void sink_input_drop_cb(pa_sink_input *i, size_t length) {
+static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
     memblockq_stream *u;
 
-    pa_assert(i);
-    pa_assert(length > 0);
+    pa_sink_input_assert_ref(i);
+    pa_assert(nbytes > 0);
+    u = MEMBLOCKQ_STREAM(i->userdata);
+    memblockq_stream_assert_ref(u);
+
+    if (!u->memblockq)
+        return;
+
+    pa_memblockq_rewind(u->memblockq, nbytes);
+}
+
+static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
+    memblockq_stream *u;
+
+    pa_sink_input_assert_ref(i);
     u = MEMBLOCKQ_STREAM(i->userdata);
     memblockq_stream_assert_ref(u);
 
     if (!u->memblockq)
         return;
 
-    pa_memblockq_drop(u->memblockq, length);
+    pa_memblockq_set_maxrewind(u->memblockq, nbytes);
 }
 
 pa_sink_input* pa_memblockq_sink_input_new(
         pa_sink *sink,
-        const char *name,
         const pa_sample_spec *ss,
         const pa_channel_map *map,
         pa_memblockq *q,
-        pa_cvolume *volume) {
+        pa_cvolume *volume,
+        pa_proplist *p) {
 
     memblockq_stream *u = NULL;
     pa_sink_input_new_data data;
@@ -149,41 +187,36 @@ pa_sink_input* pa_memblockq_sink_input_new(
     /* We allow creating this stream with no q set, so that it can be
      * filled in later */
 
-    if (q && pa_memblockq_get_length(q) <= 0) {
-        pa_memblockq_free(q);
-        return NULL;
-    }
-
-    if (volume && pa_cvolume_is_muted(volume)) {
-        pa_memblockq_free(q);
-        return NULL;
-    }
-
     u = pa_msgobject_new(memblockq_stream);
     u->parent.parent.free = memblockq_stream_free;
     u->parent.process_msg = memblockq_stream_process_msg;
     u->core = sink->core;
     u->sink_input = NULL;
-    u->memblockq = q;
+    u->memblockq = NULL;
 
     pa_sink_input_new_data_init(&data);
     data.sink = sink;
-    data.name = name;
     data.driver = __FILE__;
     pa_sink_input_new_data_set_sample_spec(&data, ss);
     pa_sink_input_new_data_set_channel_map(&data, map);
     pa_sink_input_new_data_set_volume(&data, volume);
+    pa_proplist_update(data.proplist, PA_UPDATE_REPLACE, p);
+
+    u->sink_input = pa_sink_input_new(sink->core, &data, 0);
+    pa_sink_input_new_data_done(&data);
 
-    if (!(u->sink_input = pa_sink_input_new(sink->core, &data, 0)))
+    if (!u->sink_input)
         goto fail;
 
-    u->sink_input->peek = sink_input_peek_cb;
-    u->sink_input->drop = sink_input_drop_cb;
+    u->sink_input->pop = sink_input_pop_cb;
+    u->sink_input->process_rewind = sink_input_process_rewind_cb;
+    u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
     u->sink_input->kill = sink_input_kill_cb;
+    u->sink_input->state_change = sink_input_state_change_cb;
     u->sink_input->userdata = u;
 
     if (q)
-        pa_memblockq_prebuf_disable(q);
+        pa_memblockq_sink_input_set_queue(u->sink_input, q);
 
     /* The reference to u is dangling here, because we want
      * to keep this stream around until it is fully played. */
@@ -202,11 +235,12 @@ fail:
 
 int pa_play_memblockq(
         pa_sink *sink,
-        const char *name,
         const pa_sample_spec *ss,
         const pa_channel_map *map,
         pa_memblockq *q,
-        pa_cvolume *volume) {
+        pa_cvolume *volume,
+        pa_proplist *p,
+        uint32_t *sink_input_index) {
 
     pa_sink_input *i;
 
@@ -214,10 +248,14 @@ int pa_play_memblockq(
     pa_assert(ss);
     pa_assert(q);
 
-    if (!(i = pa_memblockq_sink_input_new(sink, name, ss, map, q, volume)))
+    if (!(i = pa_memblockq_sink_input_new(sink, ss, map, q, volume, p)))
         return -1;
 
     pa_sink_input_put(i);
+
+    if (sink_input_index)
+        *sink_input_index = i->index;
+
     pa_sink_input_unref(i);
 
     return 0;
@@ -232,5 +270,10 @@ void pa_memblockq_sink_input_set_queue(pa_sink_input *i, pa_memblockq *q) {
 
     if (u->memblockq)
         pa_memblockq_free(u->memblockq);
-    u->memblockq = q;
+
+    if ((u->memblockq = q)) {
+        pa_memblockq_set_prebuf(q, 0);
+        pa_memblockq_set_silence(q, NULL);
+        pa_memblockq_willneed(q);
+    }
 }
index d879031..9ecf770 100644 (file)
 
 pa_sink_input* pa_memblockq_sink_input_new(
         pa_sink *sink,
-        const char *name,
         const pa_sample_spec *ss,
         const pa_channel_map *map,
         pa_memblockq *q,
-        pa_cvolume *volume);
+        pa_cvolume *volume,
+        pa_proplist *p);
 
 void pa_memblockq_sink_input_set_queue(pa_sink_input *i, pa_memblockq *q);
 
 int pa_play_memblockq(
     pa_sink *sink,
-    const char *name,
     const pa_sample_spec *ss,
     const pa_channel_map *map,
     pa_memblockq *q,
-    pa_cvolume *cvolume);
+    pa_cvolume *cvolume,
+    pa_proplist *p,
+    uint32_t *sink_input_index);
 
 #endif
index 6aaec56..67a9213 100644 (file)
@@ -3,7 +3,7 @@
 /***
   This file is part of PulseAudio.
 
-  Copyright 2004-2006 Lennart Poettering
+  Copyright 2004-2008 Lennart Poettering
 
   PulseAudio is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published
 #include <string.h>
 
 #include <pulse/xmalloc.h>
+#include <pulse/gccmacro.h>
 
 #include <pulsecore/sink-input.h>
-#include <pulsecore/gccmacro.h>
 #include <pulsecore/thread-mq.h>
+#include <pulsecore/play-memblockq.h>
 
 #include "play-memchunk.h"
 
-typedef struct memchunk_stream {
-    pa_msgobject parent;
-    pa_core *core;
-    pa_sink_input *sink_input;
-    pa_memchunk memchunk;
-} memchunk_stream;
-
-enum {
-    MEMCHUNK_STREAM_MESSAGE_UNLINK,
-};
-
-PA_DECLARE_CLASS(memchunk_stream);
-#define MEMCHUNK_STREAM(o) (memchunk_stream_cast(o))
-static PA_DEFINE_CHECK_TYPE(memchunk_stream, pa_msgobject);
-
-static void memchunk_stream_unlink(memchunk_stream *u) {
-    pa_assert(u);
-
-    if (!u->sink_input)
-        return;
-
-    pa_sink_input_unlink(u->sink_input);
-
-    pa_sink_input_unref(u->sink_input);
-    u->sink_input = NULL;
-
-    memchunk_stream_unref(u);
-}
-
-static void memchunk_stream_free(pa_object *o) {
-    memchunk_stream *u = MEMCHUNK_STREAM(o);
-    pa_assert(u);
-
-    memchunk_stream_unlink(u);
-
-    if (u->memchunk.memblock)
-        pa_memblock_unref(u->memchunk.memblock);
-
-    pa_xfree(u);
-}
-
-static int memchunk_stream_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) {
-    memchunk_stream *u = MEMCHUNK_STREAM(o);
-    memchunk_stream_assert_ref(u);
-
-    switch (code) {
-        case MEMCHUNK_STREAM_MESSAGE_UNLINK:
-            memchunk_stream_unlink(u);
-            break;
-    }
-
-    return 0;
-}
-
-static void sink_input_kill_cb(pa_sink_input *i) {
-    pa_sink_input_assert_ref(i);
-
-    memchunk_stream_unlink(MEMCHUNK_STREAM(i->userdata));
-}
-
-static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
-    memchunk_stream *u;
-
-    pa_assert(i);
-    pa_assert(chunk);
-    u = MEMCHUNK_STREAM(i->userdata);
-    memchunk_stream_assert_ref(u);
-
-    if (!u->memchunk.memblock)
-        return -1;
-
-    if (u->memchunk.length <= 0) {
-        pa_memblock_unref(u->memchunk.memblock);
-        u->memchunk.memblock = NULL;
-        pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u), MEMCHUNK_STREAM_MESSAGE_UNLINK, NULL, 0, NULL, NULL);
-        return -1;
-    }
-
-    pa_assert(u->memchunk.memblock);
-    *chunk = u->memchunk;
-    pa_memblock_ref(chunk->memblock);
-
-    return 0;
-}
-
-static void sink_input_drop_cb(pa_sink_input *i, size_t length) {
-    memchunk_stream *u;
-
-    pa_assert(i);
-    pa_assert(length > 0);
-    u = MEMCHUNK_STREAM(i->userdata);
-    memchunk_stream_assert_ref(u);
-
-    if (length < u->memchunk.length) {
-        u->memchunk.length -= length;
-        u->memchunk.index += length;
-    } else
-        u->memchunk.length = 0;
-}
-
 int pa_play_memchunk(
         pa_sink *sink,
-        const char *name,
         const pa_sample_spec *ss,
         const pa_channel_map *map,
         const pa_memchunk *chunk,
-        pa_cvolume *volume) {
+        pa_cvolume *volume,
+        pa_proplist *p,
+        uint32_t *sink_input_index) {
 
-    memchunk_stream *u = NULL;
-    pa_sink_input_new_data data;
+    pa_memblockq *q;
+    int r;
 
     pa_assert(sink);
     pa_assert(ss);
     pa_assert(chunk);
 
-    if (volume && pa_cvolume_is_muted(volume))
-        return 0;
-
-    pa_memchunk_will_need(chunk);
-
-    u = pa_msgobject_new(memchunk_stream);
-    u->parent.parent.free = memchunk_stream_free;
-    u->parent.process_msg = memchunk_stream_process_msg;
-    u->core = sink->core;
-    u->memchunk = *chunk;
-    pa_memblock_ref(u->memchunk.memblock);
-
-    pa_sink_input_new_data_init(&data);
-    data.sink = sink;
-    data.driver = __FILE__;
-    data.name = name;
-    pa_sink_input_new_data_set_sample_spec(&data, ss);
-    pa_sink_input_new_data_set_channel_map(&data, map);
-    pa_sink_input_new_data_set_volume(&data, volume);
-
-    if (!(u->sink_input = pa_sink_input_new(sink->core, &data, 0)))
-        goto fail;
-
-    u->sink_input->peek = sink_input_peek_cb;
-    u->sink_input->drop = sink_input_drop_cb;
-    u->sink_input->kill = sink_input_kill_cb;
-    u->sink_input->userdata = u;
+    q = pa_memblockq_new(0, chunk->length, 0, pa_frame_size(ss), 1, 1, 0, NULL);
+    pa_assert_se(pa_memblockq_push(q, chunk) >= 0);
 
-    pa_sink_input_put(u->sink_input);
-
-    /* The reference to u is dangling here, because we want to keep
-     * this stream around until it is fully played. */
+    if ((r = pa_play_memblockq(sink, ss, map, q, volume, p, sink_input_index)) < 0) {
+        pa_memblockq_free(q);
+        return r;
+    }
 
     return 0;
-
-fail:
-    if (u)
-        memchunk_stream_unref(u);
-
-    return -1;
 }
-
index 5afb094..f7c9d17 100644 (file)
 
 int pa_play_memchunk(
     pa_sink *sink,
-    const char *name,
     const pa_sample_spec *ss,
     const pa_channel_map *map,
     const pa_memchunk *chunk,
-    pa_cvolume *cvolume);
+    pa_cvolume *cvolume,
+    pa_proplist *p,
+    uint32_t *sink_input_index);
 
 #endif
index ceb6ae4..2f797a1 100644 (file)
@@ -82,7 +82,7 @@ pa_protocol_cli* pa_protocol_cli_new(pa_core *core, pa_socket_server *server, pa
     p = pa_xnew(pa_protocol_cli, 1);
     p->module = m;
     p->core = core;
-    p->server = server;
+    p->server = pa_socket_server_ref(server);
     p->connections = pa_idxset_new(NULL, NULL);
 
     pa_socket_server_set_callback(p->server, on_connection, p);
index f963f2a..492dc9f 100644 (file)
 #define PLAYBACK_BUFFER_SECONDS (.25)
 #define PLAYBACK_BUFFER_FRAGMENTS (10)
 #define RECORD_BUFFER_SECONDS (5)
-#define RECORD_BUFFER_FRAGMENTS (100)
 
 #define MAX_CACHE_SAMPLE_SIZE (2048000)
 
+#define DEFAULT_SINK_LATENCY (150*PA_USEC_PER_MSEC)
+#define DEFAULT_SOURCE_LATENCY (150*PA_USEC_PER_MSEC)
+
 #define SCACHE_PREFIX "esound."
 
 /* This is heavily based on esound's code */
@@ -102,8 +104,9 @@ typedef struct connection {
 
     struct {
         pa_memblock *current_memblock;
-        size_t memblock_index, fragment_size;
+        size_t memblock_index;
         pa_atomic_t missing;
+        pa_bool_t underrun;
     } playback;
 
     struct {
@@ -122,7 +125,7 @@ static PA_DEFINE_CHECK_TYPE(connection, pa_msgobject);
 struct pa_protocol_esound {
     pa_module *module;
     pa_core *core;
-    int public;
+    pa_bool_t public;
     pa_socket_server *server;
     pa_idxset *connections;
 
@@ -149,8 +152,9 @@ typedef struct proto_handler {
     const char *description;
 } esd_proto_handler_info_t;
 
-static void sink_input_drop_cb(pa_sink_input *i, size_t length);
-static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk);
+static int sink_input_pop_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk);
+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_kill_cb(pa_sink_input *i);
 static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk);
 static pa_usec_t source_output_get_latency_cb(pa_source_output *o);
@@ -398,8 +402,7 @@ static int esd_proto_stream_play(connection *c, PA_GCC_UNUSED esd_proto_t reques
         CHECK_VALIDITY(sink, "No such sink: %s", c->protocol->sink_name);
     }
 
-    strncpy(name, data, sizeof(name));
-    name[sizeof(name)-1] = 0;
+    pa_strlcpy(name, data, sizeof(name));
 
     utf8_name = pa_utf8_filter(name);
     pa_client_set_name(c->client, utf8_name);
@@ -410,34 +413,39 @@ static int esd_proto_stream_play(connection *c, PA_GCC_UNUSED esd_proto_t reques
     pa_assert(!c->sink_input && !c->input_memblockq);
 
     pa_sink_input_new_data_init(&sdata);
-    sdata.sink = sink;
     sdata.driver = __FILE__;
-    sdata.name = c->client->name;
-    pa_sink_input_new_data_set_sample_spec(&sdata, &ss);
     sdata.module = c->protocol->module;
     sdata.client = c->client;
+    sdata.sink = sink;
+    pa_proplist_update(sdata.proplist, PA_UPDATE_MERGE, c->client->proplist);
+    pa_sink_input_new_data_set_sample_spec(&sdata, &ss);
 
     c->sink_input = pa_sink_input_new(c->protocol->core, &sdata, 0);
+    pa_sink_input_new_data_done(&sdata);
+
     CHECK_VALIDITY(c->sink_input, "Failed to create sink input.");
 
     l = (size_t) (pa_bytes_per_second(&ss)*PLAYBACK_BUFFER_SECONDS);
     c->input_memblockq = pa_memblockq_new(
             0,
             l,
-            0,
+            l,
             pa_frame_size(&ss),
             (size_t) -1,
             l/PLAYBACK_BUFFER_FRAGMENTS,
+            0,
             NULL);
-    pa_iochannel_socket_set_rcvbuf(c->io, l/PLAYBACK_BUFFER_FRAGMENTS*2);
-    c->playback.fragment_size = l/PLAYBACK_BUFFER_FRAGMENTS;
+    pa_iochannel_socket_set_rcvbuf(c->io, l);
 
     c->sink_input->parent.process_msg = sink_input_process_msg;
-    c->sink_input->peek = sink_input_peek_cb;
-    c->sink_input->drop = sink_input_drop_cb;
+    c->sink_input->pop = sink_input_pop_cb;
+    c->sink_input->process_rewind = sink_input_process_rewind_cb;
+    c->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
     c->sink_input->kill = sink_input_kill_cb;
     c->sink_input->userdata = c;
 
+    pa_sink_input_set_requested_latency(c->sink_input, DEFAULT_SINK_LATENCY);
+
     c->state = ESD_STREAMING_DATA;
 
     c->protocol->n_player++;
@@ -497,8 +505,7 @@ static int esd_proto_stream_record(connection *c, esd_proto_t request, const voi
         }
     }
 
-    strncpy(name, data, sizeof(name));
-    name[sizeof(name)-1] = 0;
+    pa_strlcpy(name, data, sizeof(name));
 
     utf8_name = pa_utf8_filter(name);
     pa_client_set_name(c->client, utf8_name);
@@ -509,32 +516,37 @@ static int esd_proto_stream_record(connection *c, esd_proto_t request, const voi
     pa_assert(!c->output_memblockq && !c->source_output);
 
     pa_source_output_new_data_init(&sdata);
-    sdata.source = source;
     sdata.driver = __FILE__;
-    sdata.name = c->client->name;
-    pa_source_output_new_data_set_sample_spec(&sdata, &ss);
     sdata.module = c->protocol->module;
     sdata.client = c->client;
+    sdata.source = source;
+    pa_proplist_update(sdata.proplist, PA_UPDATE_MERGE, c->client->proplist);
+    pa_source_output_new_data_set_sample_spec(&sdata, &ss);
+
+    c->source_output = pa_source_output_new(c->protocol->core, &sdata, 0);
+    pa_source_output_new_data_done(&sdata);
 
-    c->source_output = pa_source_output_new(c->protocol->core, &sdata, 9);
-    CHECK_VALIDITY(c->source_output, "Failed to create source_output.");
+    CHECK_VALIDITY(c->source_output, "Failed to create source output.");
 
     l = (size_t) (pa_bytes_per_second(&ss)*RECORD_BUFFER_SECONDS);
     c->output_memblockq = pa_memblockq_new(
             0,
             l,
-            0,
+            l,
             pa_frame_size(&ss),
             1,
             0,
+            0,
             NULL);
-    pa_iochannel_socket_set_sndbuf(c->io, l/RECORD_BUFFER_FRAGMENTS*2);
+    pa_iochannel_socket_set_sndbuf(c->io, l);
 
     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);
+
     c->state = ESD_STREAMING_DATA;
 
     c->protocol->n_player++;
@@ -638,8 +650,8 @@ static int esd_proto_all_info(connection *c, esd_proto_t request, const void *da
         memset(name, 0, ESD_NAME_MAX); /* don't leak old data */
         if (conn->original_name)
             strncpy(name, conn->original_name, ESD_NAME_MAX);
-        else if (conn->client && conn->client->name)
-            strncpy(name, conn->client->name, ESD_NAME_MAX);
+        else if (conn->client && pa_proplist_gets(conn->client->proplist, PA_PROP_APPLICATION_NAME))
+            strncpy(name, pa_proplist_gets(conn->client->proplist, PA_PROP_APPLICATION_NAME), ESD_NAME_MAX);
         connection_write(c, name, ESD_NAME_MAX);
 
         /* rate */
@@ -785,8 +797,7 @@ static int esd_proto_sample_cache(connection *c, PA_GCC_UNUSED esd_proto_t reque
     CHECK_VALIDITY(sc_length <= MAX_CACHE_SAMPLE_SIZE, "Sample too large (%d bytes).", (int)sc_length);
 
     strcpy(name, SCACHE_PREFIX);
-    strncpy(name+sizeof(SCACHE_PREFIX)-1, data, ESD_NAME_MAX);
-    name[sizeof(name)-1] = 0;
+    pa_strlcpy(name+sizeof(SCACHE_PREFIX)-1, data, ESD_NAME_MAX);
 
     CHECK_VALIDITY(pa_utf8_valid(name), "Invalid UTF8 in sample name.");
 
@@ -800,7 +811,7 @@ static int esd_proto_sample_cache(connection *c, PA_GCC_UNUSED esd_proto_t reque
 
     c->state = ESD_CACHING_SAMPLE;
 
-    pa_scache_add_item(c->protocol->core, c->scache.name, NULL, NULL, NULL, &idx);
+    pa_scache_add_item(c->protocol->core, c->scache.name, NULL, NULL, NULL, c->client->proplist, &idx);
 
     idx += 1;
     connection_write(c, &idx, sizeof(uint32_t));
@@ -818,8 +829,7 @@ static int esd_proto_sample_get_id(connection *c, PA_GCC_UNUSED esd_proto_t requ
     pa_assert(length == ESD_NAME_MAX);
 
     strcpy(name, SCACHE_PREFIX);
-    strncpy(name+sizeof(SCACHE_PREFIX)-1, data, ESD_NAME_MAX);
-    name[sizeof(name)-1] = 0;
+    pa_strlcpy(name+sizeof(SCACHE_PREFIX)-1, data, ESD_NAME_MAX);
 
     CHECK_VALIDITY(pa_utf8_valid(name), "Invalid UTF8 in sample name.");
 
@@ -851,7 +861,7 @@ static int esd_proto_sample_free_or_play(connection *c, esd_proto_t request, con
             pa_sink *sink;
 
             if ((sink = pa_namereg_get(c->protocol->core, c->protocol->sink_name, PA_NAMEREG_SINK, 1)))
-                if (pa_scache_play_item(c->protocol->core, name, sink, PA_VOLUME_NORM) >= 0)
+                if (pa_scache_play_item(c->protocol->core, name, sink, PA_VOLUME_NORM, c->client->proplist, NULL) >= 0)
                     ok = idx + 1;
         } else {
             pa_assert(request == ESD_PROTO_SAMPLE_FREE);
@@ -992,7 +1002,7 @@ static int do_read(connection *c) {
             uint32_t idx;
 
             c->scache.memchunk.index = 0;
-            pa_scache_add_item(c->protocol->core, c->scache.name, &c->scache.sample_spec, NULL, &c->scache.memchunk, &idx);
+            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;
@@ -1012,6 +1022,7 @@ static int do_read(connection *c) {
         ssize_t r;
         size_t l;
         void *p;
+        size_t space;
 
         pa_assert(c->input_memblockq);
 
@@ -1020,21 +1031,26 @@ static int do_read(connection *c) {
         if (!(l = pa_atomic_load(&c->playback.missing)))
             return 0;
 
-        if (l > c->playback.fragment_size)
-            l = c->playback.fragment_size;
+        if (c->playback.current_memblock) {
+
+            space = pa_memblock_get_length(c->playback.current_memblock) - c->playback.memblock_index;
 
-        if (c->playback.current_memblock)
-            if (pa_memblock_get_length(c->playback.current_memblock) - c->playback.memblock_index < l) {
+            if (space <= 0) {
                 pa_memblock_unref(c->playback.current_memblock);
                 c->playback.current_memblock = NULL;
-                c->playback.memblock_index = 0;
             }
+        }
 
         if (!c->playback.current_memblock) {
-            pa_assert_se(c->playback.current_memblock = pa_memblock_new(c->protocol->core->mempool, c->playback.fragment_size*2));
+            pa_assert_se(c->playback.current_memblock = pa_memblock_new(c->protocol->core->mempool, 0));
             c->playback.memblock_index = 0;
+
+            space = pa_memblock_get_length(c->playback.current_memblock);
         }
 
+        if (l > space)
+            l = space;
+
         p = pa_memblock_acquire(c->playback.current_memblock);
         r = pa_iochannel_read(c->io, (uint8_t*) p+c->playback.memblock_index, l);
         pa_memblock_release(c->playback.current_memblock);
@@ -1122,12 +1138,11 @@ static void do_work(connection *c) {
     if (c->dead)
         return;
 
-    if (pa_iochannel_is_readable(c->io)) {
+    if (pa_iochannel_is_readable(c->io))
         if (do_read(c) < 0)
             goto fail;
-    }
 
-    if (c->state == ESD_STREAMING_DATA && c->source_output && pa_iochannel_is_hungup(c->io))
+    if (c->state == ESD_STREAMING_DATA && !c->sink_input && pa_iochannel_is_hungup(c->io))
         /* In case we are in capture mode we will never call read()
          * on the socket, hence we need to detect the hangup manually
          * here, instead of simply waiting for read() to return 0. */
@@ -1212,15 +1227,19 @@ static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int
             /* New data from the main loop */
             pa_memblockq_push_align(c->input_memblockq, chunk);
 
+            if (pa_memblockq_is_readable(c->input_memblockq) && c->playback.underrun) {
+                pa_log_debug("Requesting rewind due to end of underrun.");
+                pa_sink_input_request_rewind(c->sink_input, 0, FALSE, TRUE);
+            }
+
 /*             pa_log("got data, %u", pa_memblockq_get_length(c->input_memblockq)); */
 
             return 0;
         }
 
-        case SINK_INPUT_MESSAGE_DISABLE_PREBUF: {
+        case SINK_INPUT_MESSAGE_DISABLE_PREBUF:
             pa_memblockq_prebuf_disable(c->input_memblockq);
             return 0;
-        }
 
         case PA_SINK_INPUT_MESSAGE_GET_LATENCY: {
             pa_usec_t *r = userdata;
@@ -1237,41 +1256,62 @@ static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int
 }
 
 /* Called from thread context */
-static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
+static int sink_input_pop_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
     connection*c;
-    int r;
 
-    pa_assert(i);
+    pa_sink_input_assert_ref(i);
     c = CONNECTION(i->userdata);
     connection_assert_ref(c);
     pa_assert(chunk);
 
-    if ((r = pa_memblockq_peek(c->input_memblockq, chunk)) < 0 && c->dead)
-        pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_UNLINK_CONNECTION, NULL, 0, NULL, NULL);
+    if (pa_memblockq_peek(c->input_memblockq, chunk) < 0) {
+
+        c->playback.underrun = TRUE;
+
+        if (c->dead && pa_sink_input_safe_to_remove(i))
+            pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_UNLINK_CONNECTION, NULL, 0, NULL, NULL);
+
+        return -1;
+    } else {
+        size_t m;
 
-    return r;
+        c->playback.underrun = FALSE;
+
+        pa_memblockq_drop(c->input_memblockq, chunk->length);
+        m = pa_memblockq_pop_missing(c->input_memblockq);
+
+        if (m > 0)
+            if (pa_atomic_add(&c->playback.missing, m) <= 0)
+                pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_REQUEST_DATA, NULL, 0, NULL, NULL);
+
+        return 0;
+    }
 }
 
 /* Called from thread context */
-static void sink_input_drop_cb(pa_sink_input *i, size_t length) {
-    connection*c;
-    size_t old, new;
+static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
+    connection *c;
 
-    pa_assert(i);
+    pa_sink_input_assert_ref(i);
     c = CONNECTION(i->userdata);
     connection_assert_ref(c);
-    pa_assert(length);
 
-    /*     pa_log("DROP"); */
+    /* If we are in an underrun, then we don't rewind */
+    if (i->thread_info.underrun_for > 0)
+        return;
 
-    old = pa_memblockq_missing(c->input_memblockq);
-    pa_memblockq_drop(c->input_memblockq, length);
-    new = pa_memblockq_missing(c->input_memblockq);
+    pa_memblockq_rewind(c->input_memblockq, nbytes);
+}
 
-    if (new > old) {
-        if (pa_atomic_add(&c->playback.missing, new - old) <= 0)
-            pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_REQUEST_DATA, NULL, 0, NULL, NULL);
-    }
+/* Called from thread context */
+static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
+    connection *c;
+
+    pa_sink_input_assert_ref(i);
+    c = CONNECTION(i->userdata);
+    connection_assert_ref(c);
+
+    pa_memblockq_set_maxrewind(c->input_memblockq, nbytes);
 }
 
 static void sink_input_kill_cb(pa_sink_input *i) {
@@ -1286,7 +1326,7 @@ static void sink_input_kill_cb(pa_sink_input *i) {
 static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) {
     connection *c;
 
-    pa_assert(o);
+    pa_source_output_assert_ref(o);
     c = CONNECTION(o->userdata);
     pa_assert(c);
     pa_assert(chunk);
@@ -1303,7 +1343,7 @@ static void source_output_kill_cb(pa_source_output *o) {
 static pa_usec_t source_output_get_latency_cb(pa_source_output *o) {
     connection*c;
 
-    pa_assert(o);
+    pa_source_output_assert_ref(o);
     c = CONNECTION(o->userdata);
     pa_assert(c);
 
@@ -1349,7 +1389,8 @@ static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata)
     pa_iochannel_socket_peer_to_string(io, pname, sizeof(pname));
     pa_snprintf(cname, sizeof(cname), "EsounD client (%s)", pname);
     c->client = pa_client_new(p->core, __FILE__, cname);
-    c->client->owner = p->module;
+    pa_proplist_sets(c->client->proplist, "esound-protocol.peer", pname);
+    c->client->module = p->module;
     c->client->kill = client_kill_cb;
     c->client->userdata = c;
 
@@ -1374,11 +1415,10 @@ static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata)
 
     c->playback.current_memblock = NULL;
     c->playback.memblock_index = 0;
-    c->playback.fragment_size = 0;
+    c->playback.underrun = TRUE;
     pa_atomic_store(&c->playback.missing, 0);
 
-    c->scache.memchunk.length = c->scache.memchunk.index = 0;
-    c->scache.memchunk.memblock = NULL;
+    pa_memchunk_reset(&c->scache.memchunk);
     c->scache.name = NULL;
 
     c->original_name = NULL;
@@ -1436,7 +1476,7 @@ pa_protocol_esound* pa_protocol_esound_new(pa_core*core, pa_socket_server *serve
     p->core = core;
     p->module = m;
     p->public = public;
-    p->server = server;
+    p->server = pa_socket_server_ref(server);
     pa_socket_server_set_callback(p->server, on_connection, p);
     p->connections = pa_idxset_new(NULL, NULL);
 
@@ -1459,7 +1499,8 @@ void pa_protocol_esound_free(pa_protocol_esound *p) {
         connection_unlink(c);
     pa_idxset_free(p->connections, NULL, NULL);
 
-    pa_socket_server_unref(p->server);
+    if (p->server)
+        pa_socket_server_unref(p->server);
 
     if (p->auth_ip_acl)
         pa_ip_acl_free(p->auth_ip_acl);
index d91ae14..bc2e9af 100644 (file)
@@ -168,7 +168,7 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {
 #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("Fully Qualified Domain Name:", pa_get_fqdn(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));
                 PRINTF_FIELD("Default Sink:", pa_namereg_get_default_sink_name(c->protocol->core));
                 PRINTF_FIELD("Default Source:", pa_namereg_get_default_source_name(c->protocol->core));
@@ -255,7 +255,7 @@ pa_protocol_http* pa_protocol_http_new(pa_core *core, pa_socket_server *server,
     p = pa_xnew(pa_protocol_http, 1);
     p->module = m;
     p->core = core;
-    p->server = server;
+    p->server = pa_socket_server_ref(server);
     p->connections = pa_idxset_new(NULL, NULL);
 
     pa_socket_server_set_callback(p->server, on_connection, p);
index 174342e..2adcdfc 100644 (file)
@@ -71,6 +71,9 @@
 #define MAX_CONNECTIONS 64
 
 #define MAX_MEMBLOCKQ_LENGTH (4*1024*1024) /* 4MB */
+#define DEFAULT_TLENGTH_MSEC 2000 /* 2s */
+#define DEFAULT_PROCESS_MSEC 20   /* 20ms */
+#define DEFAULT_FRAGSIZE_MSEC DEFAULT_TLENGTH_MSEC
 
 typedef struct connection connection;
 struct pa_protocol_native;
@@ -84,6 +87,7 @@ typedef struct record_stream {
     pa_source_output *source_output;
     pa_memblockq *memblockq;
     size_t fragment_size;
+    pa_usec_t source_latency;
 } record_stream;
 
 typedef struct output_stream {
@@ -98,17 +102,17 @@ typedef struct playback_stream {
 
     pa_sink_input *sink_input;
     pa_memblockq *memblockq;
-    int drain_request;
+    pa_bool_t drain_request;
     uint32_t drain_tag;
     uint32_t syncid;
-    int underrun;
 
     pa_atomic_t missing;
     size_t minreq;
+    pa_usec_t sink_latency;
 
     /* Only updated after SINK_INPUT_MESSAGE_UPDATE_LATENCY */
     int64_t read_index, write_index;
-    size_t resampled_chunk_length;
+    size_t render_memblockq_length;
 } playback_stream;
 
 typedef struct upload_stream {
@@ -122,12 +126,13 @@ typedef struct upload_stream {
     char *name;
     pa_sample_spec sample_spec;
     pa_channel_map channel_map;
+    pa_proplist *proplist;
 } upload_stream;
 
 struct connection {
     pa_msgobject parent;
 
-    int authorized;
+    pa_bool_t authorized;
     uint32_t version;
     pa_protocol_native *protocol;
     pa_client *client;
@@ -162,11 +167,11 @@ static PA_DEFINE_CHECK_TYPE(connection, pa_msgobject);
 struct pa_protocol_native {
     pa_module *module;
     pa_core *core;
-    int public;
+    pa_bool_t public;
     pa_socket_server *server;
     pa_idxset *connections;
     uint8_t auth_cookie[PA_NATIVE_COOKIE_LENGTH];
-    int auth_cookie_in_property;
+    pa_bool_t auth_cookie_in_property;
 #ifdef HAVE_CREDS
     char *auth_group;
 #endif
@@ -187,7 +192,8 @@ enum {
     PLAYBACK_STREAM_MESSAGE_REQUEST_DATA,      /* data requested from sink input from the main loop */
     PLAYBACK_STREAM_MESSAGE_UNDERFLOW,
     PLAYBACK_STREAM_MESSAGE_OVERFLOW,
-    PLAYBACK_STREAM_MESSAGE_DRAIN_ACK
+    PLAYBACK_STREAM_MESSAGE_DRAIN_ACK,
+    PLAYBACK_STREAM_MESSAGE_STARTED
 };
 
 enum {
@@ -199,11 +205,13 @@ enum {
     CONNECTION_MESSAGE_REVOKE
 };
 
-static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk);
-static void sink_input_drop_cb(pa_sink_input *i, size_t length);
+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_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 send_memblock(connection *c);
 static void request_bytes(struct playback_stream*s);
@@ -254,6 +262,8 @@ static void command_move_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag
 static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
 static void command_set_stream_buffer_attr(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
 static void command_update_stream_sample_rate(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_update_proplist(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_remove_proplist(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,
@@ -335,7 +345,15 @@ static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = {
     [PA_COMMAND_SET_RECORD_STREAM_BUFFER_ATTR] = command_set_stream_buffer_attr,
 
     [PA_COMMAND_UPDATE_PLAYBACK_STREAM_SAMPLE_RATE] = command_update_stream_sample_rate,
-    [PA_COMMAND_UPDATE_RECORD_STREAM_SAMPLE_RATE] = command_update_stream_sample_rate
+    [PA_COMMAND_UPDATE_RECORD_STREAM_SAMPLE_RATE] = command_update_stream_sample_rate,
+
+    [PA_COMMAND_UPDATE_RECORD_STREAM_PROPLIST] = command_update_proplist,
+    [PA_COMMAND_UPDATE_PLAYBACK_STREAM_PROPLIST] = command_update_proplist,
+    [PA_COMMAND_UPDATE_CLIENT_PROPLIST] = command_update_proplist,
+
+    [PA_COMMAND_REMOVE_RECORD_STREAM_PROPLIST] = command_remove_proplist,
+    [PA_COMMAND_REMOVE_PLAYBACK_STREAM_PROPLIST] = command_remove_proplist,
+    [PA_COMMAND_REMOVE_CLIENT_PROPLIST] = command_remove_proplist,
 };
 
 /* structure management */
@@ -359,6 +377,9 @@ static void upload_stream_free(pa_object *o) {
 
     pa_xfree(s->name);
 
+    if (s->proplist)
+        pa_proplist_free(s->proplist);
+
     if (s->memchunk.memblock)
         pa_memblock_unref(s->memchunk.memblock);
 
@@ -369,7 +390,9 @@ static upload_stream* upload_stream_new(
         connection *c,
         const pa_sample_spec *ss,
         const pa_channel_map *map,
-        const char *name, size_t length) {
+        const char *name,
+        size_t length,
+        pa_proplist *p) {
 
     upload_stream *s;
 
@@ -377,6 +400,7 @@ static upload_stream* upload_stream_new(
     pa_assert(ss);
     pa_assert(name);
     pa_assert(length > 0);
+    pa_assert(p);
 
     s = pa_msgobject_new(upload_stream);
     s->parent.parent.parent.free = upload_stream_free;
@@ -386,6 +410,8 @@ static upload_stream* upload_stream_new(
     s->name = pa_xstrdup(name);
     pa_memchunk_reset(&s->memchunk);
     s->length = length;
+    s->proplist = pa_proplist_copy(p);
+    pa_proplist_update(s->proplist, PA_UPDATE_MERGE, c->client->proplist);
 
     pa_idxset_put(c->output_streams, s, &s->index);
 
@@ -444,15 +470,70 @@ 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, uint32_t *maxlength, uint32_t *fragsize) {
+    pa_assert(s);
+    pa_assert(maxlength);
+    pa_assert(fragsize);
+
+    if (*maxlength <= 0 || *maxlength > MAX_MEMBLOCKQ_LENGTH)
+        *maxlength = MAX_MEMBLOCKQ_LENGTH;
+
+    if (*fragsize <= 0)
+        *fragsize = pa_usec_to_bytes(DEFAULT_FRAGSIZE_MSEC*PA_USEC_PER_MSEC, &s->source_output->sample_spec);
+
+    if (adjust_latency) {
+        pa_usec_t fragsize_usec;
+
+        /* So, the user asked us to adjust the latency according to
+         * the what the source can provide. Half the latency will be
+         * spent on the hw buffer, half of it in the async buffer
+         * queue we maintain for each client. */
+
+        fragsize_usec = pa_bytes_to_usec(*fragsize, &s->source_output->sample_spec);
+
+        s->source_latency = pa_source_output_set_requested_latency(s->source_output, fragsize_usec/2);
+
+        if (fragsize_usec >= s->source_latency*2)
+            fragsize_usec -= s->source_latency;
+        else
+            fragsize_usec = s->source_latency;
+
+        *fragsize = pa_usec_to_bytes(fragsize_usec, &s->source_output->sample_spec);
+    }
+}
+
+static void fix_record_buffer_attr_post(record_stream *s, uint32_t *maxlength, uint32_t *fragsize) {
+    size_t base;
+
+    pa_assert(s);
+    pa_assert(maxlength);
+    pa_assert(fragsize);
+
+    *maxlength = pa_memblockq_get_maxlength(s->memblockq);
+
+    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;
+
+    *fragsize = s->fragment_size;
+}
+
 static record_stream* record_stream_new(
         connection *c,
         pa_source *source,
         pa_sample_spec *ss,
         pa_channel_map *map,
-        const char *name,
+        pa_bool_t peak_detect,
         uint32_t *maxlength,
-        uint32_t fragment_size,
-        pa_source_output_flags_t flags) {
+        uint32_t *fragsize,
+        pa_source_output_flags_t flags,
+        pa_proplist *p,
+        pa_bool_t adjust_latency) {
 
     record_stream *s;
     pa_source_output *source_output;
@@ -461,20 +542,27 @@ static record_stream* record_stream_new(
 
     pa_assert(c);
     pa_assert(ss);
-    pa_assert(name);
     pa_assert(maxlength);
-    pa_assert(*maxlength > 0);
+    pa_assert(p);
 
     pa_source_output_new_data_init(&data);
+
+    pa_proplist_update(data.proplist, PA_UPDATE_REPLACE, p);
+    pa_proplist_update(data.proplist, PA_UPDATE_MERGE, c->client->proplist);
+    data.driver = __FILE__;
     data.module = c->protocol->module;
     data.client = c->client;
     data.source = source;
-    data.driver = __FILE__;
-    data.name = name;
     pa_source_output_new_data_set_sample_spec(&data, ss);
     pa_source_output_new_data_set_channel_map(&data, map);
+    if (peak_detect)
+        data.resample_method = PA_RESAMPLER_PEAKS;
 
-    if (!(source_output = pa_source_output_new(c->protocol->core, &data, flags)))
+    source_output = pa_source_output_new(c->protocol->core, &data, flags);
+
+    pa_source_output_new_data_done(&data);
+
+    if (!source_output)
         return NULL;
 
     s = pa_msgobject_new(record_stream);
@@ -482,6 +570,7 @@ static record_stream* record_stream_new(
     s->parent.process_msg = record_stream_process_msg;
     s->connection = c;
     s->source_output = source_output;
+
     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;
@@ -489,23 +578,19 @@ static record_stream* record_stream_new(
     s->source_output->suspend = source_output_suspend_cb;
     s->source_output->userdata = s;
 
+    fix_record_buffer_attr_pre(s, adjust_latency, maxlength, fragsize);
+
     s->memblockq = pa_memblockq_new(
             0,
             *maxlength,
             0,
-            base = pa_frame_size(&s->source_output->sample_spec),
+            base = pa_frame_size(&source_output->sample_spec),
             1,
             0,
+            0,
             NULL);
 
-    *maxlength = pa_memblockq_get_maxlength(s->memblockq);
-
-    s->fragment_size = (fragment_size/base)*base;
-    if (s->fragment_size <= 0)
-        s->fragment_size = base;
-
-    if (s->fragment_size > *maxlength)
-        s->fragment_size = *maxlength;
+    fix_record_buffer_attr_post(s, maxlength, fragsize);
 
     *ss = s->source_output->sample_spec;
     *map = s->source_output->channel_map;
@@ -559,21 +644,14 @@ static int playback_stream_process_msg(pa_msgobject *o, int code, void*userdata,
             uint32_t l = 0;
 
             for (;;) {
-                int32_t k;
-
-                if ((k = pa_atomic_load(&s->missing)) <= 0)
+                if ((l = pa_atomic_load(&s->missing)) <= 0)
                     break;
 
-                l += k;
-
-                if (l < s->minreq)
-                    break;
-
-                if (pa_atomic_sub(&s->missing, k) <= k)
+                if (pa_atomic_cmpxchg(&s->missing, l, 0))
                     break;
             }
 
-            if (l < s->minreq)
+            if (l <= 0)
                 break;
 
             t = pa_tagstruct_new(NULL, 0);
@@ -583,7 +661,7 @@ static int playback_stream_process_msg(pa_msgobject *o, int code, void*userdata,
             pa_tagstruct_putu32(t, l);
             pa_pstream_send_tagstruct(s->connection->pstream, t);
 
-/*             pa_log("Requesting %u bytes", l);     */
+/*             pa_log("Requesting %lu bytes", (unsigned long) l); */
             break;
         }
 
@@ -611,41 +689,172 @@ static int playback_stream_process_msg(pa_msgobject *o, int code, void*userdata,
             break;
         }
 
+        case PLAYBACK_STREAM_MESSAGE_STARTED:
+
+            if (s->connection->version >= 13) {
+                pa_tagstruct *t;
+
+                /* Notify the user we're overflowed*/
+                t = pa_tagstruct_new(NULL, 0);
+                pa_tagstruct_putu32(t, PA_COMMAND_STARTED);
+                pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */
+                pa_tagstruct_putu32(t, s->index);
+                pa_pstream_send_tagstruct(s->connection->pstream, t);
+            }
+
+            break;
+
         case PLAYBACK_STREAM_MESSAGE_DRAIN_ACK:
             pa_pstream_send_simple_ack(s->connection->pstream, PA_PTR_TO_UINT(userdata));
             break;
-
     }
 
     return 0;
 }
 
+static void fix_playback_buffer_attr_pre(playback_stream *s, pa_bool_t adjust_latency, uint32_t *maxlength, uint32_t *tlength, uint32_t* prebuf, uint32_t* minreq) {
+    size_t frame_size;
+    pa_usec_t tlength_usec, minreq_usec, sink_usec;
+
+    pa_assert(s);
+    pa_assert(maxlength);
+    pa_assert(tlength);
+    pa_assert(prebuf);
+    pa_assert(minreq);
+
+    if (*maxlength <= 0 || *maxlength > MAX_MEMBLOCKQ_LENGTH)
+        *maxlength = MAX_MEMBLOCKQ_LENGTH;
+    if (*tlength <= 0)
+        *tlength = pa_usec_to_bytes(DEFAULT_TLENGTH_MSEC*PA_USEC_PER_MSEC, &s->sink_input->sample_spec);
+    if (*minreq <= 0)
+        *minreq = pa_usec_to_bytes(DEFAULT_PROCESS_MSEC*PA_USEC_PER_MSEC, &s->sink_input->sample_spec);
+
+    frame_size = pa_frame_size(&s->sink_input->sample_spec);
+    if (*minreq <= 0)
+        *minreq = frame_size;
+    if (*tlength < *minreq+frame_size)
+        *tlength = *minreq+frame_size;
+
+    tlength_usec = pa_bytes_to_usec(*tlength, &s->sink_input->sample_spec);
+    minreq_usec = pa_bytes_to_usec(*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 (adjust_latency) {
+
+        /* So, the user asked us to adjust the latency of the stream
+         * buffer according to the what the sink can provide. The
+         * tlength passed in shall be the overall latency. Roughly
+         * half the latency will be spent on the hw buffer, the other
+         * half of it in the async buffer queue we maintain for each
+         * client. In between we'll have a safety space of size
+         * 2*minreq. Why the 2*minreq? When the hw buffer is completey
+         * empty and needs to be filled, then our buffer must have
+         * enough data to fulfill this request immediatly and thus
+         * have at least the same tlength as the size of the hw
+         * buffer. It additionally needs space for 2 times minreq
+         * because if the buffer ran empty and a partial fillup
+         * happens immediately on the next iteration we need to be
+         * able to fulfill it and give the application also minreq
+         * time to fill it up again for the next request Makes 2 times
+         * minreq in plus.. */
+
+        if (tlength_usec > minreq_usec*2)
+            sink_usec = (tlength_usec - minreq_usec*2)/2;
+        else
+            sink_usec = 0;
+
+    } else {
+
+        /* Ok, the user didn't ask us to adjust the latency, but we
+         * still need to make sure that the parameters from the user
+         * do make sense. */
+
+        if (tlength_usec > minreq_usec*2)
+            sink_usec = (tlength_usec - minreq_usec*2);
+        else
+            sink_usec = 0;
+    }
+
+    s->sink_latency = pa_sink_input_set_requested_latency(s->sink_input, sink_usec);
+
+    if (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->sink_latency + 2*minreq_usec)
+        tlength_usec = s->sink_latency + 2*minreq_usec;
+
+    *tlength = pa_usec_to_bytes(tlength_usec, &s->sink_input->sample_spec);
+    *minreq = pa_usec_to_bytes(minreq_usec, &s->sink_input->sample_spec);
+
+    if (*minreq <= 0) {
+        *minreq += frame_size;
+        *tlength += frame_size*2;
+    }
+
+    if (*tlength <= *minreq)
+        *tlength =  *minreq*2 + frame_size;
+
+    if (*prebuf <= 0)
+        *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);
+
+    *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);
+
+    s->minreq = *minreq;
+}
+
 static playback_stream* playback_stream_new(
         connection *c,
         pa_sink *sink,
         pa_sample_spec *ss,
         pa_channel_map *map,
-        const char *name,
         uint32_t *maxlength,
         uint32_t *tlength,
         uint32_t *prebuf,
         uint32_t *minreq,
         pa_cvolume *volume,
+        pa_bool_t muted,
         uint32_t syncid,
         uint32_t *missing,
-        pa_sink_input_flags_t flags) {
+        pa_sink_input_flags_t flags,
+        pa_proplist *p,
+        pa_bool_t adjust_latency) {
 
     playback_stream *s, *ssync;
     pa_sink_input *sink_input;
-    pa_memblock *silence;
+    pa_memchunk silence;
     uint32_t idx;
     int64_t start_index;
     pa_sink_input_new_data data;
 
     pa_assert(c);
     pa_assert(ss);
-    pa_assert(name);
     pa_assert(maxlength);
+    pa_assert(tlength);
+    pa_assert(prebuf);
+    pa_assert(minreq);
+    pa_assert(volume);
+    pa_assert(missing);
+    pa_assert(p);
 
     /* Find syncid group */
     for (ssync = pa_idxset_first(c->output_streams, &idx); ssync; ssync = pa_idxset_next(c->output_streams, &idx)) {
@@ -667,17 +876,24 @@ static playback_stream* playback_stream_new(
     }
 
     pa_sink_input_new_data_init(&data);
-    data.sink = sink;
+
+    pa_proplist_update(data.proplist, PA_UPDATE_REPLACE, p);
+    pa_proplist_update(data.proplist, PA_UPDATE_MERGE, c->client->proplist);
     data.driver = __FILE__;
-    data.name = name;
+    data.module = c->protocol->module;
+    data.client = c->client;
+    data.sink = sink;
     pa_sink_input_new_data_set_sample_spec(&data, ss);
     pa_sink_input_new_data_set_channel_map(&data, map);
     pa_sink_input_new_data_set_volume(&data, volume);
-    data.module = c->protocol->module;
-    data.client = c->client;
+    pa_sink_input_new_data_set_muted(&data, muted);
     data.sync_base = ssync ? ssync->sink_input : NULL;
 
-    if (!(sink_input = pa_sink_input_new(c->protocol->core, &data, flags)))
+    sink_input = pa_sink_input_new(c->protocol->core, &data, flags);
+
+    pa_sink_input_new_data_done(&data);
+
+    if (!sink_input)
         return NULL;
 
     s = pa_msgobject_new(playback_stream);
@@ -686,11 +902,11 @@ static playback_stream* playback_stream_new(
     s->connection = c;
     s->syncid = syncid;
     s->sink_input = sink_input;
-    s->underrun = 1;
 
     s->sink_input->parent.process_msg = sink_input_process_msg;
-    s->sink_input->peek = sink_input_peek_cb;
-    s->sink_input->drop = sink_input_drop_cb;
+    s->sink_input->pop = sink_input_pop_cb;
+    s->sink_input->process_rewind = sink_input_process_rewind_cb;
+    s->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
     s->sink_input->kill = sink_input_kill_cb;
     s->sink_input->moved = sink_input_moved_cb;
     s->sink_input->suspend = sink_input_suspend_cb;
@@ -698,36 +914,39 @@ static playback_stream* playback_stream_new(
 
     start_index = ssync ? pa_memblockq_get_read_index(ssync->memblockq) : 0;
 
-    silence = pa_silence_memblock_new(c->protocol->core->mempool, &s->sink_input->sample_spec, 0);
+    fix_playback_buffer_attr_pre(s, adjust_latency, maxlength, tlength, prebuf, minreq);
+    pa_sink_input_get_silence(sink_input, &silence);
 
     s->memblockq = pa_memblockq_new(
             start_index,
             *maxlength,
             *tlength,
-            pa_frame_size(&s->sink_input->sample_spec),
+            pa_frame_size(&sink_input->sample_spec),
             *prebuf,
             *minreq,
-            silence);
+            0,
+            &silence);
 
-    pa_memblock_unref(silence);
+    pa_memblock_unref(silence.memblock);
+    fix_playback_buffer_attr_post(s, maxlength, tlength, prebuf, minreq);
 
-    *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);
     *missing = (uint32_t) pa_memblockq_pop_missing(s->memblockq);
 
     *ss = s->sink_input->sample_spec;
     *map = s->sink_input->channel_map;
 
-    s->minreq = pa_memblockq_get_minreq(s->memblockq);
     pa_atomic_store(&s->missing, 0);
-    s->drain_request = 0;
+    s->drain_request = FALSE;
 
     pa_idxset_put(c->output_streams, s, &s->index);
 
-    pa_sink_input_put(s->sink_input);
+    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);
 
+    pa_sink_input_put(s->sink_input);
     return s;
 }
 
@@ -814,13 +1033,13 @@ static void request_bytes(playback_stream *s) {
     if (m <= 0)
         return;
 
-/*     pa_log("request_bytes(%u)", m); */
+/*     pa_log("request_bytes(%lu)", (unsigned long) m); */
 
     previous_missing = pa_atomic_add(&s->missing, m);
-    if (previous_missing < s->minreq && previous_missing+m >= s->minreq) {
-        pa_assert(pa_thread_mq_get());
+
+    if (pa_memblockq_prebuf_active(s->memblockq) ||
+        (previous_missing < s->minreq && previous_missing+m >= s->minreq))
         pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_REQUEST_DATA, NULL, 0, NULL, NULL);
-    }
 }
 
 static void send_memblock(connection *c) {
@@ -879,6 +1098,43 @@ static void send_record_stream_killed(record_stream *r) {
 
 /*** sink input callbacks ***/
 
+static void handle_seek(playback_stream *s, int64_t indexw) {
+    playback_stream_assert_ref(s);
+
+/*     pa_log("handle_seek: %llu -- %i", (unsigned long long) s->sink_input->thread_info.underrun_for, pa_memblockq_is_readable(s->memblockq)); */
+
+    if (s->sink_input->thread_info.underrun_for > 0) {
+
+/*         pa_log("%lu vs. %lu", (unsigned long) pa_memblockq_get_length(s->memblockq), (unsigned long) pa_memblockq_get_prebuf(s->memblockq)); */
+
+        if (pa_memblockq_is_readable(s->memblockq)) {
+
+            /* We just ended an underrun, let's ask the sink
+             * for a complete rewind rewrite */
+
+            pa_log_debug("Requesting rewind due to end of underrun.");
+            pa_sink_input_request_rewind(s->sink_input,
+                                         s->sink_input->thread_info.underrun_for == (size_t) -1 ? 0 : s->sink_input->thread_info.underrun_for,
+                                         FALSE, TRUE);
+        }
+
+    } else {
+        int64_t indexr;
+
+        indexr = pa_memblockq_get_read_index(s->memblockq);
+
+        if (indexw < indexr) {
+            /* OK, the sink already asked for this data, so
+             * let's have it usk us again */
+
+            pa_log_debug("Requesting rewind due to rewrite.");
+            pa_sink_input_request_rewind(s->sink_input, indexr - indexw, TRUE, FALSE);
+        }
+    }
+
+    request_bytes(s);
+}
+
 /* Called from thread context */
 static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) {
     pa_sink_input *i = PA_SINK_INPUT(o);
@@ -890,48 +1146,44 @@ static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int
 
     switch (code) {
 
-        case SINK_INPUT_MESSAGE_SEEK:
+        case SINK_INPUT_MESSAGE_SEEK: {
+            int64_t windex;
+
+            windex = pa_memblockq_get_write_index(s->memblockq);
             pa_memblockq_seek(s->memblockq, offset, PA_PTR_TO_UINT(userdata));
-            request_bytes(s);
+
+            handle_seek(s, windex);
             return 0;
+        }
 
         case SINK_INPUT_MESSAGE_POST_DATA: {
+            int64_t windex;
+
             pa_assert(chunk);
 
-/*             pa_log("sink input post: %u", chunk->length); */
+            windex = pa_memblockq_get_write_index(s->memblockq);
 
-            if (pa_memblockq_push_align(s->memblockq, chunk) < 0) {
+/*             pa_log("sink input post: %lu %lli", (unsigned long) chunk->length, (long long) windex); */
 
+            if (pa_memblockq_push_align(s->memblockq, chunk) < 0) {
                 pa_log_warn("Failed to push data into queue");
                 pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_OVERFLOW, NULL, 0, NULL, NULL);
                 pa_memblockq_seek(s->memblockq, chunk->length, PA_SEEK_RELATIVE);
             }
 
-            request_bytes(s);
-
-            s->underrun = 0;
-            return 0;
-        }
-
-        case SINK_INPUT_MESSAGE_DRAIN: {
-
-            pa_memblockq_prebuf_disable(s->memblockq);
+            handle_seek(s, windex);
 
-            if (!pa_memblockq_is_readable(s->memblockq))
-                pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_DRAIN_ACK, userdata, 0, NULL, NULL);
-            else {
-                s->drain_tag = PA_PTR_TO_UINT(userdata);
-                s->drain_request = 1;
-            }
-            request_bytes(s);
+/*             pa_log("sink input post2: %lu", (unsigned long) pa_memblockq_get_length(s->memblockq)); */
 
             return 0;
         }
 
+        case SINK_INPUT_MESSAGE_DRAIN:
         case SINK_INPUT_MESSAGE_FLUSH:
         case SINK_INPUT_MESSAGE_PREBUF_FORCE:
         case SINK_INPUT_MESSAGE_TRIGGER: {
 
+            int64_t windex;
             pa_sink_input *isync;
             void (*func)(pa_memblockq *bq);
 
@@ -944,6 +1196,7 @@ static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int
                     func = pa_memblockq_prebuf_force;
                     break;
 
+                case SINK_INPUT_MESSAGE_DRAIN:
                 case SINK_INPUT_MESSAGE_TRIGGER:
                     func = pa_memblockq_prebuf_disable;
                     break;
@@ -952,23 +1205,32 @@ static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int
                     pa_assert_not_reached();
             }
 
+            windex = pa_memblockq_get_write_index(s->memblockq);
             func(s->memblockq);
-            s->underrun = 0;
-            request_bytes(s);
+            handle_seek(s, windex);
 
             /* Do the same for all other members in the sync group */
             for (isync = i->sync_prev; isync; isync = isync->sync_prev) {
                 playback_stream *ssync = PLAYBACK_STREAM(isync->userdata);
+                windex = pa_memblockq_get_write_index(ssync->memblockq);
                 func(ssync->memblockq);
-                ssync->underrun = 0;
-                request_bytes(ssync);
+                handle_seek(ssync, windex);
             }
 
             for (isync = i->sync_next; isync; isync = isync->sync_next) {
                 playback_stream *ssync = PLAYBACK_STREAM(isync->userdata);
+                windex = pa_memblockq_get_write_index(ssync->memblockq);
                 func(ssync->memblockq);
-                ssync->underrun = 0;
-                request_bytes(ssync);
+                handle_seek(ssync, windex);
+            }
+
+            if (code == SINK_INPUT_MESSAGE_DRAIN) {
+                if (!pa_memblockq_is_readable(s->memblockq))
+                    pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_DRAIN_ACK, userdata, 0, NULL, NULL);
+                else {
+                    s->drain_tag = PA_PTR_TO_UINT(userdata);
+                    s->drain_request = TRUE;
+                }
             }
 
             return 0;
@@ -978,14 +1240,21 @@ static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int
 
             s->read_index = pa_memblockq_get_read_index(s->memblockq);
             s->write_index = pa_memblockq_get_write_index(s->memblockq);
-            s->resampled_chunk_length = s->sink_input->thread_info.resampled_chunk.memblock ? s->sink_input->thread_info.resampled_chunk.length : 0;
+            s->render_memblockq_length = pa_memblockq_get_length(s->sink_input->thread_info.render_memblockq);
             return 0;
 
-        case PA_SINK_INPUT_MESSAGE_SET_STATE:
+        case PA_SINK_INPUT_MESSAGE_SET_STATE: {
+            int64_t windex;
+
+            windex = pa_memblockq_get_write_index(s->memblockq);
 
             pa_memblockq_prebuf_force(s->memblockq);
-            request_bytes(s);
+
+            handle_seek(s, windex);
+
+            /* Fall through to the default handler */
             break;
+        }
 
         case PA_SINK_INPUT_MESSAGE_GET_LATENCY: {
             pa_usec_t *r = userdata;
@@ -1002,7 +1271,7 @@ static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int
 }
 
 /* Called from thread context */
-static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
+static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) {
     playback_stream *s;
 
     pa_sink_input_assert_ref(i);
@@ -1010,42 +1279,56 @@ static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chun
     playback_stream_assert_ref(s);
     pa_assert(chunk);
 
-    if (pa_memblockq_get_length(s->memblockq) <= 0 && !s->underrun) {
-        pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_UNDERFLOW, NULL, 0, NULL, NULL);
-        s->underrun = 1;
-    }
-
     if (pa_memblockq_peek(s->memblockq, chunk) < 0) {
-/*         pa_log("peek: failure");     */
+
+/*         pa_log("UNDERRUN: %lu", pa_memblockq_get_length(s->memblockq)); */
+
+        if (s->drain_request && pa_sink_input_safe_to_remove(i)) {
+            s->drain_request = FALSE;
+            pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_DRAIN_ACK, PA_UINT_TO_PTR(s->drain_tag), 0, NULL, NULL);
+        } else if (i->thread_info.playing_for > 0)
+            pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_UNDERFLOW, NULL, 0, NULL, NULL);
+
+/*         pa_log("adding %llu bytes", (unsigned long long) nbytes); */
+
+        request_bytes(s);
+
         return -1;
     }
 
-/*     pa_log("peek: %u", chunk->length); */
+/*     pa_log("NOTUNDERRUN %lu", (unsigned long) chunk->length); */
+
+    if (i->thread_info.underrun_for > 0)
+        pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_STARTED, NULL, 0, NULL, NULL);
 
+    pa_memblockq_drop(s->memblockq, chunk->length);
     request_bytes(s);
 
     return 0;
 }
 
-/* Called from thread context */
-static void sink_input_drop_cb(pa_sink_input *i, size_t length) {
+static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
     playback_stream *s;
 
     pa_sink_input_assert_ref(i);
     s = PLAYBACK_STREAM(i->userdata);
     playback_stream_assert_ref(s);
-    pa_assert(length > 0);
 
-    pa_memblockq_drop(s->memblockq, length);
+    /* If we are in an underrun, then we don't rewind */
+    if (i->thread_info.underrun_for > 0)
+        return;
 
-    if (s->drain_request && !pa_memblockq_is_readable(s->memblockq)) {
-        s->drain_request = 0;
-        pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_DRAIN_ACK, PA_UINT_TO_PTR(s->drain_tag), 0, NULL, NULL);
-    }
+    pa_memblockq_rewind(s->memblockq, nbytes);
+}
 
-    request_bytes(s);
+static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
+    playback_stream *s;
+
+    pa_sink_input_assert_ref(i);
+    s = PLAYBACK_STREAM(i->userdata);
+    playback_stream_assert_ref(s);
 
-/*     pa_log("after_drop: %u %u", pa_memblockq_get_length(s->memblockq), pa_memblockq_is_readable(s->memblockq)); */
+    pa_memblockq_set_maxrewind(s->memblockq, nbytes);
 }
 
 /* Called from main context */
@@ -1084,11 +1367,24 @@ static void sink_input_suspend_cb(pa_sink_input *i, pa_bool_t suspend) {
 static void sink_input_moved_cb(pa_sink_input *i) {
     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, &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);
+
     if (s->connection->version < 12)
       return;
 
@@ -1099,6 +1395,15 @@ static void sink_input_moved_cb(pa_sink_input *i) {
     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);
+
+    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_pstream_send_tagstruct(s->connection->pstream, t);
 }
 
@@ -1163,11 +1468,19 @@ static void source_output_suspend_cb(pa_source_output *o, pa_bool_t suspend) {
 static void source_output_moved_cb(pa_source_output *o) {
     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, &maxlength, &fragsize);
+    pa_memblockq_set_maxlength(s->memblockq, maxlength);
+    fix_record_buffer_attr_post(s, &maxlength, &fragsize);
+
     if (s->connection->version < 12)
       return;
 
@@ -1178,6 +1491,13 @@ static void source_output_moved_cb(pa_source_output *o) {
     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);
+
+    if (s->connection->version >= 13) {
+        pa_tagstruct_putu32(t, maxlength);
+        pa_tagstruct_putu32(t, fragsize);
+        pa_tagstruct_put_usec(t, s->source_latency);
+    }
+
     pa_pstream_send_tagstruct(s->connection->pstream, t);
 }
 
@@ -1208,38 +1528,62 @@ static void command_create_playback_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GC
     connection *c = CONNECTION(userdata);
     playback_stream *s;
     uint32_t maxlength, tlength, prebuf, minreq, sink_index, syncid, missing;
-    const char *name, *sink_name;
+    const char *name = NULL, *sink_name;
     pa_sample_spec ss;
     pa_channel_map map;
     pa_tagstruct *reply;
     pa_sink *sink = NULL;
     pa_cvolume volume;
-    int corked;
-    int no_remap = 0, no_remix = 0, fix_format = 0, fix_rate = 0, fix_channels = 0, no_move = 0, variable_rate = 0;
+    pa_bool_t
+        corked = FALSE,
+        no_remap = FALSE,
+        no_remix = FALSE,
+        fix_format = FALSE,
+        fix_rate = FALSE,
+        fix_channels = FALSE,
+        no_move = FALSE,
+        variable_rate = FALSE,
+        muted = FALSE,
+        adjust_latency = FALSE;
+
     pa_sink_input_flags_t flags = 0;
+    pa_proplist *p;
 
     connection_assert_ref(c);
     pa_assert(t);
 
-    if (pa_tagstruct_get(
-            t,
-            PA_TAG_STRING, &name,
-            PA_TAG_SAMPLE_SPEC, &ss,
-            PA_TAG_CHANNEL_MAP, &map,
-            PA_TAG_U32, &sink_index,
-            PA_TAG_STRING, &sink_name,
-            PA_TAG_U32, &maxlength,
-            PA_TAG_BOOLEAN, &corked,
-            PA_TAG_U32, &tlength,
-            PA_TAG_U32, &prebuf,
-            PA_TAG_U32, &minreq,
-            PA_TAG_U32, &syncid,
-            PA_TAG_CVOLUME, &volume,
-            PA_TAG_INVALID) < 0 || !name) {
+    if ((c->version < 13 && (pa_tagstruct_gets(t, &name) < 0 || !name)) ||
+        pa_tagstruct_get(
+                t,
+                PA_TAG_SAMPLE_SPEC, &ss,
+                PA_TAG_CHANNEL_MAP, &map,
+                PA_TAG_U32, &sink_index,
+                PA_TAG_STRING, &sink_name,
+                PA_TAG_U32, &maxlength,
+                PA_TAG_BOOLEAN, &corked,
+                PA_TAG_U32, &tlength,
+                PA_TAG_U32, &prebuf,
+                PA_TAG_U32, &minreq,
+                PA_TAG_U32, &syncid,
+                PA_TAG_CVOLUME, &volume,
+                PA_TAG_INVALID) < 0) {
+
         protocol_error(c);
         return;
     }
 
+    CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+    CHECK_VALIDITY(c->pstream, sink_index != PA_INVALID_INDEX || !sink_name || (*sink_name && pa_utf8_valid(sink_name)), tag, PA_ERR_INVALID);
+    CHECK_VALIDITY(c->pstream, pa_channel_map_valid(&map), tag, PA_ERR_INVALID);
+    CHECK_VALIDITY(c->pstream, pa_sample_spec_valid(&ss), tag, PA_ERR_INVALID);
+    CHECK_VALIDITY(c->pstream, pa_cvolume_valid(&volume), tag, PA_ERR_INVALID);
+    CHECK_VALIDITY(c->pstream, map.channels == ss.channels && volume.channels == ss.channels, tag, PA_ERR_INVALID);
+
+    p = pa_proplist_new();
+
+    if (name)
+        pa_proplist_sets(p, PA_PROP_MEDIA_NAME, name);
+
     if (c->version >= 12)  {
         /* Since 0.9.8 the user can ask for a couple of additional flags */
 
@@ -1250,32 +1594,45 @@ static void command_create_playback_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GC
             pa_tagstruct_get_boolean(t, &fix_channels) < 0 ||
             pa_tagstruct_get_boolean(t, &no_move) < 0 ||
             pa_tagstruct_get_boolean(t, &variable_rate) < 0) {
+
+            protocol_error(c);
+            pa_proplist_free(p);
+            return;
+        }
+    }
+
+    if (c->version >= 13) {
+
+        if (pa_tagstruct_get_boolean(t, &muted) < 0 ||
+            pa_tagstruct_get_boolean(t, &adjust_latency) < 0 ||
+            pa_tagstruct_get_proplist(t, p) < 0) {
             protocol_error(c);
+            pa_proplist_free(p);
             return;
         }
     }
 
     if (!pa_tagstruct_eof(t)) {
         protocol_error(c);
+        pa_proplist_free(p);
         return;
     }
 
-    CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
-    CHECK_VALIDITY(c->pstream, name && pa_utf8_valid(name), tag, PA_ERR_INVALID);
-    CHECK_VALIDITY(c->pstream, sink_index != PA_INVALID_INDEX || !sink_name || (*sink_name && pa_utf8_valid(name)), tag, PA_ERR_INVALID);
-    CHECK_VALIDITY(c->pstream, pa_channel_map_valid(&map), tag, PA_ERR_INVALID);
-    CHECK_VALIDITY(c->pstream, pa_sample_spec_valid(&ss), tag, PA_ERR_INVALID);
-    CHECK_VALIDITY(c->pstream, pa_cvolume_valid(&volume), tag, PA_ERR_INVALID);
-    CHECK_VALIDITY(c->pstream, map.channels == ss.channels && volume.channels == ss.channels, tag, PA_ERR_INVALID);
-    CHECK_VALIDITY(c->pstream, maxlength > 0, tag, PA_ERR_INVALID);
-    CHECK_VALIDITY(c->pstream, maxlength <= MAX_MEMBLOCKQ_LENGTH, tag, PA_ERR_INVALID);
-
     if (sink_index != PA_INVALID_INDEX) {
-        sink = pa_idxset_get_by_index(c->protocol->core->sinks, sink_index);
-        CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY);
+
+        if (!(sink = pa_idxset_get_by_index(c->protocol->core->sinks, sink_index))) {
+            pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY);
+            pa_proplist_free(p);
+            return;
+        }
+
     } else if (sink_name) {
-        sink = pa_namereg_get(c->protocol->core, sink_name, PA_NAMEREG_SINK, 1);
-        CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY);
+
+        if (!(sink = pa_namereg_get(c->protocol->core, sink_name, PA_NAMEREG_SINK, 1))) {
+            pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY);
+            pa_proplist_free(p);
+            return;
+        }
     }
 
     flags =
@@ -1288,7 +1645,9 @@ static void command_create_playback_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GC
         (no_move ?  PA_SINK_INPUT_DONT_MOVE : 0) |
         (variable_rate ?  PA_SINK_INPUT_VARIABLE_RATE : 0);
 
-    s = playback_stream_new(c, sink, &ss, &map, name, &maxlength, &tlength, &prebuf, &minreq, &volume, syncid, &missing, flags);
+    s = playback_stream_new(c, sink, &ss, &map, &maxlength, &tlength, &prebuf, &minreq, &volume, muted, syncid, &missing, flags, p, adjust_latency);
+    pa_proplist_free(p);
+
     CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_INVALID);
 
     reply = reply_new(tag);
@@ -1322,6 +1681,9 @@ static void command_create_playback_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GC
         pa_tagstruct_put_boolean(reply, pa_sink_get_state(s->sink_input->sink) == PA_SINK_SUSPENDED);
     }
 
+    if (c->version >= 13)
+        pa_tagstruct_put_usec(reply, s->sink_latency);
+
     pa_pstream_send_tagstruct(c->pstream, reply);
 }
 
@@ -1393,14 +1755,24 @@ static void command_create_record_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_
     pa_channel_map map;
     pa_tagstruct *reply;
     pa_source *source = NULL;
-    int corked;
-    int no_remap = 0, no_remix = 0, fix_format = 0, fix_rate = 0, fix_channels = 0, no_move = 0, variable_rate = 0;
+    pa_bool_t
+        corked = FALSE,
+        no_remap = FALSE,
+        no_remix = FALSE,
+        fix_format = FALSE,
+        fix_rate = FALSE,
+        fix_channels = FALSE,
+        no_move = FALSE,
+        variable_rate = FALSE,
+        adjust_latency = FALSE,
+        peak_detect = FALSE;
     pa_source_output_flags_t flags = 0;
+    pa_proplist *p;
 
     connection_assert_ref(c);
     pa_assert(t);
 
-    if (pa_tagstruct_gets(t, &name) < 0 ||
+    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 ||
@@ -1412,6 +1784,17 @@ static void command_create_record_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_
         return;
     }
 
+    CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+    CHECK_VALIDITY(c->pstream, pa_sample_spec_valid(&ss), tag, PA_ERR_INVALID);
+    CHECK_VALIDITY(c->pstream, pa_channel_map_valid(&map), tag, PA_ERR_INVALID);
+    CHECK_VALIDITY(c->pstream, source_index != PA_INVALID_INDEX || !source_name || (*source_name && pa_utf8_valid(source_name)), tag, PA_ERR_INVALID);
+    CHECK_VALIDITY(c->pstream, map.channels == ss.channels, tag, PA_ERR_INVALID);
+
+    p = pa_proplist_new();
+
+    if (name)
+        pa_proplist_sets(p, PA_PROP_MEDIA_NAME, name);
+
     if (c->version >= 12)  {
         /* Since 0.9.8 the user can ask for a couple of additional flags */
 
@@ -1422,16 +1805,47 @@ static void command_create_record_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_
             pa_tagstruct_get_boolean(t, &fix_channels) < 0 ||
             pa_tagstruct_get_boolean(t, &no_move) < 0 ||
             pa_tagstruct_get_boolean(t, &variable_rate) < 0) {
+
             protocol_error(c);
+            pa_proplist_free(p);
+            return;
+        }
+    }
+
+    if (c->version >= 13) {
+
+        if (pa_tagstruct_get_boolean(t, &peak_detect) < 0 ||
+            pa_tagstruct_get_boolean(t, &adjust_latency) < 0 ||
+            pa_tagstruct_get_proplist(t, p) < 0) {
+            protocol_error(c);
+            pa_proplist_free(p);
             return;
         }
     }
 
     if (!pa_tagstruct_eof(t)) {
         protocol_error(c);
+        pa_proplist_free(p);
         return;
     }
 
+    if (source_index != PA_INVALID_INDEX) {
+
+        if (!(source = pa_idxset_get_by_index(c->protocol->core->sources, source_index))) {
+            pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY);
+            pa_proplist_free(p);
+            return;
+        }
+
+    } else if (source_name) {
+
+        if (!(source = pa_namereg_get(c->protocol->core, source_name, PA_NAMEREG_SOURCE, 1))) {
+            pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY);
+            pa_proplist_free(p);
+            return;
+        }
+    }
+
     flags =
         (corked ?  PA_SOURCE_OUTPUT_START_CORKED : 0) |
         (no_remap ?  PA_SOURCE_OUTPUT_NO_REMAP : 0) |
@@ -1442,24 +1856,9 @@ static void command_create_record_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_
         (no_move ?  PA_SOURCE_OUTPUT_DONT_MOVE : 0) |
         (variable_rate ?  PA_SOURCE_OUTPUT_VARIABLE_RATE : 0);
 
-    CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
-    CHECK_VALIDITY(c->pstream, name && pa_utf8_valid(name), tag, PA_ERR_INVALID);
-    CHECK_VALIDITY(c->pstream, pa_sample_spec_valid(&ss), tag, PA_ERR_INVALID);
-    CHECK_VALIDITY(c->pstream, pa_channel_map_valid(&map), tag, PA_ERR_INVALID);
-    CHECK_VALIDITY(c->pstream, source_index != PA_INVALID_INDEX || !source_name || (*source_name && pa_utf8_valid(source_name)), tag, PA_ERR_INVALID);
-    CHECK_VALIDITY(c->pstream, map.channels == ss.channels, tag, PA_ERR_INVALID);
-    CHECK_VALIDITY(c->pstream, maxlength > 0, tag, PA_ERR_INVALID);
-    CHECK_VALIDITY(c->pstream, maxlength <= MAX_MEMBLOCKQ_LENGTH, tag, PA_ERR_INVALID);
-
-    if (source_index != PA_INVALID_INDEX) {
-        source = pa_idxset_get_by_index(c->protocol->core->sources, source_index);
-        CHECK_VALIDITY(c->pstream, source, tag, PA_ERR_NOENTITY);
-    } else if (source_name) {
-        source = pa_namereg_get(c->protocol->core, source_name, PA_NAMEREG_SOURCE, 1);
-        CHECK_VALIDITY(c->pstream, source, tag, PA_ERR_NOENTITY);
-    }
+    s = record_stream_new(c, source, &ss, &map, peak_detect, &maxlength, &fragment_size, flags, p, adjust_latency);
+    pa_proplist_free(p);
 
-    s = record_stream_new(c, source, &ss, &map, name, &maxlength, fragment_size, flags);
     CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_INVALID);
 
     reply = reply_new(tag);
@@ -1471,7 +1870,7 @@ static void command_create_record_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_
         /* 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) s->fragment_size);
+        pa_tagstruct_putu32(reply, (uint32_t) fragment_size);
     }
 
     if (c->version >= 12) {
@@ -1488,6 +1887,9 @@ static void command_create_record_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_
         pa_tagstruct_put_boolean(reply, pa_source_get_state(s->source_output->source) == PA_SOURCE_SUSPENDED);
     }
 
+    if (c->version >= 13)
+        pa_tagstruct_put_usec(reply, s->source_latency);
+
     pa_pstream_send_tagstruct(c->pstream, reply);
 }
 
@@ -1512,6 +1914,7 @@ static void command_auth(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t
     connection *c = CONNECTION(userdata);
     const void*cookie;
     pa_tagstruct *reply;
+    char tmp[16];
 
     connection_assert_ref(c);
     pa_assert(t);
@@ -1529,29 +1932,32 @@ static void command_auth(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t
         return;
     }
 
+    pa_snprintf(tmp, sizeof(tmp), "%u", c->version);
+    pa_proplist_sets(c->client->proplist, "native-protocol.version", tmp);
+
     if (!c->authorized) {
-        int success = 0;
+        pa_bool_t success = FALSE;
 
 #ifdef HAVE_CREDS
         const pa_creds *creds;
 
         if ((creds = pa_pdispatch_creds(pd))) {
             if (creds->uid == getuid())
-                success = 1;
+                success = TRUE;
             else if (c->protocol->auth_group) {
                 int r;
                 gid_t gid;
 
                 if ((gid = pa_get_gid_of_group(c->protocol->auth_group)) == (gid_t) -1)
-                    pa_log_warn("failed to get GID of group '%s'", c->protocol->auth_group);
+                    pa_log_warn("Failed to get GID of group '%s'", c->protocol->auth_group);
                 else if (gid == creds->gid)
-                    success = 1;
+                    success = TRUE;
 
                 if (!success) {
                     if ((r = pa_uid_in_group(creds->uid, c->protocol->auth_group)) < 0)
-                        pa_log_warn("failed to check group membership.");
+                        pa_log_warn("Failed to check group membership.");
                     else if (r > 0)
-                        success = 1;
+                        success = TRUE;
                 }
             }
 
@@ -1564,7 +1970,7 @@ static void command_auth(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t
                 pa_mempool_is_shared(c->protocol->core->mempool) &&
                 creds->uid == getuid()) {
 
-                pa_pstream_use_shm(c->pstream, 1);
+                pa_pstream_enable_shm(c->pstream, TRUE);
                 pa_log_info("Enabled SHM for new connection");
             }
 
@@ -1572,7 +1978,7 @@ static void command_auth(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t
 #endif
 
         if (!success && memcmp(c->protocol->auth_cookie, cookie, PA_NATIVE_COOKIE_LENGTH) == 0)
-            success = 1;
+            success = TRUE;
 
         if (!success) {
             pa_log_warn("Denied access to client with invalid authorization data.");
@@ -1580,7 +1986,7 @@ static void command_auth(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t
             return;
         }
 
-        c->authorized = 1;
+        c->authorized = TRUE;
         if (c->auth_timeout_event) {
             c->protocol->core->mainloop->time_free(c->auth_timeout_event);
             c->auth_timeout_event = NULL;
@@ -1608,21 +2014,42 @@ static void command_auth(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t
 
 static void command_set_client_name(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
     connection *c = CONNECTION(userdata);
-    const char *name;
+    const char *name = NULL;
+    pa_proplist *p;
+    pa_tagstruct *reply;
 
     connection_assert_ref(c);
     pa_assert(t);
 
-    if (pa_tagstruct_gets(t, &name) < 0 ||
+    p = pa_proplist_new();
+
+    if ((c->version < 13 && pa_tagstruct_gets(t, &name) < 0) ||
+        (c->version >= 13 && pa_tagstruct_get_proplist(t, p) < 0) ||
         !pa_tagstruct_eof(t)) {
+
         protocol_error(c);
+        pa_proplist_free(p);
         return;
     }
 
-    CHECK_VALIDITY(c->pstream, name && pa_utf8_valid(name), tag, PA_ERR_INVALID);
+    if (name)
+        if (pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, name) < 0) {
+            pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID);
+            pa_proplist_free(p);
+            return;
+        }
 
-    pa_client_set_name(c->client, name);
-    pa_pstream_send_simple_ack(c->pstream, tag);
+    pa_proplist_update(c->client->proplist, PA_UPDATE_REPLACE, p);
+    pa_proplist_free(p);
+
+    pa_subscription_post(c->protocol->core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_CHANGE, c->client->index);
+
+    reply = reply_new(tag);
+
+    if (c->version >= 13)
+        pa_tagstruct_putu32(reply, c->client->index);
+
+    pa_pstream_send_tagstruct(c->pstream, reply);
 }
 
 static void command_lookup(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
@@ -1738,16 +2165,22 @@ static void command_get_playback_latency(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_
     reply = reply_new(tag);
 
     latency = pa_sink_get_latency(s->sink_input->sink);
-    latency += pa_bytes_to_usec(s->resampled_chunk_length, &s->sink_input->sample_spec);
+    latency += pa_bytes_to_usec(s->render_memblockq_length, &s->sink_input->sample_spec);
 
     pa_tagstruct_put_usec(reply, latency);
 
     pa_tagstruct_put_usec(reply, 0);
-    pa_tagstruct_put_boolean(reply, pa_sink_input_get_state(s->sink_input) == PA_SINK_INPUT_RUNNING);
+    pa_tagstruct_put_boolean(reply, s->sink_input->thread_info.playing_for > 0);
     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_pstream_send_tagstruct(c->pstream, reply);
 }
 
@@ -1775,7 +2208,7 @@ static void command_get_record_latency(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UN
     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, 0);
+    pa_tagstruct_put_boolean(reply, pa_source_get_state(s->source_output->source) == PA_SOURCE_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));
@@ -1787,19 +2220,19 @@ static void command_create_upload_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_
     connection *c = CONNECTION(userdata);
     upload_stream *s;
     uint32_t length;
-    const char *name;
+    const char *name = NULL;
     pa_sample_spec ss;
     pa_channel_map map;
     pa_tagstruct *reply;
+    pa_proplist *p;
 
     connection_assert_ref(c);
     pa_assert(t);
 
-    if (pa_tagstruct_gets(t, &name) < 0 ||
+    if ((c->version < 13 && pa_tagstruct_gets(t, &name) < 0) ||
         pa_tagstruct_get_sample_spec(t, &ss) < 0 ||
         pa_tagstruct_get_channel_map(t, &map) < 0 ||
-        pa_tagstruct_getu32(t, &length) < 0 ||
-        !pa_tagstruct_eof(t)) {
+        pa_tagstruct_getu32(t, &length) < 0) {
         protocol_error(c);
         return;
     }
@@ -1810,9 +2243,24 @@ static void command_create_upload_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_
     CHECK_VALIDITY(c->pstream, map.channels == ss.channels, tag, PA_ERR_INVALID);
     CHECK_VALIDITY(c->pstream, (length % pa_frame_size(&ss)) == 0 && length > 0, tag, PA_ERR_INVALID);
     CHECK_VALIDITY(c->pstream, length <= PA_SCACHE_ENTRY_SIZE_MAX, tag, PA_ERR_TOOLARGE);
-    CHECK_VALIDITY(c->pstream, name && *name && pa_utf8_valid(name), tag, PA_ERR_INVALID);
 
-    s = upload_stream_new(c, &ss, &map, name, length);
+    if (c->version < 13)
+        CHECK_VALIDITY(c->pstream, name && *name && pa_utf8_valid(name), tag, PA_ERR_INVALID);
+
+    p = pa_proplist_new();
+
+    if (c->version >= 13 && pa_tagstruct_get_proplist(t, p) < 0) {
+        protocol_error(c);
+        pa_proplist_free(p);
+        return;
+    }
+
+    if (c->version < 13)
+        pa_proplist_sets(p, PA_PROP_MEDIA_NAME, name);
+
+    s = upload_stream_new(c, &ss, &map, name, length, p);
+    pa_proplist_free(p);
+
     CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_INVALID);
 
     reply = reply_new(tag);
@@ -1842,7 +2290,7 @@ static void command_finish_upload_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_
     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, &idx) < 0)
+    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);
@@ -1856,20 +2304,23 @@ static void command_play_sample(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED ui
     pa_volume_t volume;
     pa_sink *sink;
     const char *name, *sink_name;
+    uint32_t idx;
+    pa_proplist *p;
+    pa_tagstruct *reply;
 
     connection_assert_ref(c);
     pa_assert(t);
 
+    CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+
     if (pa_tagstruct_getu32(t, &sink_index) < 0 ||
         pa_tagstruct_gets(t, &sink_name) < 0 ||
         pa_tagstruct_getu32(t, &volume) < 0 ||
-        pa_tagstruct_gets(t, &name) < 0 ||
-        !pa_tagstruct_eof(t)) {
+        pa_tagstruct_gets(t, &name) < 0) {
         protocol_error(c);
         return;
     }
 
-    CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
     CHECK_VALIDITY(c->pstream, sink_index != PA_INVALID_INDEX || !sink_name || (*sink_name && pa_utf8_valid(name)), tag, PA_ERR_INVALID);
     CHECK_VALIDITY(c->pstream, name && *name && pa_utf8_valid(name), tag, PA_ERR_INVALID);
 
@@ -1880,12 +2331,29 @@ static void command_play_sample(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED ui
 
     CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY);
 
-    if (pa_scache_play_item(c->protocol->core, name, sink, volume) < 0) {
+    p = pa_proplist_new();
+
+    if ((c->version >= 13 && pa_tagstruct_get_proplist(t, p) < 0) ||
+        !pa_tagstruct_eof(t)) {
+        protocol_error(c);
+        pa_proplist_free(p);
+        return;
+    }
+
+    if (pa_scache_play_item(c->protocol->core, name, sink, volume, p, &idx) < 0) {
         pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY);
+        pa_proplist_free(p);
         return;
     }
 
-    pa_pstream_send_simple_ack(c->pstream, tag);
+    pa_proplist_free(p);
+
+    reply = reply_new(tag);
+
+    if (c->version >= 13)
+        pa_tagstruct_putu32(reply, idx);
+
+    pa_pstream_send_tagstruct(c->pstream, reply);
 }
 
 static void command_remove_sample(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
@@ -1942,7 +2410,7 @@ static void sink_fill_tagstruct(connection *c, pa_tagstruct *t, pa_sink *sink) {
         t,
         PA_TAG_U32, sink->index,
         PA_TAG_STRING, sink->name,
-        PA_TAG_STRING, sink->description,
+        PA_TAG_STRING, pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)),
         PA_TAG_SAMPLE_SPEC, &fixed_ss,
         PA_TAG_CHANNEL_MAP, &sink->channel_map,
         PA_TAG_U32, sink->module ? sink->module->index : PA_INVALID_INDEX,
@@ -1954,6 +2422,11 @@ static void sink_fill_tagstruct(connection *c, pa_tagstruct *t, pa_sink *sink) {
         PA_TAG_STRING, sink->driver,
         PA_TAG_U32, sink->flags,
         PA_TAG_INVALID);
+
+    if (c->version >= 13) {
+        pa_tagstruct_put_proplist(t, sink->proplist);
+        pa_tagstruct_put_usec(t, pa_sink_get_requested_latency(sink));
+    }
 }
 
 static void source_fill_tagstruct(connection *c, pa_tagstruct *t, pa_source *source) {
@@ -1968,7 +2441,7 @@ static void source_fill_tagstruct(connection *c, pa_tagstruct *t, pa_source *sou
         t,
         PA_TAG_U32, source->index,
         PA_TAG_STRING, source->name,
-        PA_TAG_STRING, source->description,
+        PA_TAG_STRING, pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)),
         PA_TAG_SAMPLE_SPEC, &fixed_ss,
         PA_TAG_CHANNEL_MAP, &source->channel_map,
         PA_TAG_U32, source->module ? source->module->index : PA_INVALID_INDEX,
@@ -1980,16 +2453,26 @@ static void source_fill_tagstruct(connection *c, pa_tagstruct *t, pa_source *sou
         PA_TAG_STRING, source->driver,
         PA_TAG_U32, source->flags,
         PA_TAG_INVALID);
+
+    if (c->version >= 13) {
+        pa_tagstruct_put_proplist(t, source->proplist);
+        pa_tagstruct_put_usec(t, pa_source_get_requested_latency(source));
+    }
 }
 
-static void client_fill_tagstruct(pa_tagstruct *t, pa_client *client) {
+
+static void client_fill_tagstruct(connection *c, pa_tagstruct *t, pa_client *client) {
     pa_assert(t);
     pa_assert(client);
 
     pa_tagstruct_putu32(t, client->index);
-    pa_tagstruct_puts(t, client->name);
-    pa_tagstruct_putu32(t, client->owner ? client->owner->index : PA_INVALID_INDEX);
+    pa_tagstruct_puts(t, pa_strnull(pa_proplist_gets(client->proplist, PA_PROP_APPLICATION_NAME)));
+    pa_tagstruct_putu32(t, client->module ? client->module->index : PA_INVALID_INDEX);
     pa_tagstruct_puts(t, client->driver);
+
+    if (c->version >= 13)
+        pa_tagstruct_put_proplist(t, client->proplist);
+
 }
 
 static void module_fill_tagstruct(pa_tagstruct *t, pa_module *module) {
@@ -2012,7 +2495,7 @@ static void sink_input_fill_tagstruct(connection *c, pa_tagstruct *t, pa_sink_in
     fixup_sample_spec(c, &fixed_ss, &s->sample_spec);
 
     pa_tagstruct_putu32(t, s->index);
-    pa_tagstruct_puts(t, s->name);
+    pa_tagstruct_puts(t, pa_strnull(pa_proplist_gets(s->proplist, PA_PROP_MEDIA_NAME)));
     pa_tagstruct_putu32(t, s->module ? s->module->index : PA_INVALID_INDEX);
     pa_tagstruct_putu32(t, s->client ? s->client->index : PA_INVALID_INDEX);
     pa_tagstruct_putu32(t, s->sink->index);
@@ -2025,6 +2508,8 @@ static void sink_input_fill_tagstruct(connection *c, pa_tagstruct *t, pa_sink_in
     pa_tagstruct_puts(t, s->driver);
     if (c->version >= 11)
         pa_tagstruct_put_boolean(t, pa_sink_input_get_mute(s));
+    if (c->version >= 13)
+        pa_tagstruct_put_proplist(t, s->proplist);
 }
 
 static void source_output_fill_tagstruct(connection *c, pa_tagstruct *t, pa_source_output *s) {
@@ -2036,7 +2521,7 @@ static void source_output_fill_tagstruct(connection *c, pa_tagstruct *t, pa_sour
     fixup_sample_spec(c, &fixed_ss, &s->sample_spec);
 
     pa_tagstruct_putu32(t, s->index);
-    pa_tagstruct_puts(t, s->name);
+    pa_tagstruct_puts(t, pa_strnull(pa_proplist_gets(s->proplist, PA_PROP_MEDIA_NAME)));
     pa_tagstruct_putu32(t, s->module ? s->module->index : PA_INVALID_INDEX);
     pa_tagstruct_putu32(t, s->client ? s->client->index : PA_INVALID_INDEX);
     pa_tagstruct_putu32(t, s->source->index);
@@ -2046,6 +2531,9 @@ static void source_output_fill_tagstruct(connection *c, pa_tagstruct *t, pa_sour
     pa_tagstruct_put_usec(t, pa_source_get_latency(s->source));
     pa_tagstruct_puts(t, pa_resample_method_to_string(pa_source_output_get_resample_method(s)));
     pa_tagstruct_puts(t, s->driver);
+
+    if (c->version >= 13)
+        pa_tagstruct_put_proplist(t, s->proplist);
 }
 
 static void scache_fill_tagstruct(connection *c, pa_tagstruct *t, pa_scache_entry *e) {
@@ -2065,6 +2553,9 @@ static void scache_fill_tagstruct(connection *c, pa_tagstruct *t, pa_scache_entr
     pa_tagstruct_putu32(t, e->memchunk.length);
     pa_tagstruct_put_boolean(t, e->lazy);
     pa_tagstruct_puts(t, e->filename);
+
+    if (c->version >= 13)
+        pa_tagstruct_put_proplist(t, e->proplist);
 }
 
 static void command_get_info(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
@@ -2134,7 +2625,7 @@ static void command_get_info(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, u
     else if (source)
         source_fill_tagstruct(c, reply, source);
     else if (client)
-        client_fill_tagstruct(reply, client);
+        client_fill_tagstruct(c, reply, client);
     else if (module)
         module_fill_tagstruct(reply, module);
     else if (si)
@@ -2189,7 +2680,7 @@ static void command_get_info_list(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t comma
             else if (command == PA_COMMAND_GET_SOURCE_INFO_LIST)
                 source_fill_tagstruct(c, reply, p);
             else if (command == PA_COMMAND_GET_CLIENT_INFO_LIST)
-                client_fill_tagstruct(reply, p);
+                client_fill_tagstruct(c, reply, p);
             else if (command == PA_COMMAND_GET_MODULE_INFO_LIST)
                 module_fill_tagstruct(reply, p);
             else if (command == PA_COMMAND_GET_SINK_INPUT_INFO_LIST)
@@ -2227,7 +2718,7 @@ static void command_get_server_info(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSE
     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_fqdn(txt, sizeof(txt)));
+    pa_tagstruct_puts(reply, pa_get_host_name(txt, sizeof(txt)));
 
     fixup_sample_spec(c, &fixed_ss, &c->protocol->core->default_sample_spec);
     pa_tagstruct_put_sample_spec(reply, &fixed_ss);
@@ -2360,7 +2851,7 @@ static void command_set_mute(
 
     connection *c = CONNECTION(userdata);
     uint32_t idx;
-    int mute;
+    pa_bool_t mute;
     pa_sink *sink = NULL;
     pa_source *source = NULL;
     pa_sink_input *si = NULL;
@@ -2423,7 +2914,7 @@ static void command_set_mute(
 static void command_cork_playback_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
     connection *c = CONNECTION(userdata);
     uint32_t idx;
-    int b;
+    pa_bool_t b;
     playback_stream *s;
 
     connection_assert_ref(c);
@@ -2490,7 +2981,7 @@ static void command_cork_record_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UN
     connection *c = CONNECTION(userdata);
     uint32_t idx;
     record_stream *s;
-    int b;
+    pa_bool_t b;
 
     connection_assert_ref(c);
     pa_assert(t);
@@ -2551,6 +3042,7 @@ static void command_set_stream_buffer_attr(pa_pdispatch *pd, uint32_t command, u
 
     if (command == PA_COMMAND_SET_PLAYBACK_STREAM_BUFFER_ATTR) {
         playback_stream *s;
+        pa_bool_t adjust_latency = FALSE;
 
         s = pa_idxset_get_by_index(c->output_streams, idx);
         CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY);
@@ -2563,28 +3055,31 @@ static void command_set_stream_buffer_attr(pa_pdispatch *pd, uint32_t command, u
                     PA_TAG_U32, &prebuf,
                     PA_TAG_U32, &minreq,
                     PA_TAG_INVALID) < 0 ||
+            (c->version >= 13 && pa_tagstruct_get_boolean(t, &adjust_latency) < 0) ||
             !pa_tagstruct_eof(t)) {
             protocol_error(c);
             return;
         }
 
-        CHECK_VALIDITY(c->pstream, maxlength > 0, tag, PA_ERR_INVALID);
-        CHECK_VALIDITY(c->pstream, maxlength <= MAX_MEMBLOCKQ_LENGTH, tag, PA_ERR_INVALID);
-
+        fix_playback_buffer_attr_pre(s, adjust_latency, &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);
 
         reply = reply_new(tag);
-        pa_tagstruct_putu32(reply, (uint32_t) pa_memblockq_get_maxlength(s->memblockq));
-        pa_tagstruct_putu32(reply, (uint32_t) pa_memblockq_get_tlength(s->memblockq));
-        pa_tagstruct_putu32(reply, (uint32_t) pa_memblockq_get_prebuf(s->memblockq));
-        pa_tagstruct_putu32(reply, (uint32_t) pa_memblockq_get_minreq(s->memblockq));
+        pa_tagstruct_putu32(reply, maxlength);
+        pa_tagstruct_putu32(reply, tlength);
+        pa_tagstruct_putu32(reply, prebuf);
+        pa_tagstruct_putu32(reply, minreq);
+
+        if (c->version >= 13)
+            pa_tagstruct_put_usec(reply, s->sink_latency);
 
     } else {
         record_stream *s;
-        size_t base;
+        pa_bool_t adjust_latency = FALSE;
         pa_assert(command == PA_COMMAND_SET_RECORD_STREAM_BUFFER_ATTR);
 
         s = pa_idxset_get_by_index(c->record_streams, idx);
@@ -2595,27 +3090,22 @@ static void command_set_stream_buffer_attr(pa_pdispatch *pd, uint32_t command, u
                     PA_TAG_U32, &maxlength,
                     PA_TAG_U32, &fragsize,
                     PA_TAG_INVALID) < 0 ||
+            (c->version >= 13 && pa_tagstruct_get_boolean(t, &adjust_latency) < 0) ||
             !pa_tagstruct_eof(t)) {
             protocol_error(c);
             return;
         }
 
-        CHECK_VALIDITY(c->pstream, maxlength > 0, tag, PA_ERR_INVALID);
-        CHECK_VALIDITY(c->pstream, maxlength <= MAX_MEMBLOCKQ_LENGTH, tag, PA_ERR_INVALID);
-
+        fix_record_buffer_attr_pre(s, adjust_latency, &maxlength, &fragsize);
         pa_memblockq_set_maxlength(s->memblockq, maxlength);
-
-        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 > pa_memblockq_get_maxlength(s->memblockq))
-            s->fragment_size = pa_memblockq_get_maxlength(s->memblockq);
+        fix_record_buffer_attr_post(s, &maxlength, &fragsize);
 
         reply = reply_new(tag);
-        pa_tagstruct_putu32(reply, (uint32_t) pa_memblockq_get_maxlength(s->memblockq));
-        pa_tagstruct_putu32(reply, s->fragment_size);
+        pa_tagstruct_putu32(reply, maxlength);
+        pa_tagstruct_putu32(reply, fragsize);
+
+        if (c->version >= 13)
+            pa_tagstruct_put_usec(reply, s->source_latency);
     }
 
     pa_pstream_send_tagstruct(c->pstream, reply);
@@ -2661,6 +3151,168 @@ static void command_update_stream_sample_rate(pa_pdispatch *pd, uint32_t command
     pa_pstream_send_simple_ack(c->pstream, tag);
 }
 
+static void command_update_proplist(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+    connection *c = CONNECTION(userdata);
+    uint32_t idx;
+    uint32_t mode;
+    pa_proplist *p;
+
+    connection_assert_ref(c);
+    pa_assert(t);
+
+    CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+
+    p = pa_proplist_new();
+
+    if (command == PA_COMMAND_UPDATE_CLIENT_PROPLIST) {
+
+        if (pa_tagstruct_getu32(t, &mode) < 0 ||
+            pa_tagstruct_get_proplist(t, p) < 0 ||
+            !pa_tagstruct_eof(t)) {
+            protocol_error(c);
+            pa_proplist_free(p);
+            return;
+        }
+
+    } else {
+
+        if (pa_tagstruct_getu32(t, &idx) < 0 ||
+            pa_tagstruct_getu32(t, &mode) < 0 ||
+            pa_tagstruct_get_proplist(t, p) < 0 ||
+            !pa_tagstruct_eof(t)) {
+            protocol_error(c);
+            pa_proplist_free(p);
+            return;
+        }
+    }
+
+    CHECK_VALIDITY(c->pstream, mode == PA_UPDATE_SET || mode == PA_UPDATE_MERGE || mode == PA_UPDATE_REPLACE, tag, PA_ERR_INVALID);
+
+    if (command == PA_COMMAND_UPDATE_PLAYBACK_STREAM_PROPLIST) {
+        playback_stream *s;
+
+        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);
+
+        pa_proplist_update(s->sink_input->proplist, mode, p);
+        pa_subscription_post(c->protocol->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, s->sink_input->index);
+
+    } else if (command == PA_COMMAND_UPDATE_RECORD_STREAM_PROPLIST) {
+        record_stream *s;
+
+        s = pa_idxset_get_by_index(c->record_streams, idx);
+        CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY);
+
+        pa_proplist_update(s->source_output->proplist, mode, p);
+        pa_subscription_post(c->protocol->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, s->source_output->index);
+    } else {
+        pa_assert(command == PA_COMMAND_UPDATE_CLIENT_PROPLIST);
+
+        pa_proplist_update(c->client->proplist, mode, p);
+        pa_subscription_post(c->protocol->core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_CHANGE, c->client->index);
+    }
+
+    pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+static void command_remove_proplist(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+    connection *c = CONNECTION(userdata);
+    uint32_t idx;
+    unsigned changed = 0;
+    pa_proplist *p;
+    pa_strlist *l = NULL;
+
+    connection_assert_ref(c);
+    pa_assert(t);
+
+    CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+
+    if (command != PA_COMMAND_REMOVE_CLIENT_PROPLIST) {
+
+        if (pa_tagstruct_getu32(t, &idx) < 0) {
+            protocol_error(c);
+            return;
+        }
+    }
+
+    if (command == PA_COMMAND_REMOVE_PLAYBACK_STREAM_PROPLIST) {
+        playback_stream *s;
+
+        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);
+
+        p = s->sink_input->proplist;
+
+    } else if (command == PA_COMMAND_REMOVE_RECORD_STREAM_PROPLIST) {
+        record_stream *s;
+
+        s = pa_idxset_get_by_index(c->record_streams, idx);
+        CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY);
+
+        p = s->source_output->proplist;
+    } else {
+        pa_assert(command == PA_COMMAND_REMOVE_CLIENT_PROPLIST);
+
+        p = c->client->proplist;
+    }
+
+    for (;;) {
+        const char *k;
+
+        if (pa_tagstruct_gets(t, &k) < 0) {
+            protocol_error(c);
+            pa_strlist_free(l);
+            return;
+        }
+
+        if (!k)
+            break;
+
+        l = pa_strlist_prepend(l, k);
+    }
+
+    if (!pa_tagstruct_eof(t)) {
+        protocol_error(c);
+        pa_strlist_free(l);
+        return;
+    }
+
+    for (;;) {
+        char *z;
+
+        l = pa_strlist_pop(l, &z);
+
+        if (!z)
+            break;
+
+        changed += pa_proplist_unset(p, z) >= 0;
+        pa_xfree(z);
+    }
+
+    pa_pstream_send_simple_ack(c->pstream, tag);
+
+    if (changed) {
+        if (command == PA_COMMAND_REMOVE_PLAYBACK_STREAM_PROPLIST) {
+            playback_stream *s;
+
+            s = pa_idxset_get_by_index(c->output_streams, idx);
+            pa_subscription_post(c->protocol->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, s->sink_input->index);
+
+        } else if (command == PA_COMMAND_REMOVE_RECORD_STREAM_PROPLIST) {
+            record_stream *s;
+
+            s = pa_idxset_get_by_index(c->record_streams, idx);
+            pa_subscription_post(c->protocol->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, s->source_output->index);
+
+        } else {
+            pa_assert(command == PA_COMMAND_REMOVE_CLIENT_PROPLIST);
+            pa_subscription_post(c->protocol->core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_CHANGE, c->client->index);
+        }
+    }
+}
+
 static void command_set_default_sink_or_source(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
     connection *c = CONNECTION(userdata);
     const char *s;
@@ -2991,7 +3643,7 @@ static void command_move_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag
 
         CHECK_VALIDITY(c->pstream, si && sink, tag, PA_ERR_NOENTITY);
 
-        if (pa_sink_input_move_to(si, sink, 0) < 0) {
+        if (pa_sink_input_move_to(si, sink) < 0) {
             pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID);
             return;
         }
@@ -3023,7 +3675,7 @@ static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa
     connection *c = CONNECTION(userdata);
     uint32_t idx = PA_INVALID_INDEX;
     const char *name = NULL;
-    int b;
+    pa_bool_t b;
 
     connection_assert_ref(c);
     pa_assert(t);
@@ -3247,11 +3899,11 @@ static void on_connection(PA_GCC_UNUSED pa_socket_server*s, pa_iochannel *io, vo
     c->parent.parent.free = connection_free;
     c->parent.process_msg = connection_process_msg;
 
-    c->authorized = !!p->public;
+    c->authorized = p->public;
 
     if (!c->authorized && p->auth_ip_acl && pa_ip_acl_check(p->auth_ip_acl, pa_iochannel_get_recv_fd(io)) > 0) {
         pa_log_info("Client authenticated by IP ACL.");
-        c->authorized = 1;
+        c->authorized = TRUE;
     }
 
     if (!c->authorized) {
@@ -3267,9 +3919,10 @@ static void on_connection(PA_GCC_UNUSED pa_socket_server*s, pa_iochannel *io, vo
     pa_iochannel_socket_peer_to_string(io, pname, sizeof(pname));
     pa_snprintf(cname, sizeof(cname), "Native client (%s)", pname);
     c->client = pa_client_new(p->core, __FILE__, cname);
+    pa_proplist_sets(c->client->proplist, "native-protocol.peer", pname);
     c->client->kill = client_kill_cb;
     c->client->userdata = c;
-    c->client->owner = p->module;
+    c->client->module = p->module;
 
     c->pstream = pa_pstream_new(p->core->mainloop, io, p->core->mempool);
 
@@ -3302,12 +3955,12 @@ static void on_connection(PA_GCC_UNUSED pa_socket_server*s, pa_iochannel *io, vo
 static int load_key(pa_protocol_native*p, const char*fn) {
     pa_assert(p);
 
-    p->auth_cookie_in_property = 0;
+    p->auth_cookie_in_property = FALSE;
 
     if (!fn && pa_authkey_prop_get(p->core, PA_NATIVE_COOKIE_PROPERTY_NAME, p->auth_cookie, sizeof(p->auth_cookie)) >= 0) {
         pa_log_info("using already loaded auth cookie.");
         pa_authkey_prop_ref(p->core, PA_NATIVE_COOKIE_PROPERTY_NAME);
-        p->auth_cookie_in_property = 1;
+        p->auth_cookie_in_property = TRUE;
         return 0;
     }
 
@@ -3320,7 +3973,7 @@ static int load_key(pa_protocol_native*p, const char*fn) {
     pa_log_info("loading cookie from disk.");
 
     if (pa_authkey_prop_put(p->core, PA_NATIVE_COOKIE_PROPERTY_NAME, p->auth_cookie, sizeof(p->auth_cookie)) >= 0)
-        p->auth_cookie_in_property = 1;
+        p->auth_cookie_in_property = TRUE;
 
     return 0;
 }
@@ -3347,7 +4000,7 @@ static pa_protocol_native* protocol_new_internal(pa_core *c, pa_module *m, pa_mo
 
 #ifdef HAVE_CREDS
     {
-        pa_bool_t a = 1;
+        pa_bool_t a = TRUE;
         if (pa_modargs_get_value_boolean(ma, "auth-group-enabled", &a) < 0) {
             pa_log("auth-group-enabled= expects a boolean argument.");
             return NULL;
@@ -3392,7 +4045,7 @@ pa_protocol_native* pa_protocol_native_new(pa_core *core, pa_socket_server *serv
     if (!(p = protocol_new_internal(core, m, ma)))
         return NULL;
 
-    p->server = server;
+    p->server = pa_socket_server_ref(server);
     pa_socket_server_set_callback(p->server, on_connection, p);
 
     if (pa_socket_server_get_address(p->server, t, sizeof(t))) {
index 777def3..cbe4844 100644 (file)
@@ -32,6 +32,7 @@
 #include <string.h>
 
 #include <pulse/xmalloc.h>
+#include <pulse/timeval.h>
 
 #include <pulsecore/sink-input.h>
 #include <pulsecore/source-output.h>
@@ -42,6 +43,7 @@
 #include <pulsecore/core-error.h>
 #include <pulsecore/atomic.h>
 #include <pulsecore/thread-mq.h>
+#include <pulsecore/core-util.h>
 
 #include "protocol-simple.h"
 
@@ -57,12 +59,13 @@ typedef struct connection {
     pa_client *client;
     pa_memblockq *input_memblockq, *output_memblockq;
 
-    int dead;
+    pa_bool_t dead;
 
     struct {
         pa_memblock *current_memblock;
-        size_t memblock_index, fragment_size;
+        size_t memblock_index;
         pa_atomic_t missing;
+        pa_bool_t underrun;
     } playback;
 } connection;
 
@@ -101,7 +104,8 @@ enum {
 #define PLAYBACK_BUFFER_SECONDS (.5)
 #define PLAYBACK_BUFFER_FRAGMENTS (10)
 #define RECORD_BUFFER_SECONDS (5)
-#define RECORD_BUFFER_FRAGMENTS (100)
+#define DEFAULT_SINK_LATENCY (300*PA_USEC_PER_MSEC)
+#define DEFAULT_SOURCE_LATENCY (300*PA_USEC_PER_MSEC)
 
 static void connection_unlink(connection *c) {
     pa_assert(c);
@@ -140,8 +144,6 @@ static void connection_free(pa_object *o) {
     connection *c = CONNECTION(o);
     pa_assert(c);
 
-    connection_unref(c);
-
     if (c->playback.current_memblock)
         pa_memblock_unref(c->playback.current_memblock);
 
@@ -158,27 +160,33 @@ static int do_read(connection *c) {
     ssize_t r;
     size_t l;
     void *p;
+    size_t space;
 
     connection_assert_ref(c);
 
     if (!c->sink_input || (l = pa_atomic_load(&c->playback.missing)) <= 0)
         return 0;
 
-    if (l > c->playback.fragment_size)
-        l = c->playback.fragment_size;
+    if (c->playback.current_memblock) {
 
-    if (c->playback.current_memblock)
-        if (pa_memblock_get_length(c->playback.current_memblock) - c->playback.memblock_index < l) {
+        space = pa_memblock_get_length(c->playback.current_memblock) - c->playback.memblock_index;
+
+        if (space <= 0) {
             pa_memblock_unref(c->playback.current_memblock);
             c->playback.current_memblock = NULL;
-            c->playback.memblock_index = 0;
         }
+    }
 
     if (!c->playback.current_memblock) {
-        pa_assert_se(c->playback.current_memblock = pa_memblock_new(c->protocol->core->mempool, l));
+        pa_assert_se(c->playback.current_memblock = pa_memblock_new(c->protocol->core->mempool, 0));
         c->playback.memblock_index = 0;
+
+        space = pa_memblock_get_length(c->playback.current_memblock);
     }
 
+    if (l > space)
+        l = space;
+
     p = pa_memblock_acquire(c->playback.current_memblock);
     r = pa_iochannel_read(c->io, (uint8_t*) p + c->playback.memblock_index, l);
     pa_memblock_release(c->playback.current_memblock);
@@ -248,16 +256,16 @@ static void do_work(connection *c) {
     if (c->dead)
         return;
 
-    if (pa_iochannel_is_readable(c->io)) {
+    if (pa_iochannel_is_readable(c->io))
         if (do_read(c) < 0)
             goto fail;
-    } else if (pa_iochannel_is_hungup(c->io))
+
+    if (!c->sink_input && pa_iochannel_is_hungup(c->io))
         goto fail;
 
-    if (pa_iochannel_is_writable(c->io)) {
+    if (pa_iochannel_is_writable(c->io))
         if (do_write(c) < 0)
             goto fail;
-    }
 
     return;
 
@@ -266,7 +274,7 @@ fail:
     if (c->sink_input) {
 
         /* If there is a sink input, we first drain what we already have read before shutting down the connection */
-        c->dead = 1;
+        c->dead = TRUE;
 
         pa_iochannel_free(c->io);
         c->io = NULL;
@@ -318,15 +326,19 @@ static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int
             /* New data from the main loop */
             pa_memblockq_push_align(c->input_memblockq, chunk);
 
+            if (pa_memblockq_is_readable(c->input_memblockq) && c->playback.underrun) {
+                pa_log_debug("Requesting rewind due to end of underrun.");
+                pa_sink_input_request_rewind(c->sink_input, 0, FALSE, TRUE);
+            }
+
 /*             pa_log("got data, %u", pa_memblockq_get_length(c->input_memblockq)); */
 
             return 0;
         }
 
-        case SINK_INPUT_MESSAGE_DISABLE_PREBUF: {
+        case SINK_INPUT_MESSAGE_DISABLE_PREBUF:
             pa_memblockq_prebuf_disable(c->input_memblockq);
             return 0;
-        }
 
         case PA_SINK_INPUT_MESSAGE_GET_LATENCY: {
             pa_usec_t *r = userdata;
@@ -343,43 +355,62 @@ static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int
 }
 
 /* Called from thread context */
-static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
+static int sink_input_pop_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
     connection *c;
-    int r;
 
-    pa_assert(i);
+    pa_sink_input_assert_ref(i);
     c = CONNECTION(i->userdata);
     connection_assert_ref(c);
     pa_assert(chunk);
 
-    r = pa_memblockq_peek(c->input_memblockq, chunk);
+    if (pa_memblockq_peek(c->input_memblockq, chunk) < 0) {
+
+        c->playback.underrun = TRUE;
 
-/*     pa_log("peeked %u %i", r >= 0 ? chunk->length: 0, r); */
+        if (c->dead && pa_sink_input_safe_to_remove(i))
+            pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_UNLINK_CONNECTION, NULL, 0, NULL, NULL);
 
-    if (c->dead && r < 0)
-        pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_UNLINK_CONNECTION, NULL, 0, NULL, NULL);
+        return -1;
+    } else {
+        size_t m;
+
+        c->playback.underrun = FALSE;
+
+        pa_memblockq_drop(c->input_memblockq, chunk->length);
+        m = pa_memblockq_pop_missing(c->input_memblockq);
+
+        if (m > 0)
+            if (pa_atomic_add(&c->playback.missing, m) <= 0)
+                pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_REQUEST_DATA, NULL, 0, NULL, NULL);
 
-    return r;
+        return 0;
+    }
 }
 
 /* Called from thread context */
-static void sink_input_drop_cb(pa_sink_input *i, size_t length) {
+static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
     connection *c;
-    size_t old, new;
 
-    pa_assert(i);
+    pa_sink_input_assert_ref(i);
     c = CONNECTION(i->userdata);
     connection_assert_ref(c);
-    pa_assert(length);
 
-    old = pa_memblockq_missing(c->input_memblockq);
-    pa_memblockq_drop(c->input_memblockq, length);
-    new = pa_memblockq_missing(c->input_memblockq);
+    /* If we are in an underrun, then we don't rewind */
+    if (i->thread_info.underrun_for > 0)
+        return;
 
-    if (new > old) {
-        if (pa_atomic_add(&c->playback.missing, new - old) <= 0)
-            pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_REQUEST_DATA, NULL, 0, NULL, NULL);
-    }
+    pa_memblockq_rewind(c->input_memblockq, nbytes);
+}
+
+/* Called from thread context */
+static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
+    connection *c;
+
+    pa_sink_input_assert_ref(i);
+    c = CONNECTION(i->userdata);
+    connection_assert_ref(c);
+
+    pa_memblockq_set_maxrewind(c->input_memblockq, nbytes);
 }
 
 /* Called from main context */
@@ -395,7 +426,7 @@ static void sink_input_kill_cb(pa_sink_input *i) {
 static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) {
     connection *c;
 
-    pa_assert(o);
+    pa_source_output_assert_ref(o);
     c = CONNECTION(o->userdata);
     pa_assert(c);
     pa_assert(chunk);
@@ -414,7 +445,7 @@ static void source_output_kill_cb(pa_source_output *o) {
 static pa_usec_t source_output_get_latency_cb(pa_source_output *o) {
     connection*c;
 
-    pa_assert(o);
+    pa_source_output_assert_ref(o);
     c = CONNECTION(o->userdata);
     pa_assert(c);
 
@@ -449,7 +480,7 @@ static void io_callback(pa_iochannel*io, void *userdata) {
 static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata) {
     pa_protocol_simple *p = userdata;
     connection *c = NULL;
-    char cname[256];
+    char cname[256], pname[128];
 
     pa_assert(s);
     pa_assert(io);
@@ -471,49 +502,64 @@ static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata)
     c->protocol = p;
     c->playback.current_memblock = NULL;
     c->playback.memblock_index = 0;
-    c->playback.fragment_size = 0;
-    c->dead = 0;
+    c->dead = FALSE;
+    c->playback.underrun = TRUE;
     pa_atomic_store(&c->playback.missing, 0);
 
-    pa_iochannel_socket_peer_to_string(io, cname, sizeof(cname));
+    pa_iochannel_socket_peer_to_string(io, pname, sizeof(pname));
+    pa_snprintf(cname, sizeof(cname), "Simple client (%s)", pname);
     pa_assert_se(c->client = pa_client_new(p->core, __FILE__, cname));
-    c->client->owner = p->module;
+    pa_proplist_sets(c->client->proplist, "simple-protocol.peer", pname);
+    c->client->module = p->module;
     c->client->kill = client_kill_cb;
     c->client->userdata = c;
 
     if (p->mode & PLAYBACK) {
         pa_sink_input_new_data data;
         size_t l;
+        pa_sink *sink;
+
+        if (!(sink = pa_namereg_get(c->protocol->core, c->protocol->sink_name, PA_NAMEREG_SINK, TRUE))) {
+            pa_log("Failed to get sink.");
+            goto fail;
+        }
 
         pa_sink_input_new_data_init(&data);
         data.driver = __FILE__;
-        data.name = c->client->name;
-        pa_sink_input_new_data_set_sample_spec(&data, &p->sample_spec);
         data.module = p->module;
         data.client = c->client;
+        data.sink = sink;
+        pa_proplist_update(data.proplist, PA_UPDATE_MERGE, c->client->proplist);
+        pa_sink_input_new_data_set_sample_spec(&data, &p->sample_spec);
+
+        c->sink_input = pa_sink_input_new(p->core, &data, 0);
+        pa_sink_input_new_data_done(&data);
 
-        if (!(c->sink_input = pa_sink_input_new(p->core, &data, 0))) {
+        if (!c->sink_input) {
             pa_log("Failed to create sink input.");
             goto fail;
         }
 
         c->sink_input->parent.process_msg = sink_input_process_msg;
-        c->sink_input->peek = sink_input_peek_cb;
-        c->sink_input->drop = sink_input_drop_cb;
+        c->sink_input->pop = sink_input_pop_cb;
+        c->sink_input->process_rewind = sink_input_process_rewind_cb;
+        c->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
         c->sink_input->kill = sink_input_kill_cb;
         c->sink_input->userdata = c;
 
+        pa_sink_input_set_requested_latency(c->sink_input, DEFAULT_SINK_LATENCY);
+
         l = (size_t) (pa_bytes_per_second(&p->sample_spec)*PLAYBACK_BUFFER_SECONDS);
         c->input_memblockq = pa_memblockq_new(
                 0,
                 l,
-                0,
+                l,
                 pa_frame_size(&p->sample_spec),
                 (size_t) -1,
                 l/PLAYBACK_BUFFER_FRAGMENTS,
+                0,
                 NULL);
-        pa_iochannel_socket_set_rcvbuf(io, l/PLAYBACK_BUFFER_FRAGMENTS*5);
-        c->playback.fragment_size = l/PLAYBACK_BUFFER_FRAGMENTS;
+        pa_iochannel_socket_set_rcvbuf(io, l);
 
         pa_atomic_store(&c->playback.missing, pa_memblockq_missing(c->input_memblockq));
 
@@ -523,15 +569,25 @@ static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata)
     if (p->mode & RECORD) {
         pa_source_output_new_data data;
         size_t l;
+        pa_source *source;
+
+        if (!(source = pa_namereg_get(c->protocol->core, c->protocol->source_name, PA_NAMEREG_SOURCE, TRUE))) {
+            pa_log("Failed to get source.");
+            goto fail;
+        }
 
         pa_source_output_new_data_init(&data);
         data.driver = __FILE__;
-        data.name = c->client->name;
-        pa_source_output_new_data_set_sample_spec(&data, &p->sample_spec);
         data.module = p->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, &p->sample_spec);
 
-        if (!(c->source_output = pa_source_output_new(p->core, &data, 0))) {
+        c->source_output = pa_source_output_new(p->core, &data, 0);
+        pa_source_output_new_data_done(&data);
+
+        if (!c->source_output) {
             pa_log("Failed to create source output.");
             goto fail;
         }
@@ -540,6 +596,8 @@ static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata)
         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(&p->sample_spec)*RECORD_BUFFER_SECONDS);
         c->output_memblockq = pa_memblockq_new(
                 0,
@@ -548,8 +606,9 @@ static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata)
                 pa_frame_size(&p->sample_spec),
                 1,
                 0,
+                0,
                 NULL);
-        pa_iochannel_socket_set_sndbuf(io, l/RECORD_BUFFER_FRAGMENTS*2);
+        pa_iochannel_socket_set_sndbuf(io, l);
 
         pa_source_output_put(c->source_output);
     }
@@ -570,12 +629,13 @@ pa_protocol_simple* pa_protocol_simple_new(pa_core *core, pa_socket_server *serv
 
     pa_assert(core);
     pa_assert(server);
+    pa_assert(m);
     pa_assert(ma);
 
     p = pa_xnew0(pa_protocol_simple, 1);
     p->module = m;
     p->core = core;
-    p->server = server;
+    p->server = pa_socket_server_ref(server);
     p->connections = pa_idxset_new(NULL, NULL);
 
     p->sample_spec = core->default_sample_spec;
@@ -594,7 +654,7 @@ pa_protocol_simple* pa_protocol_simple_new(pa_core *core, pa_socket_server *serv
     }
     p->mode = enable ? RECORD : 0;
 
-    enable = 1;
+    enable = TRUE;
     if (pa_modargs_get_value_boolean(ma, "playback", &enable) < 0) {
         pa_log("playback= expects a numeric argument.");
         goto fail;
index 9d32a36..5004d67 100644 (file)
@@ -98,7 +98,7 @@ struct item_info {
     /* packet info */
     pa_packet *packet;
 #ifdef HAVE_CREDS
-    int with_creds;
+    pa_bool_t with_creds;
     pa_creds creds;
 #endif
 
@@ -121,7 +121,7 @@ struct pa_pstream {
 
     pa_queue *send_queue;
 
-    int dead;
+    pa_bool_t dead;
 
     struct {
         pa_pstream_descriptor descriptor;
@@ -141,7 +141,7 @@ struct pa_pstream {
         size_t index;
     } read;
 
-    int use_shm;
+    pa_bool_t use_shm;
     pa_memimport *import;
     pa_memexport *export;
 
@@ -167,7 +167,7 @@ struct pa_pstream {
 
 #ifdef HAVE_CREDS
     pa_creds read_creds, write_creds;
-    int read_creds_valid, send_creds_now;
+    pa_bool_t read_creds_valid, send_creds_now;
 #endif
 };
 
@@ -239,7 +239,7 @@ pa_pstream *pa_pstream_new(pa_mainloop_api *m, pa_iochannel *io, pa_mempool *poo
     PA_REFCNT_INIT(p);
     p->io = io;
     pa_iochannel_set_callback(io, io_callback, p);
-    p->dead = 0;
+    p->dead = FALSE;
 
     p->mainloop = m;
     p->defer_event = m->defer_new(m, defer_callback, p);
@@ -269,18 +269,18 @@ pa_pstream *pa_pstream_new(pa_mainloop_api *m, pa_iochannel *io, pa_mempool *poo
 
     p->mempool = pool;
 
-    p->use_shm = 0;
+    p->use_shm = FALSE;
     p->export = NULL;
 
     /* We do importing unconditionally */
     p->import = pa_memimport_new(p->mempool, memimport_release_cb, p);
 
-    pa_iochannel_socket_set_rcvbuf(io, 1024*8);
-    pa_iochannel_socket_set_sndbuf(io, 1024*8);
+    pa_iochannel_socket_set_rcvbuf(io, pa_mempool_block_size_max(p->mempool));
+    pa_iochannel_socket_set_sndbuf(io, pa_mempool_block_size_max(p->mempool));
 
 #ifdef HAVE_CREDS
-    p->send_creds_now = 0;
-    p->read_creds_valid = 0;
+    p->send_creds_now = FALSE;
+    p->read_creds_valid = FALSE;
 #endif
     return p;
 }
@@ -374,7 +374,7 @@ void pa_pstream_send_memblock(pa_pstream*p, uint32_t channel, int64_t offset, pa
             i = pa_xnew(struct item_info, 1);
         i->type = PA_PSTREAM_ITEM_MEMBLOCK;
 
-        n = MIN(length, bsm);
+        n = PA_MIN(length, bsm);
         i->chunk.index = chunk->index + idx;
         i->chunk.length = n;
         i->chunk.memblock = pa_memblock_ref(chunk->memblock);
@@ -383,7 +383,7 @@ void pa_pstream_send_memblock(pa_pstream*p, uint32_t channel, int64_t offset, pa
         i->offset = offset;
         i->seek_mode = seek_mode;
 #ifdef HAVE_CREDS
-        i->with_creds = 0;
+        i->with_creds = FALSE;
 #endif
 
         pa_queue_push(p->send_queue, i);
@@ -410,7 +410,7 @@ void pa_pstream_send_release(pa_pstream *p, uint32_t block_id) {
     item->type = PA_PSTREAM_ITEM_SHMRELEASE;
     item->block_id = block_id;
 #ifdef HAVE_CREDS
-    item->with_creds = 0;
+    item->with_creds = FALSE;
 #endif
 
     pa_queue_push(p->send_queue, item);
@@ -447,7 +447,7 @@ void pa_pstream_send_revoke(pa_pstream *p, uint32_t block_id) {
     item->type = PA_PSTREAM_ITEM_SHMREVOKE;
     item->block_id = block_id;
 #ifdef HAVE_CREDS
-    item->with_creds = 0;
+    item->with_creds = FALSE;
 #endif
 
     pa_queue_push(p->send_queue, item);
@@ -504,7 +504,7 @@ static void prepare_next_write_item(pa_pstream *p) {
 
     } else {
         uint32_t flags;
-        int send_payload = 1;
+        pa_bool_t send_payload = TRUE;
 
         pa_assert(p->write.current->type == PA_PSTREAM_ITEM_MEMBLOCK);
         pa_assert(p->write.current->chunk.memblock);
@@ -529,7 +529,7 @@ static void prepare_next_write_item(pa_pstream *p) {
                                  &length) >= 0) {
 
                 flags |= PA_FLAG_SHMDATA;
-                send_payload = 0;
+                send_payload = FALSE;
 
                 p->write.shm_info[PA_PSTREAM_SHM_BLOCKID] = htonl(block_id);
                 p->write.shm_info[PA_PSTREAM_SHM_SHMID] = htonl(shm_id);
@@ -599,7 +599,7 @@ static int do_write(pa_pstream *p) {
         if ((r = pa_iochannel_write_with_creds(p->io, d, l, &p->write_creds)) < 0)
             goto fail;
 
-        p->send_creds_now = 0;
+        p->send_creds_now = FALSE;
     } else
 #endif
 
@@ -875,7 +875,7 @@ frame_done:
     p->read.data = NULL;
 
 #ifdef HAVE_CREDS
-    p->read_creds_valid = 0;
+    p->read_creds_valid = FALSE;
 #endif
 
     return 0;
@@ -935,14 +935,14 @@ void pa_pstream_set_revoke_callback(pa_pstream *p, pa_pstream_block_id_cb_t cb,
     p->release_callback_userdata = userdata;
 }
 
-int pa_pstream_is_pending(pa_pstream *p) {
-    int b;
+pa_bool_t pa_pstream_is_pending(pa_pstream *p) {
+    pa_bool_t b;
 
     pa_assert(p);
     pa_assert(PA_REFCNT_VALUE(p) > 0);
 
     if (p->dead)
-        b = 0;
+        b = FALSE;
     else
         b = p->write.current || !pa_queue_is_empty(p->send_queue);
 
@@ -971,7 +971,7 @@ void pa_pstream_unlink(pa_pstream *p) {
     if (p->dead)
         return;
 
-    p->dead = 1;
+    p->dead = TRUE;
 
     if (p->import) {
         pa_memimport_free(p->import);
@@ -999,7 +999,7 @@ void pa_pstream_unlink(pa_pstream *p) {
     p->recieve_memblock_callback = NULL;
 }
 
-void pa_pstream_use_shm(pa_pstream *p, int enable) {
+void pa_pstream_enable_shm(pa_pstream *p, pa_bool_t enable) {
     pa_assert(p);
     pa_assert(PA_REFCNT_VALUE(p) > 0);
 
@@ -1018,3 +1018,10 @@ void pa_pstream_use_shm(pa_pstream *p, int enable) {
         }
     }
 }
+
+pa_bool_t pa_pstream_get_shm(pa_pstream *p) {
+    pa_assert(p);
+    pa_assert(PA_REFCNT_VALUE(p) > 0);
+
+    return p->use_shm;
+}
index 72babea..00b37b7 100644 (file)
@@ -35,6 +35,7 @@
 #include <pulsecore/iochannel.h>
 #include <pulsecore/memchunk.h>
 #include <pulsecore/creds.h>
+#include <pulsecore/macro.h>
 
 typedef struct pa_pstream pa_pstream;
 
@@ -44,8 +45,11 @@ typedef void (*pa_pstream_notify_cb_t)(pa_pstream *p, void *userdata);
 typedef void (*pa_pstream_block_id_cb_t)(pa_pstream *p, uint32_t block_id, void *userdata);
 
 pa_pstream* pa_pstream_new(pa_mainloop_api *m, pa_iochannel *io, pa_mempool *p);
-void pa_pstream_unref(pa_pstream*p);
+
 pa_pstream* pa_pstream_ref(pa_pstream*p);
+void pa_pstream_unref(pa_pstream*p);
+
+void pa_pstream_unlink(pa_pstream *p);
 
 void pa_pstream_send_packet(pa_pstream*p, pa_packet *packet, const pa_creds *creds);
 void pa_pstream_send_memblock(pa_pstream*p, uint32_t channel, int64_t offset, pa_seek_mode_t seek, const pa_memchunk *chunk);
@@ -59,10 +63,9 @@ void pa_pstream_set_die_callback(pa_pstream *p, pa_pstream_notify_cb_t cb, void
 void pa_pstream_set_release_callback(pa_pstream *p, pa_pstream_block_id_cb_t cb, void *userdata);
 void pa_pstream_set_revoke_callback(pa_pstream *p, pa_pstream_block_id_cb_t cb, void *userdata);
 
-int pa_pstream_is_pending(pa_pstream *p);
-
-void pa_pstream_use_shm(pa_pstream *p, int enable);
+pa_bool_t pa_pstream_is_pending(pa_pstream *p);
 
-void pa_pstream_unlink(pa_pstream *p);
+void pa_pstream_enable_shm(pa_pstream *p, pa_bool_t enable);
+pa_bool_t pa_pstream_get_shm(pa_pstream *p);
 
 #endif
index 64271ab..f0885fb 100644 (file)
@@ -32,6 +32,9 @@
 #define PA_REFCNT_INIT(p) \
     pa_atomic_store(&(p)->_ref, 1)
 
+#define PA_REFCNT_INIT_ZERO(p) \
+    pa_atomic_store(&(p)->_ref, 0)
+
 #define PA_REFCNT_INC(p) \
     pa_atomic_inc(&(p)->_ref)
 
index fe7f1ad..d645639 100644 (file)
@@ -47,7 +47,7 @@
 #include "resampler.h"
 
 /* Number of samples of extra space we allow the resamplers to return */
-#define EXTRA_SAMPLES 128
+#define EXTRA_FRAMES 128
 
 struct pa_resampler {
     pa_resample_method_t method;
@@ -79,6 +79,15 @@ struct pa_resampler {
         unsigned i_counter;
     } trivial;
 
+    struct { /* data specific to the peak finder pseudo resampler */
+        unsigned o_counter;
+        unsigned i_counter;
+
+        float max_f[PA_CHANNELS_MAX];
+        int16_t max_i[PA_CHANNELS_MAX];
+
+    } peaks;
+
 #ifdef HAVE_LIBSAMPLERATE
     struct { /* data specific to libsamplerate */
         SRC_STATE *state;
@@ -99,6 +108,7 @@ static int copy_init(pa_resampler *r);
 static int trivial_init(pa_resampler*r);
 static int speex_init(pa_resampler*r);
 static int ffmpeg_init(pa_resampler*r);
+static int peaks_init(pa_resampler*r);
 #ifdef HAVE_LIBSAMPLERATE
 static int libsamplerate_init(pa_resampler*r);
 #endif
@@ -144,7 +154,8 @@ static int (* const init_table[])(pa_resampler*r) = {
     [PA_RESAMPLER_SPEEX_FIXED_BASE+10]     = speex_init,
     [PA_RESAMPLER_FFMPEG]                  = ffmpeg_init,
     [PA_RESAMPLER_AUTO]                    = NULL,
-    [PA_RESAMPLER_COPY]                    = copy_init
+    [PA_RESAMPLER_COPY]                    = copy_init,
+    [PA_RESAMPLER_PEAKS]                   = peaks_init,
 };
 
 static inline size_t sample_size(pa_sample_format_t f) {
@@ -242,9 +253,9 @@ pa_resampler* pa_resampler_new(
     if ((method >= PA_RESAMPLER_SPEEX_FIXED_BASE && method <= PA_RESAMPLER_SPEEX_FIXED_MAX) ||
         (method == PA_RESAMPLER_FFMPEG))
         r->work_format = PA_SAMPLE_S16NE;
-    else if (method == PA_RESAMPLER_TRIVIAL || method == PA_RESAMPLER_COPY) {
+    else if (method == PA_RESAMPLER_TRIVIAL || method == PA_RESAMPLER_COPY || method == PA_RESAMPLER_PEAKS) {
 
-        if (r->map_required || a->format != b->format) {
+        if (r->map_required || a->format != b->format || method == PA_RESAMPLER_PEAKS) {
 
             if (a->format == PA_SAMPLE_S32NE || a->format == PA_SAMPLE_S32RE ||
                 a->format == PA_SAMPLE_FLOAT32NE || a->format == PA_SAMPLE_FLOAT32RE ||
@@ -347,6 +358,12 @@ size_t pa_resampler_request(pa_resampler *r, size_t out_length) {
     return (((out_length / r->o_fz)*r->i_ss.rate)/r->o_ss.rate) * r->i_fz;
 }
 
+size_t pa_resampler_result(pa_resampler *r, size_t in_length) {
+    pa_assert(r);
+
+    return (((in_length / r->i_fz)*r->o_ss.rate)/r->i_ss.rate) * r->o_fz;
+}
+
 size_t pa_resampler_max_block_size(pa_resampler *r) {
     size_t block_size_max;
     pa_sample_spec ss;
@@ -358,22 +375,17 @@ size_t pa_resampler_max_block_size(pa_resampler *r) {
 
     /* We deduce the "largest" sample spec we're using during the
      * conversion */
-    ss = r->i_ss;
-    if (r->o_ss.channels > ss.channels)
-        ss.channels = r->o_ss.channels;
+    ss.channels = PA_MAX(r->i_ss.channels, r->o_ss.channels);
 
     /* We silently assume that the format enum is ordered by size */
-    if (r->o_ss.format > ss.format)
-        ss.format = r->o_ss.format;
-    if (r->work_format > ss.format)
-        ss.format = r->work_format;
+    ss.format = PA_MAX(r->i_ss.format, r->o_ss.format);
+    ss.format = PA_MAX(ss.format, r->work_format);
 
-    if (r->o_ss.rate > ss.rate)
-        ss.rate = r->o_ss.rate;
+    ss.rate = PA_MAX(r->i_ss.rate, r->o_ss.rate);
 
     fs = pa_frame_size(&ss);
 
-    return (((block_size_max/fs + EXTRA_SAMPLES)*r->i_ss.rate)/ss.rate)*r->i_fz;
+    return (((block_size_max/fs - EXTRA_FRAMES)*r->i_ss.rate)/ss.rate)*r->i_fz;
 }
 
 void pa_resampler_reset(pa_resampler *r) {
@@ -420,7 +432,8 @@ static const char * const resample_methods[] = {
     "speex-fixed-10",
     "ffmpeg",
     "auto",
-    "copy"
+    "copy",
+    "peaks"
 };
 
 const char *pa_resample_method_to_string(pa_resample_method_t m) {
@@ -1069,7 +1082,7 @@ static pa_memchunk *resample(pa_resampler *r, pa_memchunk *input) {
     in_n_samples = input->length / r->w_sz;
     in_n_frames = in_n_samples / r->o_ss.channels;
 
-    out_n_frames = ((in_n_frames*r->o_ss.rate)/r->i_ss.rate)+EXTRA_SAMPLES;
+    out_n_frames = ((in_n_frames*r->o_ss.rate)/r->i_ss.rate)+EXTRA_FRAMES;
     out_n_samples = out_n_frames * r->o_ss.channels;
 
     r->buf3.index = 0;
@@ -1400,6 +1413,113 @@ static int trivial_init(pa_resampler*r) {
     return 0;
 }
 
+/* Peak finder implementation */
+
+static void peaks_resample(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) {
+    size_t fz;
+    unsigned o_index;
+    void *src, *dst;
+    unsigned start = 0;
+
+    pa_assert(r);
+    pa_assert(input);
+    pa_assert(output);
+    pa_assert(out_n_frames);
+
+    fz = r->w_sz * r->o_ss.channels;
+
+    src = (uint8_t*) pa_memblock_acquire(input->memblock) + input->index;
+    dst = (uint8_t*) pa_memblock_acquire(output->memblock) + output->index;
+
+    for (o_index = 0;; o_index++, r->peaks.o_counter++) {
+        unsigned j;
+
+        j = ((r->peaks.o_counter * r->i_ss.rate) / r->o_ss.rate);
+        j = j > r->peaks.i_counter ? j - r->peaks.i_counter : 0;
+
+        if (j >= in_n_frames)
+            break;
+
+        pa_assert(o_index * fz < pa_memblock_get_length(output->memblock));
+
+        if (r->work_format == PA_SAMPLE_S16NE) {
+            unsigned i, c;
+            int16_t *s = (int16_t*) ((uint8_t*) src + fz * j);
+            int16_t *d = (int16_t*) ((uint8_t*) dst + fz * o_index);
+
+            for (i = start; i <= j; i++)
+                for (c = 0; c < r->o_ss.channels; c++, s++) {
+                    int16_t n;
+
+                    n = *s < 0 ? -*s : *s;
+
+                    if (n > r->peaks.max_i[c])
+                        r->peaks.max_i[c] = n;
+                }
+
+            for (c = 0; c < r->o_ss.channels; c++, d++)
+                 *d = r->peaks.max_i[c];
+
+            memset(r->peaks.max_i, 0, sizeof(r->peaks.max_i));
+        } else {
+            unsigned i, c;
+            float *s = (float*) ((uint8_t*) src + fz * j);
+            float *d = (float*) ((uint8_t*) dst + fz * o_index);
+
+            pa_assert(r->work_format == PA_SAMPLE_FLOAT32NE);
+
+            for (i = start; i <= j; i++)
+                for (c = 0; c < r->o_ss.channels; c++, s++) {
+                    float n = fabsf(*s);
+
+                    if (n > r->peaks.max_f[c])
+                        r->peaks.max_f[c] = n;
+                }
+
+            for (c = 0; c < r->o_ss.channels; c++, d++)
+                *d = r->peaks.max_f[c];
+
+            memset(r->peaks.max_f, 0, sizeof(r->peaks.max_f));
+        }
+    }
+
+    pa_memblock_release(input->memblock);
+    pa_memblock_release(output->memblock);
+
+    *out_n_frames = o_index;
+
+    r->peaks.i_counter += in_n_frames;
+
+    /* Normalize counters */
+    while (r->peaks.i_counter >= r->i_ss.rate) {
+        pa_assert(r->peaks.o_counter >= r->o_ss.rate);
+
+        r->peaks.i_counter -= r->i_ss.rate;
+        r->peaks.o_counter -= r->o_ss.rate;
+    }
+}
+
+static void peaks_update_rates_or_reset(pa_resampler *r) {
+    pa_assert(r);
+
+    r->peaks.i_counter = 0;
+    r->peaks.o_counter = 0;
+}
+
+static int peaks_init(pa_resampler*r) {
+    pa_assert(r);
+
+    r->peaks.o_counter = r->peaks.i_counter = 0;
+    memset(r->peaks.max_i, 0, sizeof(r->peaks.max_i));
+    memset(r->peaks.max_f, 0, sizeof(r->peaks.max_f));
+
+    r->impl_resample = peaks_resample;
+    r->impl_update_rates = peaks_update_rates_or_reset;
+    r->impl_reset = peaks_update_rates_or_reset;
+
+    return 0;
+}
+
 /*** ffmpeg based implementation ***/
 
 static void ffmpeg_resample(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) {
index 82d0108..8534f5b 100644 (file)
@@ -46,6 +46,7 @@ typedef enum pa_resample_method {
     PA_RESAMPLER_FFMPEG,
     PA_RESAMPLER_AUTO, /* automatic select based on sample format */
     PA_RESAMPLER_COPY,
+    PA_RESAMPLER_PEAKS,
     PA_RESAMPLER_MAX
 } pa_resample_method_t;
 
@@ -69,6 +70,9 @@ void pa_resampler_free(pa_resampler *r);
 /* Returns the size of an input memory block which is required to return the specified amount of output data */
 size_t pa_resampler_request(pa_resampler *r, size_t out_length);
 
+/* Inverse of pa_resampler_request() */
+size_t pa_resampler_result(pa_resampler *r, size_t in_length);
+
 /* Returns the maximum size of input blocks we can process without needing bounce buffers larger than the mempool tile size. */
 size_t pa_resampler_max_block_size(pa_resampler *r);
 
index 07d776e..e74e524 100644 (file)
@@ -96,3 +96,24 @@ pa_usec_t pa_rtclock_usec(void) {
 
     return pa_timeval_load(pa_rtclock_get(&tv));
 }
+
+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;
+}
index f0360af..f68ad76 100644 (file)
@@ -40,4 +40,6 @@ pa_bool_t pa_rtclock_hrtimer(void);
 /* timer with a resolution better than this are considered high-resolution */
 #define PA_HRTIMER_THRESHOLD_USEC 10
 
+struct timeval* pa_rtclock_from_wallclock(struct timeval *tv);
+
 #endif
index 8300826..64fa42a 100644 (file)
@@ -63,7 +63,6 @@ struct pa_rtpoll {
 
     pa_bool_t timer_enabled;
     struct timeval next_elapse;
-    pa_usec_t period;
 
     pa_bool_t scan_for_dead;
     pa_bool_t running, installed, rebuild_needed, quit;
@@ -72,6 +71,7 @@ struct pa_rtpoll {
     int rtsig;
     sigset_t sigset_unblocked;
     timer_t timer;
+    pa_bool_t timer_armed;
 #ifdef __linux__
     pa_bool_t dont_use_ppoll;
 #endif
@@ -99,7 +99,7 @@ struct pa_rtpoll_item {
 
 PA_STATIC_FLIST_DECLARE(items, 0, pa_xfree);
 
-static void signal_handler_noop(int s) { }
+static void signal_handler_noop(int s) { /* write(2, "signal\n", 7); */ }
 
 pa_rtpoll *pa_rtpoll_new(void) {
     pa_rtpoll *p;
@@ -131,6 +131,7 @@ pa_rtpoll *pa_rtpoll_new(void) {
     p->rtsig = -1;
     sigemptyset(&p->sigset_unblocked);
     p->timer = (timer_t) -1;
+    p->timer_armed = FALSE;
 
 #endif
 
@@ -139,7 +140,6 @@ pa_rtpoll *pa_rtpoll_new(void) {
     p->pollfd2 = pa_xnew(struct pollfd, p->n_pollfd_alloc);
     p->n_pollfd_used = 0;
 
-    p->period = 0;
     memset(&p->next_elapse, 0, sizeof(p->next_elapse));
     p->timer_enabled = FALSE;
 
@@ -368,15 +368,13 @@ int pa_rtpoll_run(pa_rtpoll *p, pa_bool_t wait) {
     if (p->rebuild_needed)
         rtpoll_rebuild(p);
 
+    memset(&timeout, 0, sizeof(timeout));
+
     /* Calculate timeout */
-    if (!wait || p->quit) {
-        timeout.tv_sec = 0;
-        timeout.tv_usec = 0;
-    } else if (p->timer_enabled) {
+    if (wait && !p->quit && p->timer_enabled) {
         struct timeval now;
         pa_rtclock_get(&now);
 
-        memset(&timeout, 0, sizeof(timeout));
         if (pa_timeval_cmp(&p->next_elapse, &now) > 0)
             pa_timeval_add(&timeout, pa_timeval_diff(&p->next_elapse, &now));
     }
@@ -391,14 +389,14 @@ int pa_rtpoll_run(pa_rtpoll *p, pa_bool_t wait) {
         struct timespec ts;
         ts.tv_sec = timeout.tv_sec;
         ts.tv_nsec = timeout.tv_usec * 1000;
-        r = ppoll(p->pollfd, p->n_pollfd_used, p->timer_enabled ? &ts : NULL, p->rtsig < 0 ? NULL : &p->sigset_unblocked);
+        r = ppoll(p->pollfd, p->n_pollfd_used, (!wait || p->quit || p->timer_enabled) ? &ts : NULL, p->rtsig < 0 ? NULL : &p->sigset_unblocked);
     }
 #ifdef __linux__
     else
 #endif
 
 #endif
-        r = poll(p->pollfd, p->n_pollfd_used, p->timer_enabled ? (timeout.tv_sec*1000) + (timeout.tv_usec / 1000) : -1);
+        r = poll(p->pollfd, p->n_pollfd_used, (!wait || p->quit || p->timer_enabled) ? (timeout.tv_sec*1000) + (timeout.tv_usec / 1000) : -1);
 
     if (r < 0) {
         if (errno == EAGAIN || errno == EINTR)
@@ -409,21 +407,6 @@ int pa_rtpoll_run(pa_rtpoll *p, pa_bool_t wait) {
         reset_all_revents(p);
     }
 
-    if (p->timer_enabled) {
-        if (p->period > 0) {
-            struct timeval now;
-            pa_rtclock_get(&now);
-
-            pa_timeval_add(&p->next_elapse, p->period);
-
-            /* Guarantee that the next timeout will happen in the future */
-            if (pa_timeval_cmp(&p->next_elapse, &now) < 0)
-                pa_timeval_add(&p->next_elapse, (pa_timeval_diff(&now, &p->next_elapse) / p->period + 1) * p->period);
-
-        } else
-            p->timer_enabled = FALSE;
-    }
-
     /* Let's tell everyone that we left the sleep */
     for (i = p->items; i && i->priority < PA_RTPOLL_NEVER; i = i->next) {
 
@@ -481,26 +464,35 @@ static void update_timer(pa_rtpoll *p) {
 
         if (p->timer != (timer_t) -1) {
             struct itimerspec its;
-            memset(&its, 0, sizeof(its));
+            struct timespec ts = { .tv_sec = 0, .tv_nsec = 0 };
+            sigset_t ss;
+
+            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 (p->timer_enabled) {
+                memset(&its, 0, sizeof(its));
+
                 its.it_value.tv_sec = p->next_elapse.tv_sec;
                 its.it_value.tv_nsec = p->next_elapse.tv_usec*1000;
 
                 /* Make sure that 0,0 is not understood as
                  * "disarming" */
-                if (its.it_value.tv_sec == 0)
+                if (its.it_value.tv_sec == 0 && its.it_value.tv_nsec == 0)
                     its.it_value.tv_nsec = 1;
-
-                if (p->period > 0) {
-                    struct timeval tv;
-                    pa_timeval_store(&tv, p->period);
-                    its.it_interval.tv_sec = tv.tv_sec;
-                    its.it_interval.tv_nsec = tv.tv_usec*1000;
-                }
+                pa_assert_se(timer_settime(p->timer, TIMER_ABSTIME, &its, NULL) == 0);
             }
 
-            pa_assert_se(timer_settime(p->timer, TIMER_ABSTIME, &its, NULL) == 0);
+            p->timer_armed = p->timer_enabled;
         }
 
 #ifdef __linux__
@@ -510,23 +502,10 @@ static void update_timer(pa_rtpoll *p) {
 #endif
 }
 
-void pa_rtpoll_set_timer_absolute(pa_rtpoll *p, const struct timeval *ts) {
-    pa_assert(p);
-    pa_assert(ts);
-
-    p->next_elapse = *ts;
-    p->period = 0;
-    p->timer_enabled = TRUE;
-
-    update_timer(p);
-}
-
-void pa_rtpoll_set_timer_periodic(pa_rtpoll *p, pa_usec_t usec) {
+void pa_rtpoll_set_timer_absolute(pa_rtpoll *p, pa_usec_t usec) {
     pa_assert(p);
 
-    p->period = usec;
-    pa_rtclock_get(&p->next_elapse);
-    pa_timeval_add(&p->next_elapse, usec);
+    pa_timeval_store(&p->next_elapse, usec);
     p->timer_enabled = TRUE;
 
     update_timer(p);
@@ -535,7 +514,6 @@ void pa_rtpoll_set_timer_periodic(pa_rtpoll *p, pa_usec_t usec) {
 void pa_rtpoll_set_timer_relative(pa_rtpoll *p, pa_usec_t usec) {
     pa_assert(p);
 
-    p->period = 0;
     pa_rtclock_get(&p->next_elapse);
     pa_timeval_add(&p->next_elapse, usec);
     p->timer_enabled = TRUE;
@@ -546,7 +524,6 @@ void pa_rtpoll_set_timer_relative(pa_rtpoll *p, pa_usec_t usec) {
 void pa_rtpoll_set_timer_disabled(pa_rtpoll *p) {
     pa_assert(p);
 
-    p->period = 0;
     memset(&p->next_elapse, 0, sizeof(p->next_elapse));
     p->timer_enabled = FALSE;
 
@@ -683,23 +660,23 @@ pa_rtpoll_item *pa_rtpoll_item_new_fdsem(pa_rtpoll *p, pa_rtpoll_priority_t prio
     return i;
 }
 
-static int asyncmsgq_before(pa_rtpoll_item *i) {
+static int asyncmsgq_read_before(pa_rtpoll_item *i) {
     pa_assert(i);
 
-    if (pa_asyncmsgq_before_poll(i->userdata) < 0)
+    if (pa_asyncmsgq_read_before_poll(i->userdata) < 0)
         return 1; /* 1 means immediate restart of the loop */
 
     return 0;
 }
 
-static void asyncmsgq_after(pa_rtpoll_item *i) {
+static void asyncmsgq_read_after(pa_rtpoll_item *i) {
     pa_assert(i);
 
     pa_assert((i->pollfd[0].revents & ~POLLIN) == 0);
-    pa_asyncmsgq_after_poll(i->userdata);
+    pa_asyncmsgq_read_after_poll(i->userdata);
 }
 
-static int asyncmsgq_work(pa_rtpoll_item *i) {
+static int asyncmsgq_read_work(pa_rtpoll_item *i) {
     pa_msgobject *object;
     int code;
     void *data;
@@ -725,7 +702,7 @@ static int asyncmsgq_work(pa_rtpoll_item *i) {
     return 0;
 }
 
-pa_rtpoll_item *pa_rtpoll_item_new_asyncmsgq(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_asyncmsgq *q) {
+pa_rtpoll_item *pa_rtpoll_item_new_asyncmsgq_read(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_asyncmsgq *q) {
     pa_rtpoll_item *i;
     struct pollfd *pollfd;
 
@@ -735,12 +712,47 @@ pa_rtpoll_item *pa_rtpoll_item_new_asyncmsgq(pa_rtpoll *p, pa_rtpoll_priority_t
     i = pa_rtpoll_item_new(p, prio, 1);
 
     pollfd = pa_rtpoll_item_get_pollfd(i, NULL);
-    pollfd->fd = pa_asyncmsgq_get_fd(q);
+    pollfd->fd = pa_asyncmsgq_read_fd(q);
     pollfd->events = POLLIN;
 
-    i->before_cb = asyncmsgq_before;
-    i->after_cb = asyncmsgq_after;
-    i->work_cb = asyncmsgq_work;
+    i->before_cb = asyncmsgq_read_before;
+    i->after_cb = asyncmsgq_read_after;
+    i->work_cb = asyncmsgq_read_work;
+    i->userdata = q;
+
+    return i;
+}
+
+static int asyncmsgq_write_before(pa_rtpoll_item *i) {
+    pa_assert(i);
+
+    pa_asyncmsgq_write_before_poll(i->userdata);
+    return 0;
+}
+
+static void asyncmsgq_write_after(pa_rtpoll_item *i) {
+    pa_assert(i);
+
+    pa_assert((i->pollfd[0].revents & ~POLLIN) == 0);
+    pa_asyncmsgq_write_after_poll(i->userdata);
+}
+
+pa_rtpoll_item *pa_rtpoll_item_new_asyncmsgq_write(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_asyncmsgq *q) {
+    pa_rtpoll_item *i;
+    struct pollfd *pollfd;
+
+    pa_assert(p);
+    pa_assert(q);
+
+    i = pa_rtpoll_item_new(p, prio, 1);
+
+    pollfd = pa_rtpoll_item_get_pollfd(i, NULL);
+    pollfd->fd = pa_asyncmsgq_write_fd(q);
+    pollfd->events = POLLIN;
+
+    i->before_cb = asyncmsgq_write_before;
+    i->after_cb = asyncmsgq_write_after;
+    i->work_cb = NULL;
     i->userdata = q;
 
     return i;
index 02f5c7c..16dadbc 100644 (file)
@@ -74,8 +74,7 @@ void pa_rtpoll_install(pa_rtpoll *p);
  * cleanly. */
 int pa_rtpoll_run(pa_rtpoll *f, pa_bool_t wait);
 
-void pa_rtpoll_set_timer_absolute(pa_rtpoll *p, const struct timeval *ts);
-void pa_rtpoll_set_timer_periodic(pa_rtpoll *p, pa_usec_t usec);
+void pa_rtpoll_set_timer_absolute(pa_rtpoll *p, pa_usec_t usec);
 void pa_rtpoll_set_timer_relative(pa_rtpoll *p, pa_usec_t usec);
 void pa_rtpoll_set_timer_disabled(pa_rtpoll *p);
 
@@ -107,7 +106,8 @@ void pa_rtpoll_item_set_userdata(pa_rtpoll_item *i, void *userdata);
 void* pa_rtpoll_item_get_userdata(pa_rtpoll_item *i);
 
 pa_rtpoll_item *pa_rtpoll_item_new_fdsem(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_fdsem *s);
-pa_rtpoll_item *pa_rtpoll_item_new_asyncmsgq(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_asyncmsgq *q);
+pa_rtpoll_item *pa_rtpoll_item_new_asyncmsgq_read(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_asyncmsgq *q);
+pa_rtpoll_item *pa_rtpoll_item_new_asyncmsgq_write(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_asyncmsgq *q);
 
 /* Requests the loop to exit. Will cause the next iteration of
  * pa_rtpoll_run() to return 0 */
index 4ea5d44..a802829 100644 (file)
 
 #define PA_SILENCE_MAX (PA_PAGE_SIZE*16)
 
-pa_memblock *pa_silence_memblock_new(pa_mempool *pool, const pa_sample_spec *spec, size_t length) {
-    size_t fs;
-    pa_assert(pool);
-    pa_assert(spec);
-
-    if (length <= 0)
-        length = pa_bytes_per_second(spec)/20; /* 50 ms */
-
-    if (length > PA_SILENCE_MAX)
-        length = PA_SILENCE_MAX;
-
-    fs = pa_frame_size(spec);
-
-    length = (length+fs-1)/fs;
-
-    if (length <= 0)
-        length = 1;
-
-    length *= fs;
-
-    return pa_silence_memblock(pa_memblock_new(pool, length), spec);
-}
-
 pa_memblock *pa_silence_memblock(pa_memblock* b, const pa_sample_spec *spec) {
     void *data;
 
@@ -74,10 +51,11 @@ pa_memblock *pa_silence_memblock(pa_memblock* b, const pa_sample_spec *spec) {
     data = pa_memblock_acquire(b);
     pa_silence_memory(data, pa_memblock_get_length(b), spec);
     pa_memblock_release(b);
+
     return b;
 }
 
-void pa_silence_memchunk(pa_memchunk *c, const pa_sample_spec *spec) {
+pa_memchunk* pa_silence_memchunk(pa_memchunk *c, const pa_sample_spec *spec) {
     void *data;
 
     pa_assert(c);
@@ -87,37 +65,38 @@ void pa_silence_memchunk(pa_memchunk *c, const pa_sample_spec *spec) {
     data = pa_memblock_acquire(c->memblock);
     pa_silence_memory((uint8_t*) data+c->index, c->length, spec);
     pa_memblock_release(c->memblock);
-}
 
-void pa_silence_memory(void *p, size_t length, const pa_sample_spec *spec) {
-    uint8_t c = 0;
-    pa_assert(p);
-    pa_assert(length > 0);
-    pa_assert(spec);
+    return c;
+}
 
-    switch (spec->format) {
+static uint8_t silence_byte(pa_sample_format_t format) {
+    switch (format) {
         case PA_SAMPLE_U8:
-            c = 0x80;
-            break;
+            return 0x80;
         case PA_SAMPLE_S16LE:
         case PA_SAMPLE_S16BE:
         case PA_SAMPLE_S32LE:
         case PA_SAMPLE_S32BE:
-        case PA_SAMPLE_FLOAT32:
-        case PA_SAMPLE_FLOAT32RE:
-            c = 0;
-            break;
+        case PA_SAMPLE_FLOAT32LE:
+        case PA_SAMPLE_FLOAT32BE:
+            return 0;
         case PA_SAMPLE_ALAW:
-            c = 0xd5;
-            break;
+            return 0xd5;
         case PA_SAMPLE_ULAW:
-            c = 0xff;
-            break;
+            return 0xff;
         default:
             pa_assert_not_reached();
     }
+    return 0;
+}
 
-    memset(p, c, length);
+void* pa_silence_memory(void *p, size_t length, const pa_sample_spec *spec) {
+    pa_assert(p);
+    pa_assert(length > 0);
+    pa_assert(spec);
+
+    memset(p, silence_byte(spec->format), length);
+    return p;
 }
 
 static void calc_linear_integer_stream_volumes(pa_mix_info streams[], unsigned nstreams, const pa_sample_spec *spec) {
@@ -631,6 +610,9 @@ void pa_volume_memchunk(
     pa_assert(c->length % pa_frame_size(spec) == 0);
     pa_assert(volume);
 
+    if (pa_memblock_is_silence(c->memblock))
+        return;
+
     if (pa_cvolume_channels_equal_to(volume, PA_VOLUME_NORM))
         return;
 
@@ -931,3 +913,117 @@ void pa_deinterleave(const void *src, void *dst[], unsigned channels, size_t ss,
         }
     }
 }
+
+static pa_memblock *silence_memblock_new(pa_mempool *pool, uint8_t c) {
+    pa_memblock *b;
+    size_t length;
+    void *data;
+
+    pa_assert(pool);
+
+    length = PA_MIN(pa_mempool_block_size_max(pool), PA_SILENCE_MAX);
+
+    b = pa_memblock_new(pool, length);
+
+    data = pa_memblock_acquire(b);
+    memset(data, c, length);
+    pa_memblock_release(b);
+
+    pa_memblock_set_is_silence(b, TRUE);
+
+    return b;
+}
+
+void pa_silence_cache_init(pa_silence_cache *cache) {
+    pa_assert(cache);
+
+    memset(cache, 0, sizeof(pa_silence_cache));
+}
+
+void pa_silence_cache_done(pa_silence_cache *cache) {
+    pa_sample_format_t f;
+    pa_assert(cache);
+
+    for (f = 0; f < PA_SAMPLE_MAX; f++)
+        if (cache->blocks[f])
+            pa_memblock_unref(cache->blocks[f]);
+
+    memset(cache, 0, sizeof(pa_silence_cache));
+}
+
+pa_memchunk* pa_silence_memchunk_get(pa_silence_cache *cache, pa_mempool *pool, pa_memchunk* ret, const pa_sample_spec *spec, size_t length) {
+    pa_memblock *b;
+    size_t l;
+
+    pa_assert(cache);
+    pa_assert(pa_sample_spec_valid(spec));
+
+    if (!(b = cache->blocks[spec->format]))
+
+        switch (spec->format) {
+            case PA_SAMPLE_U8:
+                cache->blocks[PA_SAMPLE_U8] = b = silence_memblock_new(pool, 0x80);
+                break;
+            case PA_SAMPLE_S16LE:
+            case PA_SAMPLE_S16BE:
+            case PA_SAMPLE_S32LE:
+            case PA_SAMPLE_S32BE:
+            case PA_SAMPLE_FLOAT32LE:
+            case PA_SAMPLE_FLOAT32BE:
+                cache->blocks[PA_SAMPLE_S16LE] = b = silence_memblock_new(pool, 0);
+                cache->blocks[PA_SAMPLE_S16BE] = pa_memblock_ref(b);
+                cache->blocks[PA_SAMPLE_S32LE] = pa_memblock_ref(b);
+                cache->blocks[PA_SAMPLE_S32BE] = pa_memblock_ref(b);
+                cache->blocks[PA_SAMPLE_FLOAT32LE] = pa_memblock_ref(b);
+                cache->blocks[PA_SAMPLE_FLOAT32BE] = pa_memblock_ref(b);
+                break;
+            case PA_SAMPLE_ALAW:
+                cache->blocks[PA_SAMPLE_ALAW] = b = silence_memblock_new(pool, 0xd5);
+                break;
+            case PA_SAMPLE_ULAW:
+                cache->blocks[PA_SAMPLE_ULAW] = b = silence_memblock_new(pool, 0xff);
+                break;
+            default:
+                pa_assert_not_reached();
+    }
+
+    pa_assert(b);
+
+    ret->memblock = pa_memblock_ref(b);
+
+    l = pa_memblock_get_length(b);
+    if (length > l || length == 0)
+        length = l;
+
+    ret->length = pa_frame_align(length, spec);
+    ret->index = 0;
+
+    return ret;
+}
+
+void pa_sample_clamp(pa_sample_format_t format, void *dst, size_t dstr, const void *src, size_t sstr, unsigned n) {
+    const float *s;
+    float *d;
+
+    s = src; d = dst;
+
+    if (format == PA_SAMPLE_FLOAT32NE) {
+
+        float minus_one = -1.0, plus_one = 1.0;
+        oil_clip_f32(d, dstr, s, sstr, n, &minus_one, &plus_one);
+
+    } else {
+        pa_assert(format == PA_SAMPLE_FLOAT32RE);
+
+        for (; n > 0; n--) {
+            float f;
+
+            f = PA_FLOAT32_SWAP(*s);
+            f = PA_CLAMP_UNLIKELY(f, -1.0, 1.0);
+            *d = PA_FLOAT32_SWAP(f);
+
+            s = (const float*) ((const uint8_t*) s + sstr);
+            d = (float*) ((uint8_t*) d + dstr);
+        }
+    }
+}
index 2ef8f92..59b4c63 100644 (file)
 #include <pulsecore/memblock.h>
 #include <pulsecore/memchunk.h>
 
-pa_memblock *pa_silence_memblock(pa_memblock* b, const pa_sample_spec *spec);
-pa_memblock *pa_silence_memblock_new(pa_mempool *pool, const pa_sample_spec *spec, size_t length);
-void pa_silence_memchunk(pa_memchunk *c, const pa_sample_spec *spec);
-void pa_silence_memory(void *p, size_t length, const pa_sample_spec *spec);
+typedef struct pa_silence_cache {
+    pa_memblock* blocks[PA_SAMPLE_MAX];
+} pa_silence_cache;
+
+void pa_silence_cache_init(pa_silence_cache *cache);
+void pa_silence_cache_done(pa_silence_cache *cache);
+
+void *pa_silence_memory(void *p, size_t length, const pa_sample_spec *spec);
+pa_memchunk* pa_silence_memchunk(pa_memchunk *c, const pa_sample_spec *spec);
+pa_memblock* pa_silence_memblock(pa_memblock *b, const pa_sample_spec *spec);
+
+pa_memchunk* pa_silence_memchunk_get(pa_silence_cache *cache, pa_mempool *pool, pa_memchunk* ret, const pa_sample_spec *spec, size_t length);
 
 typedef struct pa_mix_info {
     pa_memchunk chunk;
@@ -70,4 +78,6 @@ int pa_frame_aligned(size_t l, const pa_sample_spec *ss) PA_GCC_PURE;
 void pa_interleave(const void *src[], unsigned channels, void *dst, size_t ss, unsigned n);
 void pa_deinterleave(const void *src, void *dst[], unsigned channels, size_t ss, unsigned n);
 
+void pa_sample_clamp(pa_sample_format_t format, void *dst, size_t dstr, const void *src, size_t sstr, unsigned n);
+
 #endif
index 7c764e3..a684381 100644 (file)
@@ -42,6 +42,7 @@
 #endif
 
 #include <pulse/xmalloc.h>
+#include <pulse/gccmacro.h>
 
 #include <pulsecore/core-error.h>
 #include <pulsecore/log.h>
@@ -56,7 +57,7 @@
 #define MADV_REMOVE 9
 #endif
 
-#define MAX_SHM_SIZE (PA_ALIGN(1024*1024*20))
+#define MAX_SHM_SIZE (PA_ALIGN(1024*1024*64))
 
 #ifdef __linux__
 /* On Linux we know that the shared memory blocks are files in
 
 #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 */
-struct shm_marker {
+/* We now put this SHM marker at the end of each segment. It's
+ * optional, to not require a reboot when upgrading, though */
+struct shm_marker PA_GCC_PACKED {
     pa_atomic_t marker; /* 0xbeefcafe */
     pa_atomic_t pid;
-    void *_reserverd1;
-    void *_reserverd2;
-    void *_reserverd3;
-    void *_reserverd4;
+    uint64_t *_reserverd1;
+    uint64_t *_reserverd2;
+    uint64_t *_reserverd3;
+    uint64_t *_reserverd4;
 };
 
 static char *segment_name(char *fn, size_t l, unsigned id) {
@@ -84,13 +86,13 @@ static char *segment_name(char *fn, size_t l, unsigned id) {
     return fn;
 }
 
-int pa_shm_create_rw(pa_shm *m, size_t size, int shared, mode_t mode) {
+int pa_shm_create_rw(pa_shm *m, size_t size, pa_bool_t shared, mode_t mode) {
     char fn[32];
     int fd = -1;
 
     pa_assert(m);
     pa_assert(size > 0);
-    pa_assert(size < MAX_SHM_SIZE);
+    pa_assert(size <= MAX_SHM_SIZE);
     pa_assert(mode >= 0600);
 
     /* Each time we create a new SHM area, let's first drop all stale
@@ -122,7 +124,7 @@ int pa_shm_create_rw(pa_shm *m, size_t size, int shared, mode_t mode) {
         m->ptr = pa_xmalloc(m->size);
 #endif
 
-        m->do_unlink = 0;
+        m->do_unlink = FALSE;
 
     } else {
 #ifdef HAVE_SHM_OPEN
@@ -155,7 +157,7 @@ int pa_shm_create_rw(pa_shm *m, size_t size, int shared, mode_t mode) {
         pa_atomic_store(&marker->marker, SHM_MARKER);
 
         pa_assert_se(close(fd) == 0);
-        m->do_unlink = 1;
+        m->do_unlink = TRUE;
 #else
         return -1;
 #endif
@@ -282,7 +284,9 @@ int pa_shm_attach_ro(pa_shm *m, unsigned id) {
         goto fail;
     }
 
-    if (st.st_size <= 0 || st.st_size > MAX_SHM_SIZE+PA_ALIGN(sizeof(struct shm_marker)) || PA_ALIGN(st.st_size) != st.st_size) {
+    if (st.st_size <= 0 ||
+        st.st_size > (off_t) (MAX_SHM_SIZE+PA_ALIGN(sizeof(struct shm_marker))) ||
+        PA_ALIGN((size_t) st.st_size) != (size_t) st.st_size) {
         pa_log("Invalid shared memory segment size");
         goto fail;
     }
@@ -371,7 +375,7 @@ int pa_shm_cleanup(void) {
         /* Ok, the owner of this shms segment is dead, so, let's remove the segment */
         segment_name(fn, sizeof(fn), id);
 
-        if (shm_unlink(fn) < 0 && errno != EACCES)
+        if (shm_unlink(fn) < 0 && errno != EACCES && errno != ENOENT)
             pa_log_warn("Failed to remove SHM segment %s: %s\n", fn, pa_cstrerror(errno));
     }
 
index 270591d..60bc355 100644 (file)
 
 #include <sys/types.h>
 
+#include <pulsecore/macro.h>
+
 typedef struct pa_shm {
     unsigned id;
     void *ptr;
     size_t size;
-    int do_unlink;
-    int shared;
+    pa_bool_t do_unlink:1;
+    pa_bool_t shared:1;
 } pa_shm;
 
-int pa_shm_create_rw(pa_shm *m, size_t size, int shared, mode_t mode);
+int pa_shm_create_rw(pa_shm *m, size_t size, pa_bool_t shared, mode_t mode);
 int pa_shm_attach_ro(pa_shm *m, unsigned id);
 
 void pa_shm_punch(pa_shm *m, size_t offset, size_t size);
diff --git a/src/pulsecore/shmasyncq.c b/src/pulsecore/shmasyncq.c
new file mode 100644 (file)
index 0000000..7af2985
--- /dev/null
@@ -0,0 +1,222 @@
+/* $Id$ */
+
+/***
+  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
+  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 <unistd.h>
+#include <errno.h>
+
+#include <pulsecore/atomic.h>
+#include <pulsecore/log.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+#include <pulse/xmalloc.h>
+
+#include "fdsem.h"
+
+/* For debugging purposes we can define _Y to put and extra thread
+ * yield between each operation. */
+
+/* #define PROFILE */
+
+#ifdef PROFILE
+#define _Y pa_thread_yield()
+#else
+#define _Y do { } while(0)
+#endif
+
+
+struct pa_shmasyncq {
+    pa_fdsem *read_fdsem, *write_fdsem;
+    pa_shmasyncq_data *data;
+};
+
+static int is_power_of_two(unsigned size) {
+    return !(size & (size - 1));
+}
+
+static int reduce(pa_shmasyncq *l, int value) {
+    return value & (unsigned) (l->n_elements - 1);
+}
+
+static pa_atomic_t* get_cell(pa_shmasyncq *l, unsigned i) {
+    pa_assert(i < l->data->n_elements);
+
+    return (pa_atomic_t*) ((uint8*t) l->data + PA_ALIGN(sizeof(pa_shmasyncq_data)) + i * (PA_ALIGN(sizeof(pa_atomic_t)) + PA_ALIGN(element_size)))
+}
+
+static void *get_cell_data(pa_atomic_t *a) {
+    return (uint8_t*) a + PA_ALIGN(sizeof(atomic_t));
+}
+
+pa_shmasyncq *pa_shmasyncq_new(unsigned n_elements, size_t element_size, void *data, int fd[2]) {
+    pa_shmasyncq *l;
+
+    pa_assert(n_elements > 0);
+    pa_assert(is_power_of_two(n_elements));
+    pa_assert(element_size > 0);
+    pa_assert(data);
+    pa_assert(fd);
+
+    l = pa_xnew(pa_shmasyncq, 1);
+
+    l->data = data;
+    memset(data, 0, PA_SHMASYNCQ_SIZE(n_elements, element_size));
+
+    l->data->n_elements = n_elements;
+    l->data->element_size = element_size;
+
+    if (!(l->read_fdsem = pa_fdsem_new_shm(&d->read_fdsem_data, &fd[0]))) {
+        pa_xfree(l);
+        return NULL;
+    }
+
+    if (!(l->write_fdsem = pa_fdsem_new(&d->write_fdsem_data, &fd[1]))) {
+        pa_fdsem_free(l->read_fdsem);
+        pa_xfree(l);
+        return NULL;
+    }
+
+    return l;
+}
+
+void pa_shmasyncq_free(pa_shmasyncq *l, pa_free_cb_t free_cb) {
+    pa_assert(l);
+
+    if (free_cb) {
+        void *p;
+
+        while ((p = pa_shmasyncq_pop(l, 0)))
+            free_cb(p);
+    }
+
+    pa_fdsem_free(l->read_fdsem);
+    pa_fdsem_free(l->write_fdsem);
+    pa_xfree(l);
+}
+
+int pa_shmasyncq_push(pa_shmasyncq*l, void *p, int wait) {
+    int idx;
+    pa_atomic_ptr_t *cells;
+
+    pa_assert(l);
+    pa_assert(p);
+
+    cells = PA_SHMASYNCQ_CELLS(l);
+
+    _Y;
+    idx = reduce(l, l->write_idx);
+
+    if (!pa_atomic_ptr_cmpxchg(&cells[idx], NULL, p)) {
+
+        if (!wait)
+            return -1;
+
+/*         pa_log("sleeping on push"); */
+
+        do {
+            pa_fdsem_wait(l->read_fdsem);
+        } while (!pa_atomic_ptr_cmpxchg(&cells[idx], NULL, p));
+    }
+
+    _Y;
+    l->write_idx++;
+
+    pa_fdsem_post(l->write_fdsem);
+
+    return 0;
+}
+
+void* pa_shmasyncq_pop(pa_shmasyncq*l, int wait) {
+    int idx;
+    void *ret;
+    pa_atomic_ptr_t *cells;
+
+    pa_assert(l);
+
+    cells = PA_SHMASYNCQ_CELLS(l);
+
+    _Y;
+    idx = reduce(l, l->read_idx);
+
+    if (!(ret = pa_atomic_ptr_load(&cells[idx]))) {
+
+        if (!wait)
+            return NULL;
+
+/*         pa_log("sleeping on pop"); */
+
+        do {
+            pa_fdsem_wait(l->write_fdsem);
+        } while (!(ret = pa_atomic_ptr_load(&cells[idx])));
+    }
+
+    pa_assert(ret);
+
+    /* Guaranteed to succeed if we only have a single reader */
+    pa_assert_se(pa_atomic_ptr_cmpxchg(&cells[idx], ret, NULL));
+
+    _Y;
+    l->read_idx++;
+
+    pa_fdsem_post(l->read_fdsem);
+
+    return ret;
+}
+
+int pa_shmasyncq_get_fd(pa_shmasyncq *q) {
+    pa_assert(q);
+
+    return pa_fdsem_get(q->write_fdsem);
+}
+
+int pa_shmasyncq_before_poll(pa_shmasyncq *l) {
+    int idx;
+    pa_atomic_ptr_t *cells;
+
+    pa_assert(l);
+
+    cells = PA_SHMASYNCQ_CELLS(l);
+
+    _Y;
+    idx = reduce(l, l->read_idx);
+
+    for (;;) {
+        if (pa_atomic_ptr_load(&cells[idx]))
+            return -1;
+
+        if (pa_fdsem_before_poll(l->write_fdsem) >= 0)
+            return 0;
+    }
+
+    return 0;
+}
+
+void pa_shmasyncq_after_poll(pa_shmasyncq *l) {
+    pa_assert(l);
+
+    pa_fdsem_after_poll(l->write_fdsem);
+}
diff --git a/src/pulsecore/shmasyncq.h b/src/pulsecore/shmasyncq.h
new file mode 100644 (file)
index 0000000..ca35ffd
--- /dev/null
@@ -0,0 +1,62 @@
+#ifndef foopulseshmasyncqhfoo
+#define foopulseshmasyncqhfoo
+
+/* $Id$ */
+
+/***
+  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 <sys/types.h>
+
+#include <pulse/def.h>
+#include <pulsecore/macro.h>
+
+/* Similar to pa_asyncq, but stores data in a shared memory segment */
+
+
+struct pa_shmasyncq_data {
+    unsigned n_elements;
+    size_t element_size;
+    unsigned read_idx;
+    unsigned write_idx;
+    pa_fdsem_data read_fdsem_data, write_fdsem_data;
+};
+
+#define PA_SHMASYNCQ_DEFAULT_N_ELEMENTS 128
+#define PA_SHMASYNCQ_SIZE(n_elements, element_size) (PA_ALIGN(sizeof(pa_shmasyncq_data)) + (((n_elements) * (PA_ALIGN(sizeof(pa_atomic_t)) + PA_ALIGN(element_size)))))
+#define PA_SHMASYNCQ_DEFAULT_SIZE(element_size) PA_SHMASYNCQ_SIZE(PA_SHMASYNCQ_DEFAULT_N_ELEMENTS, element_size)
+
+typedef struct pa_shmasyncq pa_shmasyncq;
+
+pa_shmasyncq *pa_shmasyncq_new(unsigned n_elements, size_t element_size, void *data, int fd[2]);
+void pa_shmasyncq_free(pa_shmasyncq* q, pa_free_cb_t free_cb);
+
+void* pa_shmasyncq_pop_begin(pa_shmasyncq *q, pa_bool_t wait);
+void pa_shmasyncq_pop_commit(pa_shmasyncq *q);
+
+int* pa_shmasyncq_push_begin(pa_shmasyncq *q, pa_bool_t wait);
+void pa_shmasyncq_push_commit(pa_shmasyncq *q);
+
+int pa_shmasyncq_get_fd(pa_shmasyncq *q);
+int pa_shmasyncq_before_poll(pa_shmasyncq *a);
+void pa_shmasyncq_after_poll(pa_shmasyncq *a);
+
+#endif
index 07ddb83..d51ff81 100644 (file)
 #include <pulsecore/log.h>
 #include <pulsecore/play-memblockq.h>
 #include <pulsecore/namereg.h>
+#include <pulsecore/core-util.h>
 
 #include "sink-input.h"
 
+#define MEMBLOCKQ_MAXLENGTH (32*1024*1024)
 #define CONVERT_BUFFER_LENGTH (PA_PAGE_SIZE)
-#define SILENCE_BUFFER_LENGTH (PA_PAGE_SIZE*12)
 #define MOVE_BUFFER_LENGTH (PA_PAGE_SIZE*256)
 
 static PA_DEFINE_CHECK_TYPE(pa_sink_input, pa_msgobject);
@@ -54,10 +55,18 @@ pa_sink_input_new_data* pa_sink_input_new_data_init(pa_sink_input_new_data *data
 
     memset(data, 0, sizeof(*data));
     data->resample_method = PA_RESAMPLER_INVALID;
+    data->proplist = pa_proplist_new();
 
     return data;
 }
 
+void pa_sink_input_new_data_set_sample_spec(pa_sink_input_new_data *data, const pa_sample_spec *spec) {
+    pa_assert(data);
+
+    if ((data->sample_spec_is_set = !!spec))
+        data->sample_spec = *spec;
+}
+
 void pa_sink_input_new_data_set_channel_map(pa_sink_input_new_data *data, const pa_channel_map *map) {
     pa_assert(data);
 
@@ -72,18 +81,32 @@ void pa_sink_input_new_data_set_volume(pa_sink_input_new_data *data, const pa_cv
         data->volume = *volume;
 }
 
-void pa_sink_input_new_data_set_sample_spec(pa_sink_input_new_data *data, const pa_sample_spec *spec) {
+void pa_sink_input_new_data_set_muted(pa_sink_input_new_data *data, pa_bool_t mute) {
     pa_assert(data);
 
-    if ((data->sample_spec_is_set = !!spec))
-        data->sample_spec = *spec;
+    data->muted_is_set = TRUE;
+    data->muted = !!mute;
 }
 
-void pa_sink_input_new_data_set_muted(pa_sink_input_new_data *data, pa_bool_t mute) {
+void pa_sink_input_new_data_done(pa_sink_input_new_data *data) {
     pa_assert(data);
 
-    data->muted_is_set = TRUE;
-    data->muted = !!mute;
+    pa_proplist_free(data->proplist);
+}
+
+static void reset_callbacks(pa_sink_input *i) {
+    pa_assert(i);
+
+    i->pop = NULL;
+    i->process_rewind = NULL;
+    i->update_max_rewind = NULL;
+    i->attach = NULL;
+    i->detach = NULL;
+    i->suspend = NULL;
+    i->moved = NULL;
+    i->kill = NULL;
+    i->get_latency = NULL;
+    i->state_change = NULL;
 }
 
 pa_sink_input* pa_sink_input_new(
@@ -102,7 +125,6 @@ pa_sink_input* pa_sink_input_new(
         return NULL;
 
     pa_return_null_if_fail(!data->driver || pa_utf8_valid(data->driver));
-    pa_return_null_if_fail(!data->name || pa_utf8_valid(data->name));
 
     if (!data->sink)
         data->sink = pa_namereg_get(core, NULL, PA_NAMEREG_SINK, 1);
@@ -132,6 +154,9 @@ pa_sink_input* pa_sink_input_new(
     pa_return_null_if_fail(pa_cvolume_valid(&data->volume));
     pa_return_null_if_fail(data->volume.channels == data->sample_spec.channels);
 
+    if (!data->muted_is_set)
+        data->muted = FALSE;
+
     if (flags & PA_SINK_INPUT_FIX_FORMAT)
         data->sample_spec.format = data->sink->sample_spec.format;
 
@@ -150,9 +175,6 @@ pa_sink_input* pa_sink_input_new(
     if (data->volume.channels != data->sample_spec.channels)
         pa_cvolume_set(&data->volume, data->sample_spec.channels, pa_cvolume_avg(&data->volume));
 
-    if (!data->muted_is_set)
-        data->muted = 0;
-
     if (data->resample_method == PA_RESAMPLER_INVALID)
         data->resample_method = core->resample_method;
 
@@ -192,7 +214,7 @@ pa_sink_input* pa_sink_input_new(
     i->core = core;
     i->state = PA_SINK_INPUT_INIT;
     i->flags = flags;
-    i->name = pa_xstrdup(data->name);
+    i->proplist = pa_proplist_copy(data->proplist);
     i->driver = pa_xstrdup(data->driver);
     i->module = data->module;
     i->sink = data->sink;
@@ -215,33 +237,38 @@ pa_sink_input* pa_sink_input_new(
     } else
         i->sync_next = i->sync_prev = NULL;
 
-    i->peek = NULL;
-    i->drop = NULL;
-    i->kill = NULL;
-    i->get_latency = NULL;
-    i->attach = NULL;
-    i->detach = NULL;
-    i->suspend = NULL;
-    i->moved = NULL;
+    reset_callbacks(i);
     i->userdata = NULL;
 
     i->thread_info.state = i->state;
+    i->thread_info.attached = FALSE;
     pa_atomic_store(&i->thread_info.drained, 1);
     i->thread_info.sample_spec = i->sample_spec;
-    i->thread_info.silence_memblock = NULL;
-    i->thread_info.move_silence = 0;
-    pa_memchunk_reset(&i->thread_info.resampled_chunk);
     i->thread_info.resampler = resampler;
     i->thread_info.volume = i->volume;
     i->thread_info.muted = i->muted;
-    i->thread_info.attached = FALSE;
+    i->thread_info.requested_sink_latency = (pa_usec_t) -1;
+    i->thread_info.rewrite_nbytes = 0;
+    i->thread_info.rewrite_flush = FALSE;
+    i->thread_info.underrun_for = (uint64_t) -1;
+    i->thread_info.playing_for = 0;
+
+    i->thread_info.render_memblockq = pa_memblockq_new(
+            0,
+            MEMBLOCKQ_MAXLENGTH,
+            0,
+            pa_frame_size(&i->sink->sample_spec),
+            0,
+            1,
+            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_log_info("Created input %u \"%s\" on %s with sample spec %s and channel map %s",
                 i->index,
-                i->name,
+                pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME)),
                 i->sink->name,
                 pa_sample_spec_snprint(st, sizeof(st), &i->sample_spec),
                 pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map));
@@ -302,7 +329,7 @@ void pa_sink_input_unlink(pa_sink_input *i) {
 
     pa_sink_input_ref(i);
 
-    linked = PA_SINK_INPUT_LINKED(i->state);
+    linked = PA_SINK_INPUT_IS_LINKED(i->state);
 
     if (linked)
         pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], i);
@@ -318,21 +345,14 @@ void pa_sink_input_unlink(pa_sink_input *i) {
     if (pa_idxset_remove_by_data(i->sink->inputs, i, NULL))
         pa_sink_input_unref(i);
 
-    if (linked) {
-        pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_REMOVE_INPUT, i, 0, NULL);
-        sink_input_set_state(i, PA_SINK_INPUT_UNLINKED);
-        pa_sink_update_status(i->sink);
-    } else
-        i->state = PA_SINK_INPUT_UNLINKED;
+    update_n_corked(i, PA_SINK_INPUT_UNLINKED);
+    i->state = PA_SINK_INPUT_UNLINKED;
 
-    i->peek = NULL;
-    i->drop = NULL;
-    i->kill = NULL;
-    i->get_latency = NULL;
-    i->attach = NULL;
-    i->detach = NULL;
-    i->suspend = NULL;
-    i->moved = NULL;
+    if (linked)
+        if (i->sink->asyncmsgq)
+            pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_REMOVE_INPUT, i, 0, NULL);
+
+    reset_callbacks(i);
 
     if (linked) {
         pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_REMOVE, i->index);
@@ -349,55 +369,51 @@ static void sink_input_free(pa_object *o) {
     pa_assert(i);
     pa_assert(pa_sink_input_refcnt(i) == 0);
 
-    if (PA_SINK_INPUT_LINKED(i->state))
+    if (PA_SINK_INPUT_IS_LINKED(i->state))
         pa_sink_input_unlink(i);
 
-    pa_log_info("Freeing output %u \"%s\"", i->index, i->name);
+    pa_log_info("Freeing input %u \"%s\"", i->index, pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME)));
 
     pa_assert(!i->thread_info.attached);
 
-    if (i->thread_info.resampled_chunk.memblock)
-        pa_memblock_unref(i->thread_info.resampled_chunk.memblock);
+    if (i->thread_info.render_memblockq)
+        pa_memblockq_free(i->thread_info.render_memblockq);
 
     if (i->thread_info.resampler)
         pa_resampler_free(i->thread_info.resampler);
 
-    if (i->thread_info.silence_memblock)
-        pa_memblock_unref(i->thread_info.silence_memblock);
+    if (i->proplist)
+        pa_proplist_free(i->proplist);
 
-    pa_xfree(i->name);
     pa_xfree(i->driver);
     pa_xfree(i);
 }
 
 void pa_sink_input_put(pa_sink_input *i) {
+    pa_sink_input_state_t state;
     pa_sink_input_assert_ref(i);
 
     pa_assert(i->state == PA_SINK_INPUT_INIT);
-    pa_assert(i->peek);
-    pa_assert(i->drop);
+    pa_assert(i->pop);
+    pa_assert(i->process_rewind);
 
-    i->thread_info.state = i->state = i->flags & PA_SINK_INPUT_START_CORKED ? PA_SINK_INPUT_CORKED : PA_SINK_INPUT_RUNNING;
     i->thread_info.volume = i->volume;
     i->thread_info.muted = i->muted;
 
-    if (i->state == PA_SINK_INPUT_CORKED)
-        i->sink->n_corked++;
+    state = i->flags & PA_SINK_INPUT_START_CORKED ? PA_SINK_INPUT_CORKED : PA_SINK_INPUT_RUNNING;
+
+    update_n_corked(i, state);
+    i->state = state;
 
     pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_ADD_INPUT, i, 0, NULL);
-    pa_sink_update_status(i->sink);
 
     pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW, i->index);
     pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], i);
-
-    /* Please note that if you change something here, you have to
-       change something in pa_sink_input_move() with the ghost stream
-       registration too. */
 }
 
 void pa_sink_input_kill(pa_sink_input*i) {
     pa_sink_input_assert_ref(i);
-    pa_assert(PA_SINK_INPUT_LINKED(i->state));
+    pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
 
     if (i->kill)
         i->kill(i);
@@ -407,7 +423,7 @@ pa_usec_t pa_sink_input_get_latency(pa_sink_input *i) {
     pa_usec_t r = 0;
 
     pa_sink_input_assert_ref(i);
-    pa_assert(PA_SINK_INPUT_LINKED(i->state));
+    pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
 
     if (pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_GET_LATENCY, &r, 0, NULL) < 0)
         r = 0;
@@ -419,232 +435,316 @@ pa_usec_t pa_sink_input_get_latency(pa_sink_input *i) {
 }
 
 /* Called from thread context */
-int pa_sink_input_peek(pa_sink_input *i, size_t length, pa_memchunk *chunk, pa_cvolume *volume) {
-    int ret = -1;
-    int do_volume_adj_here;
-    int volume_is_norm;
-    size_t block_size_max;
+int pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, pa_memchunk *chunk, pa_cvolume *volume) {
+    pa_bool_t do_volume_adj_here;
+    pa_bool_t volume_is_norm;
+    size_t block_size_max_sink, block_size_max_sink_input;
+    size_t ilength;
 
     pa_sink_input_assert_ref(i);
-    pa_assert(PA_SINK_INPUT_LINKED(i->thread_info.state));
-    pa_assert(pa_frame_aligned(length, &i->sink->sample_spec));
+    pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state));
+    pa_assert(pa_frame_aligned(slength, &i->sink->sample_spec));
     pa_assert(chunk);
     pa_assert(volume);
 
-    if (!i->peek || !i->drop || i->thread_info.state == PA_SINK_INPUT_CORKED)
-        goto finish;
+/*     pa_log_debug("peek"); */
+
+    if (!i->pop)
+        return -1;
+
+    pa_assert(i->thread_info.state == PA_SINK_INPUT_RUNNING ||
+              i->thread_info.state == PA_SINK_INPUT_CORKED ||
+              i->thread_info.state == PA_SINK_INPUT_DRAINED);
+
+    /* If there's still some rewrite request the handle, but the sink
+    didn't do this for us, we do it here. However, since the sink
+    apparently doesn't support rewinding, we pass 0 here. This still
+    allows rewinding through the render buffer. */
+    pa_sink_input_process_rewind(i, 0);
+
+    block_size_max_sink_input = i->thread_info.resampler ?
+        pa_resampler_max_block_size(i->thread_info.resampler) :
+        pa_frame_align(pa_mempool_block_size_max(i->sink->core->mempool), &i->sample_spec);
 
-    pa_assert(i->thread_info.state == PA_SINK_INPUT_RUNNING || i->thread_info.state == PA_SINK_INPUT_DRAINED);
+    block_size_max_sink = pa_frame_align(pa_mempool_block_size_max(i->sink->core->mempool), &i->sink->sample_spec);
 
     /* Default buffer size */
-    if (length <= 0)
-        length = pa_frame_align(CONVERT_BUFFER_LENGTH, &i->sink->sample_spec);
-
-    /* Make sure the buffer fits in the mempool tile */
-    block_size_max = pa_mempool_block_size_max(i->sink->core->mempool);
-    if (length > block_size_max)
-        length = pa_frame_align(block_size_max, &i->sink->sample_spec);
-
-    if (i->thread_info.move_silence > 0) {
-        size_t l;
-
-        /* We have just been moved and shall play some silence for a
-         * while until the old sink has drained its playback buffer */
-
-        if (!i->thread_info.silence_memblock)
-            i->thread_info.silence_memblock = pa_silence_memblock_new(
-                    i->sink->core->mempool,
-                    &i->sink->sample_spec,
-                    pa_frame_align(SILENCE_BUFFER_LENGTH, &i->sink->sample_spec));
-
-        chunk->memblock = pa_memblock_ref(i->thread_info.silence_memblock);
-        chunk->index = 0;
-        l = pa_memblock_get_length(chunk->memblock);
-        chunk->length = i->thread_info.move_silence < l ? i->thread_info.move_silence : l;
-
-        ret = 0;
-        do_volume_adj_here = 1;
-        goto finish;
-    }
+    if (slength <= 0)
+        slength = pa_frame_align(CONVERT_BUFFER_LENGTH, &i->sink->sample_spec);
 
-    if (!i->thread_info.resampler) {
-        do_volume_adj_here = 0; /* FIXME??? */
-        ret = i->peek(i, length, chunk);
-        goto finish;
-    }
+    if (slength > block_size_max_sink)
+        slength = block_size_max_sink;
+
+    if (i->thread_info.resampler) {
+        ilength = pa_resampler_request(i->thread_info.resampler, slength);
+
+        if (ilength <= 0)
+            ilength = pa_frame_align(CONVERT_BUFFER_LENGTH, &i->sample_spec);
+    } else
+        ilength = slength;
+
+    if (ilength > block_size_max_sink_input)
+        ilength = block_size_max_sink_input;
+
+    /* If the channel maps of the sink and this stream differ, we need
+     * to adjust the volume *before* we resample. Otherwise we can do
+     * it after and leave it for the sink code */
 
     do_volume_adj_here = !pa_channel_map_equal(&i->channel_map, &i->sink->channel_map);
     volume_is_norm = pa_cvolume_is_norm(&i->thread_info.volume) && !i->thread_info.muted;
 
-    while (!i->thread_info.resampled_chunk.memblock) {
+    while (!pa_memblockq_is_readable(i->thread_info.render_memblockq)) {
         pa_memchunk tchunk;
-        size_t l, rmbs;
 
-        l = pa_resampler_request(i->thread_info.resampler, length);
+        /* There's nothing in our render queue. We need to fill it up
+         * with data from the implementor. */
 
-        if (l <= 0)
-            l = pa_frame_align(CONVERT_BUFFER_LENGTH, &i->sample_spec);
+        if (i->thread_info.state == PA_SINK_INPUT_CORKED ||
+            i->pop(i, ilength, &tchunk) < 0) {
 
-        rmbs = pa_resampler_max_block_size(i->thread_info.resampler);
-        if (l > rmbs)
-            l = rmbs;
+            /* OK, we're corked or the implementor didn't give us any
+             * data, so let's just hand out silence */
+            pa_atomic_store(&i->thread_info.drained, 1);
 
-        if ((ret = i->peek(i, l, &tchunk)) < 0)
-            goto finish;
+            pa_memblockq_seek(i->thread_info.render_memblockq, slength, PA_SEEK_RELATIVE);
+            i->thread_info.playing_for = 0;
+            if (i->thread_info.underrun_for != (uint64_t) -1)
+                i->thread_info.underrun_for += ilength;
+            break;
+        }
+
+        pa_atomic_store(&i->thread_info.drained, 0);
 
         pa_assert(tchunk.length > 0);
+        pa_assert(tchunk.memblock);
 
-        if (tchunk.length > l)
-            tchunk.length = l;
+        i->thread_info.underrun_for = 0;
+        i->thread_info.playing_for += tchunk.length;
 
-        i->drop(i, tchunk.length);
+        while (tchunk.length > 0) {
+            pa_memchunk wchunk;
 
-        /* It might be necessary to adjust the volume here */
-        if (do_volume_adj_here && !volume_is_norm) {
-            pa_memchunk_make_writable(&tchunk, 0);
+            wchunk = tchunk;
+            pa_memblock_ref(wchunk.memblock);
 
-            if (i->thread_info.muted)
-                pa_silence_memchunk(&tchunk, &i->thread_info.sample_spec);
-            else
-                pa_volume_memchunk(&tchunk, &i->thread_info.sample_spec, &i->thread_info.volume);
+            if (wchunk.length > block_size_max_sink_input)
+                wchunk.length = block_size_max_sink_input;
+
+            /* It might be necessary to adjust the volume here */
+            if (do_volume_adj_here && !volume_is_norm) {
+                pa_memchunk_make_writable(&wchunk, 0);
+
+                if (i->thread_info.muted)
+                    pa_silence_memchunk(&wchunk, &i->thread_info.sample_spec);
+                else
+                    pa_volume_memchunk(&wchunk, &i->thread_info.sample_spec, &i->thread_info.volume);
+            }
+
+            if (!i->thread_info.resampler)
+                pa_memblockq_push_align(i->thread_info.render_memblockq, &wchunk);
+            else {
+                pa_memchunk rchunk;
+                pa_resampler_run(i->thread_info.resampler, &wchunk, &rchunk);
+
+/*                 pa_log_debug("pushing %lu", (unsigned long) rchunk.length); */
+
+                if (rchunk.memblock) {
+                    pa_memblockq_push_align(i->thread_info.render_memblockq, &rchunk);
+                    pa_memblock_unref(rchunk.memblock);
+                }
+            }
+
+            pa_memblock_unref(wchunk.memblock);
+
+            tchunk.index += wchunk.length;
+            tchunk.length -= wchunk.length;
         }
 
-        pa_resampler_run(i->thread_info.resampler, &tchunk, &i->thread_info.resampled_chunk);
         pa_memblock_unref(tchunk.memblock);
     }
 
-    pa_assert(i->thread_info.resampled_chunk.memblock);
-    pa_assert(i->thread_info.resampled_chunk.length > 0);
+    pa_assert_se(pa_memblockq_peek(i->thread_info.render_memblockq, chunk) >= 0);
 
-    *chunk = i->thread_info.resampled_chunk;
-    pa_memblock_ref(i->thread_info.resampled_chunk.memblock);
+    pa_assert(chunk->length > 0);
+    pa_assert(chunk->memblock);
 
-    ret = 0;
+/*     pa_log_debug("peeking %lu", (unsigned long) chunk->length); */
 
-finish:
+    if (chunk->length > block_size_max_sink)
+        chunk->length = block_size_max_sink;
 
-    if (ret >= 0)
-        pa_atomic_store(&i->thread_info.drained, 0);
-    else if (ret < 0)
-        pa_atomic_store(&i->thread_info.drained, 1);
-
-    if (ret >= 0) {
-        /* Let's see if we had to apply the volume adjustment
-         * ourselves, or if this can be done by the sink for us */
+    /* Let's see if we had to apply the volume adjustment ourselves,
+     * or if this can be done by the sink for us */
 
-        if (do_volume_adj_here)
-            /* We had different channel maps, so we already did the adjustment */
-            pa_cvolume_reset(volume, i->sink->sample_spec.channels);
-        else if (i->thread_info.muted)
-            /* We've both the same channel map, so let's have the sink do the adjustment for us*/
-            pa_cvolume_mute(volume, i->sink->sample_spec.channels);
-        else
-            *volume = i->thread_info.volume;
-    }
+    if (do_volume_adj_here)
+        /* We had different channel maps, so we already did the adjustment */
+        pa_cvolume_reset(volume, i->sink->sample_spec.channels);
+    else if (i->thread_info.muted)
+        /* We've both the same channel map, so let's have the sink do the adjustment for us*/
+        pa_cvolume_mute(volume, i->sink->sample_spec.channels);
+    else
+        *volume = i->thread_info.volume;
 
-    return ret;
+    return 0;
 }
 
 /* Called from thread context */
-void pa_sink_input_drop(pa_sink_input *i, size_t length) {
+void pa_sink_input_drop(pa_sink_input *i, size_t nbytes /* in sink sample spec */) {
     pa_sink_input_assert_ref(i);
-    pa_assert(PA_SINK_INPUT_LINKED(i->thread_info.state));
-    pa_assert(pa_frame_aligned(length, &i->sink->sample_spec));
-    pa_assert(length > 0);
 
-    if (!i->peek || !i->drop || i->thread_info.state == PA_SINK_INPUT_CORKED)
-        return;
+    pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state));
+    pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec));
+    pa_assert(nbytes > 0);
 
-    if (i->thread_info.move_silence > 0) {
+/*     pa_log_debug("dropping %lu", (unsigned long) nbytes); */
 
-        if (i->thread_info.move_silence >= length) {
-            i->thread_info.move_silence -= length;
-            length = 0;
-        } else {
-            length -= i->thread_info.move_silence;
-            i->thread_info.move_silence = 0;
-        }
+    /* If there's still some rewrite request the handle, but the sink
+    didn't do this for us, we do it here. However, since the sink
+    apparently doesn't support rewinding, we pass 0 here. This still
+    allows rewinding through the render buffer. */
+    pa_sink_input_process_rewind(i, 0);
 
-        if (i->thread_info.move_silence <= 0) {
-            if (i->thread_info.silence_memblock) {
-                pa_memblock_unref(i->thread_info.silence_memblock);
-                i->thread_info.silence_memblock = NULL;
-            }
-        }
+    pa_memblockq_drop(i->thread_info.render_memblockq, nbytes);
+}
+
+/* Called from thread context */
+void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in sink sample spec */) {
+    size_t lbq;
+    pa_sink_input_assert_ref(i);
+
+    pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state));
+    pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec));
+
+/*     pa_log_debug("rewind(%lu, %lu)", (unsigned long) nbytes, (unsigned long) i->thread_info.rewrite_nbytes); */
 
-        if (length <= 0)
-            return;
+    lbq = pa_memblockq_get_length(i->thread_info.render_memblockq);
+
+    if (nbytes > 0) {
+        pa_log_debug("Have to rewind %lu bytes on render memblockq.", (unsigned long) nbytes);
+        pa_memblockq_rewind(i->thread_info.render_memblockq, nbytes);
     }
 
-    if (i->thread_info.resampled_chunk.memblock) {
-        size_t l = length;
+    if (i->thread_info.rewrite_nbytes == (size_t) -1) {
 
-        if (l > i->thread_info.resampled_chunk.length)
-            l = i->thread_info.resampled_chunk.length;
+        /* We were asked to drop all buffered data, and rerequest new
+         * data from implementor the next time push() is called */
 
-        i->thread_info.resampled_chunk.index += l;
-        i->thread_info.resampled_chunk.length -= l;
+        pa_memblockq_flush(i->thread_info.render_memblockq);
 
-        if (i->thread_info.resampled_chunk.length <= 0) {
-            pa_memblock_unref(i->thread_info.resampled_chunk.memblock);
-            pa_memchunk_reset(&i->thread_info.resampled_chunk);
-        }
+    } else if (i->thread_info.rewrite_nbytes > 0) {
+        size_t max_rewrite, amount;
+
+        /* Calculate how much make sense to rewrite at most */
+        max_rewrite = nbytes + lbq;
+
+        /* Transform into local domain */
+        if (i->thread_info.resampler)
+            max_rewrite = pa_resampler_request(i->thread_info.resampler, max_rewrite);
+
+        /* Calculate how much of the rewinded data should actually be rewritten */
+        amount = PA_MIN(i->thread_info.rewrite_nbytes, max_rewrite);
+
+        if (amount > 0) {
+            pa_log_debug("Have to rewind %lu bytes on implementor.", (unsigned long) amount);
 
-        length -= l;
+            /* Tell the implementor */
+            if (i->process_rewind)
+                i->process_rewind(i, amount);
+
+            /* Convert back to to sink domain */
+            if (i->thread_info.resampler)
+                amount = pa_resampler_result(i->thread_info.resampler, amount);
+
+            if (amount > 0)
+                /* Ok, now update the write pointer */
+                pa_memblockq_seek(i->thread_info.render_memblockq, - ((int64_t) amount), PA_SEEK_RELATIVE);
+
+            if (i->thread_info.rewrite_flush)
+                pa_memblockq_silence(i->thread_info.render_memblockq);
+
+            /* And reset the resampler */
+            if (i->thread_info.resampler)
+                pa_resampler_reset(i->thread_info.resampler);
+        }
     }
 
-    if (length > 0) {
+    i->thread_info.rewrite_nbytes = 0;
+    i->thread_info.rewrite_flush = FALSE;
+}
+
+/* Called from thread context */
+void pa_sink_input_update_max_rewind(pa_sink_input *i, size_t nbytes  /* in the sink's sample spec */) {
+    pa_sink_input_assert_ref(i);
+    pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state));
+    pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec));
 
-        if (i->thread_info.resampler) {
-            /* So, we have a resampler. To avoid discontinuities we
-             * have to actually read all data that could be read and
-             * pass it through the resampler. */
+    pa_memblockq_set_maxrewind(i->thread_info.render_memblockq, nbytes);
 
-            while (length > 0) {
-                pa_memchunk chunk;
-                pa_cvolume volume;
+    if (i->update_max_rewind)
+        i->update_max_rewind(i, i->thread_info.resampler ? pa_resampler_request(i->thread_info.resampler, nbytes) : nbytes);
+}
 
-                if (pa_sink_input_peek(i, length, &chunk, &volume) >= 0) {
-                    size_t l;
+static pa_usec_t fixup_latency(pa_sink *s, pa_usec_t usec) {
+    pa_sink_assert_ref(s);
 
-                    pa_memblock_unref(chunk.memblock);
+    if (usec == (pa_usec_t) -1)
+        return usec;
 
-                    l = chunk.length;
-                    if (l > length)
-                        l = length;
+    if (s->max_latency > 0 && usec > s->max_latency)
+        usec = s->max_latency;
 
-                    pa_sink_input_drop(i, l);
-                    length -= l;
+    if (s->min_latency > 0 && usec < s->min_latency)
+        usec = s->min_latency;
 
-                } else {
-                    size_t l;
+    return usec;
+}
 
-                    l = pa_resampler_request(i->thread_info.resampler, length);
+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);
 
-                    /* Hmmm, peeking failed, so let's at least drop
-                     * the right amount of data */
+    usec = fixup_latency(i->sink, usec);
 
-                    if (l > 0)
-                        if (i->drop)
-                            i->drop(i, l);
+    i->thread_info.requested_sink_latency = usec;
+    pa_sink_invalidate_requested_latency(i->sink);
 
-                    break;
-                }
-            }
+    return usec;
+}
 
-        } else {
+pa_usec_t pa_sink_input_set_requested_latency(pa_sink_input *i, pa_usec_t usec) {
+    pa_sink_input_assert_ref(i);
 
-            /* We have no resampler, hence let's just drop the data */
+    usec = fixup_latency(i->sink, usec);
 
-            if (i->drop)
-                i->drop(i, length);
-        }
+    if (PA_SINK_INPUT_IS_LINKED(i->state))
+        pa_asyncmsgq_post(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_REQUESTED_LATENCY, NULL, (int64_t) usec, NULL, NULL);
+    else {
+        /* If this sink input is not realized yet, we have to touch
+         * the thread info data directly */
+        i->thread_info.requested_sink_latency = usec;
+        i->sink->thread_info.requested_latency_valid = FALSE;
     }
+
+    return usec;
+}
+
+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))
+        pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_GET_REQUESTED_LATENCY, &usec, 0, NULL);
+    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;
 }
 
 void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume) {
     pa_sink_input_assert_ref(i);
-    pa_assert(PA_SINK_INPUT_LINKED(i->state));
+    pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
 
     if (pa_cvolume_equal(&i->volume, volume))
         return;
@@ -657,7 +757,7 @@ void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume) {
 
 const pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i) {
     pa_sink_input_assert_ref(i);
-    pa_assert(PA_SINK_INPUT_LINKED(i->state));
+    pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
 
     return &i->volume;
 }
@@ -665,7 +765,7 @@ const pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i) {
 void pa_sink_input_set_mute(pa_sink_input *i, pa_bool_t mute) {
     pa_assert(i);
     pa_sink_input_assert_ref(i);
-    pa_assert(PA_SINK_INPUT_LINKED(i->state));
+    pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
 
     if (!i->muted == !mute)
         return;
@@ -678,21 +778,21 @@ void pa_sink_input_set_mute(pa_sink_input *i, pa_bool_t mute) {
 
 int pa_sink_input_get_mute(pa_sink_input *i) {
     pa_sink_input_assert_ref(i);
-    pa_assert(PA_SINK_INPUT_LINKED(i->state));
+    pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
 
     return !!i->muted;
 }
 
 void pa_sink_input_cork(pa_sink_input *i, pa_bool_t b) {
     pa_sink_input_assert_ref(i);
-    pa_assert(PA_SINK_INPUT_LINKED(i->state));
+    pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
 
     sink_input_set_state(i, b ? PA_SINK_INPUT_CORKED : PA_SINK_INPUT_RUNNING);
 }
 
 int pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate) {
     pa_sink_input_assert_ref(i);
-    pa_assert(PA_SINK_INPUT_LINKED(i->state));
+    pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
     pa_return_val_if_fail(i->thread_info.resampler, -1);
 
     if (i->sample_spec.rate == rate)
@@ -707,19 +807,24 @@ int pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate) {
 }
 
 void pa_sink_input_set_name(pa_sink_input *i, const char *name) {
+    const char *old;
     pa_sink_input_assert_ref(i);
 
-    if (!i->name && !name)
+    if (!name && !pa_proplist_contains(i->proplist, PA_PROP_MEDIA_NAME))
         return;
 
-    if (i->name && name && !strcmp(i->name, name))
+    old = pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME);
+
+    if (old && name && !strcmp(old, name))
         return;
 
-    pa_xfree(i->name);
-    i->name = pa_xstrdup(name);
+    if (name)
+        pa_proplist_sets(i->proplist, PA_PROP_MEDIA_NAME, name);
+    else
+        pa_proplist_unset(i->proplist, PA_PROP_MEDIA_NAME);
 
-    if (PA_SINK_INPUT_LINKED(i->state)) {
-        pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_NAME_CHANGED], i);
+    if (PA_SINK_INPUT_IS_LINKED(i->state)) {
+        pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED], i);
         pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
     }
 }
@@ -730,15 +835,13 @@ pa_resample_method_t pa_sink_input_get_resample_method(pa_sink_input *i) {
     return i->resample_method;
 }
 
-int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, int immediately) {
+int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest) {
     pa_resampler *new_resampler;
     pa_sink *origin;
-    pa_usec_t silence_usec = 0;
-    pa_sink_input_move_info info;
     pa_sink_input_move_hook_data hook_data;
 
     pa_sink_input_assert_ref(i);
-    pa_assert(PA_SINK_INPUT_LINKED(i->state));
+    pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
     pa_sink_assert_ref(dest);
 
     origin = i->sink;
@@ -790,71 +893,7 @@ int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, int immediately) {
     hook_data.destination = dest;
     pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE], &hook_data);
 
-    memset(&info, 0, sizeof(info));
-    info.sink_input = i;
-
-    if (!immediately) {
-        pa_usec_t old_latency, new_latency;
-
-        /* Let's do a little bit of Voodoo for compensating latency
-         * differences. We assume that the accuracy for our
-         * estimations is still good enough, even though we do these
-         * operations non-atomic. */
-
-        old_latency = pa_sink_get_latency(origin);
-        new_latency = pa_sink_get_latency(dest);
-
-        /* The already resampled data should go to the old sink */
-
-        if (old_latency >= new_latency) {
-
-            /* The latency of the old sink is larger than the latency
-             * of the new sink. Therefore to compensate for the
-             * difference we to play silence on the new one for a
-             * while */
-
-            silence_usec = old_latency - new_latency;
-
-        } else {
-
-            /* The latency of new sink is larger than the latency of
-             * the old sink. Therefore we have to precompute a little
-             * and make sure that this is still played on the old
-             * sink, until we can play the first sample on the new
-             * sink.*/
-
-            info.buffer_bytes = pa_usec_to_bytes(new_latency - old_latency, &origin->sample_spec);
-        }
-
-        /* Okey, let's move it */
-
-        if (info.buffer_bytes > 0) {
-
-            info.ghost_sink_input = pa_memblockq_sink_input_new(
-                    origin,
-                    "Ghost Stream",
-                    &origin->sample_spec,
-                    &origin->channel_map,
-                    NULL,
-                    NULL);
-
-            info.ghost_sink_input->thread_info.state = info.ghost_sink_input->state = PA_SINK_INPUT_RUNNING;
-            info.ghost_sink_input->thread_info.volume = info.ghost_sink_input->volume;
-            info.ghost_sink_input->thread_info.muted = info.ghost_sink_input->muted;
-
-            info.buffer = pa_memblockq_new(0, MOVE_BUFFER_LENGTH, 0, pa_frame_size(&origin->sample_spec), 0, 0, NULL);
-        }
-    }
-
-    pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_REMOVE_INPUT_AND_BUFFER, &info, 0, NULL);
-
-    if (info.ghost_sink_input) {
-        /* Basically, do what pa_sink_input_put() does ...*/
-
-        pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW, info.ghost_sink_input->index);
-        pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], info.ghost_sink_input);
-        pa_sink_input_unref(info.ghost_sink_input);
-    }
+    pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_START_MOVE, i, 0, NULL);
 
     pa_idxset_remove_by_data(origin->inputs, i, NULL);
     pa_idxset_put(dest->inputs, i, NULL);
@@ -865,42 +904,31 @@ int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, int immediately) {
         dest->n_corked++;
     }
 
-    /* Replace resampler */
+    /* Replace resampler and render queue */
     if (new_resampler != i->thread_info.resampler) {
+
         if (i->thread_info.resampler)
             pa_resampler_free(i->thread_info.resampler);
         i->thread_info.resampler = new_resampler;
 
-        /* if the resampler changed, the silence memblock is
-         * probably invalid now, too */
-        if (i->thread_info.silence_memblock) {
-            pa_memblock_unref(i->thread_info.silence_memblock);
-            i->thread_info.silence_memblock = NULL;
-        }
+        pa_memblockq_free(i->thread_info.render_memblockq);
+
+        i->thread_info.render_memblockq = pa_memblockq_new(
+                0,
+                MEMBLOCKQ_MAXLENGTH,
+                0,
+                pa_frame_size(&i->sink->sample_spec),
+                0,
+                1,
+                0,
+                &i->sink->silence);
     }
 
-    /* Dump already resampled data */
-    if (i->thread_info.resampled_chunk.memblock) {
-        /* Hmm, this data has already been added to the ghost queue, presumably, hence let's sleep a little bit longer */
-        silence_usec += pa_bytes_to_usec(i->thread_info.resampled_chunk.length, &origin->sample_spec);
-        pa_memblock_unref(i->thread_info.resampled_chunk.memblock);
-        pa_memchunk_reset(&i->thread_info.resampled_chunk);
-    }
-
-    /* Calculate the new sleeping time */
-    if (immediately)
-        i->thread_info.move_silence = 0;
-    else
-        i->thread_info.move_silence = pa_usec_to_bytes(
-                pa_bytes_to_usec(i->thread_info.move_silence, &origin->sample_spec) +
-                silence_usec,
-                &dest->sample_spec);
-
-    pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_ADD_INPUT, i, 0, NULL);
-
     pa_sink_update_status(origin);
     pa_sink_update_status(dest);
 
+    pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_FINISH_MOVE, i, 0, NULL);
+
     if (i->moved)
         i->moved(i);
 
@@ -914,31 +942,57 @@ int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, int immediately) {
     return 0;
 }
 
+void pa_sink_input_set_state_within_thread(pa_sink_input *i, pa_sink_input_state_t state) {
+    pa_sink_input_assert_ref(i);
+
+    if (state == i->thread_info.state)
+        return;
+
+    if ((state == PA_SINK_INPUT_DRAINED || state == PA_SINK_INPUT_RUNNING) &&
+        !(i->thread_info.state == PA_SINK_INPUT_DRAINED || i->thread_info.state != PA_SINK_INPUT_RUNNING))
+        pa_atomic_store(&i->thread_info.drained, 1);
+
+    if (state == PA_SINK_INPUT_CORKED && i->thread_info.state != PA_SINK_INPUT_CORKED) {
+
+        /* This will tell the implementing sink input driver to rewind
+         * so that the unplayed already mixed data is not lost */
+        pa_sink_input_request_rewind(i, 0, TRUE, TRUE);
+
+    } else if (i->thread_info.state == PA_SINK_INPUT_CORKED && state != PA_SINK_INPUT_CORKED) {
+
+        /* OK, we're being uncorked. Make sure we're not rewound when
+         * the hw buffer is remixed and request a remix. */
+        pa_sink_input_request_rewind(i, 0, FALSE, TRUE);
+    }
+
+    if (i->state_change)
+        i->state_change(i, state);
+
+    i->thread_info.state = state;
+}
+
 /* Called from thread context */
 int pa_sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) {
     pa_sink_input *i = PA_SINK_INPUT(o);
 
     pa_sink_input_assert_ref(i);
-    pa_assert(PA_SINK_INPUT_LINKED(i->thread_info.state));
+    pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state));
 
     switch (code) {
         case PA_SINK_INPUT_MESSAGE_SET_VOLUME:
             i->thread_info.volume = *((pa_cvolume*) userdata);
+            pa_sink_input_request_rewind(i, 0, TRUE, FALSE);
             return 0;
 
         case PA_SINK_INPUT_MESSAGE_SET_MUTE:
             i->thread_info.muted = PA_PTR_TO_UINT(userdata);
+            pa_sink_input_request_rewind(i, 0, TRUE, FALSE);
             return 0;
 
         case PA_SINK_INPUT_MESSAGE_GET_LATENCY: {
             pa_usec_t *r = userdata;
 
-            if (i->thread_info.resampled_chunk.memblock)
-                *r += pa_bytes_to_usec(i->thread_info.resampled_chunk.length, &i->sink->sample_spec);
-
-            if (i->thread_info.move_silence)
-                *r += pa_bytes_to_usec(i->thread_info.move_silence, &i->sink->sample_spec);
-
+            *r += pa_bytes_to_usec(pa_memblockq_get_length(i->thread_info.render_memblockq), &i->sink->sample_spec);
             return 0;
         }
 
@@ -952,26 +1006,26 @@ int pa_sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t
         case PA_SINK_INPUT_MESSAGE_SET_STATE: {
             pa_sink_input *ssync;
 
-            if ((PA_PTR_TO_UINT(userdata) == PA_SINK_INPUT_DRAINED || PA_PTR_TO_UINT(userdata) == PA_SINK_INPUT_RUNNING) &&
-                (i->thread_info.state != PA_SINK_INPUT_DRAINED) && (i->thread_info.state != PA_SINK_INPUT_RUNNING))
-                pa_atomic_store(&i->thread_info.drained, 1);
+            pa_sink_input_set_state_within_thread(i, PA_PTR_TO_UINT(userdata));
 
-            i->thread_info.state = PA_PTR_TO_UINT(userdata);
+            for (ssync = i->thread_info.sync_prev; ssync; ssync = ssync->thread_info.sync_prev)
+                pa_sink_input_set_state_within_thread(ssync, PA_PTR_TO_UINT(userdata));
 
-            for (ssync = i->thread_info.sync_prev; ssync; ssync = ssync->thread_info.sync_prev) {
-                if ((PA_PTR_TO_UINT(userdata) == PA_SINK_INPUT_DRAINED || PA_PTR_TO_UINT(userdata) == PA_SINK_INPUT_RUNNING) &&
-                    (ssync->thread_info.state != PA_SINK_INPUT_DRAINED) && (ssync->thread_info.state != PA_SINK_INPUT_RUNNING))
-                    pa_atomic_store(&ssync->thread_info.drained, 1);
-                ssync->thread_info.state = PA_PTR_TO_UINT(userdata);
-            }
+            for (ssync = i->thread_info.sync_next; ssync; ssync = ssync->thread_info.sync_next)
+                pa_sink_input_set_state_within_thread(ssync, PA_PTR_TO_UINT(userdata));
 
-            for (ssync = i->thread_info.sync_next; ssync; ssync = ssync->thread_info.sync_next) {
-                if ((PA_PTR_TO_UINT(userdata) == PA_SINK_INPUT_DRAINED || PA_PTR_TO_UINT(userdata) == PA_SINK_INPUT_RUNNING) &&
-                    (ssync->thread_info.state != PA_SINK_INPUT_DRAINED) && (ssync->thread_info.state != PA_SINK_INPUT_RUNNING))
-                    pa_atomic_store(&ssync->thread_info.drained, 1);
-                ssync->thread_info.state = PA_PTR_TO_UINT(userdata);
-            }
+            return 0;
+        }
+
+        case PA_SINK_INPUT_MESSAGE_SET_REQUESTED_LATENCY:
+
+            pa_sink_input_set_requested_latency_within_thread(i, (pa_usec_t) offset);
+            return 0;
+
+        case PA_SINK_INPUT_MESSAGE_GET_REQUESTED_LATENCY: {
+            pa_usec_t *r = userdata;
 
+            *r = i->thread_info.requested_sink_latency;
             return 0;
         }
     }
@@ -987,3 +1041,86 @@ pa_sink_input_state_t pa_sink_input_get_state(pa_sink_input *i) {
 
     return i->state;
 }
+
+/* Called from IO context */
+pa_bool_t pa_sink_input_safe_to_remove(pa_sink_input *i) {
+    pa_sink_input_assert_ref(i);
+
+    if (PA_SINK_INPUT_IS_LINKED(i->thread_info.state))
+        return pa_memblockq_is_empty(i->thread_info.render_memblockq);
+
+    return TRUE;
+}
+
+void pa_sink_input_request_rewind(pa_sink_input *i, size_t nbytes  /* in our sample spec */, pa_bool_t rewrite, pa_bool_t flush) {
+    size_t lbq;
+
+    /* If 'rewrite' is TRUE the sink is rewound as far as requested
+     * and possible and the exact value of this is passed back the
+     * implementor via process_rewind(). If 'flush' is also TRUE all
+     * already rendered data is also dropped.
+     *
+     * If 'rewrite' is FALSE the sink is rewound as far as requested
+     * and possible and the already rendered data is dropped so that
+     * in the next iteration we read new data from the
+     * implementor. This implies 'flush' is TRUE. */
+
+    pa_sink_input_assert_ref(i);
+    pa_assert(i->thread_info.rewrite_nbytes == 0);
+
+    /* We don't take rewind requests while we are corked */
+    if (i->thread_info.state == PA_SINK_INPUT_CORKED)
+        return;
+
+    pa_assert(rewrite || flush);
+
+    /* Calculate how much we can rewind locally without having to
+     * touch the sink */
+    if (rewrite)
+        lbq = pa_memblockq_get_length(i->thread_info.render_memblockq);
+    else
+        lbq = 0;
+
+    /* Check if rewinding for the maximum is requested, and if so, fix up */
+    if (nbytes <= 0) {
+
+        /* Calculate maximum number of bytes that could be rewound in theory */
+        nbytes = i->sink->thread_info.max_rewind + lbq;
+
+        /* Transform from sink domain */
+        if (i->thread_info.resampler)
+            nbytes = pa_resampler_request(i->thread_info.resampler, nbytes);
+    }
+
+    if (rewrite) {
+        /* Make sure to not overwrite over underruns */
+        if (nbytes > i->thread_info.playing_for)
+            nbytes = (size_t) i->thread_info.playing_for;
+
+        i->thread_info.rewrite_nbytes = nbytes;
+    } else
+        i->thread_info.rewrite_nbytes = (size_t) -1;
+
+    i->thread_info.rewrite_flush = flush && i->thread_info.rewrite_nbytes != 0;
+
+    /* Transform to sink domain */
+    if (i->thread_info.resampler)
+        nbytes = pa_resampler_result(i->thread_info.resampler, nbytes);
+
+    if (nbytes > lbq)
+        pa_sink_request_rewind(i->sink, nbytes - lbq);
+}
+
+pa_memchunk* pa_sink_input_get_silence(pa_sink_input *i, pa_memchunk *ret) {
+    pa_sink_input_assert_ref(i);
+    pa_assert(ret);
+
+    pa_silence_memchunk_get(
+                &i->sink->core->silence_cache,
+                i->sink->core->mempool,
+                ret,
+                &i->sample_spec,
+                i->thread_info.resampler ? pa_resampler_max_block_size(i->thread_info.resampler) : 0);
+
+    return ret;
+}
index 8975db9..5f14612 100644 (file)
@@ -46,7 +46,7 @@ typedef enum pa_sink_input_state {
     PA_SINK_INPUT_UNLINKED      /*< The stream is dead */
 } pa_sink_input_state_t;
 
-static inline pa_bool_t PA_SINK_INPUT_LINKED(pa_sink_input_state_t x) {
+static inline pa_bool_t PA_SINK_INPUT_IS_LINKED(pa_sink_input_state_t x) {
     return x == PA_SINK_INPUT_DRAINED || x == PA_SINK_INPUT_RUNNING || x == PA_SINK_INPUT_CORKED;
 }
 
@@ -73,7 +73,8 @@ struct pa_sink_input {
     pa_sink_input_state_t state;
     pa_sink_input_flags_t flags;
 
-    char *name, *driver;                /* may be NULL */
+    pa_proplist *proplist;
+    char *driver;                       /* may be NULL */
     pa_module *module;                  /* may be NULL */
     pa_client *client;                  /* may be NULL */
 
@@ -87,17 +88,26 @@ struct pa_sink_input {
     pa_cvolume volume;
     pa_bool_t muted;
 
-    /* Returns the chunk of audio data (but doesn't drop it
-     * yet!). Returns -1 on failure. Called from IO thread context. If
-     * data needs to be generated from scratch then please in the
-     * specified length. This is an optimization only. If less data is
-     * available, it's fine to return a smaller block. If more data is
-     * already ready, it is better to return the full block.*/
-    int (*peek) (pa_sink_input *i, size_t length, pa_memchunk *chunk);
+    pa_resample_method_t resample_method;
 
-    /* Drops the specified number of bytes, usually called right after
-     * peek(), but not necessarily. Called from IO thread context. */
-    void (*drop) (pa_sink_input *i, size_t length);
+    /* Returns the chunk of audio data and drops it from the
+     * queue. Returns -1 on failure. Called from IO thread context. If
+     * data needs to be generated from scratch then please in the
+     * specified length request_nbytes. This is an optimization
+     * only. If less data is available, it's fine to return a smaller
+     * block. If more data is already ready, it is better to return
+     * the full block. */
+    int (*pop) (pa_sink_input *i, size_t request_nbytes, pa_memchunk *chunk); /* may NOT be NULL */
+
+    /* Rewind the queue by the specified number of bytes. Called just
+     * before peek() if it is called at all. Only called if the sink
+     * input driver ever plans to call
+     * pa_sink_input_request_rewind(). Called from IO context. */
+    void (*process_rewind) (pa_sink_input *i, size_t nbytes);     /* may NOT be NULL */
+
+    /* Called whenever the maximum rewindable size of the sink
+     * changes. Called from IO context. */
+    void (*update_max_rewind) (pa_sink_input *i, size_t nbytes); /* may be NULL */
 
     /* If non-NULL this function is called when the input is first
      * connected to a sink or when the rtpoll/asyncmsgq fields
@@ -128,7 +138,9 @@ struct pa_sink_input {
     instead. */
     pa_usec_t (*get_latency) (pa_sink_input *i); /* may be NULL */
 
-    pa_resample_method_t resample_method;
+    /* If non_NULL this function is called from thread context if the
+     * state changes. The old state is found in thread_info.state.  */
+    void (*state_change) (pa_sink_input *i, pa_sink_input_state_t state); /* may be NULL */
 
     struct {
         pa_sink_input_state_t state;
@@ -138,19 +150,22 @@ struct pa_sink_input {
 
         pa_sample_spec sample_spec;
 
-        pa_memchunk resampled_chunk;
         pa_resampler *resampler;                     /* may be NULL */
 
-        /* Some silence to play before the actual data. This is used to
-         * compensate for latency differences when moving a sink input
-         * "hot" between sinks. */
-        size_t move_silence;
-        pa_memblock *silence_memblock;               /* may be NULL */
+        /* We maintain a history of resampled audio data here. */
+        pa_memblockq *render_memblockq;
+
+        size_t rewrite_nbytes;
+        pa_bool_t rewrite_flush;
+        uint64_t underrun_for, playing_for;
 
         pa_sink_input *sync_prev, *sync_next;
 
         pa_cvolume volume;
         pa_bool_t muted;
+
+        /* The requested latency for the sink */
+        pa_usec_t requested_sink_latency;
     } thread_info;
 
     void *userdata;
@@ -165,11 +180,15 @@ enum {
     PA_SINK_INPUT_MESSAGE_GET_LATENCY,
     PA_SINK_INPUT_MESSAGE_SET_RATE,
     PA_SINK_INPUT_MESSAGE_SET_STATE,
+    PA_SINK_INPUT_MESSAGE_SET_REQUESTED_LATENCY,
+    PA_SINK_INPUT_MESSAGE_GET_REQUESTED_LATENCY,
     PA_SINK_INPUT_MESSAGE_MAX
 };
 
 typedef struct pa_sink_input_new_data {
-    const char *name, *driver;
+    pa_proplist *proplist;
+
+    const char *driver;
     pa_module *module;
     pa_client *client;
 
@@ -190,16 +209,17 @@ typedef struct pa_sink_input_new_data {
     pa_sink_input *sync_base;
 } pa_sink_input_new_data;
 
-typedef struct pa_sink_input_move_hook_data {
-    pa_sink_input *sink_input;
-    pa_sink *destination;
-} pa_sink_input_move_hook_data;
-
 pa_sink_input_new_data* pa_sink_input_new_data_init(pa_sink_input_new_data *data);
 void pa_sink_input_new_data_set_sample_spec(pa_sink_input_new_data *data, const pa_sample_spec *spec);
 void pa_sink_input_new_data_set_channel_map(pa_sink_input_new_data *data, const pa_channel_map *map);
 void pa_sink_input_new_data_set_volume(pa_sink_input_new_data *data, const pa_cvolume *volume);
 void pa_sink_input_new_data_set_muted(pa_sink_input_new_data *data, pa_bool_t mute);
+void pa_sink_input_new_data_done(pa_sink_input_new_data *data);
+
+typedef struct pa_sink_input_move_hook_data {
+    pa_sink_input *sink_input;
+    pa_sink *destination;
+} pa_sink_input_move_hook_data;
 
 /* To be called by the implementing module only */
 
@@ -213,7 +233,22 @@ void pa_sink_input_unlink(pa_sink_input* i);
 
 void pa_sink_input_set_name(pa_sink_input *i, const char *name);
 
-/* Callable by everyone */
+pa_usec_t pa_sink_input_set_requested_latency(pa_sink_input *i, pa_usec_t usec);
+
+/* Request that the specified number of bytes already written out to
+the hw device is rewritten, if possible.  Please note that this is
+only a kind request. The sink driver may not be able to fulfill it
+fully -- or at all. If the request for a rewrite was successful, the
+sink driver will call ->rewind() and pass the number of bytes that
+could be rewound in the HW device. This functionality is required for
+implementing the "zero latency" write-through functionality. */
+void pa_sink_input_request_rewind(pa_sink_input *i, size_t nbytes, pa_bool_t rewrite, pa_bool_t flush);
+
+void pa_sink_input_cork(pa_sink_input *i, pa_bool_t b);
+
+int pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate);
+
+/* Callable by everyone from main thread*/
 
 /* External code may request disconnection with this function */
 void pa_sink_input_kill(pa_sink_input*i);
@@ -225,27 +260,29 @@ const pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i);
 void pa_sink_input_set_mute(pa_sink_input *i, pa_bool_t mute);
 int pa_sink_input_get_mute(pa_sink_input *i);
 
-void pa_sink_input_cork(pa_sink_input *i, pa_bool_t b);
-
-int pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate);
-
 pa_resample_method_t pa_sink_input_get_resample_method(pa_sink_input *i);
 
-int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, int immediately);
+int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest);
 
 pa_sink_input_state_t pa_sink_input_get_state(pa_sink_input *i);
 
-/* To be used exclusively by the sink driver thread */
+pa_usec_t pa_sink_input_get_requested_latency(pa_sink_input *i);
+
+/* To be used exclusively by the sink driver IO thread */
 
 int pa_sink_input_peek(pa_sink_input *i, size_t length, pa_memchunk *chunk, pa_cvolume *volume);
 void pa_sink_input_drop(pa_sink_input *i, size_t length);
+void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in the sink's sample spec */);
+void pa_sink_input_update_max_rewind(pa_sink_input *i, size_t nbytes  /* in the sink's sample spec */);
+
+void pa_sink_input_set_state_within_thread(pa_sink_input *i, pa_sink_input_state_t state);
+
 int pa_sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk);
 
-typedef struct pa_sink_input_move_info {
-    pa_sink_input *sink_input;
-    pa_sink_input *ghost_sink_input;
-    pa_memblockq *buffer;
-    size_t buffer_bytes;
-} pa_sink_input_move_info;
+pa_usec_t pa_sink_input_set_requested_latency_within_thread(pa_sink_input *i, pa_usec_t usec);
+
+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);
 
 #endif
index 9adb609..31c3cfc 100644 (file)
@@ -33,6 +33,7 @@
 #include <pulse/introspect.h>
 #include <pulse/utf8.h>
 #include <pulse/xmalloc.h>
+#include <pulse/timeval.h>
 
 #include <pulsecore/sink-input.h>
 #include <pulsecore/namereg.h>
 
 #define MAX_MIX_CHANNELS 32
 #define MIX_BUFFER_LENGTH (PA_PAGE_SIZE)
-#define SILENCE_BUFFER_LENGTH (PA_PAGE_SIZE*12)
+#define DEFAULT_MIN_LATENCY (4*PA_USEC_PER_MSEC)
 
 static PA_DEFINE_CHECK_TYPE(pa_sink, pa_msgobject);
 
 static void sink_free(pa_object *s);
 
+pa_sink_new_data* pa_sink_new_data_init(pa_sink_new_data *data) {
+    pa_assert(data);
+
+    memset(data, 0, sizeof(*data));
+    data->proplist = pa_proplist_new();
+
+    return data;
+}
+
+void pa_sink_new_data_set_name(pa_sink_new_data *data, const char *name) {
+    pa_assert(data);
+
+    pa_xfree(data->name);
+    data->name = pa_xstrdup(name);
+}
+
+void pa_sink_new_data_set_sample_spec(pa_sink_new_data *data, const pa_sample_spec *spec) {
+    pa_assert(data);
+
+    if ((data->sample_spec_is_set = !!spec))
+        data->sample_spec = *spec;
+}
+
+void pa_sink_new_data_set_channel_map(pa_sink_new_data *data, const pa_channel_map *map) {
+    pa_assert(data);
+
+    if ((data->channel_map_is_set = !!map))
+        data->channel_map = *map;
+}
+
+void pa_sink_new_data_set_volume(pa_sink_new_data *data, const pa_cvolume *volume) {
+    pa_assert(data);
+
+    if ((data->volume_is_set = !!volume))
+        data->volume = *volume;
+}
+
+void pa_sink_new_data_set_muted(pa_sink_new_data *data, pa_bool_t mute) {
+    pa_assert(data);
+
+    data->muted_is_set = TRUE;
+    data->muted = !!mute;
+}
+
+void pa_sink_new_data_done(pa_sink_new_data *data) {
+    pa_assert(data);
+
+    pa_xfree(data->name);
+    pa_proplist_free(data->proplist);
+}
+
+static void reset_callbacks(pa_sink *s) {
+    pa_assert(s);
+
+    s->set_state = NULL;
+    s->get_volume = NULL;
+    s->set_volume = NULL;
+    s->get_mute = NULL;
+    s->set_mute = NULL;
+    s->request_rewind = NULL;
+    s->update_requested_latency = NULL;
+}
+
 pa_sink* pa_sink_new(
         pa_core *core,
-        const char *driver,
-        const char *name,
-        int fail,
-        const pa_sample_spec *spec,
-        const pa_channel_map *map) {
+        pa_sink_new_data *data,
+        pa_sink_flags_t flags) {
 
     pa_sink *s;
-    char *n = NULL;
-    char st[256];
-    pa_channel_map tmap;
+    const char *name;
+    char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+    pa_source_new_data source_data;
+    const char *dn;
 
     pa_assert(core);
-    pa_assert(name);
-    pa_assert(spec);
+    pa_assert(data);
+    pa_assert(data->name);
+
+    s = pa_msgobject_new(pa_sink);
 
-    pa_return_null_if_fail(pa_sample_spec_valid(spec));
+    if (!(name = pa_namereg_register(core, data->name, PA_NAMEREG_SINK, s, data->namereg_fail))) {
+        pa_xfree(s);
+        return NULL;
+    }
 
-    if (!map)
-        pa_return_null_if_fail((map = pa_channel_map_init_auto(&tmap, spec->channels, PA_CHANNEL_MAP_DEFAULT)));
+    pa_sink_new_data_set_name(data, name);
 
-    pa_return_null_if_fail(map && pa_channel_map_valid(map));
-    pa_return_null_if_fail(map->channels == spec->channels);
-    pa_return_null_if_fail(!driver || pa_utf8_valid(driver));
-    pa_return_null_if_fail(name && pa_utf8_valid(name) && *name);
+    if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_NEW], data) < 0) {
+        pa_xfree(s);
+        pa_namereg_unregister(core, name);
+        return NULL;
+    }
 
-    s = pa_msgobject_new(pa_sink);
+    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]);
 
-    if (!(name = pa_namereg_register(core, name, PA_NAMEREG_SINK, s, fail))) {
+    pa_return_null_if_fail(data->sample_spec_is_set && pa_sample_spec_valid(&data->sample_spec));
+
+    if (!data->channel_map_is_set)
+        pa_return_null_if_fail(pa_channel_map_init_auto(&data->channel_map, data->sample_spec.channels, PA_CHANNEL_MAP_DEFAULT));
+
+    pa_return_null_if_fail(pa_channel_map_valid(&data->channel_map));
+    pa_return_null_if_fail(data->channel_map.channels == data->sample_spec.channels);
+
+    if (!data->volume_is_set)
+        pa_cvolume_reset(&data->volume, data->sample_spec.channels);
+
+    pa_return_null_if_fail(pa_cvolume_valid(&data->volume));
+    pa_return_null_if_fail(data->volume.channels == data->sample_spec.channels);
+
+    if (!data->muted_is_set)
+        data->muted = FALSE;
+
+    if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_FIXATE], data) < 0) {
         pa_xfree(s);
+        pa_namereg_unregister(core, name);
         return NULL;
     }
 
@@ -92,57 +178,78 @@ pa_sink* pa_sink_new(
 
     s->core = core;
     s->state = PA_SINK_INIT;
-    s->flags = 0;
+    s->flags = flags;
     s->name = pa_xstrdup(name);
-    s->description = NULL;
-    s->driver = pa_xstrdup(driver);
-    s->module = NULL;
+    s->proplist = pa_proplist_copy(data->proplist);
+    s->driver = pa_xstrdup(data->driver);
+    s->module = data->module;
 
-    s->sample_spec = *spec;
-    s->channel_map = *map;
+    s->sample_spec = data->sample_spec;
+    s->channel_map = data->channel_map;
 
     s->inputs = pa_idxset_new(NULL, NULL);
     s->n_corked = 0;
 
-    pa_cvolume_reset(&s->volume, spec->channels);
-    s->muted = FALSE;
+    s->volume = data->volume;
+    s->muted = data->muted;
     s->refresh_volume = s->refresh_mute = FALSE;
 
-    s->get_latency = NULL;
-    s->set_volume = NULL;
-    s->get_volume = NULL;
-    s->set_mute = NULL;
-    s->get_mute = NULL;
-    s->set_state = NULL;
+    reset_callbacks(s);
     s->userdata = NULL;
 
     s->asyncmsgq = NULL;
     s->rtpoll = NULL;
-    s->silence = NULL;
-
-    pa_assert_se(pa_idxset_put(core->sinks, s, &s->index) >= 0);
-
-    pa_sample_spec_snprint(st, sizeof(st), spec);
-    pa_log_info("Created sink %u \"%s\" with sample spec \"%s\"", s->index, s->name, st);
 
-    n = pa_sprintf_malloc("%s.monitor", name);
-
-    if (!(s->monitor_source = pa_source_new(core, driver, n, 0, spec, map)))
-        pa_log_warn("Failed to create monitor source.");
-    else {
-        char *d;
-        s->monitor_source->monitor_of = s;
-        d = pa_sprintf_malloc("Monitor Source of %s", s->name);
-        pa_source_set_description(s->monitor_source, d);
-        pa_xfree(d);
-    }
+    pa_silence_memchunk_get(
+            &core->silence_cache,
+            core->mempool,
+            &s->silence,
+            &s->sample_spec,
+            0);
 
-    pa_xfree(n);
+    s->min_latency = DEFAULT_MIN_LATENCY;
+    s->max_latency = s->min_latency;
 
     s->thread_info.inputs = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
     s->thread_info.soft_volume = s->volume;
     s->thread_info.soft_muted = s->muted;
     s->thread_info.state = s->state;
+    s->thread_info.rewind_nbytes = 0;
+    s->thread_info.max_rewind = 0;
+    s->thread_info.requested_latency_valid = FALSE;
+    s->thread_info.requested_latency = 0;
+
+    pa_assert_se(pa_idxset_put(core->sinks, s, &s->index) >= 0);
+
+    pa_log_info("Created sink %u \"%s\" with sample spec %s and channel map %s",
+                s->index,
+                s->name,
+                pa_sample_spec_snprint(st, sizeof(st), &s->sample_spec),
+                pa_channel_map_snprint(cm, sizeof(cm), &s->channel_map));
+
+    pa_source_new_data_init(&source_data);
+    pa_source_new_data_set_sample_spec(&source_data, &s->sample_spec);
+    pa_source_new_data_set_channel_map(&source_data, &s->channel_map);
+    source_data.name = pa_sprintf_malloc("%s.monitor", name);
+    source_data.driver = data->driver;
+    source_data.module = data->module;
+
+    dn = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION);
+    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, 0);
+
+    pa_source_new_data_done(&source_data);
+
+    if (!s->monitor_source) {
+        pa_sink_unlink(s);
+        pa_sink_unref(s);
+        return NULL;
+    }
+
+    s->monitor_source->monitor_of = s;
+    pa_source_set_max_rewind(s->monitor_source, s->thread_info.max_rewind);
 
     return s;
 }
@@ -157,15 +264,16 @@ static int sink_set_state(pa_sink *s, pa_sink_state_t state) {
         return 0;
 
     suspend_change =
-        (s->state == PA_SINK_SUSPENDED && PA_SINK_OPENED(state)) ||
-        (PA_SINK_OPENED(s->state) && state == PA_SINK_SUSPENDED);
+        (s->state == PA_SINK_SUSPENDED && PA_SINK_IS_OPENED(state)) ||
+        (PA_SINK_IS_OPENED(s->state) && state == PA_SINK_SUSPENDED);
 
     if (s->set_state)
         if ((ret = s->set_state(s, state)) < 0)
             return -1;
 
-    if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) < 0)
-        return -1;
+    if (s->asyncmsgq)
+        if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) < 0)
+            return -1;
 
     s->state = state;
 
@@ -193,12 +301,17 @@ void pa_sink_put(pa_sink* s) {
     pa_assert(s->asyncmsgq);
     pa_assert(s->rtpoll);
 
+    pa_assert(!s->min_latency || !s->max_latency || s->min_latency <= s->max_latency);
+
+    if (!(s->flags & PA_SINK_HW_VOLUME_CTRL))
+        s->flags |= PA_SINK_DECIBEL_VOLUME;
+
     pa_assert_se(sink_set_state(s, PA_SINK_IDLE) == 0);
 
     pa_source_put(s->monitor_source);
 
     pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_NEW, s->index);
-    pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_NEW_POST], s);
+    pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_PUT], s);
 }
 
 void pa_sink_unlink(pa_sink* s) {
@@ -215,7 +328,7 @@ void pa_sink_unlink(pa_sink* s) {
      * may be called multiple times on the same sink without bad
      * effects. */
 
-    linked = PA_SINK_LINKED(s->state);
+    linked = PA_SINK_IS_LINKED(s->state);
 
     if (linked)
         pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_UNLINK], s);
@@ -235,12 +348,7 @@ void pa_sink_unlink(pa_sink* s) {
     else
         s->state = PA_SINK_UNLINKED;
 
-    s->get_latency = NULL;
-    s->get_volume = NULL;
-    s->set_volume = NULL;
-    s->set_mute = NULL;
-    s->get_mute = NULL;
-    s->set_state = NULL;
+    reset_callbacks(s);
 
     if (s->monitor_source)
         pa_source_unlink(s->monitor_source);
@@ -258,7 +366,7 @@ static void sink_free(pa_object *o) {
     pa_assert(s);
     pa_assert(pa_sink_refcnt(s) == 0);
 
-    if (PA_SINK_LINKED(s->state))
+    if (PA_SINK_IS_LINKED(s->state))
         pa_sink_unlink(s);
 
     pa_log_info("Freeing sink %u \"%s\"", s->index, s->name);
@@ -275,18 +383,20 @@ static void sink_free(pa_object *o) {
 
     pa_hashmap_free(s->thread_info.inputs, NULL, NULL);
 
-    if (s->silence)
-        pa_memblock_unref(s->silence);
+    if (s->silence.memblock)
+        pa_memblock_unref(s->silence.memblock);
 
     pa_xfree(s->name);
-    pa_xfree(s->description);
     pa_xfree(s->driver);
+
+    if (s->proplist)
+        pa_proplist_free(s->proplist);
+
     pa_xfree(s);
 }
 
 void pa_sink_set_asyncmsgq(pa_sink *s, pa_asyncmsgq *q) {
     pa_sink_assert_ref(s);
-    pa_assert(q);
 
     s->asyncmsgq = q;
 
@@ -296,7 +406,6 @@ void pa_sink_set_asyncmsgq(pa_sink *s, pa_asyncmsgq *q) {
 
 void pa_sink_set_rtpoll(pa_sink *s, pa_rtpoll *p) {
     pa_sink_assert_ref(s);
-    pa_assert(p);
 
     s->rtpoll = p;
     if (s->monitor_source)
@@ -305,7 +414,7 @@ void pa_sink_set_rtpoll(pa_sink *s, pa_rtpoll *p) {
 
 int pa_sink_update_status(pa_sink*s) {
     pa_sink_assert_ref(s);
-    pa_assert(PA_SINK_LINKED(s->state));
+    pa_assert(PA_SINK_IS_LINKED(s->state));
 
     if (s->state == PA_SINK_SUSPENDED)
         return 0;
@@ -315,7 +424,7 @@ int pa_sink_update_status(pa_sink*s) {
 
 int pa_sink_suspend(pa_sink *s, pa_bool_t suspend) {
     pa_sink_assert_ref(s);
-    pa_assert(PA_SINK_LINKED(s->state));
+    pa_assert(PA_SINK_IS_LINKED(s->state));
 
     if (suspend)
         return sink_set_state(s, PA_SINK_SUSPENDED);
@@ -323,17 +432,35 @@ int pa_sink_suspend(pa_sink *s, pa_bool_t suspend) {
         return sink_set_state(s, pa_sink_used_by(s) ? PA_SINK_RUNNING : PA_SINK_IDLE);
 }
 
-void pa_sink_ping(pa_sink *s) {
+void pa_sink_process_rewind(pa_sink *s, size_t nbytes) {
+    pa_sink_input *i;
+    void *state = NULL;
     pa_sink_assert_ref(s);
-    pa_assert(PA_SINK_LINKED(s->state));
+    pa_assert(PA_SINK_IS_LINKED(s->state));
+
+    /* Make sure the sink code already reset the counter! */
+    pa_assert(s->thread_info.rewind_nbytes <= 0);
+
+    if (nbytes <= 0)
+        return;
+
+    pa_log_debug("Processing rewind...");
+
+    while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) {
+        pa_sink_input_assert_ref(i);
+        pa_sink_input_process_rewind(i, nbytes);
+    }
+
+    if (s->monitor_source && PA_SOURCE_IS_OPENED(pa_source_get_state(s->monitor_source)))
+        pa_source_process_rewind(s->monitor_source, nbytes);
 
-    pa_asyncmsgq_post(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_PING, NULL, 0, NULL, NULL);
 }
 
-static unsigned fill_mix_info(pa_sink *s, size_t length, pa_mix_info *info, unsigned maxinfo) {
+static unsigned fill_mix_info(pa_sink *s, size_t *length, pa_mix_info *info, unsigned maxinfo) {
     pa_sink_input *i;
     unsigned n = 0;
     void *state = NULL;
+    size_t mixlength = *length;
 
     pa_sink_assert_ref(s);
     pa_assert(info);
@@ -341,8 +468,16 @@ static unsigned fill_mix_info(pa_sink *s, size_t length, pa_mix_info *info, unsi
     while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL)) && maxinfo > 0) {
         pa_sink_input_assert_ref(i);
 
-        if (pa_sink_input_peek(i, length, &info->chunk, &info->volume) < 0)
+        if (pa_sink_input_peek(i, *length, &info->chunk, &info->volume) < 0)
+            continue;
+
+        if (mixlength == 0 || info->chunk.length < mixlength)
+            mixlength = info->chunk.length;
+
+        if (pa_memblock_is_silence(info->chunk.memblock)) {
+            pa_memblock_unref(info->chunk.memblock);
             continue;
+        }
 
         info->userdata = pa_sink_input_ref(i);
 
@@ -354,6 +489,9 @@ static unsigned fill_mix_info(pa_sink *s, size_t length, pa_mix_info *info, unsi
         maxinfo--;
     }
 
+    if (mixlength > 0)
+        *length = mixlength;
+
     return n;
 }
 
@@ -421,12 +559,14 @@ void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result) {
     size_t block_size_max;
 
     pa_sink_assert_ref(s);
-    pa_assert(PA_SINK_OPENED(s->thread_info.state));
+    pa_assert(PA_SINK_IS_OPENED(s->thread_info.state));
     pa_assert(pa_frame_aligned(length, &s->sample_spec));
     pa_assert(result);
 
     pa_sink_ref(s);
 
+    s->thread_info.rewind_nbytes = 0;
+
     if (length <= 0)
         length = pa_frame_align(MIX_BUFFER_LENGTH, &s->sample_spec);
 
@@ -436,24 +576,15 @@ void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result) {
 
     pa_assert(length > 0);
 
-    n = s->thread_info.state == PA_SINK_RUNNING ? fill_mix_info(s, length, info, MAX_MIX_CHANNELS) : 0;
+    n = s->thread_info.state == PA_SINK_RUNNING ? fill_mix_info(s, &length, info, MAX_MIX_CHANNELS) : 0;
 
     if (n == 0) {
 
-        if (length > SILENCE_BUFFER_LENGTH)
-            length = pa_frame_align(SILENCE_BUFFER_LENGTH, &s->sample_spec);
-
-        pa_assert(length > 0);
-
-        if (!s->silence || pa_memblock_get_length(s->silence) < length) {
-            if (s->silence)
-                pa_memblock_unref(s->silence);
-            s->silence = pa_silence_memblock_new(s->core->mempool, &s->sample_spec, length);
-        }
+        *result = s->silence;
+        pa_memblock_ref(result->memblock);
 
-        result->memblock = pa_memblock_ref(s->silence);
-        result->length = length;
-        result->index = 0;
+        if (result->length > length)
+            result->length = length;
 
     } else if (n == 1) {
         pa_cvolume volume;
@@ -467,6 +598,7 @@ 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_log("adjusting 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);
@@ -478,7 +610,11 @@ void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result) {
         result->memblock = pa_memblock_new(s->core->mempool, length);
 
         ptr = pa_memblock_acquire(result->memblock);
-        result->length = pa_mix(info, n, ptr, length, &s->sample_spec, &s->thread_info.soft_volume, s->thread_info.soft_muted);
+        result->length = pa_mix(info, n,
+                                ptr, length,
+                                &s->sample_spec,
+                                &s->thread_info.soft_volume,
+                                s->thread_info.soft_muted);
         pa_memblock_release(result->memblock);
 
         result->index = 0;
@@ -487,7 +623,7 @@ void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result) {
     if (s->thread_info.state == PA_SINK_RUNNING)
         inputs_drop(s, info, n, result->length);
 
-    if (s->monitor_source && PA_SOURCE_OPENED(pa_source_get_state(s->monitor_source)))
+    if (s->monitor_source && PA_SOURCE_IS_OPENED(pa_source_get_state(s->monitor_source)))
         pa_source_post(s->monitor_source, result);
 
     pa_sink_unref(s);
@@ -496,9 +632,10 @@ void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result) {
 void pa_sink_render_into(pa_sink*s, pa_memchunk *target) {
     pa_mix_info info[MAX_MIX_CHANNELS];
     unsigned n;
+    size_t length, block_size_max;
 
     pa_sink_assert_ref(s);
-    pa_assert(PA_SINK_OPENED(s->thread_info.state));
+    pa_assert(PA_SINK_IS_OPENED(s->thread_info.state));
     pa_assert(target);
     pa_assert(target->memblock);
     pa_assert(target->length > 0);
@@ -506,34 +643,46 @@ void pa_sink_render_into(pa_sink*s, pa_memchunk *target) {
 
     pa_sink_ref(s);
 
-    n = s->thread_info.state == PA_SINK_RUNNING ? fill_mix_info(s, target->length, info, MAX_MIX_CHANNELS) : 0;
+    s->thread_info.rewind_nbytes = 0;
+
+    length = target->length;
+    block_size_max = pa_mempool_block_size_max(s->core->mempool);
+    if (length > block_size_max)
+        length = pa_frame_align(block_size_max, &s->sample_spec);
+
+    n = s->thread_info.state == PA_SINK_RUNNING ? fill_mix_info(s, &length, info, MAX_MIX_CHANNELS) : 0;
 
     if (n == 0) {
+        if (target->length > length)
+            target->length = length;
+
         pa_silence_memchunk(target, &s->sample_spec);
     } else if (n == 1) {
-        if (target->length > info[0].chunk.length)
-            target->length = info[0].chunk.length;
+        pa_cvolume volume;
 
-        if (s->thread_info.soft_muted)
+        if (target->length > length)
+            target->length = length;
+
+        pa_sw_cvolume_multiply(&volume, &s->thread_info.soft_volume, &info[0].volume);
+
+        if (s->thread_info.soft_muted || pa_cvolume_is_muted(&volume))
             pa_silence_memchunk(target, &s->sample_spec);
         else {
-            void *src, *ptr;
-            pa_cvolume volume;
-
-            ptr = pa_memblock_acquire(target->memblock);
-            src = pa_memblock_acquire(info[0].chunk.memblock);
+            pa_memchunk vchunk;
 
-            memcpy((uint8_t*) ptr + target->index,
-                   (uint8_t*) src + info[0].chunk.index,
-                   target->length);
+            vchunk = info[0].chunk;
+            pa_memblock_ref(vchunk.memblock);
 
-            pa_memblock_release(target->memblock);
-            pa_memblock_release(info[0].chunk.memblock);
+            if (vchunk.length > target->length)
+                vchunk.length = target->length;
 
-            pa_sw_cvolume_multiply(&volume, &s->thread_info.soft_volume, &info[0].volume);
+            if (!pa_cvolume_is_norm(&volume)) {
+                pa_memchunk_make_writable(&vchunk, 0);
+                pa_volume_memchunk(&vchunk, &s->sample_spec, &volume);
+            }
 
-            if (!pa_cvolume_is_norm(&volume))
-                pa_volume_memchunk(target, &s->sample_spec, &volume);
+            pa_memchunk_memcpy(target, &vchunk);
+            pa_memblock_unref(vchunk.memblock);
         }
 
     } else {
@@ -542,8 +691,7 @@ void pa_sink_render_into(pa_sink*s, pa_memchunk *target) {
         ptr = pa_memblock_acquire(target->memblock);
 
         target->length = pa_mix(info, n,
-                                (uint8_t*) ptr + target->index,
-                                target->length,
+                                (uint8_t*) ptr + target->index, length,
                                 &s->sample_spec,
                                 &s->thread_info.soft_volume,
                                 s->thread_info.soft_muted);
@@ -554,7 +702,7 @@ void pa_sink_render_into(pa_sink*s, pa_memchunk *target) {
     if (s->thread_info.state == PA_SINK_RUNNING)
         inputs_drop(s, info, n, target->length);
 
-    if (s->monitor_source && PA_SOURCE_OPENED(pa_source_get_state(s->monitor_source)))
+    if (s->monitor_source && PA_SOURCE_IS_OPENED(pa_source_get_state(s->monitor_source)))
         pa_source_post(s->monitor_source, target);
 
     pa_sink_unref(s);
@@ -565,7 +713,7 @@ void pa_sink_render_into_full(pa_sink *s, pa_memchunk *target) {
     size_t l, d;
 
     pa_sink_assert_ref(s);
-    pa_assert(PA_SINK_OPENED(s->thread_info.state));
+    pa_assert(PA_SINK_IS_OPENED(s->thread_info.state));
     pa_assert(target);
     pa_assert(target->memblock);
     pa_assert(target->length > 0);
@@ -573,6 +721,8 @@ void pa_sink_render_into_full(pa_sink *s, pa_memchunk *target) {
 
     pa_sink_ref(s);
 
+    s->thread_info.rewind_nbytes = 0;
+
     l = target->length;
     d = 0;
     while (l > 0) {
@@ -591,11 +741,13 @@ void pa_sink_render_into_full(pa_sink *s, pa_memchunk *target) {
 
 void pa_sink_render_full(pa_sink *s, size_t length, pa_memchunk *result) {
     pa_sink_assert_ref(s);
-    pa_assert(PA_SINK_OPENED(s->thread_info.state));
+    pa_assert(PA_SINK_IS_OPENED(s->thread_info.state));
     pa_assert(length > 0);
     pa_assert(pa_frame_aligned(length, &s->sample_spec));
     pa_assert(result);
 
+    s->thread_info.rewind_nbytes = 0;
+
     /*** This needs optimization ***/
 
     result->index = 0;
@@ -605,50 +757,16 @@ void pa_sink_render_full(pa_sink *s, size_t length, pa_memchunk *result) {
     pa_sink_render_into_full(s, result);
 }
 
-void pa_sink_skip(pa_sink *s, size_t length) {
-    pa_sink_input *i;
-    void *state = NULL;
-
-    pa_sink_assert_ref(s);
-    pa_assert(PA_SINK_OPENED(s->thread_info.state));
-    pa_assert(length > 0);
-    pa_assert(pa_frame_aligned(length, &s->sample_spec));
-
-    if (pa_source_used_by(s->monitor_source)) {
-        pa_memchunk chunk;
-
-        /* If something is connected to our monitor source, we have to
-         * pass valid data to it */
-
-        while (length > 0) {
-            pa_sink_render(s, length, &chunk);
-            pa_memblock_unref(chunk.memblock);
-
-            pa_assert(chunk.length <= length);
-            length -= chunk.length;
-        }
-
-    } else {
-        /* Ok, noone cares about the rendered data, so let's not even render it */
-
-        while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) {
-            pa_sink_input_assert_ref(i);
-            pa_sink_input_drop(i, length);
-        }
-    }
-}
-
 pa_usec_t pa_sink_get_latency(pa_sink *s) {
     pa_usec_t usec = 0;
 
     pa_sink_assert_ref(s);
-    pa_assert(PA_SINK_LINKED(s->state));
+    pa_assert(PA_SINK_IS_LINKED(s->state));
 
-    if (!PA_SINK_OPENED(s->state))
-        return 0;
+    /* The returned value is supposed to be in the time domain of the sound card! */
 
-    if (s->get_latency)
-        return s->get_latency(s);
+    if (!PA_SINK_IS_OPENED(s->state))
+        return 0;
 
     if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0)
         return 0;
@@ -660,7 +778,7 @@ void pa_sink_set_volume(pa_sink *s, const pa_cvolume *volume) {
     int changed;
 
     pa_sink_assert_ref(s);
-    pa_assert(PA_SINK_LINKED(s->state));
+    pa_assert(PA_SINK_IS_LINKED(s->state));
     pa_assert(volume);
 
     changed = !pa_cvolume_equal(volume, &s->volume);
@@ -680,7 +798,7 @@ const pa_cvolume *pa_sink_get_volume(pa_sink *s) {
     struct pa_cvolume old_volume;
 
     pa_sink_assert_ref(s);
-    pa_assert(PA_SINK_LINKED(s->state));
+    pa_assert(PA_SINK_IS_LINKED(s->state));
 
     old_volume = s->volume;
 
@@ -700,7 +818,7 @@ void pa_sink_set_mute(pa_sink *s, pa_bool_t mute) {
     int changed;
 
     pa_sink_assert_ref(s);
-    pa_assert(PA_SINK_LINKED(s->state));
+    pa_assert(PA_SINK_IS_LINKED(s->state));
 
     changed = s->muted != mute;
     s->muted = mute;
@@ -719,7 +837,7 @@ pa_bool_t pa_sink_get_mute(pa_sink *s) {
     pa_bool_t old_muted;
 
     pa_sink_assert_ref(s);
-    pa_assert(PA_SINK_LINKED(s->state));
+    pa_assert(PA_SINK_IS_LINKED(s->state));
 
     old_muted = s->muted;
 
@@ -735,43 +853,34 @@ pa_bool_t pa_sink_get_mute(pa_sink *s) {
     return s->muted;
 }
 
-void pa_sink_set_module(pa_sink *s, pa_module *m) {
-    pa_sink_assert_ref(s);
-
-    if (s->module == m)
-        return;
-
-    s->module = m;
-
-    if (s->monitor_source)
-        pa_source_set_module(s->monitor_source, m);
-
-    pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
-}
-
 void pa_sink_set_description(pa_sink *s, const char *description) {
+    const char *old;
     pa_sink_assert_ref(s);
 
-    if (!description && !s->description)
+    if (!description && !pa_proplist_contains(s->proplist, PA_PROP_DEVICE_DESCRIPTION))
         return;
 
-    if (description && s->description && !strcmp(description, s->description))
+    old = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION);
+
+    if (old && description && !strcmp(old, description))
         return;
 
-    pa_xfree(s->description);
-    s->description = pa_xstrdup(description);
+    if (description)
+        pa_proplist_sets(s->proplist, PA_PROP_DEVICE_DESCRIPTION, description);
+    else
+        pa_proplist_unset(s->proplist, PA_PROP_DEVICE_DESCRIPTION);
 
     if (s->monitor_source) {
         char *n;
 
-        n = pa_sprintf_malloc("Monitor Source of %s", s->description? s->description : s->name);
+        n = pa_sprintf_malloc("Monitor Source of %s", description ? description : s->name);
         pa_source_set_description(s->monitor_source, n);
         pa_xfree(n);
     }
 
-    if (PA_SINK_LINKED(s->state)) {
+    if (PA_SINK_IS_LINKED(s->state)) {
         pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
-        pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_DESCRIPTION_CHANGED], s);
+        pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_PROPLIST_CHANGED], s);
     }
 }
 
@@ -779,7 +888,7 @@ unsigned pa_sink_linked_by(pa_sink *s) {
     unsigned ret;
 
     pa_sink_assert_ref(s);
-    pa_assert(PA_SINK_LINKED(s->state));
+    pa_assert(PA_SINK_IS_LINKED(s->state));
 
     ret = pa_idxset_size(s->inputs);
 
@@ -796,16 +905,15 @@ unsigned pa_sink_used_by(pa_sink *s) {
     unsigned ret;
 
     pa_sink_assert_ref(s);
-    pa_assert(PA_SINK_LINKED(s->state));
+    pa_assert(PA_SINK_IS_LINKED(s->state));
 
     ret = pa_idxset_size(s->inputs);
     pa_assert(ret >= s->n_corked);
-    ret -= s->n_corked;
 
     /* Streams connected to our monitor source do not matter for
      * pa_sink_used_by()!.*/
 
-    return ret;
+    return ret - s->n_corked;
 }
 
 int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) {
@@ -817,6 +925,11 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
 
         case PA_SINK_MESSAGE_ADD_INPUT: {
             pa_sink_input *i = PA_SINK_INPUT(userdata);
+
+            /* If you change anything here, make sure to change the
+             * sink input handling a few lines down at
+             * PA_SINK_MESSAGE_FINISH_MOVE, too. */
+
             pa_hashmap_put(s->thread_info.inputs, PA_UINT32_TO_PTR(i->index), pa_sink_input_ref(i));
 
             /* Since the caller sleeps in pa_sink_input_put(), we can
@@ -841,9 +954,16 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
             if (i->attach)
                 i->attach(i);
 
-            /* If you change anything here, make sure to change the
-             * ghost sink input handling a few lines down at
-             * PA_SINK_MESSAGE_REMOVE_INPUT_AND_BUFFER, too. */
+            pa_sink_input_set_state_within_thread(i, i->state);
+
+            pa_sink_input_update_max_rewind(i, s->thread_info.max_rewind);
+
+            pa_sink_invalidate_requested_latency(s);
+
+            /* We don't rewind here automatically. This is left to the
+             * sink input implementor because some sink inputs need a
+             * slow start, i.e. need some time to buffer client
+             * samples before beginning streaming. */
 
             return 0;
         }
@@ -853,7 +973,9 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
 
             /* If you change anything here, make sure to change the
              * sink input handling a few lines down at
-             * PA_SINK_MESSAGE_REMOVE_INPUT_AND_BUFFER, too. */
+             * PA_SINK_MESSAGE_PREPAPRE_MOVE, too. */
+
+            pa_sink_input_set_state_within_thread(i, i->state);
 
             if (i->detach)
                 i->detach(i);
@@ -881,82 +1003,93 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
             if (pa_hashmap_remove(s->thread_info.inputs, PA_UINT32_TO_PTR(i->index)))
                 pa_sink_input_unref(i);
 
+            pa_sink_invalidate_requested_latency(s);
+            pa_sink_request_rewind(s, 0);
+
             return 0;
         }
 
-        case PA_SINK_MESSAGE_REMOVE_INPUT_AND_BUFFER: {
-            pa_sink_input_move_info *info = userdata;
-            int volume_is_norm;
+        case PA_SINK_MESSAGE_START_MOVE: {
+            pa_sink_input *i = PA_SINK_INPUT(userdata);
 
             /* We don't support moving synchronized streams. */
-            pa_assert(!info->sink_input->sync_prev);
-            pa_assert(!info->sink_input->sync_next);
-            pa_assert(!info->sink_input->thread_info.sync_next);
-            pa_assert(!info->sink_input->thread_info.sync_prev);
+            pa_assert(!i->sync_prev);
+            pa_assert(!i->sync_next);
+            pa_assert(!i->thread_info.sync_next);
+            pa_assert(!i->thread_info.sync_prev);
 
-            if (info->sink_input->detach)
-                info->sink_input->detach(info->sink_input);
+            if (i->thread_info.state != PA_SINK_INPUT_CORKED) {
+                pa_usec_t usec = 0;
+                size_t sink_nbytes, total_nbytes;
 
-            pa_assert(info->sink_input->thread_info.attached);
-            info->sink_input->thread_info.attached = FALSE;
+                /* Get the latency of the sink */
+                if (PA_MSGOBJECT(s)->process_msg(PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0)
+                    usec = 0;
 
-            if (info->ghost_sink_input) {
-                pa_assert(info->buffer_bytes > 0);
-                pa_assert(info->buffer);
+                sink_nbytes = pa_usec_to_bytes(usec, &s->sample_spec);
+                total_nbytes = sink_nbytes + pa_memblockq_get_length(i->thread_info.render_memblockq);
 
-                volume_is_norm = pa_cvolume_is_norm(&info->sink_input->thread_info.volume);
+                if (total_nbytes > 0) {
+                    i->thread_info.rewrite_nbytes = i->thread_info.resampler ? pa_resampler_request(i->thread_info.resampler, total_nbytes) : total_nbytes;
+                    i->thread_info.rewrite_flush = TRUE;
+                    pa_sink_input_process_rewind(i, sink_nbytes);
+                }
+            }
 
-                pa_log_debug("Buffering %lu bytes ...", (unsigned long) info->buffer_bytes);
+            if (i->detach)
+                i->detach(i);
 
-                while (info->buffer_bytes > 0) {
-                    pa_memchunk memchunk;
-                    pa_cvolume volume;
-                    size_t n;
+            pa_assert(i->thread_info.attached);
+            i->thread_info.attached = FALSE;
 
-                    if (pa_sink_input_peek(info->sink_input, info->buffer_bytes, &memchunk, &volume) < 0)
-                        break;
+            /* Let's remove the sink input ...*/
+            if (pa_hashmap_remove(s->thread_info.inputs, PA_UINT32_TO_PTR(i->index)))
+                pa_sink_input_unref(i);
 
-                    n = memchunk.length > info->buffer_bytes ? info->buffer_bytes : memchunk.length;
-                    pa_sink_input_drop(info->sink_input, n);
-                    memchunk.length = n;
+            pa_sink_invalidate_requested_latency(s);
 
-                    if (!volume_is_norm) {
-                        pa_memchunk_make_writable(&memchunk, 0);
-                        pa_volume_memchunk(&memchunk, &s->sample_spec, &volume);
-                    }
+            pa_log_debug("Requesting rewind due to started move");
+            pa_sink_request_rewind(s, 0);
 
-                    if (pa_memblockq_push(info->buffer, &memchunk) < 0) {
-                        pa_memblock_unref(memchunk.memblock);
-                        break;
-                    }
+            return 0;
+        }
 
-                    pa_memblock_unref(memchunk.memblock);
-                    info->buffer_bytes -= n;
-                }
+        case PA_SINK_MESSAGE_FINISH_MOVE: {
+            pa_sink_input *i = PA_SINK_INPUT(userdata);
 
-                /* Add the remaining already resampled chunk to the buffer */
-                if (info->sink_input->thread_info.resampled_chunk.memblock)
-                    pa_memblockq_push(info->buffer, &info->sink_input->thread_info.resampled_chunk);
+            /* We don't support moving synchronized streams. */
+            pa_assert(!i->sync_prev);
+            pa_assert(!i->sync_next);
+            pa_assert(!i->thread_info.sync_next);
+            pa_assert(!i->thread_info.sync_prev);
 
-                pa_memblockq_sink_input_set_queue(info->ghost_sink_input, info->buffer);
+            pa_hashmap_put(s->thread_info.inputs, PA_UINT32_TO_PTR(i->index), pa_sink_input_ref(i));
 
-                pa_log_debug("Buffered %lu bytes ...", (unsigned long) pa_memblockq_get_length(info->buffer));
-            }
+            pa_assert(!i->thread_info.attached);
+            i->thread_info.attached = TRUE;
 
-            /* Let's remove the sink input ...*/
-            if (pa_hashmap_remove(s->thread_info.inputs, PA_UINT32_TO_PTR(info->sink_input->index)))
-                pa_sink_input_unref(info->sink_input);
+            if (i->attach)
+                i->attach(i);
+
+            pa_sink_input_update_max_rewind(i, s->thread_info.max_rewind);
 
-            /* .. and add the ghost sink input instead */
-            if (info->ghost_sink_input) {
-                pa_hashmap_put(s->thread_info.inputs, PA_UINT32_TO_PTR(info->ghost_sink_input->index), pa_sink_input_ref(info->ghost_sink_input));
-                info->ghost_sink_input->thread_info.sync_prev = info->ghost_sink_input->thread_info.sync_next = NULL;
+            pa_sink_input_set_requested_latency_within_thread(i, i->thread_info.requested_sink_latency);
 
-                pa_assert(!info->ghost_sink_input->thread_info.attached);
-                info->ghost_sink_input->thread_info.attached = TRUE;
+            if (i->thread_info.state != PA_SINK_INPUT_CORKED) {
+                pa_usec_t usec = 0;
+                size_t nbytes;
 
-                if (info->ghost_sink_input->attach)
-                    info->ghost_sink_input->attach(info->ghost_sink_input);
+                /* Get the latency of the sink */
+                if (PA_MSGOBJECT(s)->process_msg(PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0)
+                    usec = 0;
+
+                nbytes = pa_usec_to_bytes(usec, &s->sample_spec);
+
+                if (nbytes > 0)
+                    pa_sink_input_drop(i, nbytes);
+
+                pa_log_debug("Requesting rewind due to finished move");
+                pa_sink_request_rewind(s, nbytes);
             }
 
             return 0;
@@ -964,10 +1097,14 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
 
         case PA_SINK_MESSAGE_SET_VOLUME:
             s->thread_info.soft_volume = *((pa_cvolume*) userdata);
+
+            pa_sink_request_rewind(s, 0);
             return 0;
 
         case PA_SINK_MESSAGE_SET_MUTE:
             s->thread_info.soft_muted = PA_PTR_TO_UINT(userdata);
+
+            pa_sink_request_rewind(s, 0);
             return 0;
 
         case PA_SINK_MESSAGE_GET_VOLUME:
@@ -978,9 +1115,6 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
             *((pa_bool_t*) userdata) = s->thread_info.soft_muted;
             return 0;
 
-        case PA_SINK_MESSAGE_PING:
-            return 0;
-
         case PA_SINK_MESSAGE_SET_STATE:
 
             s->thread_info.state = PA_PTR_TO_UINT(userdata);
@@ -992,13 +1126,20 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
              * asyncmsgq and rtpoll fields can be changed without
              * problems */
             pa_sink_detach_within_thread(s);
-            break;
+            return 0;
 
         case PA_SINK_MESSAGE_ATTACH:
 
             /* Reattach all streams */
             pa_sink_attach_within_thread(s);
-            break;
+            return 0;
+
+        case PA_SINK_MESSAGE_GET_REQUESTED_LATENCY: {
+
+            pa_usec_t *usec = userdata;
+            *usec = pa_sink_get_requested_latency_within_thread(s);
+            return 0;
+        }
 
         case PA_SINK_MESSAGE_GET_LATENCY:
         case PA_SINK_MESSAGE_MAX:
@@ -1023,14 +1164,14 @@ int pa_sink_suspend_all(pa_core *c, pa_bool_t suspend) {
 
 void pa_sink_detach(pa_sink *s) {
     pa_sink_assert_ref(s);
-    pa_assert(PA_SINK_LINKED(s->state));
+    pa_assert(PA_SINK_IS_LINKED(s->state));
 
     pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_DETACH, NULL, 0, NULL);
 }
 
 void pa_sink_attach(pa_sink *s) {
     pa_sink_assert_ref(s);
-    pa_assert(PA_SINK_LINKED(s->state));
+    pa_assert(PA_SINK_IS_LINKED(s->state));
 
     pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_ATTACH, NULL, 0, NULL);
 }
@@ -1040,7 +1181,7 @@ void pa_sink_detach_within_thread(pa_sink *s) {
     void *state = NULL;
 
     pa_sink_assert_ref(s);
-    pa_assert(PA_SINK_LINKED(s->thread_info.state));
+    pa_assert(PA_SINK_IS_LINKED(s->thread_info.state));
 
     while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL)))
         if (i->detach)
@@ -1055,7 +1196,7 @@ void pa_sink_attach_within_thread(pa_sink *s) {
     void *state = NULL;
 
     pa_sink_assert_ref(s);
-    pa_assert(PA_SINK_LINKED(s->thread_info.state));
+    pa_assert(PA_SINK_IS_LINKED(s->thread_info.state));
 
     while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL)))
         if (i->attach)
@@ -1064,3 +1205,98 @@ void pa_sink_attach_within_thread(pa_sink *s) {
     if (s->monitor_source)
         pa_source_attach_within_thread(s->monitor_source);
 }
+
+void pa_sink_request_rewind(pa_sink*s, size_t nbytes) {
+    pa_sink_assert_ref(s);
+    pa_assert(PA_SINK_IS_LINKED(s->thread_info.state));
+
+    if (nbytes <= 0)
+        nbytes = s->thread_info.max_rewind;
+
+    nbytes = PA_MIN(nbytes, s->thread_info.max_rewind);
+
+    if (nbytes <= s->thread_info.rewind_nbytes)
+        return;
+
+    s->thread_info.rewind_nbytes = nbytes;
+
+    if (s->request_rewind)
+        s->request_rewind(s);
+}
+
+pa_usec_t pa_sink_get_requested_latency_within_thread(pa_sink *s) {
+    pa_usec_t result = (pa_usec_t) -1;
+    pa_sink_input *i;
+    void *state = NULL;
+
+    pa_sink_assert_ref(s);
+
+    if (s->thread_info.requested_latency_valid)
+        return s->thread_info.requested_latency;
+
+    while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL)))
+
+        if (i->thread_info.requested_sink_latency != (pa_usec_t) -1 &&
+            (result == (pa_usec_t) -1 || result > i->thread_info.requested_sink_latency))
+            result = i->thread_info.requested_sink_latency;
+
+    if (result != (pa_usec_t) -1) {
+        if (s->max_latency > 0 && result > s->max_latency)
+            result = s->max_latency;
+
+        if (s->min_latency > 0 && result < s->min_latency)
+            result = s->min_latency;
+    }
+
+    s->thread_info.requested_latency = result;
+    s->thread_info.requested_latency_valid = TRUE;
+
+    return result;
+}
+
+pa_usec_t pa_sink_get_requested_latency(pa_sink *s) {
+    pa_usec_t usec = 0;
+
+    pa_sink_assert_ref(s);
+    pa_assert(PA_SINK_IS_LINKED(s->state));
+
+    if (!PA_SINK_IS_OPENED(s->state))
+        return 0;
+
+    if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_REQUESTED_LATENCY, &usec, 0, NULL) < 0)
+        return 0;
+
+    if (usec == (pa_usec_t) -1)
+        usec = s->max_latency;
+
+    return usec;
+}
+
+void pa_sink_set_max_rewind(pa_sink *s, size_t max_rewind) {
+    pa_sink_input *i;
+    void *state = NULL;
+
+    pa_sink_assert_ref(s);
+
+    if (max_rewind == s->thread_info.max_rewind)
+        return;
+
+    s->thread_info.max_rewind = max_rewind;
+
+    while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL)))
+        pa_sink_input_update_max_rewind(i, s->thread_info.max_rewind);
+
+    if (s->monitor_source)
+        pa_source_set_max_rewind(s->monitor_source, s->thread_info.max_rewind);
+}
+
+void pa_sink_invalidate_requested_latency(pa_sink *s) {
+
+    pa_sink_assert_ref(s);
+    pa_assert(PA_SINK_IS_LINKED(s->thread_info.state));
+
+    s->thread_info.requested_latency_valid = FALSE;
+
+    if (s->update_requested_latency)
+        s->update_requested_latency(s);
+}
index e996930..f297c8f 100644 (file)
@@ -33,7 +33,6 @@ typedef struct pa_sink pa_sink;
 #include <pulse/channelmap.h>
 #include <pulse/volume.h>
 
-#include <pulsecore/core-def.h>
 #include <pulsecore/core.h>
 #include <pulsecore/idxset.h>
 #include <pulsecore/source.h>
@@ -52,11 +51,11 @@ typedef enum pa_sink_state {
     PA_SINK_UNLINKED
 } pa_sink_state_t;
 
-static inline pa_bool_t PA_SINK_OPENED(pa_sink_state_t x) {
+static inline pa_bool_t PA_SINK_IS_OPENED(pa_sink_state_t x) {
     return x == PA_SINK_RUNNING || x == PA_SINK_IDLE;
 }
 
-static inline pa_bool_t PA_SINK_LINKED(pa_sink_state_t x) {
+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;
 }
 
@@ -69,7 +68,8 @@ struct pa_sink {
     pa_sink_flags_t flags;
 
     char *name;
-    char *description, *driver;            /* may be NULL */
+    char *driver;                           /* may be NULL */
+    pa_proplist *proplist;
 
     pa_module *module;                      /* may be NULL */
 
@@ -85,16 +85,47 @@ struct pa_sink {
     pa_bool_t refresh_volume;
     pa_bool_t refresh_mute;
 
-    int (*set_state)(pa_sink *s, pa_sink_state_t state); /* may be NULL */
-    int (*set_volume)(pa_sink *s);           /* dito */
-    int (*get_volume)(pa_sink *s);           /* dito */
-    int (*get_mute)(pa_sink *s);             /* dito */
-    int (*set_mute)(pa_sink *s);             /* dito */
-    pa_usec_t (*get_latency)(pa_sink *s);    /* dito */
-
     pa_asyncmsgq *asyncmsgq;
     pa_rtpoll *rtpoll;
 
+    pa_memchunk silence;
+
+    pa_usec_t min_latency; /* we won't go below this latency */
+    pa_usec_t max_latency; /* An upper limit for the latencies */
+
+    /* Called when the main loop requests a state change. Called from
+     * main loop context. If returns -1 the state change will be
+     * inhibited */
+    int (*set_state)(pa_sink *s, pa_sink_state_t state); /* may be NULL */
+
+    /* Callled when the volume is queried. Called from main loop
+     * context. If this is NULL a PA_SINK_MESSAGE_GET_VOLUME message
+     * will be sent to the IO thread instead. */
+    int (*get_volume)(pa_sink *s);             /* may be null */
+
+    /* Called when the volume shall be changed. Called from main loop
+     * context. If this is NULL a PA_SINK_MESSAGE_SET_VOLUME message
+     * will be sent to the IO thread instead. */
+    int (*set_volume)(pa_sink *s);             /* dito */
+
+    /* Called when the mute setting is queried. Called from main loop
+     * context. If this is NULL a PA_SINK_MESSAGE_GET_MUTE message
+     * will be sent to the IO thread instead. */
+    int (*get_mute)(pa_sink *s);               /* dito */
+
+    /* Called when the mute setting shall be changed. Called from main
+     * loop context. If this is NULL a PA_SINK_MESSAGE_SET_MUTE
+     * message will be sent to the IO thread instead. */
+    int (*set_mute)(pa_sink *s);               /* dito */
+
+    /* Called when a rewind request is issued. Called from IO thread
+     * context. */
+    void (*request_rewind)(pa_sink *s);        /* dito */
+
+    /* Called when a the requested latency is changed. Called from IO
+     * thread context. */
+    void (*update_requested_latency)(pa_sink *s); /* dito */
+
     /* Contains copies of the above data so that the real-time worker
      * thread can work without access locking */
     struct {
@@ -102,9 +133,17 @@ struct pa_sink {
         pa_hashmap *inputs;
         pa_cvolume soft_volume;
         pa_bool_t soft_muted;
-    } thread_info;
 
-    pa_memblock *silence;
+        pa_bool_t requested_latency_valid;
+        pa_usec_t requested_latency;
+
+        /* The number of bytes we need keep around to be able to satisfy
+         * every DMA buffer rewrite */
+        size_t max_rewind;
+
+        /* Maximum of what clients requested to rewind in this cycle */
+        size_t rewind_nbytes;
+    } thread_info;
 
     void *userdata;
 };
@@ -120,28 +159,52 @@ typedef enum pa_sink_message {
     PA_SINK_MESSAGE_GET_MUTE,
     PA_SINK_MESSAGE_SET_MUTE,
     PA_SINK_MESSAGE_GET_LATENCY,
+    PA_SINK_MESSAGE_GET_REQUESTED_LATENCY,
     PA_SINK_MESSAGE_SET_STATE,
-    PA_SINK_MESSAGE_PING,
-    PA_SINK_MESSAGE_REMOVE_INPUT_AND_BUFFER,
+    PA_SINK_MESSAGE_START_MOVE,
+    PA_SINK_MESSAGE_FINISH_MOVE,
     PA_SINK_MESSAGE_ATTACH,
     PA_SINK_MESSAGE_DETACH,
     PA_SINK_MESSAGE_MAX
 } pa_sink_message_t;
 
+typedef struct pa_sink_new_data {
+    char *name;
+    pa_bool_t namereg_fail;
+    pa_proplist *proplist;
+
+    const char *driver;
+    pa_module *module;
+
+    pa_sample_spec sample_spec;
+    pa_bool_t sample_spec_is_set;
+    pa_channel_map channel_map;
+    pa_bool_t channel_map_is_set;
+
+    pa_cvolume volume;
+    pa_bool_t volume_is_set;
+    pa_bool_t muted;
+    pa_bool_t muted_is_set;
+} pa_sink_new_data;
+
+pa_sink_new_data* pa_sink_new_data_init(pa_sink_new_data *data);
+void pa_sink_new_data_set_name(pa_sink_new_data *data, const char *name);
+void pa_sink_new_data_set_sample_spec(pa_sink_new_data *data, const pa_sample_spec *spec);
+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_done(pa_sink_new_data *data);
+
 /* To be called exclusively by the sink driver, from main context */
 
 pa_sink* pa_sink_new(
         pa_core *core,
-        const char *driver,
-        const char *name,
-        int namereg_fail,
-        const pa_sample_spec *spec,
-        const pa_channel_map *map);
+        pa_sink_new_data *data,
+        pa_sink_flags_t flags);
 
 void pa_sink_put(pa_sink *s);
 void pa_sink_unlink(pa_sink* s);
 
-void pa_sink_set_module(pa_sink *sink, pa_module *m);
 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);
@@ -151,17 +214,14 @@ void pa_sink_attach(pa_sink *s);
 
 /* May be called by everyone, from main context */
 
+/* The returned value is supposed to be in the time domain of the sound card! */
 pa_usec_t pa_sink_get_latency(pa_sink *s);
+pa_usec_t pa_sink_get_requested_latency(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);
 
-/* Sends a ping message to the sink thread, to make it wake up and
- * check for data to process even if there is no real message is
- * sent */
-void pa_sink_ping(pa_sink *s);
-
 void pa_sink_set_volume(pa_sink *sink, const pa_cvolume *volume);
 const pa_cvolume *pa_sink_get_volume(pa_sink *sink);
 void pa_sink_set_mute(pa_sink *sink, pa_bool_t mute);
@@ -178,11 +238,21 @@ void pa_sink_render_full(pa_sink *s, size_t length, pa_memchunk *result);
 void pa_sink_render_into(pa_sink*s, pa_memchunk *target);
 void pa_sink_render_into_full(pa_sink *s, pa_memchunk *target);
 
-void pa_sink_skip(pa_sink *s, size_t length);
+void pa_sink_process_rewind(pa_sink *s, size_t nbytes);
 
 int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk);
 
 void pa_sink_attach_within_thread(pa_sink *s);
 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);
+
+/* To be called exclusively by sink input drivers, from IO context */
+
+void pa_sink_request_rewind(pa_sink*s, size_t nbytes);
+
+void pa_sink_invalidate_requested_latency(pa_sink *s);
+
 #endif
index 5b5bc5c..a99193b 100644 (file)
@@ -77,9 +77,9 @@ struct pa_socket_client {
     pa_io_event *io_event;
     pa_time_event *timeout_event;
     pa_defer_event *defer_event;
-    void (*callback)(pa_socket_client*c, pa_iochannel *io, void *userdata);
+    pa_socket_client_cb_t callback;
     void *userdata;
-    int local;
+    pa_bool_t local;
 #ifdef HAVE_LIBASYNCNS
     asyncns_t *asyncns;
     asyncns_query_t * asyncns_query;
@@ -87,7 +87,7 @@ struct pa_socket_client {
 #endif
 };
 
-static pa_socket_client*pa_socket_client_new(pa_mainloop_api *m) {
+static pa_socket_client* socket_client_new(pa_mainloop_api *m) {
     pa_socket_client *c;
     pa_assert(m);
 
@@ -96,11 +96,11 @@ static pa_socket_client*pa_socket_client_new(pa_mainloop_api *m) {
     c->mainloop = m;
     c->fd = -1;
     c->io_event = NULL;
-    c->defer_event = NULL;
     c->timeout_event = NULL;
+    c->defer_event = NULL;
     c->callback = NULL;
     c->userdata = NULL;
-    c->local = 0;
+    c->local = FALSE;
 
 #ifdef HAVE_LIBASYNCNS
     c->asyncns = NULL;
@@ -119,15 +119,15 @@ static void free_events(pa_socket_client *c) {
         c->io_event = NULL;
     }
 
-    if (c->defer_event) {
-        c->mainloop->defer_free(c->defer_event);
-        c->defer_event = NULL;
-    }
-
     if (c->timeout_event) {
         c->mainloop->time_free(c->timeout_event);
         c->timeout_event = NULL;
     }
+
+    if (c->defer_event) {
+        c->mainloop->defer_free(c->defer_event);
+        c->defer_event = NULL;
+    }
 }
 
 static void do_call(pa_socket_client *c) {
@@ -177,7 +177,7 @@ finish:
     pa_socket_client_unref(c);
 }
 
-static void connect_fixed_cb(pa_mainloop_api *m, pa_defer_event *e, void *userdata) {
+static void connect_defer_cb(pa_mainloop_api *m, pa_defer_event *e, void *userdata) {
     pa_socket_client *c = userdata;
 
     pa_assert(m);
@@ -223,7 +223,7 @@ static int do_connect(pa_socket_client *c, const struct sockaddr *sa, socklen_t
 
         pa_assert_se(c->io_event = c->mainloop->io_new(c->mainloop, c->fd, PA_IO_EVENT_OUTPUT, connect_io_cb, c));
     } else
-        pa_assert_se(c->defer_event = c->mainloop->defer_new(c->mainloop, connect_fixed_cb, c));
+        pa_assert_se(c->defer_event = c->mainloop->defer_new(c->mainloop, connect_defer_cb, c));
 
     return 0;
 }
@@ -252,8 +252,7 @@ pa_socket_client* pa_socket_client_new_unix(pa_mainloop_api *m, const char *file
 
     memset(&sa, 0, sizeof(sa));
     sa.sun_family = AF_UNIX;
-    strncpy(sa.sun_path, filename, sizeof(sa.sun_path)-1);
-    sa.sun_path[sizeof(sa.sun_path) - 1] = 0;
+    pa_strlcpy(sa.sun_path, filename, sizeof(sa.sun_path));
 
     return pa_socket_client_new_sockaddr(m, (struct sockaddr*) &sa, sizeof(sa));
 }
@@ -273,7 +272,7 @@ static int sockaddr_prepare(pa_socket_client *c, const struct sockaddr *sa, size
 
     switch (sa->sa_family) {
         case AF_UNIX:
-            c->local = 1;
+            c->local = TRUE;
             break;
 
         case AF_INET:
@@ -285,7 +284,7 @@ static int sockaddr_prepare(pa_socket_client *c, const struct sockaddr *sa, size
             break;
 
         default:
-            c->local = 0;
+            c->local = FALSE;
     }
 
     if ((c->fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) {
@@ -294,6 +293,7 @@ static int sockaddr_prepare(pa_socket_client *c, const struct sockaddr *sa, size
     }
 
     pa_make_fd_cloexec(c->fd);
+
     if (sa->sa_family == AF_INET || sa->sa_family == AF_INET6)
         pa_make_tcp_socket_low_delay(c->fd);
     else
@@ -312,7 +312,7 @@ pa_socket_client* pa_socket_client_new_sockaddr(pa_mainloop_api *m, const struct
     pa_assert(sa);
     pa_assert(salen > 0);
 
-    pa_assert_se(c = pa_socket_client_new(m));
+    pa_assert_se(c = socket_client_new(m));
 
     if (sockaddr_prepare(c, sa, salen) < 0)
         goto fail;
@@ -361,7 +361,7 @@ pa_socket_client* pa_socket_client_ref(pa_socket_client *c) {
     return c;
 }
 
-void pa_socket_client_set_callback(pa_socket_client *c, void (*on_connection)(pa_socket_client *c, pa_iochannel*io, void *userdata), void *userdata) {
+void pa_socket_client_set_callback(pa_socket_client *c, pa_socket_client_cb_t on_connection, void *userdata) {
     pa_assert(c);
     pa_assert(PA_REFCNT_VALUE(c) >= 1);
 
@@ -489,23 +489,22 @@ pa_socket_client* pa_socket_client_new_string(pa_mainloop_api *m, const char*nam
             hints.ai_family = a.type == PA_PARSED_ADDRESS_TCP4 ? PF_INET : (a.type == PA_PARSED_ADDRESS_TCP6 ? PF_INET6 : PF_UNSPEC);
             hints.ai_socktype = SOCK_STREAM;
 
-#ifdef HAVE_LIBASYNCNS
+#if defined(HAVE_LIBASYNCNS)
             {
                 asyncns_t *asyncns;
 
                 if (!(asyncns = asyncns_new(1)))
                     goto finish;
 
-                c = pa_socket_client_new(m);
+                pa_assert_se(c = socket_client_new(m));
                 c->asyncns = asyncns;
                 c->asyncns_io_event = m->io_new(m, asyncns_fd(c->asyncns), PA_IO_EVENT_INPUT, asyncns_cb, c);
                 c->asyncns_query = asyncns_getaddrinfo(c->asyncns, a.path_or_host, port, &hints);
                 pa_assert(c->asyncns_query);
                 start_timeout(c);
             }
-#else /* HAVE_LIBASYNCNS */
+#elif defined(HAVE_GETADDRINFO)
             {
-#ifdef HAVE_GETADDRINFO
                 int ret;
                 struct addrinfo *res = NULL;
 
@@ -520,7 +519,9 @@ pa_socket_client* pa_socket_client_new_string(pa_mainloop_api *m, const char*nam
                 }
 
                 freeaddrinfo(res);
-#else /* HAVE_GETADDRINFO */
+            }
+#else
+            {
                 struct hostent *host = NULL;
                 struct sockaddr_in s;
 
@@ -546,7 +547,6 @@ pa_socket_client* pa_socket_client_new_string(pa_mainloop_api *m, const char*nam
 
                 if ((c = pa_socket_client_new_sockaddr(m, (struct sockaddr*)&s, sizeof(s))))
                     start_timeout(c);
-#endif /* HAVE_GETADDRINFO */
             }
 #endif /* HAVE_LIBASYNCNS */
         }
@@ -561,7 +561,7 @@ finish:
 /* Return non-zero when the target sockaddr is considered
    local. "local" means UNIX socket or TCP socket on localhost. Other
    local IP addresses are not considered local. */
-int pa_socket_client_is_local(pa_socket_client *c) {
+pa_bool_t pa_socket_client_is_local(pa_socket_client *c) {
     pa_assert(c);
     pa_assert(PA_REFCNT_VALUE(c) >= 1);
 
index b1d58ef..41e8c3b 100644 (file)
@@ -34,17 +34,19 @@ struct sockaddr;
 
 typedef struct pa_socket_client pa_socket_client;
 
+typedef void (*pa_socket_client_cb_t)(pa_socket_client *c, pa_iochannel*io, void *userdata);
+
 pa_socket_client* pa_socket_client_new_ipv4(pa_mainloop_api *m, uint32_t address, uint16_t port);
 pa_socket_client* pa_socket_client_new_ipv6(pa_mainloop_api *m, uint8_t address[16], uint16_t port);
 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);
 
-void pa_socket_client_unref(pa_socket_client *c);
 pa_socket_client* pa_socket_client_ref(pa_socket_client *c);
+void pa_socket_client_unref(pa_socket_client *c);
 
-void pa_socket_client_set_callback(pa_socket_client *c, void (*on_connection)(pa_socket_client *c, pa_iochannel*io, void *userdata), void *userdata);
+void pa_socket_client_set_callback(pa_socket_client *c, pa_socket_client_cb_t on_connection, void *userdata);
 
-int pa_socket_client_is_local(pa_socket_client *c);
+pa_bool_t pa_socket_client_is_local(pa_socket_client *c);
 
 #endif
index bb1f3e9..e209676 100644 (file)
@@ -3,7 +3,7 @@
 /***
   This file is part of PulseAudio.
 
-  Copyright 2004-2006 Lennart Poettering
+  Copyright 2004-2008 Lennart Poettering
 
   PulseAudio is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published
 #include <pulsecore/log.h>
 #include <pulsecore/thread-mq.h>
 #include <pulsecore/core-util.h>
+#include <pulsecore/sample-util.h>
 
 #include "sound-file-stream.h"
 
+#define MEMBLOCKQ_MAXLENGTH (16*1024*1024)
+
 typedef struct file_stream {
     pa_msgobject parent;
     pa_core *core;
-    SNDFILE *sndfile;
     pa_sink_input *sink_input;
-    pa_memchunk memchunk;
+
+    SNDFILE *sndfile;
     sf_count_t (*readf_function)(SNDFILE *sndfile, void *ptr, sf_count_t frames);
-    size_t drop;
+
+    /* We need this memblockq here to easily fulfill rewind requests
+     * (even beyond the file start!) */
+    pa_memblockq *memblockq;
 } file_stream;
 
 enum {
@@ -62,6 +68,7 @@ PA_DECLARE_CLASS(file_stream);
 #define FILE_STREAM(o) (file_stream_cast(o))
 static PA_DEFINE_CHECK_TYPE(file_stream, pa_msgobject);
 
+/* Called from main context */
 static void file_stream_unlink(file_stream *u) {
     pa_assert(u);
 
@@ -69,7 +76,6 @@ static void file_stream_unlink(file_stream *u) {
         return;
 
     pa_sink_input_unlink(u->sink_input);
-
     pa_sink_input_unref(u->sink_input);
     u->sink_input = NULL;
 
@@ -77,14 +83,13 @@ static void file_stream_unlink(file_stream *u) {
     file_stream_unref(u);
 }
 
+/* Called from main context */
 static void file_stream_free(pa_object *o) {
     file_stream *u = FILE_STREAM(o);
     pa_assert(u);
 
-    file_stream_unlink(u);
-
-    if (u->memchunk.memblock)
-        pa_memblock_unref(u->memchunk.memblock);
+    if (u->memblockq)
+        pa_memblockq_free(u->memblockq);
 
     if (u->sndfile)
         sf_close(u->sndfile);
@@ -92,6 +97,7 @@ static void file_stream_free(pa_object *o) {
     pa_xfree(u);
 }
 
+/* Called from main context */
 static int file_stream_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) {
     file_stream *u = FILE_STREAM(o);
     file_stream_assert_ref(u);
@@ -105,117 +111,128 @@ static int file_stream_process_msg(pa_msgobject *o, int code, void*userdata, int
     return 0;
 }
 
+/* Called from main context */
 static void sink_input_kill_cb(pa_sink_input *i) {
+    file_stream *u;
+
     pa_sink_input_assert_ref(i);
+    u = FILE_STREAM(i->userdata);
+    file_stream_assert_ref(u);
 
-    file_stream_unlink(FILE_STREAM(i->userdata));
+    file_stream_unlink(u);
 }
 
-static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
+/* Called from IO thread context */
+static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) {
     file_stream *u;
 
-    pa_assert(i);
-    pa_assert(chunk);
+    pa_sink_input_assert_ref(i);
     u = FILE_STREAM(i->userdata);
     file_stream_assert_ref(u);
 
-    if (!u->sndfile)
-        return -1;
+    /* If we are added for the first time, ask for a rewinding so that
+     * we are heard right-away. */
+    if (PA_SINK_INPUT_IS_LINKED(state) &&
+        i->thread_info.state == PA_SINK_INPUT_INIT)
+        pa_sink_input_request_rewind(i, 0, FALSE, TRUE);
+}
 
-    for (;;) {
+/* Called from IO thread context */
+static int sink_input_pop_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
+    file_stream *u;
 
-        if (!u->memchunk.memblock) {
+    pa_sink_input_assert_ref(i);
+    pa_assert(chunk);
+    u = FILE_STREAM(i->userdata);
+    file_stream_assert_ref(u);
 
-            u->memchunk.memblock = pa_memblock_new(i->sink->core->mempool, length);
-            u->memchunk.index = 0;
+    if (!u->memblockq)
+        return -1;
 
-            if (u->readf_function) {
-                sf_count_t n;
-                void *p;
-                size_t fs = pa_frame_size(&i->sample_spec);
+    pa_log_debug("pop: %lu", (unsigned long) length);
 
-                p = pa_memblock_acquire(u->memchunk.memblock);
-                n = u->readf_function(u->sndfile, p, length/fs);
-                pa_memblock_release(u->memchunk.memblock);
+    for (;;) {
+        pa_memchunk tchunk;
+        size_t fs;
+        void *p;
+        sf_count_t n;
+
+        if (pa_memblockq_peek(u->memblockq, chunk) >= 0) {
+            pa_memblockq_drop(u->memblockq, chunk->length);
+            return 0;
+        }
 
-                if (n <= 0)
-                    n = 0;
+        if (!u->sndfile)
+            break;
 
-                u->memchunk.length = n * fs;
-            } else {
-                sf_count_t n;
-                void *p;
+        tchunk.memblock = pa_memblock_new(i->sink->core->mempool, length);
+        tchunk.index = 0;
 
-                p = pa_memblock_acquire(u->memchunk.memblock);
-                n = sf_read_raw(u->sndfile, p, length);
-                pa_memblock_release(u->memchunk.memblock);
+        p = pa_memblock_acquire(tchunk.memblock);
 
-                if (n <= 0)
-                    n = 0;
+        if (u->readf_function) {
+            fs = pa_frame_size(&i->sample_spec);
+            n = u->readf_function(u->sndfile, p, length/fs);
+        } else {
+            fs = 1;
+            n = sf_read_raw(u->sndfile, p, length);
+        }
 
-                u->memchunk.length = n;
-            }
+        pa_memblock_release(tchunk.memblock);
 
-            if (u->memchunk.length <= 0) {
+        if (n <= 0) {
+            pa_memblock_unref(tchunk.memblock);
 
-                pa_memblock_unref(u->memchunk.memblock);
-                pa_memchunk_reset(&u->memchunk);
+            sf_close(u->sndfile);
+            u->sndfile = NULL;
+            break;
+        }
 
-                pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u), FILE_STREAM_MESSAGE_UNLINK, NULL, 0, NULL, NULL);
+        tchunk.length = n * fs;
 
-                sf_close(u->sndfile);
-                u->sndfile = NULL;
+        pa_memblockq_push(u->memblockq, &tchunk);
+        pa_memblock_unref(tchunk.memblock);
+    }
 
-                return -1;
-            }
-        }
+    pa_log_debug("peek fail");
 
-        pa_assert(u->memchunk.memblock);
-        pa_assert(u->memchunk.length > 0);
+    if (pa_sink_input_safe_to_remove(i)) {
+        pa_log_debug("completed to play");
 
-        if (u->drop < u->memchunk.length) {
-            u->memchunk.index += u->drop;
-            u->memchunk.length -= u->drop;
-            u->drop = 0;
-            break;
-        }
+        pa_memblockq_free(u->memblockq);
+        u->memblockq = NULL;
 
-        u->drop -= u->memchunk.length;
-        pa_memblock_unref(u->memchunk.memblock);
-        pa_memchunk_reset(&u->memchunk);
+        pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u), FILE_STREAM_MESSAGE_UNLINK, NULL, 0, NULL, NULL);
     }
 
-    *chunk = u->memchunk;
-    pa_memblock_ref(chunk->memblock);
+    return -1;
+ }
 
-    pa_assert(chunk->length > 0);
-    pa_assert(u->drop <= 0);
+static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
+    file_stream *u;
 
-    return 0;
+    pa_sink_input_assert_ref(i);
+    pa_assert(nbytes > 0);
+    u = FILE_STREAM(i->userdata);
+    file_stream_assert_ref(u);
+
+    if (!u->memblockq)
+        return;
+
+    pa_memblockq_rewind(u->memblockq, nbytes);
 }
 
-static void sink_input_drop_cb(pa_sink_input *i, size_t length) {
+static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
     file_stream *u;
 
-    pa_assert(i);
-    pa_assert(length > 0);
+    pa_sink_input_assert_ref(i);
     u = FILE_STREAM(i->userdata);
     file_stream_assert_ref(u);
 
-    if (u->memchunk.memblock) {
-
-        if (length < u->memchunk.length) {
-            u->memchunk.index += length;
-            u->memchunk.length -= length;
-            return;
-        }
-
-        length -= u->memchunk.length;
-        pa_memblock_unref(u->memchunk.memblock);
-        pa_memchunk_reset(&u->memchunk);
-    }
+    if (!u->memblockq)
+        return;
 
-    u->drop += length;
+    pa_memblockq_set_maxrewind(u->memblockq, nbytes);
 }
 
 int pa_play_file(
@@ -237,10 +254,9 @@ int pa_play_file(
     u->parent.process_msg = file_stream_process_msg;
     u->core = sink->core;
     u->sink_input = NULL;
-    pa_memchunk_reset(&u->memchunk);
     u->sndfile = NULL;
     u->readf_function = NULL;
-    u->drop = 0;
+    u->memblockq = NULL;
 
     memset(&sfinfo, 0, sizeof(sfinfo));
 
@@ -312,18 +328,26 @@ int pa_play_file(
     pa_sink_input_new_data_init(&data);
     data.sink = sink;
     data.driver = __FILE__;
-    data.name = fname;
     pa_sink_input_new_data_set_sample_spec(&data, &ss);
     pa_sink_input_new_data_set_volume(&data, volume);
+    pa_proplist_sets(data.proplist, PA_PROP_MEDIA_NAME, fname);
+    pa_proplist_sets(data.proplist, PA_PROP_MEDIA_FILENAME, fname);
 
-    if (!(u->sink_input = pa_sink_input_new(sink->core, &data, 0)))
+    u->sink_input = pa_sink_input_new(sink->core, &data, 0);
+    pa_sink_input_new_data_done(&data);
+
+    if (!u->sink_input)
         goto fail;
 
-    u->sink_input->peek = sink_input_peek_cb;
-    u->sink_input->drop = sink_input_drop_cb;
+    u->sink_input->pop = sink_input_pop_cb;
+    u->sink_input->process_rewind = sink_input_process_rewind_cb;
+    u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
     u->sink_input->kill = sink_input_kill_cb;
+    u->sink_input->state_change = sink_input_state_change_cb;
     u->sink_input->userdata = u;
 
+    u->memblockq = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0, pa_frame_size(&ss), 1, 1, 0, NULL);
+
     pa_sink_input_put(u->sink_input);
 
     /* The reference to u is dangling here, because we want to keep
index 88c1146..5c36937 100644 (file)
 #include <pulse/utf8.h>
 #include <pulse/xmalloc.h>
 
+#include <pulsecore/sample-util.h>
 #include <pulsecore/core-subscribe.h>
 #include <pulsecore/log.h>
 #include <pulsecore/namereg.h>
+#include <pulsecore/core-util.h>
 
 #include "source-output.h"
 
+#define MEMBLOCKQ_MAXLENGTH (32*1024*1024)
+
 static PA_DEFINE_CHECK_TYPE(pa_source_output, pa_msgobject);
 
 static void source_output_free(pa_object* mo);
@@ -47,9 +51,18 @@ pa_source_output_new_data* pa_source_output_new_data_init(pa_source_output_new_d
 
     memset(data, 0, sizeof(*data));
     data->resample_method = PA_RESAMPLER_INVALID;
+    data->proplist = pa_proplist_new();
+
     return data;
 }
 
+void pa_source_output_new_data_set_sample_spec(pa_source_output_new_data *data, const pa_sample_spec *spec) {
+    pa_assert(data);
+
+    if ((data->sample_spec_is_set = !!spec))
+        data->sample_spec = *spec;
+}
+
 void pa_source_output_new_data_set_channel_map(pa_source_output_new_data *data, const pa_channel_map *map) {
     pa_assert(data);
 
@@ -57,11 +70,25 @@ void pa_source_output_new_data_set_channel_map(pa_source_output_new_data *data,
         data->channel_map = *map;
 }
 
-void pa_source_output_new_data_set_sample_spec(pa_source_output_new_data *data, const pa_sample_spec *spec) {
+void pa_source_output_new_data_done(pa_source_output_new_data *data) {
     pa_assert(data);
 
-    if ((data->sample_spec_is_set = !!spec))
-        data->sample_spec = *spec;
+    pa_proplist_free(data->proplist);
+}
+
+static void reset_callbacks(pa_source_output *o) {
+    pa_assert(o);
+
+    o->push = NULL;
+    o->process_rewind = NULL;
+    o->update_max_rewind = NULL;
+    o->attach = NULL;
+    o->detach = NULL;
+    o->suspend = NULL;
+    o->moved = NULL;
+    o->kill = NULL;
+    o->get_latency = NULL;
+    o->state_change = NULL;
 }
 
 pa_source_output* pa_source_output_new(
@@ -80,7 +107,6 @@ pa_source_output* pa_source_output_new(
         return NULL;
 
     pa_return_null_if_fail(!data->driver || pa_utf8_valid(data->driver));
-    pa_return_null_if_fail(!data->name || pa_utf8_valid(data->name));
 
     if (!data->source)
         data->source = pa_namereg_get(core, NULL, PA_NAMEREG_SOURCE, 1);
@@ -156,7 +182,7 @@ pa_source_output* pa_source_output_new(
     o->core = core;
     o->state = PA_SOURCE_OUTPUT_INIT;
     o->flags = flags;
-    o->name = pa_xstrdup(data->name);
+    o->proplist = pa_proplist_copy(data->proplist);
     o->driver = pa_xstrdup(data->driver);
     o->module = data->module;
     o->source = data->source;
@@ -166,26 +192,31 @@ pa_source_output* pa_source_output_new(
     o->sample_spec = data->sample_spec;
     o->channel_map = data->channel_map;
 
-    o->push = NULL;
-    o->kill = NULL;
-    o->get_latency = NULL;
-    o->detach = NULL;
-    o->attach = NULL;
-    o->suspend = NULL;
-    o->moved = NULL;
+    reset_callbacks(o);
     o->userdata = NULL;
 
     o->thread_info.state = o->state;
     o->thread_info.attached = FALSE;
     o->thread_info.sample_spec = o->sample_spec;
     o->thread_info.resampler = resampler;
+    o->thread_info.requested_source_latency = (pa_usec_t) -1;
+
+    o->thread_info.delay_memblockq = pa_memblockq_new(
+            0,
+            MEMBLOCKQ_MAXLENGTH,
+            0,
+            pa_frame_size(&o->source->sample_spec),
+            0,
+            1,
+            0,
+            &o->source->silence);
 
     pa_assert_se(pa_idxset_put(core->source_outputs, o, &o->index) == 0);
     pa_assert_se(pa_idxset_put(o->source->outputs, pa_source_output_ref(o), NULL) == 0);
 
     pa_log_info("Created output %u \"%s\" on %s with sample spec %s and channel map %s",
                 o->index,
-                o->name,
+                pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_MEDIA_NAME)),
                 o->source->name,
                 pa_sample_spec_snprint(st, sizeof(st), &o->sample_spec),
                 pa_channel_map_snprint(cm, sizeof(cm), &o->channel_map));
@@ -195,22 +226,27 @@ pa_source_output* pa_source_output_new(
     return o;
 }
 
-static int source_output_set_state(pa_source_output *o, pa_source_output_state_t state) {
+static void update_n_corked(pa_source_output *o, pa_source_output_state_t state) {
     pa_assert(o);
 
-    if (o->state == state)
-        return 0;
-
-    if (pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) < 0)
-        return -1;
-
     if (o->state == PA_SOURCE_OUTPUT_CORKED && state != PA_SOURCE_OUTPUT_CORKED)
         pa_assert_se(o->source->n_corked -- >= 1);
     else if (o->state != PA_SOURCE_OUTPUT_CORKED && state == PA_SOURCE_OUTPUT_CORKED)
         o->source->n_corked++;
 
     pa_source_update_status(o->source);
+}
 
+static int source_output_set_state(pa_source_output *o, pa_source_output_state_t state) {
+    pa_assert(o);
+
+    if (o->state == state)
+        return 0;
+
+    if (pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) < 0)
+        return -1;
+
+    update_n_corked(o, state);
     o->state = state;
 
     if (state != PA_SOURCE_OUTPUT_UNLINKED)
@@ -228,7 +264,7 @@ void pa_source_output_unlink(pa_source_output*o) {
 
     pa_source_output_ref(o);
 
-    linked = PA_SOURCE_OUTPUT_LINKED(o->state);
+    linked = PA_SOURCE_OUTPUT_IS_LINKED(o->state);
 
     if (linked)
         pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK], o);
@@ -237,20 +273,14 @@ void pa_source_output_unlink(pa_source_output*o) {
     if (pa_idxset_remove_by_data(o->source->outputs, o, NULL))
         pa_source_output_unref(o);
 
-    if (linked) {
-        pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL);
-        source_output_set_state(o, PA_SOURCE_OUTPUT_UNLINKED);
-        pa_source_update_status(o->source);
-    } else
-        o->state = PA_SOURCE_OUTPUT_UNLINKED;
+    update_n_corked(o, PA_SOURCE_OUTPUT_UNLINKED);
+    o->state = PA_SOURCE_OUTPUT_UNLINKED;
 
-    o->push = NULL;
-    o->kill = NULL;
-    o->get_latency = NULL;
-    o->attach = NULL;
-    o->detach = NULL;
-    o->suspend = NULL;
-    o->moved = NULL;
+    if (linked)
+        if (o->source->asyncmsgq)
+            pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL);
+
+    reset_callbacks(o);
 
     if (linked) {
         pa_subscription_post(o->source->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_REMOVE, o->index);
@@ -264,45 +294,50 @@ void pa_source_output_unlink(pa_source_output*o) {
 static void source_output_free(pa_object* mo) {
     pa_source_output *o = PA_SOURCE_OUTPUT(mo);
 
+    pa_assert(o);
     pa_assert(pa_source_output_refcnt(o) == 0);
 
-    if (PA_SOURCE_OUTPUT_LINKED(o->state))
+    if (PA_SOURCE_OUTPUT_IS_LINKED(o->state))
         pa_source_output_unlink(o);
 
-    pa_log_info("Freeing output %u \"%s\"", o->index, o->name);
+    pa_log_info("Freeing output %u \"%s\"", o->index, pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_MEDIA_NAME)));
 
     pa_assert(!o->thread_info.attached);
 
+    if (o->thread_info.delay_memblockq)
+        pa_memblockq_free(o->thread_info.delay_memblockq);
+
     if (o->thread_info.resampler)
         pa_resampler_free(o->thread_info.resampler);
 
-    pa_xfree(o->name);
+    if (o->proplist)
+        pa_proplist_free(o->proplist);
+
     pa_xfree(o->driver);
     pa_xfree(o);
 }
 
 void pa_source_output_put(pa_source_output *o) {
+    pa_source_output_state_t state;
     pa_source_output_assert_ref(o);
 
     pa_assert(o->state == PA_SOURCE_OUTPUT_INIT);
     pa_assert(o->push);
 
-    o->thread_info.state = o->state = o->flags & PA_SOURCE_OUTPUT_START_CORKED ? PA_SOURCE_OUTPUT_CORKED : PA_SOURCE_OUTPUT_RUNNING;
+    state = o->flags & PA_SOURCE_OUTPUT_START_CORKED ? PA_SOURCE_OUTPUT_CORKED : PA_SOURCE_OUTPUT_RUNNING;
 
-    if (o->state == PA_SOURCE_OUTPUT_CORKED)
-        o->source->n_corked++;
+    update_n_corked(o, state);
+    o->state = state;
 
     pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_ADD_OUTPUT, o, 0, NULL);
-    pa_source_update_status(o->source);
 
     pa_subscription_post(o->source->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW, o->index);
-
     pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], o);
 }
 
 void pa_source_output_kill(pa_source_output*o) {
     pa_source_output_assert_ref(o);
-    pa_assert(PA_SOURCE_OUTPUT_LINKED(o->state));
+    pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
 
     if (o->kill)
         o->kill(o);
@@ -312,7 +347,7 @@ pa_usec_t pa_source_output_get_latency(pa_source_output *o) {
     pa_usec_t r = 0;
 
     pa_source_output_assert_ref(o);
-    pa_assert(PA_SOURCE_OUTPUT_LINKED(o->state));
+    pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
 
     if (pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY, &r, 0, NULL) < 0)
         r = 0;
@@ -325,42 +360,169 @@ pa_usec_t pa_source_output_get_latency(pa_source_output *o) {
 
 /* Called from thread context */
 void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk) {
-    pa_memchunk rchunk;
+    size_t length;
+    size_t limit, mbs = 0;
 
     pa_source_output_assert_ref(o);
-    pa_assert(PA_SOURCE_OUTPUT_LINKED(o->thread_info.state));
+    pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state));
     pa_assert(chunk);
-    pa_assert(chunk->length);
+    pa_assert(pa_frame_aligned(chunk->length, &o->source->sample_spec));
 
     if (!o->push || o->state == PA_SOURCE_OUTPUT_CORKED)
         return;
 
     pa_assert(o->state == PA_SOURCE_OUTPUT_RUNNING);
 
-    if (!o->thread_info.resampler) {
-        o->push(o, chunk);
-        return;
+    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, chunk->length, PA_SEEK_RELATIVE);
     }
 
-    pa_resampler_run(o->thread_info.resampler, chunk, &rchunk);
-    if (!rchunk.length)
+    limit = o->process_rewind ? 0 : o->source->thread_info.max_rewind;
+
+    /* Implement the delay queue */
+    while ((length = pa_memblockq_get_length(o->thread_info.delay_memblockq)) > limit) {
+        pa_memchunk qchunk;
+
+        length -= limit;
+
+        pa_assert_se(pa_memblockq_peek(o->thread_info.delay_memblockq, &qchunk) >= 0);
+
+        if (qchunk.length > length)
+            qchunk.length = length;
+
+        pa_assert(qchunk.length > 0);
+
+        if (!o->thread_info.resampler)
+            o->push(o, &qchunk);
+        else {
+            pa_memchunk rchunk;
+
+            if (mbs == 0)
+                mbs = pa_resampler_max_block_size(o->thread_info.resampler);
+
+            if (qchunk.length > mbs)
+                qchunk.length = mbs;
+
+            pa_resampler_run(o->thread_info.resampler, &qchunk, &rchunk);
+
+            if (rchunk.length > 0)
+                o->push(o, &rchunk);
+
+            pa_memblock_unref(rchunk.memblock);
+        }
+
+        pa_memblock_unref(qchunk.memblock);
+        pa_memblockq_drop(o->thread_info.delay_memblockq, qchunk.length);
+    }
+}
+
+/* Called from thread context */
+void pa_source_output_process_rewind(pa_source_output *o, size_t nbytes /* in sink sample spec */) {
+    pa_source_output_assert_ref(o);
+
+    pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+    pa_assert(pa_frame_aligned(nbytes, &o->source->sample_spec));
+
+    if (nbytes <= 0)
         return;
 
-    pa_assert(rchunk.memblock);
-    o->push(o, &rchunk);
-    pa_memblock_unref(rchunk.memblock);
+    if (o->process_rewind) {
+        pa_assert(pa_memblockq_get_length(o->thread_info.delay_memblockq) == 0);
+
+        if (o->thread_info.resampler)
+            nbytes = pa_resampler_result(o->thread_info.resampler, nbytes);
+
+        pa_log_debug("Have to rewind %lu bytes on implementor.", (unsigned long) nbytes);
+
+        if (nbytes > 0)
+            o->process_rewind(o, nbytes);
+
+        if (o->thread_info.resampler)
+            pa_resampler_reset(o->thread_info.resampler);
+
+    } else
+        pa_memblockq_rewind(o->thread_info.delay_memblockq, nbytes);
+}
+
+/* Called from thread context */
+void pa_source_output_update_max_rewind(pa_source_output *o, size_t nbytes  /* in the source's sample spec */) {
+    pa_source_output_assert_ref(o);
+    pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state));
+    pa_assert(pa_frame_aligned(nbytes, &o->source->sample_spec));
+
+    if (o->update_max_rewind)
+        o->update_max_rewind(o, o->thread_info.resampler ? pa_resampler_result(o->thread_info.resampler, nbytes) : nbytes);
+}
+
+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->max_latency > 0 && usec > s->max_latency)
+        usec = s->max_latency;
+
+    if (s->min_latency > 0 && usec < s->min_latency)
+        usec = s->min_latency;
+
+    return usec;
+}
+
+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);
+
+    o->thread_info.requested_source_latency = usec;
+    pa_source_invalidate_requested_latency(o->source);
+
+    return usec;
+}
+
+pa_usec_t pa_source_output_set_requested_latency(pa_source_output *o, pa_usec_t usec) {
+    pa_source_output_assert_ref(o);
+
+    usec = fixup_latency(o->source, usec);
+
+    if (PA_SOURCE_OUTPUT_IS_LINKED(o->state))
+        pa_asyncmsgq_post(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_REQUESTED_LATENCY, NULL, (int64_t) usec, NULL, NULL);
+    else {
+        /* If this sink input is not realized yet, we have to touch
+         * the thread info data directly */
+        o->thread_info.requested_source_latency = usec;
+        o->source->thread_info.requested_latency_valid = FALSE;
+    }
+
+    return usec;
+}
+
+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))
+        pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_GET_REQUESTED_LATENCY, &usec, 0, NULL);
+    else
+        /* If this sink input is not realized yet, we have to touch
+         * the thread info data directly */
+        usec = o->thread_info.requested_source_latency;
+
+    return usec;
 }
 
 void pa_source_output_cork(pa_source_output *o, pa_bool_t b) {
     pa_source_output_assert_ref(o);
-    pa_assert(PA_SOURCE_OUTPUT_LINKED(o->state));
+    pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
 
     source_output_set_state(o, b ? PA_SOURCE_OUTPUT_CORKED : PA_SOURCE_OUTPUT_RUNNING);
 }
 
 int pa_source_output_set_rate(pa_source_output *o, uint32_t rate) {
     pa_source_output_assert_ref(o);
-    pa_assert(PA_SOURCE_OUTPUT_LINKED(o->state));
+    pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
     pa_return_val_if_fail(o->thread_info.resampler, -1);
 
     if (o->sample_spec.rate == rate)
@@ -375,19 +537,24 @@ int pa_source_output_set_rate(pa_source_output *o, uint32_t rate) {
 }
 
 void pa_source_output_set_name(pa_source_output *o, const char *name) {
+    const char *old;
     pa_source_output_assert_ref(o);
 
-    if (!o->name && !name)
+    if (!name && !pa_proplist_contains(o->proplist, PA_PROP_MEDIA_NAME))
         return;
 
-    if (o->name && name && !strcmp(o->name, name))
+    old = pa_proplist_gets(o->proplist, PA_PROP_MEDIA_NAME);
+
+    if (old && name && !strcmp(old, name))
         return;
 
-    pa_xfree(o->name);
-    o->name = pa_xstrdup(name);
+    if (name)
+        pa_proplist_sets(o->proplist, PA_PROP_MEDIA_NAME, name);
+    else
+        pa_proplist_unset(o->proplist, PA_PROP_MEDIA_NAME);
 
-    if (PA_SOURCE_OUTPUT_LINKED(o->state)) {
-        pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NAME_CHANGED], o);
+    if (PA_SOURCE_OUTPUT_IS_LINKED(o->state)) {
+        pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED], o);
         pa_subscription_post(o->source->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
     }
 }
@@ -400,11 +567,11 @@ pa_resample_method_t pa_source_output_get_resample_method(pa_source_output *o) {
 
 int pa_source_output_move_to(pa_source_output *o, pa_source *dest) {
     pa_source *origin;
-    pa_resampler *new_resampler = NULL;
+    pa_resampler *new_resampler;
     pa_source_output_move_hook_data hook_data;
 
     pa_source_output_assert_ref(o);
-    pa_assert(PA_SOURCE_OUTPUT_LINKED(o->state));
+    pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
     pa_source_assert_ref(dest);
 
     origin = o->source;
@@ -444,7 +611,8 @@ int pa_source_output_move_to(pa_source_output *o, pa_source *dest) {
             pa_log_warn("Unsupported resampling operation.");
             return -1;
         }
-    }
+    } else
+        new_resampler = NULL;
 
     hook_data.source_output = o;
     hook_data.destination = dest;
@@ -467,13 +635,25 @@ int pa_source_output_move_to(pa_source_output *o, pa_source *dest) {
         if (o->thread_info.resampler)
             pa_resampler_free(o->thread_info.resampler);
         o->thread_info.resampler = new_resampler;
-    }
 
-    pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_ADD_OUTPUT, o, 0, NULL);
+        pa_memblockq_free(o->thread_info.delay_memblockq);
+
+        o->thread_info.delay_memblockq = pa_memblockq_new(
+                0,
+                MEMBLOCKQ_MAXLENGTH,
+                0,
+                pa_frame_size(&o->source->sample_spec),
+                0,
+                1,
+                0,
+                &o->source->silence);
+    }
 
     pa_source_update_status(origin);
     pa_source_update_status(dest);
 
+    pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_ADD_OUTPUT, o, 0, NULL);
+
     if (o->moved)
         o->moved(o);
 
@@ -487,26 +667,54 @@ int pa_source_output_move_to(pa_source_output *o, pa_source *dest) {
     return 0;
 }
 
+void pa_source_output_set_state_within_thread(pa_source_output *o, pa_source_output_state_t state) {
+    pa_source_output_assert_ref(o);
+
+    if (state == o->thread_info.state)
+        return;
+
+    if (o->state_change)
+        o->state_change(o, state);
+
+    o->thread_info.state = state;
+}
+
 /* Called from thread context */
 int pa_source_output_process_msg(pa_msgobject *mo, int code, void *userdata, int64_t offset, pa_memchunk* chunk) {
     pa_source_output *o = PA_SOURCE_OUTPUT(mo);
 
     pa_source_output_assert_ref(o);
-    pa_assert(PA_SOURCE_OUTPUT_LINKED(o->thread_info.state));
+    pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state));
 
     switch (code) {
 
-        case PA_SOURCE_OUTPUT_MESSAGE_SET_RATE: {
+        case PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY: {
+            pa_usec_t *r = userdata;
+
+            *r += pa_bytes_to_usec(pa_memblockq_get_length(o->thread_info.delay_memblockq), &o->source->sample_spec);
+            return 0;
+        }
+
+        case PA_SOURCE_OUTPUT_MESSAGE_SET_RATE:
 
             o->thread_info.sample_spec.rate = PA_PTR_TO_UINT(userdata);
             pa_resampler_set_output_rate(o->thread_info.resampler, PA_PTR_TO_UINT(userdata));
+            return 0;
+
+        case PA_SOURCE_OUTPUT_MESSAGE_SET_STATE:
 
+            pa_source_output_set_state_within_thread(o, PA_PTR_TO_UINT(userdata));
+            return 0;
+
+        case PA_SOURCE_OUTPUT_MESSAGE_SET_REQUESTED_LATENCY:
+
+            pa_source_output_set_requested_latency_within_thread(o, (pa_usec_t) offset);
             return 0;
-        }
 
-        case PA_SOURCE_OUTPUT_MESSAGE_SET_STATE: {
-            o->thread_info.state = PA_PTR_TO_UINT(userdata);
+        case PA_SINK_INPUT_MESSAGE_GET_REQUESTED_LATENCY: {
+            pa_usec_t *r = userdata;
 
+            *r = o->thread_info.requested_source_latency;
             return 0;
         }
     }
index d6da8d0..2dadb5c 100644 (file)
@@ -42,7 +42,7 @@ typedef enum pa_source_output_state {
     PA_SOURCE_OUTPUT_UNLINKED
 } pa_source_output_state_t;
 
-static inline pa_bool_t PA_SOURCE_OUTPUT_LINKED(pa_source_output_state_t x) {
+static inline pa_bool_t PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_state_t x) {
     return x == PA_SOURCE_OUTPUT_RUNNING || x == PA_SOURCE_OUTPUT_CORKED;
 }
 
@@ -62,10 +62,12 @@ struct pa_source_output {
 
     uint32_t index;
     pa_core *core;
+
     pa_source_output_state_t state;
     pa_source_output_flags_t flags;
 
-    char *name, *driver;                  /* may be NULL */
+    pa_proplist *proplist;
+    char *driver;                         /* may be NULL */
     pa_module *module;                    /* may be NULL */
     pa_client *client;                    /* may be NULL */
 
@@ -74,10 +76,20 @@ struct pa_source_output {
     pa_sample_spec sample_spec;
     pa_channel_map channel_map;
 
+    pa_resample_method_t resample_method;
+
     /* Pushes a new memchunk into the output. Called from IO thread
      * context. */
     void (*push)(pa_source_output *o, const pa_memchunk *chunk);
 
+    /* Only relevant for monitor sources right now: called when the
+     * recorded stream is rewound. Called from IO context*/
+    void (*process_rewind)(pa_source_output *o, size_t nbytes);
+
+    /* Called whenever the maximum rewindable size of the source
+     * changes. Called from IO thread context. */
+    void (*update_max_rewind) (pa_source_output *o, size_t nbytes); /* may be NULL */
+
     /* If non-NULL this function is called when the output is first
      * connected to a source. Called from IO thread context */
     void (*attach) (pa_source_output *o);           /* may be NULL */
@@ -87,13 +99,13 @@ struct pa_source_output {
     void (*detach) (pa_source_output *o);           /* 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 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 */
+
     /* Supposed to unlink and destroy this stream. Called from main
      * context. */
     void (*kill)(pa_source_output* o);              /* may be NULL */
@@ -104,7 +116,9 @@ struct pa_source_output {
     thread instead. */
     pa_usec_t (*get_latency) (pa_source_output *o); /* may be NULL */
 
-    pa_resample_method_t resample_method;
+    /* If non_NULL this function is called from thread context if the
+     * state changes. The old state is found in thread_info.state.  */
+    void (*state_change) (pa_source_output *o, pa_source_output_state_t state); /* may be NULL */
 
     struct {
         pa_source_output_state_t state;
@@ -114,6 +128,13 @@ struct pa_source_output {
         pa_sample_spec sample_spec;
 
         pa_resampler* resampler;              /* may be NULL */
+
+        /* We maintain a delay memblockq here for source outputs that
+         * don't implement rewind() */
+        pa_memblockq *delay_memblockq;
+
+        /* The requested latency for the source */
+        pa_usec_t requested_source_latency;
     } thread_info;
 
     void *userdata;
@@ -126,11 +147,15 @@ enum {
     PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY,
     PA_SOURCE_OUTPUT_MESSAGE_SET_RATE,
     PA_SOURCE_OUTPUT_MESSAGE_SET_STATE,
+    PA_SOURCE_OUTPUT_MESSAGE_SET_REQUESTED_LATENCY,
+    PA_SOURCE_OUTPUT_MESSAGE_GET_REQUESTED_LATENCY,
     PA_SOURCE_OUTPUT_MESSAGE_MAX
 };
 
 typedef struct pa_source_output_new_data {
-    const char *name, *driver;
+    pa_proplist *proplist;
+
+    const char *driver;
     pa_module *module;
     pa_client *client;
 
@@ -144,16 +169,16 @@ typedef struct pa_source_output_new_data {
     pa_resample_method_t resample_method;
 } pa_source_output_new_data;
 
+pa_source_output_new_data* pa_source_output_new_data_init(pa_source_output_new_data *data);
+void pa_source_output_new_data_set_sample_spec(pa_source_output_new_data *data, const pa_sample_spec *spec);
+void pa_source_output_new_data_set_channel_map(pa_source_output_new_data *data, const pa_channel_map *map);
+void pa_source_output_new_data_done(pa_source_output_new_data *data);
+
 typedef struct pa_source_output_move_hook_data {
     pa_source_output *source_output;
     pa_source *destination;
 } pa_source_output_move_hook_data;
 
-pa_source_output_new_data* pa_source_output_new_data_init(pa_source_output_new_data *data);
-void pa_source_output_new_data_set_sample_spec(pa_source_output_new_data *data, const pa_sample_spec *spec);
-void pa_source_output_new_data_set_channel_map(pa_source_output_new_data *data, const pa_channel_map *map);
-void pa_source_output_new_data_set_volume(pa_source_output_new_data *data, const pa_cvolume *volume);
-
 /* To be called by the implementing module only */
 
 pa_source_output* pa_source_output_new(
@@ -166,6 +191,12 @@ void pa_source_output_unlink(pa_source_output*o);
 
 void pa_source_output_set_name(pa_source_output *i, const char *name);
 
+pa_usec_t pa_source_output_set_requested_latency(pa_source_output *i, pa_usec_t usec);
+
+void pa_source_output_cork(pa_source_output *i, pa_bool_t b);
+
+int pa_source_output_set_rate(pa_source_output *o, uint32_t rate);
+
 /* Callable by everyone */
 
 /* External code may request disconnection with this funcion */
@@ -173,19 +204,24 @@ void pa_source_output_kill(pa_source_output*o);
 
 pa_usec_t pa_source_output_get_latency(pa_source_output *i);
 
-void pa_source_output_cork(pa_source_output *i, pa_bool_t b);
-
-int pa_source_output_set_rate(pa_source_output *o, uint32_t rate);
-
 pa_resample_method_t pa_source_output_get_resample_method(pa_source_output *o);
 
 int pa_source_output_move_to(pa_source_output *o, pa_source *dest);
 
 #define pa_source_output_get_state(o) ((o)->state)
 
+pa_usec_t pa_source_output_get_requested_latency(pa_source_output *o);
+
 /* To be used exclusively by the source driver thread */
 
 void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk);
+void pa_source_output_process_rewind(pa_source_output *o, size_t nbytes);
+void pa_source_output_update_max_rewind(pa_source_output *o, size_t nbytes);
+
+void pa_source_output_set_state_within_thread(pa_source_output *o, pa_source_output_state_t state);
+
 int pa_source_output_process_msg(pa_msgobject *mo, int code, void *userdata, int64_t offset, pa_memchunk *chunk);
 
+pa_usec_t pa_source_output_set_requested_latency_within_thread(pa_source_output *o, pa_usec_t usec);
+
 #endif
index d707ad8..c767abc 100644 (file)
@@ -32,6 +32,7 @@
 
 #include <pulse/utf8.h>
 #include <pulse/xmalloc.h>
+#include <pulse/timeval.h>
 
 #include <pulsecore/source-output.h>
 #include <pulsecore/namereg.h>
 
 #include "source.h"
 
+#define DEFAULT_MIN_LATENCY (4*PA_USEC_PER_MSEC)
+
 static PA_DEFINE_CHECK_TYPE(pa_source, pa_msgobject);
 
 static void source_free(pa_object *o);
 
+pa_source_new_data* pa_source_new_data_init(pa_source_new_data *data) {
+    pa_assert(data);
+
+    memset(data, 0, sizeof(*data));
+    data->proplist = pa_proplist_new();
+
+    return data;
+}
+
+void pa_source_new_data_set_name(pa_source_new_data *data, const char *name) {
+    pa_assert(data);
+
+    pa_xfree(data->name);
+    data->name = pa_xstrdup(name);
+}
+
+void pa_source_new_data_set_sample_spec(pa_source_new_data *data, const pa_sample_spec *spec) {
+    pa_assert(data);
+
+    if ((data->sample_spec_is_set = !!spec))
+        data->sample_spec = *spec;
+}
+
+void pa_source_new_data_set_channel_map(pa_source_new_data *data, const pa_channel_map *map) {
+    pa_assert(data);
+
+    if ((data->channel_map_is_set = !!map))
+        data->channel_map = *map;
+}
+
+void pa_source_new_data_set_volume(pa_source_new_data *data, const pa_cvolume *volume) {
+    pa_assert(data);
+
+    if ((data->volume_is_set = !!volume))
+        data->volume = *volume;
+}
+
+void pa_source_new_data_set_muted(pa_source_new_data *data, pa_bool_t mute) {
+    pa_assert(data);
+
+    data->muted_is_set = TRUE;
+    data->muted = !!mute;
+}
+
+void pa_source_new_data_done(pa_source_new_data *data) {
+    pa_assert(data);
+
+    pa_xfree(data->name);
+    pa_proplist_free(data->proplist);
+}
+
+static void reset_callbacks(pa_source *s) {
+    pa_assert(s);
+
+    s->set_state = NULL;
+    s->get_volume = NULL;
+    s->set_volume = NULL;
+    s->get_mute = NULL;
+    s->set_mute = NULL;
+    s->update_requested_latency = NULL;
+}
+
 pa_source* pa_source_new(
         pa_core *core,
-        const char *driver,
-        const char *name,
-        int fail,
-        const pa_sample_spec *spec,
-        const pa_channel_map *map) {
+        pa_source_new_data *data,
+        pa_source_flags_t flags) {
 
     pa_source *s;
-    char st[256];
-    pa_channel_map tmap;
+    const char *name;
+    char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
 
     pa_assert(core);
-    pa_assert(name);
-    pa_assert(spec);
 
-    pa_return_null_if_fail(pa_sample_spec_valid(spec));
+    s = pa_msgobject_new(pa_source);
+
+    if (!(name = pa_namereg_register(core, data->name, PA_NAMEREG_SOURCE, s, data->namereg_fail))) {
+        pa_xfree(s);
+        return NULL;
+    }
 
-    if (!map)
-        pa_return_null_if_fail(map = pa_channel_map_init_auto(&tmap, spec->channels, PA_CHANNEL_MAP_DEFAULT));
+    if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_NEW], data) < 0) {
+        pa_xfree(s);
+        pa_namereg_unregister(core, name);
+        return NULL;
+    }
 
-    pa_return_null_if_fail(map && pa_channel_map_valid(map));
-    pa_return_null_if_fail(map->channels == spec->channels);
-    pa_return_null_if_fail(!driver || pa_utf8_valid(driver));
-    pa_return_null_if_fail(pa_utf8_valid(name) && *name);
+    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]);
 
-    s = pa_msgobject_new(pa_source);
+    pa_return_null_if_fail(data->sample_spec_is_set && pa_sample_spec_valid(&data->sample_spec));
+
+    if (!data->channel_map_is_set)
+        pa_return_null_if_fail(pa_channel_map_init_auto(&data->channel_map, data->sample_spec.channels, PA_CHANNEL_MAP_DEFAULT));
 
-    if (!(name = pa_namereg_register(core, name, PA_NAMEREG_SOURCE, s, fail))) {
+    pa_return_null_if_fail(pa_channel_map_valid(&data->channel_map));
+    pa_return_null_if_fail(data->channel_map.channels == data->sample_spec.channels);
+
+    if (!data->volume_is_set)
+        pa_cvolume_reset(&data->volume, data->sample_spec.channels);
+
+    pa_return_null_if_fail(pa_cvolume_valid(&data->volume));
+    pa_return_null_if_fail(data->volume.channels == data->sample_spec.channels);
+
+    if (!data->muted_is_set)
+        data->muted = FALSE;
+
+    if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_FIXATE], data) < 0) {
         pa_xfree(s);
+        pa_namereg_unregister(core, name);
         return NULL;
     }
 
@@ -83,43 +165,54 @@ pa_source* pa_source_new(
 
     s->core = core;
     s->state = PA_SOURCE_INIT;
-    s->flags = 0;
+    s->flags = flags;
     s->name = pa_xstrdup(name);
-    s->description = NULL;
-    s->driver = pa_xstrdup(driver);
-    s->module = NULL;
+    s->proplist = pa_proplist_copy(data->proplist);
+    s->driver = pa_xstrdup(data->driver);
+    s->module = data->module;
 
-    s->sample_spec = *spec;
-    s->channel_map = *map;
+    s->sample_spec = data->sample_spec;
+    s->channel_map = data->channel_map;
 
     s->outputs = pa_idxset_new(NULL, NULL);
     s->n_corked = 0;
     s->monitor_of = NULL;
 
-    pa_cvolume_reset(&s->volume, spec->channels);
-    s->muted = FALSE;
+    s->volume = data->volume;
+    s->muted = data->muted;
     s->refresh_volume = s->refresh_muted = FALSE;
 
-    s->get_latency = NULL;
-    s->set_volume = NULL;
-    s->get_volume = NULL;
-    s->set_mute = NULL;
-    s->get_mute = NULL;
-    s->set_state = NULL;
+    reset_callbacks(s);
     s->userdata = NULL;
 
     s->asyncmsgq = NULL;
     s->rtpoll = NULL;
 
-    pa_assert_se(pa_idxset_put(core->sources, s, &s->index) >= 0);
+    pa_silence_memchunk_get(
+            &core->silence_cache,
+            core->mempool,
+            &s->silence,
+            &s->sample_spec,
+            0);
 
-    pa_sample_spec_snprint(st, sizeof(st), spec);
-    pa_log_info("Created source %u \"%s\" with sample spec \"%s\"", s->index, s->name, st);
+    s->min_latency = DEFAULT_MIN_LATENCY;
+    s->max_latency = s->min_latency;
 
     s->thread_info.outputs = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
     s->thread_info.soft_volume = s->volume;
     s->thread_info.soft_muted = s->muted;
     s->thread_info.state = s->state;
+    s->thread_info.max_rewind = 0;
+    s->thread_info.requested_latency_valid = FALSE;
+    s->thread_info.requested_latency = 0;
+
+    pa_assert_se(pa_idxset_put(core->sources, s, &s->index) >= 0);
+
+    pa_log_info("Created source %u \"%s\" with sample spec %s and channel map %s",
+                s->index,
+                s->name,
+                pa_sample_spec_snprint(st, sizeof(st), &s->sample_spec),
+                pa_channel_map_snprint(cm, sizeof(cm), &s->channel_map));
 
     return s;
 }
@@ -134,15 +227,16 @@ static int source_set_state(pa_source *s, pa_source_state_t state) {
         return 0;
 
     suspend_change =
-        (s->state == PA_SOURCE_SUSPENDED && PA_SOURCE_OPENED(state)) ||
-        (PA_SOURCE_OPENED(s->state) && state == PA_SOURCE_SUSPENDED);
+        (s->state == PA_SOURCE_SUSPENDED && PA_SOURCE_IS_OPENED(state)) ||
+        (PA_SOURCE_IS_OPENED(s->state) && state == PA_SOURCE_SUSPENDED);
 
     if (s->set_state)
         if ((ret = s->set_state(s, state)) < 0)
             return -1;
 
-    if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) < 0)
-        return -1;
+    if (s->asyncmsgq)
+        if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) < 0)
+            return -1;
 
     s->state = state;
 
@@ -167,13 +261,18 @@ void pa_source_put(pa_source *s) {
     pa_source_assert_ref(s);
 
     pa_assert(s->state == PA_SINK_INIT);
-    pa_assert(s->rtpoll);
     pa_assert(s->asyncmsgq);
+    pa_assert(s->rtpoll);
+
+    pa_assert(!s->min_latency || !s->max_latency || s->min_latency <= s->max_latency);
+
+    if (!(s->flags & PA_SOURCE_HW_VOLUME_CTRL))
+        s->flags |= PA_SOURCE_DECIBEL_VOLUME;
 
     pa_assert_se(source_set_state(s, PA_SOURCE_IDLE) == 0);
 
     pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_NEW, s->index);
-    pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_NEW_POST], s);
+    pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_PUT], s);
 }
 
 void pa_source_unlink(pa_source *s) {
@@ -185,7 +284,7 @@ void pa_source_unlink(pa_source *s) {
     /* See pa_sink_unlink() for a couple of comments how this function
      * works. */
 
-    linked = PA_SOURCE_LINKED(s->state);
+    linked = PA_SOURCE_IS_LINKED(s->state);
 
     if (linked)
         pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], s);
@@ -205,12 +304,7 @@ void pa_source_unlink(pa_source *s) {
     else
         s->state = PA_SOURCE_UNLINKED;
 
-    s->get_latency = NULL;
-    s->get_volume = NULL;
-    s->set_volume = NULL;
-    s->set_mute = NULL;
-    s->get_mute = NULL;
-    s->set_state = NULL;
+    reset_callbacks(s);
 
     if (linked) {
         pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_REMOVE, s->index);
@@ -225,7 +319,7 @@ static void source_free(pa_object *o) {
     pa_assert(s);
     pa_assert(pa_source_refcnt(s) == 0);
 
-    if (PA_SOURCE_LINKED(s->state))
+    if (PA_SOURCE_IS_LINKED(s->state))
         pa_source_unlink(s);
 
     pa_log_info("Freeing source %u \"%s\"", s->index, s->name);
@@ -237,15 +331,33 @@ static void source_free(pa_object *o) {
 
     pa_hashmap_free(s->thread_info.outputs, NULL, NULL);
 
+    if (s->silence.memblock)
+        pa_memblock_unref(s->silence.memblock);
+
     pa_xfree(s->name);
-    pa_xfree(s->description);
     pa_xfree(s->driver);
+
+    if (s->proplist)
+        pa_proplist_free(s->proplist);
+
     pa_xfree(s);
 }
 
+void pa_source_set_asyncmsgq(pa_source *s, pa_asyncmsgq *q) {
+    pa_source_assert_ref(s);
+
+    s->asyncmsgq = q;
+}
+
+void pa_source_set_rtpoll(pa_source *s, pa_rtpoll *p) {
+    pa_source_assert_ref(s);
+
+    s->rtpoll = p;
+}
+
 int pa_source_update_status(pa_source*s) {
     pa_source_assert_ref(s);
-    pa_assert(PA_SOURCE_LINKED(s->state));
+    pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
     if (s->state == PA_SOURCE_SUSPENDED)
         return 0;
@@ -255,7 +367,7 @@ int pa_source_update_status(pa_source*s) {
 
 int pa_source_suspend(pa_source *s, pa_bool_t suspend) {
     pa_source_assert_ref(s);
-    pa_assert(PA_SOURCE_LINKED(s->state));
+    pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
     if (suspend)
         return source_set_state(s, PA_SOURCE_SUSPENDED);
@@ -263,11 +375,22 @@ int pa_source_suspend(pa_source *s, pa_bool_t suspend) {
         return source_set_state(s, pa_source_used_by(s) ? PA_SOURCE_RUNNING : PA_SOURCE_IDLE);
 }
 
-void pa_source_ping(pa_source *s) {
+void pa_source_process_rewind(pa_source *s, size_t nbytes) {
+    pa_source_output *o;
+    void *state = NULL;
+
     pa_source_assert_ref(s);
-    pa_assert(PA_SOURCE_LINKED(s->state));
+    pa_assert(PA_SOURCE_IS_OPENED(s->thread_info.state));
+
+    if (nbytes <= 0)
+        return;
+
+    pa_log_debug("Processing rewind...");
 
-    pa_asyncmsgq_post(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_PING, NULL, 0, NULL, NULL);
+    while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) {
+        pa_source_output_assert_ref(o);
+        pa_source_output_process_rewind(o, nbytes);
+    }
 }
 
 void pa_source_post(pa_source*s, const pa_memchunk *chunk) {
@@ -275,7 +398,7 @@ void pa_source_post(pa_source*s, const pa_memchunk *chunk) {
     void *state = NULL;
 
     pa_source_assert_ref(s);
-    pa_assert(PA_SOURCE_OPENED(s->thread_info.state));
+    pa_assert(PA_SOURCE_IS_OPENED(s->thread_info.state));
     pa_assert(chunk);
 
     if (s->thread_info.state != PA_SOURCE_RUNNING)
@@ -292,14 +415,18 @@ void pa_source_post(pa_source*s, const pa_memchunk *chunk) {
         else
             pa_volume_memchunk(&vchunk, &s->sample_spec, &s->thread_info.soft_volume);
 
-        while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL)))
+        while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) {
+            pa_source_output_assert_ref(o);
             pa_source_output_push(o, &vchunk);
+        }
 
         pa_memblock_unref(vchunk.memblock);
     } else {
 
-        while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL)))
+        while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) {
+            pa_source_output_assert_ref(o);
             pa_source_output_push(o, chunk);
+        }
     }
 }
 
@@ -307,14 +434,11 @@ pa_usec_t pa_source_get_latency(pa_source *s) {
     pa_usec_t usec;
 
     pa_source_assert_ref(s);
-    pa_assert(PA_SOURCE_LINKED(s->state));
+    pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
-    if (!PA_SOURCE_OPENED(s->state))
+    if (!PA_SOURCE_IS_OPENED(s->state))
         return 0;
 
-    if (s->get_latency)
-        return s->get_latency(s);
-
     if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0)
         return 0;
 
@@ -325,7 +449,7 @@ void pa_source_set_volume(pa_source *s, const pa_cvolume *volume) {
     int changed;
 
     pa_source_assert_ref(s);
-    pa_assert(PA_SOURCE_LINKED(s->state));
+    pa_assert(PA_SOURCE_IS_LINKED(s->state));
     pa_assert(volume);
 
     changed = !pa_cvolume_equal(volume, &s->volume);
@@ -345,7 +469,7 @@ const pa_cvolume *pa_source_get_volume(pa_source *s) {
     pa_cvolume old_volume;
 
     pa_source_assert_ref(s);
-    pa_assert(PA_SOURCE_LINKED(s->state));
+    pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
     old_volume = s->volume;
 
@@ -365,7 +489,7 @@ void pa_source_set_mute(pa_source *s, pa_bool_t mute) {
     int changed;
 
     pa_source_assert_ref(s);
-    pa_assert(PA_SOURCE_LINKED(s->state));
+    pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
     changed = s->muted != mute;
     s->muted = mute;
@@ -384,7 +508,7 @@ pa_bool_t pa_source_get_mute(pa_source *s) {
     pa_bool_t old_muted;
 
     pa_source_assert_ref(s);
-    pa_assert(PA_SOURCE_LINKED(s->state));
+    pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
     old_muted = s->muted;
 
@@ -400,52 +524,32 @@ pa_bool_t pa_source_get_mute(pa_source *s) {
     return s->muted;
 }
 
-void pa_source_set_module(pa_source *s, pa_module *m) {
-    pa_source_assert_ref(s);
-
-    if (m == s->module)
-        return;
-
-    s->module = m;
-
-    pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
-}
-
 void pa_source_set_description(pa_source *s, const char *description) {
+    const char *old;
     pa_source_assert_ref(s);
 
-    if (!description && !s->description)
+    if (!description && !pa_proplist_contains(s->proplist, PA_PROP_DEVICE_DESCRIPTION))
         return;
 
-    if (description && s->description && !strcmp(description, s->description))
+    old = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION);
+
+    if (old && description && !strcmp(old, description))
         return;
 
-    pa_xfree(s->description);
-    s->description = pa_xstrdup(description);
+    if (description)
+        pa_proplist_sets(s->proplist, PA_PROP_DEVICE_DESCRIPTION, description);
+    else
+        pa_proplist_unset(s->proplist, PA_PROP_DEVICE_DESCRIPTION);
 
-    if (PA_SOURCE_LINKED(s->state)) {
-        pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_DESCRIPTION_CHANGED], s);
+    if (PA_SOURCE_IS_LINKED(s->state)) {
         pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+        pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED], s);
     }
 }
 
-void pa_source_set_asyncmsgq(pa_source *s, pa_asyncmsgq *q) {
-    pa_source_assert_ref(s);
-    pa_assert(q);
-
-    s->asyncmsgq = q;
-}
-
-void pa_source_set_rtpoll(pa_source *s, pa_rtpoll *p) {
-    pa_source_assert_ref(s);
-    pa_assert(p);
-
-    s->rtpoll = p;
-}
-
 unsigned pa_source_linked_by(pa_source *s) {
     pa_source_assert_ref(s);
-    pa_assert(PA_SOURCE_LINKED(s->state));
+    pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
     return pa_idxset_size(s->outputs);
 }
@@ -454,7 +558,7 @@ unsigned pa_source_used_by(pa_source *s) {
     unsigned ret;
 
     pa_source_assert_ref(s);
-    pa_assert(PA_SOURCE_LINKED(s->state));
+    pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
     ret = pa_idxset_size(s->outputs);
     pa_assert(ret >= s->n_corked);
@@ -470,6 +574,7 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_
     switch ((pa_source_message_t) code) {
         case PA_SOURCE_MESSAGE_ADD_OUTPUT: {
             pa_source_output *o = PA_SOURCE_OUTPUT(userdata);
+
             pa_hashmap_put(s->thread_info.outputs, PA_UINT32_TO_PTR(o->index), pa_source_output_ref(o));
 
             pa_assert(!o->thread_info.attached);
@@ -478,12 +583,23 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_
             if (o->attach)
                 o->attach(o);
 
+            pa_source_output_set_state_within_thread(o, o->state);
+
+            pa_source_output_update_max_rewind(o, s->thread_info.max_rewind);
+
+            /* We don't just invalidate the requested latency here,
+             * because if we are in a move we might need to fix up the
+             * requested latency. */
+            pa_source_output_set_requested_latency_within_thread(o, o->thread_info.requested_source_latency);
+
             return 0;
         }
 
         case PA_SOURCE_MESSAGE_REMOVE_OUTPUT: {
             pa_source_output *o = PA_SOURCE_OUTPUT(userdata);
 
+            pa_source_output_set_state_within_thread(o, o->state);
+
             if (o->detach)
                 o->detach(o);
 
@@ -493,6 +609,8 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_
             if (pa_hashmap_remove(s->thread_info.outputs, PA_UINT32_TO_PTR(o->index)))
                 pa_source_output_unref(o);
 
+            pa_source_invalidate_requested_latency(s);
+
             return 0;
         }
 
@@ -512,9 +630,6 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_
             *((pa_bool_t*) userdata) = s->thread_info.soft_muted;
             return 0;
 
-        case PA_SOURCE_MESSAGE_PING:
-            return 0;
-
         case PA_SOURCE_MESSAGE_SET_STATE:
             s->thread_info.state = PA_PTR_TO_UINT(userdata);
             return 0;
@@ -525,13 +640,20 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_
              * asyncmsgq and rtpoll fields can be changed without
              * problems */
             pa_source_detach_within_thread(s);
-            break;
+            return 0;
 
         case PA_SOURCE_MESSAGE_ATTACH:
 
             /* Reattach all streams */
             pa_source_attach_within_thread(s);
-            break;
+            return 0;
+
+        case PA_SOURCE_MESSAGE_GET_REQUESTED_LATENCY: {
+
+            pa_usec_t *usec = userdata;
+            *usec = pa_source_get_requested_latency_within_thread(s);
+            return 0;
+        }
 
         case PA_SOURCE_MESSAGE_GET_LATENCY:
         case PA_SOURCE_MESSAGE_MAX:
@@ -556,14 +678,14 @@ int pa_source_suspend_all(pa_core *c, pa_bool_t suspend) {
 
 void pa_source_detach(pa_source *s) {
     pa_source_assert_ref(s);
-    pa_assert(PA_SOURCE_LINKED(s->state));
+    pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
     pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_DETACH, NULL, 0, NULL);
 }
 
 void pa_source_attach(pa_source *s) {
     pa_source_assert_ref(s);
-    pa_assert(PA_SOURCE_LINKED(s->state));
+    pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
     pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_ATTACH, NULL, 0, NULL);
 }
@@ -573,7 +695,7 @@ void pa_source_detach_within_thread(pa_source *s) {
     void *state = NULL;
 
     pa_source_assert_ref(s);
-    pa_assert(PA_SOURCE_LINKED(s->thread_info.state));
+    pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state));
 
     while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL)))
         if (o->detach)
@@ -585,10 +707,83 @@ void pa_source_attach_within_thread(pa_source *s) {
     void *state = NULL;
 
     pa_source_assert_ref(s);
-    pa_assert(PA_SOURCE_LINKED(s->thread_info.state));
+    pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state));
 
     while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL)))
         if (o->attach)
             o->attach(o);
+}
+
+pa_usec_t pa_source_get_requested_latency_within_thread(pa_source *s) {
+    pa_usec_t result = (pa_usec_t) -1;
+    pa_source_output *o;
+    void *state = NULL;
+
+    pa_source_assert_ref(s);
+
+    if (s->thread_info.requested_latency_valid)
+        return s->thread_info.requested_latency;
+
+    while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL)))
+
+        if (o->thread_info.requested_source_latency != (pa_usec_t) -1 &&
+            (result == (pa_usec_t) -1 || result > o->thread_info.requested_source_latency))
+            result = o->thread_info.requested_source_latency;
+
+    if (result != (pa_usec_t) -1) {
+        if (s->max_latency > 0 && result > s->max_latency)
+            result = s->max_latency;
+
+        if (s->min_latency > 0 && result < s->min_latency)
+            result = s->min_latency;
+    }
+
+    s->thread_info.requested_latency = result;
+    s->thread_info.requested_latency_valid = TRUE;
+
+    return result;
+}
+
+pa_usec_t pa_source_get_requested_latency(pa_source *s) {
+    pa_usec_t usec;
+
+    pa_source_assert_ref(s);
+    pa_assert(PA_SOURCE_IS_LINKED(s->state));
+
+    if (!PA_SOURCE_IS_OPENED(s->state))
+        return 0;
+
+    if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_REQUESTED_LATENCY, &usec, 0, NULL) < 0)
+        return 0;
+
+    if (usec == (pa_usec_t) -1)
+        usec = s->max_latency;
+
+    return usec;
+}
+
+void pa_source_set_max_rewind(pa_source *s, size_t max_rewind) {
+    pa_source_output *o;
+    void *state = NULL;
+
+    pa_source_assert_ref(s);
+
+    if (max_rewind == s->thread_info.max_rewind)
+        return;
+
+    s->thread_info.max_rewind = max_rewind;
+
+    while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL)))
+        pa_source_output_update_max_rewind(o, s->thread_info.max_rewind);
+}
+
+void pa_source_invalidate_requested_latency(pa_source *s) {
+
+    pa_source_assert_ref(s);
+    pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state));
+
+    s->thread_info.requested_latency_valid = FALSE;
 
+    if (s->update_requested_latency)
+        s->update_requested_latency(s);
 }
index bd0a912..f9c9cbf 100644 (file)
@@ -33,7 +33,6 @@ typedef struct pa_source pa_source;
 #include <pulse/channelmap.h>
 #include <pulse/volume.h>
 
-#include <pulsecore/core-def.h>
 #include <pulsecore/core.h>
 #include <pulsecore/idxset.h>
 #include <pulsecore/memblock.h>
@@ -54,11 +53,11 @@ typedef enum pa_source_state {
     PA_SOURCE_UNLINKED
 } pa_source_state_t;
 
-static inline pa_bool_t PA_SOURCE_OPENED(pa_source_state_t x) {
+static inline pa_bool_t PA_SOURCE_IS_OPENED(pa_source_state_t x) {
     return x == PA_SOURCE_RUNNING || x == PA_SOURCE_IDLE;
 }
 
-static inline pa_bool_t PA_SOURCE_LINKED(pa_source_state_t x) {
+static inline pa_bool_t PA_SOURCE_IS_LINKED(pa_source_state_t x) {
     return x == PA_SOURCE_RUNNING || x == PA_SOURCE_IDLE || x == PA_SOURCE_SUSPENDED;
 }
 
@@ -71,7 +70,8 @@ struct pa_source {
     pa_source_flags_t flags;
 
     char *name;
-    char *description, *driver;              /* may be NULL */
+    char *driver;                             /* may be NULL */
+    pa_proplist *proplist;
 
     pa_module *module;                        /* may be NULL */
 
@@ -87,15 +87,20 @@ struct pa_source {
     pa_bool_t refresh_volume;
     pa_bool_t refresh_muted;
 
+    pa_asyncmsgq *asyncmsgq;
+    pa_rtpoll *rtpoll;
+
+    pa_memchunk silence;
+
+    pa_usec_t min_latency; /* we won't go below this latency setting */
+    pa_usec_t max_latency; /* An upper limit for the latencies */
+
     int (*set_state)(pa_source*source, pa_source_state_t state); /* may be NULL */
     int (*set_volume)(pa_source *s);         /* dito */
     int (*get_volume)(pa_source *s);         /* dito */
     int (*set_mute)(pa_source *s);           /* dito */
     int (*get_mute)(pa_source *s);           /* dito */
-    pa_usec_t (*get_latency)(pa_source *s);  /* dito */
-
-    pa_asyncmsgq *asyncmsgq;
-    pa_rtpoll *rtpoll;
+    void (*update_requested_latency)(pa_source *s); /* dito */
 
     /* Contains copies of the above data so that the real-time worker
      * thread can work without access locking */
@@ -104,6 +109,13 @@ struct pa_source {
         pa_hashmap *outputs;
         pa_cvolume soft_volume;
         pa_bool_t soft_muted;
+
+        pa_bool_t requested_latency_valid;
+        size_t requested_latency;
+
+        /* Then number of bytes this source will be rewound for at
+         * max */
+        size_t max_rewind;
     } thread_info;
 
     void *userdata;
@@ -120,27 +132,50 @@ typedef enum pa_source_message {
     PA_SOURCE_MESSAGE_GET_MUTE,
     PA_SOURCE_MESSAGE_SET_MUTE,
     PA_SOURCE_MESSAGE_GET_LATENCY,
+    PA_SOURCE_MESSAGE_GET_REQUESTED_LATENCY,
     PA_SOURCE_MESSAGE_SET_STATE,
-    PA_SOURCE_MESSAGE_PING,
     PA_SOURCE_MESSAGE_ATTACH,
     PA_SOURCE_MESSAGE_DETACH,
     PA_SOURCE_MESSAGE_MAX
 } pa_source_message_t;
 
+typedef struct pa_source_new_data {
+    char *name;
+    pa_bool_t namereg_fail;
+    pa_proplist *proplist;
+
+    const char *driver;
+    pa_module *module;
+
+    pa_sample_spec sample_spec;
+    pa_bool_t sample_spec_is_set;
+    pa_channel_map channel_map;
+    pa_bool_t channel_map_is_set;
+
+    pa_cvolume volume;
+    pa_bool_t volume_is_set;
+    pa_bool_t muted;
+    pa_bool_t muted_is_set;
+} pa_source_new_data;
+
+pa_source_new_data* pa_source_new_data_init(pa_source_new_data *data);
+void pa_source_new_data_set_name(pa_source_new_data *data, const char *name);
+void pa_source_new_data_set_sample_spec(pa_source_new_data *data, const pa_sample_spec *spec);
+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_done(pa_source_new_data *data);
+
 /* To be called exclusively by the source driver, from main context */
 
 pa_source* pa_source_new(
         pa_core *core,
-        const char *driver,
-        const char *name,
-        int namereg_fail,
-        const pa_sample_spec *spec,
-        const pa_channel_map *map);
+        pa_source_new_data *data,
+        pa_source_flags_t flags);
 
 void pa_source_put(pa_source *s);
 void pa_source_unlink(pa_source *s);
 
-void pa_source_set_module(pa_source *s, pa_module *m);
 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);
@@ -151,13 +186,12 @@ void pa_source_attach(pa_source *s);
 /* May be called by everyone, from main context */
 
 pa_usec_t pa_source_get_latency(pa_source *s);
+pa_usec_t pa_source_get_requested_latency(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);
 
-void pa_source_ping(pa_source *s);
-
 void pa_source_set_volume(pa_source *source, const pa_cvolume *volume);
 const pa_cvolume *pa_source_get_volume(pa_source *source);
 void pa_source_set_mute(pa_source *source, pa_bool_t mute);
@@ -170,10 +204,19 @@ unsigned pa_source_used_by(pa_source *s); /* Number of connected streams that ar
 /* To be called exclusively by the source driver, from IO context */
 
 void pa_source_post(pa_source*s, const pa_memchunk *b);
+void pa_source_process_rewind(pa_source *s, size_t nbytes);
 
 int pa_source_process_msg(pa_msgobject *o, int code, void *userdata, int64_t, pa_memchunk *chunk);
 
 void pa_source_attach_within_thread(pa_source *s);
 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);
+
+/* To be called exclusively by source output drivers, from IO context */
+
+void pa_source_invalidate_requested_latency(pa_source *s);
+
 #endif
index 1c0850b..d3555a2 100644 (file)
@@ -24,7 +24,7 @@
   USA.
 ***/
 
-#include <pulsecore/gccmacro.h>
+#include <pulse/gccmacro.h>
 
 typedef struct pa_strbuf pa_strbuf;
 
index 556fe80..7616cd1 100644 (file)
 
 #include "tagstruct.h"
 
+#define MAX_TAG_SIZE (64*1024)
+
 struct pa_tagstruct {
     uint8_t *data;
     size_t length, allocated;
     size_t rindex;
 
-    int dynamic;
+    pa_bool_t dynamic;
 };
 
 pa_tagstruct *pa_tagstruct_new(const uint8_t* data, size_t length) {
@@ -161,7 +163,7 @@ void pa_tagstruct_put_arbitrary(pa_tagstruct *t, const void *p, size_t length) {
     t->length += 5+length;
 }
 
-void pa_tagstruct_put_boolean(pa_tagstruct*t, int b) {
+void pa_tagstruct_put_boolean(pa_tagstruct*t, pa_bool_t b) {
     pa_assert(t);
 
     extend(t, 1);
@@ -254,6 +256,32 @@ void pa_tagstruct_put_cvolume(pa_tagstruct *t, const pa_cvolume *cvolume) {
     }
 }
 
+void pa_tagstruct_put_proplist(pa_tagstruct *t, pa_proplist *p) {
+    void *state = NULL;
+    pa_assert(t);
+    pa_assert(p);
+
+    extend(t, 1);
+
+    t->data[t->length++] = PA_TAG_PROPLIST;
+
+    for (;;) {
+        const char *k;
+        const void *d;
+        size_t l;
+
+        if (!(k = pa_proplist_iterate(p, &state)))
+            break;
+
+        pa_tagstruct_puts(t, k);
+        pa_assert_se(pa_proplist_get(p, k, &d, &l) >= 0);
+        pa_tagstruct_putu32(t, (uint32_t) l);
+        pa_tagstruct_put_arbitrary(t, d, l);
+    }
+
+    pa_tagstruct_puts(t, NULL);
+}
+
 int pa_tagstruct_gets(pa_tagstruct*t, const char **s) {
     int error = 0;
     size_t n;
@@ -379,7 +407,7 @@ const uint8_t* pa_tagstruct_data(pa_tagstruct*t, size_t *l) {
     return t->data;
 }
 
-int pa_tagstruct_get_boolean(pa_tagstruct*t, int *b) {
+int pa_tagstruct_get_boolean(pa_tagstruct*t, pa_bool_t *b) {
     pa_assert(t);
     pa_assert(b);
 
@@ -387,9 +415,9 @@ int pa_tagstruct_get_boolean(pa_tagstruct*t, int *b) {
         return -1;
 
     if (t->data[t->rindex] == PA_TAG_BOOLEAN_TRUE)
-        *b = 1;
+        *b = TRUE;
     else if (t->data[t->rindex] == PA_TAG_BOOLEAN_FALSE)
-        *b = 0;
+        *b = FALSE;
     else
         return -1;
 
@@ -529,6 +557,52 @@ int pa_tagstruct_get_cvolume(pa_tagstruct *t, pa_cvolume *cvolume) {
     return 0;
 }
 
+int pa_tagstruct_get_proplist(pa_tagstruct *t, pa_proplist *p) {
+    size_t saved_rindex;
+
+    pa_assert(t);
+    pa_assert(p);
+
+    if (t->rindex+1 > t->length)
+        return -1;
+
+    if (t->data[t->rindex] != PA_TAG_PROPLIST)
+        return -1;
+
+    saved_rindex = t->rindex;
+    t->rindex++;
+
+    for (;;) {
+        const char *k;
+        const void *d;
+        uint32_t length;
+
+        if (pa_tagstruct_gets(t, &k) < 0)
+            goto fail;
+
+        if (!k)
+            break;
+
+        if (pa_tagstruct_getu32(t, &length) < 0)
+            goto fail;
+
+        if (length > MAX_TAG_SIZE)
+            goto fail;
+
+        if (pa_tagstruct_get_arbitrary(t, &d, length) < 0)
+            goto fail;
+
+        if (pa_proplist_set(p, k, d, length) < 0)
+            goto fail;
+    }
+
+    return 0;
+
+fail:
+    t->rindex = saved_rindex;
+    return -1;
+}
+
 void pa_tagstruct_put(pa_tagstruct *t, ...) {
     va_list va;
     pa_assert(t);
@@ -591,6 +665,10 @@ void pa_tagstruct_put(pa_tagstruct *t, ...) {
                 pa_tagstruct_put_cvolume(t, va_arg(va, pa_cvolume *));
                 break;
 
+            case PA_TAG_PROPLIST:
+                pa_tagstruct_put_proplist(t, va_arg(va, pa_proplist *));
+                break;
+
             default:
                 pa_assert_not_reached();
         }
@@ -643,7 +721,7 @@ int pa_tagstruct_get(pa_tagstruct *t, ...) {
 
             case PA_TAG_BOOLEAN_TRUE:
             case PA_TAG_BOOLEAN_FALSE:
-                ret = pa_tagstruct_get_boolean(t, va_arg(va, int*));
+                ret = pa_tagstruct_get_boolean(t, va_arg(va, pa_bool_t*));
                 break;
 
             case PA_TAG_TIMEVAL:
@@ -662,6 +740,10 @@ int pa_tagstruct_get(pa_tagstruct *t, ...) {
                 ret = pa_tagstruct_get_cvolume(t, va_arg(va, pa_cvolume *));
                 break;
 
+            case PA_TAG_PROPLIST:
+                ret = pa_tagstruct_get_proplist(t, va_arg(va, pa_proplist *));
+                break;
+
             default:
                 pa_assert_not_reached();
         }
index e9bb9ac..8699e6c 100644 (file)
 #include <pulse/sample.h>
 #include <pulse/channelmap.h>
 #include <pulse/volume.h>
+#include <pulse/proplist.h>
+#include <pulse/gccmacro.h>
+
+#include <pulsecore/macro.h>
 
 typedef struct pa_tagstruct pa_tagstruct;
 
@@ -51,7 +55,8 @@ enum {
     PA_TAG_TIMEVAL = 'T',
     PA_TAG_USEC = 'U'  /* 64bit unsigned */,
     PA_TAG_CHANNEL_MAP = 'm',
-    PA_TAG_CVOLUME = 'v'
+    PA_TAG_CVOLUME = 'v',
+    PA_TAG_PROPLIST = 'P'
 };
 
 pa_tagstruct *pa_tagstruct_new(const uint8_t* data, size_t length);
@@ -70,11 +75,12 @@ void pa_tagstruct_putu64(pa_tagstruct*t, uint64_t i);
 void pa_tagstruct_puts64(pa_tagstruct*t, int64_t i);
 void pa_tagstruct_put_sample_spec(pa_tagstruct *t, const pa_sample_spec *ss);
 void pa_tagstruct_put_arbitrary(pa_tagstruct*t, const void *p, size_t length);
-void pa_tagstruct_put_boolean(pa_tagstruct*t, int b);
+void pa_tagstruct_put_boolean(pa_tagstruct*t, pa_bool_t b);
 void pa_tagstruct_put_timeval(pa_tagstruct*t, const struct timeval *tv);
 void pa_tagstruct_put_usec(pa_tagstruct*t, pa_usec_t u);
 void pa_tagstruct_put_channel_map(pa_tagstruct *t, const pa_channel_map *map);
 void pa_tagstruct_put_cvolume(pa_tagstruct *t, const pa_cvolume *cvolume);
+void pa_tagstruct_put_proplist(pa_tagstruct *t, pa_proplist *p);
 
 int pa_tagstruct_get(pa_tagstruct *t, ...);
 
@@ -85,11 +91,12 @@ int pa_tagstruct_getu64(pa_tagstruct*t, uint64_t *i);
 int pa_tagstruct_gets64(pa_tagstruct*t, int64_t *i);
 int pa_tagstruct_get_sample_spec(pa_tagstruct *t, pa_sample_spec *ss);
 int pa_tagstruct_get_arbitrary(pa_tagstruct *t, const void **p, size_t length);
-int pa_tagstruct_get_boolean(pa_tagstruct *t, int *b);
+int pa_tagstruct_get_boolean(pa_tagstruct *t, pa_bool_t *b);
 int pa_tagstruct_get_timeval(pa_tagstruct*t, struct timeval *tv);
 int pa_tagstruct_get_usec(pa_tagstruct*t, pa_usec_t *u);
 int pa_tagstruct_get_channel_map(pa_tagstruct *t, pa_channel_map *map);
 int pa_tagstruct_get_cvolume(pa_tagstruct *t, pa_cvolume *v);
+int pa_tagstruct_get_proplist(pa_tagstruct *t, pa_proplist *p);
 
 
 #endif
index 9b87942..7e39c57 100644 (file)
 
 PA_STATIC_TLS_DECLARE_NO_FREE(thread_mq);
 
-static void asyncmsgq_cb(pa_mainloop_api*api, pa_io_event* e, int fd, pa_io_event_flags_t events, void *userdata) {
+static void asyncmsgq_read_cb(pa_mainloop_api*api, pa_io_event* e, int fd, pa_io_event_flags_t events, void *userdata) {
     pa_thread_mq *q = userdata;
     pa_asyncmsgq *aq;
 
-    pa_assert(pa_asyncmsgq_get_fd(q->outq) == fd);
+    pa_assert(pa_asyncmsgq_read_fd(q->outq) == fd);
     pa_assert(events == PA_IO_EVENT_INPUT);
 
     pa_asyncmsgq_ref(aq = q->outq);
-    pa_asyncmsgq_after_poll(aq);
+    pa_asyncmsgq_write_after_poll(aq);
 
     for (;;) {
         pa_msgobject *object;
@@ -68,14 +68,24 @@ static void asyncmsgq_cb(pa_mainloop_api*api, pa_io_event* e, int fd, pa_io_even
             pa_asyncmsgq_done(aq, ret);
         }
 
-        if (pa_asyncmsgq_before_poll(aq) == 0)
+        if (pa_asyncmsgq_read_before_poll(aq) == 0)
             break;
     }
 
     pa_asyncmsgq_unref(aq);
 }
 
-void pa_thread_mq_init(pa_thread_mq *q, pa_mainloop_api *mainloop) {
+static void asyncmsgq_write_cb(pa_mainloop_api*api, pa_io_event* e, int fd, pa_io_event_flags_t events, void *userdata) {
+    pa_thread_mq *q = userdata;
+
+    pa_assert(pa_asyncmsgq_write_fd(q->inq) == fd);
+    pa_assert(events == PA_IO_EVENT_INPUT);
+
+    pa_asyncmsgq_write_after_poll(q->inq);
+    pa_asyncmsgq_write_before_poll(q->inq);
+}
+
+void pa_thread_mq_init(pa_thread_mq *q, pa_mainloop_api *mainloop, pa_rtpoll *rtpoll) {
     pa_assert(q);
     pa_assert(mainloop);
 
@@ -83,15 +93,22 @@ void pa_thread_mq_init(pa_thread_mq *q, pa_mainloop_api *mainloop) {
     pa_assert_se(q->inq = pa_asyncmsgq_new(0));
     pa_assert_se(q->outq = pa_asyncmsgq_new(0));
 
-    pa_assert_se(pa_asyncmsgq_before_poll(q->outq) == 0);
-    pa_assert_se(q->io_event = mainloop->io_new(mainloop, pa_asyncmsgq_get_fd(q->outq), PA_IO_EVENT_INPUT, asyncmsgq_cb, q));
+    pa_assert_se(pa_asyncmsgq_read_before_poll(q->outq) == 0);
+    pa_assert_se(q->read_event = mainloop->io_new(mainloop, pa_asyncmsgq_read_fd(q->outq), PA_IO_EVENT_INPUT, asyncmsgq_read_cb, q));
+
+    pa_asyncmsgq_write_before_poll(q->inq);
+    pa_assert_se(q->write_event = mainloop->io_new(mainloop, pa_asyncmsgq_write_fd(q->inq), PA_IO_EVENT_INPUT, asyncmsgq_write_cb, q));
+
+    pa_rtpoll_item_new_asyncmsgq_read(rtpoll, PA_RTPOLL_EARLY, q->inq);
+    pa_rtpoll_item_new_asyncmsgq_write(rtpoll, PA_RTPOLL_LATE, q->outq);
 }
 
 void pa_thread_mq_done(pa_thread_mq *q) {
     pa_assert(q);
 
-    q->mainloop->io_free(q->io_event);
-    q->io_event = NULL;
+    q->mainloop->io_free(q->read_event);
+    q->mainloop->io_free(q->write_event);
+    q->read_event = q->write_event = NULL;
 
     pa_asyncmsgq_unref(q->inq);
     pa_asyncmsgq_unref(q->outq);
index 13b6e01..0ae49f8 100644 (file)
@@ -26,6 +26,7 @@
 
 #include <pulse/mainloop-api.h>
 #include <pulsecore/asyncmsgq.h>
+#include <pulsecore/rtpoll.h>
 
 /* Two way communication between a thread and a mainloop. Before the
  * thread is started a pa_pthread_mq should be initialized and than
 typedef struct pa_thread_mq {
     pa_mainloop_api *mainloop;
     pa_asyncmsgq *inq, *outq;
-    pa_io_event *io_event;
+    pa_io_event *read_event, *write_event;
 } pa_thread_mq;
 
-void pa_thread_mq_init(pa_thread_mq *q, pa_mainloop_api *mainloop);
+void pa_thread_mq_init(pa_thread_mq *q, pa_mainloop_api *mainloop, pa_rtpoll *rtpoll);
 void pa_thread_mq_done(pa_thread_mq *q);
 
 /* Install the specified pa_thread_mq object for the current thread */
index 4cebded..9b4be29 100644 (file)
@@ -34,7 +34,7 @@
 
 #include "time-smoother.h"
 
-#define HISTORY_MAX 50
+#define HISTORY_MAX 64
 
 /*
  * Implementation of a time smoothing algorithm to synchronize remote
@@ -61,7 +61,6 @@
 
 struct pa_smoother {
     pa_usec_t adjust_time, history_time;
-    pa_bool_t monotonic;
 
     pa_usec_t time_offset;
 
@@ -70,27 +69,34 @@ struct pa_smoother {
 
     pa_usec_t ex, ey;     /* Point e, which we estimated before and need to smooth to */
     double de;            /* Gradient we estimated for point e */
+    pa_usec_t ry;         /* The original y value for ex */
 
                           /* History of last measurements */
     pa_usec_t history_x[HISTORY_MAX], history_y[HISTORY_MAX];
     unsigned history_idx, n_history;
 
     /* To even out for monotonicity */
-    pa_usec_t last_y;
+    pa_usec_t last_y, last_x;
 
     /* Cached parameters for our interpolation polynomial y=ax^3+b^2+cx */
     double a, b, c;
     pa_bool_t abc_valid;
 
-    pa_bool_t paused;
+    pa_bool_t monotonic:1;
+    pa_bool_t paused:1;
+
     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) {
+pa_smoother* pa_smoother_new(pa_usec_t adjust_time, pa_usec_t history_time, pa_bool_t monotonic, unsigned min_history) {
     pa_smoother *s;
 
     pa_assert(adjust_time > 0);
     pa_assert(history_time > 0);
+    pa_assert(min_history >= 2);
+    pa_assert(min_history <= HISTORY_MAX);
 
     s = pa_xnew(pa_smoother, 1);
     s->adjust_time = adjust_time;
@@ -101,18 +107,20 @@ pa_smoother* pa_smoother_new(pa_usec_t adjust_time, pa_usec_t history_time, pa_b
     s->px = s->py = 0;
     s->dp = 1;
 
-    s->ex = s->ey = 0;
+    s->ex = s->ey = s->ry = 0;
     s->de = 1;
 
     s->history_idx = 0;
     s->n_history = 0;
 
-    s->last_y = 0;
+    s->last_y = s->last_x = 0;
 
     s->abc_valid = FALSE;
 
     s->paused = FALSE;
 
+    s->min_history = min_history;
+
     return s;
 }
 
@@ -122,39 +130,58 @@ void pa_smoother_free(pa_smoother* s) {
     pa_xfree(s);
 }
 
+#define REDUCE(x)                               \
+    do {                                        \
+        x = (x) % HISTORY_MAX;                  \
+    } while(FALSE)
+
+#define REDUCE_INC(x)                           \
+    do {                                        \
+        x = ((x)+1) % HISTORY_MAX;              \
+    } while(FALSE)
+
+
 static void drop_old(pa_smoother *s, pa_usec_t x) {
-    unsigned j;
 
-    /* First drop items from history which are too old, but make sure
-     * to always keep two entries in the history */
+    /* Drop items from history which are too old, but make sure to
+     * always keep min_history in the history */
 
-    for (j = s->n_history; j > 2; j--) {
+    while (s->n_history > s->min_history) {
 
-        if (s->history_x[s->history_idx] + s->history_time >= x) {
+        if (s->history_x[s->history_idx] + s->history_time >= x)
             /* This item is still valid, and thus all following ones
              * are too, so let's quit this loop */
             break;
-        }
 
         /* Item is too old, let's drop it */
-        s->history_idx ++;
-        while (s->history_idx >= HISTORY_MAX)
-            s->history_idx -= HISTORY_MAX;
+        REDUCE_INC(s->history_idx);
 
         s->n_history --;
     }
 }
 
 static void add_to_history(pa_smoother *s, pa_usec_t x, pa_usec_t y) {
-    unsigned j;
+    unsigned j, i;
     pa_assert(s);
 
+    /* First try to update an existing history entry */
+    i = s->history_idx;
+    for (j = s->n_history; j > 0; j--) {
+
+        if (s->history_x[i] == x) {
+            s->history_y[i] = y;
+            return;
+        }
+
+        REDUCE_INC(i);
+    }
+
+    /* Drop old entries */
     drop_old(s, x);
 
     /* Calculate position for new entry */
     j = s->history_idx + s->n_history;
-    while (j >= HISTORY_MAX)
-        j -= HISTORY_MAX;
+    REDUCE(j);
 
     /* Fill in entry */
     s->history_x[j] = x;
@@ -164,8 +191,9 @@ static void add_to_history(pa_smoother *s, pa_usec_t x, pa_usec_t y) {
     s->n_history ++;
 
     /* And make sure we don't store more entries than fit in */
-    if (s->n_history >= HISTORY_MAX) {
+    if (s->n_history > HISTORY_MAX) {
         s->history_idx += s->n_history - HISTORY_MAX;
+        REDUCE(s->history_idx);
         s->n_history = HISTORY_MAX;
     }
 }
@@ -175,7 +203,9 @@ static double avg_gradient(pa_smoother *s, pa_usec_t x) {
     int64_t ax = 0, ay = 0, k, t;
     double r;
 
-    drop_old(s, x);
+    /* Too few measurements, assume gradient of 1 */
+    if (s->n_history < s->min_history)
+        return 1;
 
     /* First, calculate average of all measurements */
     i = s->history_idx;
@@ -185,15 +215,10 @@ static double avg_gradient(pa_smoother *s, pa_usec_t x) {
         ay += s->history_y[i];
         c++;
 
-        i++;
-        while (i >= HISTORY_MAX)
-            i -= HISTORY_MAX;
+        REDUCE_INC(i);
     }
 
-    /* Too few measurements, assume gradient of 1 */
-    if (c < 2)
-        return 1;
-
+    pa_assert(c >= s->min_history);
     ax /= c;
     ay /= c;
 
@@ -210,14 +235,45 @@ static double avg_gradient(pa_smoother *s, pa_usec_t x) {
         k += dx*dy;
         t += dx*dx;
 
-        i++;
-        while (i >= HISTORY_MAX)
-            i -= HISTORY_MAX;
+        REDUCE_INC(i);
     }
 
     r = (double) k / t;
 
-    return s->monotonic && r < 0 ? 0 : r;
+    return (s->monotonic && r < 0) ? 0 : r;
+}
+
+static void calc_abc(pa_smoother *s) {
+    pa_usec_t ex, ey, px, py;
+    int64_t kx, ky;
+    double de, dp;
+
+    pa_assert(s);
+
+    if (s->abc_valid)
+        return;
+
+    /* We have two points: (ex|ey) and (px|py) with two gradients at
+     * these points de and dp. We do a polynomial
+     * interpolation of degree 3 with these 6 values */
+
+    ex = s->ex; ey = s->ey;
+    px = s->px; py = s->py;
+    de = s->de; dp = s->dp;
+
+    pa_assert(ex < px);
+
+    /* To increase the dynamic range and symplify calculation, we
+     * move these values to the origin */
+    kx = (int64_t) px - (int64_t) ex;
+    ky = (int64_t) py - (int64_t) ey;
+
+    /* Calculate a, b, c for y=ax^3+bx^2+cx */
+    s->c = de;
+    s->b = (((double) (3*ky)/kx - dp - 2*de)) / kx;
+    s->a = (dp/kx - 2*s->b - de/kx) / (3*kx);
+
+    s->abc_valid = TRUE;
 }
 
 static void estimate(pa_smoother *s, pa_usec_t x, pa_usec_t *y, double *deriv) {
@@ -242,36 +298,10 @@ static void estimate(pa_smoother *s, pa_usec_t x, pa_usec_t *y, double *deriv) {
 
     } else {
 
-        if (!s->abc_valid) {
-            pa_usec_t ex, ey, px, py;
-            int64_t kx, ky;
-            double de, dp;
-
-            /* Ok, we're not yet on track, thus let's interpolate, and
-             * make sure that the first derivative is smooth */
-
-            /* We have two points: (ex|ey) and (px|py) with two gradients
-             * at these points de and dp. We do a polynomial interpolation
-             * of degree 3 with these 6 values */
-
-            ex = s->ex; ey = s->ey;
-            px = s->px; py = s->py;
-            de = s->de; dp = s->dp;
-
-            pa_assert(ex < px);
-
-            /* To increase the dynamic range and symplify calculation, we
-             * move these values to the origin */
-            kx = (int64_t) px - (int64_t) ex;
-            ky = (int64_t) py - (int64_t) ey;
+        /* Ok, we're not yet on track, thus let's interpolate, and
+         * make sure that the first derivative is smooth */
 
-            /* Calculate a, b, c for y=ax^3+b^2+cx */
-            s->c = de;
-            s->b = (((double) (3*ky)/kx - dp - 2*de)) / kx;
-            s->a = (dp/kx - 2*s->b - de/kx) / (3*kx);
-
-            s->abc_valid = TRUE;
-        }
+        calc_abc(s);
 
         /* Move to origin */
         x -= s->ex;
@@ -290,11 +320,6 @@ static void estimate(pa_smoother *s, pa_usec_t x, pa_usec_t *y, double *deriv) {
     /* Guarantee monotonicity */
     if (s->monotonic) {
 
-        if (*y < s->last_y)
-            *y = s->last_y;
-        else
-            s->last_y = *y;
-
         if (deriv && *deriv < 0)
             *deriv = 0;
     }
@@ -303,23 +328,26 @@ static void estimate(pa_smoother *s, pa_usec_t x, pa_usec_t *y, double *deriv) {
 void pa_smoother_put(pa_smoother *s, pa_usec_t x, pa_usec_t y) {
     pa_usec_t ney;
     double nde;
+    pa_bool_t is_new;
 
     pa_assert(s);
-    pa_assert(x >= s->time_offset);
 
     /* Fix up x value */
     if (s->paused)
         x = s->pause_time;
 
-    pa_assert(x >= s->time_offset);
-    x -= s->time_offset;
+    x = PA_LIKELY(x >= s->time_offset) ? x - s->time_offset : 0;
 
-    pa_assert(x >= s->ex);
+    is_new = x >= s->ex;
 
-    /* First, we calculate the position we'd estimate for x, so that
-     * we can adjust our position smoothly from this one */
-    estimate(s, x, &ney, &nde);
-    s->ex = x; s->ey = ney; s->de = nde;
+    if (is_new) {
+        /* First, we calculate the position we'd estimate for x, so that
+         * 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;
+    }
 
     /* Then, we add the new measurement to our history */
     add_to_history(s, x, y);
@@ -328,28 +356,41 @@ 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 = x + s->adjust_time;
-    s->py = y + s->dp *s->adjust_time;
+    s->px = s->ex + s->adjust_time;
+    s->py = s->ry + s->dp *s->adjust_time;
 
     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); */
 }
 
 pa_usec_t pa_smoother_get(pa_smoother *s, pa_usec_t x) {
     pa_usec_t y;
 
     pa_assert(s);
-    pa_assert(x >= s->time_offset);
 
     /* Fix up x value */
     if (s->paused)
         x = s->pause_time;
 
-    pa_assert(x >= s->time_offset);
-    x -= s->time_offset;
-
-    pa_assert(x >= s->ex);
+    x = PA_LIKELY(x >= s->time_offset) ? x - s->time_offset : 0;
 
     estimate(s, x, &y, NULL);
+
+    if (s->monotonic) {
+
+        /* Make sure the querier doesn't jump forth and back. */
+        pa_assert(x >= s->last_x);
+        s->last_x = x;
+
+        if (y < s->last_y)
+            y = s->last_y;
+        else
+            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); */
+
     return y;
 }
 
@@ -357,6 +398,8 @@ void pa_smoother_set_time_offset(pa_smoother *s, pa_usec_t offset) {
     pa_assert(s);
 
     s->time_offset = offset;
+
+/*     pa_log_debug("offset(%llu)", (unsigned long long) offset); */
 }
 
 void pa_smoother_pause(pa_smoother *s, pa_usec_t x) {
@@ -365,6 +408,8 @@ void pa_smoother_pause(pa_smoother *s, pa_usec_t x) {
     if (s->paused)
         return;
 
+/*     pa_log_debug("pause(%llu)", (unsigned long long)  x); */
+
     s->paused = TRUE;
     s->pause_time = x;
 }
@@ -377,6 +422,32 @@ void pa_smoother_resume(pa_smoother *s, pa_usec_t x) {
 
     pa_assert(x >= s->pause_time);
 
+/*     pa_log_debug("resume(%llu)", (unsigned long long) x); */
+
     s->paused = FALSE;
     s->time_offset += x - s->pause_time;
 }
+
+pa_usec_t pa_smoother_translate(pa_smoother *s, pa_usec_t x, pa_usec_t y_delay) {
+    pa_usec_t ney;
+    double nde;
+
+    pa_assert(s);
+
+    /* Fix up x value */
+    if (s->paused)
+        x = s->pause_time;
+
+    x = PA_LIKELY(x >= s->time_offset) ? x - s->time_offset : 0;
+
+    estimate(s, x, &ney, &nde);
+
+    /* Play safe and take the larger gradient, so that we wakeup
+     * earlier when this is used for sleeping */
+    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); */
+
+    return (pa_usec_t) ((double) y_delay / nde);
+}
index 8b8512e..b301b48 100644 (file)
 
 typedef struct pa_smoother pa_smoother;
 
-pa_smoother* pa_smoother_new(pa_usec_t adjust_time, pa_usec_t history_time, pa_bool_t monotonic);
+pa_smoother* pa_smoother_new(pa_usec_t x_adjust_time, pa_usec_t x_history_time, pa_bool_t monotonic, unsigned min_history);
 void pa_smoother_free(pa_smoother* s);
 
+/* Adds a new value to our dataset. x = local/system time, y = remote time */
 void pa_smoother_put(pa_smoother *s, pa_usec_t x, pa_usec_t y);
+
+/* Returns an interpolated value based on the dataset. x = local/system time, return value = remote time */
 pa_usec_t pa_smoother_get(pa_smoother *s, pa_usec_t x);
 
-void pa_smoother_set_time_offset(pa_smoother *s, pa_usec_t offset);
+/* Translates a time span from the remote time domain to the local one. x = local/system time when to estimate, y_delay = remote time span */
+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);
index f79c19c..cf5da64 100644 (file)
@@ -29,9 +29,9 @@
 #include <stdlib.h>
 
 #include <pulse/xmalloc.h>
+#include <pulse/gccmacro.h>
 
 #include <pulsecore/dynarray.h>
-#include <pulsecore/gccmacro.h>
 #include <pulsecore/macro.h>
 
 #include "tokenizer.h"
index 09b2004..0e10ed8 100644 (file)
@@ -44,7 +44,7 @@ static void producer(void *_q) {
         pa_asyncq_push(q, PA_UINT_TO_PTR(i+1), 1);
     }
 
-    pa_asyncq_push(q, PA_UINT_TO_PTR(-1), 1);
+    pa_asyncq_push(q, PA_UINT_TO_PTR(-1), TRUE);
     printf("pushed end\n");
 }
 
@@ -56,7 +56,7 @@ static void consumer(void *_q) {
     sleep(1);
 
     for (i = 0;; i++) {
-        p = pa_asyncq_pop(q, 1);
+        p = pa_asyncq_pop(q, TRUE);
 
         if (p == PA_UINT_TO_PTR(-1))
             break;
index 98f36b6..d26d2cf 100644 (file)
@@ -4,7 +4,7 @@
 #include <assert.h>
 
 #include <pulse/channelmap.h>
-#include <pulsecore/gccmacro.h>
+#include <pulse/gccmacro.h>
 
 int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char *argv[]) {
     char cm[PA_CHANNEL_MAP_SNPRINT_MAX];
index d582e9c..4563c0f 100644 (file)
@@ -30,7 +30,7 @@
 #include <signal.h>
 
 #include <pulse/mainloop.h>
-#include <pulsecore/gccmacro.h>
+#include <pulse/gccmacro.h>
 
 #ifdef TEST2
 #include <pulse/mainloop-signal.h>
index 85a509d..f894d2f 100644 (file)
@@ -42,10 +42,9 @@ static pa_context *context = NULL;
 static pa_stream *stream = NULL;
 static pa_mainloop_api *mainloop_api = NULL;
 
-static void stream_write_cb(pa_stream *p, size_t length, void *userdata) {
-
+static void stream_write_cb(pa_stream *p, size_t nbytes, void *userdata) {
     /* Just some silence */
-    pa_stream_write(p, pa_xmalloc0(length), length, pa_xfree, 0, PA_SEEK_RELATIVE);
+    pa_stream_write(p, pa_xmalloc0(nbytes), nbytes, pa_xfree, 0, PA_SEEK_RELATIVE);
 }
 
 /* This is called whenever the context status changes */
@@ -63,7 +62,7 @@ static void context_state_callback(pa_context *c, void *userdata) {
             static const pa_sample_spec ss = {
                 .format = PA_SAMPLE_S16LE,
                 .rate = 44100,
-                .channels = 1
+                .channels = 2
             };
 
             fprintf(stderr, "Connection established.\n");
@@ -112,9 +111,10 @@ int main(int argc, char *argv[]) {
     pa_threaded_mainloop_start(m);
 
     for (k = 0; k < 5000; k++) {
-        int success = 0, changed = 0;
+        pa_bool_t success = FALSE, changed = FALSE;
         pa_usec_t t, rtc;
         struct timeval now, tv;
+        pa_bool_t playing = FALSE;
 
         pa_threaded_mainloop_lock(m);
 
@@ -122,22 +122,26 @@ int main(int argc, char *argv[]) {
             const pa_timing_info *info;
 
             if (pa_stream_get_time(stream, &t) >= 0)
-                success = 1;
+                success = TRUE;
 
-            if ((info = pa_stream_get_timing_info(stream)))
-                if (last_info.tv_usec != info->timestamp.tv_usec || last_info.tv_sec != info->timestamp.tv_sec) {
-                    changed = 1;
+            if ((info = pa_stream_get_timing_info(stream))) {
+                if (memcmp(&last_info, &info->timestamp, sizeof(struct timeval))) {
+                    changed = TRUE;
                     last_info = info->timestamp;
                 }
+                if (info->playing)
+                    playing = TRUE;
+            }
         }
 
         pa_threaded_mainloop_unlock(m);
 
-        if (success) {
-            pa_gettimeofday(&now);
+        pa_gettimeofday(&now);
 
+        if (success) {
             rtc = pa_timeval_diff(&now, &start);
-            printf("%i\t%llu\t%llu\t%llu\t%llu\t%u\n", k, (unsigned long long) rtc, (unsigned long long) t, (unsigned long long) (rtc-old_rtc), (unsigned long long) (t-old_t), changed);
+            printf("%i\t%llu\t%llu\t%llu\t%llu\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), changed, playing);
+            fflush(stdout);
             old_t = t;
             old_rtc = rtc;
         }
index c386251..79a4aaa 100644 (file)
@@ -29,9 +29,9 @@
 #include <assert.h>
 
 #include <pulse/timeval.h>
+#include <pulse/gccmacro.h>
 
 #include <pulsecore/core-util.h>
-#include <pulsecore/gccmacro.h>
 
 #ifdef GLIB_MAIN_LOOP
 
index d101311..79dd579 100644 (file)
 #include <stdlib.h>
 #include <time.h>
 
+#include <pulse/gccmacro.h>
+
 #include <pulsecore/core-util.h>
 #include <pulsecore/mcalign.h>
-#include <pulsecore/gccmacro.h>
 
 /* A simple program for testing pa_mcalign */
 
index 25ea399..3fa8d79 100644 (file)
 #include <pulsecore/memblockq.h>
 #include <pulsecore/log.h>
 
+static void dump(pa_memblockq *bq) {
+    printf(">");
+
+    for (;;) {
+        pa_memchunk out;
+        char *e;
+        size_t n;
+        void *q;
+
+        if (pa_memblockq_peek(bq, &out) < 0)
+            break;
+
+        q = pa_memblock_acquire(out.memblock);
+        for (e = (char*) q + out.index, n = 0; n < out.length; n++)
+            printf("%c", *e);
+        pa_memblock_release(out.memblock);
+
+        pa_memblock_unref(out.memblock);
+        pa_memblockq_drop(bq, out.length);
+    }
+
+    printf("<\n");
+}
+
 int main(int argc, char *argv[]) {
     int ret;
 
     pa_mempool *p;
     pa_memblockq *bq;
     pa_memchunk chunk1, chunk2, chunk3, chunk4;
-    pa_memblock *silence;
+    pa_memchunk silence;
 
     pa_log_set_maximal_level(PA_LOG_DEBUG);
 
     p = pa_mempool_new(0);
 
-    silence = pa_memblock_new_fixed(p, (char*)  "__", 2, 1);
-    assert(silence);
+    silence.memblock = pa_memblock_new_fixed(p, (char*)  "__", 2, 1);
+    assert(silence.memblock);
+    silence.index = 0;
+    silence.length = pa_memblock_get_length(silence.memblock);
 
-    bq = pa_memblockq_new(0, 40, 10, 2, 4, 4, silence);
+    bq = pa_memblockq_new(0, 40, 10, 2, 4, 4, 40, &silence);
     assert(bq);
 
     chunk1.memblock = pa_memblock_new_fixed(p, (char*) "11", 2, 1);
@@ -72,13 +98,13 @@ int main(int argc, char *argv[]) {
     ret = pa_memblockq_push(bq, &chunk1);
     assert(ret == 0);
 
-    ret = pa_memblockq_push(bq, &chunk1);
+    ret = pa_memblockq_push(bq, &chunk2);
     assert(ret == 0);
 
-    ret = pa_memblockq_push(bq, &chunk2);
+    ret = pa_memblockq_push(bq, &chunk3);
     assert(ret == 0);
 
-    ret = pa_memblockq_push(bq, &chunk2);
+    ret = pa_memblockq_push(bq, &chunk4);
     assert(ret == 0);
 
     pa_memblockq_seek(bq, -6, 0);
@@ -86,7 +112,7 @@ int main(int argc, char *argv[]) {
     assert(ret == 0);
 
     pa_memblockq_seek(bq, -2, 0);
-    ret = pa_memblockq_push(bq, &chunk3);
+    ret = pa_memblockq_push(bq, &chunk1);
     assert(ret == 0);
 
     pa_memblockq_seek(bq, -10, 0);
@@ -119,35 +145,22 @@ int main(int argc, char *argv[]) {
     ret = pa_memblockq_push(bq, &chunk3);
     assert(ret == 0);
 
-    pa_memblockq_shorten(bq, pa_memblockq_get_length(bq)-2);
-
-    printf(">");
-
-    for (;;) {
-        pa_memchunk out;
-        char *e;
-        size_t n;
-
-        if (pa_memblockq_peek(bq, &out) < 0)
-            break;
+    pa_memblockq_seek(bq, 30, PA_SEEK_RELATIVE);
 
-        p = pa_memblock_acquire(out.memblock);
-        for (e = (char*) p + out.index, n = 0; n < out.length; n++)
-            printf("%c", *e);
-        pa_memblock_release(out.memblock);
+    dump(bq);
 
-        pa_memblock_unref(out.memblock);
-        pa_memblockq_drop(bq, out.length);
-    }
+    pa_memblockq_rewind(bq, 52);
 
-    printf("<\n");
+    dump(bq);
 
     pa_memblockq_free(bq);
-    pa_memblock_unref(silence);
+    pa_memblock_unref(silence.memblock);
     pa_memblock_unref(chunk1.memblock);
     pa_memblock_unref(chunk2.memblock);
     pa_memblock_unref(chunk3.memblock);
     pa_memblock_unref(chunk4.memblock);
 
+    pa_mempool_free(p);
+
     return 0;
 }
index 2da67c1..c2123b7 100644 (file)
@@ -31,7 +31,7 @@
 
 #include <pulse/simple.h>
 #include <pulse/error.h>
-#include <pulsecore/gccmacro.h>
+#include <pulse/gccmacro.h>
 
 #define BUFSIZE 1024
 
index d7d8836..9c66cc2 100644 (file)
@@ -30,7 +30,7 @@
 
 #include <pulse/simple.h>
 #include <pulse/error.h>
-#include <pulsecore/gccmacro.h>
+#include <pulse/gccmacro.h>
 
 #define BUFSIZE 1024
 
index b88f4e5..5f7a78f 100644 (file)
 #include <pulse/proplist.h>
 #include <pulse/xmalloc.h>
 #include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
 
 int main(int argc, char*argv[]) {
     pa_proplist *a, *b;
     char *s, *t;
 
     a = pa_proplist_new();
-    pa_assert_se(pa_proplist_puts(a, PA_PROP_MEDIA_TITLE, "Brandenburgische Konzerte") == 0);
-    pa_assert_se(pa_proplist_puts(a, PA_PROP_MEDIA_ARTIST, "Johann Sebastian Bach") == 0);
+    pa_assert_se(pa_proplist_sets(a, PA_PROP_MEDIA_TITLE, "Brandenburgische Konzerte") == 0);
+    pa_assert_se(pa_proplist_sets(a, PA_PROP_MEDIA_ARTIST, "Johann Sebastian Bach") == 0);
 
     b = pa_proplist_new();
-    pa_assert_se(pa_proplist_puts(b, PA_PROP_MEDIA_TITLE, "Goldbergvariationen") == 0);
-    pa_assert_se(pa_proplist_put(b, PA_PROP_MEDIA_ICON, "\0\1\2\3\4\5\6\7", 8) == 0);
+    pa_assert_se(pa_proplist_sets(b, PA_PROP_MEDIA_TITLE, "Goldbergvariationen") == 0);
+    pa_assert_se(pa_proplist_set(b, PA_PROP_MEDIA_ICON, "\0\1\2\3\4\5\6\7", 8) == 0);
 
-    pa_proplist_merge(a, b);
+    pa_proplist_update(a, PA_UPDATE_MERGE, b);
 
     pa_assert_se(!pa_proplist_gets(a, PA_PROP_MEDIA_ICON));
 
     printf("%s\n", pa_strnull(pa_proplist_gets(a, PA_PROP_MEDIA_TITLE)));
-    pa_assert_se(pa_proplist_remove(b, PA_PROP_MEDIA_TITLE) == 0);
+    pa_assert_se(pa_proplist_unset(b, PA_PROP_MEDIA_TITLE) == 0);
 
     s = pa_proplist_to_string(a);
     t = pa_proplist_to_string(b);
index e649377..af94210 100644 (file)
@@ -67,7 +67,7 @@ int main(int argc, char *argv[]) {
     pa_rtpoll_item_set_before_callback(w, worker);
 
     pa_rtpoll_install(p);
-    pa_rtpoll_set_timer_periodic(p, 10000000); /* 10 s */
+    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
new file mode 100644 (file)
index 0000000..39dfc5d
--- /dev/null
@@ -0,0 +1,119 @@
+/* $Id$ */
+
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2008 Lennart Poettering
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as
+  published by the Free Software Foundation; either version 2 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 <unistd.h>
+#include <time.h>
+#include <sched.h>
+#include <inttypes.h>
+#include <string.h>
+#include <pthread.h>
+
+#include <pulse/timeval.h>
+#include <pulse/gccmacro.h>
+
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+static int msec_lower, msec_upper;
+
+static void* work(void *p) PA_GCC_NORETURN;
+
+static void* work(void *p) {
+    cpu_set_t mask;
+    struct sched_param param;
+
+    pa_log_notice("CPU%i: Created thread.", PA_PTR_TO_INT(p));
+
+    memset(&param, 0, sizeof(param));
+    param.sched_priority = 12;
+    pa_assert_se(pthread_setschedparam(pthread_self(), SCHED_FIFO, &param) == 0);
+
+    CPU_ZERO(&mask);
+    CPU_SET(PA_PTR_TO_INT(p), &mask);
+    pa_assert_se(pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask) == 0);
+
+    for (;;) {
+        struct timespec now, end;
+        uint64_t nsec;
+
+        pa_log_notice("CPU%i: Sleeping for 1s", PA_PTR_TO_INT(p));
+        sleep(1);
+
+        pa_assert_se(clock_gettime(CLOCK_REALTIME, &end) == 0);
+
+        nsec =
+            (uint64_t) ((((double) rand())*(msec_upper-msec_lower)*PA_NSEC_PER_MSEC)/RAND_MAX) +
+            (uint64_t) (msec_lower*PA_NSEC_PER_MSEC);
+
+        pa_log_notice("CPU%i: Freezing for %ims", PA_PTR_TO_INT(p), (int) (nsec/PA_NSEC_PER_MSEC));
+
+        end.tv_sec += nsec / PA_NSEC_PER_SEC;
+        end.tv_nsec += nsec % PA_NSEC_PER_SEC;
+
+        while (end.tv_nsec > PA_NSEC_PER_SEC) {
+            end.tv_sec++;
+            end.tv_nsec -= PA_NSEC_PER_SEC;
+        }
+
+        do {
+            pa_assert_se(clock_gettime(CLOCK_REALTIME, &now) == 0);
+        } while (now.tv_sec < end.tv_sec ||
+                 (now.tv_sec == end.tv_sec && now.tv_nsec < end.tv_nsec));
+    }
+}
+
+int main(int argc, char*argv[]) {
+    int n;
+
+    srand(time(NULL));
+
+    if (argc >= 3) {
+        msec_lower = atoi(argv[1]);
+        msec_upper = atoi(argv[2]);
+    } else if (argc >= 2) {
+        msec_lower = 0;
+        msec_upper = atoi(argv[1]);
+    } else {
+        msec_lower = 0;
+        msec_upper = 1000;
+    }
+
+    pa_assert(msec_upper > 0);
+    pa_assert(msec_upper >= msec_lower);
+
+    pa_log_notice("Creating random latencies in the range of %ims to  %ims.", msec_lower, msec_upper);
+
+    for (n = 1; n < sysconf(_SC_NPROCESSORS_CONF); n++) {
+        pthread_t t;
+        pa_assert_se(pthread_create(&t, NULL, work, PA_INT_TO_PTR(n)) == 0);
+    }
+
+    work(PA_INT_TO_PTR(0));
+
+    return 0;
+}
index 0816e76..de03772 100644 (file)
@@ -47,25 +47,25 @@ int main(int argc, char*argv[]) {
 
     srand(0);
 
-    for (m = 0, u = 0; u < PA_ELEMENTSOF(msec)-2; u+= 2) {
+    for (m = 0, u = 0; u < PA_ELEMENTSOF(msec); u+= 2) {
 
-        msec[u] = m+1;
-        msec[u+1] = m + rand() % 2000 - 1000;
+        msec[u] = m+1 + (rand() % 100) - 50;
+        msec[u+1] = m + (rand() % 2000) - 1000;
 
         m += rand() % 100;
 
+        if (msec[u] < 0)
+            msec[u] = 0;
+
         if (msec[u+1] < 0)
             msec[u+1] = 0;
     }
 
-    msec[PA_ELEMENTSOF(msec)-2] = 0;
-    msec[PA_ELEMENTSOF(msec)-1] = 0;
-
-    s = pa_smoother_new(1000*PA_USEC_PER_MSEC, 2000*PA_USEC_PER_MSEC, TRUE);
+    s = pa_smoother_new(700*PA_USEC_PER_MSEC, 2000*PA_USEC_PER_MSEC, TRUE, 6);
 
     for (x = 0, u = 0; x < PA_USEC_PER_SEC * 10; x += PA_USEC_PER_MSEC) {
 
-        while (msec[u] > 0 && (pa_usec_t) msec[u]*PA_USEC_PER_MSEC < x) {
+        while (u < PA_ELEMENTSOF(msec) && (pa_usec_t) msec[u]*PA_USEC_PER_MSEC < x) {
             pa_smoother_put(s, msec[u]*PA_USEC_PER_MSEC, msec[u+1]*PA_USEC_PER_MSEC);
             printf("%i\t\t%i\n", msec[u],  msec[u+1]);
             u += 2;
diff --git a/src/tests/stripnul.c b/src/tests/stripnul.c
new file mode 100644 (file)
index 0000000..2f87e87
--- /dev/null
@@ -0,0 +1,72 @@
+/* $Id$ */
+
+/***
+  This file is part of PulseAudio.
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/macro.h>
+
+int main(int argc, char *argv[]) {
+    FILE *i, *o;
+    size_t granularity;
+    pa_bool_t found;
+    uint8_t *zero;
+
+    pa_assert_se(argc >= 2);
+    pa_assert_se((granularity = atoi(argv[1])) >= 1);
+    pa_assert_se((i = (argc >= 3) ? fopen(argv[2], "r") : stdin));
+    pa_assert_se((o = (argc >= 4) ? fopen(argv[3], "w") : stdout));
+
+    zero = pa_xmalloc0(granularity);
+
+    for (;;) {
+        uint8_t buffer[16*1024], *p;
+        size_t k;
+
+        k = fread(buffer, granularity, sizeof(buffer)/granularity, i);
+
+        if (k <= 0)
+            break;
+
+        if (found)
+            pa_assert_se(fwrite(buffer, granularity, k, o) == k);
+        else {
+            for (p = buffer; (p-buffer)/granularity < k; p += granularity)
+                if (memcmp(p, zero, granularity)) {
+                    size_t left;
+                    found = TRUE;
+                    left = k - (p-buffer)/granularity;
+                    pa_assert_se(fwrite(p, granularity, left, o) == left);
+                    break;
+                }
+        }
+    }
+
+    fflush(o);
+
+    return 0;
+}
index 47770b5..2bd1645 100644 (file)
@@ -1,8 +1,9 @@
 #include <stdio.h>
 
 #include <pulse/xmalloc.h>
+#include <pulse/gccmacro.h>
+
 #include <pulsecore/strlist.h>
-#include <pulsecore/gccmacro.h>
 
 int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char* argv[]) {
     char *t, *u;
index 558e53a..ac6d504 100644 (file)
@@ -30,8 +30,8 @@
 #include <pulse/timeval.h>
 #include <pulse/util.h>
 #include <pulse/thread-mainloop.h>
+#include <pulse/gccmacro.h>
 
-#include <pulsecore/gccmacro.h>
 #include <pulsecore/macro.h>
 
 static void tcb(pa_mainloop_api*a, pa_time_event *e, const struct timeval *tv, void *userdata) {
index dcc1ec5..91752ad 100644 (file)
@@ -3,7 +3,7 @@
 #include <stdio.h>
 
 #include <pulse/volume.h>
-#include <pulsecore/gccmacro.h>
+#include <pulse/gccmacro.h>
 
 int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char *argv[]) {
     pa_volume_t v;
index 68e308d..fc9d56d 100644 (file)
@@ -44,6 +44,8 @@
 #error Invalid PulseAudio API version
 #endif
 
+#define CLEAR_LINE "\x1B[K"
+
 static enum { RECORD, PLAYBACK } mode = PLAYBACK;
 
 static pa_context *context = NULL;
@@ -71,6 +73,8 @@ static int channel_map_set = 0;
 
 static pa_stream_flags_t flags = 0;
 
+static size_t latency = 0, process_time=0;
+
 /* A shortcut for terminating the application */
 static void quit(int ret) {
     assert(mainloop_api);
@@ -204,17 +208,38 @@ static void stream_suspended_callback(pa_stream *s, void *userdata) {
 
     if (verbose) {
         if (pa_stream_is_suspended(s))
-            fprintf(stderr, "Stream device suspended.\n");
+            fprintf(stderr, "Stream device suspended." CLEAR_LINE " \n");
         else
-            fprintf(stderr, "Stream device resumed.\n");
+            fprintf(stderr, "Stream device resumed." CLEAR_LINE " \n");
     }
 }
 
+static void stream_underflow_callback(pa_stream *s, void *userdata) {
+    assert(s);
+
+    if (verbose)
+        fprintf(stderr, "Stream underrun." CLEAR_LINE " \n");
+}
+
+static void stream_overflow_callback(pa_stream *s, void *userdata) {
+    assert(s);
+
+    if (verbose)
+        fprintf(stderr, "Stream overrun." CLEAR_LINE " \n");
+}
+
+static void stream_started_callback(pa_stream *s, void *userdata) {
+    assert(s);
+
+    if (verbose)
+        fprintf(stderr, "Stream started." CLEAR_LINE " \n");
+}
+
 static void stream_moved_callback(pa_stream *s, void *userdata) {
     assert(s);
 
     if (verbose)
-        fprintf(stderr, "Stream moved to device %s (%u, %ssuspended).\n", pa_stream_get_device_name(s), pa_stream_get_device_index(s), pa_stream_is_suspended(s) ? "" : "not ");
+        fprintf(stderr, "Stream moved to device %s (%u, %ssuspended)." CLEAR_LINE " \n", pa_stream_get_device_name(s), pa_stream_get_device_index(s), pa_stream_is_suspended(s) ? "" : "not ");
 }
 
 /* This is called whenever the context status changes */
@@ -229,12 +254,13 @@ static void context_state_callback(pa_context *c, void *userdata) {
 
         case PA_CONTEXT_READY: {
             int r;
+            pa_buffer_attr buffer_attr;
 
             assert(c);
             assert(!stream);
 
             if (verbose)
-                fprintf(stderr, "Connection established.\n");
+                fprintf(stderr, "Connection established." CLEAR_LINE " \n");
 
             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)));
@@ -246,16 +272,26 @@ static void context_state_callback(pa_context *c, void *userdata) {
             pa_stream_set_read_callback(stream, stream_read_callback, NULL);
             pa_stream_set_suspended_callback(stream, stream_suspended_callback, NULL);
             pa_stream_set_moved_callback(stream, stream_moved_callback, NULL);
+            pa_stream_set_underflow_callback(stream, stream_underflow_callback, NULL);
+            pa_stream_set_overflow_callback(stream, stream_overflow_callback, NULL);
+            pa_stream_set_started_callback(stream, stream_started_callback, NULL);
+
+            if (latency > 0) {
+                memset(&buffer_attr, 0, sizeof(buffer_attr));
+                buffer_attr.tlength = latency;
+                buffer_attr.minreq = process_time;
+                flags |= PA_STREAM_ADJUST_LATENCY;
+            }
 
             if (mode == PLAYBACK) {
                 pa_cvolume cv;
-                if ((r = pa_stream_connect_playback(stream, device, NULL, flags, pa_cvolume_set(&cv, sample_spec.channels, volume), NULL)) < 0) {
+                if ((r = pa_stream_connect_playback(stream, device, latency > 0 ? &buffer_attr : NULL, flags, pa_cvolume_set(&cv, sample_spec.channels, volume), NULL)) < 0) {
                     fprintf(stderr, "pa_stream_connect_playback() failed: %s\n", pa_strerror(pa_context_errno(c)));
                     goto fail;
                 }
 
             } else {
-                if ((r = pa_stream_connect_record(stream, device, NULL, flags)) < 0) {
+                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)));
                     goto fail;
                 }
@@ -407,14 +443,14 @@ static void exit_signal_callback(pa_mainloop_api*m, pa_signal_event *e, int sig,
 
 /* Show the current latency */
 static void stream_update_timing_callback(pa_stream *s, int success, void *userdata) {
-    pa_usec_t latency, usec;
+    pa_usec_t l, usec;
     int negative = 0;
 
     assert(s);
 
     if (!success ||
         pa_stream_get_time(s, &usec) < 0 ||
-        pa_stream_get_latency(s, &latency, &negative) < 0) {
+        pa_stream_get_latency(s, &l, &negative) < 0) {
         fprintf(stderr, "Failed to get latency: %s\n", pa_strerror(pa_context_errno(context)));
         quit(1);
         return;
@@ -422,7 +458,7 @@ static void stream_update_timing_callback(pa_stream *s, int success, void *userd
 
     fprintf(stderr, "Time: %0.3f sec; Latency: %0.0f usec.  \r",
             (float) usec / 1000000,
-            (float) latency * (negative?-1:1));
+            (float) l * (negative?-1:1));
 }
 
 /* Someone requested that the latency is shown */
@@ -478,6 +514,8 @@ static void help(const char *argv0) {
            "                                        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);
 }
@@ -494,7 +532,9 @@ enum {
     ARG_FIX_RATE,
     ARG_FIX_CHANNELS,
     ARG_NO_REMAP,
-    ARG_NO_REMIX
+    ARG_NO_REMIX,
+    ARG_LATENCY,
+    ARG_PROCESS_TIME
 };
 
 int main(int argc, char *argv[]) {
@@ -504,26 +544,28 @@ int main(int argc, char *argv[]) {
     pa_time_event *time_event = NULL;
 
     static const struct option long_options[] = {
-        {"record",      0, NULL, 'r'},
-        {"playback",    0, NULL, 'p'},
-        {"device",      1, NULL, 'd'},
-        {"server",      1, NULL, 's'},
-        {"client-name", 1, NULL, 'n'},
-        {"stream-name", 1, NULL, ARG_STREAM_NAME},
-        {"version",     0, NULL, ARG_VERSION},
-        {"help",        0, NULL, 'h'},
-        {"verbose",     0, NULL, 'v'},
-        {"volume",      1, NULL, ARG_VOLUME},
-        {"rate",        1, NULL, ARG_SAMPLERATE},
-        {"format",      1, NULL, ARG_SAMPLEFORMAT},
-        {"channels",    1, NULL, ARG_CHANNELS},
-        {"channel-map", 1, NULL, ARG_CHANNELMAP},
-        {"fix-format",  0, NULL, ARG_FIX_FORMAT},
-        {"fix-rate",    0, NULL, ARG_FIX_RATE},
-        {"fix-channels",0, NULL, ARG_FIX_CHANNELS},
-        {"no-remap",    0, NULL, ARG_NO_REMAP},
-        {"no-remix",    0, NULL, ARG_NO_REMIX},
-        {NULL,          0, NULL, 0}
+        {"record",       0, NULL, 'r'},
+        {"playback",     0, NULL, 'p'},
+        {"device",       1, NULL, 'd'},
+        {"server",       1, NULL, 's'},
+        {"client-name",  1, NULL, 'n'},
+        {"stream-name",  1, NULL, ARG_STREAM_NAME},
+        {"version",      0, NULL, ARG_VERSION},
+        {"help",         0, NULL, 'h'},
+        {"verbose",      0, NULL, 'v'},
+        {"volume",       1, NULL, ARG_VOLUME},
+        {"rate",         1, NULL, ARG_SAMPLERATE},
+        {"format",       1, NULL, ARG_SAMPLEFORMAT},
+        {"channels",     1, NULL, ARG_CHANNELS},
+        {"channel-map",  1, NULL, ARG_CHANNELMAP},
+        {"fix-format",   0, NULL, ARG_FIX_FORMAT},
+        {"fix-rate",     0, NULL, ARG_FIX_RATE},
+        {"fix-channels", 0, NULL, ARG_FIX_CHANNELS},
+        {"no-remap",     0, NULL, ARG_NO_REMAP},
+        {"no-remix",     0, NULL, ARG_NO_REMIX},
+        {"latency",      1, NULL, ARG_LATENCY},
+        {"process-time", 1, NULL, ARG_PROCESS_TIME},
+        {NULL,           0, NULL, 0}
     };
 
     if (!(bn = strrchr(argv[0], '/')))
@@ -601,7 +643,7 @@ int main(int argc, char *argv[]) {
 
             case ARG_CHANNELMAP:
                 if (!pa_channel_map_parse(&channel_map, optarg)) {
-                    fprintf(stderr, "Invalid channel map\n");
+                    fprintf(stderr, "Invalid channel map '%s'\n", optarg);
                     goto quit;
                 }
 
@@ -628,6 +670,20 @@ int main(int argc, char *argv[]) {
                 flags |= PA_STREAM_NO_REMAP_CHANNELS;
                 break;
 
+            case ARG_LATENCY:
+                if (((latency = atoi(optarg))) <= 0) {
+                    fprintf(stderr, "Invalid latency specification '%s'\n", optarg);
+                    goto quit;
+                }
+                break;
+
+            case ARG_PROCESS_TIME:
+                if (((process_time = atoi(optarg))) <= 0) {
+                    fprintf(stderr, "Invalid process time specification '%s'\n", optarg);
+                    goto quit;
+                }
+                break;
+
             default:
                 goto quit;
         }
index daa6a96..dff9af9 100644 (file)
@@ -36,6 +36,7 @@
 
 #include <pulse/error.h>
 #include <pulse/util.h>
+#include <pulse/xmalloc.h>
 
 #include <pulsecore/core-util.h>
 #include <pulsecore/log.h>
@@ -49,6 +50,7 @@ int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char*argv[]) {
     char ibuf[256], obuf[256];
     size_t ibuf_index, ibuf_length, obuf_index, obuf_length;
     fd_set ifds, ofds;
+    char *cli;
 
     if (pa_pid_file_check_running(&pid, "pulseaudio") < 0) {
         pa_log("no PulseAudio daemon running");
@@ -62,7 +64,10 @@ int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char*argv[]) {
 
     memset(&sa, 0, sizeof(sa));
     sa.sun_family = AF_UNIX;
-    pa_runtime_path("cli", sa.sun_path, sizeof(sa.sun_path));
+
+    cli = pa_runtime_path("cli");
+    pa_strlcpy(sa.sun_path, cli, sizeof(sa.sun_path));
+    pa_xfree(cli);
 
     for (i = 0; i < 5; i++) {
         int r;
index 674eaee..1f875a8 100644 (file)
@@ -158,6 +158,7 @@ static void get_server_info_callback(pa_context *c, const pa_server_info *i, voi
 
 static void get_sink_info_callback(pa_context *c, const pa_sink_info *i, int is_last, void *userdata) {
     char s[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+    char *pl;
 
     if (is_last < 0) {
         fprintf(stderr, "Failed to get sink information: %s\n", pa_strerror(pa_context_errno(c)));
@@ -179,32 +180,37 @@ static void get_sink_info_callback(pa_context *c, const pa_sink_info *i, int is_
     printf("*** Sink #%u ***\n"
            "Name: %s\n"
            "Driver: %s\n"
-           "Description: %s\n"
            "Sample Specification: %s\n"
            "Channel Map: %s\n"
            "Owner Module: %u\n"
            "Volume: %s\n"
-           "Monitor Source: %u\n"
-           "Latency: %0.0f usec\n"
-           "Flags: %s%s%s\n",
+           "Monitor Source: %s\n"
+           "Latency: %0.0f usec, configured %0.0f usec\n"
+           "Flags: %s%s%s%s%s%s\n"
+           "Properties:\n%s",
            i->index,
            i->name,
-           i->driver,
-           i->description,
+           pa_strnull(i->driver),
            pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec),
            pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map),
            i->owner_module,
            i->mute ? "muted" : pa_cvolume_snprint(cv, sizeof(cv), &i->volume),
-           i->monitor_source,
-           (double) i->latency,
+           pa_strnull(i->monitor_source_name),
+           (double) i->latency, (double) i->configured_latency,
+           i->flags & PA_SINK_HARDWARE ? "HARDWARE " : "",
+           i->flags & PA_SINK_NETWORK ? "NETWORK " : "",
+           i->flags & PA_SINK_HW_MUTE_CTRL ? "HW_MUTE_CTRL " : "",
            i->flags & PA_SINK_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "",
+           i->flags & PA_SINK_DECIBEL_VOLUME ? "DECIBEL_VOLUME " : "",
            i->flags & PA_SINK_LATENCY ? "LATENCY " : "",
-           i->flags & PA_SINK_HARDWARE ? "HARDWARE" : "");
+           pl = pa_proplist_to_string(i->proplist));
 
+    pa_xfree(pl);
 }
 
 static void get_source_info_callback(pa_context *c, const pa_source_info *i, int is_last, void *userdata) {
-    char s[PA_SAMPLE_SPEC_SNPRINT_MAX], t[32], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+    char s[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+    char *pl;
 
     if (is_last < 0) {
         fprintf(stderr, "Failed to get source information: %s\n", pa_strerror(pa_context_errno(c)));
@@ -223,33 +229,35 @@ static void get_source_info_callback(pa_context *c, const pa_source_info *i, int
         printf("\n");
     nl = 1;
 
-    snprintf(t, sizeof(t), "%u", i->monitor_of_sink);
-
     printf("*** Source #%u ***\n"
            "Name: %s\n"
            "Driver: %s\n"
-           "Description: %s\n"
            "Sample Specification: %s\n"
            "Channel Map: %s\n"
            "Owner Module: %u\n"
            "Volume: %s\n"
            "Monitor of Sink: %s\n"
-           "Latency: %0.0f usec\n"
-           "Flags: %s%s%s\n",
+           "Latency: %0.0f usec, configured %0.0f usec\n"
+           "Flags: %s%s%s%s%s%s\n"
+           "Properties:\n%s",
            i->index,
            i->name,
-           i->driver,
-           i->description,
+           pa_strnull(i->driver),
            pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec),
            pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map),
            i->owner_module,
            i->mute ? "muted" : pa_cvolume_snprint(cv, sizeof(cv), &i->volume),
-           i->monitor_of_sink != PA_INVALID_INDEX ? t : "no",
-           (double) i->latency,
+           i->monitor_of_sink_name ? i->monitor_of_sink_name : "n/a",
+           (double) i->latency, (double) i->configured_latency,
+           i->flags & PA_SOURCE_HARDWARE ? "HARDWARE " : "",
+           i->flags & PA_SOURCE_NETWORK ? "NETWORK " : "",
+           i->flags & PA_SOURCE_HW_MUTE_CTRL ? "HW_MUTE_CTRL " : "",
            i->flags & PA_SOURCE_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "",
+           i->flags & PA_SOURCE_DECIBEL_VOLUME ? "DECIBEL_VOLUME " : "",
            i->flags & PA_SOURCE_LATENCY ? "LATENCY " : "",
-           i->flags & PA_SOURCE_HARDWARE ? "HARDWARE" : "");
+           pl = pa_proplist_to_string(i->proplist));
 
+    pa_xfree(pl);
 }
 
 static void get_module_info_callback(pa_context *c, const pa_module_info *i, int is_last, void *userdata) {
@@ -283,11 +291,12 @@ static void get_module_info_callback(pa_context *c, const pa_module_info *i, int
            i->name,
            i->argument ? i->argument : "",
            i->n_used != PA_INVALID_INDEX ? t : "n/a",
-           i->auto_unload ? "yes" : "no");
+           pa_yes_no(i->auto_unload));
 }
 
 static void get_client_info_callback(pa_context *c, const pa_client_info *i, int is_last, void *userdata) {
     char t[32];
+    char *pl;
 
     if (is_last < 0) {
         fprintf(stderr, "Failed to get client information: %s\n", pa_strerror(pa_context_errno(c)));
@@ -309,17 +318,20 @@ static void get_client_info_callback(pa_context *c, const pa_client_info *i, int
     snprintf(t, sizeof(t), "%u", i->owner_module);
 
     printf("*** Client #%u ***\n"
-           "Name: %s\n"
            "Driver: %s\n"
-           "Owner Module: %s\n",
+           "Owner Module: %s\n"
+           "Properties:\n%s",
            i->index,
-           i->name,
-           i->driver,
-           i->owner_module != PA_INVALID_INDEX ? t : "n/a");
+           pa_strnull(i->driver),
+           i->owner_module != PA_INVALID_INDEX ? t : "n/a",
+           pl = pa_proplist_to_string(i->proplist));
+
+    pa_xfree(pl);
 }
 
 static void get_sink_input_info_callback(pa_context *c, const pa_sink_input_info *i, int is_last, void *userdata) {
     char t[32], k[32], s[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+    char *pl;
 
     if (is_last < 0) {
         fprintf(stderr, "Failed to get sink input information: %s\n", pa_strerror(pa_context_errno(c)));
@@ -342,7 +354,6 @@ static void get_sink_input_info_callback(pa_context *c, const pa_sink_input_info
     snprintf(k, sizeof(k), "%u", i->client);
 
     printf("*** Sink Input #%u ***\n"
-           "Name: %s\n"
            "Driver: %s\n"
            "Owner Module: %s\n"
            "Client: %s\n"
@@ -352,10 +363,10 @@ static void get_sink_input_info_callback(pa_context *c, const pa_sink_input_info
            "Volume: %s\n"
            "Buffer Latency: %0.0f usec\n"
            "Sink Latency: %0.0f usec\n"
-           "Resample method: %s\n",
+           "Resample method: %s\n"
+           "Properties:\n%s",
            i->index,
-           i->name,
-           i->driver,
+           pa_strnull(i->driver),
            i->owner_module != PA_INVALID_INDEX ? t : "n/a",
            i->client != PA_INVALID_INDEX ? k : "n/a",
            i->sink,
@@ -364,12 +375,15 @@ static void get_sink_input_info_callback(pa_context *c, const pa_sink_input_info
            i->mute ? "muted" : pa_cvolume_snprint(cv, sizeof(cv), &i->volume),
            (double) i->buffer_usec,
            (double) i->sink_usec,
-           i->resample_method ? i->resample_method : "n/a");
-}
+           i->resample_method ? i->resample_method : "n/a",
+           pl = pa_proplist_to_string(i->proplist));
 
+    pa_xfree(pl);
+}
 
 static void get_source_output_info_callback(pa_context *c, const pa_source_output_info *i, int is_last, void *userdata) {
     char t[32], k[32], s[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+    char *pl;
 
     if (is_last < 0) {
         fprintf(stderr, "Failed to get source output information: %s\n", pa_strerror(pa_context_errno(c)));
@@ -393,7 +407,6 @@ static void get_source_output_info_callback(pa_context *c, const pa_source_outpu
     snprintf(k, sizeof(k), "%u", i->client);
 
     printf("*** Source Output #%u ***\n"
-           "Name: %s\n"
            "Driver: %s\n"
            "Owner Module: %s\n"
            "Client: %s\n"
@@ -402,10 +415,10 @@ static void get_source_output_info_callback(pa_context *c, const pa_source_outpu
            "Channel Map: %s\n"
            "Buffer Latency: %0.0f usec\n"
            "Source Latency: %0.0f usec\n"
-           "Resample method: %s\n",
+           "Resample method: %s\n"
+           "Properties:\n%s",
            i->index,
-           i->name,
-           i->driver,
+           pa_strnull(i->driver),
            i->owner_module != PA_INVALID_INDEX ? t : "n/a",
            i->client != PA_INVALID_INDEX ? k : "n/a",
            i->source,
@@ -413,11 +426,15 @@ static void get_source_output_info_callback(pa_context *c, const pa_source_outpu
            pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map),
            (double) i->buffer_usec,
            (double) i->source_usec,
-           i->resample_method ? i->resample_method : "n/a");
+           i->resample_method ? i->resample_method : "n/a",
+           pl = pa_proplist_to_string(i->proplist));
+
+    pa_xfree(pl);
 }
 
 static void get_sample_info_callback(pa_context *c, const pa_sample_info *i, int is_last, void *userdata) {
     char t[32], s[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+    char *pl;
 
     if (is_last < 0) {
         fprintf(stderr, "Failed to get sample information: %s\n", pa_strerror(pa_context_errno(c)));
@@ -447,7 +464,8 @@ static void get_sample_info_callback(pa_context *c, const pa_sample_info *i, int
            "Duration: %0.1fs\n"
            "Size: %s\n"
            "Lazy: %s\n"
-           "Filename: %s\n",
+           "Filename: %s\n"
+           "Properties:\n%s",
            i->index,
            i->name,
            pa_cvolume_snprint(cv, sizeof(cv), &i->volume),
@@ -455,8 +473,11 @@ static void get_sample_info_callback(pa_context *c, const pa_sample_info *i, int
            pa_sample_spec_valid(&i->sample_spec) ? pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map) : "n/a",
            (double) i->duration/1000000,
            t,
-           i->lazy ? "yes" : "no",
-           i->filename ? i->filename : "n/a");
+           pa_yes_no(i->lazy),
+           i->filename ? i->filename : "n/a",
+           pl = pa_proplist_to_string(i->proplist));
+
+    pa_xfree(pl);
 }
 
 static void get_autoload_info_callback(pa_context *c, const pa_autoload_info *i, int is_last, void *userdata) {
@@ -868,6 +889,10 @@ int main(int argc, char *argv[]) {
 
             if (argc > optind+2)
                 source_name = pa_xstrdup(argv[optind+1]);
+        } else if (!strcmp(argv[optind], "help")) {
+            help(bn);
+            ret = 0;
+            goto quit;
         }
     }
 
index d3f034d..e43a0de 100644 (file)
@@ -53,8 +53,8 @@
 #endif
 
 #include <pulse/pulseaudio.h>
+#include <pulse/gccmacro.h>
 #include <pulsecore/llist.h>
-#include <pulsecore/gccmacro.h>
 
 /* On some systems SIOCINQ isn't defined, but FIONREAD is just an alias */
 #if !defined(SIOCINQ) && defined(FIONREAD)
@@ -302,7 +302,6 @@ static int padsp_disabled(void) {
     if (!sym_resolved) {
         sym = (int*) dlsym(RTLD_DEFAULT, "__padsp_disabled__");
         sym_resolved = 1;
-
     }
     pthread_mutex_unlock(&func_mutex);
 
@@ -316,7 +315,7 @@ static int dsp_cloak_enable(void) {
     if (padsp_disabled() & 1)
         return 0;
 
-    if (getenv("PADSP_NO_DSP"))
+    if (getenv("PADSP_NO_DSP") || getenv("PULSE_INTERNAL"))
         return 0;
 
     return 1;
@@ -326,7 +325,7 @@ static int sndstat_cloak_enable(void) {
     if (padsp_disabled() & 2)
         return 0;
 
-    if (getenv("PADSP_NO_SNDSTAT"))
+    if (getenv("PADSP_NO_SNDSTAT") || getenv("PULSE_INTERNAL"))
         return 0;
 
     return 1;
@@ -336,7 +335,7 @@ static int mixer_cloak_enable(void) {
     if (padsp_disabled() & 4)
         return 0;
 
-    if (getenv("PADSP_NO_MIXER"))
+    if (getenv("PADSP_NO_MIXER") || getenv("PULSE_INTERNAL"))
         return 0;
 
     return 1;