diff options
| -rw-r--r-- | src/daemon/daemon-conf.c | 24 | ||||
| -rw-r--r-- | src/daemon/daemon-conf.h | 3 | ||||
| -rw-r--r-- | src/daemon/daemon.conf.in | 1 | ||||
| -rw-r--r-- | src/daemon/main.c | 169 | ||||
| -rw-r--r-- | src/map-file | 7 | ||||
| -rw-r--r-- | src/modules/module-suspend-on-idle.c | 72 | ||||
| -rw-r--r-- | src/modules/module-volume-restore.c | 134 | ||||
| -rw-r--r-- | src/pulse/context.c | 9 | ||||
| -rw-r--r-- | src/pulse/def.h | 79 | ||||
| -rw-r--r-- | src/pulse/internal.h | 16 | ||||
| -rw-r--r-- | src/pulse/operation.c | 2 | ||||
| -rw-r--r-- | src/pulse/stream.c | 436 | ||||
| -rw-r--r-- | src/pulse/stream.h | 71 | ||||
| -rw-r--r-- | src/pulsecore/cli-text.c | 14 | ||||
| -rw-r--r-- | src/pulsecore/core.c | 1 | ||||
| -rw-r--r-- | src/pulsecore/core.h | 3 | ||||
| -rw-r--r-- | src/pulsecore/pid.c | 34 | ||||
| -rw-r--r-- | src/pulsecore/pid.h | 4 | ||||
| -rw-r--r-- | src/pulsecore/protocol-native.c | 365 | ||||
| -rw-r--r-- | src/pulsecore/sink-input.c | 46 | ||||
| -rw-r--r-- | src/pulsecore/sink-input.h | 18 | ||||
| -rw-r--r-- | src/pulsecore/sink.c | 27 | ||||
| -rw-r--r-- | src/pulsecore/source-output.c | 42 | ||||
| -rw-r--r-- | src/pulsecore/source-output.h | 18 | ||||
| -rw-r--r-- | src/pulsecore/source.c | 27 | ||||
| -rw-r--r-- | src/utils/pacat.c | 101 | ||||
| -rw-r--r-- | src/utils/pacmd.c | 4 | 
27 files changed, 1497 insertions, 230 deletions
diff --git a/src/daemon/daemon-conf.c b/src/daemon/daemon-conf.c index 3d63891c..c98c0218 100644 --- a/src/daemon/daemon-conf.c +++ b/src/daemon/daemon-conf.c @@ -71,6 +71,7 @@ static const pa_daemon_conf default_conf = {      .log_target = PA_LOG_SYSLOG,      .log_level = PA_LOG_NOTICE,      .resample_method = PA_RESAMPLER_AUTO, +    .disable_remixing = FALSE,      .config_file = NULL,      .use_pid_file = TRUE,      .system_instance = FALSE, @@ -410,6 +411,7 @@ int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) {          { "default-fragments",          parse_fragments,          NULL },          { "default-fragment-size-msec", parse_fragment_size_msec, NULL },          { "nice-level",                 parse_nice_level,         NULL }, +        { "disable-remixing",           pa_config_parse_bool,     NULL },  #ifdef HAVE_SYS_RESOURCE_H          { "rlimit-as",                  parse_rlimit,             NULL },          { "rlimit-core",                parse_rlimit,             NULL }, @@ -458,33 +460,34 @@ int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) {      table[22].data = c;      table[23].data = c;      table[24].data = c; +    table[25].data = &c->disable_remixing;  #ifdef HAVE_SYS_RESOURCE_H -    table[25].data = &c->rlimit_as; -    table[26].data = &c->rlimit_core; -    table[27].data = &c->rlimit_data; -    table[28].data = &c->rlimit_fsize; -    table[29].data = &c->rlimit_nofile; -    table[30].data = &c->rlimit_stack; +    table[26].data = &c->rlimit_as; +    table[27].data = &c->rlimit_core; +    table[28].data = &c->rlimit_data; +    table[29].data = &c->rlimit_fsize; +    table[30].data = &c->rlimit_nofile; +    table[31].data = &c->rlimit_stack;  #ifdef RLIMIT_NPROC -    table[31].data = &c->rlimit_nproc; +    table[32].data = &c->rlimit_nproc;  #endif  #ifdef RLIMIT_MEMLOCK  #ifndef RLIMIT_NPROC  #error "Houston, we have a numbering problem!"  #endif -    table[32].data = &c->rlimit_memlock; +    table[33].data = &c->rlimit_memlock;  #endif  #ifdef RLIMIT_NICE  #ifndef RLIMIT_MEMLOCK  #error "Houston, we have a numbering problem!"  #endif -    table[33].data = &c->rlimit_nice; +    table[34].data = &c->rlimit_nice;  #endif  #ifdef RLIMIT_RTPRIO  #ifndef RLIMIT_NICE  #error "Houston, we have a numbering problem!"  #endif -    table[34].data = &c->rlimit_rtprio; +    table[35].data = &c->rlimit_rtprio;  #endif  #endif @@ -563,6 +566,7 @@ char *pa_daemon_conf_dump(pa_daemon_conf *c) {      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)); +    pa_strbuf_printf(s, "disable-remixing = %s\n", pa_yes_no(c->disable_remixing));      pa_strbuf_printf(s, "default-sample-format = %s\n", pa_sample_format_to_string(c->default_sample_spec.format));      pa_strbuf_printf(s, "default-sample-rate = %u\n", c->default_sample_spec.rate);      pa_strbuf_printf(s, "default-sample-channels = %u\n", c->default_sample_spec.channels); diff --git a/src/daemon/daemon-conf.h b/src/daemon/daemon-conf.h index b8930bd7..3dcafbfe 100644 --- a/src/daemon/daemon-conf.h +++ b/src/daemon/daemon-conf.h @@ -64,7 +64,8 @@ typedef struct pa_daemon_conf {          use_pid_file,          system_instance,          no_cpu_limit, -        disable_shm; +        disable_shm, +        disable_remixing;      int exit_idle_time,          module_idle_time,          scache_idle_time, diff --git a/src/daemon/daemon.conf.in b/src/daemon/daemon.conf.in index 8d224e82..d664962e 100644 --- a/src/daemon/daemon.conf.in +++ b/src/daemon/daemon.conf.in @@ -46,6 +46,7 @@  ; log-level = notice  ; resample-method = speex-float-3 +; disable-remixing = no  ; no-cpu-limit = no diff --git a/src/daemon/main.c b/src/daemon/main.c index 051c323e..236819e1 100644 --- a/src/daemon/main.c +++ b/src/daemon/main.c @@ -94,6 +94,7 @@  #include "dumpmodules.h"  #include "caps.h"  #include "ltdl-bind-now.h" +#include "polkit.h"  #ifdef HAVE_LIBWRAP  /* Only one instance of these variables */ @@ -281,17 +282,21 @@ static int create_runtime_dir(void) {  #ifdef HAVE_SYS_RESOURCE_H -static void set_one_rlimit(const pa_rlimit *r, int resource, const char *name) { +static int set_one_rlimit(const pa_rlimit *r, int resource, const char *name) {      struct rlimit rl;      pa_assert(r);      if (!r->is_set) -        return; +        return 0;      rl.rlim_cur = rl.rlim_max = r->value; -    if (setrlimit(resource, &rl) < 0) +    if (setrlimit(resource, &rl) < 0) {          pa_log_warn("setrlimit(%s, (%u, %u)) failed: %s", name, (unsigned) r->value, (unsigned) r->value, pa_cstrerror(errno)); +        return -1; +    } + +    return 0;  }  static void set_all_rlimits(const pa_daemon_conf *conf) { @@ -324,9 +329,10 @@ int main(int argc, char *argv[]) {      char *s;      int r = 0, retval = 1, d = 0;      int daemon_pipe[2] = { -1, -1 }; -    int suid_root, real_root; +    pa_bool_t suid_root, real_root;      int valid_pid_file = 0;      gid_t gid = (gid_t) -1; +    pa_bool_t allow_realtime, allow_high_priority;  #ifdef OS_IS_WIN32      pa_time_event *timer; @@ -357,8 +363,8 @@ int main(int argc, char *argv[]) {      real_root = getuid() == 0;      suid_root = !real_root && geteuid() == 0;  #else -    real_root = 0; -    suid_root = 0; +    real_root = FALSE; +    suid_root = FALSE;  #endif      if (suid_root) { @@ -377,29 +383,12 @@ int main(int argc, char *argv[]) {           * is just too risky tun let PA run as root all the time. */      } -    setlocale(LC_ALL, ""); - -    if (suid_root && (pa_own_uid_in_group(PA_REALTIME_GROUP, &gid) <= 0)) { -        pa_log_info("Warning: Called SUID root, but not in group '"PA_REALTIME_GROUP"'. " -                    "For enabling real-time scheduling please become a member of '"PA_REALTIME_GROUP"' , or increase the RLIMIT_RTPRIO user limit."); -        pa_drop_caps(); -        pa_drop_root(); -        suid_root = real_root = 0; -    } - -    LTDL_SET_PRELOADED_SYMBOLS(); - -    pa_ltdl_init(); - -#ifdef OS_IS_WIN32 -    { -        WSADATA data; -        WSAStartup(MAKEWORD(2, 0), &data); -    } -#endif - -    pa_random_seed(); +    /* At this point, we are a normal user, possibly with CAP_NICE if +     * we were started SUID. If we are started as normal root, than we +     * still are normal root. */ +    setlocale(LC_ALL, ""); +    pa_log_set_maximal_level(PA_LOG_INFO);      pa_log_set_ident("pulseaudio");      conf = pa_daemon_conf_new(); @@ -411,24 +400,123 @@ int main(int argc, char *argv[]) {          goto finish;      if (pa_cmdline_parse(conf, argc, argv, &d) < 0) { -        pa_log("failed to parse command line."); +        pa_log("Failed to parse command line.");          goto finish;      }      pa_log_set_maximal_level(conf->log_level);      pa_log_set_target(conf->auto_log_target ? PA_LOG_STDERR : conf->log_target, NULL); +    if (suid_root) { +        /* Ok, we're suid root, so let's better not enable high prio +         * or RT by default */ + +        allow_high_priority = allow_realtime = FALSE; + +#ifdef HAVE_POLKIT +        if (conf->high_priority) { +            if (pa_polkit_check("org.pulseaudio.acquire-high-priority") > 0) { +                pa_log_info("PolicyKit grants us acquire-high-priority privilige."); +                allow_high_priority = TRUE; +            } else +                pa_log_info("PolicyKit refuses acquire-high-priority privilige."); +        } + +        if (conf->realtime_scheduling) { +            if (pa_polkit_check("org.pulseaudio.acquire-real-time") > 0) { +                pa_log_info("PolicyKit grants us acquire-real-time privilige."); +                allow_realtime = TRUE; +            } else +                pa_log_info("PolicyKit refuses acquire-real-time privilige."); +        } +#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; + +            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; +    } + +    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; +    } +      if (conf->high_priority && conf->cmd == PA_CMD_DAEMON)          pa_raise_priority(conf->nice_level); -    if (suid_root && (conf->cmd != PA_CMD_DAEMON || !conf->realtime_scheduling)) { -        pa_drop_caps(); -        pa_drop_root(); +    if (suid_root) { +        pa_bool_t drop; + +        drop = conf->cmd != PA_CMD_DAEMON || !conf->realtime_scheduling; + +#ifdef RLIMIT_RTPRIO +        if (!drop) { + +            /* 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 (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)); +        } +#endif + +        if (drop)  { +            pa_drop_caps(); +            pa_drop_root(); +            suid_root = real_root = FALSE; +        }      } +    LTDL_SET_PRELOADED_SYMBOLS(); +    pa_ltdl_init(); +      if (conf->dl_search_path)          lt_dlsetsearchpath(conf->dl_search_path); +#ifdef OS_IS_WIN32 +    { +        WSADATA data; +        WSAStartup(MAKEWORD(2, 0), &data); +    } +#endif + +    pa_random_seed(); +      switch (conf->cmd) {          case PA_CMD_DUMP_MODULES:              pa_dump_modules(conf, argc-d, argv+d); @@ -466,10 +554,10 @@ int main(int argc, char *argv[]) {          case PA_CMD_CHECK: {              pid_t pid; -            if (pa_pid_file_check_running(&pid) < 0) { -                pa_log_info("daemon not running"); -            } else { -                pa_log_info("daemon running as PID %u", pid); +            if (pa_pid_file_check_running(&pid, "pulseaudio") < 0) +                pa_log_info("Daemon not running"); +            else { +                pa_log_info("Daemon running as PID %u", pid);                  retval = 0;              } @@ -478,8 +566,8 @@ int main(int argc, char *argv[]) {          }          case PA_CMD_KILL: -            if (pa_pid_file_kill(SIGINT, NULL) < 0) -                pa_log("failed to kill daemon."); +            if (pa_pid_file_kill(SIGINT, NULL, "pulseaudio") < 0) +                pa_log("Failed to kill daemon.");              else                  retval = 0; @@ -496,9 +584,9 @@ int main(int argc, char *argv[]) {              pa_assert(conf->cmd == PA_CMD_DAEMON);      } -    if (real_root && !conf->system_instance) { +    if (real_root && !conf->system_instance)          pa_log_warn("This program is not intended to be run as root (unless --system is specified)."); -    } else if (!real_root && conf->system_instance) { +    else if (!real_root && conf->system_instance) {          pa_log("Root priviliges required.");          goto finish;      } @@ -645,6 +733,7 @@ int main(int argc, char *argv[]) {      c->resample_method = conf->resample_method;      c->realtime_priority = conf->realtime_priority;      c->realtime_scheduling = !!conf->realtime_scheduling; +    c->disable_remixing = !!conf->disable_remixing;      pa_assert_se(pa_signal_init(pa_mainloop_get_api(mainloop)) == 0);      pa_signal_new(SIGINT, signal_callback, c); diff --git a/src/map-file b/src/map-file index 427a3a78..f38baefd 100644 --- a/src/map-file +++ b/src/map-file @@ -156,26 +156,33 @@ pa_stream_flush;  pa_stream_get_buffer_attr;  pa_stream_get_channel_map;  pa_stream_get_context; +pa_stream_get_device_index; +pa_stream_get_device_name;  pa_stream_get_index;  pa_stream_get_latency;  pa_stream_get_sample_spec;  pa_stream_get_state;  pa_stream_get_time;  pa_stream_get_timing_info; +pa_stream_is_suspended;  pa_stream_new;  pa_stream_peek;  pa_stream_prebuf;  pa_stream_readable_size;  pa_stream_ref; +pa_stream_set_buffer_attr;  pa_stream_set_latency_update_callback; +pa_stream_set_moved_callback;  pa_stream_set_name;  pa_stream_set_overflow_callback;  pa_stream_set_read_callback;  pa_stream_set_state_callback; +pa_stream_set_suspended_callback;  pa_stream_set_underflow_callback;  pa_stream_set_write_callback;  pa_stream_trigger;  pa_stream_unref; +pa_stream_update_sample_rate;  pa_stream_update_timing_info;  pa_stream_writable_size;  pa_stream_write; diff --git a/src/modules/module-suspend-on-idle.c b/src/modules/module-suspend-on-idle.c index 4746e2b7..4c260d76 100644 --- a/src/modules/module-suspend-on-idle.c +++ b/src/modules/module-suspend-on-idle.c @@ -66,8 +66,6 @@ struct userdata {          *source_output_unlink_slot,          *sink_input_move_slot,          *source_output_move_slot, -        *sink_input_move_post_slot, -        *source_output_move_post_slot,          *sink_input_state_changed_slot,          *source_output_state_changed_slot;  }; @@ -131,27 +129,27 @@ static void resume(struct device_info *d) {      }  } -static pa_hook_result_t sink_input_new_hook_cb(pa_core *c, pa_sink_input *s, struct userdata *u) { +static pa_hook_result_t sink_input_fixate_hook_cb(pa_core *c, pa_sink_input_new_data *data, struct userdata *u) {      struct device_info *d;      pa_assert(c); -    pa_sink_input_assert_ref(s); +    pa_assert(data);      pa_assert(u); -    if ((d = pa_hashmap_get(u->device_infos, s->sink))) +    if ((d = pa_hashmap_get(u->device_infos, data->sink)))          resume(d);      return PA_HOOK_OK;  } -static pa_hook_result_t source_output_new_hook_cb(pa_core *c, pa_source_output *s, struct userdata *u) { +static pa_hook_result_t source_output_fixate_hook_cb(pa_core *c, pa_source_output_new_data *data, struct userdata *u) {      struct device_info *d;      pa_assert(c); -    pa_source_output_assert_ref(s); +    pa_assert(data);      pa_assert(u); -    if ((d = pa_hashmap_get(u->device_infos, s->source))) +    if ((d = pa_hashmap_get(u->device_infos, data->source)))          resume(d);      return PA_HOOK_OK; @@ -185,56 +183,37 @@ static pa_hook_result_t source_output_unlink_hook_cb(pa_core *c, pa_source_outpu      return PA_HOOK_OK;  } -static pa_hook_result_t sink_input_move_hook_cb(pa_core *c, pa_sink_input *s, struct userdata *u) { -    pa_assert(c); -    pa_sink_input_assert_ref(s); -    pa_assert(u); - -    if (pa_sink_used_by(s->sink) <= 1) { -        struct device_info *d; -        if ((d = pa_hashmap_get(u->device_infos, s->sink))) -            restart(d); -    } - -    return PA_HOOK_OK; -} - -static pa_hook_result_t sink_input_move_post_hook_cb(pa_core *c, pa_sink_input *s, struct userdata *u) { +static pa_hook_result_t sink_input_move_hook_cb(pa_core *c, pa_sink_input_move_hook_data *data, struct userdata *u) {      struct device_info *d; -    pa_assert(c); -    pa_sink_input_assert_ref(s); -    pa_assert(u); - -    if ((d = pa_hashmap_get(u->device_infos, s->sink))) -        resume(d); - -    return PA_HOOK_OK; -} -static pa_hook_result_t source_output_move_hook_cb(pa_core *c, pa_source_output *s, struct userdata *u) {      pa_assert(c); -    pa_source_output_assert_ref(s); +    pa_assert(data);      pa_assert(u); -    if (pa_source_used_by(s->source) <= 1) { -        struct device_info *d; +    if ((d = pa_hashmap_get(u->device_infos, data->destination))) +        resume(d); -        if ((d = pa_hashmap_get(u->device_infos, s->source))) +    if (pa_sink_used_by(data->sink_input->sink) <= 1) +        if ((d = pa_hashmap_get(u->device_infos, data->sink_input->sink)))              restart(d); -    }      return PA_HOOK_OK;  } -static pa_hook_result_t source_output_move_post_hook_cb(pa_core *c, pa_source_output *s, struct userdata *u) { +static pa_hook_result_t source_output_move_hook_cb(pa_core *c, pa_source_output_move_hook_data *data, struct userdata *u) {      struct device_info *d; +      pa_assert(c); -    pa_source_output_assert_ref(s); +    pa_assert(data);      pa_assert(u); -    if ((d = pa_hashmap_get(u->device_infos, s->source))) +    if ((d = pa_hashmap_get(u->device_infos, data->destination)))          resume(d); +    if (pa_source_used_by(data->source_output->source) <= 1) +        if ((d = pa_hashmap_get(u->device_infos, data->source_output->source))) +            restart(d); +      return PA_HOOK_OK;  } @@ -395,18 +374,15 @@ int pa__init(pa_module*m) {      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);      u->source_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_STATE_CHANGED], (pa_hook_cb_t) device_state_changed_hook_cb, u); -    u->sink_input_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], (pa_hook_cb_t) sink_input_new_hook_cb, u); -    u->source_output_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], (pa_hook_cb_t) source_output_new_hook_cb, u); +    u->sink_input_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], (pa_hook_cb_t) sink_input_fixate_hook_cb, u); +    u->source_output_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE], (pa_hook_cb_t) source_output_fixate_hook_cb, u);      u->sink_input_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK_POST], (pa_hook_cb_t) sink_input_unlink_hook_cb, u);      u->source_output_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST], (pa_hook_cb_t) source_output_unlink_hook_cb, u);      u->sink_input_move_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE], (pa_hook_cb_t) sink_input_move_hook_cb, u);      u->source_output_move_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE], (pa_hook_cb_t) source_output_move_hook_cb, u); -    u->sink_input_move_post_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_POST], (pa_hook_cb_t) sink_input_move_post_hook_cb, u); -    u->source_output_move_post_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_POST], (pa_hook_cb_t) source_output_move_post_hook_cb, u);      u->sink_input_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], (pa_hook_cb_t) sink_input_state_changed_hook_cb, u);      u->source_output_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED], (pa_hook_cb_t) source_output_state_changed_hook_cb, u); -      pa_modargs_free(ma);      return 0; @@ -449,8 +425,6 @@ void pa__done(pa_module*m) {          pa_hook_slot_free(u->sink_input_unlink_slot);      if (u->sink_input_move_slot)          pa_hook_slot_free(u->sink_input_move_slot); -    if (u->sink_input_move_post_slot) -        pa_hook_slot_free(u->sink_input_move_post_slot);      if (u->sink_input_state_changed_slot)          pa_hook_slot_free(u->sink_input_state_changed_slot); @@ -460,8 +434,6 @@ void pa__done(pa_module*m) {          pa_hook_slot_free(u->source_output_unlink_slot);      if (u->source_output_move_slot)          pa_hook_slot_free(u->source_output_move_slot); -    if (u->source_output_move_post_slot) -        pa_hook_slot_free(u->source_output_move_post_slot);      if (u->source_output_state_changed_slot)          pa_hook_slot_free(u->source_output_state_changed_slot); diff --git a/src/modules/module-volume-restore.c b/src/modules/module-volume-restore.c index 422bc7f2..192a2a78 100644 --- a/src/modules/module-volume-restore.c +++ b/src/modules/module-volume-restore.c @@ -35,6 +35,7 @@  #include <pulse/xmalloc.h>  #include <pulse/volume.h> +#include <pulse/timeval.h>  #include <pulsecore/core-error.h>  #include <pulsecore/module.h> @@ -52,14 +53,20 @@ PA_MODULE_AUTHOR("Lennart Poettering");  PA_MODULE_DESCRIPTION("Automatically restore the volume and the devices of streams");  PA_MODULE_VERSION(PACKAGE_VERSION);  PA_MODULE_LOAD_ONCE(TRUE); -PA_MODULE_USAGE("table=<filename>"); +PA_MODULE_USAGE( +        "table=<filename> " +        "restore_device=<Restore the device for each stream?> " +        "restore_volume=<Restore the volume for each stream?>" +);  #define WHITESPACE "\n\r \t" -  #define DEFAULT_VOLUME_TABLE_FILE "volume-restore.table" +#define SAVE_INTERVAL 10  static const char* const valid_modargs[] = {      "table", +    "restore_device", +    "restore_volume",      NULL,  }; @@ -67,16 +74,20 @@ struct rule {      char* name;      pa_bool_t volume_is_set;      pa_cvolume volume; -    char *sink; -    char *source; +    char *sink, *source;  };  struct userdata { +    pa_core *core;      pa_hashmap *hashmap;      pa_subscription *subscription; -    pa_hook_slot *sink_input_hook_slot, *source_output_hook_slot; +    pa_hook_slot +        *sink_input_new_hook_slot, +        *sink_input_fixate_hook_slot, +        *source_output_new_hook_slot;      pa_bool_t modified;      char *table_file; +    pa_time_event *save_time_event;  };  static pa_cvolume* parse_volume(const char *s, pa_cvolume *v) { @@ -220,12 +231,17 @@ static int save_rules(struct userdata *u) {      void *state = NULL;      struct rule *rule; +    if (!u->modified) +        return 0; + +    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) { -        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;      } @@ -249,6 +265,8 @@ static int save_rules(struct userdata *u) {      }      ret = 0; +    u->modified = FALSE; +    pa_log_debug("Successfully saved rules...");  finish:      if (f) { @@ -289,6 +307,21 @@ static char* client_name(pa_client *c) {      return t;  } +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; + +    save_rules(u); +} +  static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {      struct userdata *u =  userdata;      pa_sink_input *si = NULL; @@ -371,14 +404,48 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3          pa_hashmap_put(u->hashmap, r->name, r);          u->modified = TRUE;      } + +    if (u->modified && !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); +    }  } -static pa_hook_result_t sink_input_hook_callback(pa_core *c, pa_sink_input_new_data *data, struct userdata *u) { +static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_new_data *data, struct userdata *u) {      struct rule *r;      char *name;      pa_assert(data); +    /* In the NEW hook we only adjust the device. Adjusting the volume +     * is left for the FIXATE hook */ + +    if (!data->client || !(name = client_name(data->client))) +        return PA_HOOK_OK; + +    if ((r = pa_hashmap_get(u->hashmap, name))) { +        if (!data->sink && r->sink) { +            if ((data->sink = pa_namereg_get(c, r->sink, PA_NAMEREG_SINK, 1))) +                pa_log_info("Restoring sink for <%s>", r->name); +        } +    } + +    pa_xfree(name); + +    return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *c, pa_sink_input_new_data *data, struct userdata *u) { +    struct rule *r; +    char *name; + +    pa_assert(data); + +    /* In the FIXATE hook we only adjust the volum. Adjusting the device +     * is left for the NEW hook */ +      if (!data->client || !(name = client_name(data->client)))          return PA_HOOK_OK; @@ -388,11 +455,6 @@ static pa_hook_result_t sink_input_hook_callback(pa_core *c, pa_sink_input_new_d              pa_log_info("Restoring volume for <%s>", r->name);              pa_sink_input_new_data_set_volume(data, &r->volume);          } - -        if (!data->sink && r->sink) { -            if ((data->sink = pa_namereg_get(c, r->sink, PA_NAMEREG_SINK, 1))) -                pa_log_info("Restoring sink for <%s>", r->name); -        }      }      pa_xfree(name); @@ -400,7 +462,7 @@ static pa_hook_result_t sink_input_hook_callback(pa_core *c, pa_sink_input_new_d      return PA_HOOK_OK;  } -static pa_hook_result_t source_output_hook_callback(pa_core *c, pa_source_output_new_data *data, struct userdata *u) { +static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_output_new_data *data, struct userdata *u) {      struct rule *r;      char *name; @@ -422,6 +484,7 @@ static pa_hook_result_t source_output_hook_callback(pa_core *c, pa_source_output  int pa__init(pa_module*m) {      pa_modargs *ma = NULL;      struct userdata *u; +    pa_bool_t restore_device = TRUE, restore_volume = TRUE;      pa_assert(m); @@ -431,20 +494,39 @@ 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->subscription = NULL;      u->table_file = pa_xstrdup(pa_modargs_get_value(ma, "table", NULL));      u->modified = FALSE; -    u->sink_input_hook_slot = u->source_output_hook_slot = NULL; +    u->subscription = NULL; +    u->sink_input_new_hook_slot = u->sink_input_fixate_hook_slot = u->source_output_new_hook_slot = NULL; +    u->save_time_event = NULL;      m->userdata = u; +    if (pa_modargs_get_value_boolean(ma, "restore_device", &restore_device) < 0 || +        pa_modargs_get_value_boolean(ma, "restore_volume", &restore_volume) < 0) { +        pa_log("restore_volume= and restore_device= expect boolean arguments"); +        goto fail; +    } + +    if (!(restore_device || restore_volume)) { +        pa_log("Both restrong the volume and restoring the device are disabled. There's no point in using this module at all then, failing."); +        goto fail; +    } +      if (load_rules(u) < 0)          goto fail;      u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, subscribe_callback, u); -    u->sink_input_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], (pa_hook_cb_t) sink_input_hook_callback, u); -    u->source_output_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], (pa_hook_cb_t) source_output_hook_callback, u); + +    if (restore_device) { +        u->sink_input_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], (pa_hook_cb_t) sink_input_new_hook_callback, u); +        u->source_output_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], (pa_hook_cb_t) source_output_new_hook_callback, u); +    } + +    if (restore_volume) +        u->sink_input_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], (pa_hook_cb_t) sink_input_fixate_hook_callback, u);      pa_modargs_free(ma);      return 0; @@ -478,19 +560,21 @@ void pa__done(pa_module*m) {      if (u->subscription)          pa_subscription_free(u->subscription); -    if (u->sink_input_hook_slot) -        pa_hook_slot_free(u->sink_input_hook_slot); -    if (u->source_output_hook_slot) -        pa_hook_slot_free(u->source_output_hook_slot); +    if (u->sink_input_new_hook_slot) +        pa_hook_slot_free(u->sink_input_new_hook_slot); +    if (u->sink_input_fixate_hook_slot) +        pa_hook_slot_free(u->sink_input_fixate_hook_slot); +    if (u->source_output_new_hook_slot) +        pa_hook_slot_free(u->source_output_new_hook_slot);      if (u->hashmap) { - -        if (u->modified) -            save_rules(u); - +        save_rules(u);          pa_hashmap_free(u->hashmap, free_func, NULL);      } +    if (u->save_time_event) +        u->core->mainloop->time_free(u->save_time_event); +      pa_xfree(u->table_file);      pa_xfree(u);  } diff --git a/src/pulse/context.c b/src/pulse/context.c index 805cd44e..bc960e21 100644 --- a/src/pulse/context.c +++ b/src/pulse/context.c @@ -86,6 +86,10 @@ static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = {      [PA_COMMAND_UNDERFLOW] = pa_command_overflow_or_underflow,      [PA_COMMAND_PLAYBACK_STREAM_KILLED] = pa_command_stream_killed,      [PA_COMMAND_RECORD_STREAM_KILLED] = pa_command_stream_killed, +    [PA_COMMAND_PLAYBACK_STREAM_MOVED] = pa_command_stream_moved, +    [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_SUBSCRIBE_EVENT] = pa_command_subscribe_event  }; @@ -396,7 +400,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) { +                c->is_local > 0) {                  /* Only enable SHM if both sides are owned by the same                   * user. This is a security measure because otherwise @@ -965,6 +969,9 @@ pa_operation* pa_context_set_default_source(pa_context *c, const char *name, pa_  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);      return c->is_local;  } diff --git a/src/pulse/def.h b/src/pulse/def.h index a7c475db..dabbc5eb 100644 --- a/src/pulse/def.h +++ b/src/pulse/def.h @@ -122,7 +122,7 @@ typedef enum pa_stream_flags {                                        * ahead can be corrected                                        * quickly, without the need to                                        * wait. */ -    PA_STREAM_AUTO_TIMING_UPDATE = 8 /**< If set timing update requests +    PA_STREAM_AUTO_TIMING_UPDATE = 8, /**< If set timing update requests                                         * are issued periodically                                         * automatically. Combined with                                         * PA_STREAM_INTERPOLATE_TIMING @@ -132,6 +132,83 @@ typedef enum pa_stream_flags {                                         * pa_stream_get_latency() at                                         * all times without a packet                                         * round trip.*/ +    PA_STREAM_NO_REMAP_CHANNELS = 16, /**< Don't remap channels by +                                       * their name, instead map them +                                       * simply by their +                                       * index. Implies +                                       * PA_STREAM_NO_REMIX_CHANNELS. Only +                                       * supported when the server is +                                       * at least PA 0.9.8. It is +                                       * ignored on older +                                       * servers.\since 0.9.8 */ +    PA_STREAM_NO_REMIX_CHANNELS = 32, /**< When remapping channels by +                                       * name, don't upmix or downmix +                                       * them to related +                                       * channels. Copy them into +                                       * matching channels of the +                                       * device 1:1. Only supported +                                       * when the server is at least +                                       * PA 0.9.8. It is ignored on +                                       * older servers. \since +                                       * 0.9.8 */ +    PA_STREAM_FIX_FORMAT = 64, /**< Use the sample format of the +                                * sink/device this stream is being +                                * connected to, and possibly ignore +                                * the format the sample spec contains +                                * -- but you still have to pass a +                                * valid value in it as a hint to +                                * PulseAudio what would suit your +                                * stream best. If this is used you +                                * should query the used sample format +                                * after creating the stream by using +                                * pa_stream_get_sample_spec(). Also, +                                * if you specified manual buffer +                                * metrics it is recommended to update +                                * them with +                                * pa_stream_set_buffer_attr() to +                                * compensate for the changed frame +                                * sizes. Only supported when the +                                * server is at least PA 0.9.8. It is +                                * ignored on older servers. \since +                                * 0.9.8 */ + +    PA_STREAM_FIX_RATE = 128, /**< Use the sample rate of the sink, +                               * and possibly ignore the rate the +                               * sample spec contains. Usage similar +                               * to PA_STREAM_FIX_FORMAT.Only +                               * supported when the server is at least +                               * PA 0.9.8. It is ignored on older +                               * servers. \since 0.9.8 */ + +    PA_STREAM_FIX_CHANNELS = 256, /**< Use the number of channels and +                               * the channel map of the sink, and +                               * possibly ignore the number of +                               * channels and the map the sample spec +                               * and the passed channel map +                               * contains. Usage similar to +                               * PA_STREAM_FIX_FORMAT. Only supported +                               * when the server is at least PA +                               * 0.9.8. It is ignored on older +                               * servers. \since 0.9.8 */ +    PA_STREAM_DONT_MOVE = 512, /**< Don't allow moving of this stream to +                              * another sink/device. Useful if you use +                              * any of the PA_STREAM_FIX_ flags and +                              * want to make sure that resampling +                              * never takes place -- which might +                              * happen if the stream is moved to +                              * another sink/source whith a different +                              * sample spec/channel map. Only +                              * supported when the server is at least +                              * PA 0.9.8. It is ignored on older +                              * servers. \since 0.9.8 */ +    PA_STREAM_VARIABLE_RATE = 1024, /**< Allow dynamic changing of the +                                     * sampling rate during playback +                                     * with +                                     * pa_stream_update_sample_rate(). Only +                                     * supported when the server is at +                                     * least PA 0.9.8. It is ignored +                                     * on older servers. \since +                                     * 0.9.8 */  } pa_stream_flags_t;  /** Playback and record buffer metrics */ diff --git a/src/pulse/internal.h b/src/pulse/internal.h index 95593adb..873f1363 100644 --- a/src/pulse/internal.h +++ b/src/pulse/internal.h @@ -103,6 +103,7 @@ struct pa_stream {      PA_LLIST_FIELDS(pa_stream);      char *name; +    pa_bool_t manual_buffer_attr;      pa_buffer_attr buffer_attr;      pa_sample_spec sample_spec;      pa_channel_map channel_map; @@ -110,12 +111,17 @@ struct pa_stream {      uint32_t channel;      uint32_t syncid;      int channel_valid; -    uint32_t device_index; +    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; +    uint32_t device_index; +    char *device_name; +    pa_bool_t suspended; +      pa_memchunk peek_memchunk;      void *peek_data;      pa_memblockq *record_memblockq; @@ -157,6 +163,10 @@ struct pa_stream {      void *underflow_userdata;      pa_stream_notify_cb_t latency_update_callback;      void *latency_update_userdata; +    pa_stream_notify_cb_t moved_callback; +    void *moved_userdata; +    pa_stream_notify_cb_t suspended_callback; +    void *suspended_userdata;  };  typedef void (*pa_operation_cb_t)(void); @@ -172,12 +182,16 @@ struct pa_operation {      pa_operation_state_t state;      void *userdata;      pa_operation_cb_t callback; + +    void *private; /* some operations might need this */  };  void pa_command_request(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);  void pa_command_stream_killed(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);  void pa_command_subscribe_event(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);  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);  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); diff --git a/src/pulse/operation.c b/src/pulse/operation.c index 8a782fd1..5d2da5b8 100644 --- a/src/pulse/operation.c +++ b/src/pulse/operation.c @@ -44,6 +44,7 @@ pa_operation *pa_operation_new(pa_context *c, pa_stream *s, pa_operation_cb_t cb      PA_REFCNT_INIT(o);      o->context = c;      o->stream = s; +    o->private = NULL;      o->state = PA_OPERATION_RUNNING;      o->callback = cb; @@ -63,7 +64,6 @@ pa_operation *pa_operation_ref(pa_operation *o) {      PA_REFCNT_INC(o);      return o;  } -  void pa_operation_unref(pa_operation *o) {      pa_assert(o);      pa_assert(PA_REFCNT_VALUE(o) >= 1); diff --git a/src/pulse/stream.c b/src/pulse/stream.c index 47906a5c..92420825 100644 --- a/src/pulse/stream.c +++ b/src/pulse/stream.c @@ -51,6 +51,7 @@ pa_stream *pa_stream_new(pa_context *c, const char *name, const pa_sample_spec *      pa_assert(PA_REFCNT_VALUE(c) >= 1);      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);      s = pa_xnew(pa_stream, 1); @@ -58,6 +59,8 @@ 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; @@ -70,6 +73,10 @@ pa_stream *pa_stream_new(pa_context *c, const char *name, const pa_sample_spec *      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); @@ -84,11 +91,17 @@ pa_stream *pa_stream_new(pa_context *c, const char *name, const pa_sample_spec *      s->channel = 0;      s->channel_valid = 0;      s->syncid = c->csyncid++; -    s->device_index = PA_INVALID_INDEX; +    s->stream_index = PA_INVALID_INDEX;      s->requested_bytes = 0;      s->state = PA_STREAM_UNCONNECTED; + +    s->manual_buffer_attr = FALSE;      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; @@ -139,6 +152,7 @@ static void stream_free(pa_stream *s) {          pa_memblockq_free(s->record_memblockq);      pa_xfree(s->name); +    pa_xfree(s->device_name);      pa_xfree(s);  } @@ -178,7 +192,7 @@ uint32_t pa_stream_get_index(pa_stream *s) {      PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE, PA_INVALID_INDEX); -    return s->device_index; +    return s->stream_index;  }  void pa_stream_set_state(pa_stream *s, pa_stream_state_t st) { @@ -262,6 +276,95 @@ finish:      pa_context_unref(c);  } +void pa_command_stream_moved(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { +    pa_context *c = userdata; +    pa_stream *s; +    uint32_t channel; +    const char *dn; +    int suspended; +    uint32_t di; + +    pa_assert(pd); +    pa_assert(command == PA_COMMAND_PLAYBACK_STREAM_MOVED || command == PA_COMMAND_RECORD_STREAM_MOVED); +    pa_assert(t); +    pa_assert(c); +    pa_assert(PA_REFCNT_VALUE(c) >= 1); + +    pa_context_ref(c); + +    if (c->version < 12) { +        pa_context_fail(c, PA_ERR_PROTOCOL); +        goto finish; +    } + +    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_context_fail(c, PA_ERR_PROTOCOL); +        goto finish; +    } + +    if (!dn || di == PA_INVALID_INDEX) { +        pa_context_fail(c, PA_ERR_PROTOCOL); +        goto finish; +    } + +    if (!(s = pa_dynarray_get(command == PA_COMMAND_PLAYBACK_STREAM_MOVED ? c->playback_streams : c->record_streams, channel))) +        goto finish; + +    pa_xfree(s->device_name); +    s->device_name = pa_xstrdup(dn); +    s->device_index = di; + +    s->suspended = suspended; + +    if (s->moved_callback) +        s->moved_callback(s, s->moved_userdata); + +finish: +    pa_context_unref(c); +} + +void pa_command_stream_suspended(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { +    pa_context *c = userdata; +    pa_stream *s; +    uint32_t channel; +    int suspended; + +    pa_assert(pd); +    pa_assert(command == PA_COMMAND_PLAYBACK_STREAM_SUSPENDED || command == PA_COMMAND_RECORD_STREAM_SUSPENDED); +    pa_assert(t); +    pa_assert(c); +    pa_assert(PA_REFCNT_VALUE(c) >= 1); + +    pa_context_ref(c); + +    if (c->version < 12) { +        pa_context_fail(c, PA_ERR_PROTOCOL); +        goto finish; +    } + +    if (pa_tagstruct_getu32(t, &channel) < 0 || +        pa_tagstruct_get_boolean(t, &suspended) < 0 || +        !pa_tagstruct_eof(t)) { +        pa_context_fail(c, PA_ERR_PROTOCOL); +        goto finish; +    } + +    if (!(s = pa_dynarray_get(command == PA_COMMAND_PLAYBACK_STREAM_SUSPENDED ? c->playback_streams : c->record_streams, channel))) +        goto finish; + +    s->suspended = suspended; + +    if (s->suspended_callback) +        s->suspended_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; @@ -412,6 +515,9 @@ 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) @@ -426,6 +532,17 @@ static void create_stream_complete(pa_stream *s) {      }  } +static void automatic_buffer_attr(pa_buffer_attr *attr, pa_sample_spec *ss) { +    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; +} +  void pa_create_stream_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {      pa_stream *s = userdata; @@ -445,13 +562,13 @@ 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->device_index) < 0) || +        ((s->direction != PA_STREAM_UPLOAD) && pa_tagstruct_getu32(t, &s->stream_index) < 0) ||          ((s->direction != PA_STREAM_RECORD) && pa_tagstruct_getu32(t, &s->requested_bytes) < 0)) {          pa_context_fail(s->context, PA_ERR_PROTOCOL);          goto finish;      } -    if (pa_context_get_server_protocol_version(s->context) >= 9) { +    if (s->context->version >= 9) {          if (s->direction == PA_STREAM_PLAYBACK) {              if (pa_tagstruct_getu32(t, &s->buffer_attr.maxlength) < 0 ||                  pa_tagstruct_getu32(t, &s->buffer_attr.tlength) < 0 || @@ -469,6 +586,58 @@ void pa_create_stream_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED          }      } +    if (s->context->version >= 12) { +        pa_sample_spec ss; +        pa_channel_map cm; +        const char *dn = NULL; +        int suspended; + +        if (pa_tagstruct_get_sample_spec(t, &ss) < 0 || +            pa_tagstruct_get_channel_map(t, &cm) < 0 || +            pa_tagstruct_getu32(t, &s->device_index) < 0 || +            pa_tagstruct_gets(t, &dn) < 0 || +            pa_tagstruct_get_boolean(t, &suspended) < 0) { +            pa_context_fail(s->context, PA_ERR_PROTOCOL); +            goto finish; +        } + +        if (!dn || s->device_index == PA_INVALID_INDEX || +            ss.channels != cm.channels || +            !pa_channel_map_valid(&cm) || +            !pa_sample_spec_valid(&ss) || +            (!(s->flags & PA_STREAM_FIX_FORMAT) && ss.format != s->sample_spec.format) || +            (!(s->flags & PA_STREAM_FIX_RATE) && ss.rate != s->sample_spec.rate) || +            (!(s->flags & PA_STREAM_FIX_CHANNELS) && !pa_channel_map_equal(&cm, &s->channel_map))) { +            pa_context_fail(s->context, PA_ERR_PROTOCOL); +            goto finish; +        } + +        pa_xfree(s->device_name); +        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->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; + +            s->buffer_attr_not_ready = TRUE; +        } + +        s->channel_map = cm; +        s->sample_spec = ss; +    } +      if (!pa_tagstruct_eof(t)) {          pa_context_fail(s->context, PA_ERR_PROTOCOL);          goto finish; @@ -491,15 +660,19 @@ void pa_create_stream_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED      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; -    } else -        create_stream_complete(s); +        s->timing_info_not_ready = TRUE; +    } + +    create_stream_complete(s);  finish:      pa_stream_unref(s); @@ -525,7 +698,13 @@ static int create_stream(                                                 PA_STREAM_START_CORKED|                                                 PA_STREAM_INTERPOLATE_TIMING|                                                 PA_STREAM_NOT_MONOTONOUS| -                                               PA_STREAM_AUTO_TIMING_UPDATE : 0))), PA_ERR_INVALID); +                                               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, !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); @@ -537,15 +716,17 @@ static int create_stream(      if (sync_stream)          s->syncid = sync_stream->syncid; -    if (attr) +    if (attr) {          s->buffer_attr = *attr; -    else { +        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;      }      if (!dev) @@ -585,6 +766,19 @@ static int create_stream(      } else          pa_tagstruct_putu32(t, s->buffer_attr.fragsize); +    if (s->context->version >= 12 && s->direction != PA_STREAM_UPLOAD) { +        pa_tagstruct_put( +                t, +                PA_TAG_BOOLEAN, flags & PA_STREAM_NO_REMAP_CHANNELS, +                PA_TAG_BOOLEAN, flags & PA_STREAM_NO_REMIX_CHANNELS, +                PA_TAG_BOOLEAN, flags & PA_STREAM_FIX_FORMAT, +                PA_TAG_BOOLEAN, flags & PA_STREAM_FIX_RATE, +                PA_TAG_BOOLEAN, flags & PA_STREAM_FIX_CHANNELS, +                PA_TAG_BOOLEAN, flags & PA_STREAM_DONT_MOVE, +                PA_TAG_BOOLEAN, flags & PA_STREAM_VARIABLE_RATE, +                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); @@ -921,8 +1115,10 @@ static void stream_get_timing_info_callback(pa_pdispatch *pd, uint32_t command,      }      /* First, let's complete the initialization, if necessary. */ -    if (o->stream->state == PA_STREAM_CREATING) +    if (o->stream->state == PA_STREAM_CREATING) { +        o->stream->timing_info_not_ready = FALSE;          create_stream_complete(o->stream); +    }      if (o->stream->latency_update_callback)          o->stream->latency_update_callback(o->stream, o->stream->latency_update_userdata); @@ -1084,6 +1280,22 @@ void pa_stream_set_latency_update_callback(pa_stream *s, pa_stream_notify_cb_t c      s->latency_update_userdata = userdata;  } +void pa_stream_set_moved_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) { +    pa_assert(s); +    pa_assert(PA_REFCNT_VALUE(s) >= 1); + +    s->moved_callback = cb; +    s->moved_userdata = userdata; +} + +void pa_stream_set_suspended_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) { +    pa_assert(s); +    pa_assert(PA_REFCNT_VALUE(s) >= 1); + +    s->suspended_callback = cb; +    s->suspended_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; @@ -1420,8 +1632,208 @@ 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, -        pa_context_get_server_protocol_version(s->context) >= 9, PA_ERR_NODATA); +    PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->context->version >= 9, PA_ERR_NODATA);      return &s->buffer_attr;  } + +static void stream_set_buffer_attr_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; + +    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) < 0) +            goto finish; + +        success = 0; +    } else { + +        if (o->stream->direction == PA_STREAM_PLAYBACK) { +            if (pa_tagstruct_getu32(t, &o->stream->buffer_attr.maxlength) < 0 || +                pa_tagstruct_getu32(t, &o->stream->buffer_attr.tlength) < 0 || +                pa_tagstruct_getu32(t, &o->stream->buffer_attr.prebuf) < 0 || +                pa_tagstruct_getu32(t, &o->stream->buffer_attr.minreq) < 0) { +                pa_context_fail(o->context, PA_ERR_PROTOCOL); +                goto finish; +            } +        } else if (o->stream->direction == PA_STREAM_RECORD) { +            if (pa_tagstruct_getu32(t, &o->stream->buffer_attr.maxlength) < 0 || +                pa_tagstruct_getu32(t, &o->stream->buffer_attr.fragsize) < 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->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) { +        pa_stream_success_cb_t cb = (pa_stream_success_cb_t) o->callback; +        cb(o->stream, success, o->userdata); +    } + +finish: +    pa_operation_done(o); +    pa_operation_unref(o); +} + + +pa_operation* pa_stream_set_buffer_attr(pa_stream *s, const pa_buffer_attr *attr, 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_assert(attr); + +    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 >= 12, 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_SET_RECORD_STREAM_BUFFER_ATTR : PA_COMMAND_SET_PLAYBACK_STREAM_BUFFER_ATTR, +            &tag); +    pa_tagstruct_putu32(t, s->channel); + +    pa_tagstruct_putu32(t, attr->maxlength); + +    if (s->direction == PA_STREAM_PLAYBACK) +        pa_tagstruct_put( +                t, +                PA_TAG_U32, attr->tlength, +                PA_TAG_U32, attr->prebuf, +                PA_TAG_U32, attr->minreq, +                PA_TAG_INVALID); +    else +        pa_tagstruct_putu32(t, attr->fragsize); + +    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); + +    return o; +} + +uint32_t pa_stream_get_device_index(pa_stream *s) { +    pa_assert(s); +    pa_assert(PA_REFCNT_VALUE(s) >= 1); + +    PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE, PA_INVALID_INDEX); +    PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE, PA_INVALID_INDEX); +    PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->context->version >= 12, PA_ERR_NOTSUPPORTED, PA_INVALID_INDEX); +    PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->device_index != PA_INVALID_INDEX, PA_ERR_BADSTATE, PA_INVALID_INDEX); + +    return s->device_index; +} + +const char *pa_stream_get_device_name(pa_stream *s) { +    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); +    PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->context->version >= 12, PA_ERR_NOTSUPPORTED); +    PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->device_name, PA_ERR_BADSTATE); + +    return s->device_name; +} + +int pa_stream_is_suspended(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); +    PA_CHECK_VALIDITY(s->context, s->context->version >= 12, PA_ERR_NOTSUPPORTED); + +    return s->suspended; +} + +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; + +    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) < 0) +            goto finish; + +        success = 0; +    } else { + +        if (!pa_tagstruct_eof(t)) { +            pa_context_fail(o->context, PA_ERR_PROTOCOL); +            goto finish; +        } +    } + +    o->stream->sample_spec.rate = PA_PTR_TO_UINT(o->private); +    pa_assert(pa_sample_spec_valid(&o->stream->sample_spec)); + +    if (o->callback) { +        pa_stream_success_cb_t cb = (pa_stream_success_cb_t) o->callback; +        cb(o->stream, success, o->userdata); +    } + +finish: +    pa_operation_done(o); +    pa_operation_unref(o); +} + + +pa_operation *pa_stream_update_sample_rate(pa_stream *s, uint32_t rate, 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, rate > 0 && rate <= PA_RATE_MAX, 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->flags & PA_STREAM_VARIABLE_RATE, PA_ERR_BADSTATE); +    PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->context->version >= 12, PA_ERR_NOTSUPPORTED); + +    o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata); +    o->private = PA_UINT_TO_PTR(rate); + +    t = pa_tagstruct_command( +            s->context, +            s->direction == PA_STREAM_RECORD ? PA_COMMAND_UPDATE_RECORD_STREAM_SAMPLE_RATE : PA_COMMAND_UPDATE_PLAYBACK_STREAM_SAMPLE_RATE, +            &tag); +    pa_tagstruct_putu32(t, s->channel); +    pa_tagstruct_putu32(t, rate); + +    pa_pstream_send_tagstruct(s->context->pstream, t); +    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; + +} diff --git a/src/pulse/stream.h b/src/pulse/stream.h index 8c6a90c1..85473227 100644 --- a/src/pulse/stream.h +++ b/src/pulse/stream.h @@ -295,9 +295,38 @@ pa_stream_state_t pa_stream_get_state(pa_stream *p);  /** Return the context this stream is attached to */  pa_context* pa_stream_get_context(pa_stream *p); -/** Return the device (sink input or source output) index this stream is connected to */ +/** Return the sink input resp. source output index this stream is + * identified in the server with. This is useful for usage with the + * introspection functions, such as pa_context_get_sink_input_info() + * resp. pa_context_get_source_output_info(). */  uint32_t pa_stream_get_index(pa_stream *s); +/** Return the index of the sink or source this stream is connected to + * in the server. This is useful for usage with the introspection + * functions, such as pa_context_get_sink_info_by_index() + * resp. pa_context_get_source_info_by_index(). Please note that + * streams may be moved between sinks/sources and thus it is + * recommended to use pa_stream_set_moved_callback() to be notified + * about this. This function will return with PA_ERR_NOTSUPPORTED when the + * server is older than 0.9.8. \since 0.9.8 */ +uint32_t pa_stream_get_device_index(pa_stream *s); + +/** Return the name of the sink or source this stream is connected to + * in the server. This is useful for usage with the introspection + * functions, such as pa_context_get_sink_info_by_name() + * resp. pa_context_get_source_info_by_name(). Please note that + * streams may be moved between sinks/sources and thus it is + * recommended to use pa_stream_set_moved_callback() to be notified + * about this. This function will return with PA_ERR_NOTSUPPORTED when the + * server is older than 0.9.8. \since 0.9.8 */ +const char *pa_stream_get_device_name(pa_stream *s); + +/** Return 1 if the sink or source this stream is connected to has + * been suspended. This will return 0 if not, and negative on + * error. This function will return with PA_ERR_NOTSUPPORTED when the + * server is older than 0.9.8. \since 0.9.8 */ +int pa_stream_is_suspended(pa_stream *s); +  /** Connect the stream to a sink */  int pa_stream_connect_playback(          pa_stream *s                  /**< The stream to connect to a sink */, @@ -346,10 +375,10 @@ int pa_stream_peek(   * calling pa_stream_peek(). \since 0.8 */  int pa_stream_drop(pa_stream *p); -/** Return the nember of bytes that may be written using pa_stream_write(), in bytes */ +/** 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(), in bytes \since 0.8 */ +/** Return the number of bytes that may be read using pa_stream_read() \since 0.8 */  size_t pa_stream_readable_size(pa_stream *p);  /** Drain a playback stream. Use this for notification when the buffer is empty */ @@ -378,9 +407,28 @@ void pa_stream_set_overflow_callback(pa_stream *p, pa_stream_notify_cb_t cb, voi  /** Set the callback function that is called when a buffer underflow happens. (Only for playback streams) \since 0.8 */  void pa_stream_set_underflow_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 */ +/** 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 */  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 + * moved to a different sink/source. Use pa_stream_get_device_name()or + * pa_stream_get_device_index() to query the new sink/source. This + * notification is only generated when the server is at least + * 0.9.8. \since 0.9.8 */ +void pa_stream_set_moved_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata); + +/** Set the callback function that is called whenever the sink/source + * this stream is connected to is suspended or resumed. Use + * pa_stream_is_suspended() to query the new suspend status. Please + * note that the suspend status might also change when the stream is + * moved between devices. Thus if you call this function you very + * likely want to call pa_stream_set_moved_callback, too. This + * notification is only generated when the server is at least + * 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 */  pa_operation* pa_stream_cork(pa_stream *s, int b, pa_stream_success_cb_t cb, void *userdata); @@ -447,6 +495,21 @@ const pa_channel_map* pa_stream_get_channel_map(pa_stream *s);   * PulseAudio 0.9. \since 0.9.0 */  const pa_buffer_attr* pa_stream_get_buffer_attr(pa_stream *s); +/** Change the buffer metrics of the stream during playback. The + * server might have chosen different buffer metrics then + * requested. The selected metrics may be queried with + * pa_stream_get_buffer_attr() as soon as the callback is called. Only + * valid after the stream has been connected successfully and if the + * server is at least PulseAudio 0.9.8. \since 0.9.8 */ +pa_operation *pa_stream_set_buffer_attr(pa_stream *s, const pa_buffer_attr *attr, pa_stream_success_cb_t cb, void *userdata); + +/* Change the stream sampling rate during playback. You need to pass + * PA_STREAM_VARIABLE_RATE in the flags parameter of + * pa_stream_connect() if you plan to use this function. Only valid + * after the stream has been connected successfully and if the server + * 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); +  PA_C_DECL_END  #endif diff --git a/src/pulsecore/cli-text.c b/src/pulsecore/cli-text.c index a77bcc2c..b64cafe2 100644 --- a/src/pulsecore/cli-text.c +++ b/src/pulsecore/cli-text.c @@ -236,7 +236,7 @@ char *pa_source_output_list_to_string(pa_core *c) {              "    index: %u\n"              "\tname: '%s'\n"              "\tdriver: <%s>\n" -            "\tflags: %s%s\n" +            "\tflags: %s%s%s%s%s%s%s\n"              "\tstate: %s\n"              "\tsource: <%u> '%s'\n"              "\tlatency: <%0.0f usec>\n" @@ -248,6 +248,11 @@ char *pa_source_output_list_to_string(pa_core *c) {              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_NO_REMAP ? "NO_REMAP " : "", +            o->flags & PA_SOURCE_OUTPUT_NO_REMIX ? "NO_REMIX " : "", +            o->flags & PA_SOURCE_OUTPUT_FIX_FORMAT ? "FIX_FORMAT " : "", +            o->flags & PA_SOURCE_OUTPUT_FIX_RATE ? "FIX_RATE " : "", +            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), @@ -289,7 +294,7 @@ char *pa_sink_input_list_to_string(pa_core *c) {              "    index: %u\n"              "\tname: <%s>\n"              "\tdriver: <%s>\n" -            "\tflags: %s%s\n" +            "\tflags: %s%s%s%s%s%s%s\n"              "\tstate: %s\n"              "\tsink: <%u> '%s'\n"              "\tvolume: <%s>\n" @@ -303,6 +308,11 @@ char *pa_sink_input_list_to_string(pa_core *c) {              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_NO_REMAP ? "NO_REMAP " : "", +            i->flags & PA_SINK_INPUT_NO_REMIX ? "NO_REMIX " : "", +            i->flags & PA_SINK_INPUT_FIX_FORMAT ? "FIX_FORMAT " : "", +            i->flags & PA_SINK_INPUT_FIX_RATE ? "FIX_RATE " : "", +            i->flags & PA_SINK_INPUT_FIX_CHANNELS ? "FIX_CHANNELS " : "",              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)), diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c index 9b420c94..cf018509 100644 --- a/src/pulsecore/core.c +++ b/src/pulsecore/core.c @@ -138,6 +138,7 @@ pa_core* pa_core_new(pa_mainloop_api *m, int shared) {      c->disallow_module_loading = FALSE;      c->realtime_scheduling = FALSE;      c->realtime_priority = 5; +    c->disable_remixing = FALSE;      for (j = 0; j < PA_CORE_HOOK_MAX; j++)          pa_hook_init(&c->hooks[j], c); diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h index 9aeb7888..ce45e300 100644 --- a/src/pulsecore/core.h +++ b/src/pulsecore/core.h @@ -54,6 +54,7 @@ typedef enum pa_core_hook {      PA_CORE_HOOK_SOURCE_STATE_CHANGED,      PA_CORE_HOOK_SOURCE_DESCRIPTION_CHANGED,      PA_CORE_HOOK_SINK_INPUT_NEW, +    PA_CORE_HOOK_SINK_INPUT_FIXATE,      PA_CORE_HOOK_SINK_INPUT_PUT,      PA_CORE_HOOK_SINK_INPUT_UNLINK,      PA_CORE_HOOK_SINK_INPUT_UNLINK_POST, @@ -62,6 +63,7 @@ typedef enum pa_core_hook {      PA_CORE_HOOK_SINK_INPUT_NAME_CHANGED,      PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED,      PA_CORE_HOOK_SOURCE_OUTPUT_NEW, +    PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE,      PA_CORE_HOOK_SOURCE_OUTPUT_PUT,      PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK,      PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST, @@ -118,6 +120,7 @@ struct pa_core {      pa_bool_t is_system_instance;      pa_bool_t realtime_scheduling;      int realtime_priority; +    pa_bool_t disable_remixing;      /* hooks */      pa_hook hooks[PA_CORE_HOOK_MAX]; diff --git a/src/pulsecore/pid.c b/src/pulsecore/pid.c index 55ff2088..f3c9faaa 100644 --- a/src/pulsecore/pid.c +++ b/src/pulsecore/pid.c @@ -42,6 +42,7 @@  #endif  #include <pulse/xmalloc.h> +#include <pulse/util.h>  #include <pulsecore/core-error.h>  #include <pulsecore/core-util.h> @@ -260,8 +261,8 @@ fail:   * exists and the PID therein too. Returns 0 on succcess, -1   * otherwise. If pid is non-NULL and a running daemon was found,   * return its PID therein */ -int pa_pid_file_check_running(pid_t *pid) { -    return pa_pid_file_kill(0, pid); +int pa_pid_file_check_running(pid_t *pid, const char *binary_name) { +    return pa_pid_file_kill(0, pid, binary_name);  }  #ifndef OS_IS_WIN32 @@ -269,12 +270,14 @@ int pa_pid_file_check_running(pid_t *pid) {  /* Kill a current running daemon. Return non-zero on success, -1   * otherwise. If successful *pid contains the PID of the daemon   * process. */ -int pa_pid_file_kill(int sig, pid_t *pid) { +int pa_pid_file_kill(int sig, pid_t *pid, const char *binary_name) {      int fd = -1;      char fn[PATH_MAX];      int ret = -1;      pid_t _pid; - +#ifdef __linux__ +    char *e = NULL; +#endif      if (!pid)          pid = &_pid; @@ -286,6 +289,23 @@ int pa_pid_file_kill(int sig, pid_t *pid) {      if ((*pid = read_pid(fn, fd)) == (pid_t) -1)          goto fail; +#ifdef __linux__ +    if (binary_name) { +        pa_snprintf(fn, sizeof(fn), "/proc/%lu/exe", (unsigned long) pid); + +        if ((e = pa_readlink(fn))) { +            char *f = pa_path_get_filename(e); +            if (strcmp(f, binary_name) +#if defined(__OPTIMIZE__) +                /* libtool likes to rename our binary names ... */ +                && !(pa_startswith(f, "lt-") && strcmp(f+3, binary_name) == 0) +#endif +            ) +                goto fail; +        } +    } +#endif +      ret = kill(*pid, sig);  fail: @@ -295,13 +315,17 @@ fail:          pa_close(fd);      } +#ifdef __linux__ +    pa_xfree(e); +#endif +      return ret;  }  #else /* OS_IS_WIN32 */ -int pa_pid_file_kill(int sig, pid_t *pid) { +int pa_pid_file_kill(int sig, pid_t *pid, const char *exe_name) {      return -1;  } diff --git a/src/pulsecore/pid.h b/src/pulsecore/pid.h index 0f25d1c8..1d6de7b5 100644 --- a/src/pulsecore/pid.h +++ b/src/pulsecore/pid.h @@ -26,7 +26,7 @@  int pa_pid_file_create(void);  int pa_pid_file_remove(void); -int pa_pid_file_check_running(pid_t *pid); -int pa_pid_file_kill(int sig, pid_t *pid); +int pa_pid_file_check_running(pid_t *pid, const char *binary_name); +int pa_pid_file_kill(int sig, pid_t *pid, const char *binary_name);  #endif diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c index 1d294746..48d5cd70 100644 --- a/src/pulsecore/protocol-native.c +++ b/src/pulsecore/protocol-native.c @@ -202,12 +202,16 @@ enum {  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 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 send_memblock(connection *c);  static void request_bytes(struct playback_stream*s);  static void source_output_kill_cb(pa_source_output *o);  static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk); +static void source_output_suspend_cb(pa_source_output *o, pa_bool_t suspend); +static void source_output_moved_cb(pa_source_output *o);  static pa_usec_t source_output_get_latency_cb(pa_source_output *o);  static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk); @@ -248,6 +252,8 @@ static void command_cork_record_stream(pa_pdispatch *pd, uint32_t command, uint3  static void command_flush_record_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);  static void command_move_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);  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 const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = {      [PA_COMMAND_ERROR] = NULL, @@ -323,7 +329,13 @@ static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = {      [PA_COMMAND_REMOVE_AUTOLOAD] = command_remove_autoload,      [PA_COMMAND_MOVE_SINK_INPUT] = command_move_stream, -    [PA_COMMAND_MOVE_SOURCE_OUTPUT] = command_move_stream +    [PA_COMMAND_MOVE_SOURCE_OUTPUT] = command_move_stream, + +    [PA_COMMAND_SET_PLAYBACK_STREAM_BUFFER_ATTR] = command_set_stream_buffer_attr, +    [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  };  /* structure management */ @@ -435,12 +447,12 @@ static int record_stream_process_msg(pa_msgobject *o, int code, void*userdata, i  static record_stream* record_stream_new(          connection *c,          pa_source *source, -        const pa_sample_spec *ss, -        const pa_channel_map *map, +        pa_sample_spec *ss, +        pa_channel_map *map,          const char *name,          uint32_t *maxlength,          uint32_t fragment_size, -        int corked) { +        pa_source_output_flags_t flags) {      record_stream *s;      pa_source_output *source_output; @@ -462,7 +474,7 @@ static record_stream* record_stream_new(      pa_source_output_new_data_set_sample_spec(&data, ss);      pa_source_output_new_data_set_channel_map(&data, map); -    if (!(source_output = pa_source_output_new(c->protocol->core, &data, corked ? PA_SOURCE_OUTPUT_START_CORKED : 0))) +    if (!(source_output = pa_source_output_new(c->protocol->core, &data, flags)))          return NULL;      s = pa_msgobject_new(record_stream); @@ -473,21 +485,30 @@ static record_stream* record_stream_new(      s->source_output->push = source_output_push_cb;      s->source_output->kill = source_output_kill_cb;      s->source_output->get_latency = source_output_get_latency_cb; +    s->source_output->moved = source_output_moved_cb; +    s->source_output->suspend = source_output_suspend_cb;      s->source_output->userdata = s;      s->memblockq = pa_memblockq_new(              0,              *maxlength,              0, -            base = pa_frame_size(ss), +            base = pa_frame_size(&s->source_output->sample_spec),              1,              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; -    *maxlength = pa_memblockq_get_maxlength(s->memblockq); + +    if (s->fragment_size > *maxlength) +        s->fragment_size = *maxlength; + +    *ss = s->source_output->sample_spec; +    *map = s->source_output->channel_map;      pa_idxset_put(c->record_streams, s, &s->index); @@ -602,8 +623,8 @@ static int playback_stream_process_msg(pa_msgobject *o, int code, void*userdata,  static playback_stream* playback_stream_new(          connection *c,          pa_sink *sink, -        const pa_sample_spec *ss, -        const pa_channel_map *map, +        pa_sample_spec *ss, +        pa_channel_map *map,          const char *name,          uint32_t *maxlength,          uint32_t *tlength, @@ -611,8 +632,8 @@ static playback_stream* playback_stream_new(          uint32_t *minreq,          pa_cvolume *volume,          uint32_t syncid, -        int corked, -        uint32_t *missing) { +        uint32_t *missing, +        pa_sink_input_flags_t flags) {      playback_stream *s, *ssync;      pa_sink_input *sink_input; @@ -656,7 +677,7 @@ static playback_stream* playback_stream_new(      data.client = c->client;      data.sync_base = ssync ? ssync->sink_input : NULL; -    if (!(sink_input = pa_sink_input_new(c->protocol->core, &data, corked ? PA_SINK_INPUT_START_CORKED : 0))) +    if (!(sink_input = pa_sink_input_new(c->protocol->core, &data, flags)))          return NULL;      s = pa_msgobject_new(playback_stream); @@ -671,17 +692,19 @@ static playback_stream* playback_stream_new(      s->sink_input->peek = sink_input_peek_cb;      s->sink_input->drop = sink_input_drop_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;      s->sink_input->userdata = s;      start_index = ssync ? pa_memblockq_get_read_index(ssync->memblockq) : 0; -    silence = pa_silence_memblock_new(c->protocol->core->mempool, ss, 0); +    silence = pa_silence_memblock_new(c->protocol->core->mempool, &s->sink_input->sample_spec, 0);      s->memblockq = pa_memblockq_new(              start_index,              *maxlength,              *tlength, -            pa_frame_size(ss), +            pa_frame_size(&s->sink_input->sample_spec),              *prebuf,              *minreq,              silence); @@ -694,6 +717,9 @@ static playback_stream* playback_stream_new(      *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; @@ -1022,6 +1048,7 @@ static void sink_input_drop_cb(pa_sink_input *i, size_t length) {  /*     pa_log("after_drop: %u %u", pa_memblockq_get_length(s->memblockq), pa_memblockq_is_readable(s->memblockq)); */  } +/* Called from main context */  static void sink_input_kill_cb(pa_sink_input *i) {      playback_stream *s; @@ -1033,6 +1060,42 @@ static void sink_input_kill_cb(pa_sink_input *i) {      playback_stream_unlink(s);  } +/* Called from main context */ +static void sink_input_suspend_cb(pa_sink_input *i, pa_bool_t suspend) { +    playback_stream *s; +    pa_tagstruct *t; + +    pa_sink_input_assert_ref(i); +    s = PLAYBACK_STREAM(i->userdata); +    playback_stream_assert_ref(s); + +    t = pa_tagstruct_new(NULL, 0); +    pa_tagstruct_putu32(t, PA_COMMAND_PLAYBACK_STREAM_SUSPENDED); +    pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ +    pa_tagstruct_putu32(t, s->index); +    pa_tagstruct_put_boolean(t, suspend); +    pa_pstream_send_tagstruct(s->connection->pstream, t); +} + +/* Called from main context */ +static void sink_input_moved_cb(pa_sink_input *i) { +    playback_stream *s; +    pa_tagstruct *t; + +    pa_sink_input_assert_ref(i); +    s = PLAYBACK_STREAM(i->userdata); +    playback_stream_assert_ref(s); + +    t = pa_tagstruct_new(NULL, 0); +    pa_tagstruct_putu32(t, PA_COMMAND_PLAYBACK_STREAM_MOVED); +    pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ +    pa_tagstruct_putu32(t, s->index); +    pa_tagstruct_putu32(t, i->sink->index); +    pa_tagstruct_puts(t, i->sink->name); +    pa_tagstruct_put_boolean(t, pa_sink_get_state(i->sink) == PA_SINK_SUSPENDED); +    pa_pstream_send_tagstruct(s->connection->pstream, t); +} +  /*** source_output callbacks ***/  /* Called from thread context */ @@ -1070,6 +1133,41 @@ static pa_usec_t source_output_get_latency_cb(pa_source_output *o) {      return pa_bytes_to_usec(pa_memblockq_get_length(s->memblockq), &o->sample_spec);  } +/* Called from main context */ +static void source_output_suspend_cb(pa_source_output *o, pa_bool_t suspend) { +    record_stream *s; +    pa_tagstruct *t; + +    pa_source_output_assert_ref(o); +    s = RECORD_STREAM(o->userdata); +    record_stream_assert_ref(s); + +    t = pa_tagstruct_new(NULL, 0); +    pa_tagstruct_putu32(t, PA_COMMAND_RECORD_STREAM_SUSPENDED); +    pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ +    pa_tagstruct_putu32(t, s->index); +    pa_tagstruct_put_boolean(t, suspend); +    pa_pstream_send_tagstruct(s->connection->pstream, t); +} + +/* Called from main context */ +static void source_output_moved_cb(pa_source_output *o) { +    record_stream *s; +    pa_tagstruct *t; + +    pa_source_output_assert_ref(o); +    s = RECORD_STREAM(o->userdata); +    record_stream_assert_ref(s); + +    t = pa_tagstruct_new(NULL, 0); +    pa_tagstruct_putu32(t, PA_COMMAND_RECORD_STREAM_MOVED); +    pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ +    pa_tagstruct_putu32(t, s->index); +    pa_tagstruct_putu32(t, o->source->index); +    pa_tagstruct_puts(t, o->source->name); +    pa_pstream_send_tagstruct(s->connection->pstream, t); +} +  /*** pdispatch callbacks ***/  static void protocol_error(connection *c) { @@ -1104,6 +1202,8 @@ static void command_create_playback_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GC      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_sink_input_flags_t flags = 0;      connection_assert_ref(c);      pa_assert(t); @@ -1122,9 +1222,27 @@ static void command_create_playback_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GC              PA_TAG_U32, &minreq,              PA_TAG_U32, &syncid,              PA_TAG_CVOLUME, &volume, -            PA_TAG_INVALID) < 0 || -        !pa_tagstruct_eof(t) || -        !name) { +            PA_TAG_INVALID) < 0 || !name) { +        protocol_error(c); +        return; +    } + +    if (c->version >= 12)  { +        /* Since 0.9.8 the user can ask for a couple of additional flags */ + +        if (pa_tagstruct_get_boolean(t, &no_remap) < 0 || +            pa_tagstruct_get_boolean(t, &no_remix) < 0 || +            pa_tagstruct_get_boolean(t, &fix_format) < 0 || +            pa_tagstruct_get_boolean(t, &fix_rate) < 0 || +            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); +            return; +        } +    } + +    if (!pa_tagstruct_eof(t)) {          protocol_error(c);          return;      } @@ -1136,8 +1254,8 @@ static void command_create_playback_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GC      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 && maxlength <= MAX_MEMBLOCKQ_LENGTH, tag, PA_ERR_INVALID); -    CHECK_VALIDITY(c->pstream, maxlength >= pa_frame_size(&ss), 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); @@ -1147,7 +1265,17 @@ static void command_create_playback_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GC          CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY);      } -    s = playback_stream_new(c, sink, &ss, &map, name, &maxlength, &tlength, &prebuf, &minreq, &volume, syncid, corked, &missing); +    flags = +        (corked ?  PA_SINK_INPUT_START_CORKED : 0) | +        (no_remap ?  PA_SINK_INPUT_NO_REMAP : 0) | +        (no_remix ?  PA_SINK_INPUT_NO_REMIX : 0) | +        (fix_format ?  PA_SINK_INPUT_FIX_FORMAT : 0) | +        (fix_rate ?  PA_SINK_INPUT_FIX_RATE : 0) | +        (fix_channels ?  PA_SINK_INPUT_FIX_CHANNELS : 0) | +        (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);      CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_INVALID);      reply = reply_new(tag); @@ -1159,7 +1287,7 @@ static void command_create_playback_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GC  /*     pa_log("initial request is %u", missing); */      if (c->version >= 9) { -        /* Since 0.9 we support sending the buffer metrics back to the client */ +        /* Since 0.9.0 we support sending the buffer metrics back to the client */          pa_tagstruct_putu32(reply, (uint32_t) maxlength);          pa_tagstruct_putu32(reply, (uint32_t) tlength); @@ -1167,6 +1295,20 @@ static void command_create_playback_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GC          pa_tagstruct_putu32(reply, (uint32_t) minreq);      } +    if (c->version >= 12) { +        /* Since 0.9.8 we support sending the chosen sample +         * spec/channel map/device/suspend status back to the +         * client */ + +        pa_tagstruct_put_sample_spec(reply, &ss); +        pa_tagstruct_put_channel_map(reply, &map); + +        pa_tagstruct_putu32(reply, s->sink_input->sink->index); +        pa_tagstruct_puts(reply, s->sink_input->sink->name); + +        pa_tagstruct_put_boolean(reply, pa_sink_get_state(s->sink_input->sink) == PA_SINK_SUSPENDED); +    } +      pa_pstream_send_tagstruct(c->pstream, reply);  } @@ -1239,6 +1381,8 @@ static void command_create_record_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_      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_source_output_flags_t flags = 0;      connection_assert_ref(c);      pa_assert(t); @@ -1250,18 +1394,48 @@ static void command_create_record_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_          pa_tagstruct_gets(t, &source_name) < 0 ||          pa_tagstruct_getu32(t, &maxlength) < 0 ||          pa_tagstruct_get_boolean(t, &corked) < 0 || -        pa_tagstruct_getu32(t, &fragment_size) < 0 || -        !pa_tagstruct_eof(t)) { +        pa_tagstruct_getu32(t, &fragment_size) < 0) { +        protocol_error(c); +        return; +    } + +    if (c->version >= 12)  { +        /* Since 0.9.8 the user can ask for a couple of additional flags */ + +        if (pa_tagstruct_get_boolean(t, &no_remap) < 0 || +            pa_tagstruct_get_boolean(t, &no_remix) < 0 || +            pa_tagstruct_get_boolean(t, &fix_format) < 0 || +            pa_tagstruct_get_boolean(t, &fix_rate) < 0 || +            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); +            return; +        } +    } + +    if (!pa_tagstruct_eof(t)) {          protocol_error(c);          return;      } +    flags = +        (corked ?  PA_SOURCE_OUTPUT_START_CORKED : 0) | +        (no_remap ?  PA_SOURCE_OUTPUT_NO_REMAP : 0) | +        (no_remix ?  PA_SOURCE_OUTPUT_NO_REMIX : 0) | +        (fix_format ?  PA_SOURCE_OUTPUT_FIX_FORMAT : 0) | +        (fix_rate ?  PA_SOURCE_OUTPUT_FIX_RATE : 0) | +        (fix_channels ?  PA_SOURCE_OUTPUT_FIX_CHANNELS : 0) | +        (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) { @@ -1272,7 +1446,7 @@ static void command_create_record_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_          CHECK_VALIDITY(c->pstream, source, tag, PA_ERR_NOENTITY);      } -    s = record_stream_new(c, source, &ss, &map, name, &maxlength, fragment_size, corked); +    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); @@ -1287,6 +1461,20 @@ static void command_create_record_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_          pa_tagstruct_putu32(reply, (uint32_t) s->fragment_size);      } +    if (c->version >= 12) { +        /* Since 0.9.8 we support sending the chosen sample +         * spec/channel map/device/suspend status back to the +         * client */ + +        pa_tagstruct_put_sample_spec(reply, &ss); +        pa_tagstruct_put_channel_map(reply, &map); + +        pa_tagstruct_putu32(reply, s->source_output->source->index); +        pa_tagstruct_puts(reply, s->source_output->source->name); + +        pa_tagstruct_put_boolean(reply, pa_source_get_state(s->source_output->source) == PA_SOURCE_SUSPENDED); +    } +      pa_pstream_send_tagstruct(c->pstream, reply);  } @@ -2291,6 +2479,134 @@ static void command_flush_record_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_U      pa_pstream_send_simple_ack(c->pstream, tag);  } +static void command_set_stream_buffer_attr(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { +    connection *c = CONNECTION(userdata); +    uint32_t idx; +    uint32_t maxlength, tlength, prebuf, minreq, fragsize; +    pa_tagstruct *reply; + +    connection_assert_ref(c); +    pa_assert(t); + +    if (pa_tagstruct_getu32(t, &idx) < 0) { +        protocol_error(c); +        return; +    } + +    CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + +    if (command == PA_COMMAND_SET_PLAYBACK_STREAM_BUFFER_ATTR) { +        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); + +        if (pa_tagstruct_get( +                    t, +                    PA_TAG_U32, &maxlength, +                    PA_TAG_U32, &tlength, +                    PA_TAG_U32, &prebuf, +                    PA_TAG_U32, &minreq, +                    PA_TAG_INVALID) < 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); + +        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); + +        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)); + +    } else { +        record_stream *s; +        size_t base; +        pa_assert(command == PA_COMMAND_SET_RECORD_STREAM_BUFFER_ATTR); + +        s = pa_idxset_get_by_index(c->record_streams, idx); +        CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + +        if (pa_tagstruct_get( +                    t, +                    PA_TAG_U32, &maxlength, +                    PA_TAG_U32, &fragsize, +                    PA_TAG_INVALID) < 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); + +        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); + +        reply = reply_new(tag); +        pa_tagstruct_putu32(reply, (uint32_t) pa_memblockq_get_maxlength(s->memblockq)); +        pa_tagstruct_putu32(reply, s->fragment_size); +    } + +    pa_pstream_send_tagstruct(c->pstream, reply); +} + +static void command_update_stream_sample_rate(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { +    connection *c = CONNECTION(userdata); +    uint32_t idx; +    uint32_t rate; + +    connection_assert_ref(c); +    pa_assert(t); + +    if (pa_tagstruct_getu32(t, &idx) < 0 || +        pa_tagstruct_getu32(t, &rate) < 0 || +        !pa_tagstruct_eof(t)) { +        protocol_error(c); +        return; +    } + +    CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); +    CHECK_VALIDITY(c->pstream, rate > 0 && rate <= PA_RATE_MAX, tag, PA_ERR_INVALID); + +    if (command == PA_COMMAND_UPDATE_PLAYBACK_STREAM_SAMPLE_RATE) { +        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_sink_input_set_rate(s->sink_input, rate); + +    } else { +        record_stream *s; +        pa_assert(command == PA_COMMAND_UPDATE_RECORD_STREAM_SAMPLE_RATE); + +        s = pa_idxset_get_by_index(c->record_streams, idx); +        CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + +        pa_source_output_set_rate(s->source_output, rate); +    } + +    pa_pstream_send_simple_ack(c->pstream, tag); +} +  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; @@ -2340,6 +2656,7 @@ static void command_set_stream_name(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t com      } else {          record_stream *s; +        pa_assert(command == PA_COMMAND_SET_RECORD_STREAM_NAME);          s = pa_idxset_get_by_index(c->record_streams, idx);          CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index 730f3e5c..ec0914ec 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -93,7 +93,7 @@ pa_sink_input* pa_sink_input_new(      pa_sink_input *i;      pa_resampler *resampler = NULL; -    char st[PA_SAMPLE_SPEC_SNPRINT_MAX]; +    char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];      pa_assert(core);      pa_assert(data); @@ -132,6 +132,24 @@ 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 (flags & PA_SINK_INPUT_FIX_FORMAT) +        data->sample_spec.format = data->sink->sample_spec.format; + +    if (flags & PA_SINK_INPUT_FIX_RATE) +        data->sample_spec.rate = data->sink->sample_spec.rate; + +    if (flags & PA_SINK_INPUT_FIX_CHANNELS) { +        data->sample_spec.channels = data->sink->sample_spec.channels; +        data->channel_map = data->sink->channel_map; +    } + +    pa_assert(pa_sample_spec_valid(&data->sample_spec)); +    pa_assert(pa_channel_map_valid(&data->channel_map)); + +    /* Due to the fixing of the sample spec the volume might not match anymore */ +    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; @@ -140,6 +158,9 @@ pa_sink_input* pa_sink_input_new(      pa_return_null_if_fail(data->resample_method < PA_RESAMPLER_MAX); +    if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], data) < 0) +        return NULL; +      if (pa_idxset_size(data->sink->inputs) >= PA_MAX_INPUTS_PER_SINK) {          pa_log_warn("Failed to create sink input: too many inputs per sink.");          return NULL; @@ -154,7 +175,9 @@ pa_sink_input* pa_sink_input_new(                        &data->sample_spec, &data->channel_map,                        &data->sink->sample_spec, &data->sink->channel_map,                        data->resample_method, -                      (flags & PA_SINK_INPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0))) { +                      ((flags & PA_SINK_INPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) | +                      ((flags & PA_SINK_INPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) | +                      (core->disable_remixing || (flags & PA_SINK_INPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0)))) {              pa_log_warn("Unsupported resampling operation.");              return NULL;          } @@ -199,6 +222,7 @@ pa_sink_input* pa_sink_input_new(      i->attach = NULL;      i->detach = NULL;      i->suspend = NULL; +    i->moved = NULL;      i->userdata = NULL;      i->thread_info.state = i->state; @@ -215,11 +239,12 @@ pa_sink_input* pa_sink_input_new(      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", +    pa_log_info("Created input %u \"%s\" on %s with sample spec %s and channel map %s",                  i->index,                  i->name,                  i->sink->name, -                pa_sample_spec_snprint(st, sizeof(st), &i->sample_spec)); +                pa_sample_spec_snprint(st, sizeof(st), &i->sample_spec), +                pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map));      /* Don't forget to call pa_sink_input_put! */ @@ -307,6 +332,7 @@ void pa_sink_input_unlink(pa_sink_input *i) {      i->attach = NULL;      i->detach = NULL;      i->suspend = NULL; +    i->moved = NULL;      if (linked) {          pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_REMOVE, i->index); @@ -709,6 +735,7 @@ int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, int immediately) {      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)); @@ -750,14 +777,18 @@ int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, int immediately) {                        &i->sample_spec, &i->channel_map,                        &dest->sample_spec, &dest->channel_map,                        i->resample_method, -                      (i->flags & PA_SINK_INPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0))) { +                      ((i->flags & PA_SINK_INPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) | +                      ((i->flags & PA_SINK_INPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) | +                      (i->core->disable_remixing || (i->flags & PA_SINK_INPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0)))) {              pa_log_warn("Unsupported resampling operation.");              return -1;          }      } else          new_resampler = NULL; -    pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE], i); +    hook_data.sink_input = i; +    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; @@ -870,6 +901,9 @@ int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, int immediately) {      pa_sink_update_status(origin);      pa_sink_update_status(dest); +    if (i->moved) +        i->moved(i); +      pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_POST], i);      pa_log_debug("Successfully moved sink input %i from %s to %s.", i->index, origin->name, dest->name); diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h index 3f8e2039..8975db9e 100644 --- a/src/pulsecore/sink-input.h +++ b/src/pulsecore/sink-input.h @@ -53,7 +53,12 @@ static inline pa_bool_t PA_SINK_INPUT_LINKED(pa_sink_input_state_t x) {  typedef enum pa_sink_input_flags {      PA_SINK_INPUT_VARIABLE_RATE = 1,      PA_SINK_INPUT_DONT_MOVE = 2, -    PA_SINK_INPUT_START_CORKED = 4 +    PA_SINK_INPUT_START_CORKED = 4, +    PA_SINK_INPUT_NO_REMAP = 8, +    PA_SINK_INPUT_NO_REMIX = 16, +    PA_SINK_INPUT_FIX_FORMAT = 32, +    PA_SINK_INPUT_FIX_RATE = 64, +    PA_SINK_INPUT_FIX_CHANNELS = 128  } pa_sink_input_flags_t;  struct pa_sink_input { @@ -107,7 +112,11 @@ struct pa_sink_input {      /* If non-NULL called whenever the the sink this input is attached       * to suspends or resumes. Called from main context */ -    void (*suspend) (pa_sink_input *i, int b);   /* may be NULL */ +    void (*suspend) (pa_sink_input *i, pa_bool_t b);   /* may be NULL */ + +    /* If non-NULL called whenever the the sink this input is attached +     * to changes. Called from main context */ +    void (*moved) (pa_sink_input *i);   /* may be NULL */      /* Supposed to unlink and destroy this stream. Called from main       * context. */ @@ -181,6 +190,11 @@ 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); diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c index dccb34cc..fcc91cb1 100644 --- a/src/pulsecore/sink.c +++ b/src/pulsecore/sink.c @@ -149,23 +149,16 @@ pa_sink* pa_sink_new(  static int sink_set_state(pa_sink *s, pa_sink_state_t state) {      int ret; +    pa_bool_t suspend_change;      pa_assert(s);      if (s->state == state)          return 0; -    if ((s->state == PA_SINK_SUSPENDED && PA_SINK_OPENED(state)) || -        (PA_SINK_OPENED(s->state) && state == PA_SINK_SUSPENDED)) { -        pa_sink_input *i; -        uint32_t idx; - -        /* We're suspending or resuming, tell everyone about it */ - -        for (i = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx)); i; i = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx))) -            if (i->suspend) -                i->suspend(i, state == PA_SINK_SUSPENDED); -    } +    suspend_change = +        (s->state == PA_SINK_SUSPENDED && PA_SINK_OPENED(state)) || +        (PA_SINK_OPENED(s->state) && state == PA_SINK_SUSPENDED);      if (s->set_state)          if ((ret = s->set_state(s, state)) < 0) @@ -176,8 +169,20 @@ static int sink_set_state(pa_sink *s, pa_sink_state_t state) {      s->state = state; +    if (suspend_change) { +        pa_sink_input *i; +        uint32_t idx; + +        /* We're suspending or resuming, tell everyone about it */ + +        for (i = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx)); i; i = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx))) +            if (i->suspend) +                i->suspend(i, state == PA_SINK_SUSPENDED); +    } +      if (state != PA_SINK_UNLINKED) /* if we enter UNLINKED state pa_sink_unlink() will fire the apropriate events */          pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED], s); +      return 0;  } diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c index 5c52419e..576ddcf2 100644 --- a/src/pulsecore/source-output.c +++ b/src/pulsecore/source-output.c @@ -71,7 +71,7 @@ pa_source_output* pa_source_output_new(      pa_source_output *o;      pa_resampler *resampler = NULL; -    char st[PA_SAMPLE_SPEC_SNPRINT_MAX]; +    char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];      pa_assert(core);      pa_assert(data); @@ -103,11 +103,28 @@ pa_source_output* pa_source_output_new(      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 (flags & PA_SOURCE_OUTPUT_FIX_FORMAT) +        data->sample_spec.format = data->source->sample_spec.format; + +    if (flags & PA_SOURCE_OUTPUT_FIX_RATE) +        data->sample_spec.rate = data->source->sample_spec.rate; + +    if (flags & PA_SOURCE_OUTPUT_FIX_CHANNELS) { +        data->sample_spec.channels = data->source->sample_spec.channels; +        data->channel_map = data->source->channel_map; +    } + +    pa_assert(pa_sample_spec_valid(&data->sample_spec)); +    pa_assert(pa_channel_map_valid(&data->channel_map)); +      if (data->resample_method == PA_RESAMPLER_INVALID)          data->resample_method = core->resample_method;      pa_return_null_if_fail(data->resample_method < PA_RESAMPLER_MAX); +    if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE], data) < 0) +        return NULL; +      if (pa_idxset_size(data->source->outputs) >= PA_MAX_OUTPUTS_PER_SOURCE) {          pa_log("Failed to create source output: too many outputs per source.");          return NULL; @@ -122,7 +139,9 @@ pa_source_output* pa_source_output_new(                        &data->source->sample_spec, &data->source->channel_map,                        &data->sample_spec, &data->channel_map,                        data->resample_method, -                      (flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0))) { +                      ((flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) | +                      ((flags & PA_SOURCE_OUTPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) | +                      (core->disable_remixing || (flags & PA_SOURCE_OUTPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0)))) {              pa_log_warn("Unsupported resampling operation.");              return NULL;          } @@ -153,6 +172,7 @@ pa_source_output* pa_source_output_new(      o->detach = NULL;      o->attach = NULL;      o->suspend = NULL; +    o->moved = NULL;      o->userdata = NULL;      o->thread_info.state = o->state; @@ -163,11 +183,12 @@ pa_source_output* pa_source_output_new(      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", +    pa_log_info("Created output %u \"%s\" on %s with sample spec %s and channel map %s",                  o->index,                  o->name,                  o->source->name, -                pa_sample_spec_snprint(st, sizeof(st), &o->sample_spec)); +                pa_sample_spec_snprint(st, sizeof(st), &o->sample_spec), +                pa_channel_map_snprint(cm, sizeof(cm), &o->channel_map));      /* Don't forget to call pa_source_output_put! */ @@ -229,6 +250,7 @@ void pa_source_output_unlink(pa_source_output*o) {      o->attach = NULL;      o->detach = NULL;      o->suspend = NULL; +    o->moved = NULL;      if (linked) {          pa_subscription_post(o->source->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_REMOVE, o->index); @@ -379,6 +401,7 @@ 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_source_output_move_hook_data hook_data;      pa_source_output_assert_ref(o);      pa_assert(PA_SOURCE_OUTPUT_LINKED(o->state)); @@ -415,13 +438,17 @@ int pa_source_output_move_to(pa_source_output *o, pa_source *dest) {                        &dest->sample_spec, &dest->channel_map,                        &o->sample_spec, &o->channel_map,                        o->resample_method, -                      (o->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0))) { +                      ((o->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) | +                      ((o->flags & PA_SOURCE_OUTPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) | +                      (o->core->disable_remixing || (o->flags & PA_SOURCE_OUTPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0)))) {              pa_log_warn("Unsupported resampling operation.");              return -1;          }      } -    pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE], o); +    hook_data.source_output = o; +    hook_data.destination = dest; +    pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE], &hook_data);      /* Okey, let's move it */      pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL); @@ -447,6 +474,9 @@ int pa_source_output_move_to(pa_source_output *o, pa_source *dest) {      pa_source_update_status(origin);      pa_source_update_status(dest); +    if (o->moved) +        o->moved(o); +      pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_POST], o);      pa_log_debug("Successfully moved source output %i from %s to %s.", o->index, origin->name, dest->name); diff --git a/src/pulsecore/source-output.h b/src/pulsecore/source-output.h index e38a1e5a..d6da8d00 100644 --- a/src/pulsecore/source-output.h +++ b/src/pulsecore/source-output.h @@ -49,7 +49,12 @@ static inline pa_bool_t PA_SOURCE_OUTPUT_LINKED(pa_source_output_state_t x) {  typedef enum pa_source_output_flags {      PA_SOURCE_OUTPUT_VARIABLE_RATE = 1,      PA_SOURCE_OUTPUT_DONT_MOVE = 2, -    PA_SOURCE_OUTPUT_START_CORKED = 4 +    PA_SOURCE_OUTPUT_START_CORKED = 4, +    PA_SOURCE_OUTPUT_NO_REMAP = 8, +    PA_SOURCE_OUTPUT_NO_REMIX = 16, +    PA_SOURCE_OUTPUT_FIX_FORMAT = 32, +    PA_SOURCE_OUTPUT_FIX_RATE = 64, +    PA_SOURCE_OUTPUT_FIX_CHANNELS = 128  } pa_source_output_flags_t;  struct pa_source_output { @@ -82,8 +87,12 @@ 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, int b);   /* may be NULL */ +    void (*suspend) (pa_source_output *o, pa_bool_t b);   /* may be NULL */      /* Supposed to unlink and destroy this stream. Called from main       * context. */ @@ -135,6 +144,11 @@ typedef struct pa_source_output_new_data {      pa_resample_method_t resample_method;  } pa_source_output_new_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); diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c index 9a6902ae..5fd65cef 100644 --- a/src/pulsecore/source.c +++ b/src/pulsecore/source.c @@ -126,23 +126,16 @@ pa_source* pa_source_new(  static int source_set_state(pa_source *s, pa_source_state_t state) {      int ret; +    pa_bool_t suspend_change;      pa_assert(s);      if (s->state == state)          return 0; -    if ((s->state == PA_SOURCE_SUSPENDED && PA_SOURCE_OPENED(state)) || -        (PA_SOURCE_OPENED(s->state) && state == PA_SOURCE_SUSPENDED)) { -        pa_source_output *o; -        uint32_t idx; - -        /* We're suspending or resuming, tell everyone about it */ - -        for (o = PA_SOURCE_OUTPUT(pa_idxset_first(s->outputs, &idx)); o; o = PA_SOURCE_OUTPUT(pa_idxset_next(s->outputs, &idx))) -            if (o->suspend) -                o->suspend(o, state == PA_SINK_SUSPENDED); -    } +    suspend_change = +        (s->state == PA_SOURCE_SUSPENDED && PA_SOURCE_OPENED(state)) || +        (PA_SOURCE_OPENED(s->state) && state == PA_SOURCE_SUSPENDED);      if (s->set_state)          if ((ret = s->set_state(s, state)) < 0) @@ -153,8 +146,20 @@ static int source_set_state(pa_source *s, pa_source_state_t state) {      s->state = state; +    if (suspend_change) { +        pa_source_output *o; +        uint32_t idx; + +        /* We're suspending or resuming, tell everyone about it */ + +        for (o = PA_SOURCE_OUTPUT(pa_idxset_first(s->outputs, &idx)); o; o = PA_SOURCE_OUTPUT(pa_idxset_next(s->outputs, &idx))) +            if (o->suspend) +                o->suspend(o, state == PA_SINK_SUSPENDED); +    } +      if (state != PA_SOURCE_UNLINKED) /* if we enter UNLINKED state pa_source_unlink() will fire the apropriate events */          pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_STATE_CHANGED], s); +      return 0;  } diff --git a/src/utils/pacat.c b/src/utils/pacat.c index 96b6adb7..68e308d8 100644 --- a/src/utils/pacat.c +++ b/src/utils/pacat.c @@ -40,7 +40,7 @@  #define TIME_EVENT_USEC 50000 -#if PA_API_VERSION < 9 +#if PA_API_VERSION < 10  #error Invalid PulseAudio API version  #endif @@ -69,6 +69,8 @@ static pa_sample_spec sample_spec = {  static pa_channel_map channel_map;  static int channel_map_set = 0; +static pa_stream_flags_t flags = 0; +  /* A shortcut for terminating the application */  static void quit(int ret) {      assert(mainloop_api); @@ -105,7 +107,8 @@ static void do_stream_write(size_t length) {  /* This is called whenever new data may be written to the stream */  static void stream_write_callback(pa_stream *s, size_t length, void *userdata) { -    assert(s && length); +    assert(s); +    assert(length > 0);      if (stdio_event)          mainloop_api->io_enable(stdio_event, PA_IO_EVENT_INPUT); @@ -119,7 +122,8 @@ static void stream_write_callback(pa_stream *s, size_t length, void *userdata) {  /* This is called whenever new data may is available */  static void stream_read_callback(pa_stream *s, size_t length, void *userdata) {      const void *data; -    assert(s && length); +    assert(s); +    assert(length > 0);      if (stdio_event)          mainloop_api->io_enable(stdio_event, PA_IO_EVENT_OUTPUT); @@ -130,7 +134,8 @@ static void stream_read_callback(pa_stream *s, size_t length, void *userdata) {          return;      } -    assert(data && length); +    assert(data); +    assert(length > 0);      if (buffer) {          fprintf(stderr, "Buffer overrun, dropping incoming data\n"); @@ -159,6 +164,7 @@ static void stream_state_callback(pa_stream *s, void *userdata) {          case PA_STREAM_READY:              if (verbose) {                  const pa_buffer_attr *a; +                char cmt[PA_CHANNEL_MAP_SNPRINT_MAX], sst[PA_SAMPLE_SPEC_SNPRINT_MAX];                  fprintf(stderr, "Stream successfully created.\n"); @@ -172,9 +178,16 @@ static void stream_state_callback(pa_stream *s, void *userdata) {                          assert(mode == RECORD);                          fprintf(stderr, "Buffer metrics: maxlength=%u, fragsize=%u\n", a->maxlength, a->fragsize);                      } -                  } +                fprintf(stderr, "Using sample spec '%s', channel map '%s'.\n", +                        pa_sample_spec_snprint(sst, sizeof(sst), pa_stream_get_sample_spec(s)), +                        pa_channel_map_snprint(cmt, sizeof(cmt), pa_stream_get_channel_map(s))); + +                fprintf(stderr, "Connected to device %s (%u, %ssuspended).\n", +                        pa_stream_get_device_name(s), +                        pa_stream_get_device_index(s), +                        pa_stream_is_suspended(s) ? "" : "not ");              }              break; @@ -186,6 +199,24 @@ static void stream_state_callback(pa_stream *s, void *userdata) {      }  } +static void stream_suspended_callback(pa_stream *s, void *userdata) { +    assert(s); + +    if (verbose) { +        if (pa_stream_is_suspended(s)) +            fprintf(stderr, "Stream device suspended.\n"); +        else +            fprintf(stderr, "Stream device resumed.\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 "); +} +  /* This is called whenever the context status changes */  static void context_state_callback(pa_context *c, void *userdata) {      assert(c); @@ -199,7 +230,8 @@ static void context_state_callback(pa_context *c, void *userdata) {          case PA_CONTEXT_READY: {              int r; -            assert(c && !stream); +            assert(c); +            assert(!stream);              if (verbose)                  fprintf(stderr, "Connection established.\n"); @@ -212,16 +244,18 @@ static void context_state_callback(pa_context *c, void *userdata) {              pa_stream_set_state_callback(stream, stream_state_callback, NULL);              pa_stream_set_write_callback(stream, stream_write_callback, NULL);              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);              if (mode == PLAYBACK) {                  pa_cvolume cv; -                if ((r = pa_stream_connect_playback(stream, device, NULL, 0, pa_cvolume_set(&cv, sample_spec.channels, volume), NULL)) < 0) { +                if ((r = pa_stream_connect_playback(stream, device, 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, 0)) < 0) { +                if ((r = pa_stream_connect_record(stream, device, NULL, flags)) < 0) {                      fprintf(stderr, "pa_stream_connect_record() failed: %s\n", pa_strerror(pa_context_errno(c)));                      goto fail;                  } @@ -280,7 +314,10 @@ static void stream_drain_complete(pa_stream*s, int success, void *userdata) {  static void stdin_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) {      size_t l, w = 0;      ssize_t r; -    assert(a == mainloop_api && e && stdio_event == e); + +    assert(a == mainloop_api); +    assert(e); +    assert(stdio_event == e);      if (buffer) {          mainloop_api->io_enable(stdio_event, PA_IO_EVENT_NULL); @@ -330,7 +367,10 @@ static void stdin_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_even  /* Some data may be written to STDOUT */  static void stdout_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) {      ssize_t r; -    assert(a == mainloop_api && e && stdio_event == e); + +    assert(a == mainloop_api); +    assert(e); +    assert(stdio_event == e);      if (!buffer) {          mainloop_api->io_enable(stdio_event, PA_IO_EVENT_NULL); @@ -429,7 +469,16 @@ static void help(const char *argv0) {             "                                        float32be, ulaw, alaw (defaults to s16ne)\n"             "      --channels=CHANNELS               The number of channels, 1 for mono, 2 for stereo\n"             "                                        (defaults to 2)\n" -           "      --channel-map=CHANNELMAP          Channel map to use instead of the default\n", +           "      --channel-map=CHANNELMAP          Channel map to use instead of the default\n" +           "      --fix-format                      Take the sample format from the sink the stream is\n" +           "                                        being connected to.\n" +           "      --fix-rate                        Take the sampling rate from the sink the stream is\n" +           "                                        being connected to.\n" +           "      --fix-channels                    Take the number of channels and the channel map\n" +           "                                        from the sink the stream is being connected to.\n" +           "      --no-remix                        Don't upmix or downmix channels.\n" +           "      --no-remap                        Map channels by index instead of name.\n" +           ,             argv0);  } @@ -441,6 +490,11 @@ enum {      ARG_SAMPLEFORMAT,      ARG_CHANNELS,      ARG_CHANNELMAP, +    ARG_FIX_FORMAT, +    ARG_FIX_RATE, +    ARG_FIX_CHANNELS, +    ARG_NO_REMAP, +    ARG_NO_REMIX  };  int main(int argc, char *argv[]) { @@ -464,6 +518,11 @@ int main(int argc, char *argv[]) {          {"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}      }; @@ -549,6 +608,26 @@ int main(int argc, char *argv[]) {                  channel_map_set = 1;                  break; +            case ARG_FIX_CHANNELS: +                flags |= PA_STREAM_FIX_CHANNELS; +                break; + +            case ARG_FIX_RATE: +                flags |= PA_STREAM_FIX_RATE; +                break; + +            case ARG_FIX_FORMAT: +                flags |= PA_STREAM_FIX_FORMAT; +                break; + +            case ARG_NO_REMIX: +                flags |= PA_STREAM_NO_REMIX_CHANNELS; +                break; + +            case ARG_NO_REMAP: +                flags |= PA_STREAM_NO_REMAP_CHANNELS; +                break; +              default:                  goto quit;          } diff --git a/src/utils/pacmd.c b/src/utils/pacmd.c index 9583385b..daa6a96e 100644 --- a/src/utils/pacmd.c +++ b/src/utils/pacmd.c @@ -50,7 +50,7 @@ int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char*argv[]) {      size_t ibuf_index, ibuf_length, obuf_index, obuf_length;      fd_set ifds, ofds; -    if (pa_pid_file_check_running(&pid) < 0) { +    if (pa_pid_file_check_running(&pid, "pulseaudio") < 0) {          pa_log("no PulseAudio daemon running");          goto fail;      } @@ -75,7 +75,7 @@ int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char*argv[]) {          if (r >= 0)              break; -        if (pa_pid_file_kill(SIGUSR2, NULL) < 0) { +        if (pa_pid_file_kill(SIGUSR2, NULL, "pulseaudio") < 0) {              pa_log("failed to kill PulseAudio daemon.");              goto fail;          }  | 
