LCOV - code coverage report
Current view: top level - test/unitTest/media - test_media_filter.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 184 184 100.0 %
Date: 2024-12-21 08:56:24 Functions: 16 16 100.0 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2024 Savoir-faire Linux Inc.
       3             :  *
       4             :  *  This program is free software: you can redistribute it and/or modify
       5             :  *  it under the terms of the GNU General Public License as published by
       6             :  *  the Free Software Foundation, either version 3 of the License, or
       7             :  *  (at your option) any later version.
       8             :  *
       9             :  *  This program is distributed in the hope that it will be useful,
      10             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      12             :  *  GNU General Public License for more details.
      13             :  *
      14             :  *  You should have received a copy of the GNU General Public License
      15             :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include <cppunit/TestAssert.h>
      19             : #include <cppunit/TestFixture.h>
      20             : #include <cppunit/extensions/HelperMacros.h>
      21             : 
      22             : #include "jami.h"
      23             : #include "media/libav_deps.h"
      24             : #include "media/media_buffer.h"
      25             : #include "media/media_filter.h"
      26             : 
      27             : #include "../../test_runner.h"
      28             : 
      29             : namespace jami { namespace test {
      30             : 
      31             : class MediaFilterTest : public CppUnit::TestFixture {
      32             : public:
      33           2 :     static std::string name() { return "media_filter"; }
      34             : 
      35             :     void setUp();
      36             :     void tearDown();
      37             : 
      38             : private:
      39             :     void testAudioFilter();
      40             :     void testAudioMixing();
      41             :     void testVideoFilter();
      42             :     void testFilterParams();
      43             :     void testReinit();
      44             : 
      45           2 :     CPPUNIT_TEST_SUITE(MediaFilterTest);
      46           1 :     CPPUNIT_TEST(testAudioFilter);
      47           1 :     CPPUNIT_TEST(testAudioMixing);
      48           1 :     CPPUNIT_TEST(testVideoFilter);
      49           1 :     CPPUNIT_TEST(testFilterParams);
      50           1 :     CPPUNIT_TEST(testReinit);
      51           4 :     CPPUNIT_TEST_SUITE_END();
      52             : 
      53             :     std::unique_ptr<MediaFilter> filter_;
      54             : };
      55             : 
      56             : CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(MediaFilterTest, MediaFilterTest::name());
      57             : 
      58             : void
      59           5 : MediaFilterTest::setUp()
      60             : {
      61           5 :     libjami::init(libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
      62           5 :     libav_utils::av_init();
      63           5 :     filter_.reset(new MediaFilter);
      64           5 : }
      65             : 
      66             : void
      67           5 : MediaFilterTest::tearDown()
      68             : {
      69           5 :     libjami::fini();
      70           5 : }
      71             : 
      72             : static void
      73           2 : fill_yuv_image(uint8_t *data[4], int linesize[4], int width, int height, int frame_index)
      74             : {
      75             :     int x, y;
      76             : 
      77             :     /* Y */
      78         272 :     for (y = 0; y < height; y++)
      79       77970 :         for (x = 0; x < width; x++)
      80       77700 :             data[0][y * linesize[0] + x] = x + y + frame_index * 3;
      81             : 
      82             :     /* Cb and Cr */
      83         137 :     for (y = 0; y < height / 2; y++) {
      84       19560 :         for (x = 0; x < width / 2; x++) {
      85       19425 :             data[1][y * linesize[1] + x] = 128 + y + frame_index * 2;
      86       19425 :             data[2][y * linesize[2] + x] = 64 + x + frame_index * 5;
      87             :         }
      88             :     }
      89           2 : }
      90             : 
      91             : static void
      92         302 : fill_samples(uint16_t* samples, int sampleRate, int nbSamples, int nbChannels, float tone, float& t)
      93             : {
      94         302 :     const constexpr double pi = 3.14159265358979323846264338327950288; // M_PI
      95         302 :     const float tincr = 2 * pi * tone / sampleRate;
      96             : 
      97      288502 :     for (int j = 0; j < nbSamples; ++j) {
      98      288200 :         samples[2 * j] = (int)(sin(t) * 10000);
      99      576400 :         for (int k = 1; k < nbChannels; ++k)
     100      288200 :             samples[2 * j + k] = samples[2 * j];
     101      288200 :         t += tincr;
     102             :     }
     103         302 : }
     104             : 
     105             : static void
     106           2 : fill_samples(uint16_t* samples, int sampleRate, int nbSamples, int nbChannels, float tone)
     107             : {
     108           2 :     float t = 0;
     109           2 :     fill_samples(samples, sampleRate, nbSamples, nbChannels, tone, t);
     110           2 : }
     111             : 
     112             : static void
     113         300 : fillAudioFrameProps(AVFrame* frame, const MediaStream& ms)
     114             : {
     115         300 :     frame->format = ms.format;
     116         300 :     av_channel_layout_default(&frame->ch_layout, ms.nbChannels);
     117         300 :     frame->nb_samples = ms.frameSize;
     118         300 :     frame->sample_rate = ms.sampleRate;
     119         300 :     CPPUNIT_ASSERT(frame->format > AV_SAMPLE_FMT_NONE);
     120         300 :     CPPUNIT_ASSERT(frame->nb_samples > 0);
     121         300 :     CPPUNIT_ASSERT(frame->ch_layout.u.mask != 0);
     122         300 : }
     123             : 
     124             : void
     125           1 : MediaFilterTest::testAudioFilter()
     126             : {
     127           1 :     std::string filterSpec = "[in1] aformat=sample_fmts=u8";
     128             : 
     129             :     // constants
     130           1 :     const constexpr int nbSamples = 100;
     131           1 :     const constexpr int sampleRate = 44100;
     132           1 :     const constexpr enum AVSampleFormat format = AV_SAMPLE_FMT_S16;
     133             : 
     134             :     // prepare audio frame
     135           1 :     AudioFrame af;
     136           1 :     auto frame = af.pointer();
     137           1 :     frame->format = format;
     138           1 :     av_channel_layout_from_mask(&frame->ch_layout, AV_CH_LAYOUT_STEREO);
     139           1 :     frame->nb_samples = nbSamples;
     140           1 :     frame->sample_rate = sampleRate;
     141             : 
     142             :     // construct the filter parameters
     143           2 :     auto params = MediaStream("in1", format, rational<int>(1, sampleRate), sampleRate, frame->ch_layout.nb_channels, nbSamples);
     144             : 
     145             :     // allocate and fill frame buffers
     146           1 :     CPPUNIT_ASSERT(av_frame_get_buffer(frame, 0) >= 0);
     147           1 :     fill_samples(reinterpret_cast<uint16_t*>(frame->data[0]), sampleRate, nbSamples, frame->ch_layout.nb_channels, 440.0);
     148             : 
     149             :     // prepare filter
     150           1 :     std::vector<MediaStream> vec;
     151           1 :     vec.push_back(params);
     152           1 :     CPPUNIT_ASSERT(filter_->initialize(filterSpec, vec) >= 0);
     153             : 
     154             :     // apply filter
     155           1 :     CPPUNIT_ASSERT(filter_->feedInput(frame, "in1") >= 0);
     156           1 :     auto out = filter_->readOutput();
     157           1 :     CPPUNIT_ASSERT(out);
     158           1 :     CPPUNIT_ASSERT(out->pointer());
     159             : 
     160             :     // check if the filter worked
     161           1 :     CPPUNIT_ASSERT(out->pointer()->format == AV_SAMPLE_FMT_U8);
     162           1 : }
     163             : 
     164             : void
     165           1 : MediaFilterTest::testAudioMixing()
     166             : {
     167           1 :     std::string filterSpec = "[a1] [a2] [a3] amix=inputs=3,aformat=sample_fmts=s16";
     168             : 
     169           1 :     AudioFrame af1;
     170           1 :     auto frame1 = af1.pointer();
     171           1 :     AudioFrame af2;
     172           1 :     auto frame2 = af2.pointer();
     173           1 :     AudioFrame af3;
     174           1 :     auto frame3 = af3.pointer();
     175             : 
     176           1 :     std::vector<MediaStream> vec;
     177           1 :     vec.emplace_back("a1", AV_SAMPLE_FMT_S16, rational<int>(1, 48000), 48000, 2, 960);
     178           1 :     vec.emplace_back("a2", AV_SAMPLE_FMT_S16, rational<int>(1, 48000), 48000, 2, 960);
     179           1 :     vec.emplace_back("a3", AV_SAMPLE_FMT_S16, rational<int>(1, 48000), 48000, 2, 960);
     180           1 :     CPPUNIT_ASSERT(filter_->initialize(filterSpec, vec) >= 0);
     181             : 
     182           1 :     float t1 = 0, t2 = 0, t3 = 0;
     183         101 :     for (int i = 0; i < 100; ++i) {
     184         100 :         fillAudioFrameProps(frame1, vec[0]);
     185         100 :         frame1->pts = i * frame1->nb_samples;
     186         100 :         CPPUNIT_ASSERT(av_frame_get_buffer(frame1, 0) >= 0);
     187         100 :         fill_samples(reinterpret_cast<uint16_t*>(frame1->data[0]), frame1->sample_rate, frame1->nb_samples, frame1->ch_layout.nb_channels, 440.0, t1);
     188             : 
     189         100 :         fillAudioFrameProps(frame2, vec[1]);
     190         100 :         frame2->pts = i * frame2->nb_samples;
     191         100 :         CPPUNIT_ASSERT(av_frame_get_buffer(frame2, 0) >= 0);
     192         100 :         fill_samples(reinterpret_cast<uint16_t*>(frame2->data[0]), frame2->sample_rate, frame2->nb_samples, frame2->ch_layout.nb_channels, 329.6276, t2);
     193             : 
     194         100 :         fillAudioFrameProps(frame3, vec[2]);
     195         100 :         frame3->pts = i * frame3->nb_samples;
     196         100 :         CPPUNIT_ASSERT(av_frame_get_buffer(frame3, 0) >= 0);
     197         100 :         fill_samples(reinterpret_cast<uint16_t*>(frame3->data[0]), frame3->sample_rate, frame3->nb_samples, frame3->ch_layout.nb_channels, 349.2282, t3);
     198             : 
     199             :         // apply filter
     200         100 :         CPPUNIT_ASSERT(filter_->feedInput(frame1, "a1") >= 0);
     201         100 :         CPPUNIT_ASSERT(filter_->feedInput(frame2, "a2") >= 0);
     202         100 :         CPPUNIT_ASSERT(filter_->feedInput(frame3, "a3") >= 0);
     203             : 
     204             :         // read output
     205         100 :         auto out = filter_->readOutput();
     206         100 :         CPPUNIT_ASSERT(out);
     207         100 :         CPPUNIT_ASSERT(out->pointer());
     208             : 
     209         100 :         av_frame_unref(frame1);
     210         100 :         av_frame_unref(frame2);
     211         100 :         av_frame_unref(frame3);
     212         100 :     }
     213           1 : }
     214             : 
     215             : void
     216           1 : MediaFilterTest::testVideoFilter()
     217             : {
     218           1 :     std::string filterSpec = "[main] [top] overlay=main_w-overlay_w-10:main_h-overlay_h-10";
     219           1 :     std::string main = "main";
     220           1 :     std::string top = "top";
     221             : 
     222             :     // constants
     223           1 :     const constexpr int width1 = 320;
     224           1 :     const constexpr int height1 = 240;
     225           1 :     const constexpr int width2 = 30;
     226           1 :     const constexpr int height2 = 30;
     227           1 :     const constexpr AVPixelFormat format = AV_PIX_FMT_YUV420P;
     228             : 
     229             :     // prepare video frame
     230           1 :     libjami::VideoFrame vf1;
     231           1 :     auto frame = vf1.pointer();
     232           1 :     frame->format = format;
     233           1 :     frame->width = width1;
     234           1 :     frame->height = height1;
     235           1 :     libjami::VideoFrame vf2;
     236           1 :     auto extra = vf2.pointer();
     237           1 :     extra->format = format;
     238           1 :     extra->width = width2;
     239           1 :     extra->height = height2;
     240             : 
     241             :     // construct the filter parameters
     242           1 :     rational<int> one = rational<int>(1);
     243           2 :     auto params1 = MediaStream("main", format, one, width1, height1, one.real<int>(), one);
     244           2 :     auto params2 = MediaStream("top", format, one, width2, height2, one.real<int>(), one);
     245             : 
     246             :     // allocate and fill frame buffers
     247           1 :     CPPUNIT_ASSERT(av_frame_get_buffer(frame, 32) >= 0);
     248           1 :     fill_yuv_image(frame->data, frame->linesize, frame->width, frame->height, 0);
     249           1 :     CPPUNIT_ASSERT(av_frame_get_buffer(extra, 32) >= 0);
     250           1 :     fill_yuv_image(extra->data, extra->linesize, extra->width, extra->height, 0);
     251             : 
     252             :     // prepare filter
     253           1 :     auto vec = std::vector<MediaStream>();
     254           1 :     vec.push_back(params2); // order does not matter, as long as names match
     255           1 :     vec.push_back(params1);
     256           1 :     CPPUNIT_ASSERT(filter_->initialize(filterSpec, vec) >= 0);
     257             : 
     258             :     // apply filter
     259           1 :     CPPUNIT_ASSERT(filter_->feedInput(frame, main) >= 0);
     260           1 :     CPPUNIT_ASSERT(filter_->feedInput(extra, top) >= 0);
     261           1 :     auto out = filter_->readOutput();
     262           1 :     CPPUNIT_ASSERT(out);
     263           1 :     CPPUNIT_ASSERT(out->pointer());
     264             : 
     265             :     // check if the filter worked
     266           1 :     CPPUNIT_ASSERT(out->pointer()->width == width1 && out->pointer()->height == height1);
     267           1 : }
     268             : 
     269             : void
     270           1 : MediaFilterTest::testFilterParams()
     271             : {
     272           1 :     std::string filterSpec = "[main] [top] overlay=main_w-overlay_w-10:main_h-overlay_h-10";
     273             : 
     274             :     // constants
     275           1 :     const constexpr int width1 = 320;
     276           1 :     const constexpr int height1 = 240;
     277           1 :     const constexpr int width2 = 30;
     278           1 :     const constexpr int height2 = 30;
     279           1 :     const constexpr AVPixelFormat format = AV_PIX_FMT_YUV420P;
     280             : 
     281             :     // construct the filter parameters
     282           1 :     rational<int> one = rational<int>(1);
     283           2 :     auto params1 = MediaStream("main", format, one, width1, height1, one.real<int>(), one);
     284           2 :     auto params2 = MediaStream("top", format, one, width2, height2, one.real<int>(), one);
     285             : 
     286             :     // returned params should be invalid
     287           1 :     CPPUNIT_ASSERT(filter_->getOutputParams().format < 0);
     288             : 
     289             :     // prepare filter
     290           1 :     auto vec = std::vector<MediaStream>();
     291           1 :     vec.push_back(params2); // order does not matter, as long as names match
     292           1 :     vec.push_back(params1);
     293           1 :     CPPUNIT_ASSERT(filter_->initialize(filterSpec, vec) >= 0);
     294             : 
     295             :     // check input params
     296           2 :     auto main = filter_->getInputParams("main");
     297           1 :     CPPUNIT_ASSERT(main.format == format && main.width == width1 && main.height == height1);
     298           2 :     auto top = filter_->getInputParams("top");
     299           1 :     CPPUNIT_ASSERT(top.format == format && top.width == width2 && top.height == height2);
     300             : 
     301             :     // output params should now be valid
     302           1 :     auto ms = filter_->getOutputParams();
     303           1 :     CPPUNIT_ASSERT(ms.format >= 0 && ms.width == width1 && ms.height == height1);
     304           1 : }
     305             : 
     306             : void
     307           1 : MediaFilterTest::testReinit()
     308             : {
     309           1 :     std::string filterSpec = "[in1] aresample=48000";
     310             : 
     311             :     // prepare audio frame
     312           1 :     AudioFrame af;
     313           1 :     auto frame = af.pointer();
     314           1 :     frame->format = AV_SAMPLE_FMT_S16;
     315           1 :     av_channel_layout_from_mask(&frame->ch_layout, AV_CH_LAYOUT_STEREO);
     316           1 :     frame->nb_samples = 100;
     317           1 :     frame->sample_rate = 44100;
     318             : 
     319             :     // construct the filter parameters with different sample rate
     320           2 :     auto params = MediaStream("in1", frame->format, rational<int>(1, 16000), 16000, frame->ch_layout.nb_channels, frame->nb_samples);
     321             : 
     322             :     // allocate and fill frame buffers
     323           1 :     CPPUNIT_ASSERT(av_frame_get_buffer(frame, 0) >= 0);
     324           1 :     fill_samples(reinterpret_cast<uint16_t*>(frame->data[0]), frame->sample_rate, frame->nb_samples, frame->ch_layout.nb_channels, 440.0);
     325             : 
     326             :     // prepare filter
     327           1 :     std::vector<MediaStream> vec;
     328           1 :     vec.push_back(params);
     329           1 :     CPPUNIT_ASSERT(filter_->initialize(filterSpec, vec) >= 0);
     330             : 
     331             :     // filter should reinitialize on feedInput
     332           1 :     CPPUNIT_ASSERT(filter_->feedInput(frame, "in1") >= 0);
     333           1 : }
     334             : 
     335             : }} // namespace jami::test
     336             : 
     337           1 : RING_TEST_RUNNER(jami::test::MediaFilterTest::name());

Generated by: LCOV version 1.14