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