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-04-20 08:06:59 Functions: 16 16 100.0 %

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

Generated by: LCOV version 1.14