Add noise to very small J2K frames (#1902).
authorCarl Hetherington <cth@carlh.net>
Mon, 1 Feb 2021 00:24:55 +0000 (01:24 +0100)
committerCarl Hetherington <cth@carlh.net>
Mon, 1 Feb 2021 00:24:55 +0000 (01:24 +0100)
src/lib/dcp_video.cc
test/low_bitrate_test.cc [new file with mode: 0644]
test/wscript

index 58e10f0ed69f349b65d5c51b92b89f59a872205d..cb151772700a62e661ea4dcb1f50adabf67527bb 100644 (file)
@@ -124,15 +124,41 @@ DCPVideo::encode_locally ()
 {
        auto const comment = Config::instance()->dcp_j2k_comment();
 
 {
        auto const comment = Config::instance()->dcp_j2k_comment();
 
-       auto enc = dcp::compress_j2k (
-               convert_to_xyz (_frame, boost::bind(&Log::dcp_log, dcpomatic_log.get(), _1, _2)),
-               _j2k_bandwidth,
-               _frames_per_second,
-               _frame->eyes() == Eyes::LEFT || _frame->eyes() == Eyes::RIGHT,
-               _resolution == Resolution::FOUR_K,
-               comment.empty() ? "libdcp" : comment
+       ArrayData enc = {};
+       int constexpr minimum_size = 65536;
+
+       auto xyz = convert_to_xyz (_frame, boost::bind(&Log::dcp_log, dcpomatic_log.get(), _1, _2));
+       while (true) {
+               enc = dcp::compress_j2k (
+                       xyz,
+                       _j2k_bandwidth,
+                       _frames_per_second,
+                       _frame->eyes() == Eyes::LEFT || _frame->eyes() == Eyes::RIGHT,
+                       _resolution == Resolution::FOUR_K,
+                       comment.empty() ? "libdcp" : comment
                );
 
                );
 
+               if (enc.size() >= minimum_size) {
+                       break;
+               }
+
+               /* The JPEG2000 is too low-bitrate for some decoders <cough>DSS200</cough> so add some noise
+                * and try again.  This is slow but hopefully won't happen too often.  We have to do
+                * convert_to_xyz() again because compress_j2k() corrupts its xyz parameter.
+                */
+
+               xyz = convert_to_xyz (_frame, boost::bind(&Log::dcp_log, dcpomatic_log.get(), _1, _2));
+               auto size = xyz->size ();
+               auto pixels = size.width * size.height;
+               for (auto c = 0; c < 3; ++c) {
+                       auto p = xyz->data(c);
+                       for (auto i = 0; i < pixels; ++i) {
+                               *p = std::min(4095, std::max(0, *p + rand() % 2));
+                               ++p;
+                       }
+               }
+       }
+
        switch (_frame->eyes()) {
        case Eyes::BOTH:
                LOG_DEBUG_ENCODE (N_("Finished locally-encoded frame %1 for mono"), _index);
        switch (_frame->eyes()) {
        case Eyes::BOTH:
                LOG_DEBUG_ENCODE (N_("Finished locally-encoded frame %1 for mono"), _index);
diff --git a/test/low_bitrate_test.cc b/test/low_bitrate_test.cc
new file mode 100644 (file)
index 0000000..f7edecb
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+    Copyright (C) 2021 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/>.
+
+*/
+
+
+#include "lib/dcp_video.h"
+#include "lib/image.h"
+#include "lib/player_video.h"
+#include "lib/raw_image_proxy.h"
+extern "C" {
+#include <libavutil/pixfmt.h>
+}
+#include <boost/test/unit_test.hpp>
+#include <iostream>
+
+
+using std::make_shared;
+
+
+BOOST_AUTO_TEST_CASE (low_bitrate_test)
+{
+       auto image = make_shared<Image>(AV_PIX_FMT_RGB24, dcp::Size(1998, 1080), true);
+       image->make_black ();
+       auto proxy = make_shared<RawImageProxy>(image);
+       auto frame = make_shared<PlayerVideo>(proxy, Crop(), boost::optional<double>(), dcp::Size(1998, 1080), dcp::Size(1998, 1080), Eyes::BOTH, Part::WHOLE, boost::optional<ColourConversion>(), VideoRange::FULL, std::weak_ptr<Content>(), boost::optional<Frame>(), false);
+       auto dcp_video = make_shared<DCPVideo>(frame, 0, 24, 100000000, Resolution::TWO_K);
+       auto j2k = dcp_video->encode_locally();
+       BOOST_REQUIRE (j2k.size() >= 65536);
+}
+
+
index f47eaea737665e947279066918b1dd41ce6485f8..70aad81a33de3d3c972a6321b3a86fe4c890239f 100644 (file)
@@ -94,6 +94,7 @@ def build(bld):
                  j2k_bandwidth_test.cc
                  job_test.cc
                  kdm_naming_test.cc
                  j2k_bandwidth_test.cc
                  job_test.cc
                  kdm_naming_test.cc
+                 low_bitrate_test.cc
                  markers_test.cc
                  no_use_video_test.cc
                  optimise_stills_test.cc
                  markers_test.cc
                  no_use_video_test.cc
                  optimise_stills_test.cc