diff options
Diffstat (limited to 'src')
186 files changed, 12369 insertions, 4819 deletions
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;  | 
