2 Copyright (C) 2020 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 boost::dynamic_pointer_cast;
59 using boost::optional;
60 using boost::shared_ptr;
65 grey_image (dcp::Size size, uint8_t pixel)
67 shared_ptr<Image> grey(new Image(AV_PIX_FMT_RGB24, size, true));
68 for (int y = 0; y < size.height; ++y) {
69 uint8_t* p = grey->data()[0] + y * grey->stride()[0];
70 for (int x = 0; x < size.width; ++x) {
81 BOOST_AUTO_TEST_CASE (ffmpeg_image_full_range_not_changed)
83 dcp::Size size(640, 480);
84 uint8_t const grey_pixel = 128;
85 boost::filesystem::path const file = "build/test/ffmpeg_image_full_range_not_changed.png";
87 write_image (grey_image(size, grey_pixel), file);
89 FFmpegImageProxy proxy (file, VIDEO_RANGE_FULL);
90 ImageProxy::Result result = proxy.image ();
91 BOOST_REQUIRE (!result.error);
93 for (int y = 0; y < size.height; ++y) {
94 uint8_t* p = result.image->data()[0] + y * result.image->stride()[0];
95 for (int x = 0; x < size.width; ++x) {
96 BOOST_REQUIRE (*p++ == grey_pixel);
102 BOOST_AUTO_TEST_CASE (ffmpeg_image_video_range_expanded)
104 dcp::Size size(640, 480);
105 uint8_t const grey_pixel = 128;
106 uint8_t const expanded_grey_pixel = static_cast<uint8_t>((grey_pixel - 16) * 256.0 / 219);
107 boost::filesystem::path const file = "build/test/ffmpeg_image_video_range_expanded.png";
109 write_image (grey_image(size, grey_pixel), file);
111 FFmpegImageProxy proxy (file, VIDEO_RANGE_VIDEO);
112 ImageProxy::Result result = proxy.image ();
113 BOOST_REQUIRE (!result.error);
115 for (int y = 0; y < size.height; ++y) {
116 uint8_t* p = result.image->data()[0] + y * result.image->stride()[0];
117 for (int x = 0; x < size.width; ++x) {
118 BOOST_REQUIRE_EQUAL (*p++, expanded_grey_pixel);
124 static optional<ContentVideo> content_video;
129 video_handler (ContentVideo cv)
137 pixel_range (shared_ptr<const Image> image)
139 pair<int, int> range(INT_MAX, 0);
140 switch (image->pixel_format()) {
141 case AV_PIX_FMT_RGB24:
143 dcp::Size const size = image->sample_size(0);
144 for (int y = 0; y < size.height; ++y) {
145 uint8_t* p = image->data()[0] + y * image->stride()[0];
146 for (int x = 0; x < size.width * 3; ++x) {
147 range.first = min(range.first, static_cast<int>(*p));
148 range.second = max(range.second, static_cast<int>(*p));
154 case AV_PIX_FMT_YUV444P:
156 for (int c = 0; c < 3; ++c) {
157 dcp::Size const size = image->sample_size(c);
158 for (int y = 0; y < size.height; ++y) {
159 uint8_t* p = image->data()[c] + y * image->stride()[c];
160 for (int x = 0; x < size.width; ++x) {
161 range.first = min(range.first, static_cast<int>(*p));
162 range.second = max(range.second, static_cast<int>(*p));
169 case AV_PIX_FMT_YUV422P10LE:
170 case AV_PIX_FMT_YUV444P10LE:
171 case AV_PIX_FMT_YUV444P12LE:
173 for (int c = 0; c < 3; ++c) {
174 dcp::Size const size = image->sample_size(c);
175 for (int y = 0; y < size.height; ++y) {
176 uint16_t* p = reinterpret_cast<uint16_t*>(image->data()[c]) + y * image->stride()[c] / 2;
177 for (int x = 0; x < size.width; ++x) {
178 range.first = min(range.first, static_cast<int>(*p));
179 range.second = max(range.second, static_cast<int>(*p));
187 BOOST_REQUIRE_MESSAGE (false, "No support for pixel format " << image->pixel_format());
196 pixel_range (shared_ptr<Film> film, shared_ptr<const FFmpegContent> content)
198 shared_ptr<FFmpegDecoder> decoder(new FFmpegDecoder(film, content, false));
199 decoder->video->Data.connect (bind(&video_handler, _1));
200 content_video = boost::none;
201 while (!content_video) {
202 BOOST_REQUIRE (!decoder->pass());
205 return pixel_range (content_video->image->image().image);
211 pixel_range (shared_ptr<Film> film, shared_ptr<const ImageContent> content)
213 shared_ptr<ImageDecoder> decoder(new ImageDecoder(film, content));
214 decoder->video->Data.connect (bind(&video_handler, _1));
215 content_video = boost::none;
216 while (!content_video) {
217 BOOST_REQUIRE (!decoder->pass());
220 return pixel_range (content_video->image->image().image);
226 pixel_range (boost::filesystem::path dcp_path)
228 dcp::DCP dcp (dcp_path);
231 shared_ptr<dcp::MonoPictureAsset> picture = dynamic_pointer_cast<dcp::MonoPictureAsset>(dcp.cpls().front()->reels().front()->main_picture()->asset());
232 BOOST_REQUIRE (picture);
233 shared_ptr<dcp::OpenJPEGImage> frame = picture->start_read()->get_frame(0)->xyz_image();
235 int const width = frame->size().width;
236 int const height = frame->size().height;
238 pair<int, int> range(INT_MAX, 0);
239 for (int c = 0; c < 3; ++c) {
240 for (int y = 0; y < height; ++y) {
241 int* p = frame->data(c) + y * width;
242 for (int x = 0; x < width; ++x) {
243 range.first = min(range.first, *p);
244 range.second = max(range.second, *p);
254 /* Functions to make a Film with different sorts of content.
256 * In these names V = video range (limited)
257 * F = full range (not limited)
264 movie_V (string name)
266 shared_ptr<Film> film = new_test_film2 (name);
267 shared_ptr<FFmpegContent> content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mp4").front());
268 BOOST_REQUIRE (content);
269 film->examine_and_add_content (content);
270 BOOST_REQUIRE (!wait_for_jobs());
272 pair<int, int> range = pixel_range (film, content);
273 BOOST_CHECK_EQUAL (range.first, 15);
274 BOOST_CHECK_EQUAL (range.second, 243);
282 movie_VoF (string name)
284 shared_ptr<Film> film = new_test_film2 (name);
285 shared_ptr<FFmpegContent> content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mp4").front());
286 BOOST_REQUIRE (content);
287 film->examine_and_add_content (content);
288 BOOST_REQUIRE (!wait_for_jobs());
289 content->video->set_range (VIDEO_RANGE_FULL);
291 pair<int, int> range = pixel_range (film, content);
292 BOOST_CHECK_EQUAL (range.first, 15);
293 BOOST_CHECK_EQUAL (range.second, 243);
301 movie_F (string name)
303 shared_ptr<Film> film = new_test_film2 (name);
304 shared_ptr<FFmpegContent> content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mov").front());
305 BOOST_REQUIRE (content);
306 film->examine_and_add_content (content);
307 BOOST_REQUIRE (!wait_for_jobs());
309 pair<int, int> range = pixel_range (film, content);
310 BOOST_CHECK_EQUAL (range.first, 0);
311 BOOST_CHECK_EQUAL (range.second, 1023);
319 movie_FoV (string name)
321 shared_ptr<Film> film = new_test_film2 (name);
322 shared_ptr<FFmpegContent> content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mov").front());
323 BOOST_REQUIRE (content);
324 film->examine_and_add_content (content);
325 BOOST_REQUIRE (!wait_for_jobs());
326 content->video->set_range (VIDEO_RANGE_VIDEO);
328 pair<int, int> range = pixel_range (film, content);
329 BOOST_CHECK_EQUAL (range.first, 0);
330 BOOST_CHECK_EQUAL (range.second, 1023);
338 image_F (string name)
340 shared_ptr<Film> film = new_test_film2 (name);
341 shared_ptr<ImageContent> content = dynamic_pointer_cast<ImageContent>(content_factory("test/data/rgb_grey_testcard.png").front());
342 BOOST_REQUIRE (content);
343 film->examine_and_add_content (content);
344 BOOST_REQUIRE (!wait_for_jobs());
346 pair<int, int> range = pixel_range (film, content);
347 BOOST_CHECK_EQUAL (range.first, 0);
348 BOOST_CHECK_EQUAL (range.second, 255);
356 image_FoV (string name)
358 shared_ptr<Film> film = new_test_film2 (name);
359 shared_ptr<ImageContent> content = dynamic_pointer_cast<ImageContent>(content_factory("test/data/rgb_grey_testcard.png").front());
360 BOOST_REQUIRE (content);
361 film->examine_and_add_content (content);
362 BOOST_REQUIRE (!wait_for_jobs());
363 content->video->set_range (VIDEO_RANGE_VIDEO);
365 pair<int, int> range = pixel_range (film, content);
366 BOOST_CHECK_EQUAL (range.first, 11);
367 BOOST_CHECK_EQUAL (range.second, 250);
377 boost::filesystem::path const dcp = "test/data/RgbGreyTestcar_TST-1_F_MOS_2K_20201115_SMPTE_OV";
378 shared_ptr<Film> film = new_test_film2 (name);
379 shared_ptr<DCPContent> content(new DCPContent(dcp));
380 film->examine_and_add_content (shared_ptr<DCPContent>(new DCPContent(dcp)));
381 BOOST_REQUIRE (!wait_for_jobs());
383 pair<int, int> 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)
401 BOOST_REQUIRE (!wait_for_jobs());
402 return pixel_range (film->dir(film->dcp_name()));
406 /** Get the pixel range in a video-range movie exported from film */
409 V_movie_range (shared_ptr<Film> film)
411 shared_ptr<TranscodeJob> job (new TranscodeJob(film));
413 shared_ptr<FFmpegEncoder>(
414 new FFmpegEncoder (film, job, film->file("export.mov"), EXPORT_FORMAT_PRORES, true, false, false, 23)
417 JobManager::instance()->add (job);
418 BOOST_REQUIRE (!wait_for_jobs());
420 /* This is a bit of a hack; add the exported file into the project so we can decode it */
421 shared_ptr<FFmpegContent> content(new FFmpegContent(film->file("export.mov")));
422 film->examine_and_add_content (content);
423 BOOST_REQUIRE (!wait_for_jobs());
425 return pixel_range (film, content);
432 BOOST_AUTO_TEST_CASE (movie_V_to_dcp)
434 pair<int, int> range = dcp_range (movie_V("movie_V_to_dcp"));
435 /* Video range has been correctly expanded to full for the DCP */
436 BOOST_CHECK_EQUAL (range.first, 0);
437 BOOST_CHECK_EQUAL (range.second, 4082);
441 BOOST_AUTO_TEST_CASE (movie_VoF_to_dcp)
443 pair<int, int> 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 BOOST_CHECK_EQUAL (range.first, 350);
448 BOOST_CHECK_EQUAL (range.second, 3832);
452 BOOST_AUTO_TEST_CASE (movie_F_to_dcp)
454 pair<int, int> range = dcp_range (movie_F("movie_F_to_dcp"));
455 /* The nearly-full-range of the input has been preserved */
456 BOOST_CHECK_EQUAL (range.first, 0);
457 BOOST_CHECK_EQUAL (range.second, 4082);
461 BOOST_AUTO_TEST_CASE (video_FoV_to_dcp)
463 pair<int, int> range = dcp_range (movie_FoV("video_FoV_to_dcp"));
464 /* The nearly-full-range of the input has become even more full, and clipped */
465 BOOST_CHECK_EQUAL (range.first, 0);
466 BOOST_CHECK_EQUAL (range.second, 4095);
470 BOOST_AUTO_TEST_CASE (image_F_to_dcp)
472 pair<int, int> range = dcp_range (image_F("image_F_to_dcp"));
473 BOOST_CHECK_EQUAL (range.first, 0);
474 BOOST_CHECK_EQUAL (range.second, 4081);
478 BOOST_AUTO_TEST_CASE (image_FoV_to_dcp)
480 pair<int, int> range = dcp_range (image_FoV("image_FoV_to_dcp"));
481 BOOST_CHECK_EQUAL (range.first, 431);
482 BOOST_CHECK_EQUAL (range.second, 4012);
486 BOOST_AUTO_TEST_CASE (movie_V_to_V_movie)
488 pair<int, int> range = V_movie_range (movie_V("movie_V_to_V_movie"));
489 BOOST_CHECK_EQUAL (range.first, 60);
490 BOOST_CHECK_EQUAL (range.second, 998);
494 BOOST_AUTO_TEST_CASE (movie_VoF_to_V_movie)
496 pair<int, int> range = V_movie_range (movie_VoF("movie_VoF_to_V_movie"));
497 BOOST_CHECK_EQUAL (range.first, 116);
498 BOOST_CHECK_EQUAL (range.second, 939);
502 BOOST_AUTO_TEST_CASE (movie_F_to_V_movie)
504 pair<int, int> range = V_movie_range (movie_F("movie_F_to_V_movie"));
505 BOOST_CHECK_EQUAL (range.first, 4);
506 BOOST_CHECK_EQUAL (range.second, 1019);
510 BOOST_AUTO_TEST_CASE (movie_FoV_to_V_movie)
512 pair<int, int> range = V_movie_range (movie_FoV("movie_FoV_to_V_movie"));
513 BOOST_CHECK_EQUAL (range.first, 4);
514 BOOST_CHECK_EQUAL (range.second, 1019);
518 BOOST_AUTO_TEST_CASE (image_F_to_V_movie)
520 pair<int, int> range = V_movie_range (image_F("image_F_to_V_movie"));
521 BOOST_CHECK_EQUAL (range.first, 64);
522 BOOST_CHECK_EQUAL (range.second, 960);
526 BOOST_AUTO_TEST_CASE (image_FoV_to_V_movie)
528 pair<int, int> range = V_movie_range (image_FoV("image_FoV_to_V_movie"));
529 BOOST_CHECK_EQUAL (range.first, 102);
530 BOOST_CHECK_EQUAL (range.second, 923);
534 BOOST_AUTO_TEST_CASE (dcp_F_to_V_movie)
536 pair<int, int> range = V_movie_range (dcp_F("dcp_F_to_V_movie"));
537 BOOST_CHECK_EQUAL (range.first, 64);
538 BOOST_CHECK_EQUAL (range.second, 944);