diff --git a/Makefile.am b/Makefile.am
index 14cea53a9..5f0291d7e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -51,7 +51,6 @@ src_mpd_LDADD = \
 mpd_headers = \
 	src/check.h \
 	src/ack.h \
-	src/audio_format.h \
 	src/filter_internal.h \
 	src/command.h \
 	src/conf.h \
@@ -97,7 +96,7 @@ src_mpd_SOURCES = \
 	src/notify.cxx src/notify.hxx \
 	src/AudioConfig.cxx src/AudioConfig.hxx \
 	src/CheckAudioFormat.cxx src/CheckAudioFormat.hxx \
-	src/audio_format.c \
+	src/AudioFormat.cxx src/AudioFormat.hxx \
 	src/AudioParser.cxx src/AudioParser.hxx \
 	src/protocol/ArgParser.cxx src/protocol/ArgParser.hxx \
 	src/protocol/Result.cxx src/protocol/Result.hxx \
@@ -1173,8 +1172,7 @@ test_run_decoder_SOURCES = test/run_decoder.cxx \
 	src/Tag.cxx src/TagNames.c src/TagPool.cxx src/TagHandler.cxx \
 	src/ReplayGainInfo.cxx \
 	src/fd_util.c \
-	src/CheckAudioFormat.cxx \
-	src/audio_format.c \
+	src/AudioFormat.cxx src/CheckAudioFormat.cxx \
 	$(ARCHIVE_SRC) \
 	$(INPUT_SRC) \
 	$(TAG_SRC) \
@@ -1221,7 +1219,7 @@ test_run_filter_SOURCES = test/run_filter.cxx \
 	test/stdbin.h \
 	src/FilterPlugin.cxx src/FilterRegistry.cxx \
 	src/CheckAudioFormat.cxx \
-	src/audio_format.c \
+	src/AudioFormat.cxx \
 	src/AudioParser.cxx \
 	src/ReplayGainInfo.cxx \
 	src/AudioCompress/compress.c
@@ -1240,7 +1238,7 @@ test_run_encoder_SOURCES = test/run_encoder.cxx \
 	test/stdbin.h \
 	src/Tag.cxx src/TagNames.c src/TagPool.cxx \
 	src/CheckAudioFormat.cxx \
-	src/audio_format.c \
+	src/AudioFormat.cxx \
 	src/AudioParser.cxx
 test_run_encoder_LDADD = \
 	$(ENCODER_LIBS) \
@@ -1258,7 +1256,7 @@ test_test_vorbis_encoder_SOURCES = test/test_vorbis_encoder.cxx \
 	test/stdbin.h \
 	src/Tag.cxx src/TagNames.c src/TagPool.cxx \
 	src/CheckAudioFormat.cxx \
-	src/audio_format.c \
+	src/AudioFormat.cxx \
 	src/AudioParser.cxx \
 	$(ENCODER_SRC)
 test_test_vorbis_encoder_CPPFLAGS = $(AM_CPPFLAGS) \
@@ -1289,7 +1287,7 @@ test_run_normalize_LDADD = \
 	$(GLIB_LIBS)
 
 test_run_convert_SOURCES = test/run_convert.cxx \
-	src/audio_format.c \
+	src/AudioFormat.cxx \
 	src/CheckAudioFormat.cxx \
 	src/AudioParser.cxx
 test_run_convert_LDADD = \
@@ -1313,7 +1311,7 @@ test_run_output_SOURCES = test/run_output.cxx \
 	test/stdbin.h \
 	src/IOThread.cxx \
 	src/CheckAudioFormat.cxx \
-	src/audio_format.c \
+	src/AudioFormat.cxx \
 	src/AudioParser.cxx \
 	src/Timer.cxx src/clock.c \
 	src/Tag.cxx src/TagNames.c src/TagPool.cxx \
diff --git a/src/AudioConfig.cxx b/src/AudioConfig.cxx
index e546aed27..9ead61afe 100644
--- a/src/AudioConfig.cxx
+++ b/src/AudioConfig.cxx
@@ -19,18 +19,19 @@
 
 #include "config.h"
 #include "AudioConfig.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 #include "AudioParser.hxx"
 #include "conf.h"
 #include "mpd_error.h"
 
-static struct audio_format configured_audio_format;
+static AudioFormat configured_audio_format;
 
-void getOutputAudioFormat(const struct audio_format *inAudioFormat,
-			  struct audio_format *outAudioFormat)
+AudioFormat
+getOutputAudioFormat(AudioFormat inAudioFormat)
 {
-	*outAudioFormat = *inAudioFormat;
-	audio_format_mask_apply(outAudioFormat, &configured_audio_format);
+	AudioFormat out_audio_format = inAudioFormat;
+	out_audio_format.ApplyMask(configured_audio_format);
+	return out_audio_format;
 }
 
 void initAudioConfig(void)
@@ -42,7 +43,7 @@ void initAudioConfig(void)
 	if (param == NULL)
 		return;
 
-	ret = audio_format_parse(&configured_audio_format, param->value,
+	ret = audio_format_parse(configured_audio_format, param->value,
 				 true, &error);
 	if (!ret)
 		MPD_ERROR("error parsing line %i: %s",
diff --git a/src/AudioConfig.hxx b/src/AudioConfig.hxx
index 717a8e2e7..ebe202974 100644
--- a/src/AudioConfig.hxx
+++ b/src/AudioConfig.hxx
@@ -20,10 +20,10 @@
 #ifndef MPD_AUDIO_CONFIG_HXX
 #define MPD_AUDIO_CONFIG_HXX
 
-struct audio_format;
+struct AudioFormat;
 
-void getOutputAudioFormat(const struct audio_format *inFormat,
-			  struct audio_format *outFormat);
+AudioFormat
+getOutputAudioFormat(AudioFormat inFormat);
 
 /* make sure initPlayerData is called before this function!! */
 void initAudioConfig(void);
diff --git a/src/audio_format.c b/src/AudioFormat.cxx
similarity index 56%
rename from src/audio_format.c
rename to src/AudioFormat.cxx
index cfe3a4cae..04636c1e2 100644
--- a/src/audio_format.c
+++ b/src/AudioFormat.cxx
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * Copyright (C) 2003-2013 The Music Player Daemon Project
  * http://www.musicpd.org
  *
  * This program is free software; you can redistribute it and/or modify
@@ -17,53 +17,52 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 
 #include <assert.h>
 #include <stdio.h>
 
 void
-audio_format_mask_apply(struct audio_format *af,
-			const struct audio_format *mask)
+AudioFormat::ApplyMask(AudioFormat mask)
 {
-	assert(audio_format_valid(af));
-	assert(audio_format_mask_valid(mask));
+	assert(IsValid());
+	assert(mask.IsMaskValid());
 
-	if (mask->sample_rate != 0)
-		af->sample_rate = mask->sample_rate;
+	if (mask.sample_rate != 0)
+		sample_rate = mask.sample_rate;
 
-	if (mask->format != SAMPLE_FORMAT_UNDEFINED)
-		af->format = mask->format;
+	if (mask.format != SampleFormat::UNDEFINED)
+		format = mask.format;
 
-	if (mask->channels != 0)
-		af->channels = mask->channels;
+	if (mask.channels != 0)
+		channels = mask.channels;
 
-	assert(audio_format_valid(af));
+	assert(IsValid());
 }
 
 const char *
-sample_format_to_string(enum sample_format format)
+sample_format_to_string(SampleFormat format)
 {
 	switch (format) {
-	case SAMPLE_FORMAT_UNDEFINED:
+	case SampleFormat::UNDEFINED:
 		return "?";
 
-	case SAMPLE_FORMAT_S8:
+	case SampleFormat::S8:
 		return "8";
 
-	case SAMPLE_FORMAT_S16:
+	case SampleFormat::S16:
 		return "16";
 
-	case SAMPLE_FORMAT_S24_P32:
+	case SampleFormat::S24_P32:
 		return "24";
 
-	case SAMPLE_FORMAT_S32:
+	case SampleFormat::S32:
 		return "32";
 
-	case SAMPLE_FORMAT_FLOAT:
+	case SampleFormat::FLOAT:
 		return "f";
 
-	case SAMPLE_FORMAT_DSD:
+	case SampleFormat::DSD:
 		return "dsd";
 	}
 
@@ -73,15 +72,14 @@ sample_format_to_string(enum sample_format format)
 }
 
 const char *
-audio_format_to_string(const struct audio_format *af,
+audio_format_to_string(const AudioFormat af,
 		       struct audio_format_string *s)
 {
-	assert(af != NULL);
-	assert(s != NULL);
+	assert(s != nullptr);
 
 	snprintf(s->buffer, sizeof(s->buffer), "%u:%s:%u",
-		 af->sample_rate, sample_format_to_string(af->format),
-		 af->channels);
+		 af.sample_rate, sample_format_to_string(af.format),
+		 af.channels);
 
 	return s->buffer;
 }
diff --git a/src/AudioFormat.hxx b/src/AudioFormat.hxx
new file mode 100644
index 000000000..eb3f9b062
--- /dev/null
+++ b/src/AudioFormat.hxx
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2003-2013 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_AUDIO_FORMAT_HXX
+#define MPD_AUDIO_FORMAT_HXX
+
+#include "gcc.h"
+
+#include <stdint.h>
+#include <assert.h>
+
+#if defined(WIN32) && GCC_CHECK_VERSION(4,6)
+/* on WIN32, "FLOAT" is already defined, and this triggers -Wshadow */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wshadow"
+#endif
+
+enum class SampleFormat : uint8_t {
+	UNDEFINED = 0,
+
+	S8,
+	S16,
+
+	/**
+	 * Signed 24 bit integer samples, packed in 32 bit integers
+	 * (the most significant byte is filled with the sign bit).
+	 */
+	S24_P32,
+
+	S32,
+
+	/**
+	 * 32 bit floating point samples in the host's format.  The
+	 * range is -1.0f to +1.0f.
+	 */
+	FLOAT,
+
+	/**
+	 * Direct Stream Digital.  1-bit samples; each frame has one
+	 * byte (8 samples) per channel.
+	 */
+	DSD,
+};
+
+#if defined(WIN32) && GCC_CHECK_VERSION(4,6)
+#pragma GCC diagnostic pop
+#endif
+
+static constexpr unsigned MAX_CHANNELS = 8;
+
+/**
+ * This structure describes the format of a raw PCM stream.
+ */
+struct AudioFormat {
+	/**
+	 * The sample rate in Hz.  A better name for this attribute is
+	 * "frame rate", because technically, you have two samples per
+	 * frame in stereo sound.
+	 */
+	uint32_t sample_rate;
+
+	/**
+	 * The format samples are stored in.  See the #sample_format
+	 * enum for valid values.
+	 */
+	SampleFormat format;
+
+	/**
+	 * The number of channels.  Only mono (1) and stereo (2) are
+	 * fully supported currently.
+	 */
+	uint8_t channels;
+
+	AudioFormat() = default;
+
+	constexpr AudioFormat(uint32_t _sample_rate,
+			      SampleFormat _format, uint8_t _channels)
+		:sample_rate(_sample_rate),
+		 format(_format), channels(_channels) {}
+
+	static constexpr AudioFormat Undefined() {
+		return AudioFormat(0, SampleFormat::UNDEFINED,0);
+	}
+
+	/**
+	 * Clears the #audio_format object, i.e. sets all attributes to an
+	 * undefined (invalid) value.
+	 */
+	void Clear() {
+		sample_rate = 0;
+		format = SampleFormat::UNDEFINED;
+		channels = 0;
+	}
+
+	/**
+	 * Checks whether the object has a defined value.
+	 */
+	constexpr bool IsDefined() const {
+		return sample_rate != 0;
+	}
+
+	/**
+	 * Checks whether the object is full, i.e. all attributes are
+	 * defined.  This is more complete than IsDefined(), but
+	 * slower.
+	 */
+	constexpr bool IsFullyDefined() const {
+		return sample_rate != 0 && format != SampleFormat::UNDEFINED &&
+			channels != 0;
+	}
+
+	/**
+	 * Checks whether the object has at least one defined value.
+	 */
+	constexpr bool IsMaskDefined() const {
+		return sample_rate != 0 || format != SampleFormat::UNDEFINED ||
+			channels != 0;
+	}
+
+	bool IsValid() const;
+	bool IsMaskValid() const;
+
+	constexpr bool operator==(const AudioFormat other) const {
+		return sample_rate == other.sample_rate &&
+			format == other.format &&
+			channels == other.channels;
+	}
+
+	constexpr bool operator!=(const AudioFormat other) const {
+		return !(*this == other);
+	}
+
+	void ApplyMask(AudioFormat mask);
+
+	/**
+	 * Returns the size of each (mono) sample in bytes.
+	 */
+	unsigned GetSampleSize() const;
+
+	/**
+	 * Returns the size of each full frame in bytes.
+	 */
+	unsigned GetFrameSize() const;
+
+	/**
+	 * Returns the floating point factor which converts a time
+	 * span to a storage size in bytes.
+	 */
+	double GetTimeToSize() const;
+};
+
+/**
+ * Buffer for audio_format_string().
+ */
+struct audio_format_string {
+	char buffer[24];
+};
+
+/**
+ * Checks whether the sample rate is valid.
+ *
+ * @param sample_rate the sample rate in Hz
+ */
+static constexpr inline bool
+audio_valid_sample_rate(unsigned sample_rate)
+{
+	return sample_rate > 0 && sample_rate < (1 << 30);
+}
+
+/**
+ * Checks whether the sample format is valid.
+ *
+ * @param bits the number of significant bits per sample
+ */
+static inline bool
+audio_valid_sample_format(SampleFormat format)
+{
+	switch (format) {
+	case SampleFormat::S8:
+	case SampleFormat::S16:
+	case SampleFormat::S24_P32:
+	case SampleFormat::S32:
+	case SampleFormat::FLOAT:
+	case SampleFormat::DSD:
+		return true;
+
+	case SampleFormat::UNDEFINED:
+		break;
+	}
+
+	return false;
+}
+
+/**
+ * Checks whether the number of channels is valid.
+ */
+static constexpr inline bool
+audio_valid_channel_count(unsigned channels)
+{
+	return channels >= 1 && channels <= MAX_CHANNELS;
+}
+
+/**
+ * Returns false if the format is not valid for playback with MPD.
+ * This function performs some basic validity checks.
+ */
+inline bool
+AudioFormat::IsValid() const
+{
+	return audio_valid_sample_rate(sample_rate) &&
+		audio_valid_sample_format(format) &&
+		audio_valid_channel_count(channels);
+}
+
+/**
+ * Returns false if the format mask is not valid for playback with
+ * MPD.  This function performs some basic validity checks.
+ */
+inline bool
+AudioFormat::IsMaskValid() const
+{
+	return (sample_rate == 0 ||
+		audio_valid_sample_rate(sample_rate)) &&
+		(format == SampleFormat::UNDEFINED ||
+		 audio_valid_sample_format(format)) &&
+		(channels == 0 || audio_valid_channel_count(channels));
+}
+
+gcc_const
+static inline unsigned
+sample_format_size(SampleFormat format)
+{
+	switch (format) {
+	case SampleFormat::S8:
+		return 1;
+
+	case SampleFormat::S16:
+		return 2;
+
+	case SampleFormat::S24_P32:
+	case SampleFormat::S32:
+	case SampleFormat::FLOAT:
+		return 4;
+
+	case SampleFormat::DSD:
+		/* each frame has 8 samples per channel */
+		return 1;
+
+	case SampleFormat::UNDEFINED:
+		return 0;
+	}
+
+	assert(false);
+	gcc_unreachable();
+}
+
+inline unsigned
+AudioFormat::GetSampleSize() const
+{
+	return sample_format_size(format);
+}
+
+inline unsigned
+AudioFormat::GetFrameSize() const
+{
+	return GetSampleSize() * channels;
+}
+
+inline double
+AudioFormat::GetTimeToSize() const
+{
+	return sample_rate * GetFrameSize();
+}
+
+/**
+ * Renders a #sample_format enum into a string, e.g. for printing it
+ * in a log file.
+ *
+ * @param format a #sample_format enum value
+ * @return the string
+ */
+gcc_pure gcc_malloc
+const char *
+sample_format_to_string(SampleFormat format);
+
+/**
+ * Renders the #audio_format object into a string, e.g. for printing
+ * it in a log file.
+ *
+ * @param af the #audio_format object
+ * @param s a buffer to print into
+ * @return the string, or NULL if the #audio_format object is invalid
+ */
+gcc_pure gcc_malloc
+const char *
+audio_format_to_string(AudioFormat af,
+		       struct audio_format_string *s);
+
+#endif
diff --git a/src/AudioParser.cxx b/src/AudioParser.cxx
index 297d99f12..4c345ca33 100644
--- a/src/AudioParser.cxx
+++ b/src/AudioParser.cxx
@@ -24,7 +24,7 @@
 
 #include "config.h"
 #include "AudioParser.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 #include "CheckAudioFormat.hxx"
 #include "gcc.h"
 
@@ -69,27 +69,27 @@ parse_sample_rate(const char *src, bool mask, uint32_t *sample_rate_r,
 
 static bool
 parse_sample_format(const char *src, bool mask,
-		    enum sample_format *sample_format_r,
+		    SampleFormat *sample_format_r,
 		    const char **endptr_r, GError **error_r)
 {
 	unsigned long value;
 	char *endptr;
-	enum sample_format sample_format;
+	SampleFormat sample_format;
 
 	if (mask && *src == '*') {
-		*sample_format_r = SAMPLE_FORMAT_UNDEFINED;
+		*sample_format_r = SampleFormat::UNDEFINED;
 		*endptr_r = src + 1;
 		return true;
 	}
 
 	if (*src == 'f') {
-		*sample_format_r = SAMPLE_FORMAT_FLOAT;
+		*sample_format_r = SampleFormat::FLOAT;
 		*endptr_r = src + 1;
 		return true;
 	}
 
 	if (memcmp(src, "dsd", 3) == 0) {
-		*sample_format_r = SAMPLE_FORMAT_DSD;
+		*sample_format_r = SampleFormat::DSD;
 		*endptr_r = src + 3;
 		return true;
 	}
@@ -103,11 +103,11 @@ parse_sample_format(const char *src, bool mask,
 
 	switch (value) {
 	case 8:
-		sample_format = SAMPLE_FORMAT_S8;
+		sample_format = SampleFormat::S8;
 		break;
 
 	case 16:
-		sample_format = SAMPLE_FORMAT_S16;
+		sample_format = SampleFormat::S16;
 		break;
 
 	case 24:
@@ -115,11 +115,11 @@ parse_sample_format(const char *src, bool mask,
 			/* for backwards compatibility */
 			endptr += 2;
 
-		sample_format = SAMPLE_FORMAT_S24_P32;
+		sample_format = SampleFormat::S24_P32;
 		break;
 
 	case 32:
-		sample_format = SAMPLE_FORMAT_S32;
+		sample_format = SampleFormat::S32;
 		break;
 
 	default:
@@ -162,14 +162,14 @@ parse_channel_count(const char *src, bool mask, uint8_t *channels_r,
 }
 
 bool
-audio_format_parse(struct audio_format *dest, const char *src,
+audio_format_parse(AudioFormat &dest, const char *src,
 		   bool mask, GError **error_r)
 {
 	uint32_t rate;
-	enum sample_format sample_format;
+	SampleFormat sample_format;
 	uint8_t channels;
 
-	audio_format_clear(dest);
+	dest.Clear();
 
 	/* parse sample rate */
 
@@ -191,7 +191,7 @@ audio_format_parse(struct audio_format *dest, const char *src,
 
 #if GCC_CHECK_VERSION(4,7)
 	/* workaround -Wmaybe-uninitialized false positive */
-	sample_format = SAMPLE_FORMAT_UNDEFINED;
+	sample_format = SampleFormat::UNDEFINED;
 #endif
 
 	if (!parse_sample_format(src, mask, &sample_format, &src, error_r))
@@ -214,9 +214,10 @@ audio_format_parse(struct audio_format *dest, const char *src,
 		return false;
 	}
 
-	audio_format_init(dest, rate, sample_format, channels);
-	assert(mask ? audio_format_mask_valid(dest)
-	       : audio_format_valid(dest));
+	dest = AudioFormat(rate, sample_format, channels);
+	assert(mask
+	       ? dest.IsMaskValid()
+	       : dest.IsValid());
 
 	return true;
 }
diff --git a/src/AudioParser.hxx b/src/AudioParser.hxx
index f7855e8e3..14c26392c 100644
--- a/src/AudioParser.hxx
+++ b/src/AudioParser.hxx
@@ -27,7 +27,7 @@
 
 #include "gerror.h"
 
-struct audio_format;
+struct AudioFormat;
 
 /**
  * Parses a string in the form "SAMPLE_RATE:BITS:CHANNELS" into an
@@ -41,7 +41,7 @@ struct audio_format;
  * @return true on success
  */
 bool
-audio_format_parse(struct audio_format *dest, const char *src,
+audio_format_parse(AudioFormat &dest, const char *src,
 		   bool mask, GError **error_r);
 
 #endif
diff --git a/src/CheckAudioFormat.cxx b/src/CheckAudioFormat.cxx
index 6d484be19..fabffd56c 100644
--- a/src/CheckAudioFormat.cxx
+++ b/src/CheckAudioFormat.cxx
@@ -18,7 +18,7 @@
  */
 
 #include "CheckAudioFormat.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 
 #include <assert.h>
 
@@ -35,11 +35,12 @@ audio_check_sample_rate(unsigned long sample_rate, GError **error_r)
 }
 
 bool
-audio_check_sample_format(enum sample_format sample_format, GError **error_r)
+audio_check_sample_format(SampleFormat sample_format, GError **error_r)
 {
 	if (!audio_valid_sample_format(sample_format)) {
 		g_set_error(error_r, audio_format_quark(), 0,
-			    "Invalid sample format: %u", sample_format);
+			    "Invalid sample format: %u",
+			    unsigned(sample_format));
 		return false;
 	}
 
@@ -59,15 +60,15 @@ audio_check_channel_count(unsigned channels, GError **error_r)
 }
 
 bool
-audio_format_init_checked(struct audio_format *af, unsigned long sample_rate,
-			  enum sample_format sample_format, unsigned channels,
+audio_format_init_checked(AudioFormat &af, unsigned long sample_rate,
+			  SampleFormat sample_format, unsigned channels,
 			  GError **error_r)
 {
 	if (audio_check_sample_rate(sample_rate, error_r) &&
 	    audio_check_sample_format(sample_format, error_r) &&
 	    audio_check_channel_count(channels, error_r)) {
-		audio_format_init(af, sample_rate, sample_format, channels);
-		assert(audio_format_valid(af));
+		af = AudioFormat(sample_rate, sample_format, channels);
+		assert(af.IsValid());
 		return true;
 	} else
 		return false;
diff --git a/src/CheckAudioFormat.hxx b/src/CheckAudioFormat.hxx
index cb3e43c22..7fbce7f98 100644
--- a/src/CheckAudioFormat.hxx
+++ b/src/CheckAudioFormat.hxx
@@ -20,7 +20,7 @@
 #ifndef MPD_CHECK_AUDIO_FORMAT_HXX
 #define MPD_CHECK_AUDIO_FORMAT_HXX
 
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 
 #include <glib.h>
 
@@ -38,7 +38,7 @@ bool
 audio_check_sample_rate(unsigned long sample_rate, GError **error_r);
 
 bool
-audio_check_sample_format(enum sample_format, GError **error_r);
+audio_check_sample_format(SampleFormat sample_format, GError **error_r);
 
 bool
 audio_check_channel_count(unsigned sample_format, GError **error_r);
@@ -47,8 +47,8 @@ audio_check_channel_count(unsigned sample_format, GError **error_r);
  * Wrapper for audio_format_init(), which checks all attributes.
  */
 bool
-audio_format_init_checked(struct audio_format *af, unsigned long sample_rate,
-			  enum sample_format sample_format, unsigned channels,
+audio_format_init_checked(AudioFormat &af, unsigned long sample_rate,
+			  SampleFormat sample_format, unsigned channels,
 			  GError **error_r);
 
 #endif
diff --git a/src/CrossFade.cxx b/src/CrossFade.cxx
index 253038b26..4f5ff32ca 100644
--- a/src/CrossFade.cxx
+++ b/src/CrossFade.cxx
@@ -20,7 +20,7 @@
 #include "config.h"
 #include "CrossFade.hxx"
 #include "MusicChunk.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 #include "Tag.hxx"
 
 #include <cmath>
@@ -97,8 +97,8 @@ unsigned cross_fade_calc(float duration, float total_time,
 			 float mixramp_db, float mixramp_delay,
 			 float replay_gain_db, float replay_gain_prev_db,
 			 char *mixramp_start, char *mixramp_prev_end,
-			 const struct audio_format *af,
-			 const struct audio_format *old_format,
+			 const AudioFormat af,
+			 const AudioFormat old_format,
 			 unsigned max_chunks)
 {
 	unsigned int chunks = 0;
@@ -107,13 +107,13 @@ unsigned cross_fade_calc(float duration, float total_time,
 
 	if (duration < 0 || duration >= total_time ||
 	    /* we can't crossfade when the audio formats are different */
-	    !audio_format_equals(af, old_format))
+	    af != old_format)
 		return 0;
 
 	assert(duration >= 0);
-	assert(audio_format_valid(af));
+	assert(af.IsValid());
 
-	chunks_f = (float)audio_format_time_to_size(af) / (float)CHUNK_SIZE;
+	chunks_f = (float)af.GetTimeToSize() / (float)CHUNK_SIZE;
 
 	if (std::isnan(mixramp_delay) || !mixramp_start || !mixramp_prev_end) {
 		chunks = (chunks_f * duration + 0.5);
diff --git a/src/CrossFade.hxx b/src/CrossFade.hxx
index 1c4670758..f285c0e9a 100644
--- a/src/CrossFade.hxx
+++ b/src/CrossFade.hxx
@@ -20,7 +20,7 @@
 #ifndef MPD_CROSSFADE_HXX
 #define MPD_CROSSFADE_HXX
 
-struct audio_format;
+struct AudioFormat;
 struct music_chunk;
 
 /**
@@ -44,8 +44,7 @@ unsigned cross_fade_calc(float duration, float total_time,
 			 float mixramp_db, float mixramp_delay,
 			 float replay_gain_db, float replay_gain_prev_db,
 			 char *mixramp_start, char *mixramp_prev_end,
-			 const struct audio_format *af,
-			 const struct audio_format *old_format,
+			 AudioFormat af, AudioFormat old_format,
 			 unsigned max_chunks);
 
 #endif
diff --git a/src/DecoderAPI.cxx b/src/DecoderAPI.cxx
index a4bdf37c4..03a75b2f9 100644
--- a/src/DecoderAPI.cxx
+++ b/src/DecoderAPI.cxx
@@ -40,7 +40,7 @@
 
 void
 decoder_initialized(struct decoder *decoder,
-		    const struct audio_format *audio_format,
+		    const AudioFormat audio_format,
 		    bool seekable, float total_time)
 {
 	struct decoder_control *dc = decoder->dc;
@@ -52,12 +52,11 @@ decoder_initialized(struct decoder *decoder,
 	assert(decoder->stream_tag == NULL);
 	assert(decoder->decoder_tag == NULL);
 	assert(!decoder->seeking);
-	assert(audio_format != NULL);
-	assert(audio_format_defined(audio_format));
-	assert(audio_format_valid(audio_format));
+	assert(audio_format.IsDefined());
+	assert(audio_format.IsValid());
 
-	dc->in_audio_format = *audio_format;
-	getOutputAudioFormat(audio_format, &dc->out_audio_format);
+	dc->in_audio_format = audio_format;
+	dc->out_audio_format = getOutputAudioFormat(audio_format);
 
 	dc->seekable = seekable;
 	dc->total_time = total_time;
@@ -68,13 +67,12 @@ decoder_initialized(struct decoder *decoder,
 	dc->Unlock();
 
 	g_debug("audio_format=%s, seekable=%s",
-		audio_format_to_string(&dc->in_audio_format, &af_string),
+		audio_format_to_string(dc->in_audio_format, &af_string),
 		seekable ? "true" : "false");
 
-	if (!audio_format_equals(&dc->in_audio_format,
-				 &dc->out_audio_format))
+	if (dc->in_audio_format != dc->out_audio_format)
 		g_debug("converting to %s",
-			audio_format_to_string(&dc->out_audio_format,
+			audio_format_to_string(dc->out_audio_format,
 					       &af_string));
 }
 
@@ -371,7 +369,7 @@ decoder_data(struct decoder *decoder,
 
 	assert(dc->state == DECODE_STATE_DECODE);
 	assert(dc->pipe != NULL);
-	assert(length % audio_format_frame_size(&dc->in_audio_format) == 0);
+	assert(length % dc->in_audio_format.GetFrameSize() == 0);
 
 	dc->Lock();
 	cmd = decoder_get_virtual_command(decoder);
@@ -398,10 +396,10 @@ decoder_data(struct decoder *decoder,
 			return cmd;
 	}
 
-	if (!audio_format_equals(&dc->in_audio_format, &dc->out_audio_format)) {
-		data = decoder->conv_state.Convert(&dc->in_audio_format,
+	if (dc->in_audio_format != dc->out_audio_format) {
+		data = decoder->conv_state.Convert(dc->in_audio_format,
 						   data, length,
-						   &dc->out_audio_format,
+						   dc->out_audio_format,
 						   &length,
 						   &error);
 		if (data == NULL) {
@@ -457,7 +455,7 @@ decoder_data(struct decoder *decoder,
 		length -= nbytes;
 
 		decoder->timestamp += (double)nbytes /
-			audio_format_time_to_size(&dc->out_audio_format);
+			dc->out_audio_format.GetTimeToSize();
 
 		if (dc->end_ms > 0 &&
 		    decoder->timestamp >= dc->end_ms / 1000.0)
diff --git a/src/DecoderAPI.hxx b/src/DecoderAPI.hxx
index c70365bff..a5eb2f236 100644
--- a/src/DecoderAPI.hxx
+++ b/src/DecoderAPI.hxx
@@ -33,7 +33,7 @@
 #include "input_stream.h"
 #include "replay_gain_info.h"
 #include "Tag.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 #include "conf.h"
 
 /**
@@ -48,7 +48,7 @@
  */
 void
 decoder_initialized(struct decoder *decoder,
-		    const struct audio_format *audio_format,
+		    AudioFormat audio_format,
 		    bool seekable, float total_time);
 
 /**
diff --git a/src/DecoderControl.hxx b/src/DecoderControl.hxx
index 69de4800c..31c72657b 100644
--- a/src/DecoderControl.hxx
+++ b/src/DecoderControl.hxx
@@ -21,7 +21,7 @@
 #define MPD_DECODER_CONTROL_HXX
 
 #include "DecoderCommand.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 #include "thread/Mutex.hxx"
 #include "thread/Cond.hxx"
 
@@ -85,10 +85,10 @@ struct decoder_control {
 	double seek_where;
 
 	/** the format of the song file */
-	struct audio_format in_audio_format;
+	AudioFormat in_audio_format;
 
 	/** the format being sent to the music pipe */
-	struct audio_format out_audio_format;
+	AudioFormat out_audio_format;
 
 	/**
 	 * The song currently being decoded.  This attribute is set by
diff --git a/src/EncoderAPI.hxx b/src/EncoderAPI.hxx
index d35955f1b..d430214d6 100644
--- a/src/EncoderAPI.hxx
+++ b/src/EncoderAPI.hxx
@@ -26,7 +26,7 @@
 #define MPD_ENCODER_API_HXX
 
 #include "EncoderPlugin.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 #include "Tag.hxx"
 #include "conf.h"
 
diff --git a/src/EncoderPlugin.hxx b/src/EncoderPlugin.hxx
index 868a9998c..70bdabe1a 100644
--- a/src/EncoderPlugin.hxx
+++ b/src/EncoderPlugin.hxx
@@ -27,7 +27,7 @@
 #include <stddef.h>
 
 struct EncoderPlugin;
-struct audio_format;
+struct AudioFormat;
 struct config_param;
 struct Tag;
 
@@ -55,7 +55,7 @@ struct EncoderPlugin {
 	void (*finish)(Encoder *encoder);
 
 	bool (*open)(Encoder *encoder,
-		     struct audio_format *audio_format,
+		     AudioFormat &audio_format,
 		     GError **error);
 
 	void (*close)(Encoder *encoder);
@@ -122,7 +122,7 @@ encoder_finish(Encoder *encoder)
  * @return true on success
  */
 static inline bool
-encoder_open(Encoder *encoder, struct audio_format *audio_format,
+encoder_open(Encoder *encoder, AudioFormat &audio_format,
 	     GError **error)
 {
 	assert(!encoder->open);
diff --git a/src/FilterInternal.hxx b/src/FilterInternal.hxx
index cdc2d0ea1..103687b61 100644
--- a/src/FilterInternal.hxx
+++ b/src/FilterInternal.hxx
@@ -25,7 +25,7 @@
 #ifndef MPD_FILTER_INTERNAL_HXX
 #define MPD_FILTER_INTERNAL_HXX
 
-struct audio_format;
+struct AudioFormat;
 
 class Filter {
 public:
@@ -40,10 +40,10 @@ public:
 	 * format
 	 * @param error location to store the error occurring, or NULL
 	 * to ignore errors.
-	 * @return the format of outgoing data
+	 * @return the format of outgoing data or
+	 * AudioFormat::Undefined() on error
 	 */
-	virtual const audio_format *Open(audio_format &af,
-					 GError **error_r) = 0;
+	virtual AudioFormat Open(AudioFormat &af, GError **error_r) = 0;
 
 	/**
 	 * Closes the filter.  After that, you may call Open() again.
diff --git a/src/MusicChunk.cxx b/src/MusicChunk.cxx
index 55d2f7f30..fb9019988 100644
--- a/src/MusicChunk.cxx
+++ b/src/MusicChunk.cxx
@@ -19,7 +19,7 @@
 
 #include "config.h"
 #include "MusicChunk.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 #include "Tag.hxx"
 
 #include <assert.h>
@@ -31,22 +31,21 @@ music_chunk::~music_chunk()
 
 #ifndef NDEBUG
 bool
-music_chunk::CheckFormat(const struct audio_format &other_format) const
+music_chunk::CheckFormat(const AudioFormat other_format) const
 {
-	assert(audio_format_valid(&other_format));
+	assert(other_format.IsValid());
 
-	return length == 0 ||
-		audio_format_equals(&audio_format, &other_format);
+	return length == 0 || audio_format == other_format;
 }
 #endif
 
 void *
-music_chunk::Write(const struct audio_format &af,
+music_chunk::Write(const AudioFormat af,
 		   float data_time, uint16_t _bit_rate,
 		   size_t *max_length_r)
 {
 	assert(CheckFormat(af));
-	assert(length == 0 || audio_format_valid(&audio_format));
+	assert(length == 0 || audio_format.IsValid());
 
 	if (length == 0) {
 		/* if the chunk is empty, nobody has set bitRate and
@@ -56,7 +55,7 @@ music_chunk::Write(const struct audio_format &af,
 		times = data_time;
 	}
 
-	const size_t frame_size = audio_format_frame_size(&af);
+	const size_t frame_size = af.GetFrameSize();
 	size_t num_frames = (sizeof(data) - length) / frame_size;
 	if (num_frames == 0)
 		return NULL;
@@ -70,12 +69,12 @@ music_chunk::Write(const struct audio_format &af,
 }
 
 bool
-music_chunk::Expand(const struct audio_format &af, size_t _length)
+music_chunk::Expand(const AudioFormat af, size_t _length)
 {
-	const size_t frame_size = audio_format_frame_size(&af);
+	const size_t frame_size = af.GetFrameSize();
 
 	assert(length + _length <= sizeof(data));
-	assert(audio_format_equals(&audio_format, &af));
+	assert(audio_format == af);
 
 	length += _length;
 
diff --git a/src/MusicChunk.hxx b/src/MusicChunk.hxx
index a15b3a702..23c3b7423 100644
--- a/src/MusicChunk.hxx
+++ b/src/MusicChunk.hxx
@@ -23,7 +23,7 @@
 #include "replay_gain_info.h"
 
 #ifndef NDEBUG
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 #endif
 
 #include <stdint.h>
@@ -33,7 +33,7 @@ enum {
 	CHUNK_SIZE = 4096,
 };
 
-struct audio_format;
+struct AudioFormat;
 struct Tag;
 
 /**
@@ -90,7 +90,7 @@ struct music_chunk {
 	char data[CHUNK_SIZE];
 
 #ifndef NDEBUG
-	struct audio_format audio_format;
+	AudioFormat audio_format;
 #endif
 
 	music_chunk()
@@ -111,7 +111,7 @@ struct music_chunk {
 	 * specified audio_format.
 	 */
 	gcc_pure
-	bool CheckFormat(const struct audio_format &audio_format) const;
+	bool CheckFormat(AudioFormat audio_format) const;
 #endif
 
 	/**
@@ -128,7 +128,7 @@ struct music_chunk {
 	 * here
 	 * @return a writable buffer, or NULL if the chunk is full
 	 */
-	void *Write(const struct audio_format &af,
+	void *Write(AudioFormat af,
 		    float data_time, uint16_t bit_rate,
 		    size_t *max_length_r);
 
@@ -142,7 +142,7 @@ struct music_chunk {
 	 * @param length the number of bytes which were appended
 	 * @return true if the chunk is full
 	 */
-	bool Expand(const struct audio_format &af, size_t length);
+	bool Expand(AudioFormat af, size_t length);
 };
 
 void
diff --git a/src/MusicPipe.cxx b/src/MusicPipe.cxx
index 6f25eff82..5da56cd0c 100644
--- a/src/MusicPipe.cxx
+++ b/src/MusicPipe.cxx
@@ -41,13 +41,13 @@ struct music_pipe {
 	mutable Mutex mutex;
 
 #ifndef NDEBUG
-	struct audio_format audio_format;
+	AudioFormat audio_format;
 #endif
 
 	music_pipe()
 		:head(nullptr), tail_r(&head), size(0) {
 #ifndef NDEBUG
-		audio_format_clear(&audio_format);
+		audio_format.Clear();
 #endif
 	}
 
@@ -73,13 +73,12 @@ music_pipe_free(struct music_pipe *mp)
 
 bool
 music_pipe_check_format(const struct music_pipe *pipe,
-			const struct audio_format *audio_format)
+			const AudioFormat audio_format)
 {
 	assert(pipe != NULL);
-	assert(audio_format != NULL);
 
-	return !audio_format_defined(&pipe->audio_format) ||
-		audio_format_equals(&pipe->audio_format, audio_format);
+	return !pipe->audio_format.IsDefined() ||
+		pipe->audio_format == audio_format;
 }
 
 bool
@@ -131,7 +130,7 @@ music_pipe_shift(struct music_pipe *mp)
 		chunk->next = (struct music_chunk *)(void *)0x01010101;
 
 		if (mp->size == 0)
-			audio_format_clear(&mp->audio_format);
+			mp->audio_format.Clear();
 #endif
 	}
 
@@ -151,16 +150,16 @@ void
 music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk)
 {
 	assert(!chunk->IsEmpty());
-	assert(chunk->length == 0 || audio_format_valid(&chunk->audio_format));
+	assert(chunk->length == 0 || chunk->audio_format.IsValid());
 
 	const ScopeLock protect(mp->mutex);
 
-	assert(mp->size > 0 || !audio_format_defined(&mp->audio_format));
-	assert(!audio_format_defined(&mp->audio_format) ||
+	assert(mp->size > 0 || !mp->audio_format.IsDefined());
+	assert(!mp->audio_format.IsDefined() ||
 	       chunk->CheckFormat(mp->audio_format));
 
 #ifndef NDEBUG
-	if (!audio_format_defined(&mp->audio_format) && chunk->length > 0)
+	if (!mp->audio_format.IsDefined() && chunk->length > 0)
 		mp->audio_format = chunk->audio_format;
 #endif
 
diff --git a/src/MusicPipe.hxx b/src/MusicPipe.hxx
index 99561ca62..39ad8e981 100644
--- a/src/MusicPipe.hxx
+++ b/src/MusicPipe.hxx
@@ -23,7 +23,7 @@
 #include "gcc.h"
 
 #ifndef NDEBUG
-struct audio_format;
+struct AudioFormat;
 #endif
 
 struct music_chunk;
@@ -56,7 +56,7 @@ music_pipe_free(struct music_pipe *mp);
  */
 bool
 music_pipe_check_format(const struct music_pipe *pipe,
-			const struct audio_format *audio_format);
+			AudioFormat audio_format);
 
 /**
  * Checks if the specified chunk is enqueued in the music pipe.
diff --git a/src/OutputAPI.hxx b/src/OutputAPI.hxx
index 951d723ea..9c79d9b81 100644
--- a/src/OutputAPI.hxx
+++ b/src/OutputAPI.hxx
@@ -22,7 +22,7 @@
 
 #include "OutputPlugin.hxx"
 #include "OutputInternal.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 #include "Tag.hxx"
 #include "conf.h"
 
diff --git a/src/OutputAll.cxx b/src/OutputAll.cxx
index cdbdcfbfc..a73afbbb6 100644
--- a/src/OutputAll.cxx
+++ b/src/OutputAll.cxx
@@ -36,7 +36,7 @@
 #undef G_LOG_DOMAIN
 #define G_LOG_DOMAIN "output"
 
-static struct audio_format input_audio_format;
+static AudioFormat input_audio_format;
 
 static struct audio_output **audio_outputs;
 static unsigned int num_audio_outputs;
@@ -246,12 +246,12 @@ audio_output_all_update(void)
 	unsigned int i;
 	bool ret = false;
 
-	if (!audio_format_defined(&input_audio_format))
+	if (!input_audio_format.IsDefined())
 		return false;
 
 	for (i = 0; i < num_audio_outputs; ++i)
 		ret = audio_output_update(audio_outputs[i],
-					  &input_audio_format, g_mp) || ret;
+					  input_audio_format, g_mp) || ret;
 
 	return ret;
 }
@@ -291,14 +291,13 @@ audio_output_all_play(struct music_chunk *chunk, GError **error_r)
 }
 
 bool
-audio_output_all_open(const struct audio_format *audio_format,
+audio_output_all_open(const AudioFormat audio_format,
 		      struct music_buffer *buffer,
 		      GError **error_r)
 {
 	bool ret = false, enabled = false;
 	unsigned int i;
 
-	assert(audio_format != NULL);
 	assert(buffer != NULL);
 	assert(g_music_buffer == NULL || g_music_buffer == buffer);
 	assert((g_mp == NULL) == (g_music_buffer == NULL));
@@ -315,10 +314,9 @@ audio_output_all_open(const struct audio_format *audio_format,
 		/* if the pipe hasn't been cleared, the the audio
 		   format must not have changed */
 		assert(music_pipe_empty(g_mp) ||
-		       audio_format_equals(audio_format,
-					   &input_audio_format));
+		       audio_format == input_audio_format);
 
-	input_audio_format = *audio_format;
+	input_audio_format = audio_format;
 
 	audio_output_all_reset_reopen();
 	audio_output_all_enable_disable();
@@ -547,7 +545,7 @@ audio_output_all_close(void)
 
 	g_music_buffer = NULL;
 
-	audio_format_clear(&input_audio_format);
+	input_audio_format.Clear();
 
 	audio_output_all_elapsed_time = -1.0;
 }
@@ -570,7 +568,7 @@ audio_output_all_release(void)
 
 	g_music_buffer = NULL;
 
-	audio_format_clear(&input_audio_format);
+	input_audio_format.Clear();
 
 	audio_output_all_elapsed_time = -1.0;
 }
diff --git a/src/OutputAll.hxx b/src/OutputAll.hxx
index 0bb9f0763..10f8196aa 100644
--- a/src/OutputAll.hxx
+++ b/src/OutputAll.hxx
@@ -29,7 +29,7 @@
 #include "replay_gain_info.h"
 #include "gerror.h"
 
-struct audio_format;
+struct AudioFormat;
 struct music_buffer;
 struct music_chunk;
 struct player_control;
@@ -76,14 +76,13 @@ audio_output_all_enable_disable(void);
 /**
  * Opens all audio outputs which are not disabled.
  *
- * @param audio_format the preferred audio format, or NULL to reuse
- * the previous format
+ * @param audio_format the preferred audio format
  * @param buffer the #music_buffer where consumed #music_chunk objects
  * should be returned
  * @return true on success, false on failure
  */
 bool
-audio_output_all_open(const struct audio_format *audio_format,
+audio_output_all_open(AudioFormat audio_format,
 		      struct music_buffer *buffer,
 		      GError **error_r);
 
diff --git a/src/OutputControl.cxx b/src/OutputControl.cxx
index a11b66116..b8f3a3ea4 100644
--- a/src/OutputControl.cxx
+++ b/src/OutputControl.cxx
@@ -139,14 +139,14 @@ audio_output_disable(struct audio_output *ao)
  */
 static bool
 audio_output_open(struct audio_output *ao,
-		  const struct audio_format *audio_format,
+		  const AudioFormat audio_format,
 		  const struct music_pipe *mp)
 {
 	bool open;
 
 	assert(ao != NULL);
 	assert(ao->allow_play);
-	assert(audio_format_valid(audio_format));
+	assert(audio_format.IsValid());
 	assert(mp != NULL);
 
 	if (ao->fail_timer != NULL) {
@@ -154,8 +154,7 @@ audio_output_open(struct audio_output *ao,
 		ao->fail_timer = NULL;
 	}
 
-	if (ao->open &&
-	    audio_format_equals(audio_format, &ao->in_audio_format)) {
+	if (ao->open && audio_format == ao->in_audio_format) {
 		assert(ao->pipe == mp ||
 		       (ao->always_on && ao->pause));
 
@@ -176,7 +175,7 @@ audio_output_open(struct audio_output *ao,
 		return true;
 	}
 
-	ao->in_audio_format = *audio_format;
+	ao->in_audio_format = audio_format;
 	ao->chunk = NULL;
 
 	ao->pipe = mp;
@@ -225,7 +224,7 @@ audio_output_close_locked(struct audio_output *ao)
 
 bool
 audio_output_update(struct audio_output *ao,
-		    const struct audio_format *audio_format,
+		    const AudioFormat audio_format,
 		    const struct music_pipe *mp)
 {
 	assert(mp != NULL);
diff --git a/src/OutputControl.hxx b/src/OutputControl.hxx
index 2ff095398..373b1849c 100644
--- a/src/OutputControl.hxx
+++ b/src/OutputControl.hxx
@@ -25,7 +25,7 @@
 #include <stddef.h>
 
 struct audio_output;
-struct audio_format;
+struct AudioFormat;
 struct config_param;
 struct music_pipe;
 struct player_control;
@@ -53,7 +53,7 @@ audio_output_disable(struct audio_output *ao);
  */
 bool
 audio_output_update(struct audio_output *ao,
-		    const struct audio_format *audio_format,
+		    AudioFormat audio_format,
 		    const struct music_pipe *mp);
 
 void
diff --git a/src/OutputInit.cxx b/src/OutputInit.cxx
index 6dd40cd67..cef4755c6 100644
--- a/src/OutputInit.cxx
+++ b/src/OutputInit.cxx
@@ -155,16 +155,16 @@ ao_base_init(struct audio_output *ao,
 						 NULL);
 		if (p != NULL) {
 			bool success =
-				audio_format_parse(&ao->config_audio_format,
+				audio_format_parse(ao->config_audio_format,
 						   p, true, error_r);
 			if (!success)
 				return false;
 		} else
-			audio_format_clear(&ao->config_audio_format);
+			ao->config_audio_format.Clear();
 	} else {
 		ao->name = "default detected output";
 
-		audio_format_clear(&ao->config_audio_format);
+		ao->config_audio_format.Clear();
 	}
 
 	ao->plugin = plugin;
diff --git a/src/OutputInternal.hxx b/src/OutputInternal.hxx
index a0d376827..b23ec95b6 100644
--- a/src/OutputInternal.hxx
+++ b/src/OutputInternal.hxx
@@ -20,7 +20,7 @@
 #ifndef MPD_OUTPUT_INTERNAL_HXX
 #define MPD_OUTPUT_INTERNAL_HXX
 
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 #include "pcm/PcmBuffer.hxx"
 #include "thread/Mutex.hxx"
 #include "thread/Cond.hxx"
@@ -134,13 +134,13 @@ struct audio_output {
 	/**
 	 * The configured audio format.
 	 */
-	struct audio_format config_audio_format;
+	AudioFormat config_audio_format;
 
 	/**
 	 * The audio_format in which audio data is received from the
 	 * player thread (which in turn receives it from the decoder).
 	 */
-	struct audio_format in_audio_format;
+	AudioFormat in_audio_format;
 
 	/**
 	 * The audio_format which is really sent to the device.  This
@@ -148,7 +148,7 @@ struct audio_output {
 	 * in_audio_format, but may have been modified by
 	 * plugin->open().
 	 */
-	struct audio_format out_audio_format;
+	AudioFormat out_audio_format;
 
 	/**
 	 * The buffer used to allocate the cross-fading result.
diff --git a/src/OutputPlugin.cxx b/src/OutputPlugin.cxx
index 1e49ddbaa..00901aad2 100644
--- a/src/OutputPlugin.cxx
+++ b/src/OutputPlugin.cxx
@@ -54,7 +54,7 @@ ao_plugin_disable(struct audio_output *ao)
 }
 
 bool
-ao_plugin_open(struct audio_output *ao, struct audio_format *audio_format,
+ao_plugin_open(struct audio_output *ao, AudioFormat &audio_format,
 	       GError **error)
 {
 	return ao->plugin->open(ao, audio_format, error);
diff --git a/src/OutputPlugin.hxx b/src/OutputPlugin.hxx
index 8eb125029..4e19ffcfb 100644
--- a/src/OutputPlugin.hxx
+++ b/src/OutputPlugin.hxx
@@ -26,7 +26,7 @@
 #include <stddef.h>
 
 struct config_param;
-struct audio_format;
+struct AudioFormat;
 struct Tag;
 
 /**
@@ -89,7 +89,7 @@ struct audio_output_plugin {
 	 * @param error location to store the error occurring, or NULL
 	 * to ignore errors
 	 */
-	bool (*open)(struct audio_output *data, struct audio_format *audio_format,
+	bool (*open)(struct audio_output *data, AudioFormat &audio_format,
 		     GError **error);
 
 	/**
@@ -181,7 +181,7 @@ void
 ao_plugin_disable(struct audio_output *ao);
 
 bool
-ao_plugin_open(struct audio_output *ao, struct audio_format *audio_format,
+ao_plugin_open(struct audio_output *ao, AudioFormat &audio_format,
 	       GError **error);
 
 void
diff --git a/src/OutputThread.cxx b/src/OutputThread.cxx
index a57fe7f60..483d35680 100644
--- a/src/OutputThread.cxx
+++ b/src/OutputThread.cxx
@@ -94,11 +94,11 @@ ao_disable(struct audio_output *ao)
 	}
 }
 
-static const struct audio_format *
-ao_filter_open(struct audio_output *ao, audio_format &format,
+static AudioFormat
+ao_filter_open(struct audio_output *ao, AudioFormat &format,
 	       GError **error_r)
 {
-	assert(audio_format_valid(&format));
+	assert(format.IsValid());
 
 	/* the replay_gain filter cannot fail here */
 	if (ao->replay_gain_filter != NULL)
@@ -106,9 +106,8 @@ ao_filter_open(struct audio_output *ao, audio_format &format,
 	if (ao->other_replay_gain_filter != NULL)
 		ao->other_replay_gain_filter->Open(format, error_r);
 
-	const struct audio_format *af
-		= ao->filter->Open(format, error_r);
-	if (af == NULL) {
+	const AudioFormat af = ao->filter->Open(format, error_r);
+	if (!af.IsDefined()) {
 		if (ao->replay_gain_filter != NULL)
 			ao->replay_gain_filter->Close();
 		if (ao->other_replay_gain_filter != NULL)
@@ -139,7 +138,7 @@ ao_open(struct audio_output *ao)
 	assert(!ao->open);
 	assert(ao->pipe != NULL);
 	assert(ao->chunk == NULL);
-	assert(audio_format_valid(&ao->in_audio_format));
+	assert(ao->in_audio_format.IsValid());
 
 	if (ao->fail_timer != NULL) {
 		/* this can only happen when this
@@ -158,9 +157,9 @@ ao_open(struct audio_output *ao)
 
 	/* open the filter */
 
-	const audio_format *filter_audio_format =
+	const AudioFormat filter_audio_format =
 		ao_filter_open(ao, ao->in_audio_format, &error);
-	if (filter_audio_format == NULL) {
+	if (!filter_audio_format.IsDefined()) {
 		g_warning("Failed to open filter for \"%s\" [%s]: %s",
 			  ao->name, ao->plugin->name, error->message);
 		g_error_free(error);
@@ -169,14 +168,13 @@ ao_open(struct audio_output *ao)
 		return;
 	}
 
-	assert(audio_format_valid(filter_audio_format));
+	assert(filter_audio_format.IsValid());
 
-	ao->out_audio_format = *filter_audio_format;
-	audio_format_mask_apply(&ao->out_audio_format,
-				&ao->config_audio_format);
+	ao->out_audio_format = filter_audio_format;
+	ao->out_audio_format.ApplyMask(ao->config_audio_format);
 
 	ao->mutex.unlock();
-	success = ao_plugin_open(ao, &ao->out_audio_format, &error);
+	success = ao_plugin_open(ao, ao->out_audio_format, &error);
 	ao->mutex.lock();
 
 	assert(!ao->open);
@@ -198,12 +196,11 @@ ao_open(struct audio_output *ao)
 	g_debug("opened plugin=%s name=\"%s\" "
 		"audio_format=%s",
 		ao->plugin->name, ao->name,
-		audio_format_to_string(&ao->out_audio_format, &af_string));
+		audio_format_to_string(ao->out_audio_format, &af_string));
 
-	if (!audio_format_equals(&ao->in_audio_format,
-				 &ao->out_audio_format))
+	if (ao->in_audio_format != ao->out_audio_format)
 		g_debug("converting from %s",
-			audio_format_to_string(&ao->in_audio_format,
+			audio_format_to_string(ao->in_audio_format,
 					       &af_string));
 }
 
@@ -235,12 +232,12 @@ ao_close(struct audio_output *ao, bool drain)
 static void
 ao_reopen_filter(struct audio_output *ao)
 {
-	const struct audio_format *filter_audio_format;
 	GError *error = NULL;
 
 	ao_filter_close(ao);
-	filter_audio_format = ao_filter_open(ao, ao->in_audio_format, &error);
-	if (filter_audio_format == NULL) {
+	const AudioFormat filter_audio_format =
+		ao_filter_open(ao, ao->in_audio_format, &error);
+	if (!filter_audio_format.IsDefined()) {
 		g_warning("Failed to open filter for \"%s\" [%s]: %s",
 			  ao->name, ao->plugin->name, error->message);
 		g_error_free(error);
@@ -268,7 +265,7 @@ ao_reopen_filter(struct audio_output *ao)
 static void
 ao_reopen(struct audio_output *ao)
 {
-	if (!audio_format_fully_defined(&ao->config_audio_format)) {
+	if (!ao->config_audio_format.IsFullyDefined()) {
 		if (ao->open) {
 			const struct music_pipe *mp = ao->pipe;
 			ao_close(ao, true);
@@ -279,8 +276,7 @@ ao_reopen(struct audio_output *ao)
 		   the output's open() method determine the effective
 		   out_audio_format */
 		ao->out_audio_format = ao->in_audio_format;
-		audio_format_mask_apply(&ao->out_audio_format,
-					&ao->config_audio_format);
+		ao->out_audio_format.ApplyMask(ao->config_audio_format);
 	}
 
 	if (ao->open)
@@ -327,7 +323,7 @@ ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk,
 
 	(void)ao;
 
-	assert(length % audio_format_frame_size(&ao->in_audio_format) == 0);
+	assert(length % ao->in_audio_format.GetFrameSize() == 0);
 
 	if (length > 0 && replay_gain_filter != NULL) {
 		if (chunk->replay_gain_serial != *replay_gain_serial_p) {
@@ -399,10 +395,10 @@ ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk,
 		void *dest = ao->cross_fade_buffer.Get(other_length);
 		memcpy(dest, other_data, other_length);
 		if (!pcm_mix(dest, data, length,
-			     sample_format(ao->in_audio_format.format),
+			     ao->in_audio_format.format,
 			     1.0 - chunk->mix_ratio)) {
 			g_warning("Cannot cross-fade format %s",
-				  sample_format_to_string(sample_format(ao->in_audio_format.format)));
+				  sample_format_to_string(ao->in_audio_format.format));
 			return NULL;
 		}
 
@@ -479,7 +475,7 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
 		}
 
 		assert(nbytes <= size);
-		assert(nbytes % audio_format_frame_size(&ao->out_audio_format) == 0);
+		assert(nbytes % ao->out_audio_format.GetFrameSize() == 0);
 
 		data += nbytes;
 		size -= nbytes;
diff --git a/src/PlayerCommands.cxx b/src/PlayerCommands.cxx
index 1c1c346b7..0d4089940 100644
--- a/src/PlayerCommands.cxx
+++ b/src/PlayerCommands.cxx
@@ -31,7 +31,7 @@
 #include "protocol/ArgParser.hxx"
 
 extern "C" {
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 }
 
 #include "replay_gain_config.h"
@@ -178,12 +178,12 @@ handle_status(Client *client,
 			      player_status.elapsed_time,
 			      player_status.bit_rate);
 
-		if (audio_format_defined(&player_status.audio_format)) {
+		if (player_status.audio_format.IsDefined()) {
 			struct audio_format_string af_string;
 
 			client_printf(client,
 				      COMMAND_STATUS_AUDIO ": %s\n",
-				      audio_format_to_string(&player_status.audio_format,
+				      audio_format_to_string(player_status.audio_format,
 							     &af_string));
 		}
 	}
diff --git a/src/PlayerControl.hxx b/src/PlayerControl.hxx
index ab87f6092..bea2e05eb 100644
--- a/src/PlayerControl.hxx
+++ b/src/PlayerControl.hxx
@@ -20,7 +20,7 @@
 #ifndef MPD_PLAYER_H
 #define MPD_PLAYER_H
 
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 #include "thread/Mutex.hxx"
 #include "thread/Cond.hxx"
 
@@ -85,7 +85,7 @@ enum player_error {
 struct player_status {
 	enum player_state state;
 	uint16_t bit_rate;
-	struct audio_format audio_format;
+	AudioFormat audio_format;
 	float total_time;
 	float elapsed_time;
 };
@@ -130,7 +130,7 @@ struct player_control {
 	GError *error;
 
 	uint16_t bit_rate;
-	struct audio_format audio_format;
+	AudioFormat audio_format;
 	float total_time;
 	float elapsed_time;
 
diff --git a/src/PlayerThread.cxx b/src/PlayerThread.cxx
index d838d4ba9..68fd6390f 100644
--- a/src/PlayerThread.cxx
+++ b/src/PlayerThread.cxx
@@ -115,7 +115,7 @@ struct player {
 	/**
 	 * The current audio format for the audio outputs.
 	 */
-	struct audio_format play_audio_format;
+	AudioFormat play_audio_format;
 
 	/**
 	 * The time stamp of the chunk most recently sent to the
@@ -279,7 +279,7 @@ player_wait_for_decoder(struct player *player)
 	/* update player_control's song information */
 	pc->total_time = pc->next_song->GetDuration();
 	pc->bit_rate = 0;
-	audio_format_clear(&pc->audio_format);
+	pc->audio_format.Clear();
 
 	/* clear the queued song */
 	pc->next_song = NULL;
@@ -323,12 +323,12 @@ player_open_output(struct player *player)
 {
 	struct player_control *pc = player->pc;
 
-	assert(audio_format_defined(&player->play_audio_format));
+	assert(player->play_audio_format.IsDefined());
 	assert(pc->state == PLAYER_STATE_PLAY ||
 	       pc->state == PLAYER_STATE_PAUSE);
 
 	GError *error = NULL;
-	if (audio_output_all_open(&player->play_audio_format, player_buffer,
+	if (audio_output_all_open(player->play_audio_format, player_buffer,
 				  &error)) {
 		player->output_open = true;
 		player->paused = false;
@@ -439,7 +439,7 @@ static bool
 player_send_silence(struct player *player)
 {
 	assert(player->output_open);
-	assert(audio_format_defined(&player->play_audio_format));
+	assert(player->play_audio_format.IsDefined());
 
 	struct music_chunk *chunk = music_buffer_allocate(player_buffer);
 	if (chunk == NULL) {
@@ -451,8 +451,7 @@ player_send_silence(struct player *player)
 	chunk->audio_format = player->play_audio_format;
 #endif
 
-	size_t frame_size =
-		audio_format_frame_size(&player->play_audio_format);
+	const size_t frame_size = player->play_audio_format.GetFrameSize();
 	/* this formula ensures that we don't send
 	   partial frames */
 	unsigned num_frames = sizeof(chunk->data) / frame_size;
@@ -597,7 +596,7 @@ static void player_process_command(struct player *player)
 			pc->Lock();
 
 			pc->state = PLAYER_STATE_PAUSE;
-		} else if (!audio_format_defined(&player->play_audio_format)) {
+		} else if (!player->play_audio_format.IsDefined()) {
 			/* the decoder hasn't provided an audio format
 			   yet - don't open the audio device yet */
 			pc->Lock();
@@ -689,10 +688,10 @@ update_song_tag(Song *song, const Tag &new_tag)
 static bool
 play_chunk(struct player_control *pc,
 	   Song *song, struct music_chunk *chunk,
-	   const struct audio_format *format,
+	   const AudioFormat format,
 	   GError **error_r)
 {
-	assert(chunk->CheckFormat(*format));
+	assert(chunk->CheckFormat(format));
 
 	if (chunk->tag != NULL)
 		update_song_tag(song, *chunk->tag);
@@ -712,7 +711,7 @@ play_chunk(struct player_control *pc,
 		return false;
 
 	pc->total_play_time += (double)chunk->length /
-		audio_format_time_to_size(format);
+		format.GetTimeToSize();
 	return true;
 }
 
@@ -825,7 +824,7 @@ play_next_chunk(struct player *player)
 
 	GError *error = NULL;
 	if (!play_chunk(player->pc, player->song, chunk,
-			&player->play_audio_format, &error)) {
+			player->play_audio_format, &error)) {
 		g_warning("%s", error->message);
 
 		music_buffer_return(player_buffer, chunk);
@@ -1019,8 +1018,8 @@ static void do_play(struct player_control *pc, struct decoder_control *dc)
 						dc->replay_gain_prev_db,
 						dc->mixramp_start,
 						dc->mixramp_prev_end,
-						&dc->out_audio_format,
-						&player.play_audio_format,
+						dc->out_audio_format,
+						player.play_audio_format,
 						music_buffer_size(player_buffer) -
 						pc->buffered_before_play);
 			if (player.cross_fade_chunks > 0) {
diff --git a/src/Timer.cxx b/src/Timer.cxx
index 7ddbda3da..18ccffbcb 100644
--- a/src/Timer.cxx
+++ b/src/Timer.cxx
@@ -19,7 +19,7 @@
 
 #include "config.h"
 #include "Timer.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 #include "clock.h"
 
 #include <glib.h>
@@ -28,10 +28,10 @@
 #include <limits.h>
 #include <stddef.h>
 
-Timer::Timer(const struct audio_format &af)
+Timer::Timer(const AudioFormat af)
 	: time(0),
 	  started(false),
-	  rate(af.sample_rate * audio_format_frame_size(&af))
+	 rate(af.sample_rate * af.GetFrameSize())
 {
 }
 
diff --git a/src/Timer.hxx b/src/Timer.hxx
index 96446a988..ccc29b873 100644
--- a/src/Timer.hxx
+++ b/src/Timer.hxx
@@ -22,14 +22,14 @@
 
 #include <stdint.h>
 
-struct audio_format;
+struct AudioFormat;
 
 class Timer {
 	uint64_t time;
 	bool started;
 	const int rate;
 public:
-	explicit Timer(const struct audio_format& af);
+	explicit Timer(AudioFormat af);
 
 	bool IsStarted() const { return started; }
 
diff --git a/src/audio_format.h b/src/audio_format.h
deleted file mode 100644
index 642eaa52a..000000000
--- a/src/audio_format.h
+++ /dev/null
@@ -1,316 +0,0 @@
-/*
- * Copyright (C) 2003-2011 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_AUDIO_FORMAT_H
-#define MPD_AUDIO_FORMAT_H
-
-#include "gcc.h"
-
-#include <stdint.h>
-#include <stdbool.h>
-#include <assert.h>
-
-enum sample_format {
-	SAMPLE_FORMAT_UNDEFINED = 0,
-
-	SAMPLE_FORMAT_S8,
-	SAMPLE_FORMAT_S16,
-
-	/**
-	 * Signed 24 bit integer samples, packed in 32 bit integers
-	 * (the most significant byte is filled with the sign bit).
-	 */
-	SAMPLE_FORMAT_S24_P32,
-
-	SAMPLE_FORMAT_S32,
-
-	/**
-	 * 32 bit floating point samples in the host's format.  The
-	 * range is -1.0f to +1.0f.
-	 */
-	SAMPLE_FORMAT_FLOAT,
-
-	/**
-	 * Direct Stream Digital.  1-bit samples; each frame has one
-	 * byte (8 samples) per channel.
-	 */
-	SAMPLE_FORMAT_DSD,
-};
-
-static const unsigned MAX_CHANNELS = 8;
-
-/**
- * This structure describes the format of a raw PCM stream.
- */
-struct audio_format {
-	/**
-	 * The sample rate in Hz.  A better name for this attribute is
-	 * "frame rate", because technically, you have two samples per
-	 * frame in stereo sound.
-	 */
-	uint32_t sample_rate;
-
-	/**
-	 * The format samples are stored in.  See the #sample_format
-	 * enum for valid values.
-	 */
-	uint8_t format;
-
-	/**
-	 * The number of channels.  Only mono (1) and stereo (2) are
-	 * fully supported currently.
-	 */
-	uint8_t channels;
-};
-
-/**
- * Buffer for audio_format_string().
- */
-struct audio_format_string {
-	char buffer[24];
-};
-
-/**
- * Clears the #audio_format object, i.e. sets all attributes to an
- * undefined (invalid) value.
- */
-static inline void audio_format_clear(struct audio_format *af)
-{
-	af->sample_rate = 0;
-	af->format = SAMPLE_FORMAT_UNDEFINED;
-	af->channels = 0;
-}
-
-/**
- * Initializes an #audio_format object, i.e. sets all
- * attributes to valid values.
- */
-static inline void audio_format_init(struct audio_format *af,
-				     uint32_t sample_rate,
-				     enum sample_format format, uint8_t channels)
-{
-	af->sample_rate = sample_rate;
-	af->format = (uint8_t)format;
-	af->channels = channels;
-}
-
-/**
- * Checks whether the specified #audio_format object has a defined
- * value.
- */
-static inline bool audio_format_defined(const struct audio_format *af)
-{
-	return af->sample_rate != 0;
-}
-
-/**
- * Checks whether the specified #audio_format object is full, i.e. all
- * attributes are defined.  This is more complete than
- * audio_format_defined(), but slower.
- */
-static inline bool
-audio_format_fully_defined(const struct audio_format *af)
-{
-	return af->sample_rate != 0 && af->format != SAMPLE_FORMAT_UNDEFINED &&
-		af->channels != 0;
-}
-
-/**
- * Checks whether the specified #audio_format object has at least one
- * defined value.
- */
-static inline bool
-audio_format_mask_defined(const struct audio_format *af)
-{
-	return af->sample_rate != 0 || af->format != SAMPLE_FORMAT_UNDEFINED ||
-		af->channels != 0;
-}
-
-/**
- * Checks whether the sample rate is valid.
- *
- * @param sample_rate the sample rate in Hz
- */
-static inline bool
-audio_valid_sample_rate(unsigned sample_rate)
-{
-	return sample_rate > 0 && sample_rate < (1 << 30);
-}
-
-/**
- * Checks whether the sample format is valid.
- *
- * @param bits the number of significant bits per sample
- */
-static inline bool
-audio_valid_sample_format(enum sample_format format)
-{
-	switch (format) {
-	case SAMPLE_FORMAT_S8:
-	case SAMPLE_FORMAT_S16:
-	case SAMPLE_FORMAT_S24_P32:
-	case SAMPLE_FORMAT_S32:
-	case SAMPLE_FORMAT_FLOAT:
-	case SAMPLE_FORMAT_DSD:
-		return true;
-
-	case SAMPLE_FORMAT_UNDEFINED:
-		break;
-	}
-
-	return false;
-}
-
-/**
- * Checks whether the number of channels is valid.
- */
-static inline bool
-audio_valid_channel_count(unsigned channels)
-{
-	return channels >= 1 && channels <= MAX_CHANNELS;
-}
-
-/**
- * Returns false if the format is not valid for playback with MPD.
- * This function performs some basic validity checks.
- */
-gcc_pure
-static inline bool audio_format_valid(const struct audio_format *af)
-{
-	return audio_valid_sample_rate(af->sample_rate) &&
-		audio_valid_sample_format((enum sample_format)af->format) &&
-		audio_valid_channel_count(af->channels);
-}
-
-/**
- * Returns false if the format mask is not valid for playback with
- * MPD.  This function performs some basic validity checks.
- */
-gcc_pure
-static inline bool audio_format_mask_valid(const struct audio_format *af)
-{
-	return (af->sample_rate == 0 ||
-		audio_valid_sample_rate(af->sample_rate)) &&
-		(af->format == SAMPLE_FORMAT_UNDEFINED ||
-		 audio_valid_sample_format((enum sample_format)af->format)) &&
-		(af->channels == 0 || audio_valid_channel_count(af->channels));
-}
-
-static inline bool audio_format_equals(const struct audio_format *a,
-				       const struct audio_format *b)
-{
-	return a->sample_rate == b->sample_rate &&
-		a->format == b->format &&
-		a->channels == b->channels;
-}
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-void
-audio_format_mask_apply(struct audio_format *af,
-			const struct audio_format *mask);
-
-gcc_const
-static inline unsigned
-sample_format_size(enum sample_format format)
-{
-	switch (format) {
-	case SAMPLE_FORMAT_S8:
-		return 1;
-
-	case SAMPLE_FORMAT_S16:
-		return 2;
-
-	case SAMPLE_FORMAT_S24_P32:
-	case SAMPLE_FORMAT_S32:
-	case SAMPLE_FORMAT_FLOAT:
-		return 4;
-
-	case SAMPLE_FORMAT_DSD:
-		/* each frame has 8 samples per channel */
-		return 1;
-
-	case SAMPLE_FORMAT_UNDEFINED:
-		return 0;
-	}
-
-	assert(false);
-	gcc_unreachable();
-}
-
-/**
- * Returns the size of each (mono) sample in bytes.
- */
-gcc_pure
-static inline unsigned audio_format_sample_size(const struct audio_format *af)
-{
-	return sample_format_size((enum sample_format)af->format);
-}
-
-/**
- * Returns the size of each full frame in bytes.
- */
-gcc_pure
-static inline unsigned
-audio_format_frame_size(const struct audio_format *af)
-{
-	return audio_format_sample_size(af) * af->channels;
-}
-
-/**
- * Returns the floating point factor which converts a time span to a
- * storage size in bytes.
- */
-gcc_pure
-static inline double audio_format_time_to_size(const struct audio_format *af)
-{
-	return af->sample_rate * audio_format_frame_size(af);
-}
-
-/**
- * Renders a #sample_format enum into a string, e.g. for printing it
- * in a log file.
- *
- * @param format a #sample_format enum value
- * @return the string
- */
-gcc_pure gcc_malloc
-const char *
-sample_format_to_string(enum sample_format format);
-
-/**
- * Renders the #audio_format object into a string, e.g. for printing
- * it in a log file.
- *
- * @param af the #audio_format object
- * @param s a buffer to print into
- * @return the string, or NULL if the #audio_format object is invalid
- */
-gcc_pure gcc_malloc
-const char *
-audio_format_to_string(const struct audio_format *af,
-		       struct audio_format_string *s);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/src/decoder/AdPlugDecoderPlugin.cxx b/src/decoder/AdPlugDecoderPlugin.cxx
index a9a90c283..37a95ce5d 100644
--- a/src/decoder/AdPlugDecoderPlugin.cxx
+++ b/src/decoder/AdPlugDecoderPlugin.cxx
@@ -60,11 +60,10 @@ adplug_file_decode(struct decoder *decoder, const char *path_fs)
 	if (player == nullptr)
 		return;
 
-	struct audio_format audio_format;
-	audio_format_init(&audio_format, sample_rate, SAMPLE_FORMAT_S16, 2);
-	assert(audio_format_valid(&audio_format));
+	const AudioFormat audio_format(sample_rate, SampleFormat::S16, 2);
+	assert(audio_format.IsValid());
 
-	decoder_initialized(decoder, &audio_format, false,
+	decoder_initialized(decoder, audio_format, false,
 		player->songlength() / 1000.);
 
 	int16_t buffer[2048];
diff --git a/src/decoder/AudiofileDecoderPlugin.cxx b/src/decoder/AudiofileDecoderPlugin.cxx
index b77c41d02..9c00b20ce 100644
--- a/src/decoder/AudiofileDecoderPlugin.cxx
+++ b/src/decoder/AudiofileDecoderPlugin.cxx
@@ -114,27 +114,27 @@ setup_virtual_fops(struct input_stream *stream)
 	return vf;
 }
 
-static enum sample_format
+static SampleFormat
 audiofile_bits_to_sample_format(int bits)
 {
 	switch (bits) {
 	case 8:
-		return SAMPLE_FORMAT_S8;
+		return SampleFormat::S8;
 
 	case 16:
-		return SAMPLE_FORMAT_S16;
+		return SampleFormat::S16;
 
 	case 24:
-		return SAMPLE_FORMAT_S24_P32;
+		return SampleFormat::S24_P32;
 
 	case 32:
-		return SAMPLE_FORMAT_S32;
+		return SampleFormat::S32;
 	}
 
-	return SAMPLE_FORMAT_UNDEFINED;
+	return SampleFormat::UNDEFINED;
 }
 
-static enum sample_format
+static SampleFormat
 audiofile_setup_sample_format(AFfilehandle af_fp)
 {
 	int fs, bits;
@@ -160,7 +160,7 @@ audiofile_stream_decode(struct decoder *decoder, struct input_stream *is)
 	AFvirtualfile *vf;
 	int fs, frame_count;
 	AFfilehandle af_fp;
-	struct audio_format audio_format;
+	AudioFormat audio_format;
 	float total_time;
 	uint16_t bit_rate;
 	int ret;
@@ -180,7 +180,7 @@ audiofile_stream_decode(struct decoder *decoder, struct input_stream *is)
 		return;
 	}
 
-	if (!audio_format_init_checked(&audio_format,
+	if (!audio_format_init_checked(audio_format,
 				       afGetRate(af_fp, AF_DEFAULT_TRACK),
 				       audiofile_setup_sample_format(af_fp),
 				       afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK),
@@ -199,7 +199,7 @@ audiofile_stream_decode(struct decoder *decoder, struct input_stream *is)
 
 	fs = (int)afGetVirtualFrameSize(af_fp, AF_DEFAULT_TRACK, 1);
 
-	decoder_initialized(decoder, &audio_format, true, total_time);
+	decoder_initialized(decoder, audio_format, true, total_time);
 
 	do {
 		ret = afReadFrames(af_fp, AF_DEFAULT_TRACK, chunk,
diff --git a/src/decoder/DsdiffDecoderPlugin.cxx b/src/decoder/DsdiffDecoderPlugin.cxx
index 4b9a59a7a..10b31a204 100644
--- a/src/decoder/DsdiffDecoderPlugin.cxx
+++ b/src/decoder/DsdiffDecoderPlugin.cxx
@@ -433,9 +433,9 @@ dsdiff_stream_decode(struct decoder *decoder, struct input_stream *is)
 		return;
 
 	GError *error = nullptr;
-	struct audio_format audio_format;
-	if (!audio_format_init_checked(&audio_format, metadata.sample_rate / 8,
-				       SAMPLE_FORMAT_DSD,
+	AudioFormat audio_format;
+	if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8,
+				       SampleFormat::DSD,
 				       metadata.channels, &error)) {
 		g_warning("%s", error->message);
 		g_error_free(error);
@@ -448,7 +448,7 @@ dsdiff_stream_decode(struct decoder *decoder, struct input_stream *is)
 			 (float) metadata.sample_rate;
 
 	/* success: file was recognized */
-	decoder_initialized(decoder, &audio_format, false, songtime);
+	decoder_initialized(decoder, audio_format, false, songtime);
 
 	/* every iteration of the following loop decodes one "DSD"
 	   chunk from a DFF file */
@@ -487,9 +487,9 @@ dsdiff_scan_stream(struct input_stream *is,
 	if (!dsdiff_read_metadata(nullptr, is, &metadata, &chunk_header))
 		return false;
 
-	struct audio_format audio_format;
-	if (!audio_format_init_checked(&audio_format, metadata.sample_rate / 8,
-				       SAMPLE_FORMAT_DSD,
+	AudioFormat audio_format;
+	if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8,
+				       SampleFormat::DSD,
 				       metadata.channels, nullptr))
 		/* refuse to parse files which we cannot play anyway */
 		return false;
diff --git a/src/decoder/DsfDecoderPlugin.cxx b/src/decoder/DsfDecoderPlugin.cxx
index 9661d70e6..ad1323d88 100644
--- a/src/decoder/DsfDecoderPlugin.cxx
+++ b/src/decoder/DsfDecoderPlugin.cxx
@@ -285,9 +285,9 @@ dsf_stream_decode(struct decoder *decoder, struct input_stream *is)
 		return;
 
 	GError *error = NULL;
-	struct audio_format audio_format;
-	if (!audio_format_init_checked(&audio_format, metadata.sample_rate / 8,
-				       SAMPLE_FORMAT_DSD,
+	AudioFormat audio_format;
+	if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8,
+				       SampleFormat::DSD,
 				       metadata.channels, &error)) {
 		g_warning("%s", error->message);
 		g_error_free(error);
@@ -299,7 +299,7 @@ dsf_stream_decode(struct decoder *decoder, struct input_stream *is)
 			 (float) metadata.sample_rate;
 
 	/* success: file was recognized */
-	decoder_initialized(decoder, &audio_format, false, songtime);
+	decoder_initialized(decoder, audio_format, false, songtime);
 
 	if (!dsf_decode_chunk(decoder, is, metadata.channels,
 			      chunk_size,
@@ -317,9 +317,9 @@ dsf_scan_stream(struct input_stream *is,
 	if (!dsf_read_metadata(NULL, is, &metadata))
 		return false;
 
-	struct audio_format audio_format;
-	if (!audio_format_init_checked(&audio_format, metadata.sample_rate / 8,
-				       SAMPLE_FORMAT_DSD,
+	AudioFormat audio_format;
+	if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8,
+				       SampleFormat::DSD,
 				       metadata.channels, NULL))
 		/* refuse to parse files which we cannot play anyway */
 		return false;
diff --git a/src/decoder/FaadDecoderPlugin.cxx b/src/decoder/FaadDecoderPlugin.cxx
index 1b7edb49f..547ba24e0 100644
--- a/src/decoder/FaadDecoderPlugin.cxx
+++ b/src/decoder/FaadDecoderPlugin.cxx
@@ -248,7 +248,7 @@ faad_song_duration(DecoderBuffer *buffer, struct input_stream *is)
  */
 static bool
 faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer,
-		  struct audio_format *audio_format, GError **error_r)
+		  AudioFormat &audio_format, GError **error_r)
 {
 	int32_t nbytes;
 	uint32_t sample_rate;
@@ -285,7 +285,7 @@ faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer,
 	decoder_buffer_consume(buffer, nbytes);
 
 	return audio_format_init_checked(audio_format, sample_rate,
-					 SAMPLE_FORMAT_S16, channels, error_r);
+					 SampleFormat::S16, channels, error_r);
 }
 
 /**
@@ -325,7 +325,7 @@ faad_get_file_time_float(struct input_stream *is)
 
 	if (length < 0) {
 		bool ret;
-		struct audio_format audio_format;
+		AudioFormat audio_format;
 
 		NeAACDecHandle decoder = NeAACDecOpen();
 
@@ -336,7 +336,7 @@ faad_get_file_time_float(struct input_stream *is)
 
 		decoder_buffer_fill(buffer);
 
-		ret = faad_decoder_init(decoder, buffer, &audio_format, nullptr);
+		ret = faad_decoder_init(decoder, buffer, audio_format, nullptr);
 		if (ret)
 			length = 0;
 
@@ -370,7 +370,7 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
 {
 	GError *error = nullptr;
 	float total_time = 0;
-	struct audio_format audio_format;
+	AudioFormat audio_format;
 	bool ret;
 	uint16_t bit_rate = 0;
 	DecoderBuffer *buffer;
@@ -400,7 +400,7 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
 
 	/* initialize it */
 
-	ret = faad_decoder_init(decoder, buffer, &audio_format, &error);
+	ret = faad_decoder_init(decoder, buffer, audio_format, &error);
 	if (!ret) {
 		g_warning("%s", error->message);
 		g_error_free(error);
@@ -410,7 +410,7 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
 
 	/* initialize the MPD core */
 
-	decoder_initialized(mpd_decoder, &audio_format, false, total_time);
+	decoder_initialized(mpd_decoder, audio_format, false, total_time);
 
 	/* the decoder loop */
 
diff --git a/src/decoder/FfmpegDecoderPlugin.cxx b/src/decoder/FfmpegDecoderPlugin.cxx
index b4aa947c9..e4330f4d6 100644
--- a/src/decoder/FfmpegDecoderPlugin.cxx
+++ b/src/decoder/FfmpegDecoderPlugin.cxx
@@ -52,6 +52,11 @@ extern "C" {
 #undef G_LOG_DOMAIN
 #define G_LOG_DOMAIN "ffmpeg"
 
+/* suppress the ffmpeg compatibility macro */
+#ifdef SampleFormat
+#undef SampleFormat
+#endif
+
 static GLogLevelFlags
 level_ffmpeg_to_glib(int level)
 {
@@ -297,20 +302,20 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
 }
 
 G_GNUC_CONST
-static enum sample_format
+static SampleFormat
 ffmpeg_sample_format(enum AVSampleFormat sample_fmt)
 {
 	switch (sample_fmt) {
 	case AV_SAMPLE_FMT_S16:
 	case AV_SAMPLE_FMT_S16P:
-		return SAMPLE_FORMAT_S16;
+		return SampleFormat::S16;
 
 	case AV_SAMPLE_FMT_S32:
 	case AV_SAMPLE_FMT_S32P:
-		return SAMPLE_FORMAT_S32;
+		return SampleFormat::S32;
 
 	case AV_SAMPLE_FMT_FLTP:
-		return SAMPLE_FORMAT_FLOAT;
+		return SampleFormat::FLOAT;
 
 	default:
 		break;
@@ -325,7 +330,7 @@ ffmpeg_sample_format(enum AVSampleFormat sample_fmt)
 	else
 		g_warning("Unsupported libavcodec SampleFormat value: %d",
 			  sample_fmt);
-	return SAMPLE_FORMAT_UNDEFINED;
+	return SampleFormat::UNDEFINED;
 }
 
 static AVInputFormat *
@@ -420,14 +425,14 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
 		return;
 	}
 
-	const enum sample_format sample_format =
+	const SampleFormat sample_format =
 		ffmpeg_sample_format(codec_context->sample_fmt);
-	if (sample_format == SAMPLE_FORMAT_UNDEFINED)
+	if (sample_format == SampleFormat::UNDEFINED)
 		return;
 
 	GError *error = NULL;
-	struct audio_format audio_format;
-	if (!audio_format_init_checked(&audio_format,
+	AudioFormat audio_format;
+	if (!audio_format_init_checked(audio_format,
 				       codec_context->sample_rate,
 				       sample_format,
 				       codec_context->channels, &error)) {
@@ -455,7 +460,7 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
 		? format_context->duration / AV_TIME_BASE
 		: 0;
 
-	decoder_initialized(decoder, &audio_format,
+	decoder_initialized(decoder, audio_format,
 			    input->seekable, total_time);
 
 	AVFrame *frame = avcodec_alloc_frame();
diff --git a/src/decoder/FfmpegMetaData.hxx b/src/decoder/FfmpegMetaData.hxx
index 466d2cb1d..0fd73df04 100644
--- a/src/decoder/FfmpegMetaData.hxx
+++ b/src/decoder/FfmpegMetaData.hxx
@@ -26,6 +26,11 @@ extern "C" {
 #include <libavutil/dict.h>
 }
 
+/* suppress the ffmpeg compatibility macro */
+#ifdef SampleFormat
+#undef SampleFormat
+#endif
+
 struct tag_handler;
 
 void
diff --git a/src/decoder/FlacCommon.cxx b/src/decoder/FlacCommon.cxx
index 32917ed86..5bcc20b97 100644
--- a/src/decoder/FlacCommon.cxx
+++ b/src/decoder/FlacCommon.cxx
@@ -40,24 +40,24 @@ flac_data::flac_data(struct decoder *_decoder,
 {
 }
 
-static enum sample_format
+static SampleFormat
 flac_sample_format(unsigned bits_per_sample)
 {
 	switch (bits_per_sample) {
 	case 8:
-		return SAMPLE_FORMAT_S8;
+		return SampleFormat::S8;
 
 	case 16:
-		return SAMPLE_FORMAT_S16;
+		return SampleFormat::S16;
 
 	case 24:
-		return SAMPLE_FORMAT_S24_P32;
+		return SampleFormat::S24_P32;
 
 	case 32:
-		return SAMPLE_FORMAT_S32;
+		return SampleFormat::S32;
 
 	default:
-		return SAMPLE_FORMAT_UNDEFINED;
+		return SampleFormat::UNDEFINED;
 	}
 }
 
@@ -69,7 +69,7 @@ flac_got_stream_info(struct flac_data *data,
 		return;
 
 	GError *error = nullptr;
-	if (!audio_format_init_checked(&data->audio_format,
+	if (!audio_format_init_checked(data->audio_format,
 				       stream_info->sample_rate,
 				       flac_sample_format(stream_info->bits_per_sample),
 				       stream_info->channels, &error)) {
@@ -79,7 +79,7 @@ flac_got_stream_info(struct flac_data *data,
 		return;
 	}
 
-	data->frame_size = audio_format_frame_size(&data->audio_format);
+	data->frame_size = data->audio_format.GetFrameSize();
 
 	if (data->total_frames == 0)
 		data->total_frames = stream_info->total_samples;
@@ -132,7 +132,7 @@ flac_got_first_frame(struct flac_data *data, const FLAC__FrameHeader *header)
 		return false;
 
 	GError *error = nullptr;
-	if (!audio_format_init_checked(&data->audio_format,
+	if (!audio_format_init_checked(data->audio_format,
 				       header->sample_rate,
 				       flac_sample_format(header->bits_per_sample),
 				       header->channels, &error)) {
@@ -142,9 +142,9 @@ flac_got_first_frame(struct flac_data *data, const FLAC__FrameHeader *header)
 		return false;
 	}
 
-	data->frame_size = audio_format_frame_size(&data->audio_format);
+	data->frame_size = data->audio_format.GetFrameSize();
 
-	decoder_initialized(data->decoder, &data->audio_format,
+	decoder_initialized(data->decoder, data->audio_format,
 			    data->input_stream->seekable,
 			    (float)data->total_frames /
 			    (float)data->audio_format.sample_rate);
@@ -170,7 +170,7 @@ flac_common_write(struct flac_data *data, const FLAC__Frame * frame,
 	buffer = data->buffer.Get(buffer_size);
 
 	flac_convert(buffer, frame->header.channels,
-		     (enum sample_format)data->audio_format.format, buf,
+		     data->audio_format.format, buf,
 		     0, frame->header.blocksize);
 
 	if (nbytes > 0)
diff --git a/src/decoder/FlacCommon.hxx b/src/decoder/FlacCommon.hxx
index d2e240d81..f9fade6fc 100644
--- a/src/decoder/FlacCommon.hxx
+++ b/src/decoder/FlacCommon.hxx
@@ -56,7 +56,7 @@ struct flac_data : public FlacInput {
 	 * The validated audio format of the FLAC file.  This
 	 * attribute is defined if "initialized" is true.
 	 */
-	struct audio_format audio_format;
+	AudioFormat audio_format;
 
 	/**
 	 * The total number of frames in this song.  The decoder
diff --git a/src/decoder/FlacDecoderPlugin.cxx b/src/decoder/FlacDecoderPlugin.cxx
index fc0925610..7becf73e5 100644
--- a/src/decoder/FlacDecoderPlugin.cxx
+++ b/src/decoder/FlacDecoderPlugin.cxx
@@ -144,7 +144,7 @@ flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd,
 
 	if (data->initialized) {
 		/* done */
-		decoder_initialized(data->decoder, &data->audio_format,
+		decoder_initialized(data->decoder, data->audio_format,
 				    data->input_stream->seekable,
 				    (float)data->total_frames /
 				    (float)data->audio_format.sample_rate);
diff --git a/src/decoder/FlacPcm.cxx b/src/decoder/FlacPcm.cxx
index 17a13edda..ff855fa70 100644
--- a/src/decoder/FlacPcm.cxx
+++ b/src/decoder/FlacPcm.cxx
@@ -76,12 +76,12 @@ flac_convert_8(int8_t *dest,
 
 void
 flac_convert(void *dest,
-	     unsigned int num_channels, enum sample_format sample_format,
+	     unsigned int num_channels, SampleFormat sample_format,
 	     const FLAC__int32 *const buf[],
 	     unsigned int position, unsigned int end)
 {
 	switch (sample_format) {
-	case SAMPLE_FORMAT_S16:
+	case SampleFormat::S16:
 		if (num_channels == 2)
 			flac_convert_stereo16((int16_t*)dest, buf,
 					      position, end);
@@ -90,20 +90,20 @@ flac_convert(void *dest,
 					position, end);
 		break;
 
-	case SAMPLE_FORMAT_S24_P32:
-	case SAMPLE_FORMAT_S32:
+	case SampleFormat::S24_P32:
+	case SampleFormat::S32:
 		flac_convert_32((int32_t*)dest, num_channels, buf,
 				position, end);
 		break;
 
-	case SAMPLE_FORMAT_S8:
+	case SampleFormat::S8:
 		flac_convert_8((int8_t*)dest, num_channels, buf,
 			       position, end);
 		break;
 
-	case SAMPLE_FORMAT_FLOAT:
-	case SAMPLE_FORMAT_DSD:
-	case SAMPLE_FORMAT_UNDEFINED:
+	case SampleFormat::FLOAT:
+	case SampleFormat::DSD:
+	case SampleFormat::UNDEFINED:
 		assert(false);
 		gcc_unreachable();
 	}
diff --git a/src/decoder/FlacPcm.hxx b/src/decoder/FlacPcm.hxx
index 97d214c17..fa85f65dd 100644
--- a/src/decoder/FlacPcm.hxx
+++ b/src/decoder/FlacPcm.hxx
@@ -20,13 +20,13 @@
 #ifndef MPD_FLAC_PCM_HXX
 #define MPD_FLAC_PCM_HXX
 
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 
 #include <FLAC/ordinals.h>
 
 void
 flac_convert(void *dest,
-	     unsigned int num_channels, enum sample_format sample_format,
+	     unsigned int num_channels, SampleFormat sample_format,
 	     const FLAC__int32 *const buf[],
 	     unsigned int position, unsigned int end);
 
diff --git a/src/decoder/FluidsynthDecoderPlugin.cxx b/src/decoder/FluidsynthDecoderPlugin.cxx
index 15d2f5e6b..e559ad45e 100644
--- a/src/decoder/FluidsynthDecoderPlugin.cxx
+++ b/src/decoder/FluidsynthDecoderPlugin.cxx
@@ -166,9 +166,8 @@ fluidsynth_file_decode(struct decoder *decoder, const char *path_fs)
 	/* initialization complete - announce the audio format to the
 	   MPD core */
 
-	struct audio_format audio_format;
-	audio_format_init(&audio_format, sample_rate, SAMPLE_FORMAT_S16, 2);
-	decoder_initialized(decoder, &audio_format, false, -1);
+	const AudioFormat audio_format(sample_rate, SampleFormat::S16, 2);
+	decoder_initialized(decoder, audio_format, false, -1);
 
 	while (fluid_player_get_status(player) == FLUID_PLAYER_PLAYING) {
 		int16_t buffer[2048];
diff --git a/src/decoder/GmeDecoderPlugin.cxx b/src/decoder/GmeDecoderPlugin.cxx
index 8158ab553..d8edbe4cb 100644
--- a/src/decoder/GmeDecoderPlugin.cxx
+++ b/src/decoder/GmeDecoderPlugin.cxx
@@ -153,9 +153,9 @@ gme_file_decode(struct decoder *decoder, const char *path_fs)
 	/* initialize the MPD decoder */
 
 	GError *error = nullptr;
-	struct audio_format audio_format;
-	if (!audio_format_init_checked(&audio_format, GME_SAMPLE_RATE,
-				       SAMPLE_FORMAT_S16, GME_CHANNELS,
+	AudioFormat audio_format;
+	if (!audio_format_init_checked(audio_format, GME_SAMPLE_RATE,
+				       SampleFormat::S16, GME_CHANNELS,
 				       &error)) {
 		g_warning("%s", error->message);
 		g_error_free(error);
@@ -164,7 +164,7 @@ gme_file_decode(struct decoder *decoder, const char *path_fs)
 		return;
 	}
 
-	decoder_initialized(decoder, &audio_format, true, song_len);
+	decoder_initialized(decoder, audio_format, true, song_len);
 
 	gme_err = gme_start_track(emu, song_num);
 	if (gme_err != nullptr)
diff --git a/src/decoder/MadDecoderPlugin.cxx b/src/decoder/MadDecoderPlugin.cxx
index b75e12343..04d171b9b 100644
--- a/src/decoder/MadDecoderPlugin.cxx
+++ b/src/decoder/MadDecoderPlugin.cxx
@@ -1124,11 +1124,11 @@ mp3_decode(struct decoder *decoder, struct input_stream *input_stream)
 		return;
 	}
 
-	struct audio_format audio_format;
+	AudioFormat audio_format;
 	GError *error = nullptr;
-	if (!audio_format_init_checked(&audio_format,
+	if (!audio_format_init_checked(audio_format,
 				       data.frame.header.samplerate,
-				       SAMPLE_FORMAT_S24_P32,
+				       SampleFormat::S24_P32,
 				       MAD_NCHANNELS(&data.frame.header),
 				       &error)) {
 		g_warning("%s", error->message);
@@ -1138,7 +1138,7 @@ mp3_decode(struct decoder *decoder, struct input_stream *input_stream)
 		return;
 	}
 
-	decoder_initialized(decoder, &audio_format,
+	decoder_initialized(decoder, audio_format,
 			    input_stream_is_seekable(input_stream),
 			    data.total_time);
 
diff --git a/src/decoder/MikmodDecoderPlugin.cxx b/src/decoder/MikmodDecoderPlugin.cxx
index d332664ee..3aa8a68ed 100644
--- a/src/decoder/MikmodDecoderPlugin.cxx
+++ b/src/decoder/MikmodDecoderPlugin.cxx
@@ -147,7 +147,6 @@ mikmod_decoder_file_decode(struct decoder *decoder, const char *path_fs)
 {
 	char *path2;
 	MODULE *handle;
-	struct audio_format audio_format;
 	int ret;
 	SBYTE buffer[MIKMOD_FRAME_SIZE];
 	enum decoder_command cmd = DECODE_COMMAND_NONE;
@@ -164,10 +163,10 @@ mikmod_decoder_file_decode(struct decoder *decoder, const char *path_fs)
 	/* Prevent module from looping forever */
 	handle->loop = 0;
 
-	audio_format_init(&audio_format, mikmod_sample_rate, SAMPLE_FORMAT_S16, 2);
-	assert(audio_format_valid(&audio_format));
+	const AudioFormat audio_format(mikmod_sample_rate, SampleFormat::S16, 2);
+	assert(audio_format.IsValid());
 
-	decoder_initialized(decoder, &audio_format, false, 0);
+	decoder_initialized(decoder, audio_format, false, 0);
 
 	Player_Start(handle);
 	while (cmd == DECODE_COMMAND_NONE && Player_Active()) {
diff --git a/src/decoder/ModplugDecoderPlugin.cxx b/src/decoder/ModplugDecoderPlugin.cxx
index 2ba3b0f49..b95736bf8 100644
--- a/src/decoder/ModplugDecoderPlugin.cxx
+++ b/src/decoder/ModplugDecoderPlugin.cxx
@@ -94,7 +94,6 @@ mod_decode(struct decoder *decoder, struct input_stream *is)
 	ModPlugFile *f;
 	ModPlug_Settings settings;
 	GByteArray *bdatas;
-	struct audio_format audio_format;
 	int ret;
 	char audio_buffer[MODPLUG_FRAME_SIZE];
 	enum decoder_command cmd = DECODE_COMMAND_NONE;
@@ -122,10 +121,10 @@ mod_decode(struct decoder *decoder, struct input_stream *is)
 		return;
 	}
 
-	audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2);
-	assert(audio_format_valid(&audio_format));
+	static constexpr AudioFormat audio_format(44100, SampleFormat::S16, 2);
+	assert(audio_format.IsValid());
 
-	decoder_initialized(decoder, &audio_format,
+	decoder_initialized(decoder, audio_format,
 			    input_stream_is_seekable(is),
 			    ModPlug_GetLength(f) / 1000.0);
 
diff --git a/src/decoder/MpcdecDecoderPlugin.cxx b/src/decoder/MpcdecDecoderPlugin.cxx
index 921d7d923..cfb9c034b 100644
--- a/src/decoder/MpcdecDecoderPlugin.cxx
+++ b/src/decoder/MpcdecDecoderPlugin.cxx
@@ -154,9 +154,9 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is)
 	mpc_demux_get_info(demux, &info);
 
 	GError *error = nullptr;
-	struct audio_format audio_format;
-	if (!audio_format_init_checked(&audio_format, info.sample_freq,
-				       SAMPLE_FORMAT_S24_P32,
+	AudioFormat audio_format;
+	if (!audio_format_init_checked(audio_format, info.sample_freq,
+				       SampleFormat::S24_P32,
 				       info.channels, &error)) {
 		g_warning("%s", error->message);
 		g_error_free(error);
@@ -173,7 +173,7 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is)
 
 	decoder_replay_gain(mpd_decoder, &replay_gain_info);
 
-	decoder_initialized(mpd_decoder, &audio_format,
+	decoder_initialized(mpd_decoder, audio_format,
 			    input_stream_is_seekable(is),
 			    mpc_streaminfo_get_length(&info));
 
diff --git a/src/decoder/Mpg123DecoderPlugin.cxx b/src/decoder/Mpg123DecoderPlugin.cxx
index 73f94ea44..1aac825a2 100644
--- a/src/decoder/Mpg123DecoderPlugin.cxx
+++ b/src/decoder/Mpg123DecoderPlugin.cxx
@@ -56,7 +56,7 @@ mpd_mpg123_finish(void)
  */
 static bool
 mpd_mpg123_open(mpg123_handle *handle, const char *path_fs,
-		struct audio_format *audio_format)
+		AudioFormat &audio_format)
 {
 	GError *gerror = nullptr;
 	char *path_dup;
@@ -90,7 +90,7 @@ mpd_mpg123_open(mpg123_handle *handle, const char *path_fs,
 		return false;
 	}
 
-	if (!audio_format_init_checked(audio_format, rate, SAMPLE_FORMAT_S16,
+	if (!audio_format_init_checked(audio_format, rate, SampleFormat::S16,
 				       channels, &gerror)) {
 		g_warning("%s", gerror->message);
 		g_error_free(gerror);
@@ -103,7 +103,6 @@ mpd_mpg123_open(mpg123_handle *handle, const char *path_fs,
 static void
 mpd_mpg123_file_decode(struct decoder *decoder, const char *path_fs)
 {
-	struct audio_format audio_format;
 	mpg123_handle *handle;
 	int error;
 	off_t num_samples;
@@ -119,7 +118,8 @@ mpd_mpg123_file_decode(struct decoder *decoder, const char *path_fs)
 		return;
 	}
 
-	if (!mpd_mpg123_open(handle, path_fs, &audio_format)) {
+	AudioFormat audio_format;
+	if (!mpd_mpg123_open(handle, path_fs, audio_format)) {
 		mpg123_delete(handle);
 		return;
 	}
@@ -128,7 +128,7 @@ mpd_mpg123_file_decode(struct decoder *decoder, const char *path_fs)
 
 	/* tell MPD core we're ready */
 
-	decoder_initialized(decoder, &audio_format, true,
+	decoder_initialized(decoder, audio_format, true,
 			    (float)num_samples /
 			    (float)audio_format.sample_rate);
 
@@ -198,7 +198,6 @@ static bool
 mpd_mpg123_scan_file(const char *path_fs,
 		     const struct tag_handler *handler, void *handler_ctx)
 {
-	struct audio_format audio_format;
 	mpg123_handle *handle;
 	int error;
 	off_t num_samples;
@@ -210,7 +209,8 @@ mpd_mpg123_scan_file(const char *path_fs,
 		return false;
 	}
 
-	if (!mpd_mpg123_open(handle, path_fs, &audio_format)) {
+	AudioFormat audio_format;
+	if (!mpd_mpg123_open(handle, path_fs, audio_format)) {
 		mpg123_delete(handle);
 		return false;
 	}
diff --git a/src/decoder/OpusDecoderPlugin.cxx b/src/decoder/OpusDecoderPlugin.cxx
index 08c67b570..94c687317 100644
--- a/src/decoder/OpusDecoderPlugin.cxx
+++ b/src/decoder/OpusDecoderPlugin.cxx
@@ -202,11 +202,10 @@ MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
 		return DECODE_COMMAND_STOP;
 	}
 
-	struct audio_format audio_format;
-	audio_format_init(&audio_format, opus_sample_rate,
-			  SAMPLE_FORMAT_S16, channels);
-	decoder_initialized(decoder, &audio_format, false, -1);
-	frame_size = audio_format_frame_size(&audio_format);
+	const AudioFormat audio_format(opus_sample_rate,
+				       SampleFormat::S16, channels);
+	decoder_initialized(decoder, audio_format, false, -1);
+	frame_size = audio_format.GetFrameSize();
 
 	/* allocate an output buffer for 16 bit PCM samples big enough
 	   to hold a quarter second, larger than 120ms required by
diff --git a/src/decoder/PcmDecoderPlugin.cxx b/src/decoder/PcmDecoderPlugin.cxx
index f64357e68..8976f511f 100644
--- a/src/decoder/PcmDecoderPlugin.cxx
+++ b/src/decoder/PcmDecoderPlugin.cxx
@@ -36,9 +36,9 @@ extern "C" {
 static void
 pcm_stream_decode(struct decoder *decoder, struct input_stream *is)
 {
-	static constexpr struct audio_format audio_format = {
+	static constexpr AudioFormat audio_format = {
 		44100,
-		SAMPLE_FORMAT_S16,
+		SampleFormat::S16,
 		2,
 	};
 
@@ -49,14 +49,14 @@ pcm_stream_decode(struct decoder *decoder, struct input_stream *is)
 	GError *error = nullptr;
 	enum decoder_command cmd;
 
-	double time_to_size = audio_format_time_to_size(&audio_format);
+	const double time_to_size = audio_format.GetTimeToSize();
 
 	float total_time = -1;
 	const goffset size = input_stream_get_size(is);
 	if (size >= 0)
 		total_time = size / time_to_size;
 
-	decoder_initialized(decoder, &audio_format,
+	decoder_initialized(decoder, audio_format,
 			    input_stream_is_seekable(is), total_time);
 
 	do {
diff --git a/src/decoder/SndfileDecoderPlugin.cxx b/src/decoder/SndfileDecoderPlugin.cxx
index b1bb97538..63401a47b 100644
--- a/src/decoder/SndfileDecoderPlugin.cxx
+++ b/src/decoder/SndfileDecoderPlugin.cxx
@@ -99,7 +99,7 @@ static SF_VIRTUAL_IO vio = {
  * Converts a frame number to a timestamp (in seconds).
  */
 static float
-frame_to_time(sf_count_t frame, const struct audio_format *audio_format)
+frame_to_time(sf_count_t frame, const AudioFormat *audio_format)
 {
 	return (float)frame / (float)audio_format->sample_rate;
 }
@@ -108,7 +108,7 @@ frame_to_time(sf_count_t frame, const struct audio_format *audio_format)
  * Converts a timestamp (in seconds) to a frame number.
  */
 static sf_count_t
-time_to_frame(float t, const struct audio_format *audio_format)
+time_to_frame(float t, const AudioFormat *audio_format)
 {
 	return (sf_count_t)(t * audio_format->sample_rate);
 }
@@ -119,7 +119,6 @@ sndfile_stream_decode(struct decoder *decoder, struct input_stream *is)
 	GError *error = nullptr;
 	SNDFILE *sf;
 	SF_INFO info;
-	struct audio_format audio_format;
 	size_t frame_size;
 	sf_count_t read_frames, num_frames;
 	int buffer[4096];
@@ -136,18 +135,19 @@ sndfile_stream_decode(struct decoder *decoder, struct input_stream *is)
 	/* for now, always read 32 bit samples.  Later, we could lower
 	   MPD's CPU usage by reading 16 bit samples with
 	   sf_readf_short() on low-quality source files. */
-	if (!audio_format_init_checked(&audio_format, info.samplerate,
-				       SAMPLE_FORMAT_S32,
+	AudioFormat audio_format;
+	if (!audio_format_init_checked(audio_format, info.samplerate,
+				       SampleFormat::S32,
 				       info.channels, &error)) {
 		g_warning("%s", error->message);
 		g_error_free(error);
 		return;
 	}
 
-	decoder_initialized(decoder, &audio_format, info.seekable,
+	decoder_initialized(decoder, audio_format, info.seekable,
 			    frame_to_time(info.frames, &audio_format));
 
-	frame_size = audio_format_frame_size(&audio_format);
+	frame_size = audio_format.GetFrameSize();
 	read_frames = sizeof(buffer) / frame_size;
 
 	do {
diff --git a/src/decoder/VorbisDecoderPlugin.cxx b/src/decoder/VorbisDecoderPlugin.cxx
index 68d5a21f0..f51480d71 100644
--- a/src/decoder/VorbisDecoderPlugin.cxx
+++ b/src/decoder/VorbisDecoderPlugin.cxx
@@ -202,12 +202,12 @@ vorbis_stream_decode(struct decoder *decoder,
 		return;
 	}
 
-	struct audio_format audio_format;
-	if (!audio_format_init_checked(&audio_format, vi->rate,
+	AudioFormat audio_format;
+	if (!audio_format_init_checked(audio_format, vi->rate,
 #ifdef HAVE_TREMOR
-				       SAMPLE_FORMAT_S16,
+				       SampleFormat::S16,
 #else
-				       SAMPLE_FORMAT_FLOAT,
+				       SampleFormat::FLOAT,
 #endif
 				       vi->channels, &error)) {
 		g_warning("%s", error->message);
@@ -219,7 +219,7 @@ vorbis_stream_decode(struct decoder *decoder,
 	if (total_time < 0)
 		total_time = 0;
 
-	decoder_initialized(decoder, &audio_format, vis.seekable, total_time);
+	decoder_initialized(decoder, audio_format, vis.seekable, total_time);
 
 	enum decoder_command cmd = decoder_get_command(decoder);
 
diff --git a/src/decoder/WavpackDecoderPlugin.cxx b/src/decoder/WavpackDecoderPlugin.cxx
index 1a31b7aac..aa62a0f67 100644
--- a/src/decoder/WavpackDecoderPlugin.cxx
+++ b/src/decoder/WavpackDecoderPlugin.cxx
@@ -106,27 +106,27 @@ format_samples_float(G_GNUC_UNUSED int bytes_per_sample, void *buffer,
 /**
  * Choose a MPD sample format from libwavpacks' number of bits.
  */
-static enum sample_format
+static SampleFormat
 wavpack_bits_to_sample_format(bool is_float, int bytes_per_sample)
 {
 	if (is_float)
-		return SAMPLE_FORMAT_FLOAT;
+		return SampleFormat::FLOAT;
 
 	switch (bytes_per_sample) {
 	case 1:
-		return SAMPLE_FORMAT_S8;
+		return SampleFormat::S8;
 
 	case 2:
-		return SAMPLE_FORMAT_S16;
+		return SampleFormat::S16;
 
 	case 3:
-		return SAMPLE_FORMAT_S24_P32;
+		return SampleFormat::S24_P32;
 
 	case 4:
-		return SAMPLE_FORMAT_S32;
+		return SampleFormat::S32;
 
 	default:
-		return SAMPLE_FORMAT_UNDEFINED;
+		return SampleFormat::UNDEFINED;
 	}
 }
 
@@ -139,8 +139,8 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek)
 {
 	GError *error = NULL;
 	bool is_float;
-	enum sample_format sample_format;
-	struct audio_format audio_format;
+	SampleFormat sample_format;
+	AudioFormat audio_format;
 	format_samples_t format_samples;
 	float total_time;
 	int bytes_per_sample, output_sample_size;
@@ -150,7 +150,7 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek)
 		wavpack_bits_to_sample_format(is_float,
 					      WavpackGetBytesPerSample(wpc));
 
-	if (!audio_format_init_checked(&audio_format,
+	if (!audio_format_init_checked(audio_format,
 				       WavpackGetSampleRate(wpc),
 				       sample_format,
 				       WavpackGetNumChannels(wpc), &error)) {
@@ -168,14 +168,14 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek)
 	total_time = WavpackGetNumSamples(wpc);
 	total_time /= audio_format.sample_rate;
 	bytes_per_sample = WavpackGetBytesPerSample(wpc);
-	output_sample_size = audio_format_frame_size(&audio_format);
+	output_sample_size = audio_format.GetFrameSize();
 
 	/* wavpack gives us all kind of samples in a 32-bit space */
 	int32_t chunk[1024];
 	const uint32_t samples_requested = G_N_ELEMENTS(chunk) /
 		audio_format.channels;
 
-	decoder_initialized(decoder, &audio_format, can_seek, total_time);
+	decoder_initialized(decoder, audio_format, can_seek, total_time);
 
 	enum decoder_command cmd = decoder_get_command(decoder);
 	while (cmd != DECODE_COMMAND_STOP) {
diff --git a/src/decoder/WildmidiDecoderPlugin.cxx b/src/decoder/WildmidiDecoderPlugin.cxx
index 721229f87..832cabe76 100644
--- a/src/decoder/WildmidiDecoderPlugin.cxx
+++ b/src/decoder/WildmidiDecoderPlugin.cxx
@@ -60,9 +60,9 @@ wildmidi_finish(void)
 static void
 wildmidi_file_decode(struct decoder *decoder, const char *path_fs)
 {
-	static const struct audio_format audio_format = {
+	static constexpr AudioFormat audio_format = {
 		WILDMIDI_SAMPLE_RATE,
-		SAMPLE_FORMAT_S16,
+		SampleFormat::S16,
 		2,
 	};
 	midi *wm;
@@ -79,7 +79,7 @@ wildmidi_file_decode(struct decoder *decoder, const char *path_fs)
 		return;
 	}
 
-	decoder_initialized(decoder, &audio_format, true,
+	decoder_initialized(decoder, audio_format, true,
 			    info->approx_total_samples / WILDMIDI_SAMPLE_RATE);
 
 	do {
diff --git a/src/decoder/sidplay_decoder_plugin.cxx b/src/decoder/sidplay_decoder_plugin.cxx
index cfe82cf57..d63dca6af 100644
--- a/src/decoder/sidplay_decoder_plugin.cxx
+++ b/src/decoder/sidplay_decoder_plugin.cxx
@@ -285,11 +285,10 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
 
 	/* initialize the MPD decoder */
 
-	struct audio_format audio_format;
-	audio_format_init(&audio_format, 48000, SAMPLE_FORMAT_S16, channels);
-	assert(audio_format_valid(&audio_format));
+	const AudioFormat audio_format(48000, SampleFormat::S16, channels);
+	assert(audio_format.IsValid());
 
-	decoder_initialized(decoder, &audio_format, true, (float)song_len);
+	decoder_initialized(decoder, audio_format, true, (float)song_len);
 
 	/* .. and play */
 
diff --git a/src/encoder/FlacEncoderPlugin.cxx b/src/encoder/FlacEncoderPlugin.cxx
index 3aeb96cf7..5e7e7f526 100644
--- a/src/encoder/FlacEncoderPlugin.cxx
+++ b/src/encoder/FlacEncoderPlugin.cxx
@@ -20,7 +20,7 @@
 #include "config.h"
 #include "FlacEncoderPlugin.hxx"
 #include "EncoderAPI.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 #include "pcm/PcmBuffer.hxx"
 #include "util/fifo_buffer.h"
 
@@ -40,7 +40,7 @@ extern "C" {
 struct flac_encoder {
 	Encoder encoder;
 
-	struct audio_format audio_format;
+	AudioFormat audio_format;
 	unsigned compression;
 
 	FLAC__StreamEncoder *fse;
@@ -160,31 +160,31 @@ flac_encoder_close(Encoder *_encoder)
 }
 
 static bool
-flac_encoder_open(Encoder *_encoder, struct audio_format *audio_format,
+flac_encoder_open(Encoder *_encoder, AudioFormat &audio_format,
 		     GError **error)
 {
 	struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
 	unsigned bits_per_sample;
 
-	encoder->audio_format = *audio_format;
+	encoder->audio_format = audio_format;
 
 	/* FIXME: flac should support 32bit as well */
-	switch (audio_format->format) {
-	case SAMPLE_FORMAT_S8:
+	switch (audio_format.format) {
+	case SampleFormat::S8:
 		bits_per_sample = 8;
 		break;
 
-	case SAMPLE_FORMAT_S16:
+	case SampleFormat::S16:
 		bits_per_sample = 16;
 		break;
 
-	case SAMPLE_FORMAT_S24_P32:
+	case SampleFormat::S24_P32:
 		bits_per_sample = 24;
 		break;
 
 	default:
 		bits_per_sample = 24;
-		audio_format->format = SAMPLE_FORMAT_S24_P32;
+		audio_format.format = SampleFormat::S24_P32;
 	}
 
 	/* allocate the encoder */
@@ -263,30 +263,33 @@ flac_encoder_write(Encoder *_encoder,
 
 	/* format conversion */
 
-	num_frames = length / audio_format_frame_size(&encoder->audio_format);
+	num_frames = length / encoder->audio_format.GetFrameSize();
 	num_samples = num_frames * encoder->audio_format.channels;
 
 	switch (encoder->audio_format.format) {
-	case SAMPLE_FORMAT_S8:
+	case SampleFormat::S8:
 		exbuffer = encoder->expand_buffer.Get(length * 4);
 		pcm8_to_flac((int32_t *)exbuffer, (const int8_t *)data,
 			     num_samples);
 		buffer = exbuffer;
 		break;
 
-	case SAMPLE_FORMAT_S16:
+	case SampleFormat::S16:
 		exbuffer = encoder->expand_buffer.Get(length * 2);
 		pcm16_to_flac((int32_t *)exbuffer, (const int16_t *)data,
 			      num_samples);
 		buffer = exbuffer;
 		break;
 
-	case SAMPLE_FORMAT_S24_P32:
-	case SAMPLE_FORMAT_S32:
+	case SampleFormat::S24_P32:
+	case SampleFormat::S32:
 		/* nothing need to be done; format is the same for
 		   both mpd and libFLAC */
 		buffer = data;
 		break;
+
+	default:
+		gcc_unreachable();
 	}
 
 	/* feed samples to encoder */
diff --git a/src/encoder/LameEncoderPlugin.cxx b/src/encoder/LameEncoderPlugin.cxx
index 933fa3ff2..ea0b91380 100644
--- a/src/encoder/LameEncoderPlugin.cxx
+++ b/src/encoder/LameEncoderPlugin.cxx
@@ -20,7 +20,7 @@
 #include "config.h"
 #include "LameEncoderPlugin.hxx"
 #include "EncoderAPI.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 
 #include <lame/lame.h>
 
@@ -32,7 +32,7 @@
 struct LameEncoder final {
 	Encoder encoder;
 
-	struct audio_format audio_format;
+	AudioFormat audio_format;
 	float quality;
 	int bitrate;
 
@@ -187,15 +187,15 @@ lame_encoder_setup(LameEncoder *encoder, GError **error)
 }
 
 static bool
-lame_encoder_open(Encoder *_encoder, struct audio_format *audio_format,
+lame_encoder_open(Encoder *_encoder, AudioFormat &audio_format,
 		  GError **error)
 {
 	LameEncoder *encoder = (LameEncoder *)_encoder;
 
-	audio_format->format = SAMPLE_FORMAT_S16;
-	audio_format->channels = 2;
+	audio_format.format = SampleFormat::S16;
+	audio_format.channels = 2;
 
-	encoder->audio_format = *audio_format;
+	encoder->audio_format = audio_format;
 
 	encoder->gfp = lame_init();
 	if (encoder->gfp == nullptr) {
@@ -233,7 +233,7 @@ lame_encoder_write(Encoder *_encoder,
 	assert(encoder->buffer_length == 0);
 
 	const unsigned num_frames =
-		length / audio_format_frame_size(&encoder->audio_format);
+		length / encoder->audio_format.GetFrameSize();
 	float *left = g_new(float, num_frames);
 	float *right = g_new(float, num_frames);
 
diff --git a/src/encoder/NullEncoderPlugin.cxx b/src/encoder/NullEncoderPlugin.cxx
index 39e391063..206d55c2f 100644
--- a/src/encoder/NullEncoderPlugin.cxx
+++ b/src/encoder/NullEncoderPlugin.cxx
@@ -66,7 +66,7 @@ null_encoder_close(Encoder *_encoder)
 
 static bool
 null_encoder_open(Encoder *_encoder,
-		  gcc_unused struct audio_format *audio_format,
+		  gcc_unused AudioFormat &audio_format,
 		  gcc_unused GError **error)
 {
 	NullEncoder *encoder = (NullEncoder *)_encoder;
diff --git a/src/encoder/OpusEncoderPlugin.cxx b/src/encoder/OpusEncoderPlugin.cxx
index a5947e4b8..a6f36f7d5 100644
--- a/src/encoder/OpusEncoderPlugin.cxx
+++ b/src/encoder/OpusEncoderPlugin.cxx
@@ -21,7 +21,7 @@
 #include "OpusEncoderPlugin.hxx"
 #include "OggStream.hxx"
 #include "EncoderAPI.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 #include "mpd_error.h"
 
 #include <opus.h>
@@ -44,7 +44,7 @@ struct opus_encoder {
 
 	/* runtime information */
 
-	struct audio_format audio_format;
+	AudioFormat audio_format;
 
 	size_t frame_size;
 
@@ -144,37 +144,37 @@ opus_encoder_finish(Encoder *_encoder)
 
 static bool
 opus_encoder_open(Encoder *_encoder,
-		  struct audio_format *audio_format,
+		  AudioFormat &audio_format,
 		  GError **error_r)
 {
 	struct opus_encoder *encoder = (struct opus_encoder *)_encoder;
 
 	/* libopus supports only 48 kHz */
-	audio_format->sample_rate = 48000;
+	audio_format.sample_rate = 48000;
 
-	if (audio_format->channels > 2)
-		audio_format->channels = 1;
+	if (audio_format.channels > 2)
+		audio_format.channels = 1;
 
-	switch ((enum sample_format)audio_format->format) {
-	case SAMPLE_FORMAT_S16:
-	case SAMPLE_FORMAT_FLOAT:
+	switch (audio_format.format) {
+	case SampleFormat::S16:
+	case SampleFormat::FLOAT:
 		break;
 
-	case SAMPLE_FORMAT_S8:
-		audio_format->format = SAMPLE_FORMAT_S16;
+	case SampleFormat::S8:
+		audio_format.format = SampleFormat::S16;
 		break;
 
 	default:
-		audio_format->format = SAMPLE_FORMAT_FLOAT;
+		audio_format.format = SampleFormat::FLOAT;
 		break;
 	}
 
-	encoder->audio_format = *audio_format;
-	encoder->frame_size = audio_format_frame_size(audio_format);
+	encoder->audio_format = audio_format;
+	encoder->frame_size = audio_format.GetFrameSize();
 
 	int error;
-	encoder->enc = opus_encoder_create(audio_format->sample_rate,
-					   audio_format->channels,
+	encoder->enc = opus_encoder_create(audio_format.sample_rate,
+					   audio_format.channels,
 					   OPUS_APPLICATION_AUDIO,
 					   &error);
 	if (encoder->enc == nullptr) {
@@ -190,7 +190,7 @@ opus_encoder_open(Encoder *_encoder,
 
 	opus_encoder_ctl(encoder->enc, OPUS_GET_LOOKAHEAD(&encoder->lookahead));
 
-	encoder->buffer_frames = audio_format->sample_rate / 50;
+	encoder->buffer_frames = audio_format.sample_rate / 50;
 	encoder->buffer_size = encoder->frame_size * encoder->buffer_frames;
 	encoder->buffer_position = 0;
 	encoder->buffer = (unsigned char *)g_malloc(encoder->buffer_size);
@@ -218,7 +218,7 @@ opus_encoder_do_encode(struct opus_encoder *encoder, bool eos,
 	assert(encoder->buffer_position == encoder->buffer_size);
 
 	opus_int32 result =
-		encoder->audio_format.format == SAMPLE_FORMAT_S16
+		encoder->audio_format.format == SampleFormat::S16
 		? opus_encode(encoder->enc,
 			      (const opus_int16 *)encoder->buffer,
 			      encoder->buffer_frames,
diff --git a/src/encoder/TwolameEncoderPlugin.cxx b/src/encoder/TwolameEncoderPlugin.cxx
index c307b7b4f..4e2d47b63 100644
--- a/src/encoder/TwolameEncoderPlugin.cxx
+++ b/src/encoder/TwolameEncoderPlugin.cxx
@@ -20,7 +20,7 @@
 #include "config.h"
 #include "TwolameEncoderPlugin.hxx"
 #include "EncoderAPI.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 
 #include <twolame.h>
 
@@ -32,7 +32,7 @@
 struct TwolameEncoder final {
 	Encoder encoder;
 
-	struct audio_format audio_format;
+	AudioFormat audio_format;
 	float quality;
 	int bitrate;
 
@@ -187,15 +187,15 @@ twolame_encoder_setup(TwolameEncoder *encoder, GError **error)
 }
 
 static bool
-twolame_encoder_open(Encoder *_encoder, struct audio_format *audio_format,
+twolame_encoder_open(Encoder *_encoder, AudioFormat &audio_format,
 		     GError **error)
 {
 	TwolameEncoder *encoder = (TwolameEncoder *)_encoder;
 
-	audio_format->format = SAMPLE_FORMAT_S16;
-	audio_format->channels = 2;
+	audio_format.format = SampleFormat::S16;
+	audio_format.channels = 2;
 
-	encoder->audio_format = *audio_format;
+	encoder->audio_format = audio_format;
 
 	encoder->options = twolame_init();
 	if (encoder->options == nullptr) {
@@ -243,7 +243,7 @@ twolame_encoder_write(Encoder *_encoder,
 	assert(encoder->buffer_length == 0);
 
 	const unsigned num_frames =
-		length / audio_format_frame_size(&encoder->audio_format);
+		length / encoder->audio_format.GetFrameSize();
 
 	int bytes_out = twolame_encode_buffer_interleaved(encoder->options,
 							  src, num_frames,
diff --git a/src/encoder/VorbisEncoderPlugin.cxx b/src/encoder/VorbisEncoderPlugin.cxx
index 1fc9bde67..bc43ffa43 100644
--- a/src/encoder/VorbisEncoderPlugin.cxx
+++ b/src/encoder/VorbisEncoderPlugin.cxx
@@ -22,7 +22,7 @@
 #include "OggStream.hxx"
 #include "EncoderAPI.hxx"
 #include "Tag.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 #include "mpd_error.h"
 
 #include <vorbis/vorbisenc.h>
@@ -43,7 +43,7 @@ struct vorbis_encoder {
 
 	/* runtime information */
 
-	struct audio_format audio_format;
+	AudioFormat audio_format;
 
 	vorbis_dsp_state vd;
 	vorbis_block vb;
@@ -202,14 +202,14 @@ vorbis_encoder_send_header(struct vorbis_encoder *encoder)
 
 static bool
 vorbis_encoder_open(Encoder *_encoder,
-		    struct audio_format *audio_format,
+		    AudioFormat &audio_format,
 		    GError **error)
 {
 	struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
 
-	audio_format->format = SAMPLE_FORMAT_FLOAT;
+	audio_format.format = SampleFormat::FLOAT;
 
-	encoder->audio_format = *audio_format;
+	encoder->audio_format = audio_format;
 
 	if (!vorbis_encoder_reinit(encoder, error))
 		return false;
@@ -328,8 +328,7 @@ vorbis_encoder_write(Encoder *_encoder,
 {
 	struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
 
-	unsigned num_frames = length
-		/ audio_format_frame_size(&encoder->audio_format);
+	unsigned num_frames = length / encoder->audio_format.GetFrameSize();
 
 	/* this is for only 16-bit audio */
 
diff --git a/src/encoder/WaveEncoderPlugin.cxx b/src/encoder/WaveEncoderPlugin.cxx
index 939012d89..cc94247cb 100644
--- a/src/encoder/WaveEncoderPlugin.cxx
+++ b/src/encoder/WaveEncoderPlugin.cxx
@@ -100,32 +100,32 @@ wave_encoder_finish(Encoder *_encoder)
 
 static bool
 wave_encoder_open(Encoder *_encoder,
-		  gcc_unused struct audio_format *audio_format,
+		  AudioFormat &audio_format,
 		  gcc_unused GError **error)
 {
 	WaveEncoder *encoder = (WaveEncoder *)_encoder;
 
-	assert(audio_format_valid(audio_format));
+	assert(audio_format.IsValid());
 
-	switch (audio_format->format) {
-	case SAMPLE_FORMAT_S8:
+	switch (audio_format.format) {
+	case SampleFormat::S8:
 		encoder->bits = 8;
 		break;
 
-	case SAMPLE_FORMAT_S16:
+	case SampleFormat::S16:
 		encoder->bits = 16;
 		break;
 
-	case SAMPLE_FORMAT_S24_P32:
+	case SampleFormat::S24_P32:
 		encoder->bits = 24;
 		break;
 
-	case SAMPLE_FORMAT_S32:
+	case SampleFormat::S32:
 		encoder->bits = 32;
 		break;
 
 	default:
-		audio_format->format = SAMPLE_FORMAT_S16;
+		audio_format.format = SampleFormat::S16;
 		encoder->bits = 16;
 		break;
 	}
@@ -136,10 +136,10 @@ wave_encoder_open(Encoder *_encoder,
 
 	/* create PCM wave header in initial buffer */
 	fill_wave_header(header,
-			audio_format->channels,
+			 audio_format.channels,
 			 encoder->bits,
-			audio_format->sample_rate,
-			 (encoder->bits / 8) * audio_format->channels );
+			 audio_format.sample_rate,
+			 (encoder->bits / 8) * audio_format.channels);
 	fifo_buffer_append(encoder->buffer, sizeof(*header));
 
 	return true;
diff --git a/src/filter/AutoConvertFilterPlugin.cxx b/src/filter/AutoConvertFilterPlugin.cxx
index 55ee46948..1ad42c2ab 100644
--- a/src/filter/AutoConvertFilterPlugin.cxx
+++ b/src/filter/AutoConvertFilterPlugin.cxx
@@ -23,18 +23,11 @@
 #include "FilterPlugin.hxx"
 #include "FilterInternal.hxx"
 #include "FilterRegistry.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 
 #include <assert.h>
 
 class AutoConvertFilter final : public Filter {
-	/**
-	 * The audio format being fed to the underlying filter.  This
-	 * plugin actually doesn't need this variable, we have it here
-	 * just so our open() method doesn't return a stack pointer.
-	 */
-	audio_format child_audio_format;
-
 	/**
 	 * The underlying filter.
 	 */
@@ -52,46 +45,45 @@ public:
 		delete filter;
 	}
 
-	virtual const audio_format *Open(audio_format &af, GError **error_r);
+	virtual AudioFormat Open(AudioFormat &af, GError **error_r);
 	virtual void Close();
 	virtual const void *FilterPCM(const void *src, size_t src_size,
 				      size_t *dest_size_r, GError **error_r);
 };
 
-const struct audio_format *
-AutoConvertFilter::Open(audio_format &in_audio_format, GError **error_r)
+AudioFormat
+AutoConvertFilter::Open(AudioFormat &in_audio_format, GError **error_r)
 {
-	assert(audio_format_valid(&in_audio_format));
+	assert(in_audio_format.IsValid());
 
 	/* open the "real" filter */
 
-	child_audio_format = in_audio_format;
-	const audio_format *out_audio_format =
-		filter->Open(child_audio_format, error_r);
-	if (out_audio_format == nullptr)
-		return nullptr;
+	const AudioFormat child_audio_format = in_audio_format;
+	AudioFormat out_audio_format = filter->Open(in_audio_format, error_r);
+	if (!out_audio_format.IsDefined())
+		return out_audio_format;
 
 	/* need to convert? */
 
-	if (!audio_format_equals(&child_audio_format, &in_audio_format)) {
+	if (in_audio_format != child_audio_format) {
 		/* yes - create a convert_filter */
 
 		convert = filter_new(&convert_filter_plugin, nullptr, error_r);
 		if (convert == nullptr) {
 			filter->Close();
-			return nullptr;
+			return AudioFormat::Undefined();
 		}
 
-		audio_format audio_format2 = in_audio_format;
-		const audio_format *audio_format3 =
+		AudioFormat audio_format2 = in_audio_format;
+		AudioFormat audio_format3 =
 			convert->Open(audio_format2, error_r);
-		if (audio_format3 == nullptr) {
+		if (!audio_format3.IsDefined()) {
 			delete convert;
 			filter->Close();
-			return nullptr;
+			return AudioFormat::Undefined();
 		}
 
-		assert(audio_format_equals(&audio_format2, &in_audio_format));
+		assert(audio_format2 == in_audio_format);
 
 		convert_filter_set(convert, child_audio_format);
 	} else
diff --git a/src/filter/ChainFilterPlugin.cxx b/src/filter/ChainFilterPlugin.cxx
index ac0f10980..2d7fdf938 100644
--- a/src/filter/ChainFilterPlugin.cxx
+++ b/src/filter/ChainFilterPlugin.cxx
@@ -23,7 +23,7 @@
 #include "FilterPlugin.hxx"
 #include "FilterInternal.hxx"
 #include "FilterRegistry.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 
 #include <glib.h>
 
@@ -53,7 +53,7 @@ public:
 		children.emplace_back(name, filter);
 	}
 
-	virtual const audio_format *Open(audio_format &af, GError **error_r);
+	virtual AudioFormat Open(AudioFormat &af, GError **error_r) override;
 	virtual void Close();
 	virtual const void *FilterPCM(const void *src, size_t src_size,
 				      size_t *dest_size_r, GError **error_r);
@@ -96,43 +96,43 @@ ChainFilter::CloseUntil(const Filter *until)
 	gcc_unreachable();
 }
 
-static const struct audio_format *
+static AudioFormat
 chain_open_child(const char *name, Filter *filter,
-		 const audio_format &prev_audio_format,
+		 const AudioFormat &prev_audio_format,
 		 GError **error_r)
 {
-	audio_format conv_audio_format = prev_audio_format;
-	const audio_format *next_audio_format =
+	AudioFormat conv_audio_format = prev_audio_format;
+	const AudioFormat next_audio_format =
 		filter->Open(conv_audio_format, error_r);
-	if (next_audio_format == NULL)
-		return NULL;
+	if (!next_audio_format.IsDefined())
+		return next_audio_format;
 
-	if (!audio_format_equals(&conv_audio_format, &prev_audio_format)) {
+	if (conv_audio_format != prev_audio_format) {
 		struct audio_format_string s;
 
 		filter->Close();
 		g_set_error(error_r, filter_quark(), 0,
 			    "Audio format not supported by filter '%s': %s",
 			    name,
-			    audio_format_to_string(&prev_audio_format, &s));
-		return NULL;
+			    audio_format_to_string(prev_audio_format, &s));
+		return AudioFormat::Undefined();
 	}
 
 	return next_audio_format;
 }
 
-const audio_format *
-ChainFilter::Open(audio_format &in_audio_format, GError **error_r)
+AudioFormat
+ChainFilter::Open(AudioFormat &in_audio_format, GError **error_r)
 {
-	const audio_format *audio_format = &in_audio_format;
+	AudioFormat audio_format = in_audio_format;
 
 	for (auto &child : children) {
 		audio_format = chain_open_child(child.name, child.filter,
-						*audio_format, error_r);
-		if (audio_format == NULL) {
+						audio_format, error_r);
+		if (!audio_format.IsDefined()) {
 			/* rollback, close all children */
 			CloseUntil(child.filter);
-			return NULL;
+			break;
 		}
 	}
 
diff --git a/src/filter/ConvertFilterPlugin.cxx b/src/filter/ConvertFilterPlugin.cxx
index 09a2c9848..f98184489 100644
--- a/src/filter/ConvertFilterPlugin.cxx
+++ b/src/filter/ConvertFilterPlugin.cxx
@@ -25,7 +25,7 @@
 #include "conf.h"
 #include "pcm/PcmConvert.hxx"
 #include "util/Manual.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 #include "poison.h"
 
 #include <assert.h>
@@ -36,27 +36,27 @@ class ConvertFilter final : public Filter {
 	 * The input audio format; PCM data is passed to the filter()
 	 * method in this format.
 	 */
-	audio_format in_audio_format;
+	AudioFormat in_audio_format;
 
 	/**
 	 * The output audio format; the consumer of this plugin
 	 * expects PCM data in this format.  This defaults to
 	 * #in_audio_format, and can be set with convert_filter_set().
 	 */
-	audio_format out_audio_format;
+	AudioFormat out_audio_format;
 
 	Manual<PcmConvert> state;
 
 public:
-	void Set(const audio_format &_out_audio_format) {
-		assert(audio_format_valid(&in_audio_format));
-		assert(audio_format_valid(&out_audio_format));
-		assert(audio_format_valid(&_out_audio_format));
+	void Set(const AudioFormat &_out_audio_format) {
+		assert(in_audio_format.IsValid());
+		assert(out_audio_format.IsValid());
+		assert(_out_audio_format.IsValid());
 
 		out_audio_format = _out_audio_format;
 	}
 
-	virtual const audio_format *Open(audio_format &af, GError **error_r);
+	virtual AudioFormat Open(AudioFormat &af, GError **error_r) override;
 	virtual void Close();
 	virtual const void *FilterPCM(const void *src, size_t src_size,
 				      size_t *dest_size_r, GError **error_r);
@@ -69,15 +69,15 @@ convert_filter_init(gcc_unused const struct config_param *param,
 	return new ConvertFilter();
 }
 
-const struct audio_format *
-ConvertFilter::Open(audio_format &audio_format, gcc_unused GError **error_r)
+AudioFormat
+ConvertFilter::Open(AudioFormat &audio_format, gcc_unused GError **error_r)
 {
-	assert(audio_format_valid(&audio_format));
+	assert(audio_format.IsValid());
 
 	in_audio_format = out_audio_format = audio_format;
 	state.Construct();
 
-	return &in_audio_format;
+	return in_audio_format;
 }
 
 void
@@ -93,15 +93,15 @@ const void *
 ConvertFilter::FilterPCM(const void *src, size_t src_size,
 			 size_t *dest_size_r, GError **error_r)
 {
-	if (audio_format_equals(&in_audio_format, &out_audio_format)) {
+	if (in_audio_format == out_audio_format) {
 		/* optimized special case: no-op */
 		*dest_size_r = src_size;
 		return src;
 	}
 
-	return state->Convert(&in_audio_format,
+	return state->Convert(in_audio_format,
 			      src, src_size,
-			      &out_audio_format, dest_size_r,
+			      out_audio_format, dest_size_r,
 			      error_r);
 }
 
@@ -111,7 +111,7 @@ const struct filter_plugin convert_filter_plugin = {
 };
 
 void
-convert_filter_set(Filter *_filter, const audio_format &out_audio_format)
+convert_filter_set(Filter *_filter, const AudioFormat out_audio_format)
 {
 	ConvertFilter *filter = (ConvertFilter *)_filter;
 
diff --git a/src/filter/ConvertFilterPlugin.hxx b/src/filter/ConvertFilterPlugin.hxx
index 840bf496f..c814aaf49 100644
--- a/src/filter/ConvertFilterPlugin.hxx
+++ b/src/filter/ConvertFilterPlugin.hxx
@@ -21,7 +21,7 @@
 #define MPD_CONVERT_FILTER_PLUGIN_HXX
 
 class Filter;
-struct audio_format;
+struct AudioFormat;
 
 /**
  * Sets the output audio format for the specified filter.  You must
@@ -30,6 +30,6 @@ struct audio_format;
  * the last in a chain.
  */
 void
-convert_filter_set(Filter *filter, const audio_format &out_audio_format);
+convert_filter_set(Filter *filter, AudioFormat out_audio_format);
 
 #endif
diff --git a/src/filter/NormalizeFilterPlugin.cxx b/src/filter/NormalizeFilterPlugin.cxx
index f4e2963cc..31bcabd36 100644
--- a/src/filter/NormalizeFilterPlugin.cxx
+++ b/src/filter/NormalizeFilterPlugin.cxx
@@ -22,7 +22,7 @@
 #include "FilterInternal.hxx"
 #include "FilterRegistry.hxx"
 #include "pcm/PcmBuffer.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 #include "AudioCompress/compress.h"
 
 #include <assert.h>
@@ -34,7 +34,7 @@ class NormalizeFilter final : public Filter {
 	PcmBuffer buffer;
 
 public:
-	virtual const audio_format *Open(audio_format &af, GError **error_r);
+	virtual AudioFormat Open(AudioFormat &af, GError **error_r) override;
 	virtual void Close();
 	virtual const void *FilterPCM(const void *src, size_t src_size,
 				      size_t *dest_size_r, GError **error_r);
@@ -47,14 +47,14 @@ normalize_filter_init(gcc_unused const struct config_param *param,
 	return new NormalizeFilter();
 }
 
-const struct audio_format *
-NormalizeFilter::Open(audio_format &audio_format, gcc_unused GError **error_r)
+AudioFormat
+NormalizeFilter::Open(AudioFormat &audio_format, gcc_unused GError **error_r)
 {
-	audio_format.format = SAMPLE_FORMAT_S16;
+	audio_format.format = SampleFormat::S16;
 
 	compressor = Compressor_new(0);
 
-	return &audio_format;
+	return audio_format;
 }
 
 void
diff --git a/src/filter/NullFilterPlugin.cxx b/src/filter/NullFilterPlugin.cxx
index d68065a39..3ff78aa6f 100644
--- a/src/filter/NullFilterPlugin.cxx
+++ b/src/filter/NullFilterPlugin.cxx
@@ -28,13 +28,14 @@
 #include "FilterPlugin.hxx"
 #include "FilterInternal.hxx"
 #include "FilterRegistry.hxx"
+#include "AudioFormat.hxx"
 #include "gcc.h"
 
 class NullFilter final : public Filter {
 public:
-	virtual const audio_format *Open(audio_format &af,
-					 gcc_unused GError **error_r) {
-		return &af;
+	virtual AudioFormat Open(AudioFormat &af,
+				 gcc_unused GError **error_r) {
+		return af;
 	}
 
 	virtual void Close() {}
diff --git a/src/filter/ReplayGainFilterPlugin.cxx b/src/filter/ReplayGainFilterPlugin.cxx
index d736c910f..be6c7ad2b 100644
--- a/src/filter/ReplayGainFilterPlugin.cxx
+++ b/src/filter/ReplayGainFilterPlugin.cxx
@@ -22,7 +22,7 @@
 #include "FilterPlugin.hxx"
 #include "FilterInternal.hxx"
 #include "FilterRegistry.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 #include "replay_gain_info.h"
 #include "replay_gain_config.h"
 #include "MixerControl.hxx"
@@ -66,7 +66,7 @@ class ReplayGainFilter final : public Filter {
 	 */
 	unsigned volume;
 
-	struct audio_format format;
+	AudioFormat format;
 
 	PcmBuffer buffer;
 
@@ -112,7 +112,7 @@ public:
 	 */
 	void Update();
 
-	virtual const audio_format *Open(audio_format &af, GError **error_r);
+	virtual AudioFormat Open(AudioFormat &af, GError **error_r) override;
 	virtual void Close();
 	virtual const void *FilterPCM(const void *src, size_t src_size,
 				      size_t *dest_size_r, GError **error_r);
@@ -159,12 +159,12 @@ replay_gain_filter_init(gcc_unused const struct config_param *param,
 	return new ReplayGainFilter();
 }
 
-const audio_format *
-ReplayGainFilter::Open(audio_format &af, gcc_unused GError **error_r)
+AudioFormat
+ReplayGainFilter::Open(AudioFormat &af, gcc_unused GError **error_r)
 {
 	format = af;
 
-	return &format;
+	return format;
 }
 
 void
@@ -196,7 +196,7 @@ ReplayGainFilter::FilterPCM(const void *src, size_t src_size,
 	memcpy(dest, src, src_size);
 
 	bool success = pcm_volume(dest, src_size,
-				  sample_format(format.format),
+				  format.format,
 				  volume);
 	if (!success) {
 		g_set_error(error_r, replay_gain_quark(), 0,
diff --git a/src/filter/RouteFilterPlugin.cxx b/src/filter/RouteFilterPlugin.cxx
index 3dc0991f9..4f4ceacde 100644
--- a/src/filter/RouteFilterPlugin.cxx
+++ b/src/filter/RouteFilterPlugin.cxx
@@ -42,7 +42,7 @@
 #include "config.h"
 #include "conf.h"
 #include "ConfigQuark.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 #include "CheckAudioFormat.hxx"
 #include "FilterPlugin.hxx"
 #include "FilterInternal.hxx"
@@ -79,12 +79,12 @@ class RouteFilter final : public Filter {
 	/**
 	 * The actual input format of our signal, once opened
 	 */
-	struct audio_format input_format;
+	AudioFormat input_format;
 
 	/**
 	 * The decided upon output format, once opened
 	 */
-	struct audio_format output_format;
+	AudioFormat output_format;
 
 	/**
 	 * The size, in bytes, of each multichannel frame in the
@@ -120,7 +120,7 @@ public:
 	 */
 	bool Configure(const config_param *param, GError **error_r);
 
-	virtual const audio_format *Open(audio_format &af, GError **error_r);
+	virtual AudioFormat Open(AudioFormat &af, GError **error_r) override;
 	virtual void Close();
 	virtual const void *FilterPCM(const void *src, size_t src_size,
 				      size_t *dest_size_r, GError **error_r);
@@ -241,12 +241,12 @@ route_filter_init(const config_param *param, GError **error_r)
 	return filter;
 }
 
-const struct audio_format *
-RouteFilter::Open(audio_format &audio_format, gcc_unused GError **error_r)
+AudioFormat
+RouteFilter::Open(AudioFormat &audio_format, gcc_unused GError **error_r)
 {
 	// Copy the input format for later reference
 	input_format = audio_format;
-	input_frame_size = audio_format_frame_size(&input_format);
+	input_frame_size = input_format.GetFrameSize();
 
 	// Decide on an output format which has enough channels,
 	// and is otherwise identical
@@ -254,9 +254,9 @@ RouteFilter::Open(audio_format &audio_format, gcc_unused GError **error_r)
 	output_format.channels = min_output_channels;
 
 	// Precalculate this simple value, to speed up allocation later
-	output_frame_size = audio_format_frame_size(&output_format);
+	output_frame_size = output_format.GetFrameSize();
 
-	return &output_format;
+	return output_format;
 }
 
 void
@@ -271,8 +271,7 @@ RouteFilter::FilterPCM(const void *src, size_t src_size,
 {
 	size_t number_of_frames = src_size / input_frame_size;
 
-	size_t bytes_per_frame_per_channel =
-		audio_format_sample_size(&input_format);
+	const size_t bytes_per_frame_per_channel = input_format.GetSampleSize();
 
 	// A moving pointer that always refers to channel 0 in the input, at the currently handled frame
 	const uint8_t *base_source = (const uint8_t *)src;
diff --git a/src/filter/VolumeFilterPlugin.cxx b/src/filter/VolumeFilterPlugin.cxx
index 824ad0ab4..3888cefe4 100644
--- a/src/filter/VolumeFilterPlugin.cxx
+++ b/src/filter/VolumeFilterPlugin.cxx
@@ -25,7 +25,7 @@
 #include "conf.h"
 #include "pcm/PcmVolume.hxx"
 #include "pcm/PcmBuffer.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 
 #include <assert.h>
 #include <string.h>
@@ -36,7 +36,7 @@ class VolumeFilter final : public Filter {
 	 */
 	unsigned volume;
 
-	struct audio_format format;
+	AudioFormat format;
 
 	PcmBuffer buffer;
 
@@ -56,7 +56,7 @@ public:
 		volume = _volume;
 	}
 
-	virtual const audio_format *Open(audio_format &af, GError **error_r);
+	virtual AudioFormat Open(AudioFormat &af, GError **error_r) override;
 	virtual void Close();
 	virtual const void *FilterPCM(const void *src, size_t src_size,
 				      size_t *dest_size_r, GError **error_r);
@@ -75,12 +75,12 @@ volume_filter_init(gcc_unused const struct config_param *param,
 	return new VolumeFilter();
 }
 
-const struct audio_format *
-VolumeFilter::Open(audio_format &audio_format, gcc_unused GError **error_r)
+AudioFormat
+VolumeFilter::Open(AudioFormat &audio_format, gcc_unused GError **error_r)
 {
 	format = audio_format;
 
-	return &format;
+	return format;
 }
 
 void
@@ -112,7 +112,7 @@ VolumeFilter::FilterPCM(const void *src, size_t src_size,
 	memcpy(dest, src, src_size);
 
 	bool success = pcm_volume(dest, src_size,
-				  sample_format(format.format),
+				  format.format,
 				  volume);
 	if (!success) {
 		g_set_error(error_r, volume_quark(), 0,
diff --git a/src/output/AlsaOutputPlugin.cxx b/src/output/AlsaOutputPlugin.cxx
index 7d0e9e09c..0e70babb6 100644
--- a/src/output/AlsaOutputPlugin.cxx
+++ b/src/output/AlsaOutputPlugin.cxx
@@ -233,26 +233,26 @@ alsa_test_default_device(void)
 }
 
 static snd_pcm_format_t
-get_bitformat(enum sample_format sample_format)
+get_bitformat(SampleFormat sample_format)
 {
 	switch (sample_format) {
-	case SAMPLE_FORMAT_UNDEFINED:
-	case SAMPLE_FORMAT_DSD:
+	case SampleFormat::UNDEFINED:
+	case SampleFormat::DSD:
 		return SND_PCM_FORMAT_UNKNOWN;
 
-	case SAMPLE_FORMAT_S8:
+	case SampleFormat::S8:
 		return SND_PCM_FORMAT_S8;
 
-	case SAMPLE_FORMAT_S16:
+	case SampleFormat::S16:
 		return SND_PCM_FORMAT_S16;
 
-	case SAMPLE_FORMAT_S24_P32:
+	case SampleFormat::S24_P32:
 		return SND_PCM_FORMAT_S24;
 
-	case SAMPLE_FORMAT_S32:
+	case SampleFormat::S32:
 		return SND_PCM_FORMAT_S32;
 
-	case SAMPLE_FORMAT_FLOAT:
+	case SampleFormat::FLOAT:
 		return SND_PCM_FORMAT_FLOAT;
 	}
 
@@ -324,7 +324,7 @@ alsa_try_format_or_packed(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
  */
 static int
 alsa_output_try_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
-		       enum sample_format sample_format,
+		       SampleFormat sample_format,
 		       bool *packed_r, bool *reverse_endian_r)
 {
 	snd_pcm_format_t alsa_format = get_bitformat(sample_format);
@@ -355,36 +355,36 @@ alsa_output_try_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
  */
 static int
 alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
-			 struct audio_format *audio_format,
+			 AudioFormat &audio_format,
 			 bool *packed_r, bool *reverse_endian_r)
 {
 	/* try the input format first */
 
 	int err = alsa_output_try_format(pcm, hwparams,
-					 sample_format(audio_format->format),
+					 audio_format.format,
 					 packed_r, reverse_endian_r);
 
 	/* if unsupported by the hardware, try other formats */
 
-	static const enum sample_format probe_formats[] = {
-		SAMPLE_FORMAT_S24_P32,
-		SAMPLE_FORMAT_S32,
-		SAMPLE_FORMAT_S16,
-		SAMPLE_FORMAT_S8,
-		SAMPLE_FORMAT_UNDEFINED,
+	static const SampleFormat probe_formats[] = {
+		SampleFormat::S24_P32,
+		SampleFormat::S32,
+		SampleFormat::S16,
+		SampleFormat::S8,
+		SampleFormat::UNDEFINED,
 	};
 
 	for (unsigned i = 0;
-	     err == -EINVAL && probe_formats[i] != SAMPLE_FORMAT_UNDEFINED;
+	     err == -EINVAL && probe_formats[i] != SampleFormat::UNDEFINED;
 	     ++i) {
-		const enum sample_format mpd_format = probe_formats[i];
-		if (mpd_format == audio_format->format)
+		const SampleFormat mpd_format = probe_formats[i];
+		if (mpd_format == audio_format.format)
 			continue;
 
 		err = alsa_output_try_format(pcm, hwparams, mpd_format,
 					     packed_r, reverse_endian_r);
 		if (err == 0)
-			audio_format->format = mpd_format;
+			audio_format.format = mpd_format;
 	}
 
 	return err;
@@ -395,11 +395,11 @@ alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
  * the configured settings and the audio format.
  */
 static bool
-alsa_setup(AlsaOutput *ad, struct audio_format *audio_format,
+alsa_setup(AlsaOutput *ad, AudioFormat &audio_format,
 	   bool *packed_r, bool *reverse_endian_r, GError **error)
 {
-	unsigned int sample_rate = audio_format->sample_rate;
-	unsigned int channels = audio_format->channels;
+	unsigned int sample_rate = audio_format.sample_rate;
+	unsigned int channels = audio_format.channels;
 	int err;
 	const char *cmd = NULL;
 	int retry = MPD_ALSA_RETRY_NR;
@@ -443,7 +443,7 @@ configure_hw:
 		g_set_error(error, alsa_output_quark(), err,
 			    "ALSA device \"%s\" does not support format %s: %s",
 			    alsa_device(ad),
-			    sample_format_to_string(sample_format(audio_format->format)),
+			    sample_format_to_string(audio_format.format),
 			    snd_strerror(-err));
 		return false;
 	}
@@ -458,21 +458,21 @@ configure_hw:
 	if (err < 0) {
 		g_set_error(error, alsa_output_quark(), err,
 			    "ALSA device \"%s\" does not support %i channels: %s",
-			    alsa_device(ad), (int)audio_format->channels,
+			    alsa_device(ad), (int)audio_format.channels,
 			    snd_strerror(-err));
 		return false;
 	}
-	audio_format->channels = (int8_t)channels;
+	audio_format.channels = (int8_t)channels;
 
 	err = snd_pcm_hw_params_set_rate_near(ad->pcm, hwparams,
 					      &sample_rate, NULL);
 	if (err < 0 || sample_rate == 0) {
 		g_set_error(error, alsa_output_quark(), err,
 			    "ALSA device \"%s\" does not support %u Hz audio",
-			    alsa_device(ad), audio_format->sample_rate);
+			    alsa_device(ad), audio_format.sample_rate);
 		return false;
 	}
-	audio_format->sample_rate = sample_rate;
+	audio_format.sample_rate = sample_rate;
 
 	snd_pcm_uframes_t buffer_size_min, buffer_size_max;
 	snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min);
@@ -603,22 +603,22 @@ error:
 }
 
 static bool
-alsa_setup_dsd(AlsaOutput *ad, struct audio_format *audio_format,
+alsa_setup_dsd(AlsaOutput *ad, const AudioFormat audio_format,
 	       bool *shift8_r, bool *packed_r, bool *reverse_endian_r,
 	       GError **error_r)
 {
 	assert(ad->dsd_usb);
-	assert(audio_format->format == SAMPLE_FORMAT_DSD);
+	assert(audio_format.format == SampleFormat::DSD);
 
 	/* pass 24 bit to alsa_setup() */
 
-	struct audio_format usb_format = *audio_format;
-	usb_format.format = SAMPLE_FORMAT_S24_P32;
+	AudioFormat usb_format = audio_format;
+	usb_format.format = SampleFormat::S24_P32;
 	usb_format.sample_rate /= 2;
 
-	const struct audio_format check = usb_format;
+	const AudioFormat check = usb_format;
 
-	if (!alsa_setup(ad, &usb_format, packed_r, reverse_endian_r, error_r))
+	if (!alsa_setup(ad, usb_format, packed_r, reverse_endian_r, error_r))
 		return false;
 
 	/* if the device allows only 32 bit, shift all DSD-over-USB
@@ -626,11 +626,11 @@ alsa_setup_dsd(AlsaOutput *ad, struct audio_format *audio_format,
 	   the DSD-over-USB documentation does not specify whether
 	   this is legal, but there is anecdotical evidence that this
 	   is possible (and the only option for some devices) */
-	*shift8_r = usb_format.format == SAMPLE_FORMAT_S32;
-	if (usb_format.format == SAMPLE_FORMAT_S32)
-		usb_format.format = SAMPLE_FORMAT_S24_P32;
+	*shift8_r = usb_format.format == SampleFormat::S32;
+	if (usb_format.format == SampleFormat::S32)
+		usb_format.format = SampleFormat::S24_P32;
 
-	if (!audio_format_equals(&usb_format, &check)) {
+	if (usb_format != check) {
 		/* no bit-perfect playback, which is required
 		   for DSD over USB */
 		g_set_error(error_r, alsa_output_quark(), 0,
@@ -644,13 +644,13 @@ alsa_setup_dsd(AlsaOutput *ad, struct audio_format *audio_format,
 }
 
 static bool
-alsa_setup_or_dsd(AlsaOutput *ad, struct audio_format *audio_format,
+alsa_setup_or_dsd(AlsaOutput *ad, AudioFormat &audio_format,
 		  GError **error_r)
 {
 	bool shift8 = false, packed, reverse_endian;
 
 	const bool dsd_usb = ad->dsd_usb &&
-		audio_format->format == SAMPLE_FORMAT_DSD;
+		audio_format.format == SampleFormat::DSD;
 	const bool success = dsd_usb
 		? alsa_setup_dsd(ad, audio_format,
 				 &shift8, &packed, &reverse_endian,
@@ -660,14 +660,14 @@ alsa_setup_or_dsd(AlsaOutput *ad, struct audio_format *audio_format,
 	if (!success)
 		return false;
 
-	ad->pcm_export->Open(sample_format(audio_format->format),
-			     audio_format->channels,
+	ad->pcm_export->Open(audio_format.format,
+			     audio_format.channels,
 			     dsd_usb, shift8, packed, reverse_endian);
 	return true;
 }
 
 static bool
-alsa_open(struct audio_output *ao, struct audio_format *audio_format, GError **error)
+alsa_open(struct audio_output *ao, AudioFormat &audio_format, GError **error)
 {
 	AlsaOutput *ad = (AlsaOutput *)ao;
 
@@ -688,8 +688,8 @@ alsa_open(struct audio_output *ao, struct audio_format *audio_format, GError **e
 		return false;
 	}
 
-	ad->in_frame_size = audio_format_frame_size(audio_format);
-	ad->out_frame_size = ad->pcm_export->GetFrameSize(*audio_format);
+	ad->in_frame_size = audio_format.GetFrameSize();
+	ad->out_frame_size = ad->pcm_export->GetFrameSize(audio_format);
 
 	return true;
 }
diff --git a/src/output/AoOutputPlugin.cxx b/src/output/AoOutputPlugin.cxx
index b534ddf08..d25a30973 100644
--- a/src/output/AoOutputPlugin.cxx
+++ b/src/output/AoOutputPlugin.cxx
@@ -200,18 +200,18 @@ ao_output_close(struct audio_output *ao)
 }
 
 static bool
-ao_output_open(struct audio_output *ao, struct audio_format *audio_format,
+ao_output_open(struct audio_output *ao, AudioFormat &audio_format,
 	       GError **error)
 {
 	ao_sample_format format = OUR_AO_FORMAT_INITIALIZER;
 	AoOutput *ad = (AoOutput *)ao;
 
-	switch (audio_format->format) {
-	case SAMPLE_FORMAT_S8:
+	switch (audio_format.format) {
+	case SampleFormat::S8:
 		format.bits = 8;
 		break;
 
-	case SAMPLE_FORMAT_S16:
+	case SampleFormat::S16:
 		format.bits = 16;
 		break;
 
@@ -219,14 +219,14 @@ ao_output_open(struct audio_output *ao, struct audio_format *audio_format,
 		/* support for 24 bit samples in libao is currently
 		   dubious, and until we have sorted that out,
 		   convert everything to 16 bit */
-		audio_format->format = SAMPLE_FORMAT_S16;
+		audio_format.format = SampleFormat::S16;
 		format.bits = 16;
 		break;
 	}
 
-	format.rate = audio_format->sample_rate;
+	format.rate = audio_format.sample_rate;
 	format.byte_format = AO_FMT_NATIVE;
-	format.channels = audio_format->channels;
+	format.channels = audio_format.channels;
 
 	ad->device = ao_open_live(ad->driver, &format, ad->options);
 
diff --git a/src/output/FifoOutputPlugin.cxx b/src/output/FifoOutputPlugin.cxx
index 1151d9f3a..a0d07c702 100644
--- a/src/output/FifoOutputPlugin.cxx
+++ b/src/output/FifoOutputPlugin.cxx
@@ -227,12 +227,12 @@ fifo_output_finish(struct audio_output *ao)
 }
 
 static bool
-fifo_output_open(struct audio_output *ao, struct audio_format *audio_format,
+fifo_output_open(struct audio_output *ao, AudioFormat &audio_format,
 		 G_GNUC_UNUSED GError **error)
 {
 	FifoOutput *fd = (FifoOutput *)ao;
 
-	fd->timer = new Timer(*audio_format);
+	fd->timer = new Timer(audio_format);
 
 	return true;
 }
diff --git a/src/output/HttpdInternal.hxx b/src/output/HttpdInternal.hxx
index c2ac96be5..8bc0c130e 100644
--- a/src/output/HttpdInternal.hxx
+++ b/src/output/HttpdInternal.hxx
@@ -131,13 +131,13 @@ struct HttpdOutput final : private ServerSocket {
 	/**
 	 * Caller must lock the mutex.
 	 */
-	bool OpenEncoder(struct audio_format *audio_format,
+	bool OpenEncoder(AudioFormat &audio_format,
 			 GError **error_r);
 
 	/**
 	 * Caller must lock the mutex.
 	 */
-	bool Open(struct audio_format *audio_format, GError **error_r);
+	bool Open(AudioFormat &audio_format, GError **error_r);
 
 	/**
 	 * Caller must lock the mutex.
diff --git a/src/output/HttpdOutputPlugin.cxx b/src/output/HttpdOutputPlugin.cxx
index 36bd1aee1..462dd3429 100644
--- a/src/output/HttpdOutputPlugin.cxx
+++ b/src/output/HttpdOutputPlugin.cxx
@@ -291,7 +291,7 @@ httpd_output_disable(struct audio_output *ao)
 }
 
 inline bool
-HttpdOutput::OpenEncoder(struct audio_format *audio_format, GError **error)
+HttpdOutput::OpenEncoder(AudioFormat &audio_format, GError **error)
 {
 	if (!encoder_open(encoder, audio_format, error))
 		return false;
@@ -307,7 +307,7 @@ HttpdOutput::OpenEncoder(struct audio_format *audio_format, GError **error)
 }
 
 inline bool
-HttpdOutput::Open(struct audio_format *audio_format, GError **error_r)
+HttpdOutput::Open(AudioFormat &audio_format, GError **error_r)
 {
 	assert(!open);
 	assert(clients.empty());
@@ -320,7 +320,7 @@ HttpdOutput::Open(struct audio_format *audio_format, GError **error_r)
 	/* initialize other attributes */
 
 	clients_cnt = 0;
-	timer = new Timer(*audio_format);
+	timer = new Timer(audio_format);
 
 	open = true;
 
@@ -328,7 +328,7 @@ HttpdOutput::Open(struct audio_format *audio_format, GError **error_r)
 }
 
 static bool
-httpd_output_open(struct audio_output *ao, struct audio_format *audio_format,
+httpd_output_open(struct audio_output *ao, AudioFormat &audio_format,
 		  GError **error)
 {
 	HttpdOutput *httpd = Cast(ao);
diff --git a/src/output/JackOutputPlugin.cxx b/src/output/JackOutputPlugin.cxx
index 4f21b36f8..6c9ee9335 100644
--- a/src/output/JackOutputPlugin.cxx
+++ b/src/output/JackOutputPlugin.cxx
@@ -67,7 +67,7 @@ struct JackOutput {
 	size_t ringbuffer_size;
 
 	/* the current audio format */
-	struct audio_format audio_format;
+	AudioFormat audio_format;
 
 	/* jack library stuff */
 	jack_port_t *ports[MAX_PORTS];
@@ -203,18 +203,18 @@ mpd_jack_shutdown(void *arg)
 }
 
 static void
-set_audioformat(JackOutput *jd, struct audio_format *audio_format)
+set_audioformat(JackOutput *jd, AudioFormat &audio_format)
 {
-	audio_format->sample_rate = jack_get_sample_rate(jd->client);
+	audio_format.sample_rate = jack_get_sample_rate(jd->client);
 
 	if (jd->num_source_ports == 1)
-		audio_format->channels = 1;
-	else if (audio_format->channels > jd->num_source_ports)
-		audio_format->channels = 2;
+		audio_format.channels = 1;
+	else if (audio_format.channels > jd->num_source_ports)
+		audio_format.channels = 2;
 
-	if (audio_format->format != SAMPLE_FORMAT_S16 &&
-	    audio_format->format != SAMPLE_FORMAT_S24_P32)
-		audio_format->format = SAMPLE_FORMAT_S24_P32;
+	if (audio_format.format != SampleFormat::S16 &&
+	    audio_format.format != SampleFormat::S24_P32)
+		audio_format.format = SampleFormat::S24_P32;
 }
 
 static void
@@ -591,7 +591,7 @@ mpd_jack_start(JackOutput *jd, GError **error_r)
 }
 
 static bool
-mpd_jack_open(struct audio_output *ao, struct audio_format *audio_format,
+mpd_jack_open(struct audio_output *ao, AudioFormat &audio_format,
 	      GError **error_r)
 {
 	JackOutput *jd = (JackOutput *)ao;
@@ -607,7 +607,7 @@ mpd_jack_open(struct audio_output *ao, struct audio_format *audio_format,
 		return false;
 
 	set_audioformat(jd, audio_format);
-	jd->audio_format = *audio_format;
+	jd->audio_format = audio_format;
 
 	if (!mpd_jack_start(jd, error_r))
 		return false;
@@ -684,12 +684,12 @@ mpd_jack_write_samples(JackOutput *jd, const void *src,
 		       unsigned num_samples)
 {
 	switch (jd->audio_format.format) {
-	case SAMPLE_FORMAT_S16:
+	case SampleFormat::S16:
 		mpd_jack_write_samples_16(jd, (const int16_t*)src,
 					  num_samples);
 		break;
 
-	case SAMPLE_FORMAT_S24_P32:
+	case SampleFormat::S24_P32:
 		mpd_jack_write_samples_24(jd, (const int32_t*)src,
 					  num_samples);
 		break;
@@ -705,7 +705,7 @@ mpd_jack_play(struct audio_output *ao, const void *chunk, size_t size,
 	      GError **error_r)
 {
 	JackOutput *jd = (JackOutput *)ao;
-	const size_t frame_size = audio_format_frame_size(&jd->audio_format);
+	const size_t frame_size = jd->audio_format.GetFrameSize();
 	size_t space = 0, space1;
 
 	jd->pause = false;
diff --git a/src/output/NullOutputPlugin.cxx b/src/output/NullOutputPlugin.cxx
index 07452bbdb..814a9d2e2 100644
--- a/src/output/NullOutputPlugin.cxx
+++ b/src/output/NullOutputPlugin.cxx
@@ -66,13 +66,13 @@ null_finish(struct audio_output *ao)
 }
 
 static bool
-null_open(struct audio_output *ao, struct audio_format *audio_format,
+null_open(struct audio_output *ao, AudioFormat &audio_format,
 	  gcc_unused GError **error)
 {
 	NullOutput *nd = (NullOutput *)ao;
 
 	if (nd->sync)
-		nd->timer = new Timer(*audio_format);
+		nd->timer = new Timer(audio_format);
 
 	return true;
 }
diff --git a/src/output/OSXOutputPlugin.cxx b/src/output/OSXOutputPlugin.cxx
index cbd27413f..afe8e064d 100644
--- a/src/output/OSXOutputPlugin.cxx
+++ b/src/output/OSXOutputPlugin.cxx
@@ -316,30 +316,30 @@ osx_output_close(struct audio_output *ao)
 }
 
 static bool
-osx_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error)
+osx_output_open(struct audio_output *ao, AudioFormat &audio_format, GError **error)
 {
 	OSXOutput *od = (OSXOutput *)ao;
 
 	AudioStreamBasicDescription stream_description;
-	stream_description.mSampleRate = audio_format->sample_rate;
+	stream_description.mSampleRate = audio_format.sample_rate;
 	stream_description.mFormatID = kAudioFormatLinearPCM;
 	stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
 
-	switch (audio_format->format) {
-	case SAMPLE_FORMAT_S8:
+	switch (audio_format.format) {
+	case SampleFormat::S8:
 		stream_description.mBitsPerChannel = 8;
 		break;
 
-	case SAMPLE_FORMAT_S16:
+	case SampleFormat::S16:
 		stream_description.mBitsPerChannel = 16;
 		break;
 
-	case SAMPLE_FORMAT_S32:
+	case SampleFormat::S32:
 		stream_description.mBitsPerChannel = 32;
 		break;
 
 	default:
-		audio_format->format = SAMPLE_FORMAT_S32;
+		audio_format.format = SampleFormat::S32;
 		stream_description.mBitsPerChannel = 32;
 		break;
 	}
@@ -348,11 +348,10 @@ osx_output_open(struct audio_output *ao, struct audio_format *audio_format, GErr
 	stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
 #endif
 
-	stream_description.mBytesPerPacket =
-		audio_format_frame_size(audio_format);
+	stream_description.mBytesPerPacket = audio_format.GetFrameSize();
 	stream_description.mFramesPerPacket = 1;
 	stream_description.mBytesPerFrame = stream_description.mBytesPerPacket;
-	stream_description.mChannelsPerFrame = audio_format->channels;
+	stream_description.mChannelsPerFrame = audio_format.channels;
 
 	ComponentResult result =
 		AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat,
@@ -374,8 +373,8 @@ osx_output_open(struct audio_output *ao, struct audio_format *audio_format, GErr
 	}
 
 	/* create a buffer of 1s */
-	od->buffer = fifo_buffer_new(audio_format->sample_rate *
-				     audio_format_frame_size(audio_format));
+	od->buffer = fifo_buffer_new(audio_format.sample_rate *
+				     audio_format.GetFrameSize());
 
 	status = AudioOutputUnitStart(od->au);
 	if (status != 0) {
diff --git a/src/output/OpenALOutputPlugin.cxx b/src/output/OpenALOutputPlugin.cxx
index e68032d75..81c75f6c4 100644
--- a/src/output/OpenALOutputPlugin.cxx
+++ b/src/output/OpenALOutputPlugin.cxx
@@ -66,26 +66,26 @@ openal_output_quark(void)
 }
 
 static ALenum
-openal_audio_format(struct audio_format *audio_format)
+openal_audio_format(AudioFormat &audio_format)
 {
-	/* note: cannot map SAMPLE_FORMAT_S8 to AL_FORMAT_STEREO8 or
+	/* note: cannot map SampleFormat::S8 to AL_FORMAT_STEREO8 or
 	   AL_FORMAT_MONO8 since OpenAL expects unsigned 8 bit
 	   samples, while MPD uses signed samples */
 
-	switch (audio_format->format) {
-	case SAMPLE_FORMAT_S16:
-		if (audio_format->channels == 2)
+	switch (audio_format.format) {
+	case SampleFormat::S16:
+		if (audio_format.channels == 2)
 			return AL_FORMAT_STEREO16;
-		if (audio_format->channels == 1)
+		if (audio_format.channels == 1)
 			return AL_FORMAT_MONO16;
 
 		/* fall back to mono */
-		audio_format->channels = 1;
+		audio_format.channels = 1;
 		return openal_audio_format(audio_format);
 
 	default:
 		/* fall back to 16 bit */
-		audio_format->format = SAMPLE_FORMAT_S16;
+		audio_format.format = SampleFormat::S16;
 		return openal_audio_format(audio_format);
 	}
 }
@@ -169,7 +169,7 @@ openal_finish(struct audio_output *ao)
 }
 
 static bool
-openal_open(struct audio_output *ao, struct audio_format *audio_format,
+openal_open(struct audio_output *ao, AudioFormat &audio_format,
 	    GError **error)
 {
 	OpenALOutput *od = (OpenALOutput *)ao;
@@ -199,7 +199,7 @@ openal_open(struct audio_output *ao, struct audio_format *audio_format,
 	}
 
 	od->filled = 0;
-	od->frequency = audio_format->sample_rate;
+	od->frequency = audio_format.sample_rate;
 
 	return true;
 }
diff --git a/src/output/OssOutputPlugin.cxx b/src/output/OssOutputPlugin.cxx
index 70ffd8bfc..49b02386b 100644
--- a/src/output/OssOutputPlugin.cxx
+++ b/src/output/OssOutputPlugin.cxx
@@ -70,7 +70,7 @@ struct OssOutput {
 	 * The current input audio format.  This is needed to reopen
 	 * the device after cancel().
 	 */
-	struct audio_format audio_format;
+	AudioFormat audio_format;
 
 	/**
 	 * The current OSS audio format.  This is needed to reopen the
@@ -308,10 +308,10 @@ oss_try_ioctl(int fd, unsigned long request, int value,
  * specified number is not supported.
  */
 static bool
-oss_setup_channels(int fd, struct audio_format *audio_format, GError **error_r)
+oss_setup_channels(int fd, AudioFormat &audio_format, GError **error_r)
 {
 	const char *const msg = "Failed to set channel count";
-	int channels = audio_format->channels;
+	int channels = audio_format.channels;
 	enum oss_setup_result result =
 		oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, msg, error_r);
 	switch (result) {
@@ -319,7 +319,7 @@ oss_setup_channels(int fd, struct audio_format *audio_format, GError **error_r)
 		if (!audio_valid_channel_count(channels))
 		    break;
 
-		audio_format->channels = channels;
+		audio_format.channels = channels;
 		return true;
 
 	case ERROR:
@@ -330,7 +330,7 @@ oss_setup_channels(int fd, struct audio_format *audio_format, GError **error_r)
 	}
 
 	for (unsigned i = 1; i < 2; ++i) {
-		if (i == audio_format->channels)
+		if (i == audio_format.channels)
 			/* don't try that again */
 			continue;
 
@@ -342,7 +342,7 @@ oss_setup_channels(int fd, struct audio_format *audio_format, GError **error_r)
 			if (!audio_valid_channel_count(channels))
 			    break;
 
-			audio_format->channels = channels;
+			audio_format.channels = channels;
 			return true;
 
 		case ERROR:
@@ -362,11 +362,11 @@ oss_setup_channels(int fd, struct audio_format *audio_format, GError **error_r)
  * specified sample rate is not supported.
  */
 static bool
-oss_setup_sample_rate(int fd, struct audio_format *audio_format,
+oss_setup_sample_rate(int fd, AudioFormat &audio_format,
 		      GError **error_r)
 {
 	const char *const msg = "Failed to set sample rate";
-	int sample_rate = audio_format->sample_rate;
+	int sample_rate = audio_format.sample_rate;
 	enum oss_setup_result result =
 		oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate,
 				msg, error_r);
@@ -375,7 +375,7 @@ oss_setup_sample_rate(int fd, struct audio_format *audio_format,
 		if (!audio_valid_sample_rate(sample_rate))
 			break;
 
-		audio_format->sample_rate = sample_rate;
+		audio_format.sample_rate = sample_rate;
 		return true;
 
 	case ERROR:
@@ -388,7 +388,7 @@ oss_setup_sample_rate(int fd, struct audio_format *audio_format,
 	static const int sample_rates[] = { 48000, 44100, 0 };
 	for (unsigned i = 0; sample_rates[i] != 0; ++i) {
 		sample_rate = sample_rates[i];
-		if (sample_rate == (int)audio_format->sample_rate)
+		if (sample_rate == (int)audio_format.sample_rate)
 			continue;
 
 		result = oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate,
@@ -398,7 +398,7 @@ oss_setup_sample_rate(int fd, struct audio_format *audio_format,
 			if (!audio_valid_sample_rate(sample_rate))
 				break;
 
-			audio_format->sample_rate = sample_rate;
+			audio_format.sample_rate = sample_rate;
 			return true;
 
 		case ERROR:
@@ -418,28 +418,28 @@ oss_setup_sample_rate(int fd, struct audio_format *audio_format,
  * AFMT_QUERY if there is no direct counterpart.
  */
 static int
-sample_format_to_oss(enum sample_format format)
+sample_format_to_oss(SampleFormat format)
 {
 	switch (format) {
-	case SAMPLE_FORMAT_UNDEFINED:
-	case SAMPLE_FORMAT_FLOAT:
-	case SAMPLE_FORMAT_DSD:
+	case SampleFormat::UNDEFINED:
+	case SampleFormat::FLOAT:
+	case SampleFormat::DSD:
 		return AFMT_QUERY;
 
-	case SAMPLE_FORMAT_S8:
+	case SampleFormat::S8:
 		return AFMT_S8;
 
-	case SAMPLE_FORMAT_S16:
+	case SampleFormat::S16:
 		return AFMT_S16_NE;
 
-	case SAMPLE_FORMAT_S24_P32:
+	case SampleFormat::S24_P32:
 #ifdef AFMT_S24_NE
 		return AFMT_S24_NE;
 #else
 		return AFMT_QUERY;
 #endif
 
-	case SAMPLE_FORMAT_S32:
+	case SampleFormat::S32:
 #ifdef AFMT_S32_NE
 		return AFMT_S32_NE;
 #else
@@ -452,47 +452,47 @@ sample_format_to_oss(enum sample_format format)
 
 /**
  * Convert an OSS sample format to its MPD counterpart.  Returns
- * SAMPLE_FORMAT_UNDEFINED if there is no direct counterpart.
+ * SampleFormat::UNDEFINED if there is no direct counterpart.
  */
-static enum sample_format
+static SampleFormat
 sample_format_from_oss(int format)
 {
 	switch (format) {
 	case AFMT_S8:
-		return SAMPLE_FORMAT_S8;
+		return SampleFormat::S8;
 
 	case AFMT_S16_NE:
-		return SAMPLE_FORMAT_S16;
+		return SampleFormat::S16;
 
 #ifdef AFMT_S24_PACKED
 	case AFMT_S24_PACKED:
-		return SAMPLE_FORMAT_S24_P32;
+		return SampleFormat::S24_P32;
 #endif
 
 #ifdef AFMT_S24_NE
 	case AFMT_S24_NE:
-		return SAMPLE_FORMAT_S24_P32;
+		return SampleFormat::S24_P32;
 #endif
 
 #ifdef AFMT_S32_NE
 	case AFMT_S32_NE:
-		return SAMPLE_FORMAT_S32;
+		return SampleFormat::S32;
 #endif
 
 	default:
-		return SAMPLE_FORMAT_UNDEFINED;
+		return SampleFormat::UNDEFINED;
 	}
 }
 
 /**
  * Probe one sample format.
  *
- * @return the selected sample format or SAMPLE_FORMAT_UNDEFINED on
+ * @return the selected sample format or SampleFormat::UNDEFINED on
  * error
  */
 static enum oss_setup_result
-oss_probe_sample_format(int fd, enum sample_format sample_format,
-			enum sample_format *sample_format_r,
+oss_probe_sample_format(int fd, SampleFormat sample_format,
+			SampleFormat *sample_format_r,
 			int *oss_format_r,
 #ifdef AFMT_S24_PACKED
 			PcmExport &pcm_export,
@@ -509,7 +509,7 @@ oss_probe_sample_format(int fd, enum sample_format sample_format,
 				"Failed to set sample format", error_r);
 
 #ifdef AFMT_S24_PACKED
-	if (result == UNSUPPORTED && sample_format == SAMPLE_FORMAT_S24_P32) {
+	if (result == UNSUPPORTED && sample_format == SampleFormat::S24_P32) {
 		/* if the driver doesn't support padded 24 bit, try
 		   packed 24 bit */
 		oss_format = AFMT_S24_PACKED;
@@ -523,7 +523,7 @@ oss_probe_sample_format(int fd, enum sample_format sample_format,
 		return result;
 
 	sample_format = sample_format_from_oss(oss_format);
-	if (sample_format == SAMPLE_FORMAT_UNDEFINED)
+	if (sample_format == SampleFormat::UNDEFINED)
 		return UNSUPPORTED;
 
 	*sample_format_r = sample_format;
@@ -544,16 +544,16 @@ oss_probe_sample_format(int fd, enum sample_format sample_format,
  * specified format is not supported.
  */
 static bool
-oss_setup_sample_format(int fd, struct audio_format *audio_format,
+oss_setup_sample_format(int fd, AudioFormat &audio_format,
 			int *oss_format_r,
 #ifdef AFMT_S24_PACKED
 			PcmExport &pcm_export,
 #endif
 			GError **error_r)
 {
-	enum sample_format mpd_format;
+	SampleFormat mpd_format;
 	enum oss_setup_result result =
-		oss_probe_sample_format(fd, sample_format(audio_format->format),
+		oss_probe_sample_format(fd, audio_format.format,
 					&mpd_format, oss_format_r,
 #ifdef AFMT_S24_PACKED
 					pcm_export,
@@ -561,7 +561,7 @@ oss_setup_sample_format(int fd, struct audio_format *audio_format,
 					error_r);
 	switch (result) {
 	case SUCCESS:
-		audio_format->format = mpd_format;
+		audio_format.format = mpd_format;
 		return true;
 
 	case ERROR:
@@ -577,17 +577,17 @@ oss_setup_sample_format(int fd, struct audio_format *audio_format,
 	/* the requested sample format is not available - probe for
 	   other formats supported by MPD */
 
-	static const enum sample_format sample_formats[] = {
-		SAMPLE_FORMAT_S24_P32,
-		SAMPLE_FORMAT_S32,
-		SAMPLE_FORMAT_S16,
-		SAMPLE_FORMAT_S8,
-		SAMPLE_FORMAT_UNDEFINED /* sentinel */
+	static const SampleFormat sample_formats[] = {
+		SampleFormat::S24_P32,
+		SampleFormat::S32,
+		SampleFormat::S16,
+		SampleFormat::S8,
+		SampleFormat::UNDEFINED /* sentinel */
 	};
 
-	for (unsigned i = 0; sample_formats[i] != SAMPLE_FORMAT_UNDEFINED; ++i) {
+	for (unsigned i = 0; sample_formats[i] != SampleFormat::UNDEFINED; ++i) {
 		mpd_format = sample_formats[i];
-		if (mpd_format == audio_format->format)
+		if (mpd_format == audio_format.format)
 			/* don't try that again */
 			continue;
 
@@ -599,7 +599,7 @@ oss_setup_sample_format(int fd, struct audio_format *audio_format,
 						 error_r);
 		switch (result) {
 		case SUCCESS:
-			audio_format->format = mpd_format;
+			audio_format.format = mpd_format;
 			return true;
 
 		case ERROR:
@@ -619,7 +619,7 @@ oss_setup_sample_format(int fd, struct audio_format *audio_format,
  * Sets up the OSS device which was opened before.
  */
 static bool
-oss_setup(OssOutput *od, struct audio_format *audio_format,
+oss_setup(OssOutput *od, AudioFormat &audio_format,
 	  GError **error_r)
 {
 	return oss_setup_channels(od->fd, audio_format, error_r) &&
@@ -687,7 +687,7 @@ oss_reopen(OssOutput *od, GError **error_r)
 }
 
 static bool
-oss_output_open(struct audio_output *ao, struct audio_format *audio_format,
+oss_output_open(struct audio_output *ao, AudioFormat &audio_format,
 		GError **error)
 {
 	OssOutput *od = (OssOutput *)ao;
@@ -705,7 +705,7 @@ oss_output_open(struct audio_output *ao, struct audio_format *audio_format,
 		return false;
 	}
 
-	od->audio_format = *audio_format;
+	od->audio_format = audio_format;
 	return true;
 }
 
diff --git a/src/output/PipeOutputPlugin.cxx b/src/output/PipeOutputPlugin.cxx
index 6e72eff12..f76dd04c1 100644
--- a/src/output/PipeOutputPlugin.cxx
+++ b/src/output/PipeOutputPlugin.cxx
@@ -95,7 +95,7 @@ pipe_output_finish(struct audio_output *ao)
 
 static bool
 pipe_output_open(struct audio_output *ao,
-		 G_GNUC_UNUSED struct audio_format *audio_format,
+		 G_GNUC_UNUSED AudioFormat &audio_format,
 		 G_GNUC_UNUSED GError **error)
 {
 	PipeOutput *pd = (PipeOutput *)ao;
diff --git a/src/output/PulseOutputPlugin.cxx b/src/output/PulseOutputPlugin.cxx
index 5098e2adc..65526c8fb 100644
--- a/src/output/PulseOutputPlugin.cxx
+++ b/src/output/PulseOutputPlugin.cxx
@@ -578,7 +578,7 @@ pulse_output_setup_stream(PulseOutput *po, const pa_sample_spec *ss,
 }
 
 static bool
-pulse_output_open(struct audio_output *ao, struct audio_format *audio_format,
+pulse_output_open(struct audio_output *ao, AudioFormat &audio_format,
 		  GError **error_r)
 {
 	PulseOutput *po = (PulseOutput *)ao;
@@ -615,11 +615,11 @@ pulse_output_open(struct audio_output *ao, struct audio_format *audio_format,
 
 	/* MPD doesn't support the other pulseaudio sample formats, so
 	   we just force MPD to send us everything as 16 bit */
-	audio_format->format = SAMPLE_FORMAT_S16;
+	audio_format.format = SampleFormat::S16;
 
 	ss.format = PA_SAMPLE_S16NE;
-	ss.rate = audio_format->sample_rate;
-	ss.channels = audio_format->channels;
+	ss.rate = audio_format.sample_rate;
+	ss.channels = audio_format.channels;
 
 	/* create a stream .. */
 
diff --git a/src/output/RecorderOutputPlugin.cxx b/src/output/RecorderOutputPlugin.cxx
index 6b31fcbc6..2e77463f0 100644
--- a/src/output/RecorderOutputPlugin.cxx
+++ b/src/output/RecorderOutputPlugin.cxx
@@ -192,7 +192,7 @@ RecorderOutput::EncoderToFile(GError **error_r)
 
 static bool
 recorder_output_open(struct audio_output *ao,
-		     struct audio_format *audio_format,
+		     AudioFormat &audio_format,
 		     GError **error_r)
 {
 	RecorderOutput *recorder = (RecorderOutput *)ao;
diff --git a/src/output/RoarOutputPlugin.cxx b/src/output/RoarOutputPlugin.cxx
index 835c7cdd9..bf2bf1789 100644
--- a/src/output/RoarOutputPlugin.cxx
+++ b/src/output/RoarOutputPlugin.cxx
@@ -144,39 +144,41 @@ roar_finish(struct audio_output *ao)
 
 static void
 roar_use_audio_format(struct roar_audio_info *info,
-		      struct audio_format *audio_format)
+		      AudioFormat &audio_format)
 {
-	info->rate = audio_format->sample_rate;
-	info->channels = audio_format->channels;
+	info->rate = audio_format.sample_rate;
+	info->channels = audio_format.channels;
 	info->codec = ROAR_CODEC_PCM_S;
 
-	switch (audio_format->format) {
-	case SAMPLE_FORMAT_UNDEFINED:
+	switch (audio_format.format) {
+	case SampleFormat::UNDEFINED:
+	case SampleFormat::FLOAT:
+	case SampleFormat::DSD:
 		info->bits = 16;
-		audio_format->format = SAMPLE_FORMAT_S16;
+		audio_format.format = SampleFormat::S16;
 		break;
 
-	case SAMPLE_FORMAT_S8:
+	case SampleFormat::S8:
 		info->bits = 8;
 		break;
 
-	case SAMPLE_FORMAT_S16:
+	case SampleFormat::S16:
 		info->bits = 16;
 		break;
 
-	case SAMPLE_FORMAT_S24_P32:
+	case SampleFormat::S24_P32:
 		info->bits = 32;
-		audio_format->format = SAMPLE_FORMAT_S32;
+		audio_format.format = SampleFormat::S32;
 		break;
 
-	case SAMPLE_FORMAT_S32:
+	case SampleFormat::S32:
 		info->bits = 32;
 		break;
 	}
 }
 
 static bool
-roar_open(struct audio_output *ao, struct audio_format *audio_format, GError **error)
+roar_open(struct audio_output *ao, AudioFormat &audio_format, GError **error)
 {
 	RoarOutput *self = (RoarOutput *)ao;
 	const ScopeLock protect(self->mutex);
diff --git a/src/output/ShoutOutputPlugin.cxx b/src/output/ShoutOutputPlugin.cxx
index a0f75da1d..fbad24ec8 100644
--- a/src/output/ShoutOutputPlugin.cxx
+++ b/src/output/ShoutOutputPlugin.cxx
@@ -116,9 +116,8 @@ inline bool
 ShoutOutput::Configure(const config_param *param, GError **error_r)
 {
 
-	const struct audio_format *audio_format =
-		&base.config_audio_format;
-	if (!audio_format_fully_defined(audio_format)) {
+	const AudioFormat audio_format = base.config_audio_format;
+	if (!audio_format.IsFullyDefined()) {
 		g_set_error(error_r, shout_output_quark(), 0,
 			    "Need full audio format specification");
 		return nullptr;
@@ -269,10 +268,10 @@ ShoutOutput::Configure(const config_param *param, GError **error_r)
 		char temp[11];
 		memset(temp, 0, sizeof(temp));
 
-		snprintf(temp, sizeof(temp), "%u", audio_format->channels);
+		snprintf(temp, sizeof(temp), "%u", audio_format.channels);
 		shout_set_audio_info(shout_conn, SHOUT_AI_CHANNELS, temp);
 
-		snprintf(temp, sizeof(temp), "%u", audio_format->sample_rate);
+		snprintf(temp, sizeof(temp), "%u", audio_format.sample_rate);
 
 		shout_set_audio_info(shout_conn, SHOUT_AI_SAMPLERATE, temp);
 
@@ -428,7 +427,7 @@ shout_connect(ShoutOutput *sd, GError **error)
 }
 
 static bool
-my_shout_open_device(struct audio_output *ao, struct audio_format *audio_format,
+my_shout_open_device(struct audio_output *ao, AudioFormat &audio_format,
 		     GError **error)
 {
 	ShoutOutput *sd = (ShoutOutput *)ao;
diff --git a/src/output/SolarisOutputPlugin.cxx b/src/output/SolarisOutputPlugin.cxx
index c20de4a8b..81c793e21 100644
--- a/src/output/SolarisOutputPlugin.cxx
+++ b/src/output/SolarisOutputPlugin.cxx
@@ -113,7 +113,7 @@ solaris_output_finish(struct audio_output *ao)
 }
 
 static bool
-solaris_output_open(struct audio_output *ao, struct audio_format *audio_format,
+solaris_output_open(struct audio_output *ao, AudioFormat &audio_format,
 		    GError **error)
 {
 	SolarisOutput *so = (SolarisOutput *)ao;
@@ -122,7 +122,7 @@ solaris_output_open(struct audio_output *ao, struct audio_format *audio_format,
 
 	/* support only 16 bit mono/stereo for now; nothing else has
 	   been tested */
-	audio_format->format = SAMPLE_FORMAT_S16;
+	audio_format.format = SampleFormat::S16;
 
 	/* open the device in non-blocking mode */
 
@@ -150,8 +150,8 @@ solaris_output_open(struct audio_output *ao, struct audio_format *audio_format,
 		return false;
 	}
 
-	info.play.sample_rate = audio_format->sample_rate;
-	info.play.channels = audio_format->channels;
+	info.play.sample_rate = audio_format.sample_rate;
+	info.play.channels = audio_format.channels;
 	info.play.precision = 16;
 	info.play.encoding = AUDIO_ENCODING_LINEAR;
 
diff --git a/src/output/WinmmOutputPlugin.cxx b/src/output/WinmmOutputPlugin.cxx
index 2418731a0..de6927bd2 100644
--- a/src/output/WinmmOutputPlugin.cxx
+++ b/src/output/WinmmOutputPlugin.cxx
@@ -142,7 +142,7 @@ winmm_output_finish(struct audio_output *ao)
 }
 
 static bool
-winmm_output_open(struct audio_output *ao, struct audio_format *audio_format,
+winmm_output_open(struct audio_output *ao, AudioFormat &audio_format,
 		  GError **error_r)
 {
 	WinmmOutput *wo = (WinmmOutput *)ao;
@@ -154,30 +154,32 @@ winmm_output_open(struct audio_output *ao, struct audio_format *audio_format,
 		return false;
 	}
 
-	switch (audio_format->format) {
-	case SAMPLE_FORMAT_S8:
-	case SAMPLE_FORMAT_S16:
+	switch (audio_format.format) {
+	case SampleFormat::S8:
+	case SampleFormat::S16:
 		break;
 
-	case SAMPLE_FORMAT_S24_P32:
-	case SAMPLE_FORMAT_S32:
-	case SAMPLE_FORMAT_UNDEFINED:
+	case SampleFormat::S24_P32:
+	case SampleFormat::S32:
+	case SampleFormat::FLOAT:
+	case SampleFormat::DSD:
+	case SampleFormat::UNDEFINED:
 		/* we havn't tested formats other than S16 */
-		audio_format->format = SAMPLE_FORMAT_S16;
+		audio_format.format = SampleFormat::S16;
 		break;
 	}
 
-	if (audio_format->channels > 2)
+	if (audio_format.channels > 2)
 		/* same here: more than stereo was not tested */
-		audio_format->channels = 2;
+		audio_format.channels = 2;
 
 	WAVEFORMATEX format;
 	format.wFormatTag = WAVE_FORMAT_PCM;
-	format.nChannels = audio_format->channels;
-	format.nSamplesPerSec = audio_format->sample_rate;
-	format.nBlockAlign = audio_format_frame_size(audio_format);
+	format.nChannels = audio_format.channels;
+	format.nSamplesPerSec = audio_format.sample_rate;
+	format.nBlockAlign = audio_format.GetFrameSize();
 	format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
-	format.wBitsPerSample = audio_format_sample_size(audio_format) * 8;
+	format.wBitsPerSample = audio_format.GetSampleSize() * 8;
 	format.cbSize = 0;
 
 	MMRESULT result = waveOutOpen(&wo->handle, wo->device_id, &format,
diff --git a/src/pcm/PcmConvert.cxx b/src/pcm/PcmConvert.cxx
index 69e1c5d04..7e71da283 100644
--- a/src/pcm/PcmConvert.cxx
+++ b/src/pcm/PcmConvert.cxx
@@ -22,7 +22,7 @@
 #include "PcmChannels.hxx"
 #include "PcmFormat.hxx"
 #include "pcm_pack.h"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 
 #include <glib.h>
 
@@ -48,46 +48,46 @@ PcmConvert::Reset()
 }
 
 inline const int16_t *
-PcmConvert::Convert16(const audio_format *src_format,
+PcmConvert::Convert16(const AudioFormat src_format,
 		      const void *src_buffer, size_t src_size,
-		      const audio_format *dest_format, size_t *dest_size_r,
+		      const AudioFormat dest_format, size_t *dest_size_r,
 		      GError **error_r)
 {
 	const int16_t *buf;
 	size_t len;
 
-	assert(dest_format->format == SAMPLE_FORMAT_S16);
+	assert(dest_format.format == SampleFormat::S16);
 
 	buf = pcm_convert_to_16(format_buffer, dither,
-				sample_format(src_format->format),
+				src_format.format,
 				src_buffer, src_size,
 				&len);
 	if (buf == NULL) {
 		g_set_error(error_r, pcm_convert_quark(), 0,
 			    "Conversion from %s to 16 bit is not implemented",
-			    sample_format_to_string(sample_format(src_format->format)));
+			    sample_format_to_string(src_format.format));
 		return NULL;
 	}
 
-	if (src_format->channels != dest_format->channels) {
+	if (src_format.channels != dest_format.channels) {
 		buf = pcm_convert_channels_16(channels_buffer,
-					      dest_format->channels,
-					      src_format->channels,
+					      dest_format.channels,
+					      src_format.channels,
 					      buf, len, &len);
 		if (buf == NULL) {
 			g_set_error(error_r, pcm_convert_quark(), 0,
 				    "Conversion from %u to %u channels "
 				    "is not implemented",
-				    src_format->channels,
-				    dest_format->channels);
+				    src_format.channels,
+				    dest_format.channels);
 			return NULL;
 		}
 	}
 
-	if (src_format->sample_rate != dest_format->sample_rate) {
-		buf = resampler.Resample16(dest_format->channels,
-					   src_format->sample_rate, buf, len,
-					   dest_format->sample_rate, &len,
+	if (src_format.sample_rate != dest_format.sample_rate) {
+		buf = resampler.Resample16(dest_format.channels,
+					   src_format.sample_rate, buf, len,
+					   dest_format.sample_rate, &len,
 					   error_r);
 		if (buf == NULL)
 			return NULL;
@@ -98,45 +98,45 @@ PcmConvert::Convert16(const audio_format *src_format,
 }
 
 inline const int32_t *
-PcmConvert::Convert24(const audio_format *src_format,
+PcmConvert::Convert24(const AudioFormat src_format,
 		      const void *src_buffer, size_t src_size,
-		      const audio_format *dest_format, size_t *dest_size_r,
+		      const AudioFormat dest_format, size_t *dest_size_r,
 		      GError **error_r)
 {
 	const int32_t *buf;
 	size_t len;
 
-	assert(dest_format->format == SAMPLE_FORMAT_S24_P32);
+	assert(dest_format.format == SampleFormat::S24_P32);
 
 	buf = pcm_convert_to_24(format_buffer,
-				sample_format(src_format->format),
+				src_format.format,
 				src_buffer, src_size, &len);
 	if (buf == NULL) {
 		g_set_error(error_r, pcm_convert_quark(), 0,
 			    "Conversion from %s to 24 bit is not implemented",
-			    sample_format_to_string(sample_format(src_format->format)));
+			    sample_format_to_string(src_format.format));
 		return NULL;
 	}
 
-	if (src_format->channels != dest_format->channels) {
+	if (src_format.channels != dest_format.channels) {
 		buf = pcm_convert_channels_24(channels_buffer,
-					      dest_format->channels,
-					      src_format->channels,
+					      dest_format.channels,
+					      src_format.channels,
 					      buf, len, &len);
 		if (buf == NULL) {
 			g_set_error(error_r, pcm_convert_quark(), 0,
 				    "Conversion from %u to %u channels "
 				    "is not implemented",
-				    src_format->channels,
-				    dest_format->channels);
+				    src_format.channels,
+				    dest_format.channels);
 			return NULL;
 		}
 	}
 
-	if (src_format->sample_rate != dest_format->sample_rate) {
-		buf = resampler.Resample24(dest_format->channels,
-					   src_format->sample_rate, buf, len,
-					   dest_format->sample_rate, &len,
+	if (src_format.sample_rate != dest_format.sample_rate) {
+		buf = resampler.Resample24(dest_format.channels,
+					   src_format.sample_rate, buf, len,
+					   dest_format.sample_rate, &len,
 					   error_r);
 		if (buf == NULL)
 			return NULL;
@@ -147,45 +147,45 @@ PcmConvert::Convert24(const audio_format *src_format,
 }
 
 inline const int32_t *
-PcmConvert::Convert32(const audio_format *src_format,
+PcmConvert::Convert32(const AudioFormat src_format,
 		      const void *src_buffer, size_t src_size,
-		      const audio_format *dest_format, size_t *dest_size_r,
+		      const AudioFormat dest_format, size_t *dest_size_r,
 		      GError **error_r)
 {
 	const int32_t *buf;
 	size_t len;
 
-	assert(dest_format->format == SAMPLE_FORMAT_S32);
+	assert(dest_format.format == SampleFormat::S32);
 
 	buf = pcm_convert_to_32(format_buffer,
-				sample_format(src_format->format),
+				src_format.format,
 				src_buffer, src_size, &len);
 	if (buf == NULL) {
 		g_set_error(error_r, pcm_convert_quark(), 0,
 			    "Conversion from %s to 32 bit is not implemented",
-			    sample_format_to_string(sample_format(src_format->format)));
+			    sample_format_to_string(src_format.format));
 		return NULL;
 	}
 
-	if (src_format->channels != dest_format->channels) {
+	if (src_format.channels != dest_format.channels) {
 		buf = pcm_convert_channels_32(channels_buffer,
-					      dest_format->channels,
-					      src_format->channels,
+					      dest_format.channels,
+					      src_format.channels,
 					      buf, len, &len);
 		if (buf == NULL) {
 			g_set_error(error_r, pcm_convert_quark(), 0,
 				    "Conversion from %u to %u channels "
 				    "is not implemented",
-				    src_format->channels,
-				    dest_format->channels);
+				    src_format.channels,
+				    dest_format.channels);
 			return NULL;
 		}
 	}
 
-	if (src_format->sample_rate != dest_format->sample_rate) {
-		buf = resampler.Resample32(dest_format->channels,
-					   src_format->sample_rate, buf, len,
-					   dest_format->sample_rate, &len,
+	if (src_format.sample_rate != dest_format.sample_rate) {
+		buf = resampler.Resample32(dest_format.channels,
+					   src_format.sample_rate, buf, len,
+					   dest_format.sample_rate, &len,
 					   error_r);
 		if (buf == NULL)
 			return buf;
@@ -196,41 +196,41 @@ PcmConvert::Convert32(const audio_format *src_format,
 }
 
 inline const float *
-PcmConvert::ConvertFloat(const audio_format *src_format,
+PcmConvert::ConvertFloat(const AudioFormat src_format,
 			 const void *src_buffer, size_t src_size,
-			 const audio_format *dest_format, size_t *dest_size_r,
+			 const AudioFormat dest_format, size_t *dest_size_r,
 			 GError **error_r)
 {
 	const float *buffer = (const float *)src_buffer;
 	size_t size = src_size;
 
-	assert(dest_format->format == SAMPLE_FORMAT_FLOAT);
+	assert(dest_format.format == SampleFormat::FLOAT);
 
 	/* convert to float now */
 
 	buffer = pcm_convert_to_float(format_buffer,
-				      sample_format(src_format->format),
+				      src_format.format,
 				      buffer, size, &size);
 	if (buffer == NULL) {
 		g_set_error(error_r, pcm_convert_quark(), 0,
 			    "Conversion from %s to float is not implemented",
-			    sample_format_to_string(sample_format(src_format->format)));
+			    sample_format_to_string(src_format.format));
 		return NULL;
 	}
 
 	/* convert channels */
 
-	if (src_format->channels != dest_format->channels) {
+	if (src_format.channels != dest_format.channels) {
 		buffer = pcm_convert_channels_float(channels_buffer,
-						    dest_format->channels,
-						    src_format->channels,
+						    dest_format.channels,
+						    src_format.channels,
 						    buffer, size, &size);
 		if (buffer == NULL) {
 			g_set_error(error_r, pcm_convert_quark(), 0,
 				    "Conversion from %u to %u channels "
 				    "is not implemented",
-				    src_format->channels,
-				    dest_format->channels);
+				    src_format.channels,
+				    dest_format.channels);
 			return NULL;
 		}
 	}
@@ -238,11 +238,11 @@ PcmConvert::ConvertFloat(const audio_format *src_format,
 	/* resample with float, because this is the best format for
 	   libsamplerate */
 
-	if (src_format->sample_rate != dest_format->sample_rate) {
-		buffer = resampler.ResampleFloat(dest_format->channels,
-						 src_format->sample_rate,
+	if (src_format.sample_rate != dest_format.sample_rate) {
+		buffer = resampler.ResampleFloat(dest_format.channels,
+						 src_format.sample_rate,
 						 buffer, size,
-						 dest_format->sample_rate,
+						 dest_format.sample_rate,
 						 &size, error_r);
 		if (buffer == NULL)
 			return NULL;
@@ -253,16 +253,16 @@ PcmConvert::ConvertFloat(const audio_format *src_format,
 }
 
 const void *
-PcmConvert::Convert(const audio_format *src_format,
+PcmConvert::Convert(AudioFormat src_format,
 		    const void *src, size_t src_size,
-		    const audio_format *dest_format,
+		    const AudioFormat dest_format,
 		    size_t *dest_size_r,
 		    GError **error_r)
 {
-	struct audio_format float_format;
-	if (src_format->format == SAMPLE_FORMAT_DSD) {
+	AudioFormat float_format;
+	if (src_format.format == SampleFormat::DSD) {
 		size_t f_size;
-		const float *f = dsd.ToFloat(src_format->channels,
+		const float *f = dsd.ToFloat(src_format.channels,
 					     false, (const uint8_t *)src,
 					     src_size, &f_size);
 		if (f == NULL) {
@@ -271,31 +271,31 @@ PcmConvert::Convert(const audio_format *src_format,
 			return NULL;
 		}
 
-		float_format = *src_format;
-		float_format.format = SAMPLE_FORMAT_FLOAT;
+		float_format = src_format;
+		float_format.format = SampleFormat::FLOAT;
 
-		src_format = &float_format;
+		src_format = float_format;
 		src = f;
 		src_size = f_size;
 	}
 
-	switch (sample_format(dest_format->format)) {
-	case SAMPLE_FORMAT_S16:
+	switch (dest_format.format) {
+	case SampleFormat::S16:
 		return Convert16(src_format, src, src_size,
 				 dest_format, dest_size_r,
 				 error_r);
 
-	case SAMPLE_FORMAT_S24_P32:
+	case SampleFormat::S24_P32:
 		return Convert24(src_format, src, src_size,
 				 dest_format, dest_size_r,
 				 error_r);
 
-	case SAMPLE_FORMAT_S32:
+	case SampleFormat::S32:
 		return Convert32(src_format, src, src_size,
 				 dest_format, dest_size_r,
 				 error_r);
 
-	case SAMPLE_FORMAT_FLOAT:
+	case SampleFormat::FLOAT:
 		return ConvertFloat(src_format, src, src_size,
 				    dest_format, dest_size_r,
 				    error_r);
@@ -303,7 +303,7 @@ PcmConvert::Convert(const audio_format *src_format,
 	default:
 		g_set_error(error_r, pcm_convert_quark(), 0,
 			    "PCM conversion to %s is not implemented",
-			    sample_format_to_string(sample_format(dest_format->format)));
+			    sample_format_to_string(dest_format.format));
 		return NULL;
 	}
 }
diff --git a/src/pcm/PcmConvert.hxx b/src/pcm/PcmConvert.hxx
index f4503f5aa..42b59e407 100644
--- a/src/pcm/PcmConvert.hxx
+++ b/src/pcm/PcmConvert.hxx
@@ -27,7 +27,7 @@
 
 #include <glib.h>
 
-struct audio_format;
+struct AudioFormat;
 
 /**
  * This object is statically allocated (within another struct), and
@@ -71,34 +71,34 @@ public:
 	 * ignore errors
 	 * @return the destination buffer, or NULL on error
 	 */
-	const void *Convert(const audio_format *src_format,
+	const void *Convert(AudioFormat src_format,
 			    const void *src, size_t src_size,
-			    const audio_format *dest_format,
+			    AudioFormat dest_format,
 			    size_t *dest_size_r,
 			    GError **error_r);
 
 private:
-	const int16_t *Convert16(const audio_format *src_format,
+	const int16_t *Convert16(AudioFormat src_format,
 				 const void *src_buffer, size_t src_size,
-				 const audio_format *dest_format,
+				 AudioFormat dest_format,
 				 size_t *dest_size_r,
 				 GError **error_r);
 
-	const int32_t *Convert24(const audio_format *src_format,
+	const int32_t *Convert24(AudioFormat src_format,
 				 const void *src_buffer, size_t src_size,
-				 const audio_format *dest_format,
+				 AudioFormat dest_format,
 				 size_t *dest_size_r,
 				 GError **error_r);
 
-	const int32_t *Convert32(const audio_format *src_format,
+	const int32_t *Convert32(AudioFormat src_format,
 				 const void *src_buffer, size_t src_size,
-				 const audio_format *dest_format,
+				 AudioFormat dest_format,
 				 size_t *dest_size_r,
 				 GError **error_r);
 
-	const float *ConvertFloat(const audio_format *src_format,
+	const float *ConvertFloat(AudioFormat src_format,
 				  const void *src_buffer, size_t src_size,
-				  const audio_format *dest_format,
+				  AudioFormat dest_format,
 				  size_t *dest_size_r,
 				  GError **error_r);
 };
diff --git a/src/pcm/PcmDsdUsb.cxx b/src/pcm/PcmDsdUsb.cxx
index ef267e724..7d58dec4d 100644
--- a/src/pcm/PcmDsdUsb.cxx
+++ b/src/pcm/PcmDsdUsb.cxx
@@ -20,7 +20,7 @@
 #include "config.h"
 #include "PcmDsdUsb.hxx"
 #include "PcmBuffer.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 
 G_GNUC_CONST
 static inline uint32_t
diff --git a/src/pcm/PcmExport.cxx b/src/pcm/PcmExport.cxx
index 8840dc865..762411f59 100644
--- a/src/pcm/PcmExport.cxx
+++ b/src/pcm/PcmExport.cxx
@@ -27,21 +27,21 @@ extern "C" {
 }
 
 void
-PcmExport::Open(enum sample_format sample_format, unsigned _channels,
+PcmExport::Open(SampleFormat sample_format, unsigned _channels,
 		bool _dsd_usb, bool _shift8, bool _pack, bool _reverse_endian)
 {
 	assert(audio_valid_sample_format(sample_format));
 	assert(!_dsd_usb || audio_valid_channel_count(_channels));
 
 	channels = _channels;
-	dsd_usb = _dsd_usb && sample_format == SAMPLE_FORMAT_DSD;
+	dsd_usb = _dsd_usb && sample_format == SampleFormat::DSD;
 	if (dsd_usb)
 		/* after the conversion to DSD-over-USB, the DSD
 		   samples are stuffed inside fake 24 bit samples */
-		sample_format = SAMPLE_FORMAT_S24_P32;
+		sample_format = SampleFormat::S24_P32;
 
-	shift8 = _shift8 && sample_format == SAMPLE_FORMAT_S24_P32;
-	pack24 = _pack && sample_format == SAMPLE_FORMAT_S24_P32;
+	shift8 = _shift8 && sample_format == SampleFormat::S24_P32;
+	pack24 = _pack && sample_format == SampleFormat::S24_P32;
 
 	assert(!shift8 || !pack24);
 
@@ -58,7 +58,7 @@ PcmExport::Open(enum sample_format sample_format, unsigned _channels,
 }
 
 size_t
-PcmExport::GetFrameSize(const struct audio_format &audio_format) const
+PcmExport::GetFrameSize(const AudioFormat &audio_format) const
 {
 	if (pack24)
 		/* packed 24 bit samples (3 bytes per sample) */
@@ -71,7 +71,7 @@ PcmExport::GetFrameSize(const struct audio_format &audio_format) const
 		   bytes per sample) */
 		return channels * 4;
 
-	return audio_format_frame_size(&audio_format);
+	return audio_format.GetFrameSize();
 }
 
 const void *
diff --git a/src/pcm/PcmExport.hxx b/src/pcm/PcmExport.hxx
index e420493f0..bd18c0534 100644
--- a/src/pcm/PcmExport.hxx
+++ b/src/pcm/PcmExport.hxx
@@ -22,9 +22,9 @@
 
 #include "check.h"
 #include "PcmBuffer.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 
-struct audio_format;
+struct AudioFormat;
 
 /**
  * An object that handles export of PCM samples to some instance
@@ -61,8 +61,8 @@ struct PcmExport {
 
 	/**
 	 * Convert DSD to DSD-over-USB?  Input format must be
-	 * SAMPLE_FORMAT_DSD and output format must be
-	 * SAMPLE_FORMAT_S24_P32.
+	 * SampleFormat::DSD and output format must be
+	 * SampleFormat::S24_P32.
 	 */
 	bool dsd_usb;
 
@@ -94,14 +94,14 @@ struct PcmExport {
 	 *
 	 * @param channels the number of channels; ignored unless dsd_usb is set
 	 */
-	void Open(enum sample_format sample_format, unsigned channels,
+	void Open(SampleFormat sample_format, unsigned channels,
 		  bool dsd_usb, bool shift8, bool pack, bool reverse_endian);
 
 	/**
 	 * Calculate the size of one output frame.
 	 */
 	gcc_pure
-	size_t GetFrameSize(const struct audio_format &audio_format) const;
+	size_t GetFrameSize(const AudioFormat &audio_format) const;
 
 	/**
 	 * Export a PCM buffer.
diff --git a/src/pcm/PcmFormat.cxx b/src/pcm/PcmFormat.cxx
index 2dea09d2c..6425c7cfd 100644
--- a/src/pcm/PcmFormat.cxx
+++ b/src/pcm/PcmFormat.cxx
@@ -142,36 +142,36 @@ pcm_allocate_float_to_16(PcmBuffer &buffer,
 
 const int16_t *
 pcm_convert_to_16(PcmBuffer &buffer, PcmDither &dither,
-		  enum sample_format src_format, const void *src,
+		  SampleFormat src_format, const void *src,
 		  size_t src_size, size_t *dest_size_r)
 {
 	assert(src_size % sample_format_size(src_format) == 0);
 
 	switch (src_format) {
-	case SAMPLE_FORMAT_UNDEFINED:
-	case SAMPLE_FORMAT_DSD:
+	case SampleFormat::UNDEFINED:
+	case SampleFormat::DSD:
 		break;
 
-	case SAMPLE_FORMAT_S8:
+	case SampleFormat::S8:
 		return pcm_allocate_8_to_16(buffer,
 					    (const int8_t *)src, src_size,
 					    dest_size_r);
 
-	case SAMPLE_FORMAT_S16:
+	case SampleFormat::S16:
 		*dest_size_r = src_size;
 		return (const int16_t *)src;
 
-	case SAMPLE_FORMAT_S24_P32:
+	case SampleFormat::S24_P32:
 		return pcm_allocate_24p32_to_16(buffer, dither,
 						(const int32_t *)src, src_size,
 						dest_size_r);
 
-	case SAMPLE_FORMAT_S32:
+	case SampleFormat::S32:
 		return pcm_allocate_32_to_16(buffer, dither,
 					     (const int32_t *)src, src_size,
 					     dest_size_r);
 
-	case SAMPLE_FORMAT_FLOAT:
+	case SampleFormat::FLOAT:
 		return pcm_allocate_float_to_16(buffer,
 						(const float *)src, src_size,
 						dest_size_r);
@@ -247,36 +247,36 @@ pcm_allocate_float_to_24(PcmBuffer &buffer,
 
 const int32_t *
 pcm_convert_to_24(PcmBuffer &buffer,
-		  enum sample_format src_format, const void *src,
+		  SampleFormat src_format, const void *src,
 		  size_t src_size, size_t *dest_size_r)
 {
 	assert(src_size % sample_format_size(src_format) == 0);
 
 	switch (src_format) {
-	case SAMPLE_FORMAT_UNDEFINED:
-	case SAMPLE_FORMAT_DSD:
+	case SampleFormat::UNDEFINED:
+	case SampleFormat::DSD:
 		break;
 
-	case SAMPLE_FORMAT_S8:
+	case SampleFormat::S8:
 		return pcm_allocate_8_to_24(buffer,
 					    (const int8_t *)src, src_size,
 					    dest_size_r);
 
-	case SAMPLE_FORMAT_S16:
+	case SampleFormat::S16:
 		return pcm_allocate_16_to_24(buffer,
 					     (const int16_t *)src, src_size,
 					     dest_size_r);
 
-	case SAMPLE_FORMAT_S24_P32:
+	case SampleFormat::S24_P32:
 		*dest_size_r = src_size;
 		return (const int32_t *)src;
 
-	case SAMPLE_FORMAT_S32:
+	case SampleFormat::S32:
 		return pcm_allocate_32_to_24(buffer,
 					     (const int32_t *)src, src_size,
 					     dest_size_r);
 
-	case SAMPLE_FORMAT_FLOAT:
+	case SampleFormat::FLOAT:
 		return pcm_allocate_float_to_24(buffer,
 						(const float *)src, src_size,
 						dest_size_r);
@@ -358,36 +358,36 @@ pcm_allocate_float_to_32(PcmBuffer &buffer,
 
 const int32_t *
 pcm_convert_to_32(PcmBuffer &buffer,
-		  enum sample_format src_format, const void *src,
+		  SampleFormat src_format, const void *src,
 		  size_t src_size, size_t *dest_size_r)
 {
 	assert(src_size % sample_format_size(src_format) == 0);
 
 	switch (src_format) {
-	case SAMPLE_FORMAT_UNDEFINED:
-	case SAMPLE_FORMAT_DSD:
+	case SampleFormat::UNDEFINED:
+	case SampleFormat::DSD:
 		break;
 
-	case SAMPLE_FORMAT_S8:
+	case SampleFormat::S8:
 		return pcm_allocate_8_to_32(buffer,
 					    (const int8_t *)src, src_size,
 					    dest_size_r);
 
-	case SAMPLE_FORMAT_S16:
+	case SampleFormat::S16:
 		return pcm_allocate_16_to_32(buffer,
 					     (const int16_t *)src, src_size,
 					     dest_size_r);
 
-	case SAMPLE_FORMAT_S24_P32:
+	case SampleFormat::S24_P32:
 		return pcm_allocate_24p32_to_32(buffer,
 						(const int32_t *)src, src_size,
 						dest_size_r);
 
-	case SAMPLE_FORMAT_S32:
+	case SampleFormat::S32:
 		*dest_size_r = src_size;
 		return (const int32_t *)src;
 
-	case SAMPLE_FORMAT_FLOAT:
+	case SampleFormat::FLOAT:
 		return pcm_allocate_float_to_32(buffer,
 						(const float *)src, src_size,
 						dest_size_r);
@@ -463,35 +463,35 @@ pcm_allocate_32_to_float(PcmBuffer &buffer,
 
 const float *
 pcm_convert_to_float(PcmBuffer &buffer,
-		     enum sample_format src_format, const void *src,
+		     SampleFormat src_format, const void *src,
 		     size_t src_size, size_t *dest_size_r)
 {
 	switch (src_format) {
-	case SAMPLE_FORMAT_UNDEFINED:
-	case SAMPLE_FORMAT_DSD:
+	case SampleFormat::UNDEFINED:
+	case SampleFormat::DSD:
 		break;
 
-	case SAMPLE_FORMAT_S8:
+	case SampleFormat::S8:
 		return pcm_allocate_8_to_float(buffer,
 					       (const int8_t *)src, src_size,
 					       dest_size_r);
 
-	case SAMPLE_FORMAT_S16:
+	case SampleFormat::S16:
 		return pcm_allocate_16_to_float(buffer,
 						(const int16_t *)src, src_size,
 						dest_size_r);
 
-	case SAMPLE_FORMAT_S24_P32:
+	case SampleFormat::S24_P32:
 		return pcm_allocate_24p32_to_float(buffer,
 						   (const int32_t *)src, src_size,
 						   dest_size_r);
 
-	case SAMPLE_FORMAT_S32:
+	case SampleFormat::S32:
 		return pcm_allocate_32_to_float(buffer,
 						(const int32_t *)src, src_size,
 						dest_size_r);
 
-	case SAMPLE_FORMAT_FLOAT:
+	case SampleFormat::FLOAT:
 		*dest_size_r = src_size;
 		return (const float *)src;
 	}
diff --git a/src/pcm/PcmFormat.hxx b/src/pcm/PcmFormat.hxx
index bb5ad49ae..b18b4f932 100644
--- a/src/pcm/PcmFormat.hxx
+++ b/src/pcm/PcmFormat.hxx
@@ -20,7 +20,7 @@
 #ifndef MPD_PCM_FORMAT_HXX
 #define MPD_PCM_FORMAT_HXX
 
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 
 #include <stdint.h>
 #include <stddef.h>
@@ -42,7 +42,7 @@ class PcmDither;
  */
 const int16_t *
 pcm_convert_to_16(PcmBuffer &buffer, PcmDither &dither,
-		  enum sample_format src_format, const void *src,
+		  SampleFormat src_format, const void *src,
 		  size_t src_size, size_t *dest_size_r);
 
 /**
@@ -57,7 +57,7 @@ pcm_convert_to_16(PcmBuffer &buffer, PcmDither &dither,
  */
 const int32_t *
 pcm_convert_to_24(PcmBuffer &buffer,
-		  enum sample_format src_format, const void *src,
+		  SampleFormat src_format, const void *src,
 		  size_t src_size, size_t *dest_size_r);
 
 /**
@@ -72,7 +72,7 @@ pcm_convert_to_24(PcmBuffer &buffer,
  */
 const int32_t *
 pcm_convert_to_32(PcmBuffer &buffer,
-		  enum sample_format src_format, const void *src,
+		  SampleFormat src_format, const void *src,
 		  size_t src_size, size_t *dest_size_r);
 
 /**
@@ -87,7 +87,7 @@ pcm_convert_to_32(PcmBuffer &buffer,
  */
 const float *
 pcm_convert_to_float(PcmBuffer &buffer,
-		     enum sample_format src_format, const void *src,
+		     SampleFormat src_format, const void *src,
 		     size_t src_size, size_t *dest_size_r);
 
 #endif
diff --git a/src/pcm/PcmMix.cxx b/src/pcm/PcmMix.cxx
index b92258a83..f4a02fc47 100644
--- a/src/pcm/PcmMix.cxx
+++ b/src/pcm/PcmMix.cxx
@@ -21,7 +21,7 @@
 #include "PcmMix.hxx"
 #include "PcmVolume.hxx"
 #include "PcmUtils.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 
 #include <math.h>
 
@@ -74,35 +74,35 @@ pcm_add_vol_float(float *buffer1, const float *buffer2,
 static bool
 pcm_add_vol(void *buffer1, const void *buffer2, size_t size,
 	    int vol1, int vol2,
-	    enum sample_format format)
+	    SampleFormat format)
 {
 	switch (format) {
-	case SAMPLE_FORMAT_UNDEFINED:
-	case SAMPLE_FORMAT_DSD:
+	case SampleFormat::UNDEFINED:
+	case SampleFormat::DSD:
 		/* not implemented */
 		return false;
 
-	case SAMPLE_FORMAT_S8:
+	case SampleFormat::S8:
 		PcmAddVolumeVoid<int8_t, int32_t, 8>(buffer1, buffer2, size,
 						     vol1, vol2);
 		return true;
 
-	case SAMPLE_FORMAT_S16:
+	case SampleFormat::S16:
 		PcmAddVolumeVoid<int16_t, int32_t, 16>(buffer1, buffer2, size,
 						       vol1, vol2);
 		return true;
 
-	case SAMPLE_FORMAT_S24_P32:
+	case SampleFormat::S24_P32:
 		PcmAddVolumeVoid<int32_t, int64_t, 24>(buffer1, buffer2, size,
 						       vol1, vol2);
 		return true;
 
-	case SAMPLE_FORMAT_S32:
+	case SampleFormat::S32:
 		PcmAddVolumeVoid<int32_t, int64_t, 32>(buffer1, buffer2, size,
 						       vol1, vol2);
 		return true;
 
-	case SAMPLE_FORMAT_FLOAT:
+	case SampleFormat::FLOAT:
 		pcm_add_vol_float((float *)buffer1, (const float *)buffer2,
 				  size / 4,
 				  pcm_volume_to_float(vol1),
@@ -153,31 +153,31 @@ pcm_add_float(float *buffer1, const float *buffer2, unsigned num_samples)
 
 static bool
 pcm_add(void *buffer1, const void *buffer2, size_t size,
-	enum sample_format format)
+	SampleFormat format)
 {
 	switch (format) {
-	case SAMPLE_FORMAT_UNDEFINED:
-	case SAMPLE_FORMAT_DSD:
+	case SampleFormat::UNDEFINED:
+	case SampleFormat::DSD:
 		/* not implemented */
 		return false;
 
-	case SAMPLE_FORMAT_S8:
+	case SampleFormat::S8:
 		PcmAddVoid<int8_t, int32_t, 8>(buffer1, buffer2, size);
 		return true;
 
-	case SAMPLE_FORMAT_S16:
+	case SampleFormat::S16:
 		PcmAddVoid<int16_t, int32_t, 16>(buffer1, buffer2, size);
 		return true;
 
-	case SAMPLE_FORMAT_S24_P32:
+	case SampleFormat::S24_P32:
 		PcmAddVoid<int32_t, int64_t, 24>(buffer1, buffer2, size);
 		return true;
 
-	case SAMPLE_FORMAT_S32:
+	case SampleFormat::S32:
 		PcmAddVoid<int32_t, int64_t, 32>(buffer1, buffer2, size);
 		return true;
 
-	case SAMPLE_FORMAT_FLOAT:
+	case SampleFormat::FLOAT:
 		pcm_add_float((float *)buffer1, (const float *)buffer2,
 			      size / 4);
 		return true;
@@ -189,7 +189,7 @@ pcm_add(void *buffer1, const void *buffer2, size_t size,
 
 bool
 pcm_mix(void *buffer1, const void *buffer2, size_t size,
-	enum sample_format format, float portion1)
+	SampleFormat format, float portion1)
 {
 	int vol1;
 	float s;
diff --git a/src/pcm/PcmMix.hxx b/src/pcm/PcmMix.hxx
index bb7110d04..b50a163fd 100644
--- a/src/pcm/PcmMix.hxx
+++ b/src/pcm/PcmMix.hxx
@@ -20,7 +20,7 @@
 #ifndef MPD_PCM_MIX_HXX
 #define MPD_PCM_MIX_HXX
 
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 #include "gcc.h"
 
 #include <stddef.h>
@@ -44,6 +44,6 @@
 gcc_warn_unused_result
 bool
 pcm_mix(void *buffer1, const void *buffer2, size_t size,
-	enum sample_format format, float portion1);
+	SampleFormat format, float portion1);
 
 #endif
diff --git a/src/pcm/PcmVolume.cxx b/src/pcm/PcmVolume.cxx
index c22017ddd..2a8027400 100644
--- a/src/pcm/PcmVolume.cxx
+++ b/src/pcm/PcmVolume.cxx
@@ -20,7 +20,7 @@
 #include "config.h"
 #include "PcmVolume.hxx"
 #include "PcmUtils.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 
 #include <glib.h>
 
@@ -144,7 +144,7 @@ pcm_volume_change_float(float *buffer, const float *end, float volume)
 
 bool
 pcm_volume(void *buffer, size_t length,
-	   enum sample_format format,
+	   SampleFormat format,
 	   int volume)
 {
 	if (volume == PCM_VOLUME_1)
@@ -157,32 +157,32 @@ pcm_volume(void *buffer, size_t length,
 
 	const void *end = pcm_end_pointer(buffer, length);
 	switch (format) {
-	case SAMPLE_FORMAT_UNDEFINED:
-	case SAMPLE_FORMAT_DSD:
+	case SampleFormat::UNDEFINED:
+	case SampleFormat::DSD:
 		/* not implemented */
 		return false;
 
-	case SAMPLE_FORMAT_S8:
+	case SampleFormat::S8:
 		pcm_volume_change_8((int8_t *)buffer, (const int8_t *)end,
 				    volume);
 		return true;
 
-	case SAMPLE_FORMAT_S16:
+	case SampleFormat::S16:
 		pcm_volume_change_16((int16_t *)buffer, (const int16_t *)end,
 				     volume);
 		return true;
 
-	case SAMPLE_FORMAT_S24_P32:
+	case SampleFormat::S24_P32:
 		pcm_volume_change_24((int32_t *)buffer, (const int32_t *)end,
 				     volume);
 		return true;
 
-	case SAMPLE_FORMAT_S32:
+	case SampleFormat::S32:
 		pcm_volume_change_32((int32_t *)buffer, (const int32_t *)end,
 				     volume);
 		return true;
 
-	case SAMPLE_FORMAT_FLOAT:
+	case SampleFormat::FLOAT:
 		pcm_volume_change_float((float *)buffer, (const float *)end,
 					pcm_volume_to_float(volume));
 		return true;
diff --git a/src/pcm/PcmVolume.hxx b/src/pcm/PcmVolume.hxx
index ed660b22a..8cd82acf7 100644
--- a/src/pcm/PcmVolume.hxx
+++ b/src/pcm/PcmVolume.hxx
@@ -21,7 +21,7 @@
 #define MPD_PCM_VOLUME_HXX
 
 #include "PcmPrng.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 
 #include <stdint.h>
 #include <stddef.h>
@@ -31,7 +31,7 @@ enum {
 	PCM_VOLUME_1 = 1024,
 };
 
-struct audio_format;
+struct AudioFormat;
 
 /**
  * Converts a float value (0.0 = silence, 1.0 = 100% volume) to an
@@ -75,7 +75,7 @@ pcm_volume_dither(void)
  */
 bool
 pcm_volume(void *buffer, size_t length,
-	   enum sample_format format,
+	   SampleFormat format,
 	   int volume);
 
 #endif
diff --git a/test/dump_playlist.cxx b/test/dump_playlist.cxx
index ea341ab95..bf8ddcdfe 100644
--- a/test/dump_playlist.cxx
+++ b/test/dump_playlist.cxx
@@ -51,7 +51,7 @@ my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level,
 
 void
 decoder_initialized(G_GNUC_UNUSED struct decoder *decoder,
-		    G_GNUC_UNUSED const struct audio_format *audio_format,
+		    G_GNUC_UNUSED const AudioFormat audio_format,
 		    G_GNUC_UNUSED bool seekable,
 		    G_GNUC_UNUSED float total_time)
 {
diff --git a/test/read_mixer.cxx b/test/read_mixer.cxx
index 8fc89e93d..1d8372a98 100644
--- a/test/read_mixer.cxx
+++ b/test/read_mixer.cxx
@@ -101,7 +101,7 @@ filter_plugin_by_name(G_GNUC_UNUSED const char *name)
 
 bool
 pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED size_t length,
-	   G_GNUC_UNUSED enum sample_format format,
+	   G_GNUC_UNUSED SampleFormat format,
 	   G_GNUC_UNUSED int volume)
 {
 	assert(false);
diff --git a/test/read_tags.cxx b/test/read_tags.cxx
index ee9464b2c..420cf9f0e 100644
--- a/test/read_tags.cxx
+++ b/test/read_tags.cxx
@@ -23,7 +23,7 @@
 #include "DecoderAPI.hxx"
 #include "InputInit.hxx"
 #include "InputStream.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 #include "TagHandler.hxx"
 #include "TagId3.hxx"
 #include "ApeTag.hxx"
@@ -40,7 +40,7 @@
 
 void
 decoder_initialized(G_GNUC_UNUSED struct decoder *decoder,
-		    G_GNUC_UNUSED const struct audio_format *audio_format,
+		    G_GNUC_UNUSED const AudioFormat audio_format,
 		    G_GNUC_UNUSED bool seekable,
 		    G_GNUC_UNUSED float total_time)
 {
diff --git a/test/run_convert.cxx b/test/run_convert.cxx
index 2b0214912..ce7df42c3 100644
--- a/test/run_convert.cxx
+++ b/test/run_convert.cxx
@@ -25,7 +25,7 @@
 
 #include "config.h"
 #include "AudioParser.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 #include "pcm/PcmConvert.hxx"
 #include "conf.h"
 #include "util/fifo_buffer.h"
@@ -57,7 +57,7 @@ config_get_string(gcc_unused enum ConfigOption option,
 int main(int argc, char **argv)
 {
 	GError *error = NULL;
-	struct audio_format in_audio_format, out_audio_format;
+	AudioFormat in_audio_format, out_audio_format;
 	const void *output;
 	ssize_t nbytes;
 	size_t length;
@@ -69,15 +69,15 @@ int main(int argc, char **argv)
 
 	g_log_set_default_handler(my_log_func, NULL);
 
-	if (!audio_format_parse(&in_audio_format, argv[1],
+	if (!audio_format_parse(in_audio_format, argv[1],
 				false, &error)) {
 		g_printerr("Failed to parse audio format: %s\n",
 			   error->message);
 		return 1;
 	}
 
-	struct audio_format out_audio_format_mask;
-	if (!audio_format_parse(&out_audio_format_mask, argv[2],
+	AudioFormat out_audio_format_mask;
+	if (!audio_format_parse(out_audio_format_mask, argv[2],
 				true, &error)) {
 		g_printerr("Failed to parse audio format: %s\n",
 			   error->message);
@@ -85,9 +85,9 @@ int main(int argc, char **argv)
 	}
 
 	out_audio_format = in_audio_format;
-	audio_format_mask_apply(&out_audio_format, &out_audio_format_mask);
+	out_audio_format.ApplyMask(out_audio_format_mask);
 
-	const size_t in_frame_size = audio_format_frame_size(&in_audio_format);
+	const size_t in_frame_size = in_audio_format.GetFrameSize();
 
 	PcmConvert state;
 
@@ -112,8 +112,8 @@ int main(int argc, char **argv)
 
 		fifo_buffer_consume(buffer, length);
 
-		output = state.Convert(&in_audio_format, src, length,
-				       &out_audio_format, &length, &error);
+		output = state.Convert(in_audio_format, src, length,
+				       out_audio_format, &length, &error);
 		if (output == NULL) {
 			g_printerr("Failed to convert: %s\n", error->message);
 			return 2;
diff --git a/test/run_decoder.cxx b/test/run_decoder.cxx
index a05afb113..a5826b278 100644
--- a/test/run_decoder.cxx
+++ b/test/run_decoder.cxx
@@ -23,7 +23,7 @@
 #include "DecoderAPI.hxx"
 #include "InputInit.hxx"
 #include "input_stream.h"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 #include "stdbin.h"
 
 #include <glib.h>
@@ -52,14 +52,14 @@ struct decoder {
 
 void
 decoder_initialized(struct decoder *decoder,
-		    const struct audio_format *audio_format,
+		    const AudioFormat audio_format,
 		    G_GNUC_UNUSED bool seekable,
 		    G_GNUC_UNUSED float total_time)
 {
 	struct audio_format_string af_string;
 
 	assert(!decoder->initialized);
-	assert(audio_format_valid(audio_format));
+	assert(audio_format.IsValid());
 
 	g_printerr("audio_format=%s\n",
 		   audio_format_to_string(audio_format, &af_string));
diff --git a/test/run_encoder.cxx b/test/run_encoder.cxx
index 1beb434df..4b1521b99 100644
--- a/test/run_encoder.cxx
+++ b/test/run_encoder.cxx
@@ -20,7 +20,7 @@
 #include "config.h"
 #include "EncoderList.hxx"
 #include "EncoderPlugin.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 #include "AudioParser.hxx"
 #include "conf.h"
 #include "stdbin.h"
@@ -44,7 +44,6 @@ encoder_to_stdout(Encoder &encoder)
 int main(int argc, char **argv)
 {
 	GError *error = NULL;
-	struct audio_format audio_format;
 	bool ret;
 	const char *encoder_name;
 	static char buffer[32768];
@@ -61,8 +60,6 @@ int main(int argc, char **argv)
 	else
 		encoder_name = "vorbis";
 
-	audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2);
-
 	/* create the encoder */
 
 	const auto plugin = encoder_plugin_get(encoder_name);
@@ -84,8 +81,9 @@ int main(int argc, char **argv)
 
 	/* open the encoder */
 
+	AudioFormat audio_format(44100, SampleFormat::S16, 2);
 	if (argc > 2) {
-		ret = audio_format_parse(&audio_format, argv[2],
+		ret = audio_format_parse(audio_format, argv[2],
 					 false, &error);
 		if (!ret) {
 			g_printerr("Failed to parse audio format: %s\n",
@@ -95,7 +93,7 @@ int main(int argc, char **argv)
 		}
 	}
 
-	if (!encoder_open(encoder, &audio_format, &error)) {
+	if (!encoder_open(encoder, audio_format, &error)) {
 		g_printerr("Failed to open encoder: %s\n",
 			   error->message);
 		g_error_free(error);
diff --git a/test/run_filter.cxx b/test/run_filter.cxx
index 8df3fa732..db4a2ee89 100644
--- a/test/run_filter.cxx
+++ b/test/run_filter.cxx
@@ -21,7 +21,7 @@
 #include "conf.h"
 #include "fs/Path.hxx"
 #include "AudioParser.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 #include "FilterPlugin.hxx"
 #include "FilterInternal.hxx"
 #include "pcm/PcmVolume.hxx"
@@ -91,11 +91,9 @@ load_filter(const char *name)
 
 int main(int argc, char **argv)
 {
-	struct audio_format audio_format;
 	struct audio_format_string af_string;
 	bool success;
 	GError *error = NULL;
-	const struct audio_format *out_audio_format;
 	char buffer[4096];
 
 	if (argc < 3 || argc > 4) {
@@ -105,7 +103,7 @@ int main(int argc, char **argv)
 
 	const Path config_path = Path::FromFS(argv[1]);
 
-	audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2);
+	AudioFormat audio_format(44100, SampleFormat::S16, 2);
 
 	/* initialize GLib */
 
@@ -127,7 +125,7 @@ int main(int argc, char **argv)
 	/* parse the audio format */
 
 	if (argc > 3) {
-		success = audio_format_parse(&audio_format, argv[3],
+		success = audio_format_parse(audio_format, argv[3],
 					     false, &error);
 		if (!success) {
 			g_printerr("Failed to parse audio format: %s\n",
@@ -145,8 +143,9 @@ int main(int argc, char **argv)
 
 	/* open the filter */
 
-	out_audio_format = filter->Open(audio_format, &error);
-	if (out_audio_format == NULL) {
+	const AudioFormat out_audio_format =
+		filter->Open(audio_format, &error);
+	if (!out_audio_format.IsDefined()) {
 		g_printerr("Failed to open filter: %s\n", error->message);
 		g_error_free(error);
 		delete filter;
diff --git a/test/run_normalize.cxx b/test/run_normalize.cxx
index 070dbaf5a..72e23054f 100644
--- a/test/run_normalize.cxx
+++ b/test/run_normalize.cxx
@@ -26,7 +26,7 @@
 #include "config.h"
 #include "AudioCompress/compress.h"
 #include "AudioParser.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 #include "stdbin.h"
 
 #include <glib.h>
@@ -38,7 +38,6 @@
 int main(int argc, char **argv)
 {
 	GError *error = NULL;
-	struct audio_format audio_format;
 	bool ret;
 	struct Compressor *compressor;
 	static char buffer[4096];
@@ -49,16 +48,16 @@ int main(int argc, char **argv)
 		return 1;
 	}
 
+	AudioFormat audio_format(48000, SampleFormat::S16, 2);
 	if (argc > 1) {
-		ret = audio_format_parse(&audio_format, argv[1],
+		ret = audio_format_parse(audio_format, argv[1],
 					 false, &error);
 		if (!ret) {
 			g_printerr("Failed to parse audio format: %s\n",
 				   error->message);
 			return 1;
 		}
-	} else
-		audio_format_init(&audio_format, 48000, SAMPLE_FORMAT_S16, 2);
+	}
 
 	compressor = Compressor_new(0);
 
diff --git a/test/run_output.cxx b/test/run_output.cxx
index 399d8e731..34879c657 100644
--- a/test/run_output.cxx
+++ b/test/run_output.cxx
@@ -52,9 +52,9 @@ PcmConvert::PcmConvert() {}
 PcmConvert::~PcmConvert() {}
 
 const void *
-PcmConvert::Convert(gcc_unused const audio_format *src_format,
+PcmConvert::Convert(gcc_unused const AudioFormat src_format,
 		    gcc_unused const void *src, gcc_unused size_t src_size,
-		    gcc_unused const audio_format *dest_format,
+		    gcc_unused const AudioFormat dest_format,
 		    gcc_unused size_t *dest_size_r,
 		    gcc_unused GError **error_r)
 {
@@ -114,7 +114,7 @@ load_audio_output(const char *name)
 }
 
 static bool
-run_output(struct audio_output *ao, struct audio_format *audio_format)
+run_output(struct audio_output *ao, AudioFormat audio_format)
 {
 	/* open the audio output */
 
@@ -138,7 +138,7 @@ run_output(struct audio_output *ao, struct audio_format *audio_format)
 	g_printerr("audio_format=%s\n",
 		   audio_format_to_string(audio_format, &af_string));
 
-	size_t frame_size = audio_format_frame_size(audio_format);
+	size_t frame_size = audio_format.GetFrameSize();
 
 	/* play */
 
@@ -183,7 +183,6 @@ run_output(struct audio_output *ao, struct audio_format *audio_format)
 
 int main(int argc, char **argv)
 {
-	struct audio_format audio_format;
 	bool success;
 	GError *error = NULL;
 
@@ -194,7 +193,7 @@ int main(int argc, char **argv)
 
 	const Path config_path = Path::FromFS(argv[1]);
 
-	audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2);
+	AudioFormat audio_format(44100, SampleFormat::S16, 2);
 
 #if !GLIB_CHECK_VERSION(2,32,0)
 	g_thread_init(NULL);
@@ -227,7 +226,7 @@ int main(int argc, char **argv)
 	/* parse the audio format */
 
 	if (argc > 3) {
-		success = audio_format_parse(&audio_format, argv[3],
+		success = audio_format_parse(audio_format, argv[3],
 					     false, &error);
 		if (!success) {
 			g_printerr("Failed to parse audio format: %s\n",
@@ -239,7 +238,7 @@ int main(int argc, char **argv)
 
 	/* do it */
 
-	success = run_output(ao, &audio_format);
+	success = run_output(ao, audio_format);
 
 	/* cleanup and exit */
 
diff --git a/test/software_volume.cxx b/test/software_volume.cxx
index fee4eeff3..c46804731 100644
--- a/test/software_volume.cxx
+++ b/test/software_volume.cxx
@@ -26,7 +26,7 @@
 #include "config.h"
 #include "pcm/PcmVolume.hxx"
 #include "AudioParser.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 #include "stdbin.h"
 
 #include <glib.h>
@@ -37,7 +37,6 @@
 int main(int argc, char **argv)
 {
 	GError *error = NULL;
-	struct audio_format audio_format;
 	bool ret;
 	static char buffer[4096];
 	ssize_t nbytes;
@@ -47,20 +46,20 @@ int main(int argc, char **argv)
 		return 1;
 	}
 
+	AudioFormat audio_format(48000, SampleFormat::S16, 2);
 	if (argc > 1) {
-		ret = audio_format_parse(&audio_format, argv[1],
+		ret = audio_format_parse(audio_format, argv[1],
 					 false, &error);
 		if (!ret) {
 			g_printerr("Failed to parse audio format: %s\n",
 				   error->message);
 			return 1;
 		}
-	} else
-		audio_format_init(&audio_format, 48000, SAMPLE_FORMAT_S16, 2);
+	}
 
 	while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) {
 		if (!pcm_volume(buffer, nbytes,
-				sample_format(audio_format.format),
+				audio_format.format,
 				PCM_VOLUME_1 / 2)) {
 			g_printerr("pcm_volume() has failed\n");
 			return 2;
diff --git a/test/test_pcm_format.cxx b/test/test_pcm_format.cxx
index d7a0a2698..65d744671 100644
--- a/test/test_pcm_format.cxx
+++ b/test/test_pcm_format.cxx
@@ -24,7 +24,7 @@
 #include "pcm/PcmDither.hxx"
 #include "pcm/PcmUtils.hxx"
 #include "pcm/PcmBuffer.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 
 #include <glib.h>
 
@@ -38,7 +38,7 @@ test_pcm_format_8_to_16()
 
 	size_t d_size;
 	PcmDither dither;
-	auto d = pcm_convert_to_16(buffer, dither, SAMPLE_FORMAT_S8,
+	auto d = pcm_convert_to_16(buffer, dither, SampleFormat::S8,
 				   src, sizeof(src), &d_size);
 	auto d_end = pcm_end_pointer(d, d_size);
 	g_assert_cmpint(d_end - d, ==, N);
@@ -56,7 +56,7 @@ test_pcm_format_16_to_24()
 	PcmBuffer buffer;
 
 	size_t d_size;
-	auto d = pcm_convert_to_24(buffer, SAMPLE_FORMAT_S16,
+	auto d = pcm_convert_to_24(buffer, SampleFormat::S16,
 				   src, sizeof(src), &d_size);
 	auto d_end = pcm_end_pointer(d, d_size);
 	g_assert_cmpint(d_end - d, ==, N);
@@ -74,7 +74,7 @@ test_pcm_format_16_to_32()
 	PcmBuffer buffer;
 
 	size_t d_size;
-	auto d = pcm_convert_to_32(buffer, SAMPLE_FORMAT_S16,
+	auto d = pcm_convert_to_32(buffer, SampleFormat::S16,
 				   src, sizeof(src), &d_size);
 	auto d_end = pcm_end_pointer(d, d_size);
 	g_assert_cmpint(d_end - d, ==, N);
@@ -92,7 +92,7 @@ test_pcm_format_float()
 	PcmBuffer buffer1, buffer2;
 
 	size_t f_size;
-	auto f = pcm_convert_to_float(buffer1, SAMPLE_FORMAT_S16,
+	auto f = pcm_convert_to_float(buffer1, SampleFormat::S16,
 				      src, sizeof(src), &f_size);
 	auto f_end = pcm_end_pointer(f, f_size);
 	g_assert_cmpint(f_end - f, ==, N);
@@ -106,7 +106,7 @@ test_pcm_format_float()
 
 	size_t d_size;
 	auto d = pcm_convert_to_16(buffer2, dither,
-				   SAMPLE_FORMAT_FLOAT,
+				   SampleFormat::FLOAT,
 				   f, f_size, &d_size);
 	auto d_end = pcm_end_pointer(d, d_size);
 	g_assert_cmpint(d_end - d, ==, N);
diff --git a/test/test_pcm_mix.cxx b/test/test_pcm_mix.cxx
index b4d8486bf..b0e89639c 100644
--- a/test/test_pcm_mix.cxx
+++ b/test/test_pcm_mix.cxx
@@ -24,7 +24,7 @@
 
 #include <glib.h>
 
-template<typename T, sample_format format, typename G=GlibRandomInt<T>>
+template<typename T, SampleFormat format, typename G=GlibRandomInt<T>>
 void
 TestPcmMix(G g=G())
 {
@@ -62,23 +62,23 @@ TestPcmMix(G g=G())
 void
 test_pcm_mix_8()
 {
-	TestPcmMix<int8_t, SAMPLE_FORMAT_S8>();
+	TestPcmMix<int8_t, SampleFormat::S8>();
 }
 
 void
 test_pcm_mix_16()
 {
-	TestPcmMix<int16_t, SAMPLE_FORMAT_S16>();
+	TestPcmMix<int16_t, SampleFormat::S16>();
 }
 
 void
 test_pcm_mix_24()
 {
-	TestPcmMix<int32_t, SAMPLE_FORMAT_S24_P32>(GlibRandomInt24());
+	TestPcmMix<int32_t, SampleFormat::S24_P32>(GlibRandomInt24());
 }
 
 void
 test_pcm_mix_32()
 {
-	TestPcmMix<int32_t, SAMPLE_FORMAT_S32>();
+	TestPcmMix<int32_t, SampleFormat::S32>();
 }
diff --git a/test/test_pcm_volume.cxx b/test/test_pcm_volume.cxx
index ec3ac23b9..d5aa3782e 100644
--- a/test/test_pcm_volume.cxx
+++ b/test/test_pcm_volume.cxx
@@ -37,17 +37,17 @@ test_pcm_volume_8()
 	int8_t dest[N];
 
 	std::copy(src.begin(), src.end(), dest);
-	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S8,
+	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S8,
 				   0), ==, true);
 	g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0);
 
 	std::copy(src.begin(), src.end(), dest);
-	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S8,
+	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S8,
 				   PCM_VOLUME_1), ==, true);
 	g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0);
 
 	std::copy(src.begin(), src.end(), dest);
-	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S8,
+	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S8,
 				   PCM_VOLUME_1 / 2), ==, true);
 
 	for (unsigned i = 0; i < N; ++i) {
@@ -66,17 +66,17 @@ test_pcm_volume_16()
 	int16_t dest[N];
 
 	std::copy(src.begin(), src.end(), dest);
-	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S16,
+	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S16,
 				   0), ==, true);
 	g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0);
 
 	std::copy(src.begin(), src.end(), dest);
-	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S16,
+	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S16,
 				   PCM_VOLUME_1), ==, true);
 	g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0);
 
 	std::copy(src.begin(), src.end(), dest);
-	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S16,
+	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S16,
 				   PCM_VOLUME_1 / 2), ==, true);
 
 	for (unsigned i = 0; i < N; ++i) {
@@ -95,17 +95,17 @@ test_pcm_volume_24()
 	int32_t dest[N];
 
 	std::copy(src.begin(), src.end(), dest);
-	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S24_P32,
+	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S24_P32,
 				   0), ==, true);
 	g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0);
 
 	std::copy(src.begin(), src.end(), dest);
-	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S24_P32,
+	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S24_P32,
 				   PCM_VOLUME_1), ==, true);
 	g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0);
 
 	std::copy(src.begin(), src.end(), dest);
-	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S24_P32,
+	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S24_P32,
 				   PCM_VOLUME_1 / 2), ==, true);
 
 	for (unsigned i = 0; i < N; ++i) {
@@ -124,17 +124,17 @@ test_pcm_volume_32()
 	int32_t dest[N];
 
 	std::copy(src.begin(), src.end(), dest);
-	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S32,
+	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S32,
 				   0), ==, true);
 	g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0);
 
 	std::copy(src.begin(), src.end(), dest);
-	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S32,
+	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S32,
 				   PCM_VOLUME_1), ==, true);
 	g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0);
 
 	std::copy(src.begin(), src.end(), dest);
-	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_S32,
+	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::S32,
 				   PCM_VOLUME_1 / 2), ==, true);
 
 	for (unsigned i = 0; i < N; ++i) {
@@ -153,17 +153,17 @@ test_pcm_volume_float()
 	float dest[N];
 
 	std::copy(src.begin(), src.end(), dest);
-	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_FLOAT,
+	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::FLOAT,
 				   0), ==, true);
 	g_assert_cmpint(memcmp(dest, zero, sizeof(zero)), ==, 0);
 
 	std::copy(src.begin(), src.end(), dest);
-	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_FLOAT,
+	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::FLOAT,
 				   PCM_VOLUME_1), ==, true);
 	g_assert_cmpint(memcmp(dest, src, sizeof(src)), ==, 0);
 
 	std::copy(src.begin(), src.end(), dest);
-	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SAMPLE_FORMAT_FLOAT,
+	g_assert_cmpint(pcm_volume(dest, sizeof(dest), SampleFormat::FLOAT,
 				   PCM_VOLUME_1 / 2), ==, true);
 
 	for (unsigned i = 0; i < N; ++i)
diff --git a/test/test_vorbis_encoder.cxx b/test/test_vorbis_encoder.cxx
index 650480319..a523f9d72 100644
--- a/test/test_vorbis_encoder.cxx
+++ b/test/test_vorbis_encoder.cxx
@@ -20,7 +20,7 @@
 #include "config.h"
 #include "EncoderList.hxx"
 #include "EncoderPlugin.hxx"
-#include "audio_format.h"
+#include "AudioFormat.hxx"
 #include "conf.h"
 #include "stdbin.h"
 #include "Tag.hxx"
@@ -61,10 +61,8 @@ main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv)
 
 	/* open the encoder */
 
-	struct audio_format audio_format;
-
-	audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2);
-	success = encoder_open(encoder, &audio_format, NULL);
+	AudioFormat audio_format(44100, SampleFormat::S16, 2);
+	success = encoder_open(encoder, audio_format, NULL);
 	assert(success);
 
 	encoder_to_stdout(*encoder);