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