git: 27ef5d48c729 - main - sound: Unit test the pcm sample read and write macros

From: Christos Margiolis <christos_at_FreeBSD.org>
Date: Tue, 21 Jan 2025 12:03:56 UTC
The branch main has been updated by christos:

URL: https://cgit.FreeBSD.org/src/commit/?id=27ef5d48c729defb83a8822143dc71ab17f9d68b

commit 27ef5d48c729defb83a8822143dc71ab17f9d68b
Author:     Florian Walpen <dev@submerge.ch>
AuthorDate: 2025-01-21 11:58:05 +0000
Commit:     Christos Margiolis <christos@FreeBSD.org>
CommitDate: 2025-01-21 12:00:36 +0000

    sound: Unit test the pcm sample read and write macros
    
    Main goal is to have a unit test, with sample test data that is verified
    against the current macro implementation of pcm sample read and write
    functions. With a test in place, we can proceed on a planned refactoring
    of the sample read and write code, and confidently check the new code
    for regressions.
    
    Implementation of the unit test itself has to avoid any cast or
    conversion affected by endianness, to make the tests compatible with all
    machine architectures.
    
    MFC after:      1 week
    Reviewed by:    christos, markj
    Differential Revision:  https://reviews.freebsd.org/D48330
---
 sys/dev/sound/pcm/sound.h        | 128 +++++-----
 tests/sys/sound/Makefile         |   4 +
 tests/sys/sound/pcm_read_write.c | 509 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 577 insertions(+), 64 deletions(-)

diff --git a/sys/dev/sound/pcm/sound.h b/sys/dev/sound/pcm/sound.h
index fe04898c17a5..019fe67ac892 100644
--- a/sys/dev/sound/pcm/sound.h
+++ b/sys/dev/sound/pcm/sound.h
@@ -147,70 +147,6 @@ struct snd_mixer;
 #define RANGE(var, low, high) (var) = \
 	(((var)<(low))? (low) : ((var)>(high))? (high) : (var))
 
-/* make figuring out what a format is easier. got AFMT_STEREO already */
-#define AFMT_32BIT (AFMT_S32_LE | AFMT_S32_BE | AFMT_U32_LE | AFMT_U32_BE)
-#define AFMT_24BIT (AFMT_S24_LE | AFMT_S24_BE | AFMT_U24_LE | AFMT_U24_BE)
-#define AFMT_16BIT (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE)
-#define AFMT_G711  (AFMT_MU_LAW | AFMT_A_LAW)
-#define AFMT_8BIT (AFMT_G711 | AFMT_U8 | AFMT_S8)
-#define AFMT_SIGNED (AFMT_S32_LE | AFMT_S32_BE | AFMT_S24_LE | AFMT_S24_BE | \
-			AFMT_S16_LE | AFMT_S16_BE | AFMT_S8)
-#define AFMT_BIGENDIAN (AFMT_S32_BE | AFMT_U32_BE | AFMT_S24_BE | AFMT_U24_BE | \
-			AFMT_S16_BE | AFMT_U16_BE)
-
-#define AFMT_CONVERTIBLE	(AFMT_8BIT | AFMT_16BIT | AFMT_24BIT |	\
-				 AFMT_32BIT)
-
-/* Supported vchan mixing formats */
-#define AFMT_VCHAN		(AFMT_CONVERTIBLE & ~AFMT_G711)
-
-#define AFMT_PASSTHROUGH		AFMT_AC3
-#define AFMT_PASSTHROUGH_RATE		48000
-#define AFMT_PASSTHROUGH_CHANNEL	2
-#define AFMT_PASSTHROUGH_EXTCHANNEL	0
-
-/*
- * We're simply using unused, contiguous bits from various AFMT_ definitions.
- * ~(0xb00ff7ff)
- */
-#define AFMT_ENCODING_MASK	0xf00fffff
-#define AFMT_CHANNEL_MASK	0x07f00000
-#define AFMT_CHANNEL_SHIFT	20
-#define AFMT_CHANNEL_MAX	0x7f
-#define AFMT_EXTCHANNEL_MASK	0x08000000
-#define AFMT_EXTCHANNEL_SHIFT	27
-#define AFMT_EXTCHANNEL_MAX	1
-
-#define AFMT_ENCODING(v)	((v) & AFMT_ENCODING_MASK)
-
-#define AFMT_EXTCHANNEL(v)	(((v) & AFMT_EXTCHANNEL_MASK) >>	\
-				AFMT_EXTCHANNEL_SHIFT)
-
-#define AFMT_CHANNEL(v)		(((v) & AFMT_CHANNEL_MASK) >>		\
-				AFMT_CHANNEL_SHIFT)
-
-#define AFMT_BIT(v)		(((v) & AFMT_32BIT) ? 32 :		\
-				(((v) & AFMT_24BIT) ? 24 :		\
-				((((v) & AFMT_16BIT) ||			\
-				((v) & AFMT_PASSTHROUGH)) ? 16 : 8)))
-
-#define AFMT_BPS(v)		(AFMT_BIT(v) >> 3)
-#define AFMT_ALIGN(v)		(AFMT_BPS(v) * AFMT_CHANNEL(v))
-
-#define SND_FORMAT(f, c, e)	(AFMT_ENCODING(f) |		\
-				(((c) << AFMT_CHANNEL_SHIFT) &		\
-				AFMT_CHANNEL_MASK) |			\
-				(((e) << AFMT_EXTCHANNEL_SHIFT) &	\
-				AFMT_EXTCHANNEL_MASK))
-
-#define AFMT_U8_NE	AFMT_U8
-#define AFMT_S8_NE	AFMT_S8
-
-#define AFMT_SIGNED_NE	(AFMT_S8_NE | AFMT_S16_NE | AFMT_S24_NE | AFMT_S32_NE)
-
-#define AFMT_NE		(AFMT_SIGNED_NE | AFMT_U8_NE | AFMT_U16_NE |	\
-			 AFMT_U24_NE | AFMT_U32_NE)
-
 enum {
 	SND_DEV_CTL = 0,	/* Control port /dev/mixer */
 	SND_DEV_SEQ,		/* Sequencer /dev/sequencer */
@@ -508,4 +444,68 @@ int	sound_oss_card_info(oss_card_info *);
 
 #endif /* _KERNEL */
 
+/* make figuring out what a format is easier. got AFMT_STEREO already */
+#define AFMT_32BIT (AFMT_S32_LE | AFMT_S32_BE | AFMT_U32_LE | AFMT_U32_BE)
+#define AFMT_24BIT (AFMT_S24_LE | AFMT_S24_BE | AFMT_U24_LE | AFMT_U24_BE)
+#define AFMT_16BIT (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE)
+#define AFMT_G711  (AFMT_MU_LAW | AFMT_A_LAW)
+#define AFMT_8BIT (AFMT_G711 | AFMT_U8 | AFMT_S8)
+#define AFMT_SIGNED (AFMT_S32_LE | AFMT_S32_BE | AFMT_S24_LE | AFMT_S24_BE | \
+			AFMT_S16_LE | AFMT_S16_BE | AFMT_S8)
+#define AFMT_BIGENDIAN (AFMT_S32_BE | AFMT_U32_BE | AFMT_S24_BE | AFMT_U24_BE | \
+			AFMT_S16_BE | AFMT_U16_BE)
+
+#define AFMT_CONVERTIBLE	(AFMT_8BIT | AFMT_16BIT | AFMT_24BIT |	\
+				 AFMT_32BIT)
+
+/* Supported vchan mixing formats */
+#define AFMT_VCHAN		(AFMT_CONVERTIBLE & ~AFMT_G711)
+
+#define AFMT_PASSTHROUGH		AFMT_AC3
+#define AFMT_PASSTHROUGH_RATE		48000
+#define AFMT_PASSTHROUGH_CHANNEL	2
+#define AFMT_PASSTHROUGH_EXTCHANNEL	0
+
+/*
+ * We're simply using unused, contiguous bits from various AFMT_ definitions.
+ * ~(0xb00ff7ff)
+ */
+#define AFMT_ENCODING_MASK	0xf00fffff
+#define AFMT_CHANNEL_MASK	0x07f00000
+#define AFMT_CHANNEL_SHIFT	20
+#define AFMT_CHANNEL_MAX	0x7f
+#define AFMT_EXTCHANNEL_MASK	0x08000000
+#define AFMT_EXTCHANNEL_SHIFT	27
+#define AFMT_EXTCHANNEL_MAX	1
+
+#define AFMT_ENCODING(v)	((v) & AFMT_ENCODING_MASK)
+
+#define AFMT_EXTCHANNEL(v)	(((v) & AFMT_EXTCHANNEL_MASK) >>	\
+				AFMT_EXTCHANNEL_SHIFT)
+
+#define AFMT_CHANNEL(v)		(((v) & AFMT_CHANNEL_MASK) >>		\
+				AFMT_CHANNEL_SHIFT)
+
+#define AFMT_BIT(v)		(((v) & AFMT_32BIT) ? 32 :		\
+				(((v) & AFMT_24BIT) ? 24 :		\
+				((((v) & AFMT_16BIT) ||			\
+				((v) & AFMT_PASSTHROUGH)) ? 16 : 8)))
+
+#define AFMT_BPS(v)		(AFMT_BIT(v) >> 3)
+#define AFMT_ALIGN(v)		(AFMT_BPS(v) * AFMT_CHANNEL(v))
+
+#define SND_FORMAT(f, c, e)	(AFMT_ENCODING(f) |		\
+				(((c) << AFMT_CHANNEL_SHIFT) &		\
+				AFMT_CHANNEL_MASK) |			\
+				(((e) << AFMT_EXTCHANNEL_SHIFT) &	\
+				AFMT_EXTCHANNEL_MASK))
+
+#define AFMT_U8_NE	AFMT_U8
+#define AFMT_S8_NE	AFMT_S8
+
+#define AFMT_SIGNED_NE	(AFMT_S8_NE | AFMT_S16_NE | AFMT_S24_NE | AFMT_S32_NE)
+
+#define AFMT_NE		(AFMT_SIGNED_NE | AFMT_U8_NE | AFMT_U16_NE |	\
+			 AFMT_U24_NE | AFMT_U32_NE)
+
 #endif	/* _OS_H_ */
diff --git a/tests/sys/sound/Makefile b/tests/sys/sound/Makefile
index fb731fb8ab61..74a0765a0540 100644
--- a/tests/sys/sound/Makefile
+++ b/tests/sys/sound/Makefile
@@ -2,8 +2,12 @@ PACKAGE=	tests
 
 TESTSDIR=	${TESTSBASE}/sys/sound
 
+ATF_TESTS_C+=	pcm_read_write
 ATF_TESTS_C+=	sndstat
 
+CFLAGS+=	-I${SRCTOP}/sys
 LDFLAGS+=	-lnv
 
+CFLAGS.pcm_read_write.c+=	-Wno-cast-align
+
 .include <bsd.test.mk>
diff --git a/tests/sys/sound/pcm_read_write.c b/tests/sys/sound/pcm_read_write.c
new file mode 100644
index 000000000000..6bdc578615b5
--- /dev/null
+++ b/tests/sys/sound/pcm_read_write.c
@@ -0,0 +1,509 @@
+/*-
+ * Copyright (c) 2025 Florian Walpen
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/*
+ * These tests exercise conversion functions of the sound module, used to read
+ * pcm samples from a buffer, and write pcm samples to a buffer. The test cases
+ * are non-exhaustive, but should detect systematic errors in conversion of the
+ * various sample formats supported. In particular, the test cases establish
+ * correctness independent of the machine's endianness, making them suitable to
+ * check for architecture-specific problems.
+ */
+
+#include <sys/types.h>
+#include <sys/soundcard.h>
+
+#include <atf-c.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <dev/sound/pcm/sound.h>
+#include <dev/sound/pcm/pcm.h>
+#include <dev/sound/pcm/g711.h>
+
+/* Generic test data, with buffer content matching the sample values. */
+struct afmt_test_data {
+	const char *label;
+	uint8_t buffer[4];
+	size_t size;
+	int format;
+	intpcm_t value;
+	_Static_assert((sizeof(intpcm_t) == 4),
+	    "Test data assumes 32bit, adjust negative values to new size.");
+} static const afmt_tests[] = {
+	/* 8 bit sample formats. */
+	{"s8_1", {0x01, 0x00, 0x00, 0x00}, 1, AFMT_S8, 0x00000001},
+	{"s8_2", {0x81, 0x00, 0x00, 0x00}, 1, AFMT_S8, 0xffffff81},
+	{"u8_1", {0x01, 0x00, 0x00, 0x00}, 1, AFMT_U8, 0xffffff81},
+	{"u8_2", {0x81, 0x00, 0x00, 0x00}, 1, AFMT_U8, 0x00000001},
+
+	/* 16 bit sample formats. */
+	{"s16le_1", {0x01, 0x02, 0x00, 0x00}, 2, AFMT_S16_LE, 0x00000201},
+	{"s16le_2", {0x81, 0x82, 0x00, 0x00}, 2, AFMT_S16_LE, 0xffff8281},
+	{"s16be_1", {0x01, 0x02, 0x00, 0x00}, 2, AFMT_S16_BE, 0x00000102},
+	{"s16be_2", {0x81, 0x82, 0x00, 0x00}, 2, AFMT_S16_BE, 0xffff8182},
+	{"u16le_1", {0x01, 0x02, 0x00, 0x00}, 2, AFMT_U16_LE, 0xffff8201},
+	{"u16le_2", {0x81, 0x82, 0x00, 0x00}, 2, AFMT_U16_LE, 0x00000281},
+	{"u16be_1", {0x01, 0x02, 0x00, 0x00}, 2, AFMT_U16_BE, 0xffff8102},
+	{"u16be_2", {0x81, 0x82, 0x00, 0x00}, 2, AFMT_U16_BE, 0x00000182},
+
+	/* 24 bit sample formats. */
+	{"s24le_1", {0x01, 0x02, 0x03, 0x00}, 3, AFMT_S24_LE, 0x00030201},
+	{"s24le_2", {0x81, 0x82, 0x83, 0x00}, 3, AFMT_S24_LE, 0xff838281},
+	{"s24be_1", {0x01, 0x02, 0x03, 0x00}, 3, AFMT_S24_BE, 0x00010203},
+	{"s24be_2", {0x81, 0x82, 0x83, 0x00}, 3, AFMT_S24_BE, 0xff818283},
+	{"u24le_1", {0x01, 0x02, 0x03, 0x00}, 3, AFMT_U24_LE, 0xff830201},
+	{"u24le_2", {0x81, 0x82, 0x83, 0x00}, 3, AFMT_U24_LE, 0x00038281},
+	{"u24be_1", {0x01, 0x02, 0x03, 0x00}, 3, AFMT_U24_BE, 0xff810203},
+	{"u24be_2", {0x81, 0x82, 0x83, 0x00}, 3, AFMT_U24_BE, 0x00018283},
+
+	/* 32 bit sample formats. */
+	{"s32le_1", {0x01, 0x02, 0x03, 0x04}, 4, AFMT_S32_LE, 0x04030201},
+	{"s32le_2", {0x81, 0x82, 0x83, 0x84}, 4, AFMT_S32_LE, 0x84838281},
+	{"s32be_1", {0x01, 0x02, 0x03, 0x04}, 4, AFMT_S32_BE, 0x01020304},
+	{"s32be_2", {0x81, 0x82, 0x83, 0x84}, 4, AFMT_S32_BE, 0x81828384},
+	{"u32le_1", {0x01, 0x02, 0x03, 0x04}, 4, AFMT_U32_LE, 0x84030201},
+	{"u32le_2", {0x81, 0x82, 0x83, 0x84}, 4, AFMT_U32_LE, 0x04838281},
+	{"u32be_1", {0x01, 0x02, 0x03, 0x04}, 4, AFMT_U32_BE, 0x81020304},
+	{"u32be_2", {0x81, 0x82, 0x83, 0x84}, 4, AFMT_U32_BE, 0x01828384},
+
+	/* u-law and A-law sample formats. */
+	{"mulaw_1", {0x01, 0x00, 0x00, 0x00}, 1, AFMT_MU_LAW, 0xffffff87},
+	{"mulaw_2", {0x81, 0x00, 0x00, 0x00}, 1, AFMT_MU_LAW, 0x00000079},
+	{"alaw_1", {0x2a, 0x00, 0x00, 0x00}, 1, AFMT_A_LAW, 0xffffff83},
+	{"alaw_2", {0xab, 0x00, 0x00, 0x00}, 1, AFMT_A_LAW, 0x00000079}
+};
+
+/* Normalize sample values in strictly correct (but slow) c. */
+static intpcm_t
+local_normalize(intpcm_t value, int val_bits, int norm_bits)
+{
+	/* Avoid undefined or implementation defined behavior. */
+	if (val_bits < norm_bits)
+		/* Multiply instead of left shift (value may be negative). */
+		return (value * (1 << (norm_bits - val_bits)));
+	else if (val_bits > norm_bits)
+		/* Divide instead of right shift (value may be negative). */
+		return (value / (1 << (val_bits - norm_bits)));
+	return value;
+}
+
+/* Restrict magnitude of sample value to 24bit for 32bit calculations. */
+static intpcm_t
+local_calc_limit(intpcm_t value, int val_bits)
+{
+	/* Avoid implementation defined behavior. */
+	if (sizeof(intpcm32_t) == 32 && val_bits == 32)
+		/* Divide instead of right shift (value may be negative). */
+		return (value / (1 << 8));
+	return value;
+}
+
+/* Lookup tables to read u-law and A-law sample formats. */
+static const uint8_t ulaw_to_u8[G711_TABLE_SIZE] = ULAW_TO_U8;
+static const uint8_t alaw_to_u8[G711_TABLE_SIZE] = ALAW_TO_U8;
+
+/* Helper function to read one sample value from a buffer. */
+static intpcm_t
+local_pcm_read(uint8_t *src, uint32_t format)
+{
+	intpcm_t value;
+
+	switch (format) {
+	case AFMT_S8:
+		value = _PCM_READ_S8_NE(src);
+		break;
+	case AFMT_U8:
+		value = _PCM_READ_U8_NE(src);
+		break;
+	case AFMT_S16_LE:
+		value = _PCM_READ_S16_LE(src);
+		break;
+	case AFMT_S16_BE:
+		value = _PCM_READ_S16_BE(src);
+		break;
+	case AFMT_U16_LE:
+		value = _PCM_READ_U16_LE(src);
+		break;
+	case AFMT_U16_BE:
+		value = _PCM_READ_U16_BE(src);
+		break;
+	case AFMT_S24_LE:
+		value = _PCM_READ_S24_LE(src);
+		break;
+	case AFMT_S24_BE:
+		value = _PCM_READ_S24_BE(src);
+		break;
+	case AFMT_U24_LE:
+		value = _PCM_READ_U24_LE(src);
+		break;
+	case AFMT_U24_BE:
+		value = _PCM_READ_U24_BE(src);
+		break;
+	case AFMT_S32_LE:
+		value = _PCM_READ_S32_LE(src);
+		break;
+	case AFMT_S32_BE:
+		value = _PCM_READ_S32_BE(src);
+		break;
+	case AFMT_U32_LE:
+		value = _PCM_READ_U32_LE(src);
+		break;
+	case AFMT_U32_BE:
+		value = _PCM_READ_U32_BE(src);
+		break;
+	case AFMT_MU_LAW:
+		value = _G711_TO_INTPCM(ulaw_to_u8, *src);
+		break;
+	case AFMT_A_LAW:
+		value = _G711_TO_INTPCM(alaw_to_u8, *src);
+		break;
+	default:
+		value = 0;
+	}
+
+	return (value);
+}
+
+/* Helper function to read one sample value from a buffer for calculations. */
+static intpcm_t
+local_pcm_read_calc(uint8_t *src, uint32_t format)
+{
+	intpcm_t value;
+
+	switch (format) {
+	case AFMT_S8:
+		value = PCM_READ_S8_NE(src);
+		break;
+	case AFMT_U8:
+		value = PCM_READ_U8_NE(src);
+		break;
+	case AFMT_S16_LE:
+		value = PCM_READ_S16_LE(src);
+		break;
+	case AFMT_S16_BE:
+		value = PCM_READ_S16_BE(src);
+		break;
+	case AFMT_U16_LE:
+		value = PCM_READ_U16_LE(src);
+		break;
+	case AFMT_U16_BE:
+		value = PCM_READ_U16_BE(src);
+		break;
+	case AFMT_S24_LE:
+		value = PCM_READ_S24_LE(src);
+		break;
+	case AFMT_S24_BE:
+		value = PCM_READ_S24_BE(src);
+		break;
+	case AFMT_U24_LE:
+		value = PCM_READ_U24_LE(src);
+		break;
+	case AFMT_U24_BE:
+		value = PCM_READ_U24_BE(src);
+		break;
+	case AFMT_S32_LE:
+		value = PCM_READ_S32_LE(src);
+		break;
+	case AFMT_S32_BE:
+		value = PCM_READ_S32_BE(src);
+		break;
+	case AFMT_U32_LE:
+		value = PCM_READ_U32_LE(src);
+		break;
+	case AFMT_U32_BE:
+		value = PCM_READ_U32_BE(src);
+		break;
+	case AFMT_MU_LAW:
+		value = _G711_TO_INTPCM(ulaw_to_u8, *src);
+		break;
+	case AFMT_A_LAW:
+		value = _G711_TO_INTPCM(alaw_to_u8, *src);
+		break;
+	default:
+		value = 0;
+	}
+
+	return (value);
+}
+
+/* Helper function to read one normalized sample from a buffer. */
+static intpcm_t
+local_pcm_read_norm(uint8_t *src, uint32_t format)
+{
+	intpcm_t value;
+
+	value = local_pcm_read(src, format);
+	value <<= (32 - AFMT_BIT(format));
+	return (value);
+}
+
+/* Lookup tables to write u-law and A-law sample formats. */
+static const uint8_t u8_to_ulaw[G711_TABLE_SIZE] = U8_TO_ULAW;
+static const uint8_t u8_to_alaw[G711_TABLE_SIZE] = U8_TO_ALAW;
+
+/* Helper function to write one sample value to a buffer. */
+static void
+local_pcm_write(uint8_t *dst, intpcm_t value, uint32_t format)
+{
+	switch (format) {
+	case AFMT_S8:
+		_PCM_WRITE_S8_NE(dst, value);
+		break;
+	case AFMT_U8:
+		_PCM_WRITE_U8_NE(dst, value);
+		break;
+	case AFMT_S16_LE:
+		_PCM_WRITE_S16_LE(dst, value);
+		break;
+	case AFMT_S16_BE:
+		_PCM_WRITE_S16_BE(dst, value);
+		break;
+	case AFMT_U16_LE:
+		_PCM_WRITE_U16_LE(dst, value);
+		break;
+	case AFMT_U16_BE:
+		_PCM_WRITE_U16_BE(dst, value);
+		break;
+	case AFMT_S24_LE:
+		_PCM_WRITE_S24_LE(dst, value);
+		break;
+	case AFMT_S24_BE:
+		_PCM_WRITE_S24_BE(dst, value);
+		break;
+	case AFMT_U24_LE:
+		_PCM_WRITE_U24_LE(dst, value);
+		break;
+	case AFMT_U24_BE:
+		_PCM_WRITE_U24_BE(dst, value);
+		break;
+	case AFMT_S32_LE:
+		_PCM_WRITE_S32_LE(dst, value);
+		break;
+	case AFMT_S32_BE:
+		_PCM_WRITE_S32_BE(dst, value);
+		break;
+	case AFMT_U32_LE:
+		_PCM_WRITE_U32_LE(dst, value);
+		break;
+	case AFMT_U32_BE:
+		_PCM_WRITE_U32_BE(dst, value);
+		break;
+	case AFMT_MU_LAW:
+		*dst = _INTPCM_TO_G711(u8_to_ulaw, value);
+		break;
+	case AFMT_A_LAW:
+		*dst = _INTPCM_TO_G711(u8_to_alaw, value);
+		break;
+	default:
+		value = 0;
+	}
+}
+
+/* Helper function to write one calculation sample value to a buffer. */
+static void
+local_pcm_write_calc(uint8_t *dst, intpcm_t value, uint32_t format)
+{
+	switch (format) {
+	case AFMT_S8:
+		PCM_WRITE_S8_NE(dst, value);
+		break;
+	case AFMT_U8:
+		PCM_WRITE_U8_NE(dst, value);
+		break;
+	case AFMT_S16_LE:
+		PCM_WRITE_S16_LE(dst, value);
+		break;
+	case AFMT_S16_BE:
+		PCM_WRITE_S16_BE(dst, value);
+		break;
+	case AFMT_U16_LE:
+		PCM_WRITE_U16_LE(dst, value);
+		break;
+	case AFMT_U16_BE:
+		PCM_WRITE_U16_BE(dst, value);
+		break;
+	case AFMT_S24_LE:
+		PCM_WRITE_S24_LE(dst, value);
+		break;
+	case AFMT_S24_BE:
+		PCM_WRITE_S24_BE(dst, value);
+		break;
+	case AFMT_U24_LE:
+		PCM_WRITE_U24_LE(dst, value);
+		break;
+	case AFMT_U24_BE:
+		PCM_WRITE_U24_BE(dst, value);
+		break;
+	case AFMT_S32_LE:
+		PCM_WRITE_S32_LE(dst, value);
+		break;
+	case AFMT_S32_BE:
+		PCM_WRITE_S32_BE(dst, value);
+		break;
+	case AFMT_U32_LE:
+		PCM_WRITE_U32_LE(dst, value);
+		break;
+	case AFMT_U32_BE:
+		PCM_WRITE_U32_BE(dst, value);
+		break;
+	case AFMT_MU_LAW:
+		*dst = _INTPCM_TO_G711(u8_to_ulaw, value);
+		break;
+	case AFMT_A_LAW:
+		*dst = _INTPCM_TO_G711(u8_to_alaw, value);
+		break;
+	default:
+		value = 0;
+	}
+}
+
+/* Helper function to write one normalized sample to a buffer. */
+static void
+local_pcm_write_norm(uint8_t *dst, intpcm_t value, uint32_t format)
+{
+	local_pcm_write(dst, value >> (32 - AFMT_BIT(format)), format);
+}
+
+ATF_TC(pcm_read);
+ATF_TC_HEAD(pcm_read, tc)
+{
+	atf_tc_set_md_var(tc, "descr",
+	    "Read and verify different pcm sample formats.");
+}
+ATF_TC_BODY(pcm_read, tc)
+{
+	const struct afmt_test_data *test;
+	uint8_t src[4];
+	intpcm_t expected, result;
+	size_t i;
+
+	for (i = 0; i < nitems(afmt_tests); i++) {
+		test = &afmt_tests[i];
+
+		/* Copy byte representation, fill with distinctive pattern. */
+		memset(src, 0x66, sizeof(src));
+		memcpy(src, test->buffer, test->size);
+
+		/* Read sample at format magnitude. */
+		expected = test->value;
+		result = local_pcm_read(src, test->format);
+		ATF_CHECK_MSG(result == expected,
+		    "pcm_read[\"%s\"].value: expected=0x%08x, result=0x%08x",
+		    test->label, expected, result);
+
+		/* Read sample at format magnitude, for calculations. */
+		expected = local_calc_limit(test->value, test->size * 8);
+		result = local_pcm_read_calc(src, test->format);
+		ATF_CHECK_MSG(result == expected,
+		    "pcm_read[\"%s\"].calc: expected=0x%08x, result=0x%08x",
+		    test->label, expected, result);
+
+		/* Read sample at full 32 bit magnitude. */
+		expected = local_normalize(test->value, test->size * 8, 32);
+		result = local_pcm_read_norm(src, test->format);
+		ATF_CHECK_MSG(result == expected,
+		    "pcm_read[\"%s\"].norm: expected=0x%08x, result=0x%08x",
+		    test->label, expected, result);
+	}
+}
+
+ATF_TC(pcm_write);
+ATF_TC_HEAD(pcm_write, tc)
+{
+	atf_tc_set_md_var(tc, "descr",
+	    "Write and verify different pcm sample formats.");
+}
+ATF_TC_BODY(pcm_write, tc)
+{
+	const struct afmt_test_data *test;
+	uint8_t expected[4];
+	uint8_t dst[4];
+	intpcm_t value;
+	size_t i;
+
+	for (i = 0; i < nitems(afmt_tests); i++) {
+		test = &afmt_tests[i];
+
+		/* Write sample of format specific magnitude. */
+		memcpy(expected, test->buffer, sizeof(expected));
+		memset(dst, 0x00, sizeof(dst));
+		value = test->value;
+		local_pcm_write(dst, value, test->format);
+		ATF_CHECK_MSG(memcmp(dst, expected, sizeof(dst)) == 0,
+		    "pcm_write[\"%s\"].value: "
+		    "expected={0x%02x, 0x%02x, 0x%02x, 0x%02x}, "
+		    "result={0x%02x, 0x%02x, 0x%02x, 0x%02x}, ", test->label,
+		    expected[0], expected[1], expected[2], expected[3],
+		    dst[0], dst[1], dst[2], dst[3]);
+
+		/* Write sample of format specific, calculation magnitude. */
+		memcpy(expected, test->buffer, sizeof(expected));
+		memset(dst, 0x00, sizeof(dst));
+		value = local_calc_limit(test->value, test->size * 8);
+		if (value != test->value) {
+			/*
+			 * 32 bit sample was reduced to 24 bit resolution
+			 * for calculation, least significant byte is lost.
+			 */
+			if (test->format & AFMT_BIGENDIAN)
+				expected[3] = 0x00;
+			else
+				expected[0] = 0x00;
+		}
+		local_pcm_write_calc(dst, value, test->format);
+		ATF_CHECK_MSG(memcmp(dst, expected, sizeof(dst)) == 0,
+		    "pcm_write[\"%s\"].value: "
+		    "expected={0x%02x, 0x%02x, 0x%02x, 0x%02x}, "
+		    "result={0x%02x, 0x%02x, 0x%02x, 0x%02x}, ", test->label,
+		    expected[0], expected[1], expected[2], expected[3],
+		    dst[0], dst[1], dst[2], dst[3]);
+
+		/* Write normalized sample of full 32 bit magnitude. */
+		memcpy(expected, test->buffer, sizeof(expected));
+		memset(dst, 0x00, sizeof(dst));
+		value = local_normalize(test->value, test->size * 8, 32);
+		local_pcm_write_norm(dst, value, test->format);
+		ATF_CHECK_MSG(memcmp(dst, expected, sizeof(dst)) == 0,
+		    "pcm_write[\"%s\"].norm: "
+		    "expected={0x%02x, 0x%02x, 0x%02x, 0x%02x}, "
+		    "result={0x%02x, 0x%02x, 0x%02x, 0x%02x}, ", test->label,
+		    expected[0], expected[1], expected[2], expected[3],
+		    dst[0], dst[1], dst[2], dst[3]);
+	}
+}
+
+ATF_TC(pcm_format_bits);
+ATF_TC_HEAD(pcm_format_bits, tc)
+{
+	atf_tc_set_md_var(tc, "descr",
+	    "Verify bit width of different pcm sample formats.");
+}
+ATF_TC_BODY(pcm_format_bits, tc)
+{
+	const struct afmt_test_data *test;
+	size_t bits;
+	size_t i;
+
+	for (i = 0; i < nitems(afmt_tests); i++) {
+		test = &afmt_tests[i];
+
+		/* Check bit width determined for given sample format. */
+		bits = AFMT_BIT(test->format);
+		ATF_CHECK_MSG(bits == test->size * 8,
+		    "format_bits[%zu].size: expected=%zu, result=%zu",
+		    i, test->size * 8, bits);
+	}
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+	ATF_TP_ADD_TC(tp, pcm_read);
+	ATF_TP_ADD_TC(tp, pcm_write);
+	ATF_TP_ADD_TC(tp, pcm_format_bits);
+
+	return atf_no_error();
+}