From 4e2fb3fb89b8b80d5366466f391f21386120019e Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Tue, 20 Oct 2009 22:10:56 +0200
Subject: [PATCH] mixer_plugin: use GError for error handling

---
 src/mixer/alsa_mixer_plugin.c     | 55 ++++++++++++++++++++-----------
 src/mixer/oss_mixer_plugin.c      | 42 ++++++++++++++++-------
 src/mixer/pulse_mixer_plugin.c    | 47 ++++++++++++++++++--------
 src/mixer/software_mixer_plugin.c | 10 +++---
 src/mixer_all.c                   | 26 ++++++++++++---
 src/mixer_control.c               | 25 +++++++-------
 src/mixer_control.h               | 11 ++++---
 src/mixer_plugin.h                | 26 ++++++++++++---
 src/output_control.c              | 11 +++++--
 src/output_init.c                 | 15 ++++++---
 test/read_mixer.c                 | 20 +++++++----
 11 files changed, 203 insertions(+), 85 deletions(-)

diff --git a/src/mixer/alsa_mixer_plugin.c b/src/mixer/alsa_mixer_plugin.c
index 1a32ea9e1..0f23bc03f 100644
--- a/src/mixer/alsa_mixer_plugin.c
+++ b/src/mixer/alsa_mixer_plugin.c
@@ -42,8 +42,18 @@ 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(const struct config_param *param,
+		G_GNUC_UNUSED GError **error_r)
 {
 	struct alsa_mixer *am = g_new(struct alsa_mixer, 1);
 
@@ -81,7 +91,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 +101,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 +152,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 +170,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 +180,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 +199,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,8 +219,9 @@ 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;
 	}
 
diff --git a/src/mixer/oss_mixer_plugin.c b/src/mixer/oss_mixer_plugin.c
index fb0fd787e..3b97e38f5 100644
--- a/src/mixer/oss_mixer_plugin.c
+++ b/src/mixer/oss_mixer_plugin.c
@@ -49,6 +49,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,7 +74,7 @@ oss_find_mixer(const char *name)
 }
 
 static struct mixer *
-oss_mixer_init(const struct config_param *param)
+oss_mixer_init(const struct config_param *param, GError **error_r)
 {
 	struct oss_mixer *om = g_new(struct oss_mixer, 1);
 
@@ -78,9 +87,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 +117,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);
 	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 +133,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 +152,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 +162,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 +180,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,7 +193,9 @@ 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;
 	}
 
diff --git a/src/mixer/pulse_mixer_plugin.c b/src/mixer/pulse_mixer_plugin.c
index 54b082819..3399d5baa 100644
--- a/src/mixer/pulse_mixer_plugin.c
+++ b/src/mixer/pulse_mixer_plugin.c
@@ -45,6 +45,15 @@ struct pulse_mixer {
 
 };
 
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+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
@@ -203,7 +212,8 @@ context_state_cb(pa_context *context, void *userdata)
 
 
 static struct mixer *
-pulse_mixer_init(const struct config_param *param)
+pulse_mixer_init(const struct config_param *param,
+		 G_GNUC_UNUSED GError **error_r)
 {
 	struct pulse_mixer *pm = g_new(struct pulse_mixer,1);
 	mixer_init(&pm->base, &pulse_mixer_plugin);
@@ -226,13 +236,14 @@ pulse_mixer_finish(struct mixer *data)
 }
 
 static bool
-pulse_mixer_setup(struct pulse_mixer *pm)
+pulse_mixer_setup(struct pulse_mixer *pm, GError **error_r)
 {
 	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");
+		g_set_error(error_r, pulse_mixer_quark(), 0,
+			    "pa_context_connect() has failed");
 		return false;
 	}
 
@@ -240,15 +251,18 @@ pulse_mixer_setup(struct pulse_mixer *pm)
 
 	if (pa_threaded_mainloop_start(pm->mainloop) < 0) {
 		pa_threaded_mainloop_unlock(pm->mainloop);
-		g_debug("error start 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);
-		g_debug("error context not ready");
 		return false;
 	}
 
@@ -258,7 +272,7 @@ pulse_mixer_setup(struct pulse_mixer *pm)
 }
 
 static bool
-pulse_mixer_open(struct mixer *data)
+pulse_mixer_open(struct mixer *data, GError **error_r)
 {
 	struct pulse_mixer *pm = (struct pulse_mixer *) data;
 
@@ -269,7 +283,8 @@ pulse_mixer_open(struct mixer *data)
 
 	pm->mainloop = pa_threaded_mainloop_new();
 	if (pm->mainloop == NULL) {
-		g_debug("failed mainloop");
+		g_set_error(error_r, pulse_mixer_quark(), 0,
+			    "pa_threaded_mainloop_new() has failed");
 		return false;
 	}
 
@@ -278,11 +293,12 @@ pulse_mixer_open(struct mixer *data)
 	if (pm->context == NULL) {
 		pa_threaded_mainloop_stop(pm->mainloop);
 		pa_threaded_mainloop_free(pm->mainloop);
-		g_debug("failed context");
+		g_set_error(error_r, pulse_mixer_quark(), 0,
+			    "pa_context_new() has failed");
 		return false;
 	}
 
-	if (!pulse_mixer_setup(pm)) {
+	if (!pulse_mixer_setup(pm, error_r)) {
 		pa_threaded_mainloop_stop(pm->mainloop);
 		pa_context_disconnect(pm->context);
 		pa_context_unref(pm->context);
@@ -307,7 +323,7 @@ pulse_mixer_close(struct mixer *data)
 }
 
 static int
-pulse_mixer_get_volume(struct mixer *mixer)
+pulse_mixer_get_volume(struct mixer *mixer, GError **error_r)
 {
 	struct pulse_mixer *pm = (struct pulse_mixer *) mixer;
 	int ret;
@@ -324,12 +340,15 @@ pulse_mixer_get_volume(struct mixer *mixer)
 					   sink_input_vol, pm);
 	if (o == NULL) {
 		pa_threaded_mainloop_unlock(pm->mainloop);
-		g_debug("pa_context_get_sink_input_info() failed");
+		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;
 	}
 
@@ -343,7 +362,7 @@ pulse_mixer_get_volume(struct mixer *mixer)
 }
 
 static bool
-pulse_mixer_set_volume(struct mixer *mixer, unsigned volume)
+pulse_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
 {
 	struct pulse_mixer *pm = (struct pulse_mixer *) mixer;
 	struct pa_cvolume cvolume;
@@ -353,6 +372,7 @@ pulse_mixer_set_volume(struct mixer *mixer, unsigned volume)
 
 	if (!pm->online) {
 		pa_threaded_mainloop_unlock(pm->mainloop);
+		g_set_error(error_r, pulse_mixer_quark(), 0, "disconnected");
 		return false;
 	}
 
@@ -363,7 +383,8 @@ pulse_mixer_set_volume(struct mixer *mixer, unsigned volume)
 					     &cvolume, NULL, NULL);
 	pa_threaded_mainloop_unlock(pm->mainloop);
 	if (o == NULL) {
-		g_debug("pa_context_set_sink_input_volume() failed");
+		g_set_error(error_r, pulse_mixer_quark(), 0,
+			    "failed to set PulseAudio volume");
 		return false;
 	}
 
diff --git a/src/mixer/software_mixer_plugin.c b/src/mixer/software_mixer_plugin.c
index 661334e1b..e81d265cb 100644
--- a/src/mixer/software_mixer_plugin.c
+++ b/src/mixer/software_mixer_plugin.c
@@ -37,7 +37,8 @@ struct software_mixer {
 };
 
 static struct mixer *
-software_mixer_init(G_GNUC_UNUSED const struct config_param *param)
+software_mixer_init(G_GNUC_UNUSED const struct config_param *param,
+		    G_GNUC_UNUSED GError **error_r)
 {
 	struct software_mixer *sm = g_new(struct software_mixer, 1);
 
@@ -60,7 +61,7 @@ software_mixer_finish(struct mixer *data)
 }
 
 static bool
-software_mixer_open(struct mixer *data)
+software_mixer_open(struct mixer *data, G_GNUC_UNUSED GError **error_r)
 {
 	struct software_mixer *sm = (struct software_mixer *)data;
 
@@ -77,7 +78,7 @@ software_mixer_close(struct mixer *data)
 }
 
 static int
-software_mixer_get_volume(struct mixer *mixer)
+software_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r)
 {
 	struct software_mixer *sm = (struct software_mixer *)mixer;
 
@@ -85,7 +86,8 @@ software_mixer_get_volume(struct mixer *mixer)
 }
 
 static bool
-software_mixer_set_volume(struct mixer *mixer, unsigned volume)
+software_mixer_set_volume(struct mixer *mixer, unsigned volume,
+			  G_GNUC_UNUSED GError **error_r)
 {
 	struct software_mixer *sm = (struct software_mixer *)mixer;
 
diff --git a/src/mixer_all.c b/src/mixer_all.c
index cd05eec85..b9c1afdad 100644
--- a/src/mixer_all.c
+++ b/src/mixer_all.c
@@ -38,6 +38,8 @@ output_mixer_get_volume(unsigned i)
 {
 	struct audio_output *output;
 	struct mixer *mixer;
+	int volume;
+	GError *error = NULL;
 
 	assert(i < audio_output_count());
 
@@ -49,7 +51,14 @@ output_mixer_get_volume(unsigned i)
 	if (mixer == NULL)
 		return -1;
 
-	return mixer_get_volume(mixer);
+	volume = mixer_get_volume(mixer, &error);
+	if (volume < 0 && error != NULL) {
+		g_warning("Failed to read mixer for '%s': %s",
+			  output->name, error->message);
+		g_error_free(error);
+	}
+
+	return volume;
 }
 
 int
@@ -77,6 +86,8 @@ output_mixer_set_volume(unsigned i, unsigned volume)
 {
 	struct audio_output *output;
 	struct mixer *mixer;
+	bool success;
+	GError *error = NULL;
 
 	assert(i < audio_output_count());
 	assert(volume <= 100);
@@ -89,7 +100,14 @@ output_mixer_set_volume(unsigned i, unsigned volume)
 	if (mixer == NULL)
 		return false;
 
-	return mixer_set_volume(mixer, volume);
+	success = mixer_set_volume(mixer, volume, &error);
+	if (!success && error != NULL) {
+		g_warning("Failed to set mixer for '%s': %s",
+			  output->name, error->message);
+		g_error_free(error);
+	}
+
+	return success;
 }
 
 bool
@@ -123,7 +141,7 @@ output_mixer_get_software_volume(unsigned i)
 	if (mixer == NULL || mixer->plugin != &software_mixer_plugin)
 		return -1;
 
-	return mixer_get_volume(mixer);
+	return mixer_get_volume(mixer, NULL);
 }
 
 int
@@ -157,6 +175,6 @@ mixer_all_set_software_volume(unsigned volume)
 		struct audio_output *output = audio_output_get(i);
 		if (output->mixer != NULL &&
 		    output->mixer->plugin == &software_mixer_plugin)
-			mixer_set_volume(output->mixer, volume);
+			mixer_set_volume(output->mixer, volume, NULL);
 	}
 }
diff --git a/src/mixer_control.c b/src/mixer_control.c
index 927a1276c..7ee9fabf0 100644
--- a/src/mixer_control.c
+++ b/src/mixer_control.c
@@ -20,8 +20,6 @@
 #include "mixer_control.h"
 #include "mixer_api.h"
 
-#include <glib.h>
-
 #include <assert.h>
 #include <stddef.h>
 
@@ -29,13 +27,14 @@
 #define G_LOG_DOMAIN "mixer"
 
 struct mixer *
-mixer_new(const struct mixer_plugin *plugin, const struct config_param *param)
+mixer_new(const struct mixer_plugin *plugin, const struct config_param *param,
+	  GError **error_r)
 {
 	struct mixer *mixer;
 
 	assert(plugin != NULL);
 
-	mixer = plugin->init(param);
+	mixer = plugin->init(param, error_r);
 
 	assert(mixer == NULL || mixer->plugin == plugin);
 
@@ -55,7 +54,7 @@ mixer_free(struct mixer *mixer)
 }
 
 bool
-mixer_open(struct mixer *mixer)
+mixer_open(struct mixer *mixer, GError **error_r)
 {
 	bool success;
 
@@ -67,7 +66,7 @@ mixer_open(struct mixer *mixer)
 	if (mixer->open)
 		success = true;
 	else
-		success = mixer->open = mixer->plugin->open(mixer);
+		success = mixer->open = mixer->plugin->open(mixer, error_r);
 
 	mixer->failed = !success;
 
@@ -123,19 +122,20 @@ mixer_failed(struct mixer *mixer)
 }
 
 int
-mixer_get_volume(struct mixer *mixer)
+mixer_get_volume(struct mixer *mixer, GError **error_r)
 {
 	int volume;
 
 	assert(mixer != NULL);
 
-	if (mixer->plugin->global && !mixer->failed && !mixer_open(mixer))
+	if (mixer->plugin->global && !mixer->failed &&
+	    !mixer_open(mixer, error_r))
 		return -1;
 
 	g_mutex_lock(mixer->mutex);
 
 	if (mixer->open) {
-		volume = mixer->plugin->get_volume(mixer);
+		volume = mixer->plugin->get_volume(mixer, error_r);
 		if (volume < 0)
 			mixer_failed(mixer);
 	} else
@@ -147,20 +147,21 @@ mixer_get_volume(struct mixer *mixer)
 }
 
 bool
-mixer_set_volume(struct mixer *mixer, unsigned volume)
+mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
 {
 	bool success;
 
 	assert(mixer != NULL);
 	assert(volume <= 100);
 
-	if (mixer->plugin->global && !mixer->failed && !mixer_open(mixer))
+	if (mixer->plugin->global && !mixer->failed &&
+	    !mixer_open(mixer, error_r))
 		return false;
 
 	g_mutex_lock(mixer->mutex);
 
 	if (mixer->open) {
-		success = mixer->plugin->set_volume(mixer, volume);
+		success = mixer->plugin->set_volume(mixer, volume, error_r);
 		if (!success)
 			mixer_failed(mixer);
 	} else
diff --git a/src/mixer_control.h b/src/mixer_control.h
index b8997a795..f023317e3 100644
--- a/src/mixer_control.h
+++ b/src/mixer_control.h
@@ -25,6 +25,8 @@
 #ifndef MPD_MIXER_CONTROL_H
 #define MPD_MIXER_CONTROL_H
 
+#include <glib.h>
+
 #include <stdbool.h>
 
 struct mixer;
@@ -32,13 +34,14 @@ struct mixer_plugin;
 struct config_param;
 
 struct mixer *
-mixer_new(const struct mixer_plugin *plugin, const struct config_param *param);
+mixer_new(const struct mixer_plugin *plugin, const struct config_param *param,
+	  GError **error_r);
 
 void
 mixer_free(struct mixer *mixer);
 
 bool
-mixer_open(struct mixer *mixer);
+mixer_open(struct mixer *mixer, GError **error_r);
 
 void
 mixer_close(struct mixer *mixer);
@@ -51,9 +54,9 @@ void
 mixer_auto_close(struct mixer *mixer);
 
 int
-mixer_get_volume(struct mixer *mixer);
+mixer_get_volume(struct mixer *mixer, GError **error_r);
 
 bool
-mixer_set_volume(struct mixer *mixer, unsigned volume);
+mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r);
 
 #endif
diff --git a/src/mixer_plugin.h b/src/mixer_plugin.h
index 2b9b440e5..f3f70e2ef 100644
--- a/src/mixer_plugin.h
+++ b/src/mixer_plugin.h
@@ -27,6 +27,8 @@
 #ifndef MPD_MIXER_PLUGIN_H
 #define MPD_MIXER_PLUGIN_H
 
+#include <glib.h>
+
 #include <stdbool.h>
 
 struct config_param;
@@ -35,8 +37,13 @@ struct mixer;
 struct mixer_plugin {
 	/**
          * Alocates and configures a mixer device.
+	 *
+	 * @param error_r location to store the error occuring, or
+	 * NULL to ignore errors
+	 * @return a mixer object, or NULL on error
 	 */
-	struct mixer *(*init)(const struct config_param *param);
+	struct mixer *(*init)(const struct config_param *param,
+			      GError **error_r);
 
 	/**
 	 * Finish and free mixer data
@@ -45,8 +52,12 @@ struct mixer_plugin {
 
 	/**
 	 * Open mixer device
+	 *
+	 * @param error_r location to store the error occuring, or
+	 * NULL to ignore errors
+	 * @return true on success, false on error
 	 */
-	bool (*open)(struct mixer *data);
+	bool (*open)(struct mixer *data, GError **error_r);
 
 	/**
 	 * Close mixer device
@@ -56,18 +67,23 @@ struct mixer_plugin {
 	/**
 	 * Reads the current volume.
 	 *
+	 * @param error_r location to store the error occuring, or
+	 * NULL to ignore errors
 	 * @return the current volume (0..100 including) or -1 on
 	 * error
 	 */
-	int (*get_volume)(struct mixer *mixer);
+	int (*get_volume)(struct mixer *mixer, GError **error_r);
 
 	/**
 	 * Sets the volume.
 	 *
+	 * @param error_r location to store the error occuring, or
+	 * NULL to ignore errors
 	 * @param volume the new volume (0..100 including)
-	 * @return true on success
+	 * @return true on success, false on error
 	 */
-	bool (*set_volume)(struct mixer *mixer, unsigned volume);
+	bool (*set_volume)(struct mixer *mixer, unsigned volume,
+			   GError **error_r);
 
 	/**
 	 * If true, then the mixer is automatically opened, even if
diff --git a/src/output_control.c b/src/output_control.c
index ef77bf4fa..b833fb08d 100644
--- a/src/output_control.c
+++ b/src/output_control.c
@@ -102,8 +102,15 @@ audio_output_open(struct audio_output *ao,
 	ao_command(ao, ao->open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN);
 	open = ao->open;
 
-	if (open && ao->mixer != NULL)
-		mixer_open(ao->mixer);
+	if (open && ao->mixer != NULL) {
+		GError *error = NULL;
+
+		if (!mixer_open(ao->mixer, &error)) {
+			g_warning("Failed to open mixer for '%s': %s",
+				  ao->name, error->message);
+			g_error_free(error);
+		}
+	}
 
 	return open;
 }
diff --git a/src/output_init.c b/src/output_init.c
index b139ec0fd..a628a2499 100644
--- a/src/output_init.c
+++ b/src/output_init.c
@@ -91,7 +91,8 @@ audio_output_mixer_type(const struct config_param *param)
 static struct mixer *
 audio_output_load_mixer(const struct config_param *param,
 			const struct mixer_plugin *plugin,
-			struct filter *filter_chain)
+			struct filter *filter_chain,
+			GError **error_r)
 {
 	struct mixer *mixer;
 
@@ -104,10 +105,10 @@ audio_output_load_mixer(const struct config_param *param,
 		if (plugin == NULL)
 			return NULL;
 
-		return mixer_new(plugin, param);
+		return mixer_new(plugin, param, error_r);
 
 	case MIXER_TYPE_SOFTWARE:
-		mixer = mixer_new(&software_mixer_plugin, NULL);
+		mixer = mixer_new(&software_mixer_plugin, NULL, NULL);
 		assert(mixer != NULL);
 
 		filter_chain_append(filter_chain,
@@ -124,6 +125,7 @@ audio_output_init(struct audio_output *ao, const struct config_param *param,
 		  GError **error_r)
 {
 	const struct audio_output_plugin *plugin = NULL;
+	GError *error = NULL;
 
 	if (param) {
 		const char *p;
@@ -199,7 +201,12 @@ audio_output_init(struct audio_output *ao, const struct config_param *param,
 		return false;
 
 	ao->mixer = audio_output_load_mixer(param, plugin->mixer_plugin,
-					    ao->filter);
+					    ao->filter, &error);
+	if (ao->mixer == NULL && error != NULL) {
+		g_warning("Failed to initialize hardware mixer for '%s': %s",
+			  ao->name, error->message);
+		g_error_free(error);
+	}
 
 	/* the "convert" filter must be the last one in the chain */
 
diff --git a/test/read_mixer.c b/test/read_mixer.c
index 0e814f4ba..89ea55356 100644
--- a/test/read_mixer.c
+++ b/test/read_mixer.c
@@ -46,6 +46,7 @@ pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED int length,
 
 int main(int argc, G_GNUC_UNUSED char **argv)
 {
+	GError *error = NULL;
 	struct mixer *mixer;
 	bool success;
 	int volume;
@@ -57,27 +58,34 @@ int main(int argc, G_GNUC_UNUSED char **argv)
 
 	g_thread_init(NULL);
 
-	mixer = mixer_new(&alsa_mixer_plugin, NULL);
+	mixer = mixer_new(&alsa_mixer_plugin, NULL, &error);
 	if (mixer == NULL) {
-		g_printerr("mixer_new() failed\n");
+		g_printerr("mixer_new() failed: %s\n", error->message);
+		g_error_free(error);
 		return 2;
 	}
 
-	success = mixer_open(mixer);
+	success = mixer_open(mixer, &error);
 	if (!success) {
 		mixer_free(mixer);
-		g_printerr("failed to open the mixer\n");
+		g_printerr("failed to open the mixer: %s\n", error->message);
+		g_error_free(error);
 		return 2;
 	}
 
-	volume = mixer_get_volume(mixer);
+	volume = mixer_get_volume(mixer, &error);
 	mixer_close(mixer);
 	mixer_free(mixer);
 
 	assert(volume >= -1 && volume <= 100);
 
 	if (volume < 0) {
-		g_printerr("failed to read volume\n");
+		if (error != NULL) {
+			g_printerr("failed to read volume: %s\n",
+				   error->message);
+			g_error_free(error);
+		} else
+			g_printerr("failed to read volume\n");
 		return 2;
 	}