git: 1728d26682c6 - stable/14 - sound: Implement AFMT_FLOAT support

From: Christos Margiolis <christos_at_FreeBSD.org>
Date: Sun, 06 Apr 2025 00:28:28 UTC
The branch stable/14 has been updated by christos:

URL: https://cgit.FreeBSD.org/src/commit/?id=1728d26682c65cb878971f55b4e87e24d0050524

commit 1728d26682c65cb878971f55b4e87e24d0050524
Author:     Christos Margiolis <christos@FreeBSD.org>
AuthorDate: 2025-03-30 17:45:38 +0000
Commit:     Christos Margiolis <christos@FreeBSD.org>
CommitDate: 2025-04-06 00:28:14 +0000

    sound: Implement AFMT_FLOAT support
    
    Even though the OSS manual [1] advises against using AFMT_FLOAT, there
    are applications that expect the sound driver to support it, and might
    not work properly without it.
    
    This patch adds AFMT_F32_LE|BE (as well as AFMT_FLOAT for OSS
    compatibility) in sys/soundcard.h and implements AFMT_F32_LE|BE <->
    AFMT_S32_LE|BE conversion functions. As a result, applications can
    write/read floats to/from sound(4), but internally, because sound(4)
    works with integers, we convert floating point samples to integer ones,
    before doing any processing.
    
    The reason for encoding/decoding IEEE754s manually, instead of using
    fpu_kern(9), is that fpu_kern(9) is not supported by all architectures,
    and also introduces significant overhead.
    
    The IEEE754 encoding/decoding implementation has been written by Ariff
    Abdullah [2].
    
    [1] http://manuals.opensound.com/developer/AFMT_FLOAT.html
    [2] https://people.freebsd.org/~ariff/utils/ieee754.c
    
    PR:             157050, 184380, 264973, 280612, 281390
    Sponsored by:   The FreeBSD Foundation
    MFC after:      1 week
    Reviewed by:    emaste
    Differential Revision:  https://reviews.freebsd.org/D47638
    
    (cherry picked from commit e1bbaa71d62c8681a576f9f5bedf475c7541bd35)
---
 sys/dev/sound/pcm/channel.c       |  4 +++
 sys/dev/sound/pcm/feeder_chain.c  |  2 ++
 sys/dev/sound/pcm/feeder_rate.c   |  4 +++
 sys/dev/sound/pcm/feeder_volume.c |  2 ++
 sys/dev/sound/pcm/pcm.h           | 67 +++++++++++++++++++++++++++++++++++++--
 sys/dev/sound/pcm/sound.h         | 13 +++++---
 sys/sys/soundcard.h               |  8 +++++
 tests/sys/sound/pcm_read_write.c  |  6 ++++
 8 files changed, 98 insertions(+), 8 deletions(-)

diff --git a/sys/dev/sound/pcm/channel.c b/sys/dev/sound/pcm/channel.c
index 4a96505ada66..3aa7cf219d81 100644
--- a/sys/dev/sound/pcm/channel.c
+++ b/sys/dev/sound/pcm/channel.c
@@ -977,9 +977,13 @@ static const struct {
 #if BYTE_ORDER == LITTLE_ENDIAN
 	{ "s32le", "s32", "32", AFMT_S32_LE },
 	{ "s32be",  NULL, NULL, AFMT_S32_BE },
+	{ "f32le", "f32", NULL, AFMT_F32_LE },
+	{ "f32be",  NULL, NULL, AFMT_F32_BE },
 #else
 	{ "s32le",  NULL, NULL, AFMT_S32_LE },
 	{ "s32be", "s32", "32", AFMT_S32_BE },
+	{ "f32le",  NULL, NULL, AFMT_F32_LE },
+	{ "f32be", "f32", NULL, AFMT_F32_BE },
 #endif
 	{ "u32le",  NULL, NULL, AFMT_U32_LE },
 	{ "u32be",  NULL, NULL, AFMT_U32_BE },
diff --git a/sys/dev/sound/pcm/feeder_chain.c b/sys/dev/sound/pcm/feeder_chain.c
index 1c4ddca6cdd5..56de32441de7 100644
--- a/sys/dev/sound/pcm/feeder_chain.c
+++ b/sys/dev/sound/pcm/feeder_chain.c
@@ -102,6 +102,7 @@ static uint32_t feeder_chain_formats_multi[] = {
 	AFMT_S16_LE, AFMT_S16_BE, AFMT_U16_LE, AFMT_U16_BE,
 	AFMT_S24_LE, AFMT_S24_BE, AFMT_U24_LE, AFMT_U24_BE,
 	AFMT_S32_LE, AFMT_S32_BE, AFMT_U32_LE, AFMT_U32_BE,
+	AFMT_F32_LE, AFMT_F32_BE,
 	0
 };
 
@@ -111,6 +112,7 @@ static uint32_t feeder_chain_formats_fullmulti[] = {
 	AFMT_S16_LE, AFMT_S16_BE, AFMT_U16_LE, AFMT_U16_BE,
 	AFMT_S24_LE, AFMT_S24_BE, AFMT_U24_LE, AFMT_U24_BE,
 	AFMT_S32_LE, AFMT_S32_BE, AFMT_U32_LE, AFMT_U32_BE,
+	AFMT_F32_LE, AFMT_F32_BE,
 	0
 };
 
diff --git a/sys/dev/sound/pcm/feeder_rate.c b/sys/dev/sound/pcm/feeder_rate.c
index 1610211ff5f5..9ea454cdee1e 100644
--- a/sys/dev/sound/pcm/feeder_rate.c
+++ b/sys/dev/sound/pcm/feeder_rate.c
@@ -639,6 +639,8 @@ Z_DECLARE(U, 32, LE)
 Z_DECLARE(U, 16, BE)
 Z_DECLARE(U, 24, BE)
 Z_DECLARE(U, 32, BE)
+Z_DECLARE(F, 32, LE)
+Z_DECLARE(F, 32, BE)
 #endif
 
 enum {
@@ -687,6 +689,8 @@ static const struct {
 	Z_RESAMPLER_ENTRY(U, 16, BE),
 	Z_RESAMPLER_ENTRY(U, 24, BE),
 	Z_RESAMPLER_ENTRY(U, 32, BE),
+	Z_RESAMPLER_ENTRY(F, 32, LE),
+	Z_RESAMPLER_ENTRY(F, 32, BE),
 #endif
 };
 
diff --git a/sys/dev/sound/pcm/feeder_volume.c b/sys/dev/sound/pcm/feeder_volume.c
index f72c6aa7ef4f..2d35bb56ef8f 100644
--- a/sys/dev/sound/pcm/feeder_volume.c
+++ b/sys/dev/sound/pcm/feeder_volume.c
@@ -93,6 +93,8 @@ FEEDVOLUME_DECLARE(U, 32, LE)
 FEEDVOLUME_DECLARE(U, 16, BE)
 FEEDVOLUME_DECLARE(U, 24, BE)
 FEEDVOLUME_DECLARE(U, 32, BE)
+FEEDVOLUME_DECLARE(F, 32, LE)
+FEEDVOLUME_DECLARE(F, 32, BE)
 #endif
 
 struct feed_volume_info {
diff --git a/sys/dev/sound/pcm/pcm.h b/sys/dev/sound/pcm/pcm.h
index 1de686b04097..7d0a8f0f431b 100644
--- a/sys/dev/sound/pcm/pcm.h
+++ b/sys/dev/sound/pcm/pcm.h
@@ -128,7 +128,8 @@ static const struct {
 static __always_inline __unused intpcm_t
 pcm_sample_read(const uint8_t *src, uint32_t fmt)
 {
-	intpcm_t v;
+	intpcm_t v, e, m;
+	bool s;
 
 	fmt = AFMT_ENCODING(fmt);
 
@@ -190,6 +191,34 @@ pcm_sample_read(const uint8_t *src, uint32_t fmt)
 		v = INTPCM_T(src[3] | src[2] << 8 | src[1] << 16 |
 		    (int8_t)(src[0] ^ 0x80) << 24);
 		break;
+	case AFMT_F32_LE:	/* FALLTHROUGH */
+	case AFMT_F32_BE:
+		if (fmt == AFMT_F32_LE) {
+			v = INTPCM_T(src[0] | src[1] << 8 | src[2] << 16 |
+			    (int8_t)src[3] << 24);
+		} else {
+			v = INTPCM_T(src[3] | src[2] << 8 | src[1] << 16 |
+			    (int8_t)src[0] << 24);
+		}
+		e = (v >> 23) & 0xff;
+		/* NaN, +/- Inf  or too small */
+		if (e == 0xff || e < 96) {
+			v = INTPCM_T(0);
+			break;
+		}
+		s = v & 0x80000000U;
+		if (e > 126) {
+			v = INTPCM_T((s == 0) ? PCM_S32_MAX : PCM_S32_MIN);
+			break;
+		}
+		m = 0x800000 | (v & 0x7fffff);
+		e += 8 - 127;
+		if (e < 0)
+			m >>= -e;
+		else
+			m <<= e;
+		v = INTPCM_T((s == 0) ? m : -m);
+		break;
 	default:
 		v = 0;
 		printf("%s(): unknown format: 0x%08x\n", __func__, fmt);
@@ -241,8 +270,38 @@ pcm_sample_read_calc(const uint8_t *src, uint32_t fmt)
 static __always_inline __unused void
 pcm_sample_write(uint8_t *dst, intpcm_t v, uint32_t fmt)
 {
+	intpcm_t r, e;
+
 	fmt = AFMT_ENCODING(fmt);
 
+	if (fmt & (AFMT_F32_LE | AFMT_F32_BE)) {
+		if (v == 0)
+			r = 0;
+		else if (v == PCM_S32_MAX)
+			r = 0x3f800000;
+		else if (v == PCM_S32_MIN)
+			r = 0x80000000U | 0x3f800000;
+		else {
+			r = 0;
+			if (v < 0) {
+				r |= 0x80000000U;
+				v = -v;
+			}
+			e = 127 - 8;
+			while ((v & 0x7f000000) != 0) {
+				v >>= 1;
+				e++;
+			}
+			while ((v & 0x7f800000) == 0) {
+				v <<= 1;
+				e--;
+			}
+			r |= (e & 0xff) << 23;
+			r |= v & 0x7fffff;
+		}
+		v = r;
+	}
+
 	switch (fmt) {
 	case AFMT_AC3:
 		*(int16_t *)dst = 0;
@@ -295,13 +354,15 @@ pcm_sample_write(uint8_t *dst, intpcm_t v, uint32_t fmt)
 		dst[1] = v >> 8;
 		dst[0] = (v >> 16) ^ 0x80;
 		break;
-	case AFMT_S32_LE:
+	case AFMT_S32_LE:	/* FALLTHROUGH */
+	case AFMT_F32_LE:
 		dst[0] = v;
 		dst[1] = v >> 8;
 		dst[2] = v >> 16;
 		dst[3] = v >> 24;
 		break;
-	case AFMT_S32_BE:
+	case AFMT_S32_BE:	/* FALLTHROUGH */
+	case AFMT_F32_BE:
 		dst[3] = v;
 		dst[2] = v >> 8;
 		dst[1] = v >> 16;
diff --git a/sys/dev/sound/pcm/sound.h b/sys/dev/sound/pcm/sound.h
index 74c710f6f843..f7411ee096c9 100644
--- a/sys/dev/sound/pcm/sound.h
+++ b/sys/dev/sound/pcm/sound.h
@@ -451,15 +451,17 @@ 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_32BIT (AFMT_S32_LE | AFMT_S32_BE | AFMT_U32_LE | AFMT_U32_BE | \
+			AFMT_F32_LE | AFMT_F32_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 | \
+#define AFMT_SIGNED (AFMT_S32_LE | AFMT_S32_BE | AFMT_F32_LE | AFMT_F32_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_BIGENDIAN (AFMT_S32_BE | AFMT_U32_BE | AFMT_F32_BE | \
+			AFMT_S24_BE | AFMT_U24_BE | AFMT_S16_BE | AFMT_U16_BE)
 
 #define AFMT_CONVERTIBLE	(AFMT_8BIT | AFMT_16BIT | AFMT_24BIT |	\
 				 AFMT_32BIT)
@@ -509,7 +511,8 @@ int	sound_oss_card_info(oss_card_info *);
 #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_SIGNED_NE	(AFMT_S8_NE | AFMT_S16_NE | AFMT_S24_NE | \
+			AFMT_S32_NE | AFMT_F32_NE)
 
 #define AFMT_NE		(AFMT_SIGNED_NE | AFMT_U8_NE | AFMT_U16_NE |	\
 			 AFMT_U24_NE | AFMT_U32_NE)
diff --git a/sys/sys/soundcard.h b/sys/sys/soundcard.h
index b5434b930215..a0342ebf58f0 100644
--- a/sys/sys/soundcard.h
+++ b/sys/sys/soundcard.h
@@ -184,6 +184,8 @@ struct snd_size {
 #define AFMT_S24_BE	0x00020000	/* Big endian signed 24-bit */
 #define AFMT_U24_LE	0x00040000	/* Little endian unsigned 24-bit */
 #define AFMT_U24_BE	0x00080000	/* Big endian unsigned 24-bit */
+#define AFMT_F32_LE	0x10000000	/* Little endian 32-bit floating point */
+#define AFMT_F32_BE	0x20000000	/* Big endian 32-bit floating point */
 
 /* Machine dependent AFMT_* definitions. */
 #if BYTE_ORDER == LITTLE_ENDIAN
@@ -199,6 +201,8 @@ struct snd_size {
 #define AFMT_U16_OE	AFMT_U16_BE
 #define AFMT_U24_OE	AFMT_U24_BE
 #define AFMT_U32_OE	AFMT_U32_BE
+#define AFMT_F32_NE	AFMT_F32_LE
+#define AFMT_F32_OE	AFMT_F32_BE
 #else
 #define AFMT_S16_OE	AFMT_S16_LE
 #define AFMT_S24_OE	AFMT_S24_LE
@@ -212,8 +216,12 @@ struct snd_size {
 #define AFMT_U16_NE	AFMT_U16_BE
 #define AFMT_U24_NE	AFMT_U24_BE
 #define AFMT_U32_NE	AFMT_U32_BE
+#define AFMT_F32_NE	AFMT_F32_BE
+#define AFMT_F32_OE	AFMT_F32_LE
 #endif
 
+#define AFMT_FLOAT	AFMT_F32_NE	/* compatibility alias */
+
 #define AFMT_STEREO	0x10000000	/* can do/want stereo	*/
 
 /*
diff --git a/tests/sys/sound/pcm_read_write.c b/tests/sys/sound/pcm_read_write.c
index 2aba19840735..a77b953a78a0 100644
--- a/tests/sys/sound/pcm_read_write.c
+++ b/tests/sys/sound/pcm_read_write.c
@@ -70,6 +70,12 @@ static struct afmt_test_data {
 	{"u32be_1", {0x01, 0x02, 0x03, 0x04}, 4, AFMT_U32_BE, 0x81020304},
 	{"u32be_2", {0x81, 0x82, 0x83, 0x84}, 4, AFMT_U32_BE, 0x01828384},
 
+	/* 32 bit floating point sample formats. */
+	{"f32le_1", {0x00, 0x00, 0x00, 0x3f}, 4, AFMT_F32_LE, 0x40000000},
+	{"f32le_2", {0x00, 0x00, 0x00, 0xbf}, 4, AFMT_F32_LE, 0xc0000000},
+	{"f32be_1", {0x3f, 0x00, 0x00, 0x00}, 4, AFMT_F32_BE, 0x40000000},
+	{"f32be_2", {0xbf, 0x00, 0x00, 0x00}, 4, AFMT_F32_BE, 0xc0000000},
+
 	/* 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},