summaryrefslogtreecommitdiff
path: root/src/pulsecore
diff options
context:
space:
mode:
Diffstat (limited to 'src/pulsecore')
-rw-r--r--src/pulsecore/atomic.h2
-rw-r--r--src/pulsecore/core-util.c50
-rw-r--r--src/pulsecore/core-util.h3
-rw-r--r--src/pulsecore/core.c77
-rw-r--r--src/pulsecore/core.h8
-rw-r--r--src/pulsecore/device-port.c50
-rw-r--r--src/pulsecore/device-port.h4
-rw-r--r--src/pulsecore/macro.h38
-rw-r--r--src/pulsecore/meson.build2
-rw-r--r--src/pulsecore/resampler.c4
-rw-r--r--src/pulsecore/resampler.h3
-rw-r--r--src/pulsecore/rtpoll.c63
-rw-r--r--src/pulsecore/rtpoll.h9
-rw-r--r--src/pulsecore/sink-input.c57
-rw-r--r--src/pulsecore/sink-input.h19
-rw-r--r--src/pulsecore/sink.c53
-rw-r--r--src/pulsecore/sink.h6
-rw-r--r--src/pulsecore/source-output.c57
-rw-r--r--src/pulsecore/source-output.h19
-rw-r--r--src/pulsecore/source.c52
-rw-r--r--src/pulsecore/source.h6
21 files changed, 468 insertions, 114 deletions
diff --git a/src/pulsecore/atomic.h b/src/pulsecore/atomic.h
index a82ca83..e5c1401 100644
--- a/src/pulsecore/atomic.h
+++ b/src/pulsecore/atomic.h
@@ -117,7 +117,7 @@ static inline void* pa_atomic_ptr_load(const pa_atomic_ptr_t *a) {
}
static inline void pa_atomic_ptr_store(pa_atomic_ptr_t *a, void* p) {
- __atomic_store_n(&a->value, p, __ATOMIC_SEQ_CST);
+ __atomic_store_n(&a->value, (unsigned long) p, __ATOMIC_SEQ_CST);
}
#else
diff --git a/src/pulsecore/core-util.c b/src/pulsecore/core-util.c
index 270acd8..601b1d1 100644
--- a/src/pulsecore/core-util.c
+++ b/src/pulsecore/core-util.c
@@ -784,12 +784,16 @@ void pa_reset_priority(void) {
#endif
}
+/* Check whenever any substring in v matches the provided regex. */
int pa_match(const char *expr, const char *v) {
#if defined(HAVE_REGEX_H) || defined(HAVE_PCREPOSIX_H)
int k;
regex_t re;
int r;
+ pa_assert(expr);
+ pa_assert(v);
+
if (regcomp(&re, expr, REG_NOSUB|REG_EXTENDED) != 0) {
errno = EINVAL;
return -1;
@@ -814,6 +818,22 @@ int pa_match(const char *expr, const char *v) {
#endif
}
+/* Check whenever the provided regex pattern is valid. */
+bool pa_is_regex_valid(const char *expr) {
+#if defined(HAVE_REGEX_H) || defined(HAVE_PCREPOSIX_H)
+ regex_t re;
+
+ if (expr == NULL || regcomp(&re, expr, REG_NOSUB|REG_EXTENDED) != 0) {
+ return false;
+ }
+
+ regfree(&re);
+ return true;
+#else
+ return false;
+#endif
+}
+
/* Try to parse a boolean string value.*/
int pa_parse_boolean(const char *v) {
pa_assert(v);
@@ -859,7 +879,7 @@ int pa_parse_volume(const char *v, pa_volume_t *volume) {
len = strlen(v);
- if (len >= 64)
+ if (len <= 0 || len >= 64)
return -1;
memcpy(str, v, len + 1);
@@ -1428,7 +1448,7 @@ static int check_ours(const char *p) {
return -errno;
#ifdef HAVE_GETUID
- if (st.st_uid != getuid())
+ if (st.st_uid != getuid() && st.st_uid != 0)
return -EACCES;
#endif
@@ -2873,6 +2893,32 @@ bool pa_str_in_list_spaces(const char *haystack, const char *needle) {
return false;
}
+char* pa_str_strip_suffix(const char *str, const char *suffix) {
+ size_t str_l, suf_l, prefix;
+ char *ret;
+
+ pa_assert(str);
+ pa_assert(suffix);
+
+ str_l = strlen(str);
+ suf_l = strlen(suffix);
+
+ if (str_l < suf_l)
+ return NULL;
+
+ prefix = str_l - suf_l;
+
+ if (!pa_streq(&str[prefix], suffix))
+ return NULL;
+
+ ret = pa_xmalloc(prefix + 1);
+
+ strncpy(ret, str, prefix);
+ ret[prefix] = '\0';
+
+ return ret;
+}
+
char *pa_get_user_name_malloc(void) {
ssize_t k;
char *u;
diff --git a/src/pulsecore/core-util.h b/src/pulsecore/core-util.h
index cfb9eb2..9440af9 100644
--- a/src/pulsecore/core-util.h
+++ b/src/pulsecore/core-util.h
@@ -158,6 +158,7 @@ size_t pa_vsnprintf(char *str, size_t size, const char *format, va_list ap);
char *pa_truncate_utf8(char *c, size_t l);
int pa_match(const char *expr, const char *v);
+bool pa_is_regex_valid(const char *expr);
char *pa_getcwd(void);
char *pa_make_path_absolute(const char *p);
@@ -231,6 +232,8 @@ static inline bool pa_safe_streq(const char *a, const char *b) {
bool pa_str_in_list_spaces(const char *needle, const char *haystack);
bool pa_str_in_list(const char *haystack, const char *delimiters, const char *needle);
+char* pa_str_strip_suffix(const char *str, const char *suffix);
+
char *pa_get_host_name_malloc(void);
char *pa_get_user_name_malloc(void);
diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c
index e5bb2e4..335f802 100644
--- a/src/pulsecore/core.c
+++ b/src/pulsecore/core.c
@@ -144,7 +144,8 @@ pa_core* pa_core_new(pa_mainloop_api *m, bool shared, bool enable_memfd, size_t
c->realtime_priority = 5;
c->disable_remixing = false;
c->remixing_use_all_sink_channels = true;
- c->disable_lfe_remixing = true;
+ c->remixing_produce_lfe = false;
+ c->remixing_consume_lfe = false;
c->lfe_crossover_freq = 0;
c->deferred_volume = true;
c->resample_method = PA_RESAMPLER_SPEEX_FLOAT_BASE + 1;
@@ -349,6 +350,10 @@ void pa_core_update_default_sink(pa_core *core) {
pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_SERVER | PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX);
pa_hook_fire(&core->hooks[PA_CORE_HOOK_DEFAULT_SINK_CHANGED], core->default_sink);
+
+ /* try to move the streams from old_default_sink to the new default_sink conditionally */
+ if (old_default_sink)
+ pa_sink_move_streams_to_default_sink(core, old_default_sink, true);
}
/* a < b -> return -1
@@ -430,6 +435,10 @@ void pa_core_update_default_source(pa_core *core) {
old_default_source ? old_default_source->name : "(unset)", best ? best->name : "(unset)");
pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_SERVER | PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX);
pa_hook_fire(&core->hooks[PA_CORE_HOOK_DEFAULT_SOURCE_CHANGED], core->default_source);
+
+ /* try to move the streams from old_default_source to the new default_source conditionally */
+ if (old_default_source)
+ pa_source_move_streams_to_default_source(core, old_default_source, true);
}
void pa_core_set_exit_idle_time(pa_core *core, int time) {
@@ -519,6 +528,72 @@ void pa_core_rttime_restart(pa_core *c, pa_time_event *e, pa_usec_t usec) {
c->mainloop->time_restart(e, pa_timeval_rtstore(&tv, usec, true));
}
+void pa_core_move_streams_to_newly_available_preferred_sink(pa_core *c, pa_sink *s) {
+ pa_sink_input *si;
+ uint32_t idx;
+
+ pa_assert(c);
+ pa_assert(s);
+
+ PA_IDXSET_FOREACH(si, c->sink_inputs, idx) {
+ if (si->sink == s)
+ continue;
+
+ if (!si->sink)
+ continue;
+
+ /* Skip this sink input if it is connecting a filter sink to
+ * the master */
+ if (si->origin_sink)
+ continue;
+
+ /* It might happen that a stream and a sink are set up at the
+ same time, in which case we want to make sure we don't
+ interfere with that */
+ if (!PA_SINK_INPUT_IS_LINKED(si->state))
+ continue;
+
+ if (pa_safe_streq(si->preferred_sink, s->name))
+ pa_sink_input_move_to(si, s, false);
+ }
+
+}
+
+void pa_core_move_streams_to_newly_available_preferred_source(pa_core *c, pa_source *s) {
+ pa_source_output *so;
+ uint32_t idx;
+
+ pa_assert(c);
+ pa_assert(s);
+
+ PA_IDXSET_FOREACH(so, c->source_outputs, idx) {
+ if (so->source == s)
+ continue;
+
+ if (so->direct_on_input)
+ continue;
+
+ if (!so->source)
+ continue;
+
+ /* Skip this source output if it is connecting a filter source to
+ * the master */
+ if (so->destination_source)
+ continue;
+
+ /* It might happen that a stream and a source are set up at the
+ same time, in which case we want to make sure we don't
+ interfere with that */
+ if (!PA_SOURCE_OUTPUT_IS_LINKED(so->state))
+ continue;
+
+ if (pa_safe_streq(so->preferred_source, s->name))
+ pa_source_output_move_to(so, s, false);
+ }
+
+}
+
+
/* Helper macro to reduce repetition in pa_suspend_cause_to_string().
* Parameters:
* char *p: the current position in the write buffer
diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h
index d03897c..57924b9 100644
--- a/src/pulsecore/core.h
+++ b/src/pulsecore/core.h
@@ -213,6 +213,7 @@ struct pa_core {
int exit_idle_time, scache_idle_time;
bool flat_volumes:1;
+ bool rescue_streams:1;
bool disallow_module_loading:1;
bool disallow_exit:1;
bool running_as_daemon:1;
@@ -220,7 +221,8 @@ struct pa_core {
bool avoid_resampling:1;
bool disable_remixing:1;
bool remixing_use_all_sink_channels:1;
- bool disable_lfe_remixing:1;
+ bool remixing_produce_lfe:1;
+ bool remixing_consume_lfe:1;
bool deferred_volume:1;
pa_resample_method_t resample_method;
@@ -278,4 +280,8 @@ static const size_t PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE =
* provided buffer. The same buffer is the return value of this function. */
const char *pa_suspend_cause_to_string(pa_suspend_cause_t cause, char buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE]);
+void pa_core_move_streams_to_newly_available_preferred_sink(pa_core *c, pa_sink *s);
+
+void pa_core_move_streams_to_newly_available_preferred_source(pa_core *c, pa_source *s);
+
#endif
diff --git a/src/pulsecore/device-port.c b/src/pulsecore/device-port.c
index fb12772..b104c49 100644
--- a/src/pulsecore/device-port.c
+++ b/src/pulsecore/device-port.c
@@ -100,6 +100,30 @@ void pa_device_port_set_available(pa_device_port *p, pa_available_t status) {
else
pa_core_update_default_source(p->core);
+ if (p->direction == PA_DIRECTION_OUTPUT) {
+ pa_sink *sink;
+
+ sink = pa_device_port_get_sink(p);
+ if (sink && p == sink->active_port) {
+ if (sink->active_port->available == PA_AVAILABLE_NO) {
+ if (p->core->rescue_streams)
+ pa_sink_move_streams_to_default_sink(p->core, sink, false);
+ } else
+ pa_core_move_streams_to_newly_available_preferred_sink(p->core, sink);
+ }
+ } else {
+ pa_source *source;
+
+ source = pa_device_port_get_source(p);
+ if (source && p == source->active_port) {
+ if (source->active_port->available == PA_AVAILABLE_NO) {
+ if (p->core->rescue_streams)
+ pa_source_move_streams_to_default_source(p->core, source, false);
+ } else
+ pa_core_move_streams_to_newly_available_preferred_source(p->core, source);
+ }
+ }
+
pa_subscription_post(p->core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_CHANGE, p->card->index);
pa_hook_fire(&p->core->hooks[PA_CORE_HOOK_PORT_AVAILABLE_CHANGED], p);
}
@@ -224,3 +248,29 @@ pa_device_port *pa_device_port_find_best(pa_hashmap *ports)
return best;
}
+
+pa_sink *pa_device_port_get_sink(pa_device_port *p) {
+ pa_sink *rs = NULL;
+ pa_sink *sink;
+ uint32_t state;
+
+ PA_IDXSET_FOREACH(sink, p->card->sinks, state)
+ if (p == pa_hashmap_get(sink->ports, p->name)) {
+ rs = sink;
+ break;
+ }
+ return rs;
+}
+
+pa_source *pa_device_port_get_source(pa_device_port *p) {
+ pa_source *rs = NULL;
+ pa_source *source;
+ uint32_t state;
+
+ PA_IDXSET_FOREACH(source, p->card->sources, state)
+ if (p == pa_hashmap_get(source->ports, p->name)) {
+ rs = source;
+ break;
+ }
+ return rs;
+}
diff --git a/src/pulsecore/device-port.h b/src/pulsecore/device-port.h
index fbdce1a..41198f6 100644
--- a/src/pulsecore/device-port.h
+++ b/src/pulsecore/device-port.h
@@ -87,4 +87,8 @@ void pa_device_port_set_preferred_profile(pa_device_port *p, const char *new_pp)
pa_device_port *pa_device_port_find_best(pa_hashmap *ports);
+pa_sink *pa_device_port_get_sink(pa_device_port *p);
+
+pa_source *pa_device_port_get_source(pa_device_port *p);
+
#endif
diff --git a/src/pulsecore/macro.h b/src/pulsecore/macro.h
index bb15b7f..ec8d31c 100644
--- a/src/pulsecore/macro.h
+++ b/src/pulsecore/macro.h
@@ -34,16 +34,6 @@
#error "Please include config.h before including this file!"
#endif
-#ifndef PA_LIKELY
-#ifdef __GNUC__
-#define PA_LIKELY(x) (__builtin_expect(!!(x),1))
-#define PA_UNLIKELY(x) (__builtin_expect(!!(x),0))
-#else
-#define PA_LIKELY(x) (x)
-#define PA_UNLIKELY(x) (x)
-#endif
-#endif
-
/* Rounds down */
static inline void* PA_ALIGN_PTR(const void *p) {
return (void*) (((size_t) p) & ~(sizeof(void*) - 1));
@@ -100,34 +90,6 @@ static inline size_t PA_ALIGN(size_t l) {
#endif
#ifdef __GNUC__
-#define PA_CLAMP(x, low, high) \
- __extension__ ({ \
- typeof(x) _x = (x); \
- typeof(low) _low = (low); \
- typeof(high) _high = (high); \
- ((_x > _high) ? _high : ((_x < _low) ? _low : _x)); \
- })
-#else
-#define PA_CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
-#endif
-
-#ifdef __GNUC__
-#define PA_CLAMP_UNLIKELY(x, low, high) \
- __extension__ ({ \
- typeof(x) _x = (x); \
- typeof(low) _low = (low); \
- typeof(high) _high = (high); \
- (PA_UNLIKELY(_x > _high) ? _high : (PA_UNLIKELY(_x < _low) ? _low : _x)); \
- })
-#else
-#define PA_CLAMP_UNLIKELY(x, low, high) (PA_UNLIKELY((x) > (high)) ? (high) : (PA_UNLIKELY((x) < (low)) ? (low) : (x)))
-#endif
-
-/* We don't define a PA_CLAMP_LIKELY here, because it doesn't really
- * make sense: we cannot know if it is more likely that the values is
- * lower or greater than the boundaries.*/
-
-#ifdef __GNUC__
#define PA_ROUND_UP(a, b) \
__extension__ ({ \
typeof(a) _a = (a); \
diff --git a/src/pulsecore/meson.build b/src/pulsecore/meson.build
index 19f6b9e..0bd596c 100644
--- a/src/pulsecore/meson.build
+++ b/src/pulsecore/meson.build
@@ -198,7 +198,7 @@ libpulsecore = shared_library('pulsecore-' + pa_version_major_minor,
install_rpath : privlibdir,
install_dir : privlibdir,
link_with : libpulsecore_simd_lib,
- dependencies : [libm_dep, libpulsecommon_dep, libpulse_dep, ltdl_dep, shm_dep, sndfile_dep, database_dep, dbus_dep, libatomic_ops_dep, orc_dep, samplerate_dep, soxr_dep, speex_dep, x11_dep],
+ dependencies : [libm_dep, libpulsecommon_dep, libpulse_dep, ltdl_dep, shm_dep, sndfile_dep, database_dep, dbus_dep, libatomic_ops_dep, orc_dep, samplerate_dep, soxr_dep, speex_dep, x11_dep, libintl_dep],
implicit_include_directories : false)
libpulsecore_dep = declare_dependency(link_with: libpulsecore)
diff --git a/src/pulsecore/resampler.c b/src/pulsecore/resampler.c
index ff9795e..58463f1 100644
--- a/src/pulsecore/resampler.c
+++ b/src/pulsecore/resampler.c
@@ -1099,7 +1099,7 @@ static void setup_remap(const pa_resampler *r, pa_remap_t *m, bool *lfe_remixed)
* right input channel. Something is really wrong in this
* case anyway. */
- } else if (on_lfe(b) && !(r->flags & PA_RESAMPLER_NO_LFE)) {
+ } else if (on_lfe(b) && (r->flags & PA_RESAMPLER_PRODUCE_LFE)) {
/* We are not connected and an LFE. Let's average all
* channels for LFE. */
@@ -1150,7 +1150,7 @@ static void setup_remap(const pa_resampler *r, pa_remap_t *m, bool *lfe_remixed)
m->map_table_f[oc][ic] = (1.f/9.f) / (float) ic_unconnected_center;
ic_unconnected_center_mixed_in = true;
- } else if (on_lfe(a) && !(r->flags & PA_RESAMPLER_NO_LFE))
+ } else if (on_lfe(a) && (r->flags & PA_RESAMPLER_CONSUME_LFE))
m->map_table_f[oc][ic] = .375f / (float) ic_unconnected_lfe;
}
}
diff --git a/src/pulsecore/resampler.h b/src/pulsecore/resampler.h
index 5d3171f..5a264b3 100644
--- a/src/pulsecore/resampler.h
+++ b/src/pulsecore/resampler.h
@@ -68,8 +68,9 @@ typedef enum pa_resample_flags {
PA_RESAMPLER_VARIABLE_RATE = 0x0001U,
PA_RESAMPLER_NO_REMAP = 0x0002U, /* implies NO_REMIX */
PA_RESAMPLER_NO_REMIX = 0x0004U,
- PA_RESAMPLER_NO_LFE = 0x0008U,
PA_RESAMPLER_NO_FILL_SINK = 0x0010U,
+ PA_RESAMPLER_PRODUCE_LFE = 0x0020U,
+ PA_RESAMPLER_CONSUME_LFE = 0x0040U,
} pa_resample_flags_t;
struct pa_resampler {
diff --git a/src/pulsecore/rtpoll.c b/src/pulsecore/rtpoll.c
index 98cf88f..1085bf9 100644
--- a/src/pulsecore/rtpoll.c
+++ b/src/pulsecore/rtpoll.c
@@ -77,7 +77,9 @@ struct pa_rtpoll_item {
int (*work_cb)(pa_rtpoll_item *i);
int (*before_cb)(pa_rtpoll_item *i);
void (*after_cb)(pa_rtpoll_item *i);
- void *userdata;
+ void *work_userdata;
+ void *before_userdata;
+ void *after_userdata;
PA_LLIST_FIELDS(pa_rtpoll_item);
};
@@ -411,7 +413,9 @@ pa_rtpoll_item *pa_rtpoll_item_new(pa_rtpoll *p, pa_rtpoll_priority_t prio, unsi
i->pollfd = NULL;
i->priority = prio;
- i->userdata = NULL;
+ i->work_userdata = NULL;
+ i->before_userdata = NULL;
+ i->work_userdata = NULL;
i->before_cb = NULL;
i->after_cb = NULL;
i->work_cb = NULL;
@@ -458,42 +462,39 @@ struct pollfd *pa_rtpoll_item_get_pollfd(pa_rtpoll_item *i, unsigned *n_fds) {
return i->pollfd;
}
-void pa_rtpoll_item_set_before_callback(pa_rtpoll_item *i, int (*before_cb)(pa_rtpoll_item *i)) {
+void pa_rtpoll_item_set_before_callback(pa_rtpoll_item *i, int (*before_cb)(pa_rtpoll_item *i), void *userdata) {
pa_assert(i);
pa_assert(i->priority < PA_RTPOLL_NEVER);
i->before_cb = before_cb;
+ i->before_userdata = userdata;
}
-void pa_rtpoll_item_set_after_callback(pa_rtpoll_item *i, void (*after_cb)(pa_rtpoll_item *i)) {
+void pa_rtpoll_item_set_after_callback(pa_rtpoll_item *i, void (*after_cb)(pa_rtpoll_item *i), void *userdata) {
pa_assert(i);
pa_assert(i->priority < PA_RTPOLL_NEVER);
i->after_cb = after_cb;
+ i->after_userdata = userdata;
}
-void pa_rtpoll_item_set_work_callback(pa_rtpoll_item *i, int (*work_cb)(pa_rtpoll_item *i)) {
+void pa_rtpoll_item_set_work_callback(pa_rtpoll_item *i, int (*work_cb)(pa_rtpoll_item *i), void *userdata) {
pa_assert(i);
pa_assert(i->priority < PA_RTPOLL_NEVER);
i->work_cb = work_cb;
+ i->work_userdata = userdata;
}
-void pa_rtpoll_item_set_userdata(pa_rtpoll_item *i, void *userdata) {
+void* pa_rtpoll_item_get_work_userdata(pa_rtpoll_item *i) {
pa_assert(i);
- i->userdata = userdata;
-}
-
-void* pa_rtpoll_item_get_userdata(pa_rtpoll_item *i) {
- pa_assert(i);
-
- return i->userdata;
+ return i->work_userdata;
}
static int fdsem_before(pa_rtpoll_item *i) {
- if (pa_fdsem_before_poll(i->userdata) < 0)
+ if (pa_fdsem_before_poll(i->before_userdata) < 0)
return 1; /* 1 means immediate restart of the loop */
return 0;
@@ -503,7 +504,7 @@ static void fdsem_after(pa_rtpoll_item *i) {
pa_assert(i);
pa_assert((i->pollfd[0].revents & ~POLLIN) == 0);
- pa_fdsem_after_poll(i->userdata);
+ pa_fdsem_after_poll(i->after_userdata);
}
pa_rtpoll_item *pa_rtpoll_item_new_fdsem(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_fdsem *f) {
@@ -520,9 +521,8 @@ pa_rtpoll_item *pa_rtpoll_item_new_fdsem(pa_rtpoll *p, pa_rtpoll_priority_t prio
pollfd->fd = pa_fdsem_get(f);
pollfd->events = POLLIN;
- i->before_cb = fdsem_before;
- i->after_cb = fdsem_after;
- i->userdata = f;
+ pa_rtpoll_item_set_before_callback(i, fdsem_before, f);
+ pa_rtpoll_item_set_after_callback(i, fdsem_after, f);
return i;
}
@@ -530,7 +530,7 @@ pa_rtpoll_item *pa_rtpoll_item_new_fdsem(pa_rtpoll *p, pa_rtpoll_priority_t prio
static int asyncmsgq_read_before(pa_rtpoll_item *i) {
pa_assert(i);
- if (pa_asyncmsgq_read_before_poll(i->userdata) < 0)
+ if (pa_asyncmsgq_read_before_poll(i->before_userdata) < 0)
return 1; /* 1 means immediate restart of the loop */
return 0;
@@ -540,7 +540,7 @@ static void asyncmsgq_read_after(pa_rtpoll_item *i) {
pa_assert(i);
pa_assert((i->pollfd[0].revents & ~POLLIN) == 0);
- pa_asyncmsgq_read_after_poll(i->userdata);
+ pa_asyncmsgq_read_after_poll(i->after_userdata);
}
static int asyncmsgq_read_work(pa_rtpoll_item *i) {
@@ -552,11 +552,11 @@ static int asyncmsgq_read_work(pa_rtpoll_item *i) {
pa_assert(i);
- if (pa_asyncmsgq_get(i->userdata, &object, &code, &data, &offset, &chunk, 0) == 0) {
+ if (pa_asyncmsgq_get(i->work_userdata, &object, &code, &data, &offset, &chunk, 0) == 0) {
int ret;
if (!object && code == PA_MESSAGE_SHUTDOWN) {
- pa_asyncmsgq_done(i->userdata, 0);
+ pa_asyncmsgq_done(i->work_userdata, 0);
/* Requests the loop to exit. Will cause the next iteration of
* pa_rtpoll_run() to return 0 */
i->rtpoll->quit = true;
@@ -564,7 +564,7 @@ static int asyncmsgq_read_work(pa_rtpoll_item *i) {
}
ret = pa_asyncmsgq_dispatch(object, code, data, offset, &chunk);
- pa_asyncmsgq_done(i->userdata, ret);
+ pa_asyncmsgq_done(i->work_userdata, ret);
return 1;
}
@@ -584,10 +584,9 @@ pa_rtpoll_item *pa_rtpoll_item_new_asyncmsgq_read(pa_rtpoll *p, pa_rtpoll_priori
pollfd->fd = pa_asyncmsgq_read_fd(q);
pollfd->events = POLLIN;
- i->before_cb = asyncmsgq_read_before;
- i->after_cb = asyncmsgq_read_after;
- i->work_cb = asyncmsgq_read_work;
- i->userdata = q;
+ pa_rtpoll_item_set_before_callback(i, asyncmsgq_read_before, q);
+ pa_rtpoll_item_set_after_callback(i, asyncmsgq_read_after, q);
+ pa_rtpoll_item_set_work_callback(i, asyncmsgq_read_work, q);
return i;
}
@@ -595,7 +594,7 @@ pa_rtpoll_item *pa_rtpoll_item_new_asyncmsgq_read(pa_rtpoll *p, pa_rtpoll_priori
static int asyncmsgq_write_before(pa_rtpoll_item *i) {
pa_assert(i);
- pa_asyncmsgq_write_before_poll(i->userdata);
+ pa_asyncmsgq_write_before_poll(i->before_userdata);
return 0;
}
@@ -603,7 +602,7 @@ static void asyncmsgq_write_after(pa_rtpoll_item *i) {
pa_assert(i);
pa_assert((i->pollfd[0].revents & ~POLLIN) == 0);
- pa_asyncmsgq_write_after_poll(i->userdata);
+ pa_asyncmsgq_write_after_poll(i->after_userdata);
}
pa_rtpoll_item *pa_rtpoll_item_new_asyncmsgq_write(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_asyncmsgq *q) {
@@ -619,10 +618,8 @@ pa_rtpoll_item *pa_rtpoll_item_new_asyncmsgq_write(pa_rtpoll *p, pa_rtpoll_prior
pollfd->fd = pa_asyncmsgq_write_fd(q);
pollfd->events = POLLIN;
- i->before_cb = asyncmsgq_write_before;
- i->after_cb = asyncmsgq_write_after;
- i->work_cb = NULL;
- i->userdata = q;
+ pa_rtpoll_item_set_before_callback(i, asyncmsgq_write_before, q);
+ pa_rtpoll_item_set_after_callback(i, asyncmsgq_write_after, q);
return i;
}
diff --git a/src/pulsecore/rtpoll.h b/src/pulsecore/rtpoll.h
index 8f0715a..121b51e 100644
--- a/src/pulsecore/rtpoll.h
+++ b/src/pulsecore/rtpoll.h
@@ -80,19 +80,18 @@ struct pollfd *pa_rtpoll_item_get_pollfd(pa_rtpoll_item *i, unsigned *n_fds);
/* Set the callback that shall be called when there's time to do some work: If the
* callback returns a value > 0, the poll is skipped and the next
* iteration of the loop will start immediately. */
-void pa_rtpoll_item_set_work_callback(pa_rtpoll_item *i, int (*work_cb)(pa_rtpoll_item *i));
+void pa_rtpoll_item_set_work_callback(pa_rtpoll_item *i, int (*work_cb)(pa_rtpoll_item *i), void *userdata);
/* Set the callback that shall be called immediately before entering
* the sleeping poll: If the callback returns a value > 0, the poll is
* skipped and the next iteration of the loop will start immediately. */
-void pa_rtpoll_item_set_before_callback(pa_rtpoll_item *i, int (*before_cb)(pa_rtpoll_item *i));
+void pa_rtpoll_item_set_before_callback(pa_rtpoll_item *i, int (*before_cb)(pa_rtpoll_item *i), void *userdata);
/* Set the callback that shall be called immediately after having
* entered the sleeping poll */
-void pa_rtpoll_item_set_after_callback(pa_rtpoll_item *i, void (*after_cb)(pa_rtpoll_item *i));
+void pa_rtpoll_item_set_after_callback(pa_rtpoll_item *i, void (*after_cb)(pa_rtpoll_item *i), void *userdata);
-void pa_rtpoll_item_set_userdata(pa_rtpoll_item *i, void *userdata);
-void* pa_rtpoll_item_get_userdata(pa_rtpoll_item *i);
+void* pa_rtpoll_item_get_work_userdata(pa_rtpoll_item *i);
pa_rtpoll_item *pa_rtpoll_item_new_fdsem(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_fdsem *s);
pa_rtpoll_item *pa_rtpoll_item_new_asyncmsgq_read(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_asyncmsgq *q);
diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c
index d3ead2c..12e3c8b 100644
--- a/src/pulsecore/sink-input.c
+++ b/src/pulsecore/sink-input.c
@@ -190,7 +190,10 @@ bool pa_sink_input_new_data_set_sink(pa_sink_input_new_data *data, pa_sink *s, b
if (!data->req_formats) {
/* We're not working with the extended API */
data->sink = s;
- data->save_sink = save;
+ if (save) {
+ pa_xfree(data->preferred_sink);
+ data->preferred_sink = pa_xstrdup(s->name);
+ }
data->sink_requested_by_application = requested_by_application;
} else {
/* Extended API: let's see if this sink supports the formats the client can provide */
@@ -199,7 +202,10 @@ bool pa_sink_input_new_data_set_sink(pa_sink_input_new_data *data, pa_sink *s, b
if (formats && !pa_idxset_isempty(formats)) {
/* Sink supports at least one of the requested formats */
data->sink = s;
- data->save_sink = save;
+ if (save) {
+ pa_xfree(data->preferred_sink);
+ data->preferred_sink = pa_xstrdup(s->name);
+ }
data->sink_requested_by_application = requested_by_application;
if (data->nego_formats)
pa_idxset_free(data->nego_formats, (pa_free_cb_t) pa_format_info_free);
@@ -226,7 +232,7 @@ bool pa_sink_input_new_data_set_formats(pa_sink_input_new_data *data, pa_idxset
if (data->sink) {
/* Trigger format negotiation */
- return pa_sink_input_new_data_set_sink(data, data->sink, data->save_sink, data->sink_requested_by_application);
+ return pa_sink_input_new_data_set_sink(data, data->sink, (data->preferred_sink != NULL), data->sink_requested_by_application);
}
return true;
@@ -250,6 +256,9 @@ void pa_sink_input_new_data_done(pa_sink_input_new_data *data) {
if (data->volume_factor_sink_items)
pa_hashmap_free(data->volume_factor_sink_items);
+ if (data->preferred_sink)
+ pa_xfree(data->preferred_sink);
+
pa_proplist_free(data->proplist);
}
@@ -467,7 +476,8 @@ int pa_sink_input_new(
((data->flags & PA_SINK_INPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) |
(core->disable_remixing || (data->flags & PA_SINK_INPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) |
(core->remixing_use_all_sink_channels ? 0 : PA_RESAMPLER_NO_FILL_SINK) |
- (core->disable_lfe_remixing ? PA_RESAMPLER_NO_LFE : 0)))) {
+ (core->remixing_produce_lfe ? PA_RESAMPLER_PRODUCE_LFE : 0) |
+ (core->remixing_consume_lfe ? PA_RESAMPLER_CONSUME_LFE : 0)))) {
pa_log_warn("Unsupported resampling operation.");
return -PA_ERR_NOTSUPPORTED;
}
@@ -518,7 +528,7 @@ int pa_sink_input_new(
pa_cvolume_reset(&i->real_ratio, i->sample_spec.channels);
i->volume_writable = data->volume_writable;
i->save_volume = data->save_volume;
- i->save_sink = data->save_sink;
+ i->preferred_sink = pa_xstrdup(data->preferred_sink);
i->save_muted = data->save_muted;
i->muted = data->muted;
@@ -776,6 +786,9 @@ static void sink_input_free(pa_object *o) {
if (i->volume_factor_sink_items)
pa_hashmap_free(i->volume_factor_sink_items);
+ if (i->preferred_sink)
+ pa_xfree(i->preferred_sink);
+
pa_xfree(i->driver);
pa_xfree(i);
}
@@ -1914,7 +1927,16 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, bool save) {
i->moving(i, dest);
i->sink = dest;
- i->save_sink = save;
+ /* save == true, means user is calling the move_to() and want to
+ save the preferred_sink */
+ if (save) {
+ pa_xfree(i->preferred_sink);
+ if (dest == dest->core->default_sink)
+ i->preferred_sink = NULL;
+ else
+ i->preferred_sink = pa_xstrdup(dest->name);
+ }
+
pa_idxset_put(dest->inputs, pa_sink_input_ref(i), NULL);
PA_HASHMAP_FOREACH(v, i->volume_factor_sink_items, state)
@@ -1957,6 +1979,12 @@ void pa_sink_input_fail_move(pa_sink_input *i) {
if (pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FAIL], i) == PA_HOOK_STOP)
return;
+ /* Can we move the sink input to the default sink? */
+ if (i->core->rescue_streams && pa_sink_input_may_move_to(i, i->core->default_sink)) {
+ if (pa_sink_input_finish_move(i, i->core->default_sink, false) >= 0)
+ return;
+ }
+
if (i->moving)
i->moving(i, NULL);
@@ -2292,7 +2320,8 @@ int pa_sink_input_update_resampler(pa_sink_input *i) {
((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) |
(i->core->remixing_use_all_sink_channels ? 0 : PA_RESAMPLER_NO_FILL_SINK) |
- (i->core->disable_lfe_remixing ? PA_RESAMPLER_NO_LFE : 0));
+ (i->core->remixing_produce_lfe ? PA_RESAMPLER_PRODUCE_LFE : 0) |
+ (i->core->remixing_consume_lfe ? PA_RESAMPLER_CONSUME_LFE : 0));
if (!new_resampler) {
pa_log_warn("Unsupported resampling operation.");
@@ -2404,3 +2433,17 @@ void pa_sink_input_set_reference_ratio(pa_sink_input *i, const pa_cvolume *ratio
pa_cvolume_snprint_verbose(old_ratio_str, sizeof(old_ratio_str), &old_ratio, &i->channel_map, true),
pa_cvolume_snprint_verbose(new_ratio_str, sizeof(new_ratio_str), ratio, &i->channel_map, true));
}
+
+/* Called from the main thread. */
+void pa_sink_input_set_preferred_sink(pa_sink_input *i, pa_sink *s) {
+ pa_assert(i);
+
+ pa_xfree(i->preferred_sink);
+ if (s) {
+ i->preferred_sink = pa_xstrdup(s->name);
+ pa_sink_input_move_to(i, s, false);
+ } else {
+ i->preferred_sink = NULL;
+ pa_sink_input_move_to(i, i->core->default_sink, false);
+ }
+}
diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h
index f7f9237..d3de6e3 100644
--- a/src/pulsecore/sink-input.h
+++ b/src/pulsecore/sink-input.h
@@ -119,11 +119,16 @@ struct pa_sink_input {
bool muted:1;
- /* if true then the sink we are connected to and/or the volume
- * set is worth remembering, i.e. was explicitly chosen by the
- * user and not automatically. module-stream-restore looks for
+ /* if true then the volume and the mute state of this sink-input
+ * are worth remembering, module-stream-restore looks for
* this.*/
- bool save_sink:1, save_volume:1, save_muted:1;
+ bool save_volume:1, save_muted:1;
+
+ /* if users move the sink-input to a sink, and the sink is not default_sink,
+ * the sink->name will be saved in preferred_sink. And later if sink-input
+ * is moved to other sinks for some reason, it still can be restored to the
+ * preferred_sink at an appropriate time */
+ char *preferred_sink;
pa_resample_method_t requested_resample_method, actual_resample_method;
@@ -315,7 +320,9 @@ typedef struct pa_sink_input_new_data {
bool volume_writable:1;
- bool save_sink:1, save_volume:1, save_muted:1;
+ bool save_volume:1, save_muted:1;
+
+ char *preferred_sink;
} pa_sink_input_new_data;
pa_sink_input_new_data* pa_sink_input_new_data_init(pa_sink_input_new_data *data);
@@ -454,6 +461,8 @@ void pa_sink_input_set_volume_direct(pa_sink_input *i, const pa_cvolume *volume)
* i->reference_ratio and logs a message if the value changes. */
void pa_sink_input_set_reference_ratio(pa_sink_input *i, const pa_cvolume *ratio);
+void pa_sink_input_set_preferred_sink(pa_sink_input *i, pa_sink *s);
+
#define pa_sink_input_assert_io_context(s) \
pa_assert(pa_thread_mq_get() || !PA_SINK_INPUT_IS_LINKED((s)->state))
diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
index 55e9af3..11c3849 100644
--- a/src/pulsecore/sink.c
+++ b/src/pulsecore/sink.c
@@ -724,9 +724,14 @@ void pa_sink_put(pa_sink* s) {
pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_NEW, s->index);
pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_PUT], s);
- /* This function must be called after the PA_CORE_HOOK_SINK_PUT hook,
- * because module-switch-on-connect needs to know the old default sink */
+ /* It's good to fire the SINK_PUT hook before updating the default sink,
+ * because module-switch-on-connect will set the new sink as the default
+ * sink, and if we were to call pa_core_update_default_sink() before that,
+ * the default sink might change twice, causing unnecessary stream moving. */
+
pa_core_update_default_sink(s->core);
+
+ pa_core_move_streams_to_newly_available_preferred_sink(s->core, s);
}
/* Called from main context */
@@ -757,6 +762,9 @@ void pa_sink_unlink(pa_sink* s) {
pa_core_update_default_sink(s->core);
+ if (linked && s->core->rescue_streams)
+ pa_sink_move_streams_to_default_sink(s->core, s, false);
+
if (s->card)
pa_idxset_remove_by_data(s->card->sinks, s, NULL);
@@ -3931,3 +3939,44 @@ void pa_sink_set_reference_volume_direct(pa_sink *s, const pa_cvolume *volume) {
pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_VOLUME_CHANGED], s);
}
+
+void pa_sink_move_streams_to_default_sink(pa_core *core, pa_sink *old_sink, bool default_sink_changed) {
+ pa_sink_input *i;
+ uint32_t idx;
+
+ pa_assert(core);
+ pa_assert(old_sink);
+
+ if (core->state == PA_CORE_SHUTDOWN)
+ return;
+
+ if (core->default_sink == NULL || core->default_sink->unlink_requested)
+ return;
+
+ if (old_sink == core->default_sink)
+ return;
+
+ PA_IDXSET_FOREACH(i, old_sink->inputs, idx) {
+ if (!PA_SINK_INPUT_IS_LINKED(i->state))
+ continue;
+
+ if (!i->sink)
+ continue;
+
+ /* If default_sink_changed is false, the old sink became unavailable, so all streams must be moved. */
+ if (pa_safe_streq(old_sink->name, i->preferred_sink) && default_sink_changed)
+ continue;
+
+ if (!pa_sink_input_may_move_to(i, core->default_sink))
+ continue;
+
+ if (default_sink_changed)
+ pa_log_info("The sink input %u \"%s\" is moving to %s due to change of the default sink.",
+ i->index, pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), core->default_sink->name);
+ else
+ pa_log_info("The sink input %u \"%s\" is moving to %s, because the old sink became unavailable.",
+ i->index, pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), core->default_sink->name);
+
+ pa_sink_input_move_to(i, core->default_sink, false);
+ }
+}
diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h
index b9dd64f..1778eb7 100644
--- a/src/pulsecore/sink.h
+++ b/src/pulsecore/sink.h
@@ -558,6 +558,12 @@ int64_t pa_sink_get_latency_within_thread(pa_sink *s, bool allow_negative);
* s->reference_volume and fires change notifications. */
void pa_sink_set_reference_volume_direct(pa_sink *s, const pa_cvolume *volume);
+/* When the default_sink is changed or the active_port of a sink is changed to
+ * PA_AVAILABLE_NO, this function is called to move the streams of the old
+ * default_sink or the sink with active_port equals PA_AVAILABLE_NO to the
+ * current default_sink conditionally*/
+void pa_sink_move_streams_to_default_sink(pa_core *core, pa_sink *old_sink, bool default_sink_changed);
+
/* Verify that we called in IO context (aka 'thread context), or that
* the sink is not yet set up, i.e. the thread not set up yet. See
* pa_assert_io_context() in thread-mq.h for more information. */
diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c
index f5005ab..92f74d4 100644
--- a/src/pulsecore/source-output.c
+++ b/src/pulsecore/source-output.c
@@ -134,7 +134,10 @@ bool pa_source_output_new_data_set_source(pa_source_output_new_data *data, pa_so
if (!data->req_formats) {
/* We're not working with the extended API */
data->source = s;
- data->save_source = save;
+ if (save) {
+ pa_xfree(data->preferred_source);
+ data->preferred_source = pa_xstrdup(s->name);
+ }
data->source_requested_by_application = requested_by_application;
} else {
/* Extended API: let's see if this source supports the formats the client would like */
@@ -143,7 +146,10 @@ bool pa_source_output_new_data_set_source(pa_source_output_new_data *data, pa_so
if (formats && !pa_idxset_isempty(formats)) {
/* Source supports at least one of the requested formats */
data->source = s;
- data->save_source = save;
+ if (save) {
+ pa_xfree(data->preferred_source);
+ data->preferred_source = pa_xstrdup(s->name);
+ }
data->source_requested_by_application = requested_by_application;
if (data->nego_formats)
pa_idxset_free(data->nego_formats, (pa_free_cb_t) pa_format_info_free);
@@ -170,7 +176,7 @@ bool pa_source_output_new_data_set_formats(pa_source_output_new_data *data, pa_i
if (data->source) {
/* Trigger format negotiation */
- return pa_source_output_new_data_set_source(data, data->source, data->save_source,
+ return pa_source_output_new_data_set_source(data, data->source, (data->preferred_source != NULL),
data->source_requested_by_application);
}
@@ -189,6 +195,9 @@ void pa_source_output_new_data_done(pa_source_output_new_data *data) {
if (data->format)
pa_format_info_free(data->format);
+ if (data->preferred_source)
+ pa_xfree(data->preferred_source);
+
pa_proplist_free(data->proplist);
}
@@ -414,7 +423,8 @@ int pa_source_output_new(
((data->flags & PA_SOURCE_OUTPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) |
(core->disable_remixing || (data->flags & PA_SOURCE_OUTPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) |
(core->remixing_use_all_sink_channels ? 0 : PA_RESAMPLER_NO_FILL_SINK) |
- (core->disable_lfe_remixing ? PA_RESAMPLER_NO_LFE : 0)))) {
+ (core->remixing_produce_lfe ? PA_RESAMPLER_PRODUCE_LFE : 0) |
+ (core->remixing_consume_lfe ? PA_RESAMPLER_CONSUME_LFE : 0)))) {
pa_log_warn("Unsupported resampling operation.");
return -PA_ERR_NOTSUPPORTED;
}
@@ -459,7 +469,7 @@ int pa_source_output_new(
pa_cvolume_reset(&o->real_ratio, o->sample_spec.channels);
o->volume_writable = data->volume_writable;
o->save_volume = data->save_volume;
- o->save_source = data->save_source;
+ o->preferred_source = pa_xstrdup(data->preferred_source);
o->save_muted = data->save_muted;
o->muted = data->muted;
@@ -651,6 +661,9 @@ static void source_output_free(pa_object* mo) {
if (o->proplist)
pa_proplist_free(o->proplist);
+ if (o->preferred_source)
+ pa_xfree(o->preferred_source);
+
pa_xfree(o->driver);
pa_xfree(o);
}
@@ -1544,7 +1557,16 @@ int pa_source_output_finish_move(pa_source_output *o, pa_source *dest, bool save
o->moving(o, dest);
o->source = dest;
- o->save_source = save;
+ /* save == true, means user is calling the move_to() and want to
+ save the preferred_source */
+ if (save) {
+ pa_xfree(o->preferred_source);
+ if (dest == dest->core->default_source)
+ o->preferred_source = NULL;
+ else
+ o->preferred_source = pa_xstrdup(dest->name);
+ }
+
pa_idxset_put(o->source->outputs, pa_source_output_ref(o), NULL);
pa_cvolume_remap(&o->volume_factor_source, &o->channel_map, &o->source->channel_map);
@@ -1584,6 +1606,12 @@ void pa_source_output_fail_move(pa_source_output *o) {
if (pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FAIL], o) == PA_HOOK_STOP)
return;
+ /* Can we move the source output to the default source? */
+ if (o->core->rescue_streams && pa_source_output_may_move_to(o, o->core->default_source)) {
+ if (pa_source_output_finish_move(o, o->core->default_source, false) >= 0)
+ return;
+ }
+
if (o->moving)
o->moving(o, NULL);
@@ -1755,7 +1783,8 @@ int pa_source_output_update_resampler(pa_source_output *o) {
((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) |
(o->core->remixing_use_all_sink_channels ? 0 : PA_RESAMPLER_NO_FILL_SINK) |
- (o->core->disable_lfe_remixing ? PA_RESAMPLER_NO_LFE : 0));
+ (o->core->remixing_produce_lfe ? PA_RESAMPLER_PRODUCE_LFE : 0) |
+ (o->core->remixing_consume_lfe ? PA_RESAMPLER_CONSUME_LFE : 0));
if (!new_resampler) {
pa_log_warn("Unsupported resampling operation.");
@@ -1867,3 +1896,17 @@ void pa_source_output_set_reference_ratio(pa_source_output *o, const pa_cvolume
pa_cvolume_snprint_verbose(old_ratio_str, sizeof(old_ratio_str), &old_ratio, &o->channel_map, true),
pa_cvolume_snprint_verbose(new_ratio_str, sizeof(new_ratio_str), ratio, &o->channel_map, true));
}
+
+/* Called from the main thread. */
+void pa_source_output_set_preferred_source(pa_source_output *o, pa_source *s) {
+ pa_assert(o);
+
+ pa_xfree(o->preferred_source);
+ if (s) {
+ o->preferred_source = pa_xstrdup(s->name);
+ pa_source_output_move_to(o, s, false);
+ } else {
+ o->preferred_source = NULL;
+ pa_source_output_move_to(o, o->core->default_source, false);
+ }
+}
diff --git a/src/pulsecore/source-output.h b/src/pulsecore/source-output.h
index 16853c0..2bf5682 100644
--- a/src/pulsecore/source-output.h
+++ b/src/pulsecore/source-output.h
@@ -106,11 +106,15 @@ struct pa_source_output {
bool muted:1;
- /* if true then the source we are connected to and/or the volume
- * set is worth remembering, i.e. was explicitly chosen by the
- * user and not automatically. module-stream-restore looks for
- * this.*/
- bool save_source:1, save_volume:1, save_muted:1;
+ /* if true then the volume and the mute state of this source-output
+ * are worth remembering, module-stream-restore looks for this. */
+ bool save_volume:1, save_muted:1;
+
+ /* if users move the source-output to a source, and the source is not
+ * default_source, the source->name will be saved in preferred_source. And
+ * later if source-output is moved to other sources for some reason, it
+ * still can be restored to the preferred_source at an appropriate time */
+ char *preferred_source;
pa_resample_method_t requested_resample_method, actual_resample_method;
@@ -277,7 +281,8 @@ typedef struct pa_source_output_new_data {
bool volume_writable:1;
- bool save_source:1, save_volume:1, save_muted:1;
+ bool save_volume:1, save_muted:1;
+ char *preferred_source;
} pa_source_output_new_data;
pa_source_output_new_data* pa_source_output_new_data_init(pa_source_output_new_data *data);
@@ -397,6 +402,8 @@ void pa_source_output_set_volume_direct(pa_source_output *o, const pa_cvolume *v
* o->reference_ratio and logs a message if the value changes. */
void pa_source_output_set_reference_ratio(pa_source_output *o, const pa_cvolume *ratio);
+void pa_source_output_set_preferred_source(pa_source_output *o, pa_source *s);
+
#define pa_source_output_assert_io_context(s) \
pa_assert(pa_thread_mq_get() || !PA_SOURCE_OUTPUT_IS_LINKED((s)->state))
diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c
index 855d3de..1cb83f9 100644
--- a/src/pulsecore/source.c
+++ b/src/pulsecore/source.c
@@ -666,9 +666,13 @@ void pa_source_put(pa_source *s) {
pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_NEW, s->index);
pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_PUT], s);
- /* This function must be called after the PA_CORE_HOOK_SOURCE_PUT hook,
- * because module-switch-on-connect needs to know the old default source */
+ /* It's good to fire the SOURCE_PUT hook before updating the default source,
+ * because module-switch-on-connect will set the new source as the default
+ * source, and if we were to call pa_core_update_default_source() before that,
+ * the default source might change twice, causing unnecessary stream moving. */
pa_core_update_default_source(s->core);
+
+ pa_core_move_streams_to_newly_available_preferred_source(s->core, s);
}
/* Called from main context */
@@ -698,6 +702,9 @@ void pa_source_unlink(pa_source *s) {
pa_core_update_default_source(s->core);
+ if (linked && s->core->rescue_streams)
+ pa_source_move_streams_to_default_source(s->core, s, false);
+
if (s->card)
pa_idxset_remove_by_data(s->card->sources, s, NULL);
@@ -2988,3 +2995,44 @@ void pa_source_set_reference_volume_direct(pa_source *s, const pa_cvolume *volum
pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_VOLUME_CHANGED], s);
}
+
+void pa_source_move_streams_to_default_source(pa_core *core, pa_source *old_source, bool default_source_changed) {
+ pa_source_output *o;
+ uint32_t idx;
+
+ pa_assert(core);
+ pa_assert(old_source);
+
+ if (core->state == PA_CORE_SHUTDOWN)
+ return;
+
+ if (core->default_source == NULL || core->default_source->unlink_requested)
+ return;
+
+ if (old_source == core->default_source)
+ return;
+
+ PA_IDXSET_FOREACH(o, old_source->outputs, idx) {
+ if (!PA_SOURCE_OUTPUT_IS_LINKED(o->state))
+ continue;
+
+ if (!o->source)
+ continue;
+
+ /* If default_source_changed is false, the old source became unavailable, so all streams must be moved. */
+ if (pa_safe_streq(old_source->name, o->preferred_source) && default_source_changed)
+ continue;
+
+ if (!pa_source_output_may_move_to(o, core->default_source))
+ continue;
+
+ if (default_source_changed)
+ pa_log_info("The source output %u \"%s\" is moving to %s due to change of the default source.",
+ o->index, pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME)), core->default_source->name);
+ else
+ pa_log_info("The source output %u \"%s\" is moving to %s, because the old source became unavailable.",
+ o->index, pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME)), core->default_source->name);
+
+ pa_source_output_move_to(o, core->default_source, false);
+ }
+}
diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h
index e9462c6..12e37d6 100644
--- a/src/pulsecore/source.h
+++ b/src/pulsecore/source.h
@@ -479,6 +479,12 @@ int64_t pa_source_get_latency_within_thread(pa_source *s, bool allow_negative);
* sets s->reference_volume and fires change notifications. */
void pa_source_set_reference_volume_direct(pa_source *s, const pa_cvolume *volume);
+/* When the default_source is changed or the active_port of a source is changed to
+ * PA_AVAILABLE_NO, this function is called to move the streams of the old
+ * default_source or the source with active_port equals PA_AVAILABLE_NO to the
+ * current default_source conditionally*/
+void pa_source_move_streams_to_default_source(pa_core *core, pa_source *old_source, bool default_source_changed);
+
#define pa_source_assert_io_context(s) \
pa_assert(pa_thread_mq_get() || !PA_SOURCE_IS_LINKED((s)->state))