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