diff options
| -rw-r--r-- | src/lib/ffmpeg_image_proxy.cc | 24 | ||||
| -rw-r--r-- | src/lib/ffmpeg_image_proxy.h | 6 | ||||
| -rw-r--r-- | src/lib/image.cc | 43 | ||||
| -rw-r--r-- | src/lib/image.h | 1 | ||||
| -rw-r--r-- | src/lib/image_decoder.cc | 2 | ||||
| -rw-r--r-- | src/lib/image_examiner.cc | 2 | ||||
| -rw-r--r-- | src/lib/types.cc | 28 | ||||
| -rw-r--r-- | src/lib/types.h | 5 | ||||
| -rw-r--r-- | src/lib/util.cc | 2 | ||||
| -rw-r--r-- | test/image_proxy_test.cc | 8 | ||||
| -rw-r--r-- | test/image_test.cc | 16 | ||||
| -rw-r--r-- | test/test.cc | 4 | ||||
| -rw-r--r-- | test/video_level_test.cc | 97 | ||||
| -rw-r--r-- | test/wscript | 1 |
14 files changed, 215 insertions, 24 deletions
diff --git a/src/lib/ffmpeg_image_proxy.cc b/src/lib/ffmpeg_image_proxy.cc index 602185bb8..c978fc383 100644 --- a/src/lib/ffmpeg_image_proxy.cc +++ b/src/lib/ffmpeg_image_proxy.cc @@ -30,6 +30,7 @@ extern "C" { #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> +#include <libavutil/pixdesc.h> } DCPOMATIC_DISABLE_WARNINGS #include <libxml++/libxml++.h> @@ -48,23 +49,26 @@ using boost::optional; using boost::dynamic_pointer_cast; using dcp::raw_convert; -FFmpegImageProxy::FFmpegImageProxy (boost::filesystem::path path) +FFmpegImageProxy::FFmpegImageProxy (boost::filesystem::path path, VideoRange video_range) : _data (path) + , _video_range (video_range) , _pos (0) , _path (path) { } -FFmpegImageProxy::FFmpegImageProxy (dcp::ArrayData data) +FFmpegImageProxy::FFmpegImageProxy (dcp::ArrayData data, VideoRange video_range) : _data (data) + , _video_range (video_range) , _pos (0) { } -FFmpegImageProxy::FFmpegImageProxy (shared_ptr<cxml::Node>, shared_ptr<Socket> socket) - : _pos (0) +FFmpegImageProxy::FFmpegImageProxy (shared_ptr<cxml::Node> node, shared_ptr<Socket> socket) + : _video_range (string_to_video_range(node->string_child("VideoRange"))) + , _pos (0) { uint32_t const size = socket->read_uint32 (); _data = dcp::ArrayData (size); @@ -188,7 +192,16 @@ FFmpegImageProxy::image (optional<dcp::Size>) const throw DecodeError (N_("could not decode video")); } - _image.reset (new Image (frame)); + AVPixelFormat const pix_fmt = static_cast<AVPixelFormat>(frame->format); + + _image.reset (new Image(frame)); + if (_video_range == VIDEO_RANGE_VIDEO && av_pix_fmt_desc_get(pix_fmt)->flags & AV_PIX_FMT_FLAG_RGB) { + /* Asking for the video range to be converted by libswscale (in Image) will not work for + * RGB sources since that method only processes video range in YUV and greyscale. So we have + * to do it ourselves here. + */ + _image->video_range_to_full_range(); + } av_packet_unref (&packet); av_frame_free (&frame); @@ -206,6 +219,7 @@ void FFmpegImageProxy::add_metadata (xmlpp::Node* node) const { node->add_child("Type")->add_child_text (N_("FFmpeg")); + node->add_child("VideoRange")->add_child_text(video_range_to_string(_video_range)); } void diff --git a/src/lib/ffmpeg_image_proxy.h b/src/lib/ffmpeg_image_proxy.h index 62b99d280..4fca899f4 100644 --- a/src/lib/ffmpeg_image_proxy.h +++ b/src/lib/ffmpeg_image_proxy.h @@ -19,6 +19,7 @@ */ #include "image_proxy.h" +#include "types.h" #include <dcp/array_data.h> #include <boost/thread/mutex.hpp> #include <boost/filesystem.hpp> @@ -26,8 +27,8 @@ class FFmpegImageProxy : public ImageProxy { public: - explicit FFmpegImageProxy (boost::filesystem::path); - explicit FFmpegImageProxy (dcp::ArrayData); + explicit FFmpegImageProxy (boost::filesystem::path, VideoRange video_range); + explicit FFmpegImageProxy (dcp::ArrayData, VideoRange video_range); FFmpegImageProxy (boost::shared_ptr<cxml::Node> xml, boost::shared_ptr<Socket> socket); Result image ( @@ -44,6 +45,7 @@ public: private: dcp::ArrayData _data; + VideoRange _video_range; mutable int64_t _pos; /** Path of a file that this image came from, if applicable; stored so that failed-decode errors can give more detail. diff --git a/src/lib/image.cc b/src/lib/image.cc index 03f1bf6dc..891715a46 100644 --- a/src/lib/image.cc +++ b/src/lib/image.cc @@ -1319,3 +1319,46 @@ Image::as_png () const return dcp::ArrayData (state.data, state.size); } + + +void +Image::video_range_to_full_range () +{ + switch (_pixel_format) { + case AV_PIX_FMT_RGB24: + { + float const factor = 256.0 / 219.0; + uint8_t* p = data()[0]; + int const lines = sample_size(0).height; + for (int y = 0; y < lines; ++y) { + uint8_t* q = p; + for (int x = 0; x < line_size()[0]; ++x) { + *q = int((*q - 16) * factor); + ++q; + } + p += stride()[0]; + } + break; + } + case AV_PIX_FMT_GBRP12LE: + { + float const factor = 4096.0 / 3504.0; + for (int c = 0; c < 3; ++c) { + uint16_t* p = reinterpret_cast<uint16_t*>(data()[c]); + int const lines = sample_size(c).height; + for (int y = 0; y < lines; ++y) { + uint16_t* q = p; + int const line_size_pixels = line_size()[c] / 2; + for (int x = 0; x < line_size_pixels; ++x) { + *q = int((*q - 256) * factor); + ++q; + } + } + } + break; + } + default: + throw PixelFormatError ("video_range_to_full_range()", _pixel_format); + } +} + diff --git a/src/lib/image.h b/src/lib/image.h index ab9b3c78a..c648fda1b 100644 --- a/src/lib/image.h +++ b/src/lib/image.h @@ -74,6 +74,7 @@ public: void alpha_blend (boost::shared_ptr<const Image> image, Position<int> pos); void copy (boost::shared_ptr<const Image> image, Position<int> pos); void fade (float); + void video_range_to_full_range (); void read_from_socket (boost::shared_ptr<Socket>); void write_to_socket (boost::shared_ptr<Socket>) const; diff --git a/src/lib/image_decoder.cc b/src/lib/image_decoder.cc index 15187b11b..7757cc4aa 100644 --- a/src/lib/image_decoder.cc +++ b/src/lib/image_decoder.cc @@ -70,7 +70,7 @@ ImageDecoder::pass () */ _image.reset (new J2KImageProxy (path, _image_content->video->size(), pf)); } else { - _image.reset (new FFmpegImageProxy (path)); + _image.reset (new FFmpegImageProxy(path, _image_content->video->range())); } } diff --git a/src/lib/image_examiner.cc b/src/lib/image_examiner.cc index 6586a0d09..aa80d0daa 100644 --- a/src/lib/image_examiner.cc +++ b/src/lib/image_examiner.cc @@ -63,7 +63,7 @@ ImageExaminer::ImageExaminer (shared_ptr<const Film> film, shared_ptr<const Imag } delete[] buffer; } else { - FFmpegImageProxy proxy(content->path(0)); + FFmpegImageProxy proxy(content->path(0), content->video->range()); _video_size = proxy.image().image->size(); } diff --git a/src/lib/types.cc b/src/lib/types.cc index e7acf6992..5687a5d48 100644 --- a/src/lib/types.cc +++ b/src/lib/types.cc @@ -229,3 +229,31 @@ bool operator== (NamedChannel const& a, NamedChannel const& b) return a.name == b.name && a.index == b.index; } + +string +video_range_to_string (VideoRange r) +{ + switch (r) { + case VIDEO_RANGE_FULL: + return "full"; + case VIDEO_RANGE_VIDEO: + return "video"; + default: + DCPOMATIC_ASSERT (false); + } +} + + +VideoRange +string_to_video_range (string s) +{ + if (s == "full") { + return VIDEO_RANGE_FULL; + } else if (s == "video") { + return VIDEO_RANGE_VIDEO; + } + + DCPOMATIC_ASSERT (false); + return VIDEO_RANGE_FULL; +} + diff --git a/src/lib/types.h b/src/lib/types.h index 2ba0408ad..a10b26a63 100644 --- a/src/lib/types.h +++ b/src/lib/types.h @@ -139,12 +139,17 @@ enum ChangeType CHANGE_TYPE_CANCELLED }; + enum VideoRange { VIDEO_RANGE_FULL, ///< full, or "JPEG" (0-255 for 8-bit) VIDEO_RANGE_VIDEO ///< video, or "MPEG" (16-235 for 8-bit) }; +extern std::string video_range_to_string (VideoRange r); +extern VideoRange string_to_video_range (std::string s); + + /** Type of captions. * * The generally accepted definitions seem to be: diff --git a/src/lib/util.cc b/src/lib/util.cc index ac868c173..0a060e960 100644 --- a/src/lib/util.cc +++ b/src/lib/util.cc @@ -953,7 +953,7 @@ void emit_subtitle_image (ContentTimePeriod period, dcp::SubtitleImage sub, dcp::Size size, shared_ptr<TextDecoder> decoder) { /* XXX: this is rather inefficient; decoding the image just to get its size */ - FFmpegImageProxy proxy (sub.png_image()); + FFmpegImageProxy proxy (sub.png_image(), VIDEO_RANGE_FULL); shared_ptr<Image> image = proxy.image().image; /* set up rect with height and width */ dcpomatic::Rect<double> rect(0, 0, image->size().width / double(size.width), image->size().height / double(size.height)); diff --git a/test/image_proxy_test.cc b/test/image_proxy_test.cc index 061df9eed..0999d4b10 100644 --- a/test/image_proxy_test.cc +++ b/test/image_proxy_test.cc @@ -53,14 +53,14 @@ BOOST_AUTO_TEST_CASE (j2k_image_proxy_same_test) BOOST_AUTO_TEST_CASE (ffmpeg_image_proxy_same_test) { { - shared_ptr<FFmpegImageProxy> proxy1(new FFmpegImageProxy(data_file0)); - shared_ptr<FFmpegImageProxy> proxy2(new FFmpegImageProxy(data_file0)); + shared_ptr<FFmpegImageProxy> proxy1(new FFmpegImageProxy(data_file0, VIDEO_RANGE_FULL)); + shared_ptr<FFmpegImageProxy> proxy2(new FFmpegImageProxy(data_file0, VIDEO_RANGE_FULL)); BOOST_CHECK (proxy1->same(proxy2)); } { - shared_ptr<FFmpegImageProxy> proxy1(new FFmpegImageProxy(data_file0)); - shared_ptr<FFmpegImageProxy> proxy2(new FFmpegImageProxy(data_file1)); + shared_ptr<FFmpegImageProxy> proxy1(new FFmpegImageProxy(data_file0, VIDEO_RANGE_FULL)); + shared_ptr<FFmpegImageProxy> proxy2(new FFmpegImageProxy(data_file1, VIDEO_RANGE_FULL)); BOOST_CHECK (!proxy1->same(proxy2)); } } diff --git a/test/image_test.cc b/test/image_test.cc index bdd34c665..e2b1d71c7 100644 --- a/test/image_test.cc +++ b/test/image_test.cc @@ -137,7 +137,7 @@ BOOST_AUTO_TEST_CASE (compact_image_test) void alpha_blend_test_one (AVPixelFormat format, string suffix) { - shared_ptr<FFmpegImageProxy> proxy (new FFmpegImageProxy (TestPaths::private_data() / "prophet_frame.tiff")); + shared_ptr<FFmpegImageProxy> proxy (new FFmpegImageProxy (TestPaths::private_data() / "prophet_frame.tiff", VIDEO_RANGE_FULL)); shared_ptr<Image> raw = proxy->image().image; shared_ptr<Image> background = raw->convert_pixel_format (dcp::YUV_TO_RGB_REC709, format, true, false); @@ -259,7 +259,7 @@ BOOST_AUTO_TEST_CASE (merge_test2) /** Test Image::crop_scale_window with YUV420P and some windowing */ BOOST_AUTO_TEST_CASE (crop_scale_window_test) { - shared_ptr<FFmpegImageProxy> proxy(new FFmpegImageProxy("test/data/flat_red.png")); + shared_ptr<FFmpegImageProxy> proxy(new FFmpegImageProxy("test/data/flat_red.png", VIDEO_RANGE_FULL)); shared_ptr<Image> raw = proxy->image().image; shared_ptr<Image> out = raw->crop_scale_window(Crop(), dcp::Size(1998, 836), dcp::Size(1998, 1080), dcp::YUV_TO_RGB_REC709, VIDEO_RANGE_FULL, AV_PIX_FMT_YUV420P, true, false); shared_ptr<Image> save = out->scale(dcp::Size(1998, 1080), dcp::YUV_TO_RGB_REC709, AV_PIX_FMT_RGB24, false, false); @@ -277,7 +277,7 @@ BOOST_AUTO_TEST_CASE (crop_scale_window_test2) BOOST_AUTO_TEST_CASE (crop_scale_window_test3) { - shared_ptr<FFmpegImageProxy> proxy(new FFmpegImageProxy("test/data/player_seek_test_0.png")); + shared_ptr<FFmpegImageProxy> proxy(new FFmpegImageProxy("test/data/player_seek_test_0.png", VIDEO_RANGE_FULL)); shared_ptr<Image> xyz = proxy->image().image->convert_pixel_format(dcp::YUV_TO_RGB_REC709, AV_PIX_FMT_RGB24, true, false); shared_ptr<Image> cropped = xyz->crop_scale_window(Crop(512, 0, 0, 0), dcp::Size(1486, 1080), dcp::Size(1998, 1080), dcp::YUV_TO_RGB_REC709, VIDEO_RANGE_FULL, AV_PIX_FMT_RGB24, false, false); write_image(cropped, "build/test/crop_scale_window_test3.png"); @@ -286,7 +286,7 @@ BOOST_AUTO_TEST_CASE (crop_scale_window_test3) BOOST_AUTO_TEST_CASE (crop_scale_window_test4) { - shared_ptr<FFmpegImageProxy> proxy(new FFmpegImageProxy("test/data/player_seek_test_0.png")); + shared_ptr<FFmpegImageProxy> proxy(new FFmpegImageProxy("test/data/player_seek_test_0.png", VIDEO_RANGE_FULL)); shared_ptr<Image> xyz = proxy->image().image->convert_pixel_format(dcp::YUV_TO_RGB_REC709, AV_PIX_FMT_RGB24, true, false); shared_ptr<Image> cropped = xyz->crop_scale_window(Crop(512, 0, 0, 0), dcp::Size(1486, 1080), dcp::Size(1998, 1080), dcp::YUV_TO_RGB_REC709, VIDEO_RANGE_FULL, AV_PIX_FMT_XYZ12LE, false, false); write_image(cropped, "build/test/crop_scale_window_test4.png"); @@ -295,7 +295,7 @@ BOOST_AUTO_TEST_CASE (crop_scale_window_test4) BOOST_AUTO_TEST_CASE (crop_scale_window_test5) { - shared_ptr<FFmpegImageProxy> proxy(new FFmpegImageProxy("test/data/player_seek_test_0.png")); + shared_ptr<FFmpegImageProxy> proxy(new FFmpegImageProxy("test/data/player_seek_test_0.png", VIDEO_RANGE_FULL)); shared_ptr<Image> xyz = proxy->image().image->convert_pixel_format(dcp::YUV_TO_RGB_REC709, AV_PIX_FMT_XYZ12LE, true, false); shared_ptr<Image> cropped = xyz->crop_scale_window(Crop(512, 0, 0, 0), dcp::Size(1486, 1080), dcp::Size(1998, 1080), dcp::YUV_TO_RGB_REC709, VIDEO_RANGE_FULL, AV_PIX_FMT_RGB24, false, false); write_image(cropped, "build/test/crop_scale_window_test5.png"); @@ -304,7 +304,7 @@ BOOST_AUTO_TEST_CASE (crop_scale_window_test5) BOOST_AUTO_TEST_CASE (crop_scale_window_test6) { - shared_ptr<FFmpegImageProxy> proxy(new FFmpegImageProxy("test/data/player_seek_test_0.png")); + shared_ptr<FFmpegImageProxy> proxy(new FFmpegImageProxy("test/data/player_seek_test_0.png", VIDEO_RANGE_FULL)); shared_ptr<Image> xyz = proxy->image().image->convert_pixel_format(dcp::YUV_TO_RGB_REC709, AV_PIX_FMT_XYZ12LE, true, false); shared_ptr<Image> cropped = xyz->crop_scale_window(Crop(512, 0, 0, 0), dcp::Size(1486, 1080), dcp::Size(1998, 1080), dcp::YUV_TO_RGB_REC709, VIDEO_RANGE_FULL, AV_PIX_FMT_XYZ12LE, false, false); write_image(cropped, "build/test/crop_scale_window_test6.png"); @@ -313,7 +313,7 @@ BOOST_AUTO_TEST_CASE (crop_scale_window_test6) BOOST_AUTO_TEST_CASE (as_png_test) { - shared_ptr<FFmpegImageProxy> proxy(new FFmpegImageProxy("test/data/3d_test/000001.png")); + shared_ptr<FFmpegImageProxy> proxy(new FFmpegImageProxy("test/data/3d_test/000001.png", VIDEO_RANGE_FULL)); shared_ptr<Image> image_rgb = proxy->image().image; shared_ptr<Image> image_bgr = image_rgb->convert_pixel_format(dcp::YUV_TO_RGB_REC709, AV_PIX_FMT_BGRA, true, false); image_rgb->as_png().write ("build/test/as_png_rgb.png"); @@ -339,7 +339,7 @@ fade_test_format_black (AVPixelFormat f, string name) static void fade_test_format_red (AVPixelFormat f, float amount, string name) { - shared_ptr<FFmpegImageProxy> proxy(new FFmpegImageProxy("test/data/flat_red.png")); + shared_ptr<FFmpegImageProxy> proxy(new FFmpegImageProxy("test/data/flat_red.png", VIDEO_RANGE_FULL)); shared_ptr<Image> red = proxy->image().image->convert_pixel_format(dcp::YUV_TO_RGB_REC709, f, true, false); red->fade (amount); string const filename = "fade_test_red_" + name + ".png"; diff --git a/test/test.cc b/test/test.cc index 9ac202b80..5981697fb 100644 --- a/test/test.cc +++ b/test/test.cc @@ -336,9 +336,9 @@ static double rms_error (boost::filesystem::path ref, boost::filesystem::path check) { - FFmpegImageProxy ref_proxy (ref); + FFmpegImageProxy ref_proxy (ref, VIDEO_RANGE_FULL); shared_ptr<Image> ref_image = ref_proxy.image().image; - FFmpegImageProxy check_proxy (check); + FFmpegImageProxy check_proxy (check, VIDEO_RANGE_FULL); shared_ptr<Image> check_image = check_proxy.image().image; BOOST_REQUIRE_EQUAL (ref_image->pixel_format(), check_image->pixel_format()); diff --git a/test/video_level_test.cc b/test/video_level_test.cc new file mode 100644 index 000000000..2849910b1 --- /dev/null +++ b/test/video_level_test.cc @@ -0,0 +1,97 @@ +/* + Copyright (C) 2020 Carl Hetherington <cth@carlh.net> + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>. + +*/ + + +/** @file test/video_level_test.cc + * @brief Test that video level ranges are handled correctly. + * @ingroup specific + */ + + +#include "lib/ffmpeg_image_proxy.h" +#include "lib/image.h" +#include "test.h" +#include <boost/test/unit_test.hpp> + + +using boost::shared_ptr; + + +static +shared_ptr<Image> +grey_image (dcp::Size size, uint8_t pixel) +{ + shared_ptr<Image> grey(new Image(AV_PIX_FMT_RGB24, size, true)); + for (int y = 0; y < size.height; ++y) { + uint8_t* p = grey->data()[0] + y * grey->stride()[0]; + for (int x = 0; x < size.width; ++x) { + *p++ = pixel; + *p++ = pixel; + *p++ = pixel; + } + } + + return grey; +} + + +BOOST_AUTO_TEST_CASE (ffmpeg_image_full_range_not_changed) +{ + dcp::Size size(640, 480); + uint8_t const grey_pixel = 128; + boost::filesystem::path const file = "build/test/ffmpeg_image_full_range_not_changed.png"; + + write_image (grey_image(size, grey_pixel), file); + + FFmpegImageProxy proxy (file, VIDEO_RANGE_FULL); + ImageProxy::Result result = proxy.image (); + BOOST_REQUIRE (!result.error); + + for (int y = 0; y < size.height; ++y) { + uint8_t* p = result.image->data()[0] + y * result.image->stride()[0]; + for (int x = 0; x < size.width; ++x) { + BOOST_REQUIRE (*p++ == grey_pixel); + } + } +} + + +BOOST_AUTO_TEST_CASE (ffmpeg_image_video_range_expanded) +{ + dcp::Size size(640, 480); + uint8_t const grey_pixel = 128; + uint8_t const expanded_grey_pixel = static_cast<uint8_t>((grey_pixel - 16) * 256.0 / 219); + boost::filesystem::path const file = "build/test/ffmpeg_image_video_range_expanded.png"; + + write_image (grey_image(size, grey_pixel), file); + + FFmpegImageProxy proxy (file, VIDEO_RANGE_VIDEO); + ImageProxy::Result result = proxy.image (); + BOOST_REQUIRE (!result.error); + + for (int y = 0; y < size.height; ++y) { + uint8_t* p = result.image->data()[0] + y * result.image->stride()[0]; + for (int x = 0; x < size.width; ++x) { + BOOST_REQUIRE_EQUAL (*p++, expanded_grey_pixel); + } + } +} + + diff --git a/test/wscript b/test/wscript index 50fe91b43..176f4ca67 100644 --- a/test/wscript +++ b/test/wscript @@ -130,6 +130,7 @@ def build(bld): util_test.cc vf_test.cc video_content_scale_test.cc + video_level_test.cc video_mxf_content_test.cc vf_kdm_test.cc zipper_test.cc |
