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>
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;
66 using std::shared_ptr;
71 grey_image (dcp::Size size, uint8_t pixel)
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) {
87 BOOST_AUTO_TEST_CASE (ffmpeg_image_full_range_not_changed)
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";
93 write_image (grey_image(size, grey_pixel), file);
95 FFmpegImageProxy proxy (file);
96 ImageProxy::Result result = proxy.image (Image::Alignment::COMPACT);
97 BOOST_REQUIRE (!result.error);
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);
108 BOOST_AUTO_TEST_CASE (ffmpeg_image_video_range_expanded)
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";
115 write_image(grey_image(size, grey_pixel), file);
117 auto content = content_factory(file);
118 auto film = new_test_film2 ("ffmpeg_image_video_range_expanded", content);
119 content[0]->video->set_range (VideoRange::VIDEO);
120 auto player = make_shared<Player>(film, film->playlist());
122 shared_ptr<PlayerVideo> player_video;
123 player->Video.connect([&player_video](shared_ptr<PlayerVideo> pv, dcpomatic::DCPTime) {
126 while (!player_video) {
127 BOOST_REQUIRE (!player->pass());
130 auto image = player_video->image ([](AVPixelFormat f) { return f; }, VideoRange::FULL, false);
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);
141 BOOST_AUTO_TEST_CASE(yuv_expanded_into_full_rgb)
143 auto convert = [](int y_val, int u_val, int v_val, AVPixelFormat pix_fmt) {
144 auto const size = dcp::Size(640, 480);
145 auto yuv = make_shared<Image>(AV_PIX_FMT_YUVA444P12LE, size, Image::Alignment::PADDED);
146 BOOST_REQUIRE_EQUAL(yuv->planes(), 4);
147 for (int y = 0; y < size.height; ++y) {
148 uint16_t* Y = reinterpret_cast<uint16_t*>(yuv->data()[0] + y * yuv->stride()[0]);
149 uint16_t* U = reinterpret_cast<uint16_t*>(yuv->data()[1] + y * yuv->stride()[1]);
150 uint16_t* V = reinterpret_cast<uint16_t*>(yuv->data()[2] + y * yuv->stride()[2]);
151 uint16_t* A = reinterpret_cast<uint16_t*>(yuv->data()[3] + y * yuv->stride()[3]);
152 for (int x = 0; x < size.width; ++x) {
160 return yuv->crop_scale_window(
161 Crop(), size, size, dcp::YUVToRGB::REC709,
165 Image::Alignment::COMPACT,
170 auto white24 = convert(3760, 2048, 2048, AV_PIX_FMT_RGB24);
171 BOOST_CHECK_EQUAL(white24->data()[0][0], 255);
172 BOOST_CHECK_EQUAL(white24->data()[0][1], 255);
173 BOOST_CHECK_EQUAL(white24->data()[0][2], 255);
175 auto black24 = convert(256, 2048, 2048, AV_PIX_FMT_RGB24);
176 BOOST_CHECK_EQUAL(black24->data()[0][0], 0);
177 BOOST_CHECK_EQUAL(black24->data()[0][1], 0);
178 BOOST_CHECK_EQUAL(black24->data()[0][2], 0);
180 auto white48 = convert(3760, 2048, 2048, AV_PIX_FMT_RGB48LE);
181 BOOST_CHECK_EQUAL(reinterpret_cast<uint16_t*>(white48->data()[0])[0], 65283);
182 BOOST_CHECK_EQUAL(reinterpret_cast<uint16_t*>(white48->data()[0])[1], 65283);
183 BOOST_CHECK_EQUAL(reinterpret_cast<uint16_t*>(white48->data()[0])[2], 65283);
185 auto black48 = convert(256, 2048, 2048, AV_PIX_FMT_RGB48LE);
186 BOOST_CHECK_EQUAL(reinterpret_cast<uint16_t*>(black48->data()[0])[0], 0);
187 BOOST_CHECK_EQUAL(reinterpret_cast<uint16_t*>(black48->data()[0])[1], 0);
188 BOOST_CHECK_EQUAL(reinterpret_cast<uint16_t*>(black48->data()[0])[2], 0);
194 pixel_range (shared_ptr<const Image> image)
196 pair<int, int> range(INT_MAX, 0);
197 switch (image->pixel_format()) {
198 case AV_PIX_FMT_RGB24:
200 dcp::Size const size = image->sample_size(0);
201 for (int y = 0; y < size.height; ++y) {
202 uint8_t* p = image->data()[0] + y * image->stride()[0];
203 for (int x = 0; x < size.width * 3; ++x) {
204 range.first = min(range.first, static_cast<int>(*p));
205 range.second = max(range.second, static_cast<int>(*p));
211 case AV_PIX_FMT_YUV444P:
213 for (int c = 0; c < 3; ++c) {
214 dcp::Size const size = image->sample_size(c);
215 for (int y = 0; y < size.height; ++y) {
216 uint8_t* p = image->data()[c] + y * image->stride()[c];
217 for (int x = 0; x < size.width; ++x) {
218 range.first = min(range.first, static_cast<int>(*p));
219 range.second = max(range.second, static_cast<int>(*p));
226 case AV_PIX_FMT_YUV422P10LE:
227 case AV_PIX_FMT_YUV444P10LE:
228 case AV_PIX_FMT_YUV444P12LE:
230 for (int c = 0; c < 3; ++c) {
231 dcp::Size const size = image->sample_size(c);
232 for (int y = 0; y < size.height; ++y) {
233 uint16_t* p = reinterpret_cast<uint16_t*>(image->data()[c]) + y * image->stride()[c] / 2;
234 for (int x = 0; x < size.width; ++x) {
235 range.first = min(range.first, static_cast<int>(*p));
236 range.second = max(range.second, static_cast<int>(*p));
244 BOOST_REQUIRE_MESSAGE (false, "No support for pixel format " << image->pixel_format());
251 /** @return pixel range of the first frame in @ref content in its raw form, i.e.
252 * straight out of the decoder with no level processing, scaling etc.
256 pixel_range (shared_ptr<const Film> film, shared_ptr<const Content> content)
258 auto decoder = decoder_factory(film, content, false, false, shared_ptr<Decoder>());
259 optional<ContentVideo> content_video;
260 decoder->video->Data.connect ([&content_video](ContentVideo cv) {
263 while (!content_video) {
264 BOOST_REQUIRE (!decoder->pass());
267 return pixel_range (content_video->image->image(Image::Alignment::COMPACT).image);
273 pixel_range (boost::filesystem::path dcp_path)
275 dcp::DCP dcp (dcp_path);
278 auto picture = dynamic_pointer_cast<dcp::MonoPictureAsset>(dcp.cpls().front()->reels().front()->main_picture()->asset());
279 BOOST_REQUIRE (picture);
280 auto frame = picture->start_read()->get_frame(0)->xyz_image();
282 int const width = frame->size().width;
283 int const height = frame->size().height;
285 pair<int, int> range(INT_MAX, 0);
286 for (int c = 0; c < 3; ++c) {
287 for (int y = 0; y < height; ++y) {
288 int* p = frame->data(c) + y * width;
289 for (int x = 0; x < width; ++x) {
290 range.first = min(range.first, *p);
291 range.second = max(range.second, *p);
301 /* Functions to make a Film with different sorts of content.
303 * In these names V = video range (limited)
304 * F = full range (not limited)
311 movie_V (string name)
313 auto film = new_test_film2 (name);
314 auto content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mp4")[0]);
315 BOOST_REQUIRE (content);
316 film->examine_and_add_content (content);
317 BOOST_REQUIRE (!wait_for_jobs());
319 auto range = pixel_range (film, content);
320 BOOST_CHECK_EQUAL (range.first, 15);
321 BOOST_CHECK_EQUAL (range.second, 243);
329 movie_VoF (string name)
331 auto film = new_test_film2 (name);
332 auto content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mp4")[0]);
333 BOOST_REQUIRE (content);
334 film->examine_and_add_content (content);
335 BOOST_REQUIRE (!wait_for_jobs());
336 content->video->set_range (VideoRange::FULL);
338 auto range = pixel_range (film, content);
339 BOOST_CHECK_EQUAL (range.first, 15);
340 BOOST_CHECK_EQUAL (range.second, 243);
348 movie_F (string name)
350 auto film = new_test_film2 (name);
351 auto content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mov")[0]);
352 BOOST_REQUIRE (content);
353 film->examine_and_add_content (content);
354 BOOST_REQUIRE (!wait_for_jobs());
356 auto range = pixel_range (film, content);
357 BOOST_CHECK_EQUAL (range.first, 0);
358 BOOST_CHECK_EQUAL (range.second, 1023);
366 movie_FoV (string name)
368 auto film = new_test_film2 (name);
369 auto content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mov")[0]);
370 BOOST_REQUIRE (content);
371 film->examine_and_add_content (content);
372 BOOST_REQUIRE (!wait_for_jobs());
373 content->video->set_range (VideoRange::VIDEO);
375 auto range = pixel_range (film, content);
376 BOOST_CHECK_EQUAL (range.first, 0);
377 BOOST_CHECK_EQUAL (range.second, 1023);
385 image_F (string name)
387 auto film = new_test_film2 (name);
388 auto content = dynamic_pointer_cast<ImageContent>(content_factory("test/data/rgb_grey_testcard.png")[0]);
389 BOOST_REQUIRE (content);
390 film->examine_and_add_content (content);
391 BOOST_REQUIRE (!wait_for_jobs());
393 auto range = pixel_range (film, content);
394 BOOST_CHECK_EQUAL (range.first, 0);
395 BOOST_CHECK_EQUAL (range.second, 255);
403 image_FoV (string name)
405 auto film = new_test_film2 (name);
406 auto content = dynamic_pointer_cast<ImageContent>(content_factory("test/data/rgb_grey_testcard.png")[0]);
407 BOOST_REQUIRE (content);
408 film->examine_and_add_content (content);
409 BOOST_REQUIRE (!wait_for_jobs());
410 content->video->set_range (VideoRange::VIDEO);
412 auto range = pixel_range (film, content);
413 /* We are taking some full-range content and saying it should be read as video range, after which its
414 * pixels will still be full range.
416 BOOST_CHECK_EQUAL (range.first, 0);
417 BOOST_CHECK_EQUAL (range.second, 255);
427 boost::filesystem::path const dcp = "test/data/RgbGreyTestcar_TST-1_F_MOS_2K_20201115_SMPTE_OV";
428 auto film = new_test_film2 (name);
429 auto content = make_shared<DCPContent>(dcp);
430 film->examine_and_add_content (content);
431 BOOST_REQUIRE (!wait_for_jobs());
433 auto range = pixel_range (dcp);
434 BOOST_CHECK_EQUAL (range.first, 0);
435 BOOST_CHECK_EQUAL (range.second, 4081);
442 /* Functions to get the pixel range in different sorts of output */
445 /** Get the pixel range in a DCP made from film */
448 dcp_range (shared_ptr<Film> film)
450 make_and_verify_dcp (film);
451 return pixel_range (film->dir(film->dcp_name()));
455 /** Get the pixel range in a video-range movie exported from film */
458 V_movie_range (shared_ptr<Film> film)
460 auto job = make_shared<TranscodeJob>(film, TranscodeJob::ChangedBehaviour::IGNORE);
462 make_shared<FFmpegEncoder>(film, job, film->file("export.mov"), ExportFormat::PRORES_HQ, true, false, false, 23)
464 JobManager::instance()->add (job);
465 BOOST_REQUIRE (!wait_for_jobs());
467 /* This is a bit of a hack; add the exported file into the project so we can decode it */
468 auto content = make_shared<FFmpegContent>(film->file("export.mov"));
469 film->examine_and_add_content (content);
470 BOOST_REQUIRE (!wait_for_jobs());
472 return pixel_range (film, content);
479 BOOST_AUTO_TEST_CASE (movie_V_to_dcp)
481 auto range = dcp_range (movie_V("movie_V_to_dcp"));
482 /* Video range has been correctly expanded to full for the DCP */
483 check_int_close (range, {0, 4083}, 2);
487 BOOST_AUTO_TEST_CASE (movie_VoF_to_dcp)
489 auto range = dcp_range (movie_VoF("movie_VoF_to_dcp"));
490 /* We said that video range data was really full range, so here we are in the DCP
491 * with video-range data.
493 check_int_close (range, {350, 3832}, 2);
497 BOOST_AUTO_TEST_CASE (movie_F_to_dcp)
499 auto range = dcp_range (movie_F("movie_F_to_dcp"));
500 /* The nearly-full-range of the input has been preserved */
501 check_int_close (range, {0, 4083}, 2);
505 BOOST_AUTO_TEST_CASE (video_FoV_to_dcp)
507 auto range = dcp_range (movie_FoV("video_FoV_to_dcp"));
508 /* The nearly-full-range of the input has become even more full, and clipped */
509 check_int_close (range, {0, 4095}, 2);
513 BOOST_AUTO_TEST_CASE (image_F_to_dcp)
515 auto range = dcp_range (image_F("image_F_to_dcp"));
516 check_int_close (range, {0, 4083}, 3);
520 BOOST_AUTO_TEST_CASE (image_FoV_to_dcp)
522 auto range = dcp_range (image_FoV("image_FoV_to_dcp"));
523 /* The nearly-full-range of the input has become even more full, and clipped.
524 * XXX: I'm not sure why this doesn't quite hit 4095.
526 check_int_close (range, {0, 4095}, 16);
530 BOOST_AUTO_TEST_CASE (movie_V_to_V_movie)
532 auto range = V_movie_range (movie_V("movie_V_to_V_movie"));
533 BOOST_CHECK_EQUAL (range.first, 60);
534 BOOST_CHECK_EQUAL (range.second, 998);
538 BOOST_AUTO_TEST_CASE (movie_VoF_to_V_movie)
540 auto range = V_movie_range (movie_VoF("movie_VoF_to_V_movie"));
541 BOOST_CHECK_EQUAL (range.first, 116);
542 BOOST_CHECK_EQUAL (range.second, 939);
546 BOOST_AUTO_TEST_CASE (movie_F_to_V_movie)
548 auto range = V_movie_range (movie_F("movie_F_to_V_movie"));
549 BOOST_CHECK_EQUAL (range.first, 4);
550 BOOST_CHECK_EQUAL (range.second, 1019);
554 BOOST_AUTO_TEST_CASE (movie_FoV_to_V_movie)
556 auto range = V_movie_range (movie_FoV("movie_FoV_to_V_movie"));
557 BOOST_CHECK_EQUAL (range.first, 4);
558 BOOST_CHECK_EQUAL (range.second, 1019);
562 BOOST_AUTO_TEST_CASE (image_F_to_V_movie)
564 auto range = V_movie_range (image_F("image_F_to_V_movie"));
565 BOOST_CHECK_EQUAL (range.first, 64);
566 BOOST_CHECK_EQUAL (range.second, 960);
570 BOOST_AUTO_TEST_CASE (image_FoV_to_V_movie)
572 auto range = V_movie_range (image_FoV("image_FoV_to_V_movie"));
573 BOOST_CHECK_EQUAL (range.first, 64);
574 BOOST_CHECK_EQUAL (range.second, 960);
578 BOOST_AUTO_TEST_CASE (dcp_F_to_V_movie)
580 auto range = V_movie_range (dcp_F("dcp_F_to_V_movie"));
581 BOOST_CHECK_EQUAL (range.first, 64);
582 BOOST_CHECK_EQUAL (range.second, 944);