diff options
Diffstat (limited to 'src/mixer')
-rw-r--r-- | src/mixer/pulse_mixer_plugin.c | 335 |
1 files changed, 126 insertions, 209 deletions
diff --git a/src/mixer/pulse_mixer_plugin.c b/src/mixer/pulse_mixer_plugin.c index ecc0fc75b..b33ef80ae 100644 --- a/src/mixer/pulse_mixer_plugin.c +++ b/src/mixer/pulse_mixer_plugin.c @@ -18,12 +18,20 @@ */ #include "mixer_api.h" +#include "output/pulse_output_plugin.h" #include "conf.h" +#include "event_pipe.h" #include <glib.h> -#include <pulse/volume.h> -#include <pulse/pulseaudio.h> +#include <pulse/thread-mainloop.h> +#include <pulse/context.h> +#include <pulse/introspect.h> +#include <pulse/stream.h> +#include <pulse/subscribe.h> +#include <pulse/error.h> + +#include <assert.h> #include <string.h> #undef G_LOG_DOMAIN @@ -32,15 +40,9 @@ struct pulse_mixer { struct mixer base; - const char *server; - const char *sink; - const char *output_name; + struct pulse_output *output; - uint32_t index; bool online; - - struct pa_context *context; - struct pa_threaded_mainloop *mainloop; struct pa_cvolume volume; }; @@ -54,175 +56,159 @@ pulse_mixer_quark(void) return g_quark_from_static_string("pulse_mixer"); } -/** - * \brief waits for a pulseaudio operation to finish, frees it and - * unlocks the mainloop - * \param operation the operation to wait for - * \return true if operation has finished normally (DONE state), - * false otherwise - */ -static bool -pulse_wait_for_operation(struct pa_threaded_mainloop *mainloop, - struct pa_operation *operation) +static void +pulse_mixer_offline(struct pulse_mixer *pm) { - pa_operation_state_t state; - - assert(mainloop != NULL); - assert(operation != NULL); - - state = pa_operation_get_state(operation); - while (state == PA_OPERATION_RUNNING) { - pa_threaded_mainloop_wait(mainloop); - state = pa_operation_get_state(operation); - } + if (!pm->online) + return; - pa_operation_unref(operation); + pm->online = false; - return state == PA_OPERATION_DONE; + event_pipe_emit(PIPE_EVENT_MIXER); } +/** + * Callback invoked by pulse_mixer_update(). Receives the new mixer + * value. + */ static void -sink_input_cb(G_GNUC_UNUSED pa_context *context, const pa_sink_input_info *i, - int eol, void *userdata) +pulse_mixer_volume_cb(G_GNUC_UNUSED pa_context *context, const pa_sink_input_info *i, + int eol, void *userdata) { - struct pulse_mixer *pm = userdata; - if (eol) { - g_debug("eol error sink_input_cb"); + if (eol) return; - } if (i == NULL) { - g_debug("Sink input callback failure"); + pulse_mixer_offline(pm); return; } - g_debug("sink input cb %s, index %d ",i->name,i->index); + pm->online = true; + pm->volume = i->volume; - if (strcmp(i->name,pm->output_name) == 0) { - pm->index = i->index; - pm->online = true; - pm->volume = i->volume; - } else - g_debug("bad name"); + event_pipe_emit(PIPE_EVENT_MIXER); } static void -sink_input_vol(G_GNUC_UNUSED pa_context *context, const pa_sink_input_info *i, - int eol, void *userdata) +pulse_mixer_update(struct pulse_mixer *pm) { + pa_operation *o; - struct pulse_mixer *pm = userdata; + assert(pm->output->stream != NULL); - if (eol) { - g_debug("eol error sink_input_vol"); + if (pm->output->context == NULL) return; - } - if (i == NULL) { - g_debug("Sink input callback failure"); + o = pa_context_get_sink_input_info(pm->output->context, + pa_stream_get_index(pm->output->stream), + pulse_mixer_volume_cb, pm); + if (o == NULL) { + g_warning("pa_context_get_sink_input_info() failed: %s", + pa_strerror(pa_context_errno(pm->output->context))); + pulse_mixer_offline(pm); return; } - g_debug("sink input vol %s, index %d ", i->name, i->index); + pa_operation_unref(o); +} - pm->volume = i->volume; +static void +pulse_mixer_handle_sink_input(struct pulse_mixer *pm, + pa_subscription_event_type_t t, + uint32_t idx) +{ + if (pm->output->stream == NULL) { + pulse_mixer_offline(pm); + return; + } + + if (idx != pa_stream_get_index(pm->output->stream)) + return; - pa_threaded_mainloop_signal(pm->mainloop, 0); + if (t == PA_SUBSCRIPTION_EVENT_NEW || + t == PA_SUBSCRIPTION_EVENT_CHANGE) + pulse_mixer_update(pm); } static void -subscribe_cb(pa_context *c, pa_subscription_event_type_t t, +pulse_mixer_subscribe_cb(G_GNUC_UNUSED pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { - struct pulse_mixer *pm = userdata; - g_debug("subscribe call back"); - switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { case PA_SUBSCRIPTION_EVENT_SINK_INPUT: - if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == - PA_SUBSCRIPTION_EVENT_REMOVE && - pm->index == idx) - pm->online = false; - else { - pa_operation *o; - - o = pa_context_get_sink_input_info(c, idx, - sink_input_cb, pm); - if (o == NULL) { - g_debug("pa_context_get_sink_input_info() failed"); - return; - } - - pa_operation_unref(o); - } - + pulse_mixer_handle_sink_input(pm, + t & PA_SUBSCRIPTION_EVENT_TYPE_MASK, + idx); break; } } static void -context_state_cb(pa_context *context, void *userdata) +pulxe_mixer_context_state_cb(pa_context *context, void *userdata) { struct pulse_mixer *pm = userdata; + pa_operation *o; - switch (pa_context_get_state(context)) { - case PA_CONTEXT_READY: { - pa_operation *o; + /* pass event to the output's callback function */ + pulse_output_context_state_cb(context, pm->output); - pa_context_set_subscribe_callback(context, subscribe_cb, pm); + if (pa_context_get_state(context) == PA_CONTEXT_READY) { + /* subscribe to sink_input events after the connection + has been established */ o = pa_context_subscribe(context, (pa_subscription_mask_t)PA_SUBSCRIPTION_MASK_SINK_INPUT, NULL, NULL); if (o == NULL) { - g_debug("pa_context_subscribe() failed"); + g_warning("pa_context_subscribe() failed: %s", + pa_strerror(pa_context_errno(context))); return; } pa_operation_unref(o); - o = pa_context_get_sink_input_info_list(context, - sink_input_cb, pm); - if (o == NULL) { - g_debug("pa_context_get_sink_input_info_list() failed"); - return; - } - - pa_operation_unref(o); - - pa_threaded_mainloop_signal(pm->mainloop, 0); - break; - } - - case PA_CONTEXT_UNCONNECTED: - case PA_CONTEXT_CONNECTING: - case PA_CONTEXT_AUTHORIZING: - case PA_CONTEXT_SETTING_NAME: - break; - case PA_CONTEXT_TERMINATED: - case PA_CONTEXT_FAILED: - pa_threaded_mainloop_signal(pm->mainloop, 0); - break; + if (pm->output->stream != NULL) + pulse_mixer_update(pm); } } - static struct mixer * -pulse_mixer_init(G_GNUC_UNUSED void *ao, const struct config_param *param, - G_GNUC_UNUSED GError **error_r) +pulse_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param, + GError **error_r) { - struct pulse_mixer *pm = g_new(struct pulse_mixer,1); + struct pulse_mixer *pm; + + if (ao == NULL) { + g_set_error(error_r, pulse_mixer_quark(), 0, + "The pulse mixer cannot work without the audio output"); + return false; + } + + pm = g_new(struct pulse_mixer,1); mixer_init(&pm->base, &pulse_mixer_plugin); + pm->output = ao; pm->online = false; - pm->server = config_get_block_string(param, "server", NULL); - pm->sink = config_get_block_string(param, "sink", NULL); - pm->output_name = config_get_block_string(param, "name", NULL); + pa_threaded_mainloop_lock(pm->output->mainloop); + + /* register callbacks (override the output's context state + callback) */ + + pa_context_set_state_callback(pm->output->context, + pulxe_mixer_context_state_cb, pm); + pa_context_set_subscribe_callback(pm->output->context, + pulse_mixer_subscribe_cb, pm); + + /* check the current state now (we might have missed the first + events!) */ + pulxe_mixer_context_state_cb(pm->output->context, pm); + + pa_threaded_mainloop_unlock(pm->output->mainloop); return &pm->base; } @@ -232,79 +218,35 @@ pulse_mixer_finish(struct mixer *data) { struct pulse_mixer *pm = (struct pulse_mixer *) data; - g_free(pm); -} + /* restore callbacks */ -static bool -pulse_mixer_setup(struct pulse_mixer *pm, GError **error_r) -{ - pa_context_set_state_callback(pm->context, context_state_cb, pm); + pa_threaded_mainloop_lock(pm->output->mainloop); - if (pa_context_connect(pm->context, pm->server, - (pa_context_flags_t)0, NULL) < 0) { - g_set_error(error_r, pulse_mixer_quark(), 0, - "pa_context_connect() has failed"); - return false; + if (pm->output->context != NULL) { + pa_context_set_state_callback(pm->output->context, + pulse_output_context_state_cb, + pm->output); + pa_context_set_subscribe_callback(pm->output->context, + NULL, NULL); } - pa_threaded_mainloop_lock(pm->mainloop); + pa_threaded_mainloop_unlock(pm->output->mainloop); - if (pa_threaded_mainloop_start(pm->mainloop) < 0) { - pa_threaded_mainloop_unlock(pm->mainloop); - g_set_error(error_r, pulse_mixer_quark(), 0, - "pa_threaded_mainloop_start() has failed"); - return false; - } - - pa_threaded_mainloop_wait(pm->mainloop); - - if (pa_context_get_state(pm->context) != PA_CONTEXT_READY) { - g_set_error(error_r, pulse_mixer_quark(), 0, - "failed to connect: %s", - pa_strerror(pa_context_errno(pm->context))); - pa_threaded_mainloop_unlock(pm->mainloop); - return false; - } + /* free resources */ - pa_threaded_mainloop_unlock(pm->mainloop); - - return true; + g_free(pm); } static bool -pulse_mixer_open(struct mixer *data, GError **error_r) +pulse_mixer_open(struct mixer *data, G_GNUC_UNUSED GError **error_r) { struct pulse_mixer *pm = (struct pulse_mixer *) data; - g_debug("pulse mixer open"); - - pm->index = 0; - pm->online = false; - - pm->mainloop = pa_threaded_mainloop_new(); - if (pm->mainloop == NULL) { - g_set_error(error_r, pulse_mixer_quark(), 0, - "pa_threaded_mainloop_new() has failed"); - return false; - } - - pm->context = pa_context_new(pa_threaded_mainloop_get_api(pm->mainloop), - "Mixer mpd"); - if (pm->context == NULL) { - pa_threaded_mainloop_stop(pm->mainloop); - pa_threaded_mainloop_free(pm->mainloop); - g_set_error(error_r, pulse_mixer_quark(), 0, - "pa_context_new() has failed"); - return false; - } - - if (!pulse_mixer_setup(pm, error_r)) { - pa_threaded_mainloop_stop(pm->mainloop); - pa_context_disconnect(pm->context); - pa_context_unref(pm->context); - pa_threaded_mainloop_free(pm->mainloop); - return false; - } + pa_threaded_mainloop_lock(pm->output->mainloop); + if (pm->output->stream != NULL && + pa_stream_get_state(pm->output->stream) == PA_STREAM_READY) + pulse_mixer_update(pm); + pa_threaded_mainloop_unlock(pm->output->mainloop); return true; } @@ -314,49 +256,22 @@ pulse_mixer_close(struct mixer *data) { struct pulse_mixer *pm = (struct pulse_mixer *) data; - pa_threaded_mainloop_stop(pm->mainloop); - pa_context_disconnect(pm->context); - pa_context_unref(pm->context); - pa_threaded_mainloop_free(pm->mainloop); - - pm->online = false; + pulse_mixer_offline(pm); } static int -pulse_mixer_get_volume(struct mixer *mixer, GError **error_r) +pulse_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r) { struct pulse_mixer *pm = (struct pulse_mixer *) mixer; int ret; - pa_operation *o; - pa_threaded_mainloop_lock(pm->mainloop); - - if (!pm->online) { - pa_threaded_mainloop_unlock(pm->mainloop); - return false; - } - - o = pa_context_get_sink_input_info(pm->context, pm->index, - sink_input_vol, pm); - if (o == NULL) { - pa_threaded_mainloop_unlock(pm->mainloop); - g_set_error(error_r, pulse_mixer_quark(), 0, - "pa_context_get_sink_input_info() has failed"); - return false; - } - - if (!pulse_wait_for_operation(pm->mainloop, o)) { - pa_threaded_mainloop_unlock(pm->mainloop); - g_set_error(error_r, pulse_mixer_quark(), 0, - "failed to read PulseAudio volume"); - return false; - } + pa_threaded_mainloop_lock(pm->output->mainloop); ret = pm->online ? (int)((100*(pa_cvolume_avg(&pm->volume)+1))/PA_VOLUME_NORM) : -1; - pa_threaded_mainloop_unlock(pm->mainloop); + pa_threaded_mainloop_unlock(pm->output->mainloop); return ret; } @@ -368,10 +283,11 @@ pulse_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) struct pa_cvolume cvolume; pa_operation *o; - pa_threaded_mainloop_lock(pm->mainloop); + pa_threaded_mainloop_lock(pm->output->mainloop); - if (!pm->online) { - pa_threaded_mainloop_unlock(pm->mainloop); + if (!pm->online || pm->output->stream == NULL || + pm->output->context == NULL) { + pa_threaded_mainloop_unlock(pm->output->mainloop); g_set_error(error_r, pulse_mixer_quark(), 0, "disconnected"); return false; } @@ -379,9 +295,10 @@ pulse_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) pa_cvolume_set(&cvolume, pm->volume.channels, (pa_volume_t)volume * PA_VOLUME_NORM / 100 + 0.5); - o = pa_context_set_sink_input_volume(pm->context, pm->index, + o = pa_context_set_sink_input_volume(pm->output->context, + pa_stream_get_index(pm->output->stream), &cvolume, NULL, NULL); - pa_threaded_mainloop_unlock(pm->mainloop); + pa_threaded_mainloop_unlock(pm->output->mainloop); if (o == NULL) { g_set_error(error_r, pulse_mixer_quark(), 0, "failed to set PulseAudio volume"); |