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());
|