2 Copyright (C) 2020-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of DCP-o-matic.
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.
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.
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/>.
22 /** @file test/video_level_test.cc
23 * @brief Test that video level ranges are handled correctly.
28 #include "lib/content_factory.h"
29 #include "lib/content_video.h"
30 #include "lib/dcp_content.h"
31 #include "lib/decoder_factory.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"
48 #include <dcp/mono_picture_asset.h>
49 #include <dcp/mono_picture_frame.h>
50 #include <dcp/openjpeg_image.h>
52 #include <dcp/reel_picture_asset.h>
53 #include <boost/test/unit_test.hpp>
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;
67 using std::shared_ptr;
72 grey_image (dcp::Size size, uint8_t pixel)
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) {
88 BOOST_AUTO_TEST_CASE (ffmpeg_image_full_range_not_changed)
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";
94 write_image (grey_image(size, grey_pixel), file);
96 FFmpegImageProxy proxy (file);
97 ImageProxy::Result result = proxy.image ();
98 BOOST_REQUIRE (!result.error);
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);
109 BOOST_AUTO_TEST_CASE (ffmpeg_image_video_range_expanded)
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";
116 write_image(grey_image(size, grey_pixel), file);
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());
123 shared_ptr<PlayerVideo> player_video;
124 player->Video.connect([&player_video](shared_ptr<PlayerVideo> pv, dcpomatic::DCPTime) {
127 while (!player_video) {
128 BOOST_REQUIRE (!player->pass());
131 auto image = player_video->image ([](AVPixelFormat f) { return f; }, VideoRange::FULL, true, false);
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);
144 pixel_range (shared_ptr<const Image> image)
146 pair<int, int> range(INT_MAX, 0);
147 switch (image->pixel_format()) {
148 case AV_PIX_FMT_RGB24:
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));
161 case AV_PIX_FMT_YUV444P:
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));
176 case AV_PIX_FMT_YUV422P10LE:
177 case AV_PIX_FMT_YUV444P10LE:
178 case AV_PIX_FMT_YUV444P12LE:
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));
194 BOOST_REQUIRE_MESSAGE (false, "No support for pixel format " << image->pixel_format());
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.
206 pixel_range (shared_ptr<const Film> film, shared_ptr<const Content> content)
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) {
213 while (!content_video) {
214 BOOST_REQUIRE (!decoder->pass());
217 return pixel_range (content_video->image->image().image);
223 pixel_range (boost::filesystem::path dcp_path)
225 dcp::DCP dcp (dcp_path);
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();
232 int const width = frame->size().width;
233 int const height = frame->size().height;
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);
251 /* Functions to make a Film with different sorts of content.
253 * In these names V = video range (limited)
254 * F = full range (not limited)
261 movie_V (string name)
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());
269 auto range = pixel_range (film, content);
270 BOOST_CHECK_EQUAL (range.first, 15);
271 BOOST_CHECK_EQUAL (range.second, 243);
279 movie_VoF (string name)
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);
288 auto range = pixel_range (film, content);
289 BOOST_CHECK_EQUAL (range.first, 15);
290 BOOST_CHECK_EQUAL (range.second, 243);
298 movie_F (string name)
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());
306 auto range = pixel_range (film, content);
307 BOOST_CHECK_EQUAL (range.first, 0);
308 BOOST_CHECK_EQUAL (range.second, 1023);
316 movie_FoV (string name)
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);
325 auto range = pixel_range (film, content);
326 BOOST_CHECK_EQUAL (range.first, 0);
327 BOOST_CHECK_EQUAL (range.second, 1023);
335 image_F (string name)
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());
343 auto range = pixel_range (film, content);
344 BOOST_CHECK_EQUAL (range.first, 0);
345 BOOST_CHECK_EQUAL (range.second, 255);
353 image_FoV (string name)
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);
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.
366 BOOST_CHECK_EQUAL (range.first, 0);
367 BOOST_CHECK_EQUAL (range.second, 255);
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());
383 auto range = pixel_range (dcp);
384 BOOST_CHECK_EQUAL (range.first, 0);
385 BOOST_CHECK_EQUAL (range.second, 4081);
392 /* Functions to get the pixel range in different sorts of output */
395 /** Get the pixel range in a DCP made from film */
398 dcp_range (shared_ptr<Film> film)
400 make_and_verify_dcp (film);
401 return pixel_range (film->dir(film->dcp_name()));
405 /** Get the pixel range in a video-range movie exported from film */
408 V_movie_range (shared_ptr<Film> film)
410 auto job = make_shared<TranscodeJob>(film);
412 make_shared<FFmpegEncoder>(film, job, film->file("export.mov"), ExportFormat::PRORES, true, false, false, 23)
414 JobManager::instance()->add (job);
415 BOOST_REQUIRE (!wait_for_jobs());
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());
422 return pixel_range (film, content);
429 BOOST_AUTO_TEST_CASE (movie_V_to_dcp)
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);
437 BOOST_AUTO_TEST_CASE (movie_VoF_to_dcp)
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.
443 check_int_close (range, {350, 3832}, 2);
447 BOOST_AUTO_TEST_CASE (movie_F_to_dcp)
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);
455 BOOST_AUTO_TEST_CASE (video_FoV_to_dcp)
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);
463 BOOST_AUTO_TEST_CASE (image_F_to_dcp)
465 auto range = dcp_range (image_F("image_F_to_dcp"));
466 check_int_close (range, {0, 4083}, 3);
470 BOOST_AUTO_TEST_CASE (image_FoV_to_dcp)
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.
476 check_int_close (range, {0, 4095}, 16);
480 BOOST_AUTO_TEST_CASE (movie_V_to_V_movie)
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);
488 BOOST_AUTO_TEST_CASE (movie_VoF_to_V_movie)
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);
496 BOOST_AUTO_TEST_CASE (movie_F_to_V_movie)
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);
504 BOOST_AUTO_TEST_CASE (movie_FoV_to_V_movie)
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);
512 BOOST_AUTO_TEST_CASE (image_F_to_V_movie)
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);
520 BOOST_AUTO_TEST_CASE (image_FoV_to_V_movie)
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);
528 BOOST_AUTO_TEST_CASE (dcp_F_to_V_movie)
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);