Check that JPEG2000 frames aren't too big (i.e. too
authorCarl Hetherington <cth@carlh.net>
Wed, 6 May 2020 23:08:51 +0000 (01:08 +0200)
committerCarl Hetherington <cth@carlh.net>
Thu, 7 May 2020 19:29:27 +0000 (21:29 +0200)
many bytes).

src/verify.cc
src/verify.h
test/verify_test.cc

index 152f3dc2506c28ab985b92184d5c9acedd1396b7..b10f25c1cdbb2f8408775a3155c1003cc678920e 100644 (file)
 #include "reel.h"
 #include "reel_picture_asset.h"
 #include "reel_sound_asset.h"
+#include "mono_picture_asset.h"
+#include "mono_picture_frame.h"
+#include "stereo_picture_asset.h"
+#include "stereo_picture_frame.h"
 #include "exceptions.h"
 #include "compose.hpp"
 #include "raw_convert.h"
@@ -70,9 +74,11 @@ using std::vector;
 using std::string;
 using std::cout;
 using std::map;
+using std::max;
 using boost::shared_ptr;
 using boost::optional;
 using boost::function;
+using boost::dynamic_pointer_cast;
 
 using namespace dcp;
 using namespace xercesc;
@@ -332,6 +338,70 @@ verify_asset (shared_ptr<DCP> dcp, shared_ptr<ReelMXF> reel_mxf, function<void (
 }
 
 
+enum VerifyPictureAssetResult
+{
+       VERIFY_PICTURE_ASSET_RESULT_GOOD,
+       VERIFY_PICTURE_ASSET_RESULT_FRAME_NEARLY_TOO_BIG,
+       VERIFY_PICTURE_ASSET_RESULT_BAD,
+};
+
+
+int
+biggest_frame_size (shared_ptr<const MonoPictureFrame> frame)
+{
+       return frame->j2k_size ();
+}
+
+int
+biggest_frame_size (shared_ptr<const StereoPictureFrame> frame)
+{
+       return max(frame->left_j2k_size(), frame->right_j2k_size());
+}
+
+
+template <class A, class R, class F>
+optional<VerifyPictureAssetResult>
+verify_picture_asset_type (shared_ptr<ReelMXF> reel_mxf, function<void (float)> progress)
+{
+       shared_ptr<A> asset = dynamic_pointer_cast<A>(reel_mxf->asset_ref().asset());
+       if (!asset) {
+               return optional<VerifyPictureAssetResult>();
+       }
+
+       int biggest_frame = 0;
+       shared_ptr<R> reader = asset->start_read ();
+       int64_t const duration = asset->intrinsic_duration ();
+       for (int64_t i = 0; i < duration; ++i) {
+               shared_ptr<const F> frame = reader->get_frame (i);
+               biggest_frame = max(biggest_frame, biggest_frame_size(frame));
+               progress (float(i) / duration);
+       }
+
+       static const int max_frame =   rint(250 * 1000000 / (8 * asset->edit_rate().as_float()));
+       static const int risky_frame = rint(230 * 1000000 / (8 * asset->edit_rate().as_float()));
+       if (biggest_frame > max_frame) {
+               return VERIFY_PICTURE_ASSET_RESULT_BAD;
+       } else if (biggest_frame > risky_frame) {
+               return VERIFY_PICTURE_ASSET_RESULT_FRAME_NEARLY_TOO_BIG;
+       }
+
+       return VERIFY_PICTURE_ASSET_RESULT_GOOD;
+}
+
+
+static VerifyPictureAssetResult
+verify_picture_asset (shared_ptr<ReelMXF> reel_mxf, function<void (float)> progress)
+{
+       optional<VerifyPictureAssetResult> r = verify_picture_asset_type<MonoPictureAsset, MonoPictureAssetReader, MonoPictureFrame>(reel_mxf, progress);
+       if (!r) {
+               r = verify_picture_asset_type<StereoPictureAsset, StereoPictureAssetReader, StereoPictureFrame>(reel_mxf, progress);
+       }
+
+       DCP_ASSERT (r);
+       return *r;
+}
+
+
 list<VerificationNote>
 dcp::verify (
        vector<boost::filesystem::path> directories,
@@ -398,20 +468,41 @@ dcp::verify (
                                        }
                                        /* Check asset */
                                        if (reel->main_picture()->asset_ref().resolved()) {
-                                               stage ("Checking picture asset hash", reel->main_picture()->asset()->file());
+                                               boost::filesystem::path const file = *reel->main_picture()->asset()->file();
+                                               stage ("Checking picture asset hash", file);
                                                VerifyAssetResult const r = verify_asset (dcp, reel->main_picture(), progress);
                                                switch (r) {
                                                case VERIFY_ASSET_RESULT_BAD:
                                                        notes.push_back (
                                                                VerificationNote(
-                                                                       VerificationNote::VERIFY_ERROR, VerificationNote::PICTURE_HASH_INCORRECT, *reel->main_picture()->asset()->file()
+                                                                       VerificationNote::VERIFY_ERROR, VerificationNote::PICTURE_HASH_INCORRECT, file
                                                                        )
                                                                );
                                                        break;
                                                case VERIFY_ASSET_RESULT_CPL_PKL_DIFFER:
                                                        notes.push_back (
                                                                VerificationNote(
-                                                                       VerificationNote::VERIFY_ERROR, VerificationNote::PKL_CPL_PICTURE_HASHES_DISAGREE, *reel->main_picture()->asset()->file()
+                                                                       VerificationNote::VERIFY_ERROR, VerificationNote::PKL_CPL_PICTURE_HASHES_DISAGREE, file
+                                                                       )
+                                                               );
+                                                       break;
+                                               default:
+                                                       break;
+                                               }
+                                               stage ("Checking picture frame sizes", reel->main_picture()->asset()->file());
+                                               VerifyPictureAssetResult const pr = verify_picture_asset (reel->main_picture(), progress);
+                                               switch (pr) {
+                                               case VERIFY_PICTURE_ASSET_RESULT_BAD:
+                                                       notes.push_back (
+                                                               VerificationNote(
+                                                                       VerificationNote::VERIFY_ERROR, VerificationNote::PICTURE_FRAME_TOO_LARGE, file
+                                                                       )
+                                                               );
+                                                       break;
+                                               case VERIFY_PICTURE_ASSET_RESULT_FRAME_NEARLY_TOO_BIG:
+                                                       notes.push_back (
+                                                               VerificationNote(
+                                                                       VerificationNote::VERIFY_WARNING, VerificationNote::PICTURE_FRAME_NEARLY_TOO_LARGE, file
                                                                        )
                                                                );
                                                        break;
@@ -493,6 +584,10 @@ dcp::note_to_string (dcp::VerificationNote note)
                return String::compose("The intrinsic duration of an asset is less than 1 second long: %1", note.note().get());
        case dcp::VerificationNote::DURATION_TOO_SMALL:
                return String::compose("The duration of an asset is less than 1 second long: %1", note.note().get());
+       case dcp::VerificationNote::PICTURE_FRAME_TOO_LARGE:
+               return String::compose("The instantaneous bit rate of the picture asset %1 is larger than the limit of 250Mbit/s in at least one place", note.file()->filename());
+       case dcp::VerificationNote::PICTURE_FRAME_NEARLY_TOO_LARGE:
+               return String::compose("The instantaneous bit rate of the picture asset %1 is close to the limit of 250Mbit/s in at least one place", note.file()->filename());
        }
 
        return "";
index 8d29df6b45fe55f1940c23acb5e202ab69e17b8d..4bd91f55b01fd290af19276bfc606ff7e0b3f405 100644 (file)
@@ -82,7 +82,11 @@ public:
                /** An asset's IntrinsicDuration is less than 1 second */
                INTRINSIC_DURATION_TOO_SMALL,
                /** An asset's Duration is less than 1 second */
-               DURATION_TOO_SMALL
+               DURATION_TOO_SMALL,
+               /** The JPEG2000 data in at least one picture frame is larger than the equivalent of 250Mbit/s */
+               PICTURE_FRAME_TOO_LARGE,
+               /** The JPEG2000 data in at least one picture frame is larger than the equivalent of 230Mbit/s */
+               PICTURE_FRAME_NEARLY_TOO_LARGE,
        };
 
        VerificationNote (Type type, Code code)
index 2ff66f6945f13ec89169ed3f9ecac4b3c6a00fe1..7a553d079a138d35fa8ca779a295f81b4e4e3ef6 100644 (file)
 
 #include "verify.h"
 #include "util.h"
+#include "j2k.h"
+#include "reel.h"
+#include "reel_mono_picture_asset.h"
+#include "cpl.h"
+#include "dcp.h"
+#include "openjpeg_image.h"
+#include "mono_picture_asset.h"
+#include "mono_picture_asset_writer.h"
 #include "compose.hpp"
 #include <boost/test/unit_test.hpp>
 #include <boost/foreach.hpp>
@@ -46,6 +54,8 @@ using std::string;
 using std::vector;
 using std::make_pair;
 using boost::optional;
+using boost::shared_ptr;
+
 
 static list<pair<string, optional<boost::filesystem::path> > > stages;
 static int next_verify_test_number = 1;
@@ -77,6 +87,10 @@ setup (int reference_number, int verify_test_number)
 
 }
 
+
+/** Class that can alter a file by searching and replacing strings within it.
+ *  On destruction modifies the file whose name was given to the constructor.
+ */
 class Editor
 {
 public:
@@ -140,6 +154,10 @@ BOOST_AUTO_TEST_CASE (verify_test1)
        BOOST_REQUIRE (st->second);
        BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1/video.mxf", next_verify_test_number)));
        ++st;
+       BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
+       BOOST_REQUIRE (st->second);
+       BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1/video.mxf", next_verify_test_number)));
+       ++st;
        BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
        BOOST_REQUIRE (st->second);
        BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1/audio.mxf", next_verify_test_number)));
@@ -451,6 +469,10 @@ BOOST_AUTO_TEST_CASE (verify_test13)
        BOOST_REQUIRE (st->second);
        BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1/j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf", next_verify_test_number)));
        ++st;
+       BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
+       BOOST_REQUIRE (st->second);
+       BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1/j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf", next_verify_test_number)));
+       ++st;
        BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
        BOOST_REQUIRE (st->second);
        BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1/pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf", next_verify_test_number)));
@@ -493,3 +515,95 @@ BOOST_AUTO_TEST_CASE (verify_test14)
        next_verify_test_number++;
 }
 
+
+static
+shared_ptr<dcp::OpenJPEGImage>
+random_image ()
+{
+       shared_ptr<dcp::OpenJPEGImage> image(new dcp::OpenJPEGImage(dcp::Size(1998, 1080)));
+       int const pixels = 1998 * 1080;
+       for (int i = 0; i < 3; ++i) {
+               int* p = image->data(i);
+               for (int j = 0; j < pixels; ++j) {
+                       *p++ = rand();
+               }
+       }
+       return image;
+}
+
+
+static
+void
+dcp_from_frame (dcp::Data const& frame, boost::filesystem::path dir)
+{
+       shared_ptr<dcp::MonoPictureAsset> asset(new dcp::MonoPictureAsset(dcp::Fraction(24, 1), dcp::SMPTE));
+       boost::filesystem::create_directories (dir);
+       shared_ptr<dcp::PictureAssetWriter> writer = asset->start_write (dir / "pic.mxf", true);
+       for (int i = 0; i < 24; ++i) {
+               writer->write (frame.data().get(), frame.size());
+       }
+       writer->finalize ();
+
+       shared_ptr<dcp::ReelAsset> reel_asset(new dcp::ReelMonoPictureAsset(asset, 0));
+       shared_ptr<dcp::Reel> reel(new dcp::Reel());
+       reel->add (reel_asset);
+       shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
+       cpl->add (reel);
+       shared_ptr<dcp::DCP> dcp(new dcp::DCP(dir));
+       dcp->add (cpl);
+       dcp->write_xml (dcp::SMPTE);
+}
+
+
+/* DCP with an over-sized JPEG2000 frame */
+BOOST_AUTO_TEST_CASE (verify_test15)
+{
+       /* Compress a random image with a bandwidth of 500Mbit/s */
+       shared_ptr<dcp::OpenJPEGImage> image = random_image ();
+       dcp::Data frame = dcp::compress_j2k (image, 500000000, 24, false, false);
+
+       boost::filesystem::path const dir("build/test/verify_test15");
+       dcp_from_frame (frame, dir);
+
+       vector<boost::filesystem::path> dirs;
+       dirs.push_back (dir);
+       list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, "xsd");
+       BOOST_REQUIRE_EQUAL (notes.size(), 1);
+       BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_FRAME_TOO_LARGE);
+}
+
+
+/* DCP with a nearly over-sized JPEG2000 frame */
+BOOST_AUTO_TEST_CASE (verify_test16)
+{
+       /* Compress a random image with a bandwidth of 500Mbit/s */
+       shared_ptr<dcp::OpenJPEGImage> image = random_image ();
+       dcp::Data frame = dcp::compress_j2k (image, 240000000, 24, false, false);
+
+       boost::filesystem::path const dir("build/test/verify_test16");
+       dcp_from_frame (frame, dir);
+
+       vector<boost::filesystem::path> dirs;
+       dirs.push_back (dir);
+       list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, "xsd");
+       BOOST_REQUIRE_EQUAL (notes.size(), 1);
+       BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_FRAME_NEARLY_TOO_LARGE);
+}
+
+
+/* DCP with a within-range JPEG2000 frame */
+BOOST_AUTO_TEST_CASE (verify_test17)
+{
+       /* Compress a random image with a bandwidth of 500Mbit/s */
+       shared_ptr<dcp::OpenJPEGImage> image = random_image ();
+       dcp::Data frame = dcp::compress_j2k (image, 100000000, 24, false, false);
+
+       boost::filesystem::path const dir("build/test/verify_test17");
+       dcp_from_frame (frame, dir);
+
+       vector<boost::filesystem::path> dirs;
+       dirs.push_back (dir);
+       list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, "xsd");
+       BOOST_REQUIRE_EQUAL (notes.size(), 0);
+}
+