aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile.am1
-rw-r--r--src/mixer/pulse_mixer.c277
-rw-r--r--src/mixer_api.h1
-rw-r--r--src/output/pulse_plugin.c16
4 files changed, 295 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am
index 25478f810..2ca101720 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -489,6 +489,7 @@ endif
if HAVE_PULSE
OUTPUT_SRC += src/output/pulse_plugin.c
+MIXER_SRC += src/mixer/pulse_mixer.c
endif
if HAVE_SHOUT
diff --git a/src/mixer/pulse_mixer.c b/src/mixer/pulse_mixer.c
new file mode 100644
index 000000000..2568943af
--- /dev/null
+++ b/src/mixer/pulse_mixer.c
@@ -0,0 +1,277 @@
+#include "../output_api.h"
+#include "../mixer_api.h"
+
+#include <glib.h>
+#include <pulse/volume.h>
+#include <pulse/pulseaudio.h>
+
+struct pulse_mixer {
+ struct mixer base;
+ char *server;
+ char *sink;
+ char *output_name;
+ uint32_t index;
+ bool online;
+ struct pa_context *context;
+ struct pa_threaded_mainloop *mainloop;
+ struct pa_cvolume *volume;
+
+};
+
+static void
+sink_input_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");
+ return;
+ }
+
+ if (!i) {
+ g_debug("Sink input callback failure");
+ return;
+ }
+ g_debug("sink input cb %s, index %d ",i->name,i->index);
+ if(strcmp(i->name,pm->output_name)==0) {
+ pm->index=i->index;
+ pm->online=true;
+ *pm->volume=i->volume;
+ } else
+ g_debug("bad name");
+}
+
+static void
+sink_input_vol(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_vol");
+ return;
+ }
+
+ if (!i) {
+ g_debug("Sink input callback failure");
+ return;
+ }
+ g_debug("sink input vol %s, index %d ", i->name, i->index);
+ *pm->volume=i->volume;
+}
+
+static void
+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("pulse_mixer: 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->online =false;
+ else {
+ pa_operation *o;
+
+ if (!(o = pa_context_get_sink_input_info(pm->context, idx, sink_input_cb, pm))) {
+ g_debug("pulse_mixer: pa_context_get_sink_input_info() failed");
+ return;
+ }
+
+ pa_operation_unref(o);
+ }
+ break;
+ }
+}
+
+static void
+context_state_cb(pa_context *context, void *userdata)
+{
+ struct pulse_mixer *pm = userdata;
+ switch (pa_context_get_state(context)) {
+ case PA_CONTEXT_READY: {
+ pa_operation *o;
+
+ pa_context_set_subscribe_callback(context, subscribe_cb, pm);
+
+ if (!(o = pa_context_subscribe(context, (pa_subscription_mask_t)
+ (PA_SUBSCRIPTION_MASK_SINK_INPUT), NULL, NULL))) {
+ g_debug("pulse_mixer: pa_context_subscribe() failed");
+ return;
+ }
+ pa_operation_unref(o);
+
+
+ if (!(o = pa_context_get_sink_input_info_list(context, sink_input_cb, pm))) {
+ g_debug("pulse_mixer: 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;
+ }
+}
+
+
+static struct mixer *
+pulse_mixer_init(const struct config_param *param)
+{
+ struct pulse_mixer *pm = g_new(struct pulse_mixer,1);
+ mixer_init(&pm->base, &pulse_mixer);
+ pm->server = NULL;
+ pm->sink = NULL;
+ pm->context=NULL;
+ pm->mainloop=NULL;
+ pm->volume=NULL;
+ pm->output_name=NULL;
+ pm->index=0;
+ pm->online=false;
+
+ pm->volume = g_new(struct pa_cvolume,1);
+
+ pm->server = param != NULL
+ ? config_dup_block_string(param, "server", NULL) : NULL;
+ pm->sink = param != NULL
+ ? config_dup_block_string(param, "sink", NULL) : NULL;
+ pm->output_name = param != NULL
+ ? config_dup_block_string(param, "name", NULL) : NULL;
+
+
+ g_debug("pulse_mixer: init");
+
+ if(!(pm->mainloop = pa_threaded_mainloop_new())) {
+ g_debug("pulse_mixer: failed mainloop");
+ g_free(pm);
+ return NULL;
+ }
+
+ if(!(pm->context = pa_context_new(pa_threaded_mainloop_get_api(pm->mainloop),
+ "Mixer mpd"))) {
+ g_debug("pulse_mixer: failed context");
+ g_free(pm);
+ return NULL;
+ }
+
+ pa_context_set_state_callback(pm->context, context_state_cb, pm);
+
+ if (pa_context_connect(pm->context, pm->server,
+ (pa_context_flags_t)0, NULL) < 0) {
+ g_debug("pulse_mixer: context server fail");
+ g_free(pm);
+ return NULL;
+ }
+
+ pa_threaded_mainloop_lock(pm->mainloop);
+ if (pa_threaded_mainloop_start(pm->mainloop) < 0) {
+ g_debug("pulse_mixer: error start mainloop");
+ g_free(pm);
+ return NULL;
+ }
+
+ pa_threaded_mainloop_wait(pm->mainloop);
+
+ if (pa_context_get_state(pm->context) != PA_CONTEXT_READY) {
+ g_debug("pulse_mixer: error context not ready");
+ g_free(pm);
+ return NULL;
+ }
+
+ pa_threaded_mainloop_unlock(pm->mainloop);
+ return &pm->base ;
+
+}
+
+static void
+pulse_mixer_finish(struct mixer *data)
+{
+ struct pulse_mixer *pm = (struct pulse_mixer *) data;
+ pm->context = NULL;
+ pm->mainloop = NULL;
+ pm->volume = NULL;
+ pm->online = false;
+ g_free(pm);
+}
+
+static bool
+pulse_mixer_open(G_GNUC_UNUSED struct mixer *data)
+{
+ g_debug("pulse mixer open");
+ return true;
+}
+
+static void
+pulse_mixer_close(G_GNUC_UNUSED struct mixer *data)
+{
+ return;
+}
+
+static int
+pulse_mixer_get_volume(struct mixer *mixer)
+{
+ struct pulse_mixer *pm=(struct pulse_mixer *) mixer;
+ int ret;
+ pa_operation *o;
+
+ g_debug("pulse_mixer: get_volume %s",
+ pm->online == TRUE ? "online" : "offline");
+ if(pm->online) {
+ if (!(o = pa_context_get_sink_input_info(pm->context, pm->index,
+ sink_input_vol, pm))) {
+ g_debug("pa_context_get_sink_input_info() failed");
+ return false;
+ }
+ pa_operation_unref(o);
+
+ ret = (int)((100*(pa_cvolume_avg(pm->volume)+1))/PA_VOLUME_NORM);
+ g_debug("pulse_mixer: volume %d", ret);
+ return ret;
+ }
+
+ return false;
+}
+
+static bool
+pulse_mixer_set_volume(struct mixer *mixer, unsigned volume)
+{
+ struct pulse_mixer *pm=(struct pulse_mixer *) mixer;
+ pa_operation *o;
+ if (pm->online) {
+ pa_cvolume_set(pm->volume, (pm->volume)->channels,
+ (pa_volume_t)(volume)*PA_VOLUME_NORM/100+0.5);
+
+ if (!(o = pa_context_set_sink_input_volume(pm->context, pm->index,
+ pm->volume, NULL, NULL))) {
+ g_debug("pulse_mixer: pa_context_set_sink_input_volume() failed");
+ return false;
+ }
+
+ pa_operation_unref(o);
+ }
+
+ return true;
+}
+
+const struct mixer_plugin pulse_mixer = {
+ .init = pulse_mixer_init,
+ .finish = pulse_mixer_finish,
+ .open = pulse_mixer_open,
+ .close = pulse_mixer_close,
+ .get_volume = pulse_mixer_get_volume,
+ .set_volume = pulse_mixer_set_volume,
+};
diff --git a/src/mixer_api.h b/src/mixer_api.h
index c8f219d83..e80a6511f 100644
--- a/src/mixer_api.h
+++ b/src/mixer_api.h
@@ -27,6 +27,7 @@
extern const struct mixer_plugin alsa_mixer;
extern const struct mixer_plugin oss_mixer;
+extern const struct mixer_plugin pulse_mixer;
struct config_param;
diff --git a/src/output/pulse_plugin.c b/src/output/pulse_plugin.c
index a03275a22..25ef537d6 100644
--- a/src/output/pulse_plugin.c
+++ b/src/output/pulse_plugin.c
@@ -17,6 +17,7 @@
*/
#include "../output_api.h"
+#include "../mixer_api.h"
#include <glib.h>
#include <pulse/simple.h>
@@ -26,6 +27,7 @@
struct pulse_data {
const char *name;
+ struct mixer *mixer;
pa_simple *s;
char *server;
@@ -58,6 +60,7 @@ static void pulse_free_data(struct pulse_data *pd)
g_free(pd->server);
g_free(pd->sink);
g_free(pd);
+ mixer_free(pd->mixer);
}
static void *
@@ -73,6 +76,8 @@ pulse_init(G_GNUC_UNUSED const struct audio_format *audio_format,
pd->sink = param != NULL
? config_dup_block_string(param, "sink", NULL) : NULL;
+ pd->mixer=mixer_new(&pulse_mixer, param);
+
return pd;
}
@@ -83,6 +88,15 @@ static void pulse_finish(void *data)
pulse_free_data(pd);
}
+static struct mixer *
+pulse_get_mixer(void *data)
+{
+ struct pulse_data *pd = data;
+
+ return pd->mixer;
+}
+
+
static bool pulse_test_default_device(void)
{
pa_simple *s;
@@ -131,6 +145,7 @@ pulse_open(void *data, struct audio_format *audio_format, GError **error_r)
pa_strerror(error));
return false;
}
+ mixer_open(pd->mixer);
return true;
}
@@ -173,6 +188,7 @@ const struct audio_output_plugin pulse_plugin = {
.test_default_device = pulse_test_default_device,
.init = pulse_init,
.finish = pulse_finish,
+ .get_mixer = pulse_get_mixer,
.open = pulse_open,
.play = pulse_play,
.cancel = pulse_cancel,