diff options
189 files changed, 12423 insertions, 4823 deletions
@@ -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. diff --git a/configure.ac b/configure.ac index 47048693..ca7565de 100644 --- a/configure.ac +++ b/configure.ac @@ -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?]) diff --git a/doxygen/doxygen.conf.in b/doxygen/doxygen.conf.in index 81923a9f..7ad5d2f3 100644 --- a/doxygen/doxygen.conf.in +++ b/doxygen/doxygen.conf.in @@ -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 diff --git a/src/Makefile.am b/src/Makefile.am index 916a06a1..799e7b26 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 diff --git a/src/daemon/caps.c b/src/daemon/caps.c index d78e9689..e936d6bb 100644 --- a/src/daemon/caps.c +++ b/src/daemon/caps.c @@ -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 diff --git a/src/daemon/caps.h b/src/daemon/caps.h index 91c88418..5b21f12e 100644 --- a/src/daemon/caps.h +++ b/src/daemon/caps.h @@ -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 diff --git a/src/daemon/cmdline.c b/src/daemon/cmdline.c index f1e1282c..97c75f37 100644 --- a/src/daemon/cmdline.c +++ b/src/daemon/cmdline.c @@ -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: diff --git a/src/daemon/cpulimit.c b/src/daemon/cpulimit.c index 620a93a6..579b91e3 100644 --- a/src/daemon/cpulimit.c +++ b/src/daemon/cpulimit.c @@ -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; } } diff --git a/src/daemon/daemon-conf.c b/src/daemon/daemon-conf.c index c98c0218..f9ad7ec0 100644 --- a/src/daemon/daemon-conf.c +++ b/src/daemon/daemon-conf.c @@ -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 diff --git a/src/daemon/daemon-conf.h b/src/daemon/daemon-conf.h index 3dcafbfe..03a75661 100644 --- a/src/daemon/daemon-conf.h +++ b/src/daemon/daemon-conf.h @@ -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 diff --git a/src/daemon/daemon.conf.in b/src/daemon/daemon.conf.in index e4cfb82b..fd35c0f6 100644 --- a/src/daemon/daemon.conf.in +++ b/src/daemon/daemon.conf.in @@ -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 @@ -50,16 +51,21 @@ ; 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 diff --git a/src/daemon/main.c b/src/daemon/main.c index 6b0c81da..789d104b 100644 --- a/src/daemon/main.c +++ b/src/daemon/main.c @@ -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 diff --git a/src/map-file b/src/map-file index ffa5d103..d9189743 100644 --- a/src/map-file +++ b/src/map-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; diff --git a/src/modules/alsa-util.c b/src/modules/alsa-util.c index 6afec3bc..d212abc2 100644 --- a/src/modules/alsa-util.c +++ b/src/modules/alsa-util.c @@ -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; +} diff --git a/src/modules/alsa-util.h b/src/modules/alsa-util.h index 53d9a2fb..442c2645 100644 --- a/src/modules/alsa-util.h +++ b/src/modules/alsa-util.h @@ -29,8 +29,10 @@ #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 diff --git a/src/modules/module-alsa-sink.c b/src/modules/module-alsa-sink.c index 14aef7c9..95a72fdc 100644 --- a/src/modules/module-alsa-sink.c +++ b/src/modules/module-alsa-sink.c @@ -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, ×tamp); */ +/* pa_rtclock_from_wallclock(×tamp); */ +/* now1 = pa_timeval_load(×tamp); */ + + 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(); } diff --git a/src/modules/module-alsa-source.c b/src/modules/module-alsa-source.c index 23a2f921..e3090109 100644 --- a/src/modules/module-alsa-source.c +++ b/src/modules/module-alsa-source.c @@ -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(); } diff --git a/src/modules/module-combine.c b/src/modules/module-combine.c index 996cd4f6..fc8be18d 100644 --- a/src/modules/module-combine.c +++ b/src/modules/module-combine.c @@ -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); diff --git a/src/modules/module-default-device-restore.c b/src/modules/module-default-device-restore.c index b550ae78..a7fc3a3f 100644 --- a/src/modules/module-default-device-restore.c +++ b/src/modules/module-default-device-restore.c @@ -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 @@ -25,10 +25,16 @@ #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 index 00000000..0a41b84a --- /dev/null +++ b/src/modules/module-device-restore.c @@ -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); +} diff --git a/src/modules/module-esound-sink.c b/src/modules/module-esound-sink.c index f9bea63d..87b87c3d 100644 --- a/src/modules/module-esound-sink.c +++ b/src/modules/module-esound-sink.c @@ -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 */ diff --git a/src/modules/module-hal-detect.c b/src/modules/module-hal-detect.c index 832bc73e..44b31a59 100644 --- a/src/modules/module-hal-detect.c +++ b/src/modules/module-hal-detect.c @@ -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); } } } diff --git a/src/modules/module-jack-sink.c b/src/modules/module-jack-sink.c index a42aa9ef..1ef5d235 100644 --- a/src/modules/module-jack-sink.c +++ b/src/modules/module-jack-sink.c @@ -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); diff --git a/src/modules/module-jack-source.c b/src/modules/module-jack-source.c index 4ee08bf1..fa2ec5eb 100644 --- a/src/modules/module-jack-source.c +++ b/src/modules/module-jack-source.c @@ -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); diff --git a/src/modules/module-ladspa-sink.c b/src/modules/module-ladspa-sink.c index fcfeffd5..245efcb0 100644 --- a/src/modules/module-ladspa-sink.c +++ b/src/modules/module-ladspa-sink.c @@ -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); diff --git a/src/modules/module-match.c b/src/modules/module-match.c index ed5f3076..d0265455 100644 --- a/src/modules/module-match.c +++ b/src/modules/module-match.c @@ -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); } diff --git a/src/modules/module-null-sink.c b/src/modules/module-null-sink.c index de35fff9..aff244fa 100644 --- a/src/modules/module-null-sink.c +++ b/src/modules/module-null-sink.c @@ -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."); diff --git a/src/modules/module-oss.c b/src/modules/module-oss.c index a7df8a0c..cf7584db 100644 --- a/src/modules/module-oss.c +++ b/src/modules/module-oss.c @@ -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); diff --git a/src/modules/module-pipe-sink.c b/src/modules/module-pipe-sink.c index e720c8ad..cc648928 100644 --- a/src/modules/module-pipe-sink.c +++ b/src/modules/module-pipe-sink.c @@ -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); diff --git a/src/modules/module-pipe-source.c b/src/modules/module-pipe-source.c index 02935649..83eb4f8a 100644 --- a/src/modules/module-pipe-source.c +++ b/src/modules/module-pipe-source.c @@ -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); diff --git a/src/modules/module-protocol-stub.c b/src/modules/module-protocol-stub.c index 600201b4..8bcc19b1 100644 --- a/src/modules/module-protocol-stub.c +++ b/src/modules/module-protocol-stub.c @@ -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); diff --git a/src/modules/module-remap-sink.c b/src/modules/module-remap-sink.c index 39a9245d..0b9825e1 100644 --- a/src/modules/module-remap-sink.c +++ b/src/modules/module-remap-sink.c @@ -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); } diff --git a/src/modules/module-rescue-streams.c b/src/modules/module-rescue-streams.c index 12957c9d..7241a99f 100644 --- a/src/modules/module-rescue-streams.c +++ b/src/modules/module-rescue-streams.c @@ -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); } diff --git a/src/modules/module-sine.c b/src/modules/module-sine.c index 41d9a51c..3d917054 100644 --- a/src/modules/module-sine.c +++ b/src/modules/module-sine.c @@ -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); diff --git a/src/modules/module-suspend-on-idle.c b/src/modules/module-suspend-on-idle.c index 4c260d76..a3985974 100644 --- a/src/modules/module-suspend-on-idle.c +++ b/src/modules/module-suspend-on-idle.c @@ -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); diff --git a/src/modules/module-tunnel.c b/src/modules/module-tunnel.c index 62dac5d3..7a87fd8c 100644 --- a/src/modules/module-tunnel.c +++ b/src/modules/module-tunnel.c @@ -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)); diff --git a/src/modules/module-volume-restore.c b/src/modules/module-volume-restore.c index 192a2a78..336bcac9 100644 --- a/src/modules/module-volume-restore.c +++ b/src/modules/module-volume-restore.c @@ -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; diff --git a/src/modules/module-x11-bell.c b/src/modules/module-x11-bell.c index 87c6849d..761b82a9 100644 --- a/src/modules/module-x11-bell.c +++ b/src/modules/module-x11-bell.c @@ -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); } diff --git a/src/modules/module-zeroconf-publish.c b/src/modules/module-zeroconf-publish.c index 46969a24..6ed8e3d9 100644 --- a/src/modules/module-zeroconf-publish.c +++ b/src/modules/module-zeroconf-publish.c @@ -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; diff --git a/src/modules/oss-util.c b/src/modules/oss-util.c index 9598feee..e29f0eda 100644 --- a/src/modules/oss-util.c +++ b/src/modules/oss-util.c @@ -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; diff --git a/src/modules/oss-util.h b/src/modules/oss-util.h index 259a622a..8fea805c 100644 --- a/src/modules/oss-util.h +++ b/src/modules/oss-util.h @@ -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); diff --git a/src/modules/rtp/module-rtp-recv.c b/src/modules/rtp/module-rtp-recv.c index d8e7a781..cff5cf8b 100644 --- a/src/modules/rtp/module-rtp-recv.c +++ b/src/modules/rtp/module-rtp-recv.c @@ -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; diff --git a/src/modules/rtp/module-rtp-send.c b/src/modules/rtp/module-rtp-send.c index 95ff15de..3a526c14 100644 --- a/src/modules/rtp/module-rtp-send.c +++ b/src/modules/rtp/module-rtp-send.c @@ -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; diff --git a/src/modules/rtp/rtp.c b/src/modules/rtp/rtp.c index 997fcc34..5c299844 100644 --- a/src/modules/rtp/rtp.c +++ b/src/modules/rtp/rtp.c @@ -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; } - diff --git a/src/modules/rtp/rtp.h b/src/modules/rtp/rtp.h index ad7175ca..a366d7a6 100644 --- a/src/modules/rtp/rtp.h +++ b/src/modules/rtp/rtp.h @@ -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); diff --git a/src/modules/rtp/sap.c b/src/modules/rtp/sap.c index ed7eb0be..123bc494 100644 --- a/src/modules/rtp/sap.c +++ b/src/modules/rtp/sap.c @@ -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; diff --git a/src/modules/rtp/sap.h b/src/modules/rtp/sap.h index f906a32b..db096d61 100644 --- a/src/modules/rtp/sap.h +++ b/src/modules/rtp/sap.h @@ -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 diff --git a/src/modules/rtp/sdp.c b/src/modules/rtp/sdp.c index 50ac157a..9265a200 100644 --- a/src/modules/rtp/sdp.c +++ b/src/modules/rtp/sdp.c @@ -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; } } } diff --git a/src/pulse/browser.c b/src/pulse/browser.c index 55e0b2cd..5e4aa87b 100644 --- a/src/pulse/browser.c +++ b/src/pulse/browser.c @@ -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); diff --git a/src/pulse/cdecl.h b/src/pulse/cdecl.h index e1f23d25..922ad276 100644 --- a/src/pulse/cdecl.h +++ b/src/pulse/cdecl.h @@ -41,22 +41,4 @@ #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 diff --git a/src/pulse/channelmap.h b/src/pulse/channelmap.h index a05e1911..00d3eb0d 100644 --- a/src/pulse/channelmap.h +++ b/src/pulse/channelmap.h @@ -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. */ diff --git a/src/pulse/client-conf-x11.c b/src/pulse/client-conf-x11.c index e240ba88..49df4b6c 100644 --- a/src/pulse/client-conf-x11.c +++ b/src/pulse/client-conf-x11.c @@ -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; diff --git a/src/pulse/client-conf.c b/src/pulse/client-conf.c index c054f663..75f44182 100644 --- a/src/pulse/client-conf.c +++ b/src/pulse/client-conf.c @@ -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); diff --git a/src/pulse/context.c b/src/pulse/context.c index 7243a29d..f9f021af 100644 --- a/src/pulse/context.c +++ b/src/pulse/context.c @@ -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); + } +} diff --git a/src/pulse/context.h b/src/pulse/context.h index 1de3abad..143508f4 100644 --- a/src/pulse/context.h +++ b/src/pulse/context.h @@ -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 diff --git a/src/pulse/def.h b/src/pulse/def.h index dabbc5eb..10722329 100644 --- a/src/pulse/def.h +++ b/src/pulse/def.h @@ -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 */ diff --git a/src/pulsecore/gccmacro.h b/src/pulse/gccmacro.h index f94a8c45..032c3bae 100644 --- a/src/pulsecore/gccmacro.h +++ b/src/pulse/gccmacro.h @@ -77,13 +77,21 @@ #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 diff --git a/src/pulse/internal.h b/src/pulse/internal.h index 873f1363..d346e945 100644 --- a/src/pulse/internal.h +++ b/src/pulse/internal.h @@ -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 diff --git a/src/pulse/introspect.c b/src/pulse/introspect.c index 6610a724..857e82b4 100644 --- a/src/pulse/introspect.c +++ b/src/pulse/introspect.c @@ -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; diff --git a/src/pulse/introspect.h b/src/pulse/introspect.h index c148ee5e..d185a3a6 100644 --- a/src/pulse/introspect.h +++ b/src/pulse/introspect.h @@ -30,8 +30,10 @@ #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 * @@ -206,21 +208,32 @@ 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 diff --git a/src/pulse/mainloop-api.c b/src/pulse/mainloop-api.c index b2ed3434..dda51297 100644 --- a/src/pulse/mainloop-api.c +++ b/src/pulse/mainloop-api.c @@ -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); } - diff --git a/src/pulse/mainloop-signal.c b/src/pulse/mainloop-signal.c index e41ed14c..91c6bf6d 100644 --- a/src/pulse/mainloop-signal.c +++ b/src/pulse/mainloop-signal.c @@ -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 @@ -39,11 +39,11 @@ #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; diff --git a/src/pulse/mainloop-signal.h b/src/pulse/mainloop-signal.h index 50aa99ce..bdb0f738 100644 --- a/src/pulse/mainloop-signal.h +++ b/src/pulse/mainloop-signal.h @@ -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 diff --git a/src/pulse/operation.c b/src/pulse/operation.c index 5d2da5b8..6b5c142a 100644 --- a/src/pulse/operation.c +++ b/src/pulse/operation.c @@ -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); } diff --git a/src/pulse/proplist.c b/src/pulse/proplist.c index c27c9d84..33bd274f 100644 --- a/src/pulse/proplist.c +++ b/src/pulse/proplist.c @@ -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; +} diff --git a/src/pulse/proplist.h b/src/pulse/proplist.h index c4cf9ac9..f433ec62 100644 --- a/src/pulse/proplist.h +++ b/src/pulse/proplist.h @@ -24,67 +24,176 @@ 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 diff --git a/src/pulse/sample.c b/src/pulse/sample.c index 27c0df03..43340f20 100644 --- a/src/pulse/sample.c +++ b/src/pulse/sample.c @@ -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) { diff --git a/src/pulse/sample.h b/src/pulse/sample.h index f0b839fd..dedd72de 100644 --- a/src/pulse/sample.h +++ b/src/pulse/sample.h @@ -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 */ diff --git a/src/pulse/scache.c b/src/pulse/scache.c index 186b0a3e..e43a0b9f 100644 --- a/src/pulse/scache.c +++ b/src/pulse/scache.c @@ -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; } - diff --git a/src/pulse/scache.h b/src/pulse/scache.h index 31fd8956..46d86a19 100644 --- a/src/pulse/scache.h +++ b/src/pulse/scache.h @@ -79,14 +79,25 @@ 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 diff --git a/src/pulse/simple.h b/src/pulse/simple.h index 0ddd57e0..7fca6ac3 100644 --- a/src/pulse/simple.h +++ b/src/pulse/simple.h @@ -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 diff --git a/src/pulse/stream.c b/src/pulse/stream.c index c44323fc..4268fd6f 100644 --- a/src/pulse/stream.c +++ b/src/pulse/stream.c @@ -38,12 +38,48 @@ #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; } diff --git a/src/pulse/stream.h b/src/pulse/stream.h index 85473227..ebb45f2b 100644 --- a/src/pulse/stream.h +++ b/src/pulse/stream.h @@ -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 diff --git a/src/pulse/subscribe.c b/src/pulse/subscribe.c index 580038cc..0c5686b7 100644 --- a/src/pulse/subscribe.c +++ b/src/pulse/subscribe.c @@ -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; } diff --git a/src/pulse/timeval.c b/src/pulse/timeval.c index 70ceb71e..180e0159 100644 --- a/src/pulse/timeval.c +++ b/src/pulse/timeval.c @@ -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); diff --git a/src/pulse/timeval.h b/src/pulse/timeval.h index 65a0e513..09d53974 100644 --- a/src/pulse/timeval.h +++ b/src/pulse/timeval.h @@ -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); diff --git a/src/pulse/utf8.h b/src/pulse/utf8.h index 1e08047c..840c74e0 100644 --- a/src/pulse/utf8.h +++ b/src/pulse/utf8.h @@ -26,6 +26,7 @@ ***/ #include <pulse/cdecl.h> +#include <pulse/gccmacro.h> /** \file * UTF8 Validation functions diff --git a/src/pulse/util.h b/src/pulse/util.h index 764678e5..666ccce4 100644 --- a/src/pulse/util.h +++ b/src/pulse/util.h @@ -28,6 +28,7 @@ #include <stddef.h> #include <pulse/cdecl.h> +#include <pulse/gccmacro.h> /** \file * Assorted utility functions */ diff --git a/src/pulse/version.h.in b/src/pulse/version.h.in index 20c7a9c0..dc0f8e3b 100644 --- a/src/pulse/version.h.in +++ b/src/pulse/version.h.in @@ -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 diff --git a/src/pulse/volume.c b/src/pulse/volume.c index 3688b847..33ab1c5f 100644 --- a/src/pulse/volume.c +++ b/src/pulse/volume.c @@ -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); diff --git a/src/pulse/volume.h b/src/pulse/volume.h index 22e5b8a4..e7ceb0d7 100644 --- a/src/pulse/volume.h +++ b/src/pulse/volume.h @@ -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 diff --git a/src/pulse/xmalloc.c b/src/pulse/xmalloc.c index 5348dda4..a64761bf 100644 --- a/src/pulse/xmalloc.c +++ b/src/pulse/xmalloc.c @@ -29,9 +29,10 @@ #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; } diff --git a/src/pulsecore/asyncmsgq.c b/src/pulsecore/asyncmsgq.c index 96b43a71..eba1c2cb 100644 --- a/src/pulsecore/asyncmsgq.c +++ b/src/pulsecore/asyncmsgq.c @@ -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) { diff --git a/src/pulsecore/asyncmsgq.h b/src/pulsecore/asyncmsgq.h index 5d3867ba..93f1ce86 100644 --- a/src/pulsecore/asyncmsgq.h +++ b/src/pulsecore/asyncmsgq.h @@ -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 diff --git a/src/pulsecore/asyncq.c b/src/pulsecore/asyncq.c index 75b15c0e..8e0dfbc3 100644 --- a/src/pulsecore/asyncq.c +++ b/src/pulsecore/asyncq.c @@ -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 @@ -33,14 +33,16 @@ #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 */ @@ -51,18 +53,25 @@ #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; + } +} diff --git a/src/pulsecore/asyncq.h b/src/pulsecore/asyncq.h index 53d45866..4cdf8cd0 100644 --- a/src/pulsecore/asyncq.h +++ b/src/pulsecore/asyncq.h @@ -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 diff --git a/src/pulsecore/cli-command.c b/src/pulsecore/cli-command.c index 423c3f2a..925a2e8c 100644 --- a/src/pulsecore/cli-command.c +++ b/src/pulsecore/cli-command.c @@ -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); diff --git a/src/pulsecore/cli-command.h b/src/pulsecore/cli-command.h index c90c8e08..2a923443 100644 --- a/src/pulsecore/cli-command.h +++ b/src/pulsecore/cli-command.h @@ -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); diff --git a/src/pulsecore/cli-text.c b/src/pulsecore/cli-text.c index b64cafe2..029a7089 100644 --- a/src/pulsecore/cli-text.c +++ b/src/pulsecore/cli-text.c @@ -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, diff --git a/src/pulsecore/cli.c b/src/pulsecore/cli.c index 85e08634..47712d30 100644 --- a/src/pulsecore/cli.c +++ b/src/pulsecore/cli.c @@ -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); diff --git a/src/pulsecore/client.c b/src/pulsecore/client.c index 319b8387..4eca4e2a 100644 --- a/src/pulsecore/client.c +++ b/src/pulsecore/client.c @@ -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); } diff --git a/src/pulsecore/client.h b/src/pulsecore/client.h index 6d09b999..bff057ed 100644 --- a/src/pulsecore/client.h +++ b/src/pulsecore/client.h @@ -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 index 4bc05137..00000000 --- a/src/pulsecore/core-def.h +++ /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 diff --git a/src/pulsecore/core-scache.c b/src/pulsecore/core-scache.c index 46444a90..e5546007 100644 --- a/src/pulsecore/core-scache.c +++ b/src/pulsecore/core-scache.c @@ -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; } diff --git a/src/pulsecore/core-scache.h b/src/pulsecore/core-scache.h index ab7ec0ef..31f3ff32 100644 --- a/src/pulsecore/core-scache.h +++ b/src/pulsecore/core-scache.h @@ -29,11 +29,12 @@ #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); diff --git a/src/pulsecore/core-util.c b/src/pulsecore/core-util.c index 61d04c2d..c8ea4f52 100644 --- a/src/pulsecore/core-util.c +++ b/src/pulsecore/core-util.c @@ -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> @@ -103,12 +104,6 @@ #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)); +} diff --git a/src/pulsecore/core-util.h b/src/pulsecore/core-util.h index c8760a1f..ec4cdc43 100644 --- a/src/pulsecore/core-util.h +++ b/src/pulsecore/core-util.h @@ -30,11 +30,27 @@ #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 diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c index cf018509..3b758a38 100644 --- a/src/pulsecore/core.c +++ b/src/pulsecore/core.c @@ -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); diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h index ce45e300..50c05b4c 100644 --- a/src/pulsecore/core.h +++ b/src/pulsecore/core.h @@ -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; diff --git a/src/pulsecore/envelope.c b/src/pulsecore/envelope.c index 571f8754..2f5da5a0 100644 --- a/src/pulsecore/envelope.c +++ b/src/pulsecore/envelope.c @@ -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); diff --git a/src/pulsecore/envelope.h b/src/pulsecore/envelope.h index 23be8f6a..c54c137a 100644 --- a/src/pulsecore/envelope.h +++ b/src/pulsecore/envelope.h @@ -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; diff --git a/src/pulsecore/fdsem.c b/src/pulsecore/fdsem.c index 59eec18e..22d2a850 100644 --- a/src/pulsecore/fdsem.c +++ b/src/pulsecore/fdsem.c @@ -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; diff --git a/src/pulsecore/fdsem.h b/src/pulsecore/fdsem.h index f38ef205..f4f7b99a 100644 --- a/src/pulsecore/fdsem.h +++ b/src/pulsecore/fdsem.h @@ -33,7 +33,15 @@ 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); diff --git a/src/pulsecore/flist.h b/src/pulsecore/flist.h index daf0fec4..3d9a89a2 100644 --- a/src/pulsecore/flist.h +++ b/src/pulsecore/flist.h @@ -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 */ diff --git a/src/pulsecore/hook-list.h b/src/pulsecore/hook-list.h index b3bd600a..c288980d 100644 --- a/src/pulsecore/hook-list.h +++ b/src/pulsecore/hook-list.h @@ -24,9 +24,10 @@ 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; diff --git a/src/pulsecore/ioline.c b/src/pulsecore/ioline.c index 5fd2189b..85bbadc4 100644 --- a/src/pulsecore/ioline.c +++ b/src/pulsecore/ioline.c @@ -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); diff --git a/src/pulsecore/ioline.h b/src/pulsecore/ioline.h index 8475b798..f4edc7b4 100644 --- a/src/pulsecore/ioline.h +++ b/src/pulsecore/ioline.h @@ -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); diff --git a/src/pulsecore/log.c b/src/pulsecore/log.c index c824e84d..b5929ec4 100644 --- a/src/pulsecore/log.c +++ b/src/pulsecore/log.c @@ -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( diff --git a/src/pulsecore/log.h b/src/pulsecore/log.h index b0711dca..765dd2e5 100644 --- a/src/pulsecore/log.h +++ b/src/pulsecore/log.h @@ -27,7 +27,7 @@ #include <stdarg.h> #include <stdlib.h> -#include <pulsecore/gccmacro.h> +#include <pulse/gccmacro.h> /* A simple logging subsystem */ diff --git a/src/pulsecore/ltdl-helper.c b/src/pulsecore/ltdl-helper.c index 711396d8..b83897a6 100644 --- a/src/pulsecore/ltdl-helper.c +++ b/src/pulsecore/ltdl-helper.c @@ -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; diff --git a/src/pulsecore/macro.h b/src/pulsecore/macro.h index ba538172..1d9eafd5 100644 --- a/src/pulsecore/macro.h +++ b/src/pulsecore/macro.h @@ -33,12 +33,22 @@ #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 diff --git a/src/pulsecore/mcalign.c b/src/pulsecore/mcalign.c index 8ca7c962..e12f84f8 100644 --- a/src/pulsecore/mcalign.c +++ b/src/pulsecore/mcalign.c @@ -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); +} diff --git a/src/pulsecore/mcalign.h b/src/pulsecore/mcalign.h index 6ff8f94e..6c8b8d5f 100644 --- a/src/pulsecore/mcalign.h +++ b/src/pulsecore/mcalign.h @@ -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 diff --git a/src/pulsecore/memblock.c b/src/pulsecore/memblock.c index 99b5a13f..7005b441 100644 --- a/src/pulsecore/memblock.c +++ b/src/pulsecore/memblock.c @@ -46,8 +46,12 @@ #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); diff --git a/src/pulsecore/memblock.h b/src/pulsecore/memblock.h index c704014a..8dc3f5a3 100644 --- a/src/pulsecore/memblock.h +++ b/src/pulsecore/memblock.h @@ -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 */ diff --git a/src/pulsecore/memblockq.c b/src/pulsecore/memblockq.c index 8247feab..c047e56f 100644 --- a/src/pulsecore/memblockq.c +++ b/src/pulsecore/memblockq.c @@ -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; +} diff --git a/src/pulsecore/memblockq.h b/src/pulsecore/memblockq.h index 46637f10..7c38757f 100644 --- a/src/pulsecore/memblockq.h +++ b/src/pulsecore/memblockq.h @@ -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 diff --git a/src/pulsecore/memchunk.c b/src/pulsecore/memchunk.c index 4e73b636..16a9c140 100644 --- a/src/pulsecore/memchunk.c +++ b/src/pulsecore/memchunk.c @@ -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; +} diff --git a/src/pulsecore/memchunk.h b/src/pulsecore/memchunk.h index e6105ace..46a82406 100644 --- a/src/pulsecore/memchunk.h +++ b/src/pulsecore/memchunk.h @@ -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 diff --git a/src/pulsecore/module.c b/src/pulsecore/module.c index ae140ff4..8e5bd2d1 100644 --- a/src/pulsecore/module.c +++ b/src/pulsecore/module.c @@ -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); diff --git a/src/pulsecore/module.h b/src/pulsecore/module.h index 25f122d1..68c7238d 100644 --- a/src/pulsecore/module.h +++ b/src/pulsecore/module.h @@ -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); diff --git a/src/pulsecore/namereg.c b/src/pulsecore/namereg.c index fe520384..1b0977d7 100644 --- a/src/pulsecore/namereg.c +++ b/src/pulsecore/namereg.c @@ -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); diff --git a/src/pulsecore/namereg.h b/src/pulsecore/namereg.h index d0db9e81..0f5b4d4d 100644 --- a/src/pulsecore/namereg.h +++ b/src/pulsecore/namereg.h @@ -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); diff --git a/src/pulsecore/native-common.h b/src/pulsecore/native-common.h index 3ab2361b..56f9037e 100644 --- a/src/pulsecore/native-common.h +++ b/src/pulsecore/native-common.h @@ -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 }; diff --git a/src/pulsecore/pid.c b/src/pulsecore/pid.c index f3c9faaa..2ff132bb 100644 --- a/src/pulsecore/pid.c +++ b/src/pulsecore/pid.c @@ -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; } diff --git a/src/pulsecore/play-memblockq.c b/src/pulsecore/play-memblockq.c index 5d3c2d39..2688f923 100644 --- a/src/pulsecore/play-memblockq.c +++ b/src/pulsecore/play-memblockq.c @@ -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 @@ -30,10 +30,11 @@ #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); + } } diff --git a/src/pulsecore/play-memblockq.h b/src/pulsecore/play-memblockq.h index d8790316..9ecf7700 100644 --- a/src/pulsecore/play-memblockq.h +++ b/src/pulsecore/play-memblockq.h @@ -29,20 +29,21 @@ 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 diff --git a/src/pulsecore/play-memchunk.c b/src/pulsecore/play-memchunk.c index 6aaec567..67a92138 100644 --- a/src/pulsecore/play-memchunk.c +++ b/src/pulsecore/play-memchunk.c @@ -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 @@ -30,167 +30,37 @@ #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; } - diff --git a/src/pulsecore/play-memchunk.h b/src/pulsecore/play-memchunk.h index 5afb094c..f7c9d178 100644 --- a/src/pulsecore/play-memchunk.h +++ b/src/pulsecore/play-memchunk.h @@ -29,10 +29,11 @@ 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 diff --git a/src/pulsecore/protocol-cli.c b/src/pulsecore/protocol-cli.c index ceb6ae4d..2f797a14 100644 --- a/src/pulsecore/protocol-cli.c +++ b/src/pulsecore/protocol-cli.c @@ -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); diff --git a/src/pulsecore/protocol-esound.c b/src/pulsecore/protocol-esound.c index f963f2ad..492dc9fa 100644 --- a/src/pulsecore/protocol-esound.c +++ b/src/pulsecore/protocol-esound.c @@ -70,10 +70,12 @@ #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); diff --git a/src/pulsecore/protocol-http.c b/src/pulsecore/protocol-http.c index d91ae142..bc2e9af6 100644 --- a/src/pulsecore/protocol-http.c +++ b/src/pulsecore/protocol-http.c @@ -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); diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c index 174342ed..2adcdfc7 100644 --- a/src/pulsecore/protocol-native.c +++ b/src/pulsecore/protocol-native.c @@ -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))) { diff --git a/src/pulsecore/protocol-simple.c b/src/pulsecore/protocol-simple.c index 777def30..cbe48440 100644 --- a/src/pulsecore/protocol-simple.c +++ b/src/pulsecore/protocol-simple.c @@ -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; diff --git a/src/pulsecore/pstream.c b/src/pulsecore/pstream.c index 9d32a363..5004d67f 100644 --- a/src/pulsecore/pstream.c +++ b/src/pulsecore/pstream.c @@ -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; +} diff --git a/src/pulsecore/pstream.h b/src/pulsecore/pstream.h index 72babea9..00b37b71 100644 --- a/src/pulsecore/pstream.h +++ b/src/pulsecore/pstream.h @@ -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 diff --git a/src/pulsecore/refcnt.h b/src/pulsecore/refcnt.h index 64271ab2..f0885fb4 100644 --- a/src/pulsecore/refcnt.h +++ b/src/pulsecore/refcnt.h @@ -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) diff --git a/src/pulsecore/resampler.c b/src/pulsecore/resampler.c index fe7f1ad2..d645639c 100644 --- a/src/pulsecore/resampler.c +++ b/src/pulsecore/resampler.c @@ -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) { diff --git a/src/pulsecore/resampler.h b/src/pulsecore/resampler.h index 82d01082..8534f5b5 100644 --- a/src/pulsecore/resampler.h +++ b/src/pulsecore/resampler.h @@ -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); diff --git a/src/pulsecore/rtclock.c b/src/pulsecore/rtclock.c index 07d776e4..e74e5243 100644 --- a/src/pulsecore/rtclock.c +++ b/src/pulsecore/rtclock.c @@ -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; +} diff --git a/src/pulsecore/rtclock.h b/src/pulsecore/rtclock.h index f0360af3..f68ad761 100644 --- a/src/pulsecore/rtclock.h +++ b/src/pulsecore/rtclock.h @@ -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 diff --git a/src/pulsecore/rtpoll.c b/src/pulsecore/rtpoll.c index 83008266..64fa42ad 100644 --- a/src/pulsecore/rtpoll.c +++ b/src/pulsecore/rtpoll.c @@ -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; diff --git a/src/pulsecore/rtpoll.h b/src/pulsecore/rtpoll.h index 02f5c7c2..16dadbc3 100644 --- a/src/pulsecore/rtpoll.h +++ b/src/pulsecore/rtpoll.h @@ -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 */ diff --git a/src/pulsecore/sample-util.c b/src/pulsecore/sample-util.c index 4ea5d446..a8028296 100644 --- a/src/pulsecore/sample-util.c +++ b/src/pulsecore/sample-util.c @@ -42,29 +42,6 @@ #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); + } + } +} diff --git a/src/pulsecore/sample-util.h b/src/pulsecore/sample-util.h index 2ef8f924..59b4c632 100644 --- a/src/pulsecore/sample-util.h +++ b/src/pulsecore/sample-util.h @@ -30,10 +30,18 @@ #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 diff --git a/src/pulsecore/shm.c b/src/pulsecore/shm.c index 7c764e3a..a6843819 100644 --- a/src/pulsecore/shm.c +++ b/src/pulsecore/shm.c @@ -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 @@ -69,14 +70,15 @@ #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)); } diff --git a/src/pulsecore/shm.h b/src/pulsecore/shm.h index 270591de..60bc355f 100644 --- a/src/pulsecore/shm.h +++ b/src/pulsecore/shm.h @@ -26,15 +26,17 @@ #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 index 00000000..7af2985c --- /dev/null +++ b/src/pulsecore/shmasyncq.c @@ -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 index 00000000..ca35ffd2 --- /dev/null +++ b/src/pulsecore/shmasyncq.h @@ -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 diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index 07ddb83a..d51ff810 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -38,11 +38,12 @@ #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; +} diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h index 8975db9e..5f146122 100644 --- a/src/pulsecore/sink-input.h +++ b/src/pulsecore/sink-input.h @@ -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 diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c index 9adb6097..31c3cfc8 100644 --- a/src/pulsecore/sink.c +++ b/src/pulsecore/sink.c @@ -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> @@ -47,43 +48,128 @@ #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); +} diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h index e9969309..f297c8f1 100644 --- a/src/pulsecore/sink.h +++ b/src/pulsecore/sink.h @@ -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 diff --git a/src/pulsecore/socket-client.c b/src/pulsecore/socket-client.c index 5b5bc5ca..a99193b7 100644 --- a/src/pulsecore/socket-client.c +++ b/src/pulsecore/socket-client.c @@ -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); diff --git a/src/pulsecore/socket-client.h b/src/pulsecore/socket-client.h index b1d58eff..41e8c3bd 100644 --- a/src/pulsecore/socket-client.h +++ b/src/pulsecore/socket-client.h @@ -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 diff --git a/src/pulsecore/sound-file-stream.c b/src/pulsecore/sound-file-stream.c index bb1f3e9a..e209676f 100644 --- a/src/pulsecore/sound-file-stream.c +++ b/src/pulsecore/sound-file-stream.c @@ -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,17 +41,23 @@ #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 diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c index 88c11469..5c36937a 100644 --- a/src/pulsecore/source-output.c +++ b/src/pulsecore/source-output.c @@ -32,12 +32,16 @@ #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; } } diff --git a/src/pulsecore/source-output.h b/src/pulsecore/source-output.h index d6da8d00..2dadb5c4 100644 --- a/src/pulsecore/source-output.h +++ b/src/pulsecore/source-output.h @@ -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 diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c index d707ad86..c767abcf 100644 --- a/src/pulsecore/source.c +++ b/src/pulsecore/source.c @@ -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> @@ -41,40 +42,121 @@ #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); } diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h index bd0a9122..f9c9cbf9 100644 --- a/src/pulsecore/source.h +++ b/src/pulsecore/source.h @@ -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 diff --git a/src/pulsecore/strbuf.h b/src/pulsecore/strbuf.h index 1c0850b1..d3555a2c 100644 --- a/src/pulsecore/strbuf.h +++ b/src/pulsecore/strbuf.h @@ -24,7 +24,7 @@ USA. ***/ -#include <pulsecore/gccmacro.h> +#include <pulse/gccmacro.h> typedef struct pa_strbuf pa_strbuf; diff --git a/src/pulsecore/tagstruct.c b/src/pulsecore/tagstruct.c index 556fe806..7616cd16 100644 --- a/src/pulsecore/tagstruct.c +++ b/src/pulsecore/tagstruct.c @@ -42,12 +42,14 @@ #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(); } diff --git a/src/pulsecore/tagstruct.h b/src/pulsecore/tagstruct.h index e9bb9ac8..8699e6c8 100644 --- a/src/pulsecore/tagstruct.h +++ b/src/pulsecore/tagstruct.h @@ -32,6 +32,10 @@ #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 diff --git a/src/pulsecore/thread-mq.c b/src/pulsecore/thread-mq.c index 9b879425..7e39c577 100644 --- a/src/pulsecore/thread-mq.c +++ b/src/pulsecore/thread-mq.c @@ -43,15 +43,15 @@ 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); diff --git a/src/pulsecore/thread-mq.h b/src/pulsecore/thread-mq.h index 13b6e01f..0ae49f8c 100644 --- a/src/pulsecore/thread-mq.h +++ b/src/pulsecore/thread-mq.h @@ -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 @@ -34,10 +35,10 @@ 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 */ diff --git a/src/pulsecore/time-smoother.c b/src/pulsecore/time-smoother.c index 4cebded4..9b4be29f 100644 --- a/src/pulsecore/time-smoother.c +++ b/src/pulsecore/time-smoother.c @@ -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); +} diff --git a/src/pulsecore/time-smoother.h b/src/pulsecore/time-smoother.h index 8b8512e2..b301b48c 100644 --- a/src/pulsecore/time-smoother.h +++ b/src/pulsecore/time-smoother.h @@ -29,13 +29,19 @@ 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); diff --git a/src/pulsecore/tokenizer.c b/src/pulsecore/tokenizer.c index f79c19c5..cf5da648 100644 --- a/src/pulsecore/tokenizer.c +++ b/src/pulsecore/tokenizer.c @@ -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" diff --git a/src/tests/asyncq-test.c b/src/tests/asyncq-test.c index 09b20047..0e10ed89 100644 --- a/src/tests/asyncq-test.c +++ b/src/tests/asyncq-test.c @@ -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; diff --git a/src/tests/channelmap-test.c b/src/tests/channelmap-test.c index 98f36b61..d26d2cff 100644 --- a/src/tests/channelmap-test.c +++ b/src/tests/channelmap-test.c @@ -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]; diff --git a/src/tests/cpulimit-test.c b/src/tests/cpulimit-test.c index d582e9c5..4563c0f6 100644 --- a/src/tests/cpulimit-test.c +++ b/src/tests/cpulimit-test.c @@ -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> diff --git a/src/tests/interpol-test.c b/src/tests/interpol-test.c index 85a509d4..f894d2f3 100644 --- a/src/tests/interpol-test.c +++ b/src/tests/interpol-test.c @@ -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; } diff --git a/src/tests/mainloop-test.c b/src/tests/mainloop-test.c index c386251c..79a4aaa0 100644 --- a/src/tests/mainloop-test.c +++ b/src/tests/mainloop-test.c @@ -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 diff --git a/src/tests/mcalign-test.c b/src/tests/mcalign-test.c index d1013118..79dd5797 100644 --- a/src/tests/mcalign-test.c +++ b/src/tests/mcalign-test.c @@ -31,9 +31,10 @@ #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 */ diff --git a/src/tests/memblockq-test.c b/src/tests/memblockq-test.c index 25ea399b..3fa8d79f 100644 --- a/src/tests/memblockq-test.c +++ b/src/tests/memblockq-test.c @@ -31,22 +31,48 @@ #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; } diff --git a/src/tests/pacat-simple.c b/src/tests/pacat-simple.c index 2da67c1a..c2123b74 100644 --- a/src/tests/pacat-simple.c +++ b/src/tests/pacat-simple.c @@ -31,7 +31,7 @@ #include <pulse/simple.h> #include <pulse/error.h> -#include <pulsecore/gccmacro.h> +#include <pulse/gccmacro.h> #define BUFSIZE 1024 diff --git a/src/tests/parec-simple.c b/src/tests/parec-simple.c index d7d88360..9c66cc23 100644 --- a/src/tests/parec-simple.c +++ b/src/tests/parec-simple.c @@ -30,7 +30,7 @@ #include <pulse/simple.h> #include <pulse/error.h> -#include <pulsecore/gccmacro.h> +#include <pulse/gccmacro.h> #define BUFSIZE 1024 diff --git a/src/tests/proplist-test.c b/src/tests/proplist-test.c index b88f4e5e..5f7a78f3 100644 --- a/src/tests/proplist-test.c +++ b/src/tests/proplist-test.c @@ -28,25 +28,26 @@ #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); diff --git a/src/tests/rtpoll-test.c b/src/tests/rtpoll-test.c index e6493771..af942104 100644 --- a/src/tests/rtpoll-test.c +++ b/src/tests/rtpoll-test.c @@ -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 index 00000000..39dfc5d3 --- /dev/null +++ b/src/tests/rtstutter.c @@ -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(¶m, 0, sizeof(param)); + param.sched_priority = 12; + pa_assert_se(pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m) == 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; +} diff --git a/src/tests/smoother-test.c b/src/tests/smoother-test.c index 0816e76c..de037724 100644 --- a/src/tests/smoother-test.c +++ b/src/tests/smoother-test.c @@ -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 index 00000000..2f87e877 --- /dev/null +++ b/src/tests/stripnul.c @@ -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; +} diff --git a/src/tests/strlist-test.c b/src/tests/strlist-test.c index 47770b5d..2bd1645c 100644 --- a/src/tests/strlist-test.c +++ b/src/tests/strlist-test.c @@ -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; diff --git a/src/tests/thread-mainloop-test.c b/src/tests/thread-mainloop-test.c index 558e53a5..ac6d5049 100644 --- a/src/tests/thread-mainloop-test.c +++ b/src/tests/thread-mainloop-test.c @@ -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) { diff --git a/src/tests/voltest.c b/src/tests/voltest.c index dcc1ec51..91752ad9 100644 --- a/src/tests/voltest.c +++ b/src/tests/voltest.c @@ -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; diff --git a/src/utils/pacat.c b/src/utils/pacat.c index 68e308d8..fc9d56d6 100644 --- a/src/utils/pacat.c +++ b/src/utils/pacat.c @@ -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; } diff --git a/src/utils/pacmd.c b/src/utils/pacmd.c index daa6a96e..dff9af9d 100644 --- a/src/utils/pacmd.c +++ b/src/utils/pacmd.c @@ -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; diff --git a/src/utils/pactl.c b/src/utils/pactl.c index 674eaee6..1f875a88 100644 --- a/src/utils/pactl.c +++ b/src/utils/pactl.c @@ -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; } } diff --git a/src/utils/padsp.c b/src/utils/padsp.c index d3f034d0..e43a0de2 100644 --- a/src/utils/padsp.c +++ b/src/utils/padsp.c @@ -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; |