From ec926539a3a7a09b310e74a4fc84902e0971b29d Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Thu, 26 Feb 2009 22:04:59 +0100
Subject: [PATCH] output_plugin: report errors with GError

Use GLib's GError library for reporting output device failures.

Note that some init() methods don't clean up properly after a failure,
but that's ok for now, because the MPD core will abort anyway.
---
 src/output/alsa_plugin.c  |  55 +++++++-----
 src/output/ao_plugin.c    |  52 ++++++++----
 src/output/fifo_plugin.c  |  73 ++++++++++------
 src/output/jack_plugin.c  |  61 +++++++++-----
 src/output/mvp_plugin.c   |  67 ++++++++++-----
 src/output/null_plugin.c  |   9 +-
 src/output/oss_plugin.c   |  75 ++++++++++-------
 src/output/osx_plugin.c   |  41 ++++++---
 src/output/pulse_plugin.c |  26 ++++--
 src/output/shout_plugin.c | 173 +++++++++++++++++++++-----------------
 src/output_init.c         |  11 ++-
 src/output_plugin.h       |  34 +++++---
 src/output_thread.c       |  21 ++++-
 13 files changed, 443 insertions(+), 255 deletions(-)

diff --git a/src/output/alsa_plugin.c b/src/output/alsa_plugin.c
index 1623dcac8..874e4ac93 100644
--- a/src/output/alsa_plugin.c
+++ b/src/output/alsa_plugin.c
@@ -74,6 +74,15 @@ struct alsa_data {
 	struct mixer *mixer;
 };
 
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+alsa_output_quark(void)
+{
+	return g_quark_from_static_string("alsa_output");
+}
+
 static const char *
 alsa_device(const struct alsa_data *ad)
 {
@@ -130,7 +139,8 @@ alsa_configure(struct alsa_data *ad, const struct config_param *param)
 
 static void *
 alsa_init(G_GNUC_UNUSED const struct audio_format *audio_format,
-	  const struct config_param *param)
+	  const struct config_param *param,
+	  G_GNUC_UNUSED GError **error)
 {
 	/* no need for pthread_once thread-safety when reading config */
 	static int free_global_registered;
@@ -198,7 +208,8 @@ get_bitformat(const struct audio_format *af)
  */
 static bool
 alsa_setup(struct alsa_data *ad, struct audio_format *audio_format,
-	   snd_pcm_format_t bitformat)
+	   snd_pcm_format_t bitformat,
+	   GError **error)
 {
 	snd_pcm_hw_params_t *hwparams;
 	snd_pcm_sw_params_t *swparams;
@@ -256,17 +267,20 @@ configure_hw:
 	}
 
 	if (err < 0) {
-		g_warning("ALSA device \"%s\" does not support %u bit audio: %s\n",
-			  alsa_device(ad), audio_format->bits, snd_strerror(-err));
+		g_set_error(error, alsa_output_quark(), err,
+			    "ALSA device \"%s\" does not support %u bit audio: %s",
+			    alsa_device(ad), audio_format->bits,
+			    snd_strerror(-err));
 		return false;
 	}
 
 	err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams,
 						  &channels);
 	if (err < 0) {
-		g_warning("ALSA device \"%s\" does not support %i channels: %s\n",
-			  alsa_device(ad), (int)audio_format->channels,
-		      snd_strerror(-err));
+		g_set_error(error, alsa_output_quark(), err,
+			    "ALSA device \"%s\" does not support %i channels: %s",
+			    alsa_device(ad), (int)audio_format->channels,
+			    snd_strerror(-err));
 		return false;
 	}
 	audio_format->channels = (int8_t)channels;
@@ -274,8 +288,9 @@ configure_hw:
 	err = snd_pcm_hw_params_set_rate_near(ad->pcm, hwparams,
 					      &sample_rate, NULL);
 	if (err < 0 || sample_rate == 0) {
-		g_warning("ALSA device \"%s\" does not support %u Hz audio\n",
-			  alsa_device(ad), audio_format->sample_rate);
+		g_set_error(error, alsa_output_quark(), err,
+			    "ALSA device \"%s\" does not support %u Hz audio",
+			    alsa_device(ad), audio_format->sample_rate);
 		return false;
 	}
 	audio_format->sample_rate = sample_rate;
@@ -348,14 +363,14 @@ configure_hw:
 	return true;
 
 error:
-	g_warning("Error opening ALSA device \"%s\" (%s): %s\n",
-		  alsa_device(ad), cmd, snd_strerror(-err));
-
+	g_set_error(error, alsa_output_quark(), err,
+		    "Error opening ALSA device \"%s\" (%s): %s",
+		    alsa_device(ad), cmd, snd_strerror(-err));
 	return false;
 }
 
 static bool
-alsa_open(void *data, struct audio_format *audio_format)
+alsa_open(void *data, struct audio_format *audio_format, GError **error)
 {
 	struct alsa_data *ad = data;
 	snd_pcm_format_t bitformat;
@@ -378,12 +393,13 @@ alsa_open(void *data, struct audio_format *audio_format)
 	if (err < 0) {
 		ad->pcm = NULL;
 
-		g_warning("Error opening ALSA device \"%s\": %s\n",
-			  alsa_device(ad), snd_strerror(-err));
+		g_set_error(error, alsa_output_quark(), err,
+			    "Failed to open ALSA device \"%s\": %s",
+			    alsa_device(ad), snd_strerror(err));
 		return false;
 	}
 
-	success = alsa_setup(ad, audio_format, bitformat);
+	success = alsa_setup(ad, audio_format, bitformat, error);
 	if (!success) {
 		snd_pcm_close(ad->pcm);
 		ad->pcm = NULL;
@@ -463,7 +479,7 @@ alsa_close(void *data)
 }
 
 static size_t
-alsa_play(void *data, const void *chunk, size_t size)
+alsa_play(void *data, const void *chunk, size_t size, GError **error)
 {
 	struct alsa_data *ad = data;
 	int ret;
@@ -477,9 +493,8 @@ alsa_play(void *data, const void *chunk, size_t size)
 
 		if (ret < 0 && ret != -EAGAIN && ret != -EINTR &&
 		    alsa_recover(ad, ret) < 0) {
-			g_warning("closing ALSA device \"%s\" due to write "
-				  "error: %s\n",
-				  alsa_device(ad), snd_strerror(-errno));
+			g_set_error(error, alsa_output_quark(), errno,
+				    "%s", snd_strerror(-errno));
 			return 0;
 		}
 	}
diff --git a/src/output/ao_plugin.c b/src/output/ao_plugin.c
index 565384b7b..09d345fd9 100644
--- a/src/output/ao_plugin.c
+++ b/src/output/ao_plugin.c
@@ -33,8 +33,14 @@ struct ao_data {
 	ao_device *device;
 } AoData;
 
+static inline GQuark
+ao_output_quark(void)
+{
+	return g_quark_from_static_string("ao_output");
+}
+
 static void
-ao_output_error(const char *msg)
+ao_output_error(GError **error_r)
 {
 	const char *error;
 
@@ -63,12 +69,14 @@ ao_output_error(const char *msg)
 		error = strerror(errno);
 	}
 
-	g_warning("%s: %s\n", msg, error);
+	g_set_error(error_r, ao_output_quark(), errno,
+		    "%s", error);
 }
 
 static void *
 ao_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
-	       const struct config_param *param)
+	       const struct config_param *param,
+	       GError **error)
 {
 	struct ao_data *ad = g_new(struct ao_data, 1);
 	ao_info *ai;
@@ -84,15 +92,22 @@ ao_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
 	ao_output_ref++;
 
 	value = config_get_block_string(param, "driver", "default");
-	if (0 == strcmp(value, "default")) {
+	if (0 == strcmp(value, "default"))
 		ad->driver = ao_default_driver_id();
-	} else if ((ad->driver = ao_driver_id(value)) < 0)
-		g_error("\"%s\" is not a valid ao driver at line %i\n",
-			value, param->line);
+	else
+		ad->driver = ao_driver_id(value);
+
+	if (ad->driver < 0) {
+		g_set_error(error, ao_output_quark(), 0,
+			    "\"%s\" is not a valid ao driver",
+			    value);
+		return NULL;
+	}
 
 	if ((ai = ao_driver_info(ad->driver)) == NULL) {
-		g_error("problems getting driver info for device defined at line %i\n"
-			"you may not have permission to the audio device\n", param->line);
+		g_set_error(error, ao_output_quark(), 0,
+			    "problems getting driver info");
+		return NULL;
 	}
 
 	g_debug("using ao driver \"%s\" for \"%s\"\n", ai->short_name,
@@ -105,9 +120,12 @@ ao_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
 		for (unsigned i = 0; options[i] != NULL; ++i) {
 			gchar **key_value = g_strsplit(options[i], "=", 2);
 
-			if (key_value[0] == NULL || key_value[1] == NULL)
-				g_error("problems parsing options \"%s\"\n",
-					options[i]);
+			if (key_value[0] == NULL || key_value[1] == NULL) {
+				g_set_error(error, ao_output_quark(), 0,
+					    "problems parsing options \"%s\"",
+					    options[i]);
+				return NULL;
+			}
 
 			ao_append_option(&ad->options, key_value[0],
 					 key_value[1]);
@@ -144,7 +162,8 @@ ao_output_close(void *data)
 }
 
 static bool
-ao_output_open(void *data, struct audio_format *audio_format)
+ao_output_open(void *data, struct audio_format *audio_format,
+	       GError **error)
 {
 	ao_sample_format format;
 	struct ao_data *ad = (struct ao_data *)data;
@@ -163,7 +182,7 @@ ao_output_open(void *data, struct audio_format *audio_format)
 	ad->device = ao_open_live(ad->driver, &format, ad->options);
 
 	if (ad->device == NULL) {
-		ao_output_error("Failed to open libao");
+		ao_output_error(error);
 		return false;
 	}
 
@@ -188,7 +207,8 @@ static int ao_play_deconst(ao_device *device, const void *output_samples,
 }
 
 static size_t
-ao_output_play(void *data, const void *chunk, size_t size)
+ao_output_play(void *data, const void *chunk, size_t size,
+	       GError **error)
 {
 	struct ao_data *ad = (struct ao_data *)data;
 
@@ -196,7 +216,7 @@ ao_output_play(void *data, const void *chunk, size_t size)
 		size = ad->write_size;
 
 	if (ao_play_deconst(ad->device, chunk, size) == 0) {
-		ao_output_error("Closing libao device due to play error");
+		ao_output_error(error);
 		return 0;
 	}
 
diff --git a/src/output/fifo_plugin.c b/src/output/fifo_plugin.c
index 47f0346a7..449faa24c 100644
--- a/src/output/fifo_plugin.c
+++ b/src/output/fifo_plugin.c
@@ -42,6 +42,15 @@ struct fifo_data {
 	Timer *timer;
 };
 
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+fifo_output_quark(void)
+{
+	return g_quark_from_static_string("fifo_output");
+}
+
 static struct fifo_data *fifo_data_new(void)
 {
 	struct fifo_data *ret;
@@ -95,11 +104,12 @@ fifo_close(struct fifo_data *fd)
 }
 
 static bool
-fifo_make(struct fifo_data *fd)
+fifo_make(struct fifo_data *fd, GError **error)
 {
 	if (mkfifo(fd->path, 0666) < 0) {
-		g_warning("Couldn't create FIFO \"%s\": %s",
-			  fd->path, strerror(errno));
+		g_set_error(error, fifo_output_quark(), errno,
+			    "Couldn't create FIFO \"%s\": %s",
+			    fd->path, strerror(errno));
 		return false;
 	}
 
@@ -109,24 +119,26 @@ fifo_make(struct fifo_data *fd)
 }
 
 static bool
-fifo_check(struct fifo_data *fd)
+fifo_check(struct fifo_data *fd, GError **error)
 {
 	struct stat st;
 
 	if (stat(fd->path, &st) < 0) {
 		if (errno == ENOENT) {
 			/* Path doesn't exist */
-			return fifo_make(fd);
+			return fifo_make(fd, error);
 		}
 
-		g_warning("Failed to stat FIFO \"%s\": %s",
-			  fd->path, strerror(errno));
+		g_set_error(error, fifo_output_quark(), errno,
+			    "Failed to stat FIFO \"%s\": %s",
+			    fd->path, strerror(errno));
 		return false;
 	}
 
 	if (!S_ISFIFO(st.st_mode)) {
-		g_warning("\"%s\" already exists, but is not a FIFO",
-			  fd->path);
+		g_set_error(error, fifo_output_quark(), 0,
+			    "\"%s\" already exists, but is not a FIFO",
+			    fd->path);
 		return false;
 	}
 
@@ -134,23 +146,25 @@ fifo_check(struct fifo_data *fd)
 }
 
 static bool
-fifo_open(struct fifo_data *fd)
+fifo_open(struct fifo_data *fd, GError **error)
 {
-	if (!fifo_check(fd))
+	if (!fifo_check(fd, error))
 		return false;
 
 	fd->input = open(fd->path, O_RDONLY|O_NONBLOCK);
 	if (fd->input < 0) {
-		g_warning("Could not open FIFO \"%s\" for reading: %s",
-			  fd->path, strerror(errno));
+		g_set_error(error, fifo_output_quark(), errno,
+			    "Could not open FIFO \"%s\" for reading: %s",
+			    fd->path, strerror(errno));
 		fifo_close(fd);
 		return false;
 	}
 
 	fd->output = open(fd->path, O_WRONLY|O_NONBLOCK);
 	if (fd->output < 0) {
-		g_warning("Could not open FIFO \"%s\" for writing: %s",
-			  fd->path, strerror(errno));
+		g_set_error(error, fifo_output_quark(), errno,
+			    "Could not open FIFO \"%s\" for writing: %s",
+			    fd->path, strerror(errno));
 		fifo_close(fd);
 		return false;
 	}
@@ -160,27 +174,31 @@ fifo_open(struct fifo_data *fd)
 
 static void *
 fifo_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
-		 const struct config_param *param)
+		 const struct config_param *param,
+		 GError **error)
 {
 	struct fifo_data *fd;
 	char *value, *path;
 
 	value = config_dup_block_string(param, "path", NULL);
-	if (value == NULL)
-		g_error("No \"path\" parameter specified for fifo output "
-			"defined at line %i", param->line);
+	if (value == NULL) {
+		g_set_error(error, fifo_output_quark(), errno,
+			    "No \"path\" parameter specified");
+		return NULL;
+	}
 
 	path = parsePath(value);
 	g_free(value);
 	if (!path) {
-		g_error("Could not parse \"path\" parameter for fifo output "
-			"at line %i", param->line);
+		g_set_error(error, fifo_output_quark(), errno,
+			    "Could not parse \"path\" parameter");
+		return NULL;
 	}
 
 	fd = fifo_data_new();
 	fd->path = path;
 
-	if (!fifo_open(fd)) {
+	if (!fifo_open(fd, error)) {
 		fifo_data_free(fd);
 		return NULL;
 	}
@@ -198,7 +216,8 @@ fifo_output_finish(void *data)
 }
 
 static bool
-fifo_output_open(void *data, struct audio_format *audio_format)
+fifo_output_open(void *data, struct audio_format *audio_format,
+		 G_GNUC_UNUSED GError **error)
 {
 	struct fifo_data *fd = (struct fifo_data *)data;
 
@@ -234,7 +253,8 @@ fifo_output_cancel(void *data)
 }
 
 static size_t
-fifo_output_play(void *data, const void *chunk, size_t size)
+fifo_output_play(void *data, const void *chunk, size_t size,
+		 GError **error)
 {
 	struct fifo_data *fd = (struct fifo_data *)data;
 	ssize_t bytes;
@@ -261,8 +281,9 @@ fifo_output_play(void *data, const void *chunk, size_t size)
 				continue;
 			}
 
-			g_warning("Closing FIFO output \"%s\" due to write error: %s",
-				  fd->path, strerror(errno));
+			g_set_error(error, fifo_output_quark(), errno,
+				    "Failed to write to FIFO %s: %s",
+				    fd->path, g_strerror(errno));
 			return 0;
 		}
 	}
diff --git a/src/output/jack_plugin.c b/src/output/jack_plugin.c
index a04d37f00..6b3dd9d03 100644
--- a/src/output/jack_plugin.c
+++ b/src/output/jack_plugin.c
@@ -58,6 +58,15 @@ struct jack_data {
 	bool shutdown;
 };
 
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+jack_output_quark(void)
+{
+	return g_quark_from_static_string("jack_output");
+}
+
 static void
 mpd_jack_client_free(struct jack_data *jd)
 {
@@ -158,7 +167,7 @@ mpd_jack_info(const char *msg)
 
 static void *
 mpd_jack_init(G_GNUC_UNUSED const struct audio_format *audio_format,
-	      const struct config_param *param)
+	      const struct config_param *param, GError **error)
 {
 	struct jack_data *jd;
 	const char *value;
@@ -172,9 +181,12 @@ mpd_jack_init(G_GNUC_UNUSED const struct audio_format *audio_format,
 	if (value != NULL) {
 		char **ports = g_strsplit(value, ",", 0);
 
-		if (ports[0] == NULL || ports[1] == NULL || ports[2] != NULL)
-			g_error("two port names expected in line %d",
-				param->line);
+		if (ports[0] == NULL || ports[1] == NULL || ports[2] != NULL) {
+			g_set_error(error, jack_output_quark(), 0,
+				    "two port names expected in line %d",
+				    param->line);
+			return NULL;
+		}
 
 		jd->output_ports[0] = ports[0];
 		jd->output_ports[1] = ports[1];
@@ -204,7 +216,7 @@ mpd_jack_test_default_device(void)
 }
 
 static bool
-mpd_jack_connect(struct jack_data *jd)
+mpd_jack_connect(struct jack_data *jd, GError **error)
 {
 	const char *output_ports[2], **jports;
 
@@ -215,7 +227,8 @@ mpd_jack_connect(struct jack_data *jd)
 	jd->shutdown = false;
 
 	if ((jd->client = jack_client_new(jd->name)) == NULL) {
-		g_warning("jack server not running?");
+		g_set_error(error, jack_output_quark(), 0,
+			    "Failed to connect to JACK server");
 		return false;
 	}
 
@@ -227,14 +240,16 @@ mpd_jack_connect(struct jack_data *jd)
 						  JACK_DEFAULT_AUDIO_TYPE,
 						  JackPortIsOutput, 0);
 		if (jd->ports[i] == NULL) {
-			g_warning("Cannot register %s output port.",
-				  port_names[i]);
+			g_set_error(error, jack_output_quark(), 0,
+				    "Cannot register output port \"%s\"",
+				    port_names[i]);
 			return false;
 		}
 	}
 
 	if ( jack_activate(jd->client) ) {
-		g_warning("cannot activate client");
+		g_set_error(error, jack_output_quark(), 0,
+			    "cannot activate client");
 		return false;
 	}
 
@@ -244,7 +259,8 @@ mpd_jack_connect(struct jack_data *jd)
 		jports = jack_get_ports(jd->client, NULL, NULL,
 					JackPortIsPhysical | JackPortIsInput);
 		if (jports == NULL) {
-			g_warning("no ports found");
+			g_set_error(error, jack_output_quark(), 0,
+				    "no ports found");
 			return false;
 		}
 
@@ -267,8 +283,9 @@ mpd_jack_connect(struct jack_data *jd)
 		ret = jack_connect(jd->client, jack_port_name(jd->ports[i]),
 				   output_ports[i]);
 		if (ret != 0) {
-			g_warning("%s is not a valid Jack Client / Port",
-				  output_ports[i]);
+			g_set_error(error, jack_output_quark(), 0,
+				    "Not a valid JACK port: %s",
+				    output_ports[i]);
 
 			if (jports != NULL)
 				free(jports);
@@ -284,13 +301,13 @@ mpd_jack_connect(struct jack_data *jd)
 }
 
 static bool
-mpd_jack_open(void *data, struct audio_format *audio_format)
+mpd_jack_open(void *data, struct audio_format *audio_format, GError **error)
 {
 	struct jack_data *jd = data;
 
 	assert(jd != NULL);
 
-	if (!mpd_jack_connect(jd)) {
+	if (!mpd_jack_connect(jd, error)) {
 		mpd_jack_client_free(jd);
 		return false;
 	}
@@ -381,21 +398,23 @@ mpd_jack_write_samples(struct jack_data *jd, const void *src,
 }
 
 static size_t
-mpd_jack_play(void *data, const void *chunk, size_t size)
+mpd_jack_play(void *data, const void *chunk, size_t size, GError **error)
 {
 	struct jack_data *jd = data;
 	const size_t frame_size = audio_format_frame_size(&jd->audio_format);
 	size_t space = 0, space1;
 
-	if (jd->shutdown) {
-		g_warning("Refusing to play, because there is no client thread.");
-		return 0;
-	}
-
 	assert(size % frame_size == 0);
 	size /= frame_size;
 
-	while (!jd->shutdown) {
+	while (true) {
+		if (jd->shutdown) {
+			g_set_error(error, jack_output_quark(), 0,
+				    "Refusing to play, because "
+				    "there is no client thread");
+			return 0;
+		}
+
 		space = jack_ringbuffer_write_space(jd->ringbuffer[0]);
 		space1 = jack_ringbuffer_write_space(jd->ringbuffer[1]);
 		if (space > space1)
diff --git a/src/output/mvp_plugin.c b/src/output/mvp_plugin.c
index 65df5a2aa..b9004029b 100644
--- a/src/output/mvp_plugin.c
+++ b/src/output/mvp_plugin.c
@@ -83,6 +83,15 @@ static const unsigned mvp_sample_rates[][3] = {
 	{15, 96000, 48000}
 };
 
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+mvp_output_quark(void)
+{
+	return g_quark_from_static_string("mvp_output");
+}
+
 /**
  * Translate a sample rate to a MVP sample rate.
  *
@@ -118,7 +127,8 @@ mvp_output_test_default_device(void)
 
 static void *
 mvp_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
-		G_GNUC_UNUSED const struct config_param *param)
+		G_GNUC_UNUSED const struct config_param *param,
+		G_GNUC_UNUSED GError **error)
 {
 	struct mvp_data *md = g_new(struct mvp_data, 1);
 	md->fd = -1;
@@ -134,7 +144,8 @@ mvp_output_finish(void *data)
 }
 
 static bool
-mvp_set_pcm_params(struct mvp_data *md, struct audio_format *audio_format)
+mvp_set_pcm_params(struct mvp_data *md, struct audio_format *audio_format,
+		   GError **error)
 {
 	unsigned mix[5];
 
@@ -181,23 +192,27 @@ mvp_set_pcm_params(struct mvp_data *md, struct audio_format *audio_format)
 	 */
 	mix[2] = mvp_find_sample_rate(audio_format->sample_rate);
 	if (mix[2] == (unsigned)-1) {
-		g_warning("Can not find suitable output frequency for %u\n",
-			  audio_format->sample_rate);
+		g_set_error(error, mvp_output_quark(), 0,
+			    "Can not find suitable output frequency for %u",
+			    audio_format->sample_rate);
 		return false;
 	}
 
 	if (ioctl(md->fd, MVP_SET_AUD_FORMAT, &mix) < 0) {
-		g_warning("Can not set audio format\n");
+		g_set_error(error, mvp_output_quark(), errno,
+			    "Can not set audio format");
 		return false;
 	}
 
 	if (ioctl(md->fd, MVP_SET_AUD_SYNC, 2) != 0) {
-		g_warning("Can not set audio sync\n");
+		g_set_error(error, mvp_output_quark(), errno,
+			    "Can not set audio sync");
 		return false;
 	}
 
 	if (ioctl(md->fd, MVP_SET_AUD_PLAY, 0) < 0) {
-		g_warning("Can not set audio play mode\n");
+		g_set_error(error, mvp_output_quark(), errno,
+			    "Can not set audio play mode");
 		return false;
 	}
 
@@ -205,7 +220,7 @@ mvp_set_pcm_params(struct mvp_data *md, struct audio_format *audio_format)
 }
 
 static bool
-mvp_output_open(void *data, struct audio_format *audio_format)
+mvp_output_open(void *data, struct audio_format *audio_format, GError **error)
 {
 	struct mvp_data *md = data;
 	long long int stc = 0;
@@ -213,33 +228,38 @@ mvp_output_open(void *data, struct audio_format *audio_format)
 	bool success;
 
 	if ((md->fd = open("/dev/adec_pcm", O_RDWR | O_NONBLOCK)) < 0) {
-		g_warning("Error opening /dev/adec_pcm: %s\n",
-			  strerror(errno));
+		g_set_error(error, mvp_output_quark(), errno,
+			    "Error opening /dev/adec_pcm: %s",
+			    strerror(errno));
 		return false;
 	}
 	if (ioctl(md->fd, MVP_SET_AUD_SRC, 1) < 0) {
-		g_warning("Error setting audio source: %s\n",
-			  strerror(errno));
+		g_set_error(error, mvp_output_quark(), errno,
+			    "Error setting audio source: %s",
+			    strerror(errno));
 		return false;
 	}
 	if (ioctl(md->fd, MVP_SET_AUD_STREAMTYPE, 0) < 0) {
-		g_warning("Error setting audio streamtype: %s\n",
-			  strerror(errno));
+		g_set_error(error, mvp_output_quark(), errno,
+			    "Error setting audio streamtype: %s",
+			    strerror(errno));
 		return false;
 	}
 	if (ioctl(md->fd, MVP_SET_AUD_FORMAT, &mix) < 0) {
-		g_warning("Error setting audio format: %s\n",
-			  strerror(errno));
+		g_set_error(error, mvp_output_quark(), errno,
+			    "Error setting audio format: %s",
+			    strerror(errno));
 		return false;
 	}
 	ioctl(md->fd, MVP_SET_AUD_STC, &stc);
 	if (ioctl(md->fd, MVP_SET_AUD_BYPASS, 1) < 0) {
-		g_warning("Error setting audio streamtype: %s\n",
-			  strerror(errno));
+		g_set_error(error, mvp_output_quark(), errno,
+			    "Error setting audio streamtype: %s",
+			    strerror(errno));
 		return false;
 	}
 
-	success = mvp_set_pcm_params(md, audio_format);
+	success = mvp_set_pcm_params(md, audio_format, error);
 	if (!success)
 		return false;
 
@@ -266,7 +286,7 @@ static void mvp_output_cancel(void *data)
 }
 
 static size_t
-mvp_output_play(void *data, const void *chunk, size_t size)
+mvp_output_play(void *data, const void *chunk, size_t size, GError **error)
 {
 	struct mvp_data *md = data;
 	ssize_t ret;
@@ -275,7 +295,7 @@ mvp_output_play(void *data, const void *chunk, size_t size)
 	if (md->fd < 0) {
 		bool success;
 
-		success = mvp_output_open(md, &md->audio_format);
+		success = mvp_output_open(md, &md->audio_format, error);
 		if (!success)
 			return 0;
 	}
@@ -288,8 +308,9 @@ mvp_output_play(void *data, const void *chunk, size_t size)
 		if (ret < 0) {
 			if (errno == EINTR)
 				continue;
-			g_warning("closing mvp PCM device due to write error: "
-				  "%s\n", strerror(errno));
+
+			g_set_error(error, mvp_output_quark(), errno,
+				    "Failed to write: %s", strerror(errno));
 			return 0;
 		}
 	}
diff --git a/src/output/null_plugin.c b/src/output/null_plugin.c
index a10f5e45f..03a4b7fac 100644
--- a/src/output/null_plugin.c
+++ b/src/output/null_plugin.c
@@ -31,7 +31,8 @@ struct null_data {
 
 static void *
 null_init(G_GNUC_UNUSED const struct audio_format *audio_format,
-	  G_GNUC_UNUSED const struct config_param *param)
+	  G_GNUC_UNUSED const struct config_param *param,
+	  G_GNUC_UNUSED GError **error)
 {
 	struct null_data *nd = g_new(struct null_data, 1);
 
@@ -52,7 +53,8 @@ null_finish(void *data)
 }
 
 static bool
-null_open(void *data, struct audio_format *audio_format)
+null_open(void *data, struct audio_format *audio_format,
+	  G_GNUC_UNUSED GError **error)
 {
 	struct null_data *nd = data;
 
@@ -74,7 +76,8 @@ null_close(void *data)
 }
 
 static size_t
-null_play(void *data, G_GNUC_UNUSED const void *chunk, size_t size)
+null_play(void *data, G_GNUC_UNUSED const void *chunk, size_t size,
+	  G_GNUC_UNUSED GError **error)
 {
 	struct null_data *nd = data;
 	Timer *timer = nd->timer;
diff --git a/src/output/oss_plugin.c b/src/output/oss_plugin.c
index b070b9a9d..d4ecf88f7 100644
--- a/src/output/oss_plugin.c
+++ b/src/output/oss_plugin.c
@@ -72,6 +72,15 @@ enum oss_param {
 	OSS_BITS = 2,
 };
 
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+oss_output_quark(void)
+{
+	return g_quark_from_static_string("oss_output");
+}
+
 static enum oss_param
 oss_param_from_ioctl(unsigned param)
 {
@@ -353,7 +362,7 @@ oss_output_test_default_device(void)
 }
 
 static void *
-oss_open_default(const struct config_param *param)
+oss_open_default(GError **error)
 {
 	int i;
 	int err[G_N_ELEMENTS(default_devices)];
@@ -364,17 +373,11 @@ oss_open_default(const struct config_param *param)
 		if (ret[i] == OSS_STAT_NO_ERROR) {
 			struct oss_data *od = oss_data_new();
 			od->device = default_devices[i];
-			od->mixer = mixer_new(&oss_mixer, param);
+			od->mixer = mixer_new(&oss_mixer, NULL);
 			return od;
 		}
 	}
 
-	if (param)
-		g_warning("error trying to open specified OSS device"
-			  " at line %i\n", param->line);
-	else
-		g_warning("error trying to open default OSS device\n");
-
 	for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) {
 		const char *dev = default_devices[i];
 		switch(ret[i]) {
@@ -395,13 +398,16 @@ oss_open_default(const struct config_param *param)
 				  dev, strerror(err[i]));
 		}
 	}
-	exit(EXIT_FAILURE);
-	return NULL; /* some compilers can be dumb... */
+
+	g_set_error(error, oss_output_quark(), 0,
+		    "error trying to open default OSS device");
+	return NULL;
 }
 
 static void *
 oss_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
-		const struct config_param *param)
+		const struct config_param *param,
+		GError **error)
 {
 	const char *device = config_get_block_string(param, "device", NULL);
 	if (device != NULL) {
@@ -411,7 +417,7 @@ oss_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
 		return od;
 	}
 
-	return oss_open_default(param);
+	return oss_open_default(error);
 }
 
 static void
@@ -473,24 +479,26 @@ oss_close(struct oss_data *od)
  * Sets up the OSS device which was opened before.
  */
 static bool
-oss_setup(struct oss_data *od)
+oss_setup(struct oss_data *od, GError **error)
 {
 	int tmp;
 
 	tmp = od->audio_format.channels;
 	if (oss_set_param(od, SNDCTL_DSP_CHANNELS, &tmp)) {
-		g_warning("OSS device \"%s\" does not support %u channels: %s\n",
-			  od->device, od->audio_format.channels,
-			  strerror(errno));
+		g_set_error(error, oss_output_quark(), errno,
+			    "OSS device \"%s\" does not support %u channels: %s",
+			    od->device, od->audio_format.channels,
+			    strerror(errno));
 		return false;
 	}
 	od->audio_format.channels = tmp;
 
 	tmp = od->audio_format.sample_rate;
 	if (oss_set_param(od, SNDCTL_DSP_SPEED, &tmp)) {
-		g_warning("OSS device \"%s\" does not support %u Hz audio: %s\n",
-			  od->device, od->audio_format.sample_rate,
-			  strerror(errno));
+		g_set_error(error, oss_output_quark(), errno,
+			    "OSS device \"%s\" does not support %u Hz audio: %s",
+			    od->device, od->audio_format.sample_rate,
+			    strerror(errno));
 		return false;
 	}
 	od->audio_format.sample_rate = tmp;
@@ -511,8 +519,9 @@ oss_setup(struct oss_data *od)
 	}
 
 	if (oss_set_param(od, SNDCTL_DSP_SAMPLESIZE, &tmp)) {
-		g_warning("OSS device \"%s\" does not support %u bit audio: %s\n",
-			  od->device, tmp, strerror(errno));
+		g_set_error(error, oss_output_quark(), errno,
+			    "OSS device \"%s\" does not support %u bit audio: %s",
+			    od->device, tmp, strerror(errno));
 		return false;
 	}
 
@@ -520,17 +529,18 @@ oss_setup(struct oss_data *od)
 }
 
 static bool
-oss_open(struct oss_data *od)
+oss_open(struct oss_data *od, GError **error)
 {
 	bool success;
 
 	if ((od->fd = open(od->device, O_WRONLY)) < 0) {
-		g_warning("Error opening OSS device \"%s\": %s\n", od->device,
-			  strerror(errno));
+		g_set_error(error, oss_output_quark(), errno,
+			    "Error opening OSS device \"%s\": %s",
+			    od->device, strerror(errno));
 		return false;
 	}
 
-	success = oss_setup(od);
+	success = oss_setup(od, error);
 	if (!success) {
 		oss_close(od);
 		return false;
@@ -540,14 +550,14 @@ oss_open(struct oss_data *od)
 }
 
 static bool
-oss_output_open(void *data, struct audio_format *audio_format)
+oss_output_open(void *data, struct audio_format *audio_format, GError **error)
 {
 	bool ret;
 	struct oss_data *od = data;
 
 	od->audio_format = *audio_format;
 
-	ret = oss_open(od);
+	ret = oss_open(od, error);
 	if (!ret)
 		return false;
 
@@ -584,14 +594,14 @@ oss_output_cancel(void *data)
 }
 
 static size_t
-oss_output_play(void *data, const void *chunk, size_t size)
+oss_output_play(void *data, const void *chunk, size_t size, GError **error)
 {
 	struct oss_data *od = data;
 	ssize_t ret;
 
 	/* reopen the device since it was closed by dropBufferedAudio */
-	if (od->fd < 0 && !oss_open(od))
-		return false;
+	if (od->fd < 0 && !oss_open(od, error))
+		return 0;
 
 	while (true) {
 		ret = write(od->fd, chunk, size);
@@ -599,8 +609,9 @@ oss_output_play(void *data, const void *chunk, size_t size)
 			return (size_t)ret;
 
 		if (ret < 0 && errno != EINTR) {
-			g_warning("closing oss device \"%s\" due to write error: "
-				  "%s\n", od->device, strerror(errno));
+			g_set_error(error, oss_output_quark(), errno,
+				    "Write error on %s: %s",
+				    od->device, strerror(errno));
 			return 0;
 		}
 	}
diff --git a/src/output/osx_plugin.c b/src/output/osx_plugin.c
index fc3025ca1..7aa7659a8 100644
--- a/src/output/osx_plugin.c
+++ b/src/output/osx_plugin.c
@@ -34,6 +34,15 @@ struct osx_output {
 	size_t len;
 };
 
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+osx_output_quark(void)
+{
+	return g_quark_from_static_string("osx_output");
+}
+
 static bool
 osx_output_test_default_device(void)
 {
@@ -44,7 +53,8 @@ osx_output_test_default_device(void)
 
 static void *
 osx_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
-		G_GNUC_UNUSED const struct config_param *param)
+		G_GNUC_UNUSED const struct config_param *param,
+		G_GNUC_UNUSED GError **error)
 {
 	struct osx_output *oo = g_new(struct osx_output, 1);
 
@@ -143,7 +153,7 @@ osx_render(void *vdata,
 }
 
 static bool
-osx_output_open(void *data, struct audio_format *audio_format)
+osx_output_open(void *data, struct audio_format *audio_format, GError **error)
 {
 	struct osx_output *od = data;
 	ComponentDescription desc;
@@ -164,22 +174,25 @@ osx_output_open(void *data, struct audio_format *audio_format)
 
 	comp = FindNextComponent(NULL, &desc);
 	if (comp == 0) {
-		g_warning("Error finding OS X component\n");
+		g_set_error(error, osx_output_quark(), 0,
+			    "Error finding OS X component");
 		return false;
 	}
 
 	status = OpenAComponent(comp, &od->au);
 	if (status != noErr) {
-		g_warning("Unable to open OS X component: %s",
-			  GetMacOSStatusCommentString(status));
+		g_set_error(error, osx_output_quark(), 0,
+			    "Unable to open OS X component: %s",
+			    GetMacOSStatusCommentString(status));
 		return false;
 	}
 
 	status = AudioUnitInitialize(od->au);
 	if (status != noErr) {
 		CloseComponent(od->au);
-		g_warning("Unable to initialize OS X audio unit: %s",
-			  GetMacOSStatusCommentString(status));
+		g_set_error(error, osx_output_quark(), 0,
+			    "Unable to initialize OS X audio unit: %s",
+			    GetMacOSStatusCommentString(status));
 		return false;
 	}
 
@@ -193,7 +206,8 @@ osx_output_open(void *data, struct audio_format *audio_format)
 	if (result != noErr) {
 		AudioUnitUninitialize(od->au);
 		CloseComponent(od->au);
-		g_warning("unable to set callback for OS X audio unit\n");
+		g_set_error(error, osx_output_quark(), 0,
+			    "unable to set callback for OS X audio unit");
 		return false;
 	}
 
@@ -218,7 +232,8 @@ osx_output_open(void *data, struct audio_format *audio_format)
 	if (result != noErr) {
 		AudioUnitUninitialize(od->au);
 		CloseComponent(od->au);
-		g_warning("Unable to set format on OS X device\n");
+		g_set_error(error, osx_output_quark(), 0,
+			    "Unable to set format on OS X device");
 		return false;
 	}
 
@@ -232,8 +247,9 @@ osx_output_open(void *data, struct audio_format *audio_format)
 
 	status = AudioOutputUnitStart(od->au);
 	if (status != 0) {
-		g_warning("unable to start audio output: %s",
-			  GetMacOSStatusCommentString(status));
+		g_set_error(error, osx_output_quark(), 0,
+			    "unable to start audio output: %s",
+			    GetMacOSStatusCommentString(status));
 		return false;
 	}
 
@@ -241,7 +257,8 @@ osx_output_open(void *data, struct audio_format *audio_format)
 }
 
 static size_t
-osx_output_play(void *data, const void *chunk, size_t size)
+osx_output_play(void *data, const void *chunk, size_t size,
+		G_GNUC_UNUSED GError **error)
 {
 	struct osx_output *od = data;
 	size_t start, nbytes;
diff --git a/src/output/pulse_plugin.c b/src/output/pulse_plugin.c
index b8fe5ad88..dcdd427db 100644
--- a/src/output/pulse_plugin.c
+++ b/src/output/pulse_plugin.c
@@ -32,6 +32,15 @@ struct pulse_data {
 	char *sink;
 };
 
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+pulse_output_quark(void)
+{
+	return g_quark_from_static_string("pulse_output");
+}
+
 static struct pulse_data *pulse_new_data(void)
 {
 	struct pulse_data *ret;
@@ -53,7 +62,7 @@ static void pulse_free_data(struct pulse_data *pd)
 
 static void *
 pulse_init(G_GNUC_UNUSED const struct audio_format *audio_format,
-	   const struct config_param *param)
+	   const struct config_param *param, G_GNUC_UNUSED GError **error)
 {
 	struct pulse_data *pd;
 
@@ -98,7 +107,7 @@ static bool pulse_test_default_device(void)
 }
 
 static bool
-pulse_open(void *data, struct audio_format *audio_format)
+pulse_open(void *data, struct audio_format *audio_format, GError **error_r)
 {
 	struct pulse_data *pd = data;
 	pa_sample_spec ss;
@@ -117,9 +126,9 @@ pulse_open(void *data, struct audio_format *audio_format)
 			      &ss, NULL, NULL,
 			      &error);
 	if (!pd->s) {
-		g_warning("Cannot connect to server in PulseAudio output "
-			  "\"%s\": %s\n",
-			  pd->name, pa_strerror(error));
+		g_set_error(error_r, pulse_output_quark(), error,
+			    "Cannot connect to PulseAudio server: %s",
+			    pa_strerror(error));
 		return false;
 	}
 
@@ -151,15 +160,14 @@ static void pulse_close(void *data)
 }
 
 static size_t
-pulse_play(void *data, const void *chunk, size_t size)
+pulse_play(void *data, const void *chunk, size_t size, GError **error_r)
 {
 	struct pulse_data *pd = data;
 	int error;
 
 	if (pa_simple_write(pd->s, chunk, size, &error) < 0) {
-		g_warning("PulseAudio output \"%s\" disconnecting due to "
-			  "write error: %s\n",
-			  pd->name, pa_strerror(error));
+		g_set_error(error_r, pulse_output_quark(), error,
+			    "%s", pa_strerror(error));
 		return 0;
 	}
 
diff --git a/src/output/shout_plugin.c b/src/output/shout_plugin.c
index 2a8f2f189..2ad7e7b56 100644
--- a/src/output/shout_plugin.c
+++ b/src/output/shout_plugin.c
@@ -53,6 +53,15 @@ struct shout_data {
 
 static int shout_init_count;
 
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+shout_output_quark(void)
+{
+	return g_quark_from_static_string("shout_output");
+}
+
 static const struct encoder_plugin *
 shout_encoder_plugin_get(const char *name)
 {
@@ -97,7 +106,8 @@ static void free_shout_data(struct shout_data *sd)
 
 static void *
 my_shout_init_driver(const struct audio_format *audio_format,
-		     const struct config_param *param)
+		     const struct config_param *param,
+		     GError **error)
 {
 	struct shout_data *sd;
 	char *test;
@@ -107,7 +117,6 @@ my_shout_init_driver(const struct audio_format *audio_format,
 	char *passwd;
 	const char *encoding;
 	const struct encoder_plugin *encoder_plugin;
-	GError *error = NULL;
 	unsigned shout_format;
 	unsigned protocol;
 	const char *user;
@@ -131,8 +140,9 @@ my_shout_init_driver(const struct audio_format *audio_format,
 
 	port = config_get_block_unsigned(param, "port", 0);
 	if (port == 0) {
-		g_error("shout port \"%s\" is not a positive integer, line %i\n",
-			block_param->value, block_param->line);
+		g_set_error(error, shout_output_quark(), 0,
+			    "shout port must be configured");
+		return NULL;
 	}
 
 	check_block_param("password");
@@ -150,27 +160,33 @@ my_shout_init_driver(const struct audio_format *audio_format,
 		sd->quality = strtod(value, &test);
 
 		if (*test != '\0' || sd->quality < -1.0 || sd->quality > 10.0) {
-			g_error("shout quality \"%s\" is not a number in the "
-				"range -1 to 10, line %i",
-				value, param->line);
+			g_set_error(error, shout_output_quark(), 0,
+				    "shout quality \"%s\" is not a number in the "
+				    "range -1 to 10, line %i",
+				    value, param->line);
+			return NULL;
 		}
 
 		if (config_get_block_string(param, "bitrate", NULL) != NULL) {
-			g_error("quality and bitrate are "
-				"both defined for shout output (line %i)",
-				param->line);
+			g_set_error(error, shout_output_quark(), 0,
+				    "quality and bitrate are "
+				    "both defined");
+			return NULL;
 		}
 	} else {
 		value = config_get_block_string(param, "bitrate", NULL);
-		if (value == NULL)
-			g_error("neither bitrate nor quality defined for shout "
-				"output at line %i", param->line);
+		if (value == NULL) {
+			g_set_error(error, shout_output_quark(), 0,
+				    "neither bitrate nor quality defined");
+			return NULL;
+		}
 
 		sd->bitrate = strtol(value, &test, 10);
 
 		if (*test != '\0' || sd->bitrate <= 0) {
-			g_error("bitrate at line %i should be a positive integer "
-				"\n", param->line);
+			g_set_error(error, shout_output_quark(), 0,
+				    "bitrate must be a positive integer");
+			return NULL;
 		}
 	}
 
@@ -178,13 +194,16 @@ my_shout_init_driver(const struct audio_format *audio_format,
 
 	encoding = config_get_block_string(param, "encoding", "ogg");
 	encoder_plugin = shout_encoder_plugin_get(encoding);
-	if (encoder_plugin == NULL)
-		g_error("couldn't find shout encoder plugin \"%s\"\n",
-			encoding);
+	if (encoder_plugin == NULL) {
+		g_set_error(error, shout_output_quark(), 0,
+			    "couldn't find shout encoder plugin \"%s\"",
+			    encoding);
+		return NULL;
+	}
 
-	sd->encoder = encoder_init(encoder_plugin, param, &error);
+	sd->encoder = encoder_init(encoder_plugin, param, error);
 	if (sd->encoder == NULL)
-		g_error("%s", error->message);
+		return NULL;
 
 	if (strcmp(encoding, "mp3") == 0 || strcmp(encoding, "lame") == 0)
 		shout_format = SHOUT_FORMAT_MP3;
@@ -194,20 +213,24 @@ my_shout_init_driver(const struct audio_format *audio_format,
 	value = config_get_block_string(param, "protocol", NULL);
 	if (value != NULL) {
 		if (0 == strcmp(value, "shoutcast") &&
-		    0 != strcmp(encoding, "mp3"))
-			g_error("you cannot stream \"%s\" to shoutcast, use mp3\n",
-				encoding);
-		else if (0 == strcmp(value, "shoutcast"))
+		    0 != strcmp(encoding, "mp3")) {
+			g_set_error(error, shout_output_quark(), 0,
+				    "you cannot stream \"%s\" to shoutcast, use mp3",
+				    encoding);
+			return NULL;
+		} else if (0 == strcmp(value, "shoutcast"))
 			protocol = SHOUT_PROTOCOL_ICY;
 		else if (0 == strcmp(value, "icecast1"))
 			protocol = SHOUT_PROTOCOL_XAUDIOCAST;
 		else if (0 == strcmp(value, "icecast2"))
 			protocol = SHOUT_PROTOCOL_HTTP;
-		else
-			g_error("shout protocol \"%s\" is not \"shoutcast\" or "
-				"\"icecast1\"or "
-				"\"icecast2\", line %i\n",
-				value, param->line);
+		else {
+			g_set_error(error, shout_output_quark(), 0,
+				    "shout protocol \"%s\" is not \"shoutcast\" or "
+				    "\"icecast1\"or \"icecast2\"",
+				    value);
+			return NULL;
+		}
 	} else {
 		protocol = SHOUT_PROTOCOL_HTTP;
 	}
@@ -223,8 +246,9 @@ my_shout_init_driver(const struct audio_format *audio_format,
 	    != SHOUTERR_SUCCESS ||
 	    shout_set_protocol(sd->shout_conn, protocol) != SHOUTERR_SUCCESS ||
 	    shout_set_agent(sd->shout_conn, "MPD") != SHOUTERR_SUCCESS) {
-		g_error("error configuring shout defined at line %i: %s\n",
-			param->line, shout_get_error(sd->shout_conn));
+		g_set_error(error, shout_output_quark(), 0,
+			    "%s", shout_get_error(sd->shout_conn));
+		return NULL;
 	}
 
 	/* optional paramters */
@@ -233,14 +257,16 @@ my_shout_init_driver(const struct audio_format *audio_format,
 
 	value = config_get_block_string(param, "genre", NULL);
 	if (value != NULL && shout_set_genre(sd->shout_conn, value)) {
-		g_error("error configuring shout defined at line %i: %s\n",
-			param->line, shout_get_error(sd->shout_conn));
+		g_set_error(error, shout_output_quark(), 0,
+			    "%s", shout_get_error(sd->shout_conn));
+		return NULL;
 	}
 
 	value = config_get_block_string(param, "description", NULL);
 	if (value != NULL && shout_set_description(sd->shout_conn, value)) {
-		g_error("error configuring shout defined at line %i: %s\n",
-			param->line, shout_get_error(sd->shout_conn));
+		g_set_error(error, shout_output_quark(), 0,
+			    "%s", shout_get_error(sd->shout_conn));
+		return NULL;
 	}
 
 	{
@@ -269,7 +295,7 @@ my_shout_init_driver(const struct audio_format *audio_format,
 }
 
 static bool
-handle_shout_error(struct shout_data *sd, int err)
+handle_shout_error(struct shout_data *sd, int err, GError **error)
 {
 	switch (err) {
 	case SHOUTERR_SUCCESS:
@@ -277,17 +303,19 @@ handle_shout_error(struct shout_data *sd, int err)
 
 	case SHOUTERR_UNCONNECTED:
 	case SHOUTERR_SOCKET:
-		g_warning("Lost shout connection to %s:%i: %s\n",
-			  shout_get_host(sd->shout_conn),
-			  shout_get_port(sd->shout_conn),
-			  shout_get_error(sd->shout_conn));
+		g_set_error(error, shout_output_quark(), err,
+			    "Lost shout connection to %s:%i: %s",
+			    shout_get_host(sd->shout_conn),
+			    shout_get_port(sd->shout_conn),
+			    shout_get_error(sd->shout_conn));
 		return false;
 
 	default:
-		g_warning("shout: connection to %s:%i error: %s\n",
-			  shout_get_host(sd->shout_conn),
-			  shout_get_port(sd->shout_conn),
-			  shout_get_error(sd->shout_conn));
+		g_set_error(error, shout_output_quark(), err,
+			    "connection to %s:%i error: %s",
+			    shout_get_host(sd->shout_conn),
+			    shout_get_port(sd->shout_conn),
+			    shout_get_error(sd->shout_conn));
 		return false;
 	}
 
@@ -295,7 +323,7 @@ handle_shout_error(struct shout_data *sd, int err)
 }
 
 static bool
-write_page(struct shout_data *sd)
+write_page(struct shout_data *sd, GError **error)
 {
 	int err;
 
@@ -308,7 +336,7 @@ write_page(struct shout_data *sd)
 
 	shout_sync(sd->shout_conn);
 	err = shout_send(sd->shout_conn, sd->buf.data, sd->buf.len);
-	if (!handle_shout_error(sd, err))
+	if (!handle_shout_error(sd, err, error))
 		return false;
 
 	return true;
@@ -320,7 +348,7 @@ static void close_shout_conn(struct shout_data * sd)
 
 	if (sd->encoder != NULL) {
 		if (encoder_flush(sd->encoder, NULL))
-			write_page(sd);
+			write_page(sd, NULL);
 
 		encoder_close(sd->encoder);
 	}
@@ -362,7 +390,7 @@ static void my_shout_close_device(void *data)
 }
 
 static bool
-shout_connect(struct shout_data *sd)
+shout_connect(struct shout_data *sd, GError **error)
 {
 	int state;
 
@@ -373,58 +401,47 @@ shout_connect(struct shout_data *sd)
 		return true;
 
 	default:
-		g_warning("problem opening connection to shout server %s:%i: %s\n",
-			  shout_get_host(sd->shout_conn),
-			  shout_get_port(sd->shout_conn),
-			  shout_get_error(sd->shout_conn));
+		g_set_error(error, shout_output_quark(), 0,
+			    "problem opening connection to shout server %s:%i: %s",
+			    shout_get_host(sd->shout_conn),
+			    shout_get_port(sd->shout_conn),
+			    shout_get_error(sd->shout_conn));
 		return false;
 	}
 }
 
 static bool
-my_shout_open_device(void *data, struct audio_format *audio_format)
+my_shout_open_device(void *data, struct audio_format *audio_format,
+		     GError **error)
 {
 	struct shout_data *sd = (struct shout_data *)data;
 	bool ret;
-	GError *error = NULL;
 
-	ret = shout_connect(sd);
+	ret = shout_connect(sd, error);
 	if (!ret)
 		return false;
 
 	sd->buf.len = 0;
 
-	ret = encoder_open(sd->encoder, audio_format, &error);
+	ret = encoder_open(sd->encoder, audio_format, error) &&
+		write_page(sd, error);
 	if (!ret) {
 		shout_close(sd->shout_conn);
-		g_warning("%s", error->message);
-		g_error_free(error);
 		return false;
 	}
 
-	write_page(sd);
-
 	return true;
 }
 
 static size_t
-my_shout_play(void *data, const void *chunk, size_t size)
+my_shout_play(void *data, const void *chunk, size_t size, GError **error)
 {
 	struct shout_data *sd = (struct shout_data *)data;
-	bool ret;
-	GError *error = NULL;
 
-	ret = encoder_write(sd->encoder, chunk, size, &error);
-	if (!ret) {
-		g_warning("%s", error->message);
-		g_error_free(error);
-		return false;
-	}
-
-	if (!write_page(sd))
-		return 0;
-
-	return size;
+	return encoder_write(sd->encoder, chunk, size, error) &&
+		write_page(sd, error)
+		? size
+		: 0;
 }
 
 static bool
@@ -432,7 +449,7 @@ my_shout_pause(void *data)
 {
 	static const char silence[1020];
 
-	return my_shout_play(data, silence, sizeof(silence));
+	return my_shout_play(data, silence, sizeof(silence), NULL);
 }
 
 static void
@@ -479,7 +496,9 @@ static void my_shout_set_tag(void *data,
 			return;
 		}
 
-		write_page(sd);
+		ret = write_page(sd, NULL);
+		if (!ret)
+			return;
 
 		ret = encoder_tag(sd->encoder, tag, &error);
 		if (!ret) {
@@ -499,7 +518,7 @@ static void my_shout_set_tag(void *data,
 		}
 	}
 
-	write_page(sd);
+	write_page(sd, NULL);
 }
 
 const struct audio_output_plugin shoutPlugin = {
diff --git a/src/output_init.c b/src/output_init.c
index ab26e76ef..004b73e84 100644
--- a/src/output_init.c
+++ b/src/output_init.c
@@ -48,6 +48,7 @@ audio_output_init(struct audio_output *ao, const struct config_param *param)
 	char *format = NULL;
 	struct block_param *bp = NULL;
 	const struct audio_output_plugin *plugin = NULL;
+	GError *error = NULL;
 
 	if (param) {
 		const char *type = NULL;
@@ -97,7 +98,6 @@ audio_output_init(struct audio_output *ao, const struct config_param *param)
 	pcm_convert_init(&ao->convert_state);
 
 	if (format) {
-		GError *error = NULL;
 		bool ret;
 
 		ret = audio_format_parse(&ao->config_audio_format, format,
@@ -114,9 +114,14 @@ audio_output_init(struct audio_output *ao, const struct config_param *param)
 
 	ao->data = ao_plugin_init(plugin,
 				  format ? &ao->config_audio_format : NULL,
-				  param);
-	if (ao->data == NULL)
+				  param, &error);
+	if (ao->data == NULL) {
+		g_warning("Failed to initialize \"%s\" [%s]: %s",
+			  ao->name, ao->plugin->name,
+			  error->message);
+		g_error_free(error);
 		return false;
+	}
 
 	return true;
 }
diff --git a/src/output_plugin.h b/src/output_plugin.h
index 2def73132..1c04ea921 100644
--- a/src/output_plugin.h
+++ b/src/output_plugin.h
@@ -19,6 +19,8 @@
 #ifndef MPD_OUTPUT_PLUGIN_H
 #define MPD_OUTPUT_PLUGIN_H
 
+#include <glib.h>
+
 #include <stdbool.h>
 #include <stddef.h>
 
@@ -45,16 +47,18 @@ struct audio_output_plugin {
 	 * Configure and initialize the device, but do not open it
 	 * yet.
 	 *
-	 * @param ao an opaque pointer to the audio_output structure
 	 * @param audio_format the configured audio format, or NULL if
 	 * none is configured
 	 * @param param the configuration section, or NULL if there is
 	 * no configuration
+	 * @param error location to store the error occuring, or NULL
+	 * to ignore errors
 	 * @return NULL on error, or an opaque pointer to the plugin's
 	 * data
 	 */
 	void *(*init)(const struct audio_format *audio_format,
-		      const struct config_param *param);
+		      const struct config_param *param,
+		      GError **error);
 
 	/**
 	 * Free resources allocated by this device.
@@ -72,10 +76,14 @@ struct audio_output_plugin {
 
 	/**
 	 * Really open the device.
+	 *
 	 * @param audio_format the audio format in which data is going
 	 * to be delivered; may be modified by the plugin
+	 * @param error location to store the error occuring, or NULL
+	 * to ignore errors
 	 */
-	bool (*open)(void *data, struct audio_format *audio_format);
+	bool (*open)(void *data, struct audio_format *audio_format,
+		     GError **error);
 
 	/**
 	 * Close the device.
@@ -91,9 +99,12 @@ struct audio_output_plugin {
 	/**
 	 * Play a chunk of audio data.
 	 *
+	 * @param error location to store the error occuring, or NULL
+	 * to ignore errors
 	 * @return the number of bytes played, or 0 on error
 	 */
-	size_t (*play)(void *data, const void *chunk, size_t size);
+	size_t (*play)(void *data, const void *chunk, size_t size,
+		       GError **error);
 
 	/**
 	 * Try to cancel data which may still be in the device's
@@ -126,9 +137,10 @@ ao_plugin_test_default_device(const struct audio_output_plugin *plugin)
 static inline void *
 ao_plugin_init(const struct audio_output_plugin *plugin,
 	       const struct audio_format *audio_format,
-	       const struct config_param *param)
+	       const struct config_param *param,
+	       GError **error)
 {
-	return plugin->init(audio_format, param);
+	return plugin->init(audio_format, param, error);
 }
 
 static inline void
@@ -147,9 +159,10 @@ ao_plugin_get_mixer(const struct audio_output_plugin *plugin, void *data)
 
 static inline bool
 ao_plugin_open(const struct audio_output_plugin *plugin,
-	       void *data, struct audio_format *audio_format)
+	       void *data, struct audio_format *audio_format,
+	       GError **error)
 {
-	return plugin->open(data, audio_format);
+	return plugin->open(data, audio_format, error);
 }
 
 static inline void
@@ -168,9 +181,10 @@ ao_plugin_send_tag(const struct audio_output_plugin *plugin,
 
 static inline size_t
 ao_plugin_play(const struct audio_output_plugin *plugin,
-	       void *data, const void *chunk, size_t size)
+	       void *data, const void *chunk, size_t size,
+	       GError **error)
 {
-	return plugin->play(data, chunk, size);
+	return plugin->play(data, chunk, size, error);
 }
 
 static inline void
diff --git a/src/output_thread.c b/src/output_thread.c
index 91fd8d406..b5a4c6117 100644
--- a/src/output_thread.c
+++ b/src/output_thread.c
@@ -56,6 +56,7 @@ static void ao_play(struct audio_output *ao)
 {
 	const char *data = ao->args.play.data;
 	size_t size = ao->args.play.size;
+	GError *error = NULL;
 
 	assert(size > 0);
 	assert(size % audio_format_frame_size(&ao->in_audio_format) == 0);
@@ -76,9 +77,14 @@ static void ao_play(struct audio_output *ao)
 	while (size > 0) {
 		size_t nbytes;
 
-		nbytes = ao_plugin_play(ao->plugin, ao->data, data, size);
+		nbytes = ao_plugin_play(ao->plugin, ao->data, data, size,
+					&error);
 		if (nbytes == 0) {
 			/* play()==0 means failure */
+			g_warning("\"%s\" [%s] failed to play: %s",
+				  ao->name, ao->plugin->name, error->message);
+			g_error_free(error);
+
 			ao_plugin_cancel(ao->plugin, ao->data);
 			ao_close(ao);
 			break;
@@ -114,6 +120,7 @@ static gpointer audio_output_task(gpointer arg)
 {
 	struct audio_output *ao = arg;
 	bool ret;
+	GError *error;
 
 	while (1) {
 		switch (ao->command) {
@@ -123,15 +130,23 @@ static gpointer audio_output_task(gpointer arg)
 		case AO_COMMAND_OPEN:
 			assert(!ao->open);
 
+			error = NULL;
 			ret = ao_plugin_open(ao->plugin, ao->data,
-					     &ao->out_audio_format);
+					     &ao->out_audio_format,
+					     &error);
 
 			assert(!ao->open);
 			if (ret) {
 				pcm_convert_init(&ao->convert_state);
 				ao->open = true;
-			} else
+			} else {
+				g_warning("Failed to open \"%s\" [%s]: %s",
+					  ao->name, ao->plugin->name,
+					  error->message);
+				g_error_free(error);
+
 				ao->reopen_after = time(NULL) + REOPEN_AFTER;
+			}
 
 			ao_command_finished(ao);
 			break;