Fix typo if -> of (thanks to Uwe Dittes)
[dcpomatic.git] / test / video_level_test.cc
1 /*
2     Copyright (C) 2020-2021 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic 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 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic 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 DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21
22 /** @file  test/video_level_test.cc
23  *  @brief Test that video level ranges are handled correctly.
24  *  @ingroup feature
25  */
26
27
28 #include "lib/content_factory.h"
29 #include "lib/content_video.h"
30 #include "lib/dcp_content.h"
31 #include "lib/decoder_factory.h"
32 #include "lib/film.h"
33 #include "lib/ffmpeg_content.h"
34 #include "lib/ffmpeg_decoder.h"
35 #include "lib/ffmpeg_image_proxy.h"
36 #include "lib/image.h"
37 #include "lib/image_content.h"
38 #include "lib/image_decoder.h"
39 #include "lib/ffmpeg_encoder.h"
40 #include "lib/job_manager.h"
41 #include "lib/player.h"
42 #include "lib/player_video.h"
43 #include "lib/transcode_job.h"
44 #include "lib/video_decoder.h"
45 #include "test.h"
46 #include <dcp/cpl.h>
47 #include <dcp/dcp.h>
48 #include <dcp/mono_picture_asset.h>
49 #include <dcp/mono_picture_frame.h>
50 #include <dcp/openjpeg_image.h>
51 #include <dcp/reel.h>
52 #include <dcp/reel_picture_asset.h>
53 #include <boost/test/unit_test.hpp>
54
55
56 using std::min;
57 using std::make_pair;
58 using std::max;
59 using std::pair;
60 using std::string;
61 using std::dynamic_pointer_cast;
62 using std::make_shared;
63 using boost::optional;
64 #if BOOST_VERSION >= 106100
65 using namespace boost::placeholders;
66 #endif
67 using std::shared_ptr;
68
69
70 static
71 shared_ptr<Image>
72 grey_image (dcp::Size size, uint8_t pixel)
73 {
74         auto grey = make_shared<Image>(AV_PIX_FMT_RGB24, size, true);
75         for (int y = 0; y < size.height; ++y) {
76                 uint8_t* p = grey->data()[0] + y * grey->stride()[0];
77                 for (int x = 0; x < size.width; ++x) {
78                         *p++ = pixel;
79                         *p++ = pixel;
80                         *p++ = pixel;
81                 }
82         }
83
84         return grey;
85 }
86
87
88 BOOST_AUTO_TEST_CASE (ffmpeg_image_full_range_not_changed)
89 {
90         dcp::Size size(640, 480);
91         uint8_t const grey_pixel = 128;
92         boost::filesystem::path const file = "build/test/ffmpeg_image_full_range_not_changed.png";
93
94         write_image (grey_image(size, grey_pixel), file);
95
96         FFmpegImageProxy proxy (file);
97         ImageProxy::Result result = proxy.image ();
98         BOOST_REQUIRE (!result.error);
99
100         for (int y = 0; y < size.height; ++y) {
101                 uint8_t* p = result.image->data()[0] + y * result.image->stride()[0];
102                 for (int x = 0; x < size.width; ++x) {
103                         BOOST_REQUIRE (*p++ == grey_pixel);
104                 }
105         }
106 }
107
108
109 BOOST_AUTO_TEST_CASE (ffmpeg_image_video_range_expanded)
110 {
111         dcp::Size size(1998, 1080);
112         uint8_t const grey_pixel = 128;
113         uint8_t const expanded_grey_pixel = static_cast<uint8_t>(lrintf((grey_pixel - 16) * 256.0 / 219));
114         boost::filesystem::path const file = "build/test/ffmpeg_image_video_range_expanded.png";
115
116         write_image(grey_image(size, grey_pixel), file);
117
118         auto content = content_factory(file).front();
119         auto film = new_test_film2 ("ffmpeg_image_video_range_expanded", { content });
120         content->video->set_range (VideoRange::VIDEO);
121         auto player = make_shared<Player>(film, film->playlist());
122
123         shared_ptr<PlayerVideo> player_video;
124         player->Video.connect([&player_video](shared_ptr<PlayerVideo> pv, dcpomatic::DCPTime) {
125                 player_video = pv;
126         });
127         while (!player_video) {
128                 BOOST_REQUIRE (!player->pass());
129         }
130
131         auto image = player_video->image ([](AVPixelFormat f) { return f; }, VideoRange::FULL, true, false);
132
133         for (int y = 0; y < size.height; ++y) {
134                 uint8_t* p = image->data()[0] + y * image->stride()[0];
135                 for (int x = 0; x < size.width; ++x) {
136                         BOOST_REQUIRE_EQUAL (*p++, expanded_grey_pixel);
137                 }
138         }
139 }
140
141
142 static
143 pair<int, int>
144 pixel_range (shared_ptr<const Image> image)
145 {
146         pair<int, int> range(INT_MAX, 0);
147         switch (image->pixel_format()) {
148         case AV_PIX_FMT_RGB24:
149         {
150                 dcp::Size const size = image->sample_size(0);
151                 for (int y = 0; y < size.height; ++y) {
152                         uint8_t* p = image->data()[0] + y * image->stride()[0];
153                         for (int x = 0; x < size.width * 3; ++x) {
154                                 range.first = min(range.first, static_cast<int>(*p));
155                                 range.second = max(range.second, static_cast<int>(*p));
156                                 ++p;
157                         }
158                 }
159                 break;
160         }
161         case AV_PIX_FMT_YUV444P:
162         {
163                 for (int c = 0; c < 3; ++c) {
164                         dcp::Size const size = image->sample_size(c);
165                         for (int y = 0; y < size.height; ++y) {
166                                 uint8_t* p = image->data()[c] + y * image->stride()[c];
167                                 for (int x = 0; x < size.width; ++x) {
168                                         range.first = min(range.first, static_cast<int>(*p));
169                                         range.second = max(range.second, static_cast<int>(*p));
170                                         ++p;
171                                 }
172                         }
173                 }
174                 break;
175         }
176         case AV_PIX_FMT_YUV422P10LE:
177         case AV_PIX_FMT_YUV444P10LE:
178         case AV_PIX_FMT_YUV444P12LE:
179         {
180                 for (int c = 0; c < 3; ++c) {
181                         dcp::Size const size = image->sample_size(c);
182                         for (int y = 0; y < size.height; ++y) {
183                                 uint16_t* p = reinterpret_cast<uint16_t*>(image->data()[c]) + y * image->stride()[c] / 2;
184                                 for (int x = 0; x < size.width; ++x) {
185                                         range.first = min(range.first, static_cast<int>(*p));
186                                         range.second = max(range.second, static_cast<int>(*p));
187                                         ++p;
188                                 }
189                         }
190                 }
191                 break;
192         }
193         default:
194                 BOOST_REQUIRE_MESSAGE (false, "No support for pixel format " << image->pixel_format());
195         }
196
197         return range;
198 }
199
200
201 /** @return pixel range of the first frame in @ref content in its raw form, i.e.
202  *  straight out of the decoder with no level processing, scaling etc.
203  */
204 static
205 pair<int, int>
206 pixel_range (shared_ptr<const Film> film, shared_ptr<const Content> content)
207 {
208         auto decoder = decoder_factory(film, content, false, false, shared_ptr<Decoder>());
209         optional<ContentVideo> content_video;
210         decoder->video->Data.connect ([&content_video](ContentVideo cv) {
211                 content_video = cv;
212         });
213         while (!content_video) {
214                 BOOST_REQUIRE (!decoder->pass());
215         }
216
217         return pixel_range (content_video->image->image().image);
218 }
219
220
221 static
222 pair<int, int>
223 pixel_range (boost::filesystem::path dcp_path)
224 {
225         dcp::DCP dcp (dcp_path);
226         dcp.read ();
227
228         auto picture = dynamic_pointer_cast<dcp::MonoPictureAsset>(dcp.cpls().front()->reels().front()->main_picture()->asset());
229         BOOST_REQUIRE (picture);
230         auto frame = picture->start_read()->get_frame(0)->xyz_image();
231
232         int const width = frame->size().width;
233         int const height = frame->size().height;
234
235         pair<int, int> range(INT_MAX, 0);
236         for (int c = 0; c < 3; ++c) {
237                 for (int y = 0; y < height; ++y) {
238                         int* p = frame->data(c) + y * width;
239                         for (int x = 0; x < width; ++x) {
240                                 range.first = min(range.first, *p);
241                                 range.second = max(range.second, *p);
242                                 ++p;
243                         }
244                 }
245         }
246
247         return range;
248 }
249
250
251 /* Functions to make a Film with different sorts of content.
252  *
253  * In these names V = video range (limited)
254  *                F = full range  (not limited)
255  *                o = overridden
256  */
257
258
259 static
260 shared_ptr<Film>
261 movie_V (string name)
262 {
263         auto film = new_test_film2 (name);
264         auto content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mp4").front());
265         BOOST_REQUIRE (content);
266         film->examine_and_add_content (content);
267         BOOST_REQUIRE (!wait_for_jobs());
268
269         auto range = pixel_range (film, content);
270         BOOST_CHECK_EQUAL (range.first, 15);
271         BOOST_CHECK_EQUAL (range.second, 243);
272
273         return film;
274 }
275
276
277 static
278 shared_ptr<Film>
279 movie_VoF (string name)
280 {
281         auto film = new_test_film2 (name);
282         auto content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mp4").front());
283         BOOST_REQUIRE (content);
284         film->examine_and_add_content (content);
285         BOOST_REQUIRE (!wait_for_jobs());
286         content->video->set_range (VideoRange::FULL);
287
288         auto range = pixel_range (film, content);
289         BOOST_CHECK_EQUAL (range.first, 15);
290         BOOST_CHECK_EQUAL (range.second, 243);
291
292         return film;
293 }
294
295
296 static
297 shared_ptr<Film>
298 movie_F (string name)
299 {
300         auto film = new_test_film2 (name);
301         auto content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mov").front());
302         BOOST_REQUIRE (content);
303         film->examine_and_add_content (content);
304         BOOST_REQUIRE (!wait_for_jobs());
305
306         auto range = pixel_range (film, content);
307         BOOST_CHECK_EQUAL (range.first, 0);
308         BOOST_CHECK_EQUAL (range.second, 1023);
309
310         return film;
311 }
312
313
314 static
315 shared_ptr<Film>
316 movie_FoV (string name)
317 {
318         auto film = new_test_film2 (name);
319         auto content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mov").front());
320         BOOST_REQUIRE (content);
321         film->examine_and_add_content (content);
322         BOOST_REQUIRE (!wait_for_jobs());
323         content->video->set_range (VideoRange::VIDEO);
324
325         auto range = pixel_range (film, content);
326         BOOST_CHECK_EQUAL (range.first, 0);
327         BOOST_CHECK_EQUAL (range.second, 1023);
328
329         return film;
330 }
331
332
333 static
334 shared_ptr<Film>
335 image_F (string name)
336 {
337         auto film = new_test_film2 (name);
338         auto content = dynamic_pointer_cast<ImageContent>(content_factory("test/data/rgb_grey_testcard.png").front());
339         BOOST_REQUIRE (content);
340         film->examine_and_add_content (content);
341         BOOST_REQUIRE (!wait_for_jobs());
342
343         auto range = pixel_range (film, content);
344         BOOST_CHECK_EQUAL (range.first, 0);
345         BOOST_CHECK_EQUAL (range.second, 255);
346
347         return film;
348 }
349
350
351 static
352 shared_ptr<Film>
353 image_FoV (string name)
354 {
355         auto film = new_test_film2 (name);
356         auto content = dynamic_pointer_cast<ImageContent>(content_factory("test/data/rgb_grey_testcard.png").front());
357         BOOST_REQUIRE (content);
358         film->examine_and_add_content (content);
359         BOOST_REQUIRE (!wait_for_jobs());
360         content->video->set_range (VideoRange::VIDEO);
361
362         auto range = pixel_range (film, content);
363         /* We are taking some full-range content and saying it should be read as video range, after which its
364          * pixels will still be full range.
365          */
366         BOOST_CHECK_EQUAL (range.first, 0);
367         BOOST_CHECK_EQUAL (range.second, 255);
368
369         return film;
370 }
371
372
373 static
374 shared_ptr<Film>
375 dcp_F (string name)
376 {
377         boost::filesystem::path const dcp = "test/data/RgbGreyTestcar_TST-1_F_MOS_2K_20201115_SMPTE_OV";
378         auto film = new_test_film2 (name);
379         auto content = make_shared<DCPContent>(dcp);
380         film->examine_and_add_content (content);
381         BOOST_REQUIRE (!wait_for_jobs());
382
383         auto range = pixel_range (dcp);
384         BOOST_CHECK_EQUAL (range.first, 0);
385         BOOST_CHECK_EQUAL (range.second, 4081);
386
387         return film;
388 }
389
390
391
392 /* Functions to get the pixel range in different sorts of output */
393
394
395 /** Get the pixel range in a DCP made from film */
396 static
397 pair<int, int>
398 dcp_range (shared_ptr<Film> film)
399 {
400         make_and_verify_dcp (film);
401         return pixel_range (film->dir(film->dcp_name()));
402 }
403
404
405 /** Get the pixel range in a video-range movie exported from film */
406 static
407 pair<int, int>
408 V_movie_range (shared_ptr<Film> film)
409 {
410         auto job = make_shared<TranscodeJob>(film);
411         job->set_encoder (
412                 make_shared<FFmpegEncoder>(film, job, film->file("export.mov"), ExportFormat::PRORES, true, false, false, 23)
413                 );
414         JobManager::instance()->add (job);
415         BOOST_REQUIRE (!wait_for_jobs());
416
417         /* This is a bit of a hack; add the exported file into the project so we can decode it */
418         auto content = make_shared<FFmpegContent>(film->file("export.mov"));
419         film->examine_and_add_content (content);
420         BOOST_REQUIRE (!wait_for_jobs());
421
422         return pixel_range (film, content);
423 }
424
425
426 /* The tests */
427
428
429 BOOST_AUTO_TEST_CASE (movie_V_to_dcp)
430 {
431         auto range = dcp_range (movie_V("movie_V_to_dcp"));
432         /* Video range has been correctly expanded to full for the DCP */
433         check_int_close (range, {0, 4083}, 2);
434 }
435
436
437 BOOST_AUTO_TEST_CASE (movie_VoF_to_dcp)
438 {
439         auto range = dcp_range (movie_VoF("movie_VoF_to_dcp"));
440         /* We said that video range data was really full range, so here we are in the DCP
441          * with video-range data.
442          */
443         check_int_close (range, {350, 3832}, 2);
444 }
445
446
447 BOOST_AUTO_TEST_CASE (movie_F_to_dcp)
448 {
449         auto range = dcp_range (movie_F("movie_F_to_dcp"));
450         /* The nearly-full-range of the input has been preserved */
451         check_int_close (range, {0, 4083}, 2);
452 }
453
454
455 BOOST_AUTO_TEST_CASE (video_FoV_to_dcp)
456 {
457         auto range = dcp_range (movie_FoV("video_FoV_to_dcp"));
458         /* The nearly-full-range of the input has become even more full, and clipped */
459         check_int_close (range, {0, 4095}, 2);
460 }
461
462
463 BOOST_AUTO_TEST_CASE (image_F_to_dcp)
464 {
465         auto range = dcp_range (image_F("image_F_to_dcp"));
466         check_int_close (range, {0, 4083}, 3);
467 }
468
469
470 BOOST_AUTO_TEST_CASE (image_FoV_to_dcp)
471 {
472         auto range = dcp_range (image_FoV("image_FoV_to_dcp"));
473         /* The nearly-full-range of the input has become even more full, and clipped.
474          * XXX: I'm not sure why this doesn't quite hit 4095.
475          */
476         check_int_close (range, {0, 4095}, 16);
477 }
478
479
480 BOOST_AUTO_TEST_CASE (movie_V_to_V_movie)
481 {
482         auto range = V_movie_range (movie_V("movie_V_to_V_movie"));
483         BOOST_CHECK_EQUAL (range.first, 60);
484         BOOST_CHECK_EQUAL (range.second, 998);
485 }
486
487
488 BOOST_AUTO_TEST_CASE (movie_VoF_to_V_movie)
489 {
490         auto range = V_movie_range (movie_VoF("movie_VoF_to_V_movie"));
491         BOOST_CHECK_EQUAL (range.first, 116);
492         BOOST_CHECK_EQUAL (range.second, 939);
493 }
494
495
496 BOOST_AUTO_TEST_CASE (movie_F_to_V_movie)
497 {
498         auto range = V_movie_range (movie_F("movie_F_to_V_movie"));
499         BOOST_CHECK_EQUAL (range.first, 4);
500         BOOST_CHECK_EQUAL (range.second, 1019);
501 }
502
503
504 BOOST_AUTO_TEST_CASE (movie_FoV_to_V_movie)
505 {
506         auto range = V_movie_range (movie_FoV("movie_FoV_to_V_movie"));
507         BOOST_CHECK_EQUAL (range.first, 4);
508         BOOST_CHECK_EQUAL (range.second, 1019);
509 }
510
511
512 BOOST_AUTO_TEST_CASE (image_F_to_V_movie)
513 {
514         auto range = V_movie_range (image_F("image_F_to_V_movie"));
515         BOOST_CHECK_EQUAL (range.first, 64);
516         BOOST_CHECK_EQUAL (range.second, 960);
517 }
518
519
520 BOOST_AUTO_TEST_CASE (image_FoV_to_V_movie)
521 {
522         auto range = V_movie_range (image_FoV("image_FoV_to_V_movie"));
523         BOOST_CHECK_EQUAL (range.first, 64);
524         BOOST_CHECK_EQUAL (range.second, 960);
525 }
526
527
528 BOOST_AUTO_TEST_CASE (dcp_F_to_V_movie)
529 {
530         auto range = V_movie_range (dcp_F("dcp_F_to_V_movie"));
531         BOOST_CHECK_EQUAL (range.first, 64);
532         BOOST_CHECK_EQUAL (range.second, 944);
533 }
534