From 545685bc3209d9cfdf6c4b9aeee4715edd453dc1 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sat, 8 Oct 2011 10:25:06 +0200
Subject: audio_format: basic support for floating point samples

Support for conversion from float to 16, 24 and 32 bit integer
samples.
---
 src/audio_format.c              |  3 ++
 src/audio_format.h              |  8 +++++
 src/audio_parser.c              |  6 ++++
 src/decoder/flac_pcm.c          |  1 +
 src/output/alsa_output_plugin.c |  3 ++
 src/output/oss_output_plugin.c  |  1 +
 src/pcm_byteswap.c              |  1 +
 src/pcm_format.c                | 75 +++++++++++++++++++++++++++++++++++++++++
 src/pcm_mix.c                   |  8 +++++
 src/pcm_utils.h                 | 28 +++++++++++++++
 src/pcm_volume.c                |  4 +++
 11 files changed, 138 insertions(+)

(limited to 'src')

diff --git a/src/audio_format.c b/src/audio_format.c
index 458ec3b13..8c40457ec 100644
--- a/src/audio_format.c
+++ b/src/audio_format.c
@@ -68,6 +68,9 @@ sample_format_to_string(enum sample_format format)
 
 	case SAMPLE_FORMAT_S32:
 		return "32";
+
+	case SAMPLE_FORMAT_FLOAT:
+		return "f";
 	}
 
 	/* unreachable */
diff --git a/src/audio_format.h b/src/audio_format.h
index 71bd369e9..4f7dbfb1a 100644
--- a/src/audio_format.h
+++ b/src/audio_format.h
@@ -43,6 +43,12 @@ enum sample_format {
 	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,
 };
 
 static const unsigned MAX_CHANNELS = 8;
@@ -168,6 +174,7 @@ audio_valid_sample_format(enum sample_format format)
 	case SAMPLE_FORMAT_S24:
 	case SAMPLE_FORMAT_S24_P32:
 	case SAMPLE_FORMAT_S32:
+	case SAMPLE_FORMAT_FLOAT:
 		return true;
 
 	case SAMPLE_FORMAT_UNDEFINED:
@@ -241,6 +248,7 @@ sample_format_size(enum sample_format format)
 
 	case SAMPLE_FORMAT_S24_P32:
 	case SAMPLE_FORMAT_S32:
+	case SAMPLE_FORMAT_FLOAT:
 		return 4;
 
 	case SAMPLE_FORMAT_UNDEFINED:
diff --git a/src/audio_parser.c b/src/audio_parser.c
index 80bf9a5d7..8875239e1 100644
--- a/src/audio_parser.c
+++ b/src/audio_parser.c
@@ -81,6 +81,12 @@ parse_sample_format(const char *src, bool mask,
 		return true;
 	}
 
+	if (*src == 'f') {
+		*sample_format_r = SAMPLE_FORMAT_FLOAT;
+		*endptr_r = src + 1;
+		return true;
+	}
+
 	value = strtoul(src, &endptr, 10);
 	if (endptr == src) {
 		g_set_error(error_r, audio_parser_quark(), 0,
diff --git a/src/decoder/flac_pcm.c b/src/decoder/flac_pcm.c
index 3b56d50bd..c479ebfd4 100644
--- a/src/decoder/flac_pcm.c
+++ b/src/decoder/flac_pcm.c
@@ -102,6 +102,7 @@ flac_convert(void *dest,
 		break;
 
 	case SAMPLE_FORMAT_S24:
+	case SAMPLE_FORMAT_FLOAT:
 	case SAMPLE_FORMAT_UNDEFINED:
 		/* unreachable */
 		assert(false);
diff --git a/src/output/alsa_output_plugin.c b/src/output/alsa_output_plugin.c
index af6d1b184..f939ba5b4 100644
--- a/src/output/alsa_output_plugin.c
+++ b/src/output/alsa_output_plugin.c
@@ -212,6 +212,9 @@ get_bitformat(enum sample_format sample_format)
 
 	case SAMPLE_FORMAT_S32:
 		return SND_PCM_FORMAT_S32;
+
+	case SAMPLE_FORMAT_FLOAT:
+		return SND_PCM_FORMAT_FLOAT;
 	}
 
 	assert(false);
diff --git a/src/output/oss_output_plugin.c b/src/output/oss_output_plugin.c
index c8fce84ea..46505873b 100644
--- a/src/output/oss_output_plugin.c
+++ b/src/output/oss_output_plugin.c
@@ -395,6 +395,7 @@ sample_format_to_oss(enum sample_format format)
 {
 	switch (format) {
 	case SAMPLE_FORMAT_UNDEFINED:
+	case SAMPLE_FORMAT_FLOAT:
 		return AFMT_QUERY;
 
 	case SAMPLE_FORMAT_S8:
diff --git a/src/pcm_byteswap.c b/src/pcm_byteswap.c
index fcd995ba2..a1315c475 100644
--- a/src/pcm_byteswap.c
+++ b/src/pcm_byteswap.c
@@ -69,6 +69,7 @@ pcm_byteswap(struct pcm_buffer *buffer, enum sample_format format,
 	switch (format) {
 	case SAMPLE_FORMAT_UNDEFINED:
 	case SAMPLE_FORMAT_S24:
+	case SAMPLE_FORMAT_FLOAT:
 		/* not implemented */
 		return NULL;
 
diff --git a/src/pcm_format.c b/src/pcm_format.c
index d22dfb896..bb731b2b6 100644
--- a/src/pcm_format.c
+++ b/src/pcm_format.c
@@ -46,6 +46,18 @@ pcm_convert_32_to_16(struct pcm_dither *dither,
 	pcm_dither_32_to_16(dither, out, in, in_end);
 }
 
+static void
+pcm_convert_float_to_16(int16_t *out, const float *in, const float *in_end)
+{
+	const unsigned OUT_BITS = 16;
+	const float factor = 1 << (OUT_BITS - 1);
+
+	while (in < in_end) {
+		int sample = *in++ * factor;
+		*out++ = pcm_clamp_16(sample);
+	}
+}
+
 static int16_t *
 pcm_allocate_8_to_16(struct pcm_buffer *buffer,
 		     const int8_t *src, size_t src_size, size_t *dest_size_r)
@@ -112,6 +124,20 @@ pcm_allocate_32_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither,
 	return dest;
 }
 
+static int16_t *
+pcm_allocate_float_to_16(struct pcm_buffer *buffer,
+			 const float *src, size_t src_size,
+			 size_t *dest_size_r)
+{
+	int16_t *dest;
+	*dest_size_r = src_size / 2;
+	assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest));
+	dest = pcm_buffer_get(buffer, *dest_size_r);
+	pcm_convert_float_to_16(dest, src,
+				pcm_end_pointer(src, src_size));
+	return dest;
+}
+
 const int16_t *
 pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither,
 		  enum sample_format src_format, const void *src,
@@ -142,6 +168,10 @@ pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither,
 	case SAMPLE_FORMAT_S32:
 		return pcm_allocate_32_to_16(buffer, dither, src, src_size,
 					     dest_size_r);
+
+	case SAMPLE_FORMAT_FLOAT:
+		return pcm_allocate_float_to_16(buffer, src, src_size,
+						dest_size_r);
 	}
 
 	return NULL;
@@ -170,6 +200,18 @@ pcm_convert_32_to_24(int32_t *restrict out,
 		*out++ = *in++ >> 8;
 }
 
+static void
+pcm_convert_float_to_24(int32_t *out, const float *in, const float *in_end)
+{
+	const unsigned OUT_BITS = 24;
+	const float factor = 1 << (OUT_BITS - 1);
+
+	while (in < in_end) {
+		int sample = *in++ * factor;
+		*out++ = pcm_clamp_24(sample);
+	}
+}
+
 static int32_t *
 pcm_allocate_8_to_24(struct pcm_buffer *buffer,
 		     const int8_t *src, size_t src_size, size_t *dest_size_r)
@@ -203,6 +245,17 @@ pcm_allocate_32_to_24(struct pcm_buffer *buffer,
 	return dest;
 }
 
+static int32_t *
+pcm_allocate_float_to_24(struct pcm_buffer *buffer,
+			 const float *src, size_t src_size,
+			 size_t *dest_size_r)
+{
+	*dest_size_r = src_size;
+	int32_t *dest = pcm_buffer_get(buffer, *dest_size_r);
+	pcm_convert_float_to_24(dest, src, pcm_end_pointer(src, src_size));
+	return dest;
+}
+
 const int32_t *
 pcm_convert_to_24(struct pcm_buffer *buffer,
 		  enum sample_format src_format, const void *src,
@@ -233,6 +286,10 @@ pcm_convert_to_24(struct pcm_buffer *buffer,
 	case SAMPLE_FORMAT_S32:
 		return pcm_allocate_32_to_24(buffer, src, src_size,
 					     dest_size_r);
+
+	case SAMPLE_FORMAT_FLOAT:
+		return pcm_allocate_float_to_24(buffer, src, src_size,
+						dest_size_r);
 	}
 
 	return NULL;
@@ -309,6 +366,20 @@ pcm_allocate_24p32_to_32(struct pcm_buffer *buffer,
 	return dest;
 }
 
+static int32_t *
+pcm_allocate_float_to_32(struct pcm_buffer *buffer,
+			 const float *src, size_t src_size,
+			 size_t *dest_size_r)
+{
+	/* convert to S24_P32 first */
+	int32_t *dest = pcm_allocate_float_to_24(buffer, src, src_size,
+						 dest_size_r);
+
+	/* convert to 32 bit in-place */
+	pcm_convert_24_to_32(dest, dest, pcm_end_pointer(dest, *dest_size_r));
+	return dest;
+}
+
 const int32_t *
 pcm_convert_to_32(struct pcm_buffer *buffer,
 		  enum sample_format src_format, const void *src,
@@ -339,6 +410,10 @@ pcm_convert_to_32(struct pcm_buffer *buffer,
 	case SAMPLE_FORMAT_S32:
 		*dest_size_r = src_size;
 		return src;
+
+	case SAMPLE_FORMAT_FLOAT:
+		return pcm_allocate_float_to_32(buffer, src, src_size,
+						dest_size_r);
 	}
 
 	return NULL;
diff --git a/src/pcm_mix.c b/src/pcm_mix.c
index ef81f44a4..b94089cfb 100644
--- a/src/pcm_mix.c
+++ b/src/pcm_mix.c
@@ -128,6 +128,10 @@ pcm_add_vol(void *buffer1, const void *buffer2, size_t size,
 		pcm_add_vol_32((int32_t *)buffer1, (const int32_t *)buffer2,
 			       size / 4, vol1, vol2);
 		return true;
+
+	case SAMPLE_FORMAT_FLOAT:
+		/* XXX */
+		return false;
 	}
 
 	/* unreachable */
@@ -216,6 +220,10 @@ pcm_add(void *buffer1, const void *buffer2, size_t size,
 	case SAMPLE_FORMAT_S32:
 		pcm_add_32((int32_t *)buffer1, (const int32_t *)buffer2, size / 4);
 		return true;
+
+	case SAMPLE_FORMAT_FLOAT:
+		/* XXX */
+		return false;
 	}
 
 	/* unreachable */
diff --git a/src/pcm_utils.h b/src/pcm_utils.h
index 001423b37..4ad896570 100644
--- a/src/pcm_utils.h
+++ b/src/pcm_utils.h
@@ -63,4 +63,32 @@ pcm_range_64(int64_t sample, unsigned bits)
 	return sample;
 }
 
+G_GNUC_CONST
+static inline int16_t
+pcm_clamp_16(int x)
+{
+	static const int32_t MIN_VALUE = G_MININT16;
+	static const int32_t MAX_VALUE = G_MAXINT16;
+
+	if (G_UNLIKELY(x < MIN_VALUE))
+		return MIN_VALUE;
+	if (G_UNLIKELY(x > MAX_VALUE))
+		return MAX_VALUE;
+	return x;
+}
+
+G_GNUC_CONST
+static inline int32_t
+pcm_clamp_24(int x)
+{
+	static const int32_t MIN_VALUE = -(1 << 23);
+	static const int32_t MAX_VALUE = (1 << 23) - 1;
+
+	if (G_UNLIKELY(x < MIN_VALUE))
+		return MIN_VALUE;
+	if (G_UNLIKELY(x > MAX_VALUE))
+		return MAX_VALUE;
+	return x;
+}
+
 #endif
diff --git a/src/pcm_volume.c b/src/pcm_volume.c
index fd8670f4b..f732060b2 100644
--- a/src/pcm_volume.c
+++ b/src/pcm_volume.c
@@ -167,6 +167,10 @@ pcm_volume(void *buffer, size_t length,
 	case SAMPLE_FORMAT_S32:
 		pcm_volume_change_32(buffer, end, volume);
 		return true;
+
+	case SAMPLE_FORMAT_FLOAT:
+		/* XXX */
+		return false;
 	}
 
 	/* unreachable */
-- 
cgit v1.2.3