From d346fe3112a503433b791b04f65891beb9bd39d8 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Sun, 15 Nov 2020 20:50:39 +0100 Subject: [PATCH] Add tests of video range. These should hopefully cover all combinations of input and output range. Fixes #1851. --- test/data | 2 +- test/video_level_test.cc | 443 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 444 insertions(+), 1 deletion(-) diff --git a/test/data b/test/data index 49de24b4d..336ae34f0 160000 --- a/test/data +++ b/test/data @@ -1 +1 @@ -Subproject commit 49de24b4d45b1f3ee9bddf152dcf1dc03402c124 +Subproject commit 336ae34f03a492af4764928700d7973025d0f3e7 diff --git a/test/video_level_test.cc b/test/video_level_test.cc index 2849910b1..b584263fc 100644 --- a/test/video_level_test.cc +++ b/test/video_level_test.cc @@ -25,12 +25,38 @@ */ +#include "lib/content_factory.h" +#include "lib/content_video.h" +#include "lib/dcp_content.h" +#include "lib/film.h" +#include "lib/ffmpeg_content.h" +#include "lib/ffmpeg_decoder.h" #include "lib/ffmpeg_image_proxy.h" #include "lib/image.h" +#include "lib/image_content.h" +#include "lib/image_decoder.h" +#include "lib/ffmpeg_encoder.h" +#include "lib/job_manager.h" +#include "lib/transcode_job.h" +#include "lib/video_decoder.h" #include "test.h" +#include +#include +#include +#include +#include +#include +#include #include +using std::min; +using std::make_pair; +using std::max; +using std::pair; +using std::string; +using boost::dynamic_pointer_cast; +using boost::optional; using boost::shared_ptr; @@ -95,3 +121,420 @@ BOOST_AUTO_TEST_CASE (ffmpeg_image_video_range_expanded) } +static optional content_video; + + +static +void +video_handler (ContentVideo cv) +{ + content_video = cv; +} + + +static +pair +pixel_range (shared_ptr image) +{ + pair range(INT_MAX, 0); + switch (image->pixel_format()) { + case AV_PIX_FMT_RGB24: + { + dcp::Size const size = image->sample_size(0); + for (int y = 0; y < size.height; ++y) { + uint8_t* p = image->data()[0] + y * image->stride()[0]; + for (int x = 0; x < size.width * 3; ++x) { + range.first = min(range.first, static_cast(*p)); + range.second = max(range.second, static_cast(*p)); + ++p; + } + } + break; + } + case AV_PIX_FMT_YUV444P: + { + for (int c = 0; c < 3; ++c) { + dcp::Size const size = image->sample_size(c); + for (int y = 0; y < size.height; ++y) { + uint8_t* p = image->data()[c] + y * image->stride()[c]; + for (int x = 0; x < size.width; ++x) { + range.first = min(range.first, static_cast(*p)); + range.second = max(range.second, static_cast(*p)); + ++p; + } + } + } + break; + } + case AV_PIX_FMT_YUV422P10LE: + case AV_PIX_FMT_YUV444P10LE: + case AV_PIX_FMT_YUV444P12LE: + { + for (int c = 0; c < 3; ++c) { + dcp::Size const size = image->sample_size(c); + for (int y = 0; y < size.height; ++y) { + uint16_t* p = reinterpret_cast(image->data()[c]) + y * image->stride()[c] / 2; + for (int x = 0; x < size.width; ++x) { + range.first = min(range.first, static_cast(*p)); + range.second = max(range.second, static_cast(*p)); + ++p; + } + } + } + break; + } + default: + BOOST_REQUIRE_MESSAGE (false, "No support for pixel format " << image->pixel_format()); + } + + return range; +} + + +static +pair +pixel_range (shared_ptr film, shared_ptr content) +{ + shared_ptr decoder(new FFmpegDecoder(film, content, false)); + decoder->video->Data.connect (bind(&video_handler, _1)); + content_video = boost::none; + while (!content_video) { + BOOST_REQUIRE (!decoder->pass()); + } + + return pixel_range (content_video->image->image().image); +} + + +static +pair +pixel_range (shared_ptr film, shared_ptr content) +{ + shared_ptr decoder(new ImageDecoder(film, content)); + decoder->video->Data.connect (bind(&video_handler, _1)); + content_video = boost::none; + while (!content_video) { + BOOST_REQUIRE (!decoder->pass()); + } + + return pixel_range (content_video->image->image().image); +} + + +static +pair +pixel_range (boost::filesystem::path dcp_path) +{ + dcp::DCP dcp (dcp_path); + dcp.read (); + + shared_ptr picture = dynamic_pointer_cast(dcp.cpls().front()->reels().front()->main_picture()->asset()); + BOOST_REQUIRE (picture); + shared_ptr frame = picture->start_read()->get_frame(0)->xyz_image(); + + int const width = frame->size().width; + int const height = frame->size().height; + + pair range(INT_MAX, 0); + for (int c = 0; c < 3; ++c) { + for (int y = 0; y < height; ++y) { + int* p = frame->data(c) + y * width; + for (int x = 0; x < width; ++x) { + range.first = min(range.first, *p); + range.second = max(range.second, *p); + ++p; + } + } + } + + return range; +} + + +/* Functions to make a Film with different sorts of content. + * + * In these names V = video range (limited) + * F = full range (not limited) + * o = overridden + */ + + +static +shared_ptr +movie_V (string name) +{ + shared_ptr film = new_test_film2 (name); + shared_ptr content = dynamic_pointer_cast(content_factory("test/data/rgb_grey_testcard.mp4").front()); + BOOST_REQUIRE (content); + film->examine_and_add_content (content); + BOOST_REQUIRE (!wait_for_jobs()); + + pair range = pixel_range (film, content); + BOOST_CHECK_EQUAL (range.first, 15); + BOOST_CHECK_EQUAL (range.second, 243); + + return film; +} + + +static +shared_ptr +movie_VoF (string name) +{ + shared_ptr film = new_test_film2 (name); + shared_ptr content = dynamic_pointer_cast(content_factory("test/data/rgb_grey_testcard.mp4").front()); + BOOST_REQUIRE (content); + film->examine_and_add_content (content); + BOOST_REQUIRE (!wait_for_jobs()); + content->video->set_range (VIDEO_RANGE_FULL); + + pair range = pixel_range (film, content); + BOOST_CHECK_EQUAL (range.first, 15); + BOOST_CHECK_EQUAL (range.second, 243); + + return film; +} + + +static +shared_ptr +movie_F (string name) +{ + shared_ptr film = new_test_film2 (name); + shared_ptr content = dynamic_pointer_cast(content_factory("test/data/rgb_grey_testcard.mov").front()); + BOOST_REQUIRE (content); + film->examine_and_add_content (content); + BOOST_REQUIRE (!wait_for_jobs()); + + pair range = pixel_range (film, content); + BOOST_CHECK_EQUAL (range.first, 0); + BOOST_CHECK_EQUAL (range.second, 1023); + + return film; +} + + +static +shared_ptr +movie_FoV (string name) +{ + shared_ptr film = new_test_film2 (name); + shared_ptr content = dynamic_pointer_cast(content_factory("test/data/rgb_grey_testcard.mov").front()); + BOOST_REQUIRE (content); + film->examine_and_add_content (content); + BOOST_REQUIRE (!wait_for_jobs()); + content->video->set_range (VIDEO_RANGE_VIDEO); + + pair range = pixel_range (film, content); + BOOST_CHECK_EQUAL (range.first, 0); + BOOST_CHECK_EQUAL (range.second, 1023); + + return film; +} + + +static +shared_ptr +image_F (string name) +{ + shared_ptr film = new_test_film2 (name); + shared_ptr content = dynamic_pointer_cast(content_factory("test/data/rgb_grey_testcard.png").front()); + BOOST_REQUIRE (content); + film->examine_and_add_content (content); + BOOST_REQUIRE (!wait_for_jobs()); + + pair range = pixel_range (film, content); + BOOST_CHECK_EQUAL (range.first, 0); + BOOST_CHECK_EQUAL (range.second, 255); + + return film; +} + + +static +shared_ptr +image_FoV (string name) +{ + shared_ptr film = new_test_film2 (name); + shared_ptr content = dynamic_pointer_cast(content_factory("test/data/rgb_grey_testcard.png").front()); + BOOST_REQUIRE (content); + film->examine_and_add_content (content); + BOOST_REQUIRE (!wait_for_jobs()); + content->video->set_range (VIDEO_RANGE_VIDEO); + + pair range = pixel_range (film, content); + BOOST_CHECK_EQUAL (range.first, 11); + BOOST_CHECK_EQUAL (range.second, 250); + + return film; +} + + +static +shared_ptr +dcp_F (string name) +{ + boost::filesystem::path const dcp = "test/data/RgbGreyTestcar_TST-1_F_MOS_2K_20201115_SMPTE_OV"; + shared_ptr film = new_test_film2 (name); + shared_ptr content(new DCPContent(dcp)); + film->examine_and_add_content (shared_ptr(new DCPContent(dcp))); + BOOST_REQUIRE (!wait_for_jobs()); + + pair range = pixel_range (dcp); + BOOST_CHECK_EQUAL (range.first, 0); + BOOST_CHECK_EQUAL (range.second, 4081); + + return film; +} + + + +/* Functions to get the pixel range in different sorts of output */ + + +/** Get the pixel range in a DCP made from film */ +static +pair +dcp_range (shared_ptr film) +{ + film->make_dcp (); + BOOST_REQUIRE (!wait_for_jobs()); + return pixel_range (film->dir(film->dcp_name())); +} + + +/** Get the pixel range in a video-range movie exported from film */ +static +pair +V_movie_range (shared_ptr film) +{ + shared_ptr job (new TranscodeJob(film)); + job->set_encoder ( + shared_ptr( + new FFmpegEncoder (film, job, film->file("export.mov"), EXPORT_FORMAT_PRORES, true, false, false, 23) + ) + ); + JobManager::instance()->add (job); + BOOST_REQUIRE (!wait_for_jobs()); + + /* This is a bit of a hack; add the exported file into the project so we can decode it */ + shared_ptr content(new FFmpegContent(film->file("export.mov"))); + film->examine_and_add_content (content); + BOOST_REQUIRE (!wait_for_jobs()); + + return pixel_range (film, content); +} + + +/* The tests */ + + +BOOST_AUTO_TEST_CASE (movie_V_to_dcp) +{ + pair range = dcp_range (movie_V("movie_V_to_dcp")); + /* Video range has been correctly expanded to full for the DCP */ + BOOST_CHECK_EQUAL (range.first, 0); + BOOST_CHECK_EQUAL (range.second, 4082); +} + + +BOOST_AUTO_TEST_CASE (movie_VoF_to_dcp) +{ + pair range = dcp_range (movie_VoF("movie_VoF_to_dcp")); + /* We said that video range data was really full range, so here we are in the DCP + * with video-range data. + */ + BOOST_CHECK_EQUAL (range.first, 350); + BOOST_CHECK_EQUAL (range.second, 3832); +} + + +BOOST_AUTO_TEST_CASE (movie_F_to_dcp) +{ + pair range = dcp_range (movie_F("movie_F_to_dcp")); + /* The nearly-full-range of the input has been preserved */ + BOOST_CHECK_EQUAL (range.first, 0); + BOOST_CHECK_EQUAL (range.second, 4082); +} + + +BOOST_AUTO_TEST_CASE (video_FoV_to_dcp) +{ + pair range = dcp_range (movie_FoV("video_FoV_to_dcp")); + /* The nearly-full-range of the input has become even more full, and clipped */ + BOOST_CHECK_EQUAL (range.first, 0); + BOOST_CHECK_EQUAL (range.second, 4095); +} + + +BOOST_AUTO_TEST_CASE (image_F_to_dcp) +{ + pair range = dcp_range (image_F("image_F_to_dcp")); + BOOST_CHECK_EQUAL (range.first, 0); + BOOST_CHECK_EQUAL (range.second, 4081); +} + + +BOOST_AUTO_TEST_CASE (image_FoV_to_dcp) +{ + pair range = dcp_range (image_FoV("image_FoV_to_dcp")); + BOOST_CHECK_EQUAL (range.first, 431); + BOOST_CHECK_EQUAL (range.second, 4012); +} + + +BOOST_AUTO_TEST_CASE (movie_V_to_V_movie) +{ + pair range = V_movie_range (movie_V("movie_V_to_V_movie")); + BOOST_CHECK_EQUAL (range.first, 60); + BOOST_CHECK_EQUAL (range.second, 998); +} + + +BOOST_AUTO_TEST_CASE (movie_VoF_to_V_movie) +{ + pair range = V_movie_range (movie_VoF("movie_VoF_to_V_movie")); + BOOST_CHECK_EQUAL (range.first, 116); + BOOST_CHECK_EQUAL (range.second, 939); +} + + +BOOST_AUTO_TEST_CASE (movie_F_to_V_movie) +{ + pair range = V_movie_range (movie_F("movie_F_to_V_movie")); + BOOST_CHECK_EQUAL (range.first, 4); + BOOST_CHECK_EQUAL (range.second, 1019); +} + + +BOOST_AUTO_TEST_CASE (movie_FoV_to_V_movie) +{ + pair range = V_movie_range (movie_FoV("movie_FoV_to_V_movie")); + BOOST_CHECK_EQUAL (range.first, 4); + BOOST_CHECK_EQUAL (range.second, 1019); +} + + +BOOST_AUTO_TEST_CASE (image_F_to_V_movie) +{ + pair range = V_movie_range (image_F("image_F_to_V_movie")); + BOOST_CHECK_EQUAL (range.first, 64); + BOOST_CHECK_EQUAL (range.second, 960); +} + + +BOOST_AUTO_TEST_CASE (image_FoV_to_V_movie) +{ + pair range = V_movie_range (image_FoV("image_FoV_to_V_movie")); + BOOST_CHECK_EQUAL (range.first, 102); + BOOST_CHECK_EQUAL (range.second, 923); +} + + +BOOST_AUTO_TEST_CASE (dcp_F_to_V_movie) +{ + pair range = V_movie_range (dcp_F("dcp_F_to_V_movie")); + BOOST_CHECK_EQUAL (range.first, 64); + BOOST_CHECK_EQUAL (range.second, 944); +} + -- 2.30.2