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