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