diff --git a/src/client/videomanager.cpp b/src/client/videomanager.cpp index 3c646806ee69d10a10f2f6f4e72c8b8d91b4cc8c..8265bf8cfae3d4da7f7b8727bc0675e5d1a16dd0 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 3a3c2a1948f6e2ee61cc35277805b9c2a329ed7a..241c90b8752822e5c9fa3446c977ff60e1ba0726 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());