From c16beb439ad99ba591165f6e71ab70d82d7b9627 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Adrien=20B=C3=A9raud?= <adrien.beraud@savoirfairelinux.com>
Date: Wed, 28 Nov 2018 13:44:13 -0500
Subject: [PATCH] audioframe: implement mix

Change-Id: I8d8d6bd852d76040eb967dda76dcd8c85118e235
Reviewed-by: Philippe Gorley <philippe.gorley@savoirfairelinux.com>
---
 src/client/videomanager.cpp              | 39 ++++++++++++++++++++++--
 test/unitTest/media/test_media_frame.cpp | 36 ++++++++++++++++++++++
 2 files changed, 73 insertions(+), 2 deletions(-)

diff --git a/src/client/videomanager.cpp b/src/client/videomanager.cpp
index 3c646806ee..8265bf8cfa 100644
--- a/src/client/videomanager.cpp
+++ b/src/client/videomanager.cpp
@@ -99,9 +99,44 @@ AudioFrame::reserve(size_t nb_samples)
 }
 
 void
-AudioFrame::mix(const AudioFrame&)
+AudioFrame::mix(const AudioFrame& frame)
 {
-    RING_ERR("AudioFrame::mix not implemented yet");
+    auto& f = *pointer();
+    auto& fIn = *frame.pointer();
+    if (f.channels != fIn.channels || f.format != fIn.format || f.sample_rate != fIn.sample_rate) {
+        throw std::invalid_argument("Can't mix frames with different formats");
+    }
+    if (f.nb_samples == 0) {
+        reserve(fIn.nb_samples);
+    } else if (f.nb_samples != fIn.nb_samples) {
+        throw std::invalid_argument("Can't mix frames with different length");
+    }
+    AVSampleFormat fmt = (AVSampleFormat)f.format;
+    bool isPlanar = av_sample_fmt_is_planar(fmt);
+    unsigned samplesPerChannel = isPlanar ? f.nb_samples : f.nb_samples * f.channels;
+    unsigned channels = isPlanar ? f.channels : 1;
+    if (fmt == AV_SAMPLE_FMT_S16 || fmt == AV_SAMPLE_FMT_S16P) {
+        for (unsigned i=0; i < channels; i++) {
+            auto c = (int16_t*)f.extended_data[i];
+            auto cIn = (int16_t*)fIn.extended_data[i];
+            for (unsigned s=0; s < samplesPerChannel; s++) {
+                int32_t n = (int32_t)c[s] + (int32_t)cIn[s];
+                n = std::min<int32_t>(n, std::numeric_limits<int16_t>::max());
+                n = std::max<int32_t>(n, std::numeric_limits<int16_t>::min());
+                c[s] = n;
+            }
+        }
+    } else if (fmt == AV_SAMPLE_FMT_FLT || fmt == AV_SAMPLE_FMT_FLTP) {
+        for (unsigned i=0; i < channels; i++) {
+            auto c = (float*)f.extended_data[i];
+            auto cIn = (float*)fIn.extended_data[i];
+            for (unsigned s=0; s < samplesPerChannel; s++) {
+                c[s] += cIn[s];
+            }
+        }
+    } else {
+        throw std::invalid_argument(std::string("Unsupported format for mixing: ") + av_get_sample_fmt_name(fmt));
+    }
 }
 
 VideoFrame::~VideoFrame()
diff --git a/test/unitTest/media/test_media_frame.cpp b/test/unitTest/media/test_media_frame.cpp
index 3a3c2a1948..241c90b875 100644
--- a/test/unitTest/media/test_media_frame.cpp
+++ b/test/unitTest/media/test_media_frame.cpp
@@ -27,6 +27,7 @@ extern "C" {
 #include <libavutil/pixfmt.h>
 }
 
+#include "audio/audiobuffer.h"
 #include "dring.h"
 #include "videomanager_interface.h"
 
@@ -43,9 +44,11 @@ public:
 
 private:
     void testCopy();
+    void testMix();
 
     CPPUNIT_TEST_SUITE(MediaFrameTest);
     CPPUNIT_TEST(testCopy);
+    CPPUNIT_TEST(testMix);
     CPPUNIT_TEST_SUITE_END();
 };
 
@@ -84,6 +87,39 @@ MediaFrameTest::testCopy()
     CPPUNIT_ASSERT(v2.pointer()->data[0][0] == 42);
 }
 
+void
+MediaFrameTest::testMix()
+{
+    const AudioFormat& format = AudioFormat::STEREO();
+    const int nbSamples = format.sample_rate / 50;
+    auto a1 = std::make_unique<DRing::AudioFrame>(format, nbSamples);
+    auto d1 = reinterpret_cast<AudioSample*>(a1->pointer()->extended_data[0]);
+    d1[0] = 0;
+    d1[1] = 1;
+    d1[2] = 3;
+    d1[3] = -2;
+    d1[4] = 5;
+    d1[5] = std::numeric_limits<AudioSample>::min();
+    d1[6] = std::numeric_limits<AudioSample>::max();
+    auto a2 = std::make_unique<DRing::AudioFrame>(format, nbSamples);
+    auto d2 = reinterpret_cast<AudioSample*>(a2->pointer()->extended_data[0]);
+    d2[0] = 0;
+    d2[1] = 3;
+    d2[2] = -1;
+    d2[3] = 3;
+    d2[4] = -6;
+    d2[5] = -101;
+    d2[6] = 101;
+    a2->mix(*a1);
+    CPPUNIT_ASSERT(d2[0] == 0);
+    CPPUNIT_ASSERT(d2[1] == 4);
+    CPPUNIT_ASSERT(d2[2] == 2);
+    CPPUNIT_ASSERT(d2[3] == 1);
+    CPPUNIT_ASSERT(d2[4] == -1);
+    CPPUNIT_ASSERT(d2[5] == std::numeric_limits<AudioSample>::min());
+    CPPUNIT_ASSERT(d2[6] == std::numeric_limits<AudioSample>::max());
+}
+
 }} // namespace ring::test
 
 RING_TEST_RUNNER(ring::test::MediaFrameTest::name());
-- 
GitLab