OSDN Git Service

Add adjust_selected_channels for audio conversion
authorAndy Hung <hunga@google.com>
Fri, 1 Dec 2017 21:24:52 +0000 (13:24 -0800)
committerAndy Hung <hunga@google.com>
Thu, 7 Dec 2017 02:35:57 +0000 (18:35 -0800)
Used for multichannel effects.

Test: channels_tests
Bug: 70038539
Change-Id: I9e80d2f0565f3eff8623f01260850c628b18c3a5

audio_utils/channels.c
audio_utils/include/audio_utils/channels.h
audio_utils/tests/Android.bp
audio_utils/tests/build_and_run_all_unit_tests.sh
audio_utils/tests/channels_tests.cpp [new file with mode: 0644]
audio_utils/tests/primitives_tests.cpp

index 91d18fd..83a74b8 100644 (file)
@@ -86,6 +86,40 @@ static inline uint8x3_t int32_to_uint8x3(int32_t in) {
     return num_out_samples * sizeof(*(out_buff)); \
 }
 
+/* Channel expands from an input buffer to an output buffer.
+ * See expand_selected_channels() function below for parameter definitions.
+ * Selected channels are replaced in the output buffer, with any extra channels
+ * per frame left alone.
+ *
+ * Move from back to front so that the conversion can be done in-place
+ * i.e. in_buff == out_buff
+ * NOTE: num_in_bytes must be a multiple of in_buff_channels * in_buff_sample_size.
+ */
+/* This is written as a C macro because it operates on generic types,
+ * which in a C++ file could be alternatively achieved by a "template"
+ * or an "auto" declaration.
+ * TODO: convert this from a C file to a C++ file.
+ */
+#define EXPAND_SELECTED_CHANNELS( \
+        in_buff, in_buff_chans, out_buff, out_buff_chans, num_in_bytes) \
+{ \
+    size_t num_in_samples = (num_in_bytes) / sizeof(*(in_buff)); \
+    size_t num_out_samples = (num_in_samples * (out_buff_chans)) / (in_buff_chans); \
+    typeof(out_buff) dst_ptr = (out_buff) + num_out_samples - 1; \
+    size_t src_index; \
+    typeof(in_buff) src_ptr = (in_buff) + num_in_samples - 1; \
+    size_t num_extra_chans = (out_buff_chans) - (in_buff_chans); \
+    for (src_index = 0; src_index < num_in_samples; src_index += (in_buff_chans)) { \
+        dst_ptr -= num_extra_chans; \
+        for (size_t dst_offset = num_extra_chans; dst_offset < (out_buff_chans); dst_offset++) { \
+            *dst_ptr-- = *src_ptr--; \
+        } \
+    } \
+    /* return number of *bytes* generated */ \
+    return num_out_samples * sizeof(*(out_buff)); \
+}
+
+
 /* Channel expands from a MONO input buffer to a MULTICHANNEL output buffer by duplicating the
  * single input channel to the first 2 output channels and 0-filling the remaining.
  * See expand_channels() function below for parameter definitions.
@@ -375,3 +409,75 @@ size_t adjust_channels(const void* in_buff, size_t in_buff_chans,
 
     return num_in_bytes;
 }
+
+/*
+ * Convert a buffer of N-channel, interleaved samples to M-channel
+ * (where N < M).
+ *   in_buff points to the buffer of samples
+ *   in_buff_channels Specifies the number of channels in the input buffer.
+ *   out_buff points to the buffer to receive converted samples.
+ *   out_buff_channels Specifies the number of channels in the output buffer.
+ *   sample_size_in_bytes Specifies the number of bytes per sample.
+ *   num_in_bytes size of input buffer in BYTES
+ * returns
+ *   the number of BYTES of output data.
+ * NOTE
+ *   channels > N are left alone in out_buff.
+ *   The out and in buffers must either be completely separate (non-overlapping), or
+ *   they must both start at the same address. Partially overlapping buffers are not supported.
+ */
+static size_t expand_selected_channels(const void* in_buff, size_t in_buff_chans,
+                              void* out_buff, size_t out_buff_chans,
+                              unsigned sample_size_in_bytes, size_t num_in_bytes)
+{
+    switch (sample_size_in_bytes) {
+    case 1:
+
+        EXPAND_SELECTED_CHANNELS((const uint8_t*)in_buff, in_buff_chans,
+                        (uint8_t*)out_buff, out_buff_chans,
+                        num_in_bytes);
+        // returns in macro
+
+    case 2:
+
+        EXPAND_SELECTED_CHANNELS((const int16_t*)in_buff, in_buff_chans,
+                        (int16_t*)out_buff, out_buff_chans,
+                        num_in_bytes);
+        // returns in macro
+
+    case 3:
+
+        EXPAND_SELECTED_CHANNELS((const uint8x3_t*)in_buff, in_buff_chans,
+                        (uint8x3_t*)out_buff, out_buff_chans,
+                        num_in_bytes);
+        // returns in macro
+
+    case 4:
+
+        EXPAND_SELECTED_CHANNELS((const int32_t*)in_buff, in_buff_chans,
+                        (int32_t*)out_buff, out_buff_chans,
+                        num_in_bytes);
+        // returns in macro
+
+    default:
+        return 0;
+    }
+}
+
+size_t adjust_selected_channels(const void* in_buff, size_t in_buff_chans,
+                       void* out_buff, size_t out_buff_chans,
+                       unsigned sample_size_in_bytes, size_t num_in_bytes)
+{
+    if (out_buff_chans > in_buff_chans) {
+        return expand_selected_channels(in_buff, in_buff_chans, out_buff, out_buff_chans,
+                               sample_size_in_bytes, num_in_bytes);
+    } else if (out_buff_chans < in_buff_chans) {
+        return contract_channels(in_buff, in_buff_chans, out_buff, out_buff_chans,
+                                 sample_size_in_bytes, num_in_bytes);
+    } else if (in_buff != out_buff) {
+        memcpy(out_buff, in_buff, num_in_bytes);
+    }
+
+    return num_in_bytes;
+}
+
index 573cab7..10026f4 100644 (file)
@@ -45,6 +45,30 @@ size_t adjust_channels(const void* in_buff, size_t in_buff_chans,
                        void* out_buff, size_t out_buff_chans,
                        unsigned sample_size_in_bytes, size_t num_in_bytes);
 
+/**
+ * Expands or contracts sample data from one interleaved channel format to another.
+ * Extra expanded channels are left alone in the output buffer.
+ * Contracted channels are omitted from the end of each audio frame.
+ *
+ *   \param in_buff              points to the buffer of samples
+ *   \param in_buff_chans        Specifies the number of channels in the input buffer.
+ *   \param out_buff             points to the buffer to receive converted samples.
+ *   \param out_buff_chans       Specifies the number of channels in the output buffer.
+ *   \param sample_size_in_bytes Specifies the number of bytes per sample. 1, 2, 3, 4 are
+ *     currently valid.
+ *   \param num_in_bytes         size of input buffer in BYTES
+ *
+ * \return
+ *   the number of BYTES of output data or 0 if an error occurs.
+ *
+ * \note
+ *   The out and in buffers must either be completely separate (non-overlapping), or
+ *   they must both start at the same address. Partially overlapping buffers are not supported.
+ */
+size_t adjust_selected_channels(const void* in_buff, size_t in_buff_chans,
+                       void* out_buff, size_t out_buff_chans,
+                       unsigned sample_size_in_bytes, size_t num_in_bytes);
+
 /** \cond */
 __END_DECLS
 /** \endcond */
index 1ffe16d..8eec07c 100644 (file)
@@ -190,3 +190,26 @@ cc_test {
         },
     }
 }
+
+cc_test {
+    name: "channels_tests",
+    host_supported: true,
+
+    shared_libs: [
+        "libcutils",
+        "liblog",
+    ],
+    srcs: ["channels_tests.cpp"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    target: {
+        android: {
+            shared_libs: ["libaudioutils"],
+        },
+        host: {
+            static_libs: ["libaudioutils"],
+        },
+    }
+}
index 9003f66..46f1cd6 100755 (executable)
@@ -23,9 +23,14 @@ adb push $OUT/system/lib/libaudioutils.so /system/lib
 adb push $OUT/data/nativetest/primitives_tests/primitives_tests /system/bin
 adb shell /system/bin/primitives_tests
 
-adb push $OUT/system/bin/primitives_benchmark /system/bin
-adb shell /system/bin/primitives_benchmark
-
 echo "testing power"
 adb push $OUT/data/nativetest/power_tests/power_tests /system/bin
 adb shell /system/bin/power_tests
+
+echo "testing channels"
+adb push $OUT/data/nativetest/channels_tests/channels_tests /system/bin
+adb shell /system/bin/channels_tests
+
+echo "benchmarking primitives"
+adb push $OUT/system/bin/primitives_benchmark /system/bin
+adb shell /system/bin/primitives_benchmark
diff --git a/audio_utils/tests/channels_tests.cpp b/audio_utils/tests/channels_tests.cpp
new file mode 100644 (file)
index 0000000..eb3b5e0
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "audio_utils_channels_tests"
+
+#include <math.h>
+#include <vector>
+
+#include <gtest/gtest.h>
+#include <log/log.h>
+
+#include <audio_utils/channels.h>
+
+// TODO: Make a common include file for helper functions.
+
+template<typename T>
+void checkMonotone(const T *ary, size_t size)
+{
+    for (size_t i = 1; i < size; ++i) {
+        EXPECT_LT(ary[i-1], ary[i]);
+    }
+}
+
+template<typename T>
+void checkUnsignedMonotoneOrZero(const T *ary, size_t size)
+{
+    if (size == 0) return;
+
+    T least = ary[0];
+    for (size_t i = 1; i < size; ++i) {
+        if (ary[i]) {
+            EXPECT_LT(least, ary[i]);
+            least = ary[i];
+        }
+    }
+}
+
+template<typename T>
+void expectEq(const T &c1, const T &c2) {
+    EXPECT_EQ(c1.size(), c2.size());
+    EXPECT_EQ(0, memcmp(c1.data(), c2.data(), sizeof(c1[0]) * std::min(c1.size(), c2.size())));
+}
+
+TEST(audio_utils_channels, adjust_channels) {
+    constexpr size_t size = 65536;
+    std::vector<uint16_t> u16ref(size);
+    std::vector<uint16_t> u16expand(size * 2);
+    std::vector<uint16_t> u16ary(size);
+
+    // reference buffer is monotonic.
+    for (size_t i = 0; i < u16ref.size(); ++i) {
+        u16ref[i] = i;
+    }
+
+    // expand channels from stereo to quad.
+    adjust_channels(
+            u16ref.data() /*in_buff*/,
+            2 /*in_channels*/,
+            u16expand.data() /*out_buff*/,
+            4 /*out_channels*/,
+            sizeof(u16ref[0]) /*sample_size_in_bytes*/,
+            sizeof(u16ref[0]) * u16ref.size() /*num_in_bytes*/);
+
+    // expanded buffer must increase (or be zero).
+    checkUnsignedMonotoneOrZero(u16expand.data(), u16expand.size());
+
+    // contract channels back to stereo.
+    adjust_channels(
+            u16expand.data() /*in_buff*/,
+            4 /*in_channels*/,
+            u16ary.data() /*out_buff*/,
+            2 /*out_channels*/,
+            sizeof(u16expand[0]) /*sample_size_in_bytes*/,
+            sizeof(u16expand[0]) * u16expand.size() /*num_in_bytes*/);
+
+    // contracted array must be identical to original.
+    expectEq(u16ary, u16ref);
+}
+
+TEST(audio_utils_channels, adjust_selected_channels) {
+    constexpr size_t size = 65536;
+    std::vector<uint16_t> u16ref(size);
+    std::vector<uint16_t> u16contract(size / 2);
+    std::vector<uint16_t> u16ary(size);
+
+    // reference buffer is monotonic.
+    for (size_t i = 0; i < u16ref.size(); ++i) {
+        u16ref[i] = i;
+    }
+
+    // contract from quad to stereo.
+    adjust_selected_channels(
+            u16ref.data() /*in_buff*/,
+            4 /*in_channels*/,
+            u16contract.data() /*out_buff*/,
+            2 /*out_channels*/,
+            sizeof(u16ref[0]) /*sample_size_in_bytes*/,
+            sizeof(u16ref[0]) * u16ref.size() /*num_in_bytes*/);
+
+    // contracted buffer must increase.
+    checkMonotone(u16contract.data(), u16contract.size());
+
+    // initialize channels 3 and 4 of final comparison array.
+    for (size_t i = 0; i < u16ary.size() / 4; ++i) {
+        u16ary[i * 4 + 2] = u16ref[i * 4 + 2];
+        u16ary[i * 4 + 3] = u16ref[i * 4 + 3];
+    }
+
+    // expand stereo into channels 1 and 2 of quad comparison array.
+    adjust_selected_channels(
+            u16contract.data() /*in_buff*/,
+            2 /*in_channels*/,
+            u16ary.data() /*out_buff*/,
+            4 /*out_channels*/,
+            sizeof(u16contract[0]) /*sample_size_in_bytes*/,
+            sizeof(u16contract[0]) * u16contract.size() /*num_in_bytes*/);
+
+    // comparison array must be identical to original.
+    expectEq(u16ary, u16ref);
+}
+
+
index e90fa55..d28516a 100644 (file)
@@ -690,39 +690,6 @@ TEST(audio_utils_primitives, updown_mix) {
     EXPECT_EQ(0, memcmp(i16ary.data(), i16ref.data(), sizeof(i16ref[0]) * size));
 }
 
-TEST(audio_utils_channels, adjust_channels) {
-    uint16_t *u16ref = new uint16_t[65536];
-    uint16_t *u16expand = new uint16_t[65536*2];
-    uint16_t *u16ary = new uint16_t[65536];
-
-    // reference buffer always increases
-    for (size_t i = 0; i < 65536; ++i) {
-        u16ref[i] = i;
-    }
-
-    // expand channels from stereo to quad.
-    adjust_channels(u16ref /*in_buff*/, 2 /*in_channels*/,
-            u16expand /*out_buff*/, 4 /*out_channels*/,
-            sizeof(u16ref[0]) /*sample_size_in_bytes*/,
-            sizeof(u16ref[0])*65536 /*num_in_bytes*/);
-
-    // expanded buffer must increase (or be zero)
-    checkMonotoneOrZero(u16expand, 65536*2);
-
-    // contract channels back to stereo.
-    adjust_channels(u16expand /*in_buff*/, 4 /*in_channels*/,
-            u16ary /*out_buff*/, 2 /*out_channels*/,
-            sizeof(u16expand[0]) /*sample_size_in_bytes*/,
-            sizeof(u16expand[0])*65536*2 /*num_in_bytes*/);
-
-    // must be identical to original.
-    EXPECT_EQ(0, memcmp(u16ary, u16ref, sizeof(u16ref[0])*65536));
-
-    delete[] u16ref;
-    delete[] u16expand;
-    delete[] u16ary;
-}
-
 template<typename T, typename TComparison>
 void checkAddedClamped(T *out, const T *in1, const T *in2, size_t size,
         TComparison limNeg, TComparison limPos)