pa_alsa_ucm_mapping_context *ucm_context;
};
+enum {
+ SINK_MESSAGE_SYNC_MIXER = PA_SINK_MESSAGE_MAX
+};
+
static void userdata_free(struct userdata *u);
/* FIXME: Is there a better way to do this than device names? */
return -PA_ERR_IO;
}
+/* Called from the IO thread or the main thread depending on whether deferred
+ * volume is enabled or not (with deferred volume all mixer handling is done
+ * from the IO thread).
+ *
+ * Sets the mixer settings to match the current sink and port state (the port
+ * is given as an argument, because active_port may still point to the old
+ * port, if we're switching ports). */
+static void sync_mixer(struct userdata *u, pa_device_port *port) {
+ pa_alsa_setting *setting = NULL;
+
+ pa_assert(u);
+
+ if (!u->mixer_path)
+ return;
+
+ /* port may be NULL, because if we use a synthesized mixer path, then the
+ * sink has no ports. */
+ if (port) {
+ pa_alsa_port_data *data;
+
+ data = PA_DEVICE_PORT_DATA(port);
+ setting = data->setting;
+ }
+
+ pa_alsa_path_select(u->mixer_path, setting, u->mixer_handle, u->sink->muted);
+
+ if (u->sink->set_mute)
+ u->sink->set_mute(u->sink);
+ if (u->sink->flags & PA_SINK_DEFERRED_VOLUME) {
+ if (u->sink->write_volume)
+ u->sink->write_volume(u->sink);
+ } else {
+ if (u->sink->set_volume)
+ u->sink->set_volume(u->sink);
+ }
+}
+
/* Called from IO context */
static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
struct userdata *u = PA_SINK(o)->userdata;
return 0;
}
+
+ case SINK_MESSAGE_SYNC_MIXER: {
+ pa_device_port *port = data;
+
+ sync_mixer(u, port);
+ return 0;
+ }
}
return pa_sink_process_msg(o, code, data, offset, chunk);
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
+ /* When our session becomes active, we need to sync the mixer, because
+ * another user may have changed the mixer settings.
+ *
+ * If deferred volume is enabled, the syncing is done in the
+ * set_state_in_io_thread() callback instead. */
+ if (!(s->flags & PA_SINK_DEFERRED_VOLUME)
+ && (s->suspend_cause & PA_SUSPEND_SESSION)
+ && !(new_suspend_cause & PA_SUSPEND_SESSION))
+ sync_mixer(u, s->active_port);
+
old_state = pa_sink_get_state(u->sink);
if (PA_SINK_IS_OPENED(old_state) && new_state == PA_SINK_SUSPENDED)
pa_assert(s);
pa_assert_se(u = s->userdata);
+ /* When our session becomes active, we need to sync the mixer, because
+ * another user may have changed the mixer settings.
+ *
+ * If deferred volume is disabled, the syncing is done in the
+ * set_state_in_main_thread() callback instead. */
+ if ((s->flags & PA_SINK_DEFERRED_VOLUME)
+ && (s->suspend_cause & PA_SUSPEND_SESSION)
+ && !(new_suspend_cause & PA_SUSPEND_SESSION))
+ sync_mixer(u, s->active_port);
+
/* It may be that only the suspend cause is changing, in which case there's
- * nothing to do. */
+ * nothing more to do. */
if (new_state == s->thread_info.state)
return 0;
if (!PA_SINK_IS_LINKED(u->sink->state))
return 0;
- if (u->sink->suspend_cause & PA_SUSPEND_SESSION) {
- pa_sink_set_mixer_dirty(u->sink, true);
+ if (u->sink->suspend_cause & PA_SUSPEND_SESSION)
return 0;
- }
if (mask & SND_CTL_EVENT_MASK_VALUE) {
pa_sink_get_volume(u->sink, true);
if (mask == SND_CTL_EVENT_MASK_REMOVE)
return 0;
- if (u->sink->suspend_cause & PA_SUSPEND_SESSION) {
- pa_sink_set_mixer_dirty(u->sink, true);
+ if (u->sink->suspend_cause & PA_SUSPEND_SESSION)
return 0;
- }
if (mask & SND_CTL_EVENT_MASK_VALUE)
pa_sink_update_volume_and_mute(u->sink);
pa_assert(u->mixer_handle);
data = PA_DEVICE_PORT_DATA(p);
-
pa_assert_se(u->mixer_path = data->path);
- pa_alsa_path_select(u->mixer_path, data->setting, u->mixer_handle, s->muted);
-
mixer_volume_init(u);
- if (s->set_mute)
- s->set_mute(s);
- if (s->flags & PA_SINK_DEFERRED_VOLUME) {
- if (s->write_volume)
- s->write_volume(s);
- } else {
- if (s->set_volume)
- s->set_volume(s);
- }
+ if (s->flags & PA_SINK_DEFERRED_VOLUME)
+ pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_SYNC_MIXER, p, 0, NULL);
+ else
+ sync_mixer(u, p);
if (data->suspend_when_unavailable && p->available == PA_AVAILABLE_NO)
pa_sink_suspend(s, true, PA_SUSPEND_UNAVAILABLE);
pa_alsa_ucm_mapping_context *ucm_context;
};
+enum {
+ SOURCE_MESSAGE_SYNC_MIXER = PA_SOURCE_MESSAGE_MAX
+};
+
static void userdata_free(struct userdata *u);
static pa_hook_result_t reserve_cb(pa_reserve_wrapper *r, void *forced, struct userdata *u) {
return -PA_ERR_IO;
}
+/* Called from the IO thread or the main thread depending on whether deferred
+ * volume is enabled or not (with deferred volume all mixer handling is done
+ * from the IO thread).
+ *
+ * Sets the mixer settings to match the current source and port state (the port
+ * is given as an argument, because active_port may still point to the old
+ * port, if we're switching ports). */
+static void sync_mixer(struct userdata *u, pa_device_port *port) {
+ pa_alsa_setting *setting = NULL;
+
+ pa_assert(u);
+
+ if (!u->mixer_path)
+ return;
+
+ /* port may be NULL, because if we use a synthesized mixer path, then the
+ * source has no ports. */
+ if (port) {
+ pa_alsa_port_data *data;
+
+ data = PA_DEVICE_PORT_DATA(port);
+ setting = data->setting;
+ }
+
+ pa_alsa_path_select(u->mixer_path, setting, u->mixer_handle, u->source->muted);
+
+ if (u->source->set_mute)
+ u->source->set_mute(u->source);
+ if (u->source->flags & PA_SOURCE_DEFERRED_VOLUME) {
+ if (u->source->write_volume)
+ u->source->write_volume(u->source);
+ } else {
+ if (u->source->set_volume)
+ u->source->set_volume(u->source);
+ }
+}
+
/* Called from IO context */
static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
struct userdata *u = PA_SOURCE(o)->userdata;
return 0;
}
+
+ case SOURCE_MESSAGE_SYNC_MIXER: {
+ pa_device_port *port = data;
+
+ sync_mixer(u, port);
+ return 0;
+ }
}
return pa_source_process_msg(o, code, data, offset, chunk);
pa_source_assert_ref(s);
pa_assert_se(u = s->userdata);
+ /* When our session becomes active, we need to sync the mixer, because
+ * another user may have changed the mixer settings.
+ *
+ * If deferred volume is enabled, the syncing is done in the
+ * set_state_in_io_thread() callback instead. */
+ if (!(s->flags & PA_SOURCE_DEFERRED_VOLUME)
+ && (s->suspend_cause & PA_SUSPEND_SESSION)
+ && !(new_suspend_cause & PA_SUSPEND_SESSION))
+ sync_mixer(u, s->active_port);
+
old_state = pa_source_get_state(u->source);
if (PA_SOURCE_IS_OPENED(old_state) && new_state == PA_SOURCE_SUSPENDED)
pa_assert(s);
pa_assert_se(u = s->userdata);
+ /* When our session becomes active, we need to sync the mixer, because
+ * another user may have changed the mixer settings.
+ *
+ * If deferred volume is disabled, the syncing is done in the
+ * set_state_in_main_thread() callback instead. */
+ if ((s->flags & PA_SOURCE_DEFERRED_VOLUME)
+ && (s->suspend_cause & PA_SUSPEND_SESSION)
+ && !(new_suspend_cause & PA_SUSPEND_SESSION))
+ sync_mixer(u, s->active_port);
+
/* It may be that only the suspend cause is changing, in which case there's
- * nothing to do. */
+ * nothing more to do. */
if (new_state == s->thread_info.state)
return 0;
if (!PA_SOURCE_IS_LINKED(u->source->state))
return 0;
- if (u->source->suspend_cause & PA_SUSPEND_SESSION) {
- pa_source_set_mixer_dirty(u->source, true);
+ if (u->source->suspend_cause & PA_SUSPEND_SESSION)
return 0;
- }
if (mask & SND_CTL_EVENT_MASK_VALUE) {
pa_source_get_volume(u->source, true);
if (mask == SND_CTL_EVENT_MASK_REMOVE)
return 0;
- if (u->source->suspend_cause & PA_SUSPEND_SESSION) {
- pa_source_set_mixer_dirty(u->source, true);
+ if (u->source->suspend_cause & PA_SUSPEND_SESSION)
return 0;
- }
if (mask & SND_CTL_EVENT_MASK_VALUE)
pa_source_update_volume_and_mute(u->source);
pa_assert(u->mixer_handle);
data = PA_DEVICE_PORT_DATA(p);
-
pa_assert_se(u->mixer_path = data->path);
- pa_alsa_path_select(u->mixer_path, data->setting, u->mixer_handle, s->muted);
-
mixer_volume_init(u);
- if (s->set_mute)
- s->set_mute(s);
- if (s->flags & PA_SOURCE_DEFERRED_VOLUME) {
- if (s->write_volume)
- s->write_volume(s);
- } else {
- if (s->set_volume)
- s->set_volume(s);
- }
+ if (s->flags & PA_SOURCE_DEFERRED_VOLUME)
+ pa_asyncmsgq_send(u->source->asyncmsgq, PA_MSGOBJECT(u->source), SOURCE_MESSAGE_SYNC_MIXER, p, 0, NULL);
+ else
+ sync_mixer(u, p);
return 0;
}
PA_LLIST_FIELDS(pa_sink_volume_change);
};
-struct sink_message_set_port {
- pa_device_port *port;
- int ret;
-};
-
struct set_state_data {
pa_sink_state_t state;
pa_suspend_cause_t suspend_cause;
s->flags = flags;
s->priority = 0;
s->suspend_cause = data->suspend_cause;
- pa_sink_set_mixer_dirty(s, false);
s->name = pa_xstrdup(name);
s->proplist = pa_proplist_copy(data->proplist);
s->driver = pa_xstrdup(pa_path_get_filename(data->driver));
return sink_set_state(s, pa_sink_used_by(s) ? PA_SINK_RUNNING : PA_SINK_IDLE, 0);
}
-/* Called from any context - must be threadsafe */
-void pa_sink_set_mixer_dirty(pa_sink *s, bool is_dirty) {
- pa_atomic_store(&s->mixer_dirty, is_dirty ? 1 : 0);
-}
-
/* Called from main context */
int pa_sink_suspend(pa_sink *s, bool suspend, pa_suspend_cause_t cause) {
pa_suspend_cause_t merged_cause;
else
merged_cause = s->suspend_cause & ~cause;
- if (!(merged_cause & PA_SUSPEND_SESSION) && (pa_atomic_load(&s->mixer_dirty) != 0)) {
- /* This might look racy but isn't: If somebody sets mixer_dirty exactly here,
- it'll be handled just fine. */
- pa_sink_set_mixer_dirty(s, false);
- pa_log_debug("Mixer is now accessible. Updating alsa mixer settings.");
- if (s->active_port && s->set_port) {
- if (s->flags & PA_SINK_DEFERRED_VOLUME) {
- struct sink_message_set_port msg = { .port = s->active_port, .ret = 0 };
- pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_PORT, &msg, 0, NULL) == 0);
- }
- else
- s->set_port(s, s->active_port);
- }
- else {
- if (s->set_mute)
- s->set_mute(s);
- if (s->set_volume)
- s->set_volume(s);
- }
- }
-
if (merged_cause)
return sink_set_state(s, PA_SINK_SUSPENDED, merged_cause);
else
pa_sink_set_max_request_within_thread(s, (size_t) offset);
return 0;
- case PA_SINK_MESSAGE_SET_PORT:
-
- pa_assert(userdata);
- if (s->set_port) {
- struct sink_message_set_port *msg_data = userdata;
- msg_data->ret = s->set_port(s, msg_data->port);
- }
- return 0;
-
case PA_SINK_MESSAGE_UPDATE_VOLUME_AND_MUTE:
/* This message is sent from IO-thread and handled in main thread. */
pa_assert_ctl_context();
/* Called from main context */
int pa_sink_set_port(pa_sink *s, const char *name, bool save) {
pa_device_port *port;
- int ret;
pa_sink_assert_ref(s);
pa_assert_ctl_context();
return 0;
}
- if (s->flags & PA_SINK_DEFERRED_VOLUME) {
- struct sink_message_set_port msg = { .port = port, .ret = 0 };
- pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_PORT, &msg, 0, NULL) == 0);
- ret = msg.ret;
- }
- else
- ret = s->set_port(s, port);
-
- if (ret < 0)
+ if (s->set_port(s, port) < 0)
return -PA_ERR_NOENTITY;
pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
pa_hashmap *ports;
pa_device_port *active_port;
- pa_atomic_t mixer_dirty;
/* The latency offset is inherited from the currently active port */
int64_t port_latency_offset;
* thread context. */
pa_sink_cb_t update_requested_latency; /* may be NULL */
- /* Called whenever the port shall be changed. Called from IO
- * thread if deferred volumes are enabled, and main thread otherwise. */
+ /* Called whenever the port shall be changed. Called from the main
+ * thread. */
int (*set_port)(pa_sink *s, pa_device_port *port); /* may be NULL */
/* Called to get the list of formats supported by the sink, sorted
PA_SINK_MESSAGE_GET_MAX_REQUEST,
PA_SINK_MESSAGE_SET_MAX_REWIND,
PA_SINK_MESSAGE_SET_MAX_REQUEST,
- PA_SINK_MESSAGE_SET_PORT,
PA_SINK_MESSAGE_UPDATE_VOLUME_AND_MUTE,
PA_SINK_MESSAGE_SET_PORT_LATENCY_OFFSET,
PA_SINK_MESSAGE_MAX
bool pa_sink_update_proplist(pa_sink *s, pa_update_mode_t mode, pa_proplist *p);
int pa_sink_set_port(pa_sink *s, const char *name, bool save);
-void pa_sink_set_mixer_dirty(pa_sink *s, bool is_dirty);
unsigned pa_sink_linked_by(pa_sink *s); /* Number of connected streams */
unsigned pa_sink_used_by(pa_sink *s); /* Number of connected streams which are not corked */
PA_LLIST_FIELDS(pa_source_volume_change);
};
-struct source_message_set_port {
- pa_device_port *port;
- int ret;
-};
-
struct set_state_data {
pa_source_state_t state;
pa_suspend_cause_t suspend_cause;
s->flags = flags;
s->priority = 0;
s->suspend_cause = data->suspend_cause;
- pa_source_set_mixer_dirty(s, false);
s->name = pa_xstrdup(name);
s->proplist = pa_proplist_copy(data->proplist);
s->driver = pa_xstrdup(pa_path_get_filename(data->driver));
return source_set_state(s, pa_source_used_by(s) ? PA_SOURCE_RUNNING : PA_SOURCE_IDLE, 0);
}
-/* Called from any context - must be threadsafe */
-void pa_source_set_mixer_dirty(pa_source *s, bool is_dirty) {
- pa_atomic_store(&s->mixer_dirty, is_dirty ? 1 : 0);
-}
-
/* Called from main context */
int pa_source_suspend(pa_source *s, bool suspend, pa_suspend_cause_t cause) {
pa_suspend_cause_t merged_cause;
else
merged_cause = s->suspend_cause & ~cause;
- if (!(merged_cause & PA_SUSPEND_SESSION) && (pa_atomic_load(&s->mixer_dirty) != 0)) {
- /* This might look racy but isn't: If somebody sets mixer_dirty exactly here,
- it'll be handled just fine. */
- pa_source_set_mixer_dirty(s, false);
- pa_log_debug("Mixer is now accessible. Updating alsa mixer settings.");
- if (s->active_port && s->set_port) {
- if (s->flags & PA_SOURCE_DEFERRED_VOLUME) {
- struct source_message_set_port msg = { .port = s->active_port, .ret = 0 };
- pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_PORT, &msg, 0, NULL) == 0);
- }
- else
- s->set_port(s, s->active_port);
- }
- else {
- if (s->set_mute)
- s->set_mute(s);
- if (s->set_volume)
- s->set_volume(s);
- }
- }
-
if (merged_cause)
return source_set_state(s, PA_SOURCE_SUSPENDED, merged_cause);
else
/* Implementors need to overwrite this implementation! */
return -1;
- case PA_SOURCE_MESSAGE_SET_PORT:
-
- pa_assert(userdata);
- if (s->set_port) {
- struct source_message_set_port *msg_data = userdata;
- msg_data->ret = s->set_port(s, msg_data->port);
- }
- return 0;
-
case PA_SOURCE_MESSAGE_UPDATE_VOLUME_AND_MUTE:
/* This message is sent from IO-thread and handled in main thread. */
pa_assert_ctl_context();
/* Called from main context */
int pa_source_set_port(pa_source *s, const char *name, bool save) {
pa_device_port *port;
- int ret;
pa_source_assert_ref(s);
pa_assert_ctl_context();
return 0;
}
- if (s->flags & PA_SOURCE_DEFERRED_VOLUME) {
- struct source_message_set_port msg = { .port = port, .ret = 0 };
- pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_PORT, &msg, 0, NULL) == 0);
- ret = msg.ret;
- }
- else
- ret = s->set_port(s, port);
-
- if (ret < 0)
+ if (s->set_port(s, port) < 0)
return -PA_ERR_NOENTITY;
pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
pa_hashmap *ports;
pa_device_port *active_port;
- pa_atomic_t mixer_dirty;
/* The latency offset is inherited from the currently active port */
int64_t port_latency_offset;
* thread context. */
pa_source_cb_t update_requested_latency; /* may be NULL */
- /* Called whenever the port shall be changed. Called from IO
- * thread if deferred volumes are enabled, and main thread otherwise. */
+ /* Called whenever the port shall be changed. Called from the main
+ * thread. */
int (*set_port)(pa_source *s, pa_device_port *port); /*ditto */
/* Called to get the list of formats supported by the source, sorted
PA_SOURCE_MESSAGE_GET_FIXED_LATENCY,
PA_SOURCE_MESSAGE_GET_MAX_REWIND,
PA_SOURCE_MESSAGE_SET_MAX_REWIND,
- PA_SOURCE_MESSAGE_SET_PORT,
PA_SOURCE_MESSAGE_UPDATE_VOLUME_AND_MUTE,
PA_SOURCE_MESSAGE_SET_PORT_LATENCY_OFFSET,
PA_SOURCE_MESSAGE_MAX
bool pa_source_update_proplist(pa_source *s, pa_update_mode_t mode, pa_proplist *p);
int pa_source_set_port(pa_source *s, const char *name, bool save);
-void pa_source_set_mixer_dirty(pa_source *s, bool is_dirty);
int pa_source_reconfigure(pa_source *s, pa_sample_spec *spec, bool passthrough);