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"
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"
45 #include <dcp/mono_picture_asset.h>
46 #include <dcp/mono_picture_frame.h>
47 #include <dcp/openjpeg_image.h>
49 #include <dcp/reel_picture_asset.h>
50 #include <boost/test/unit_test.hpp>
58 using std::dynamic_pointer_cast;
59 using std::make_shared;
60 using boost::optional;
61 #if BOOST_VERSION >= 106100
62 using namespace boost::placeholders;
64 using std::shared_ptr;
69 grey_image (dcp::Size size, uint8_t pixel)
71 auto grey = make_shared<Image>(AV_PIX_FMT_RGB24, size, true);
72 for (int y = 0; y < size.height; ++y) {
73 uint8_t* p = grey->data()[0] + y * grey->stride()[0];
74 for (int x = 0; x < size.width; ++x) {
85 BOOST_AUTO_TEST_CASE (ffmpeg_image_full_range_not_changed)
87 dcp::Size size(640, 480);
88 uint8_t const grey_pixel = 128;
89 boost::filesystem::path const file = "build/test/ffmpeg_image_full_range_not_changed.png";
91 write_image (grey_image(size, grey_pixel), file);
93 FFmpegImageProxy proxy (file, VideoRange::FULL);
94 ImageProxy::Result result = proxy.image ();
95 BOOST_REQUIRE (!result.error);
97 for (int y = 0; y < size.height; ++y) {
98 uint8_t* p = result.image->data()[0] + y * result.image->stride()[0];
99 for (int x = 0; x < size.width; ++x) {
100 BOOST_REQUIRE (*p++ == grey_pixel);
106 BOOST_AUTO_TEST_CASE (ffmpeg_image_video_range_expanded)
108 dcp::Size size(640, 480);
109 uint8_t const grey_pixel = 128;
110 uint8_t const expanded_grey_pixel = static_cast<uint8_t>((grey_pixel - 16) * 256.0 / 219);
111 boost::filesystem::path const file = "build/test/ffmpeg_image_video_range_expanded.png";
113 write_image (grey_image(size, grey_pixel), file);
115 FFmpegImageProxy proxy (file, VideoRange::VIDEO);
116 ImageProxy::Result result = proxy.image ();
117 BOOST_REQUIRE (!result.error);
119 for (int y = 0; y < size.height; ++y) {
120 uint8_t* p = result.image->data()[0] + y * result.image->stride()[0];
121 for (int x = 0; x < size.width; ++x) {
122 BOOST_REQUIRE_EQUAL (*p++, expanded_grey_pixel);
128 static optional<ContentVideo> content_video;
133 video_handler (ContentVideo cv)
141 pixel_range (shared_ptr<const Image> image)
143 pair<int, int> range(INT_MAX, 0);
144 switch (image->pixel_format()) {
145 case AV_PIX_FMT_RGB24:
147 dcp::Size const size = image->sample_size(0);
148 for (int y = 0; y < size.height; ++y) {
149 uint8_t* p = image->data()[0] + y * image->stride()[0];
150 for (int x = 0; x < size.width * 3; ++x) {
151 range.first = min(range.first, static_cast<int>(*p));
152 range.second = max(range.second, static_cast<int>(*p));
158 case AV_PIX_FMT_YUV444P:
160 for (int c = 0; c < 3; ++c) {
161 dcp::Size const size = image->sample_size(c);
162 for (int y = 0; y < size.height; ++y) {
163 uint8_t* p = image->data()[c] + y * image->stride()[c];
164 for (int x = 0; x < size.width; ++x) {
165 range.first = min(range.first, static_cast<int>(*p));
166 range.second = max(range.second, static_cast<int>(*p));
173 case AV_PIX_FMT_YUV422P10LE:
174 case AV_PIX_FMT_YUV444P10LE:
175 case AV_PIX_FMT_YUV444P12LE:
177 for (int c = 0; c < 3; ++c) {
178 dcp::Size const size = image->sample_size(c);
179 for (int y = 0; y < size.height; ++y) {
180 uint16_t* p = reinterpret_cast<uint16_t*>(image->data()[c]) + y * image->stride()[c] / 2;
181 for (int x = 0; x < size.width; ++x) {
182 range.first = min(range.first, static_cast<int>(*p));
183 range.second = max(range.second, static_cast<int>(*p));
191 BOOST_REQUIRE_MESSAGE (false, "No support for pixel format " << image->pixel_format());
200 pixel_range (shared_ptr<Film> film, shared_ptr<const FFmpegContent> content)
202 auto decoder = make_shared<FFmpegDecoder>(film, content, false);
203 decoder->video->Data.connect (bind(&video_handler, _1));
204 content_video = boost::none;
205 while (!content_video) {
206 BOOST_REQUIRE (!decoder->pass());
209 return pixel_range (content_video->image->image().image);
215 pixel_range (shared_ptr<Film> film, shared_ptr<const ImageContent> content)
217 auto decoder = make_shared<ImageDecoder>(film, content);
218 decoder->video->Data.connect (bind(&video_handler, _1));
219 content_video = boost::none;
220 while (!content_video) {
221 BOOST_REQUIRE (!decoder->pass());
224 return pixel_range (content_video->image->image().image);
230 pixel_range (boost::filesystem::path dcp_path)
232 dcp::DCP dcp (dcp_path);
235 auto picture = dynamic_pointer_cast<dcp::MonoPictureAsset>(dcp.cpls().front()->reels().front()->main_picture()->asset());
236 BOOST_REQUIRE (picture);
237 auto frame = picture->start_read()->get_frame(0)->xyz_image();
239 int const width = frame->size().width;
240 int const height = frame->size().height;
242 pair<int, int> range(INT_MAX, 0);
243 for (int c = 0; c < 3; ++c) {
244 for (int y = 0; y < height; ++y) {
245 int* p = frame->data(c) + y * width;
246 for (int x = 0; x < width; ++x) {
247 range.first = min(range.first, *p);
248 range.second = max(range.second, *p);
258 /* Functions to make a Film with different sorts of content.
260 * In these names V = video range (limited)
261 * F = full range (not limited)
268 movie_V (string name)
270 auto film = new_test_film2 (name);
271 auto content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mp4").front());
272 BOOST_REQUIRE (content);
273 film->examine_and_add_content (content);
274 BOOST_REQUIRE (!wait_for_jobs());
276 auto range = pixel_range (film, content);
277 BOOST_CHECK_EQUAL (range.first, 15);
278 BOOST_CHECK_EQUAL (range.second, 243);
286 movie_VoF (string name)
288 auto film = new_test_film2 (name);
289 auto content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mp4").front());
290 BOOST_REQUIRE (content);
291 film->examine_and_add_content (content);
292 BOOST_REQUIRE (!wait_for_jobs());
293 content->video->set_range (VideoRange::FULL);
295 auto range = pixel_range (film, content);
296 BOOST_CHECK_EQUAL (range.first, 15);
297 BOOST_CHECK_EQUAL (range.second, 243);
305 movie_F (string name)
307 auto film = new_test_film2 (name);
308 auto content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mov").front());
309 BOOST_REQUIRE (content);
310 film->examine_and_add_content (content);
311 BOOST_REQUIRE (!wait_for_jobs());
313 auto range = pixel_range (film, content);
314 BOOST_CHECK_EQUAL (range.first, 0);
315 BOOST_CHECK_EQUAL (range.second, 1023);
323 movie_FoV (string name)
325 auto film = new_test_film2 (name);
326 auto content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mov").front());
327 BOOST_REQUIRE (content);
328 film->examine_and_add_content (content);
329 BOOST_REQUIRE (!wait_for_jobs());
330 content->video->set_range (VideoRange::VIDEO);
332 auto range = pixel_range (film, content);
333 BOOST_CHECK_EQUAL (range.first, 0);
334 BOOST_CHECK_EQUAL (range.second, 1023);
342 image_F (string name)
344 auto film = new_test_film2 (name);
345 auto content = dynamic_pointer_cast<ImageContent>(content_factory("test/data/rgb_grey_testcard.png").front());
346 BOOST_REQUIRE (content);
347 film->examine_and_add_content (content);
348 BOOST_REQUIRE (!wait_for_jobs());
350 auto range = pixel_range (film, content);
351 BOOST_CHECK_EQUAL (range.first, 0);
352 BOOST_CHECK_EQUAL (range.second, 255);
360 image_FoV (string name)
362 auto film = new_test_film2 (name);
363 auto content = dynamic_pointer_cast<ImageContent>(content_factory("test/data/rgb_grey_testcard.png").front());
364 BOOST_REQUIRE (content);
365 film->examine_and_add_content (content);
366 BOOST_REQUIRE (!wait_for_jobs());
367 content->video->set_range (VideoRange::VIDEO);
369 auto range = pixel_range (film, content);
370 BOOST_CHECK_EQUAL (range.first, 11);
371 BOOST_CHECK_EQUAL (range.second, 250);
381 boost::filesystem::path const dcp = "test/data/RgbGreyTestcar_TST-1_F_MOS_2K_20201115_SMPTE_OV";
382 auto film = new_test_film2 (name);
383 auto content = make_shared<DCPContent>(dcp);
384 film->examine_and_add_content (content);
385 BOOST_REQUIRE (!wait_for_jobs());
387 auto range = pixel_range (dcp);
388 BOOST_CHECK_EQUAL (range.first, 0);
389 BOOST_CHECK_EQUAL (range.second, 4081);
396 /* Functions to get the pixel range in different sorts of output */
399 /** Get the pixel range in a DCP made from film */
402 dcp_range (shared_ptr<Film> film)
404 make_and_verify_dcp (film);
405 return pixel_range (film->dir(film->dcp_name()));
409 /** Get the pixel range in a video-range movie exported from film */
412 V_movie_range (shared_ptr<Film> film)
414 auto job = make_shared<TranscodeJob>(film);
416 make_shared<FFmpegEncoder>(film, job, film->file("export.mov"), ExportFormat::PRORES, true, false, false, 23)
418 JobManager::instance()->add (job);
419 BOOST_REQUIRE (!wait_for_jobs());
421 /* This is a bit of a hack; add the exported file into the project so we can decode it */
422 auto content = make_shared<FFmpegContent>(film->file("export.mov"));
423 film->examine_and_add_content (content);
424 BOOST_REQUIRE (!wait_for_jobs());
426 return pixel_range (film, content);
433 BOOST_AUTO_TEST_CASE (movie_V_to_dcp)
435 auto range = dcp_range (movie_V("movie_V_to_dcp"));
436 /* Video range has been correctly expanded to full for the DCP */
437 check_int_close (range, {0, 4083}, 2);
441 BOOST_AUTO_TEST_CASE (movie_VoF_to_dcp)
443 auto range = dcp_range (movie_VoF("movie_VoF_to_dcp"));
444 /* We said that video range data was really full range, so here we are in the DCP
445 * with video-range data.
447 check_int_close (range, {350, 3832}, 2);
451 BOOST_AUTO_TEST_CASE (movie_F_to_dcp)
453 auto range = dcp_range (movie_F("movie_F_to_dcp"));
454 /* The nearly-full-range of the input has been preserved */
455 check_int_close (range, {0, 4083}, 2);
459 BOOST_AUTO_TEST_CASE (video_FoV_to_dcp)
461 auto range = dcp_range (movie_FoV("video_FoV_to_dcp"));
462 /* The nearly-full-range of the input has become even more full, and clipped */
463 check_int_close (range, {0, 4095}, 2);
467 BOOST_AUTO_TEST_CASE (image_F_to_dcp)
469 auto range = dcp_range (image_F("image_F_to_dcp"));
470 check_int_close (range, {0, 4083}, 3);
474 BOOST_AUTO_TEST_CASE (image_FoV_to_dcp)
476 auto range = dcp_range (image_FoV("image_FoV_to_dcp"));
477 check_int_close (range, {430, 4012}, 2);
481 BOOST_AUTO_TEST_CASE (movie_V_to_V_movie)
483 auto range = V_movie_range (movie_V("movie_V_to_V_movie"));
484 BOOST_CHECK_EQUAL (range.first, 60);
485 BOOST_CHECK_EQUAL (range.second, 998);
489 BOOST_AUTO_TEST_CASE (movie_VoF_to_V_movie)
491 auto range = V_movie_range (movie_VoF("movie_VoF_to_V_movie"));
492 BOOST_CHECK_EQUAL (range.first, 116);
493 BOOST_CHECK_EQUAL (range.second, 939);
497 BOOST_AUTO_TEST_CASE (movie_F_to_V_movie)
499 auto range = V_movie_range (movie_F("movie_F_to_V_movie"));
500 BOOST_CHECK_EQUAL (range.first, 4);
501 BOOST_CHECK_EQUAL (range.second, 1019);
505 BOOST_AUTO_TEST_CASE (movie_FoV_to_V_movie)
507 auto range = V_movie_range (movie_FoV("movie_FoV_to_V_movie"));
508 BOOST_CHECK_EQUAL (range.first, 4);
509 BOOST_CHECK_EQUAL (range.second, 1019);
513 BOOST_AUTO_TEST_CASE (image_F_to_V_movie)
515 auto range = V_movie_range (image_F("image_F_to_V_movie"));
516 BOOST_CHECK_EQUAL (range.first, 64);
517 BOOST_CHECK_EQUAL (range.second, 960);
521 BOOST_AUTO_TEST_CASE (image_FoV_to_V_movie)
523 auto range = V_movie_range (image_FoV("image_FoV_to_V_movie"));
524 BOOST_CHECK_EQUAL (range.first, 102);
525 BOOST_CHECK_EQUAL (range.second, 923);
529 BOOST_AUTO_TEST_CASE (dcp_F_to_V_movie)
531 auto range = V_movie_range (dcp_F("dcp_F_to_V_movie"));
532 BOOST_CHECK_EQUAL (range.first, 64);
533 BOOST_CHECK_EQUAL (range.second, 944);