aboutsummaryrefslogtreecommitdiffstats
path: root/src/mixer
diff options
context:
space:
mode:
Diffstat (limited to 'src/mixer')
-rw-r--r--src/mixer/alsa_mixer_plugin.c (renamed from src/mixer/alsa_mixer.c)64
-rw-r--r--src/mixer/oss_mixer_plugin.c (renamed from src/mixer/oss_mixer.c)55
-rw-r--r--src/mixer/pulse_mixer.c382
-rw-r--r--src/mixer/pulse_mixer_plugin.c234
-rw-r--r--src/mixer/pulse_mixer_plugin.h39
-rw-r--r--src/mixer/software_mixer_plugin.c109
-rw-r--r--src/mixer/software_mixer_plugin.h33
7 files changed, 494 insertions, 422 deletions
diff --git a/src/mixer/alsa_mixer.c b/src/mixer/alsa_mixer_plugin.c
index 52e553cc5..6726f785a 100644
--- a/src/mixer/alsa_mixer.c
+++ b/src/mixer/alsa_mixer_plugin.c
@@ -17,8 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../output_api.h"
-#include "../mixer_api.h"
+#include "config.h"
+#include "mixer_api.h"
+#include "output_api.h"
#include <glib.h>
#include <alsa/asoundlib.h>
@@ -42,12 +43,22 @@ struct alsa_mixer {
int volume_set;
};
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+alsa_mixer_quark(void)
+{
+ return g_quark_from_static_string("alsa_mixer");
+}
+
static struct mixer *
-alsa_mixer_init(const struct config_param *param)
+alsa_mixer_init(G_GNUC_UNUSED void *ao, const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
{
struct alsa_mixer *am = g_new(struct alsa_mixer, 1);
- mixer_init(&am->base, &alsa_mixer);
+ mixer_init(&am->base, &alsa_mixer_plugin);
am->device = config_get_block_string(param, "mixer_device",
VOLUME_MIXER_ALSA_DEFAULT);
@@ -81,7 +92,7 @@ alsa_mixer_close(struct mixer *data)
}
static bool
-alsa_mixer_open(struct mixer *data)
+alsa_mixer_open(struct mixer *data, GError **error_r)
{
struct alsa_mixer *am = (struct alsa_mixer *)data;
int err;
@@ -91,29 +102,33 @@ alsa_mixer_open(struct mixer *data)
err = snd_mixer_open(&am->handle, 0);
if (err < 0) {
- g_warning("problems opening alsa mixer: %s\n", snd_strerror(err));
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "snd_mixer_open() failed: %s", snd_strerror(err));
return false;
}
if ((err = snd_mixer_attach(am->handle, am->device)) < 0) {
- g_warning("problems attaching alsa mixer: %s\n",
- snd_strerror(err));
alsa_mixer_close(data);
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "failed to attach to %s: %s",
+ am->device, snd_strerror(err));
return false;
}
if ((err = snd_mixer_selem_register(am->handle, NULL,
NULL)) < 0) {
- g_warning("problems snd_mixer_selem_register'ing: %s\n",
- snd_strerror(err));
alsa_mixer_close(data);
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "snd_mixer_selem_register() failed: %s",
+ snd_strerror(err));
return false;
}
if ((err = snd_mixer_load(am->handle)) < 0) {
- g_warning("problems snd_mixer_selem_register'ing: %s\n",
- snd_strerror(err));
alsa_mixer_close(data);
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "snd_mixer_load() failed: %s\n",
+ snd_strerror(err));
return false;
}
@@ -138,14 +153,14 @@ alsa_mixer_open(struct mixer *data)
return true;
}
- g_warning("can't find alsa mixer control \"%s\"\n", am->control);
-
alsa_mixer_close(data);
+ g_set_error(error_r, alsa_mixer_quark(), 0,
+ "no such mixer control: %s", am->control);
return false;
}
static int
-alsa_mixer_get_volume(struct mixer *mixer)
+alsa_mixer_get_volume(struct mixer *mixer, GError **error_r)
{
struct alsa_mixer *am = (struct alsa_mixer *)mixer;
int err;
@@ -156,8 +171,9 @@ alsa_mixer_get_volume(struct mixer *mixer)
err = snd_mixer_handle_events(am->handle);
if (err < 0) {
- g_warning("problems getting alsa volume: %s (snd_mixer_%s)\n",
- snd_strerror(err), "handle_events");
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "snd_mixer_handle_events() failed: %s",
+ snd_strerror(err));
return false;
}
@@ -165,8 +181,9 @@ alsa_mixer_get_volume(struct mixer *mixer)
SND_MIXER_SCHN_FRONT_LEFT,
&level);
if (err < 0) {
- g_warning("problems getting alsa volume: %s (snd_mixer_%s)\n",
- snd_strerror(err), "selem_get_playback_volume");
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "failed to read ALSA volume: %s",
+ snd_strerror(err));
return false;
}
@@ -183,7 +200,7 @@ alsa_mixer_get_volume(struct mixer *mixer)
}
static bool
-alsa_mixer_set_volume(struct mixer *mixer, unsigned volume)
+alsa_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
{
struct alsa_mixer *am = (struct alsa_mixer *)mixer;
float vol;
@@ -203,15 +220,16 @@ alsa_mixer_set_volume(struct mixer *mixer, unsigned volume)
err = snd_mixer_selem_set_playback_volume_all(am->elem, level);
if (err < 0) {
- g_warning("problems setting alsa volume: %s\n",
- snd_strerror(err));
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "failed to set ALSA volume: %s",
+ snd_strerror(err));
return false;
}
return true;
}
-const struct mixer_plugin alsa_mixer = {
+const struct mixer_plugin alsa_mixer_plugin = {
.init = alsa_mixer_init,
.finish = alsa_mixer_finish,
.open = alsa_mixer_open,
diff --git a/src/mixer/oss_mixer.c b/src/mixer/oss_mixer_plugin.c
index f2db01ff4..6e75edd9b 100644
--- a/src/mixer/oss_mixer.c
+++ b/src/mixer/oss_mixer_plugin.c
@@ -17,8 +17,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../output_api.h"
-#include "../mixer_api.h"
+#include "config.h"
+#include "mixer_api.h"
+#include "output_api.h"
+#include "fd_util.h"
#include <glib.h>
@@ -49,6 +51,15 @@ struct oss_mixer {
int volume_control;
};
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+oss_mixer_quark(void)
+{
+ return g_quark_from_static_string("oss_mixer");
+}
+
static int
oss_find_mixer(const char *name)
{
@@ -65,11 +76,12 @@ oss_find_mixer(const char *name)
}
static struct mixer *
-oss_mixer_init(const struct config_param *param)
+oss_mixer_init(G_GNUC_UNUSED void *ao, const struct config_param *param,
+ GError **error_r)
{
struct oss_mixer *om = g_new(struct oss_mixer, 1);
- mixer_init(&om->base, &oss_mixer);
+ mixer_init(&om->base, &oss_mixer_plugin);
om->device = config_get_block_string(param, "mixer_device",
VOLUME_MIXER_OSS_DEFAULT);
@@ -78,9 +90,9 @@ oss_mixer_init(const struct config_param *param)
if (om->control != NULL) {
om->volume_control = oss_find_mixer(om->control);
if (om->volume_control < 0) {
- g_warning("mixer control \"%s\" not found",
- om->control);
g_free(om);
+ g_set_error(error_r, oss_mixer_quark(), 0,
+ "no such mixer control: %s", om->control);
return NULL;
}
} else
@@ -108,13 +120,15 @@ oss_mixer_close(struct mixer *data)
}
static bool
-oss_mixer_open(struct mixer *data)
+oss_mixer_open(struct mixer *data, GError **error_r)
{
struct oss_mixer *om = (struct oss_mixer *) data;
- om->device_fd = open(om->device, O_RDONLY);
+ om->device_fd = open_cloexec(om->device, O_RDONLY, 0);
if (om->device_fd < 0) {
- g_warning("Unable to open oss mixer \"%s\"\n", om->device);
+ g_set_error(error_r, oss_mixer_quark(), errno,
+ "failed to open %s: %s",
+ om->device, g_strerror(errno));
return false;
}
@@ -122,14 +136,17 @@ oss_mixer_open(struct mixer *data)
int devmask = 0;
if (ioctl(om->device_fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) {
- g_warning("errors getting read_devmask for oss mixer\n");
+ g_set_error(error_r, oss_mixer_quark(), errno,
+ "READ_DEVMASK failed: %s",
+ g_strerror(errno));
oss_mixer_close(data);
return false;
}
if (((1 << om->volume_control) & devmask) == 0) {
- g_warning("mixer control \"%s\" not usable\n",
- om->control);
+ g_set_error(error_r, oss_mixer_quark(), 0,
+ "mixer control \"%s\" not usable",
+ om->control);
oss_mixer_close(data);
return false;
}
@@ -138,7 +155,7 @@ oss_mixer_open(struct mixer *data)
}
static int
-oss_mixer_get_volume(struct mixer *mixer)
+oss_mixer_get_volume(struct mixer *mixer, GError **error_r)
{
struct oss_mixer *om = (struct oss_mixer *)mixer;
int left, right, level;
@@ -148,7 +165,9 @@ oss_mixer_get_volume(struct mixer *mixer)
ret = ioctl(om->device_fd, MIXER_READ(om->volume_control), &level);
if (ret < 0) {
- g_warning("unable to read oss volume\n");
+ g_set_error(error_r, oss_mixer_quark(), errno,
+ "failed to read OSS volume: %s",
+ g_strerror(errno));
return false;
}
@@ -164,7 +183,7 @@ oss_mixer_get_volume(struct mixer *mixer)
}
static bool
-oss_mixer_set_volume(struct mixer *mixer, unsigned volume)
+oss_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
{
struct oss_mixer *om = (struct oss_mixer *)mixer;
int level;
@@ -177,14 +196,16 @@ oss_mixer_set_volume(struct mixer *mixer, unsigned volume)
ret = ioctl(om->device_fd, MIXER_WRITE(om->volume_control), &level);
if (ret < 0) {
- g_warning("unable to set oss volume\n");
+ g_set_error(error_r, oss_mixer_quark(), errno,
+ "failed to set OSS volume: %s",
+ g_strerror(errno));
return false;
}
return true;
}
-const struct mixer_plugin oss_mixer = {
+const struct mixer_plugin oss_mixer_plugin = {
.init = oss_mixer_init,
.finish = oss_mixer_finish,
.open = oss_mixer_open,
diff --git a/src/mixer/pulse_mixer.c b/src/mixer/pulse_mixer.c
deleted file mode 100644
index 5d9ce0475..000000000
--- a/src/mixer/pulse_mixer.c
+++ /dev/null
@@ -1,382 +0,0 @@
-/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "mixer_api.h"
-#include "conf.h"
-
-#include <glib.h>
-#include <pulse/volume.h>
-#include <pulse/pulseaudio.h>
-
-#include <string.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "pulse_mixer"
-
-struct pulse_mixer {
- struct mixer base;
-
- const char *server;
- const char *sink;
- const char *output_name;
-
- uint32_t index;
- bool online;
-
- struct pa_context *context;
- struct pa_threaded_mainloop *mainloop;
- struct pa_cvolume volume;
-
-};
-
-/**
- * \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)
-{
- 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);
- }
-
- pa_operation_unref(operation);
-
- return state == PA_OPERATION_DONE;
-}
-
-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 == NULL) {
- 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 == NULL) {
- g_debug("Sink input callback failure");
- return;
- }
-
- g_debug("sink input vol %s, index %d ", i->name, i->index);
-
- pm->volume = i->volume;
-
- pa_threaded_mainloop_signal(pm->mainloop, 0);
-}
-
-static void
-subscribe_cb(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);
- }
-
- 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);
-
- 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");
- 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;
- }
-}
-
-
-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->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);
-
- return &pm->base;
-}
-
-static void
-pulse_mixer_finish(struct mixer *data)
-{
- struct pulse_mixer *pm = (struct pulse_mixer *) data;
-
- g_free(pm);
-}
-
-static bool
-pulse_mixer_setup(struct pulse_mixer *pm)
-{
- 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("context server fail");
- return false;
- }
-
- pa_threaded_mainloop_lock(pm->mainloop);
-
- if (pa_threaded_mainloop_start(pm->mainloop) < 0) {
- pa_threaded_mainloop_unlock(pm->mainloop);
- g_debug("error start mainloop");
- return false;
- }
-
- pa_threaded_mainloop_wait(pm->mainloop);
-
- if (pa_context_get_state(pm->context) != PA_CONTEXT_READY) {
- pa_threaded_mainloop_unlock(pm->mainloop);
- g_debug("error context not ready");
- return false;
- }
-
- pa_threaded_mainloop_unlock(pm->mainloop);
-
- return true;
-}
-
-static bool
-pulse_mixer_open(struct mixer *data)
-{
- 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_debug("failed mainloop");
- 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_debug("failed context");
- return false;
- }
-
- if (!pulse_mixer_setup(pm)) {
- pa_threaded_mainloop_stop(pm->mainloop);
- pa_context_disconnect(pm->context);
- pa_context_unref(pm->context);
- pa_threaded_mainloop_free(pm->mainloop);
- return false;
- }
-
- return true;
-}
-
-static void
-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;
-}
-
-static int
-pulse_mixer_get_volume(struct mixer *mixer)
-{
- 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_debug("pa_context_get_sink_input_info() failed");
- return false;
- }
-
- if (!pulse_wait_for_operation(pm->mainloop, o)) {
- pa_threaded_mainloop_unlock(pm->mainloop);
- return false;
- }
-
- ret = pm->online
- ? (int)((100*(pa_cvolume_avg(&pm->volume)+1))/PA_VOLUME_NORM)
- : -1;
-
- pa_threaded_mainloop_unlock(pm->mainloop);
-
- return ret;
-}
-
-static bool
-pulse_mixer_set_volume(struct mixer *mixer, unsigned volume)
-{
- struct pulse_mixer *pm = (struct pulse_mixer *) mixer;
- struct pa_cvolume cvolume;
- pa_operation *o;
-
- pa_threaded_mainloop_lock(pm->mainloop);
-
- if (!pm->online) {
- pa_threaded_mainloop_unlock(pm->mainloop);
- return false;
- }
-
- 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,
- &cvolume, NULL, NULL);
- pa_threaded_mainloop_unlock(pm->mainloop);
- if (o == NULL) {
- g_debug("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/pulse_mixer_plugin.c b/src/mixer/pulse_mixer_plugin.c
new file mode 100644
index 000000000..5669e05c4
--- /dev/null
+++ b/src/mixer/pulse_mixer_plugin.c
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "pulse_mixer_plugin.h"
+#include "mixer_api.h"
+#include "output/pulse_output_plugin.h"
+#include "conf.h"
+#include "event_pipe.h"
+
+#include <glib.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
+#define G_LOG_DOMAIN "pulse_mixer"
+
+struct pulse_mixer {
+ struct mixer base;
+
+ struct pulse_output *output;
+
+ bool online;
+ struct pa_cvolume volume;
+
+};
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+pulse_mixer_quark(void)
+{
+ return g_quark_from_static_string("pulse_mixer");
+}
+
+static void
+pulse_mixer_offline(struct pulse_mixer *pm)
+{
+ if (!pm->online)
+ return;
+
+ pm->online = false;
+
+ event_pipe_emit(PIPE_EVENT_MIXER);
+}
+
+/**
+ * Callback invoked by pulse_mixer_update(). Receives the new mixer
+ * value.
+ */
+static void
+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)
+ return;
+
+ if (i == NULL) {
+ pulse_mixer_offline(pm);
+ return;
+ }
+
+ pm->online = true;
+ pm->volume = i->volume;
+
+ event_pipe_emit(PIPE_EVENT_MIXER);
+}
+
+static void
+pulse_mixer_update(struct pulse_mixer *pm,
+ struct pa_context *context, struct pa_stream *stream)
+{
+ pa_operation *o;
+
+ assert(context != NULL);
+ assert(stream != NULL);
+ assert(pa_stream_get_state(stream) == PA_STREAM_READY);
+
+ o = pa_context_get_sink_input_info(context,
+ pa_stream_get_index(stream),
+ pulse_mixer_volume_cb, pm);
+ if (o == NULL) {
+ g_warning("pa_context_get_sink_input_info() failed: %s",
+ pa_strerror(pa_context_errno(context)));
+ pulse_mixer_offline(pm);
+ return;
+ }
+
+ pa_operation_unref(o);
+}
+
+void
+pulse_mixer_on_connect(G_GNUC_UNUSED struct pulse_mixer *pm,
+ struct pa_context *context)
+{
+ pa_operation *o;
+
+ assert(context != NULL);
+
+ o = pa_context_subscribe(context,
+ (pa_subscription_mask_t)PA_SUBSCRIPTION_MASK_SINK_INPUT,
+ NULL, NULL);
+ if (o == NULL) {
+ g_warning("pa_context_subscribe() failed: %s",
+ pa_strerror(pa_context_errno(context)));
+ return;
+ }
+
+ pa_operation_unref(o);
+}
+
+void
+pulse_mixer_on_disconnect(struct pulse_mixer *pm)
+{
+ pulse_mixer_offline(pm);
+}
+
+void
+pulse_mixer_on_change(struct pulse_mixer *pm,
+ struct pa_context *context, struct pa_stream *stream)
+{
+ pulse_mixer_update(pm, context, stream);
+}
+
+static struct mixer *
+pulse_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param,
+ GError **error_r)
+{
+ struct pulse_mixer *pm;
+ struct pulse_output *po = ao;
+
+ 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->online = false;
+ pm->output = po;
+
+ pulse_output_set_mixer(po, pm);
+
+ return &pm->base;
+}
+
+static void
+pulse_mixer_finish(struct mixer *data)
+{
+ struct pulse_mixer *pm = (struct pulse_mixer *) data;
+
+ pulse_output_clear_mixer(pm->output, pm);
+
+ /* free resources */
+
+ g_free(pm);
+}
+
+static int
+pulse_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r)
+{
+ struct pulse_mixer *pm = (struct pulse_mixer *) mixer;
+ int ret;
+
+ 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->output->mainloop);
+
+ return ret;
+}
+
+static bool
+pulse_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
+{
+ struct pulse_mixer *pm = (struct pulse_mixer *) mixer;
+ struct pa_cvolume cvolume;
+ bool success;
+
+ pa_threaded_mainloop_lock(pm->output->mainloop);
+ if (!pm->online) {
+ pa_threaded_mainloop_unlock(pm->output->mainloop);
+ g_set_error(error_r, pulse_mixer_quark(), 0, "disconnected");
+ return false;
+ }
+
+ pa_cvolume_set(&cvolume, pm->volume.channels,
+ (pa_volume_t)volume * PA_VOLUME_NORM / 100 + 0.5);
+ success = pulse_output_set_volume(pm->output, &cvolume, error_r);
+ if (success)
+ pm->volume = cvolume;
+ pa_threaded_mainloop_unlock(pm->output->mainloop);
+
+ return success;
+}
+
+const struct mixer_plugin pulse_mixer_plugin = {
+ .init = pulse_mixer_init,
+ .finish = pulse_mixer_finish,
+ .get_volume = pulse_mixer_get_volume,
+ .set_volume = pulse_mixer_set_volume,
+};
diff --git a/src/mixer/pulse_mixer_plugin.h b/src/mixer/pulse_mixer_plugin.h
new file mode 100644
index 000000000..2318b94be
--- /dev/null
+++ b/src/mixer/pulse_mixer_plugin.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PULSE_MIXER_PLUGIN_H
+#define MPD_PULSE_MIXER_PLUGIN_H
+
+#include <pulse/def.h>
+
+struct pulse_mixer;
+struct pa_context;
+struct pa_stream;
+
+void
+pulse_mixer_on_connect(struct pulse_mixer *pm, struct pa_context *context);
+
+void
+pulse_mixer_on_disconnect(struct pulse_mixer *pm);
+
+void
+pulse_mixer_on_change(struct pulse_mixer *pm,
+ struct pa_context *context, struct pa_stream *stream);
+
+#endif
diff --git a/src/mixer/software_mixer_plugin.c b/src/mixer/software_mixer_plugin.c
new file mode 100644
index 000000000..30ae13013
--- /dev/null
+++ b/src/mixer/software_mixer_plugin.c
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "software_mixer_plugin.h"
+#include "mixer_api.h"
+#include "filter_plugin.h"
+#include "filter_registry.h"
+#include "filter/volume_filter_plugin.h"
+#include "pcm_volume.h"
+
+#include <assert.h>
+#include <math.h>
+
+struct software_mixer {
+ /** the base mixer class */
+ struct mixer base;
+
+ struct filter *filter;
+
+ unsigned volume;
+};
+
+static struct mixer *
+software_mixer_init(G_GNUC_UNUSED void *ao,
+ G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct software_mixer *sm = g_new(struct software_mixer, 1);
+
+ mixer_init(&sm->base, &software_mixer_plugin);
+
+ sm->filter = filter_new(&volume_filter_plugin, NULL, NULL);
+ assert(sm->filter != NULL);
+
+ sm->volume = 100;
+
+ return &sm->base;
+}
+
+static void
+software_mixer_finish(struct mixer *data)
+{
+ struct software_mixer *sm = (struct software_mixer *)data;
+
+ g_free(sm);
+}
+
+static int
+software_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r)
+{
+ struct software_mixer *sm = (struct software_mixer *)mixer;
+
+ return sm->volume;
+}
+
+static bool
+software_mixer_set_volume(struct mixer *mixer, unsigned volume,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct software_mixer *sm = (struct software_mixer *)mixer;
+
+ assert(volume <= 100);
+
+ sm->volume = volume;
+
+ if (volume >= 100)
+ volume = PCM_VOLUME_1;
+ else if (volume > 0)
+ volume = pcm_float_to_volume((exp(volume / 25.0) - 1) /
+ (54.5981500331F - 1));
+
+ volume_filter_set(sm->filter, volume);
+ return true;
+}
+
+const struct mixer_plugin software_mixer_plugin = {
+ .init = software_mixer_init,
+ .finish = software_mixer_finish,
+ .get_volume = software_mixer_get_volume,
+ .set_volume = software_mixer_set_volume,
+ .global = true,
+};
+
+struct filter *
+software_mixer_get_filter(struct mixer *mixer)
+{
+ struct software_mixer *sm = (struct software_mixer *)mixer;
+
+ assert(sm->base.plugin == &software_mixer_plugin);
+
+ return sm->filter;
+}
diff --git a/src/mixer/software_mixer_plugin.h b/src/mixer/software_mixer_plugin.h
new file mode 100644
index 000000000..a59f7edeb
--- /dev/null
+++ b/src/mixer/software_mixer_plugin.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef SOFTWARE_MIXER_PLUGIN_H
+#define SOFTWARE_MIXER_PLUGIN_H
+
+struct mixer;
+struct filter;
+
+/**
+ * Returns the (volume) filter associated with this mixer. All users
+ * of this mixer plugin should install this filter.
+ */
+struct filter *
+software_mixer_get_filter(struct mixer *mixer);
+
+#endif