OSDN Git Service

Fix float to i16 conversion in primitives.c
authorAndy Hung <hunga@google.com>
Tue, 25 Feb 2014 02:36:35 +0000 (18:36 -0800)
committerAndy Hung <hunga@google.com>
Thu, 27 Feb 2014 01:48:54 +0000 (17:48 -0800)
Updated memcpy_to_i16_from_float():
Made bit-accurate invertible with power of 2 scaling.
Also removed discontinuity around 0 (round versus truncate).

Added clamp16FromFloat() to clamp float audio range of
[-1.0, 1.0) to int16_t.

Change-Id: I45b23f32ae46a4d7f86c8e5482ee43538298ce89
Signed-off-by: Andy Hung <hunga@google.com>
audio_utils/include/audio_utils/primitives.h
audio_utils/primitives.c

index 358563f..656b9f0 100644 (file)
@@ -70,7 +70,8 @@ void memcpy_to_u8_from_i16(uint8_t *dst, const int16_t *src, size_t count);
 void memcpy_to_i16_from_i32(int16_t *dst, const int32_t *src, size_t count);
 
 /* Shrink and copy samples from single-precision floating-point to signed 16-bit.
- * Each float should be in the range -1.0 to 1.0.  Values outside that range are clamped.
+ * Each float should be in the range -1.0 to 1.0.  Values outside that range are clamped,
+ * refer to clamp16FromFloat().
  * Parameters:
  *  dst     Destination buffer
  *  src     Source buffer
@@ -111,6 +112,45 @@ static inline int16_t clamp16(int32_t sample)
     return sample;
 }
 
+/*
+ * Convert a IEEE 754 single precision float [-1.0, 1.0) to int16_t [-32768, 32767]
+ * with clamping.  Note the open bound at 1.0, values within 1/65536 of 1.0 map
+ * to 32767 instead of 32768 (early clamping due to the smaller positive integer subrange).
+ *
+ * Values outside the range [-1.0, 1.0) are properly clamped to -32768 and 32767,
+ * including -Inf and +Inf. NaN will generally be treated either as -32768 or 32767,
+ * depending on the sign bit inside NaN (whose representation is not unique).
+ * Nevertheless, strictly speaking, NaN behavior should be considered undefined.
+ *
+ * Rounding of 0.5 lsb is to even (default for IEEE 754).
+ */
+static inline int16_t clamp16FromFloat(float f)
+{
+    /* Offset is used to expand the valid range of [-1.0, 1.0) into the 16 lsbs of the
+     * floating point significand. The normal shift is 3<<22, but the -15 offset
+     * is used to multiply by 32768.
+     */
+    static const float offset = (float)(3 << (22 - 15));
+    /* zero = (0x10f << 22) =  0x43c00000 (not directly used) */
+    static const int32_t limneg = (0x10f << 22) /*zero*/ - 32768; /* 0x43bf8000 */
+    static const int32_t limpos = (0x10f << 22) /*zero*/ + 32767; /* 0x43c07fff */
+
+    union {
+        float f;
+        int32_t i;
+    } u;
+
+    u.f = f + offset; /* recenter valid range */
+    /* Now the valid range is represented as integers between [limneg, limpos].
+     * Clamp using the fact that float representation (as an integer) is an ordered set.
+     */
+    if (u.i < limneg)
+        u.i = -32768;
+    else if (u.i > limpos)
+        u.i = 32767;
+    return u.i; /* Return lower 16 bits, the part of interest in the significand. */
+}
+
 /**
  * Multiply-accumulate 16-bit terms with 32-bit result: return a + in*v.
  */
index 90ad75a..e2d4abc 100644 (file)
@@ -56,17 +56,7 @@ void memcpy_to_i16_from_i32(int16_t *dst, const int32_t *src, size_t count)
 void memcpy_to_i16_from_float(int16_t *dst, const float *src, size_t count)
 {
     while (count--) {
-        float f = *src++;
-        int16_t i;
-        if (f > 1.0) {
-            i = 32767;
-        } else if (f < -1.0) {
-            i = -32768;
-        } else {
-            // does not specifically handle NaN
-            i = f * 32767.0;
-        }
-        *dst++ = i;
+        *dst++ = clamp16FromFloat(*src++);
     }
 }