summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2025-01-24 13:28:22 +0100
committerCarl Hetherington <cth@carlh.net>2025-01-24 13:28:22 +0100
commit7187b59857f84a5a94441f0873130cf0801e8ea6 (patch)
tree8ba431dbf60d2b6458de051d19be084242f1e675
parent744eae9a14f834088289d10d7158ad89857565ad (diff)
-rw-r--r--benchmark/cancel.cc127
-rw-r--r--benchmark/j2k_transcode.cc26
-rw-r--r--benchmark/wscript4
-rw-r--r--examples/wscript2
-rwxr-xr-xrun/benchmark2
-rw-r--r--src/cancel.h26
-rw-r--r--src/j2k_transcode.cc59
-rw-r--r--src/j2k_transcode.h39
-rw-r--r--src/wscript1
-rw-r--r--wscript9
10 files changed, 267 insertions, 28 deletions
diff --git a/benchmark/cancel.cc b/benchmark/cancel.cc
new file mode 100644
index 00000000..af475318
--- /dev/null
+++ b/benchmark/cancel.cc
@@ -0,0 +1,127 @@
+/*
+ Copyright (C) 2015-2019 Carl Hetherington <cth@carlh.net>
+
+ This file is part of libdcp.
+
+ libdcp 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.
+
+ libdcp 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 libdcp. If not, see <http://www.gnu.org/licenses/>.
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of portions of this program with the
+ OpenSSL library under certain conditions as described in each
+ individual source file, and distribute linked combinations
+ including the two.
+
+ You must obey the GNU General Public License in all respects
+ for all of the code used other than OpenSSL. If you modify
+ file(s) with this exception, you may extend this exception to your
+ version of the file(s), but you are not obligated to do so. If you
+ do not wish to do so, delete this exception statement from your
+ version. If you delete this exception statement from all source
+ files in the program, then also delete it here.
+*/
+
+
+#include "j2k_transcode.h"
+#include <boost/thread.hpp>
+#include <sys/time.h>
+#include <cfloat>
+#include <iomanip>
+#include <iostream>
+
+
+using std::cout;
+using std::cerr;
+using std::fixed;
+using std::make_shared;
+using std::setprecision;
+using std::vector;
+
+
+class Timer
+{
+public:
+ Timer()
+ {
+ gettimeofday(&_start, 0);
+ }
+
+ float get()
+ {
+ struct timeval now;
+ gettimeofday(&now, 0);
+ return (now.tv_sec + now.tv_usec / 1e6) - (_start.tv_sec + _start.tv_usec / 1e6);
+ }
+
+private:
+ struct timeval _start;
+};
+
+
+/** Test how fast J2K decodes can be cancelled */
+int
+main(int argc, char* argv[])
+{
+ if (argc < 2) {
+ cerr << "Syntax: " << argv[0] << " private-test-path\n";
+ exit(EXIT_FAILURE);
+ }
+
+ int const trials = 512;
+ int const max_sleep_ms = 200;
+
+ dcp::ArrayData j2k(boost::filesystem::path(argv[1]) / "thx.j2c");
+
+ float minimum = FLT_MAX;
+ float maximum = 0;
+ float total = 0;
+
+ vector<int> histo(25);
+
+ for (int i = 0; i < trials; ++i) {
+ auto cancel = make_shared<dcp::OpenJPEGCancel>();
+ boost::thread thread([&]() {
+ try {
+ dcp::decompress_j2k(j2k, 0, cancel);
+ } catch (...) {}
+ });
+
+ usleep((rand() % max_sleep_ms) * 1000);
+ Timer timer;
+ cancel->cancel();
+ thread.join();
+ auto time = timer.get();
+ minimum = std::min(minimum, time);
+ maximum = std::max(maximum, time);
+ total += time;
+
+ int const ms = time * 1000;
+ if (ms < static_cast<int>(histo.size())) {
+ histo[ms]++;
+ }
+ }
+
+ cout << fixed << setprecision(3) << "min: " << minimum << "\n";
+ cout << fixed << setprecision(3) << "max: " << maximum << "\n";
+ cout << fixed << setprecision(3) << "ave: " << (total / trials) << "\n";
+
+ for (auto i = 0U; i < histo.size(); ++i) {
+ std::cout << i << ": " << histo[i] << "\n";
+ }
+
+ return 0;
+}
+
+
+
+
diff --git a/benchmark/j2k_transcode.cc b/benchmark/j2k_transcode.cc
index 71602e0b..1e628079 100644
--- a/benchmark/j2k_transcode.cc
+++ b/benchmark/j2k_transcode.cc
@@ -37,6 +37,7 @@
#include "openjpeg_image.h"
#include "util.h"
#include "version.h"
+#include <boost/thread/thread.hpp>
#include <sys/time.h>
#include <iostream>
#include <cstdio>
@@ -86,8 +87,9 @@ main (int argc, char* argv[])
exit (EXIT_FAILURE);
}
- int const count = 100;
+ int const count = 128;
int const j2k_bandwidth = 100000000;
+ int const threads = 16;
dcp::ArrayData j2k (boost::filesystem::path (argv[1]) / "thx.j2c");
@@ -95,16 +97,20 @@ main (int argc, char* argv[])
Timer compress;
dcp::ArrayData recomp;
- for (int i = 0; i < count; ++i) {
- decompress.start ();
- shared_ptr<dcp::OpenJPEGImage> xyz = dcp::decompress_j2k (j2k, 0);
- decompress.stop ();
- compress.start ();
- recomp = dcp::compress_j2k (xyz, j2k_bandwidth, 24, false, false);
- compress.stop ();
- cout << (i + 1) << " ";
- cout.flush ();
+
+ auto body = [&]() {
+ for (int i = 0; i < (count / threads); ++i) {
+ auto xyz = dcp::decompress_j2k(j2k, 0);
+ }
+ };
+
+ boost::thread_group group;
+ decompress.start ();
+ for (int i = 0; i < threads; ++i) {
+ group.create_thread(body);
}
+ group.join_all();
+ decompress.stop ();
cout << "\n";
cout << "Decompress: " << count / decompress.get() << " fps.\n";
diff --git a/benchmark/wscript b/benchmark/wscript
index 7305bdaa..f3d4621b 100644
--- a/benchmark/wscript
+++ b/benchmark/wscript
@@ -32,10 +32,10 @@
#
def build(bld):
- for p in ['rgb_to_xyz', 'j2k_transcode']:
+ for p in ['rgb_to_xyz', 'j2k_transcode', 'cancel']:
obj = bld(features='cxx cxxprogram')
obj.name = p
- obj.uselib = 'BOOST_FILESYSTEM ASDCPLIB_DCPOMATIC CXML AVCODEC AVUTIL'
+ obj.uselib = 'BOOST_FILESYSTEM BOOST_THREAD ASDCPLIB_DCPOMATIC CXML AVCODEC AVUTIL OPENJPEG'
obj.cppflags = ['-g', '-O2']
obj.use = 'libdcp%s' % bld.env.API_VERSION
obj.source = "%s.cc" % p
diff --git a/examples/wscript b/examples/wscript
index 68cd79ee..113997f8 100644
--- a/examples/wscript
+++ b/examples/wscript
@@ -21,7 +21,7 @@ def build(bld):
obj = bld(features='cxx cxxprogram')
obj.name = example
obj.use = 'libdcp%s' % bld.env.API_VERSION
- obj.uselib = 'OPENJPEG CXML OPENMP ASDCPLIB_DCPOMATIC BOOST_FILESYSTEM OPENSSL XMLSEC1 MAGICK AVCODEC AVUTIL'
+ obj.uselib = 'OPENJPEG CXML OPENMP ASDCPLIB_DCPOMATIC BOOST_FILESYSTEM OPENSSL XMLSEC1 MAGICK AVCODEC AVUTIL BOOST_THREAD'
obj.source = example + '.cc'
obj.target = example
obj.install_path = ''
diff --git a/run/benchmark b/run/benchmark
index 3f18afbe..ebf03689 100755
--- a/run/benchmark
+++ b/run/benchmark
@@ -13,7 +13,7 @@ if [ "$bm" == "" ]; then
exit 1
fi
-export LD_LIBRARY_PATH=build/src
+export LD_LIBRARY_PATH=build/src:/usr/local/lib64
if [ "$perf" == "1" ]; then
perf stat build/benchmark/$bm "$*"
else
diff --git a/src/cancel.h b/src/cancel.h
new file mode 100644
index 00000000..a292e135
--- /dev/null
+++ b/src/cancel.h
@@ -0,0 +1,26 @@
+#ifndef LIBDCP_CANCEL_H
+#define LIBDCP_CANCEL_H
+
+
+#include <stdexcept>
+
+
+namespace dcp {
+
+
+class Cancel
+{
+public:
+ virtual ~Cancel() {}
+
+ virtual void cancel() = 0;
+ virtual void reset() = 0;
+};
+
+
+class Cancelled : public std::exception {};
+
+
+}
+
+#endif
diff --git a/src/j2k_transcode.cc b/src/j2k_transcode.cc
index 664d18a3..4fc48042 100644
--- a/src/j2k_transcode.cc
+++ b/src/j2k_transcode.cc
@@ -57,16 +57,16 @@ using namespace dcp;
shared_ptr<dcp::OpenJPEGImage>
-dcp::decompress_j2k (Data const& data, int reduce)
+dcp::decompress_j2k(Data const& data, int reduce, shared_ptr<OpenJPEGCancel> cancel)
{
- return dcp::decompress_j2k (data.data(), data.size(), reduce);
+ return dcp::decompress_j2k(data.data(), data.size(), reduce, cancel);
}
shared_ptr<dcp::OpenJPEGImage>
-dcp::decompress_j2k (shared_ptr<const Data> data, int reduce)
+dcp::decompress_j2k(shared_ptr<const Data> data, int reduce, shared_ptr<OpenJPEGCancel> cancel)
{
- return dcp::decompress_j2k (data->data(), data->size(), reduce);
+ return dcp::decompress_j2k (data->data(), data->size(), reduce, cancel);
}
@@ -123,7 +123,7 @@ compress_error_callback (char const * msg, void *)
shared_ptr<dcp::OpenJPEGImage>
-dcp::decompress_j2k (uint8_t const * data, int64_t size, int reduce)
+dcp::decompress_j2k(uint8_t const * data, int64_t size, int reduce, shared_ptr<OpenJPEGCancel> cancel)
{
DCP_ASSERT (reduce >= 0);
@@ -152,6 +152,10 @@ dcp::decompress_j2k (uint8_t const * data, int64_t size, int reduce)
parameters.cp_reduce = reduce;
opj_setup_decoder (decoder, &parameters);
+ if (cancel) {
+ opj_set_cancel(decoder, cancel->opj_cancel());
+ }
+
auto stream = opj_stream_default_create (OPJ_TRUE);
if (!stream) {
throw MiscError ("could not create JPEG2000 stream");
@@ -166,19 +170,24 @@ dcp::decompress_j2k (uint8_t const * data, int64_t size, int reduce)
opj_image_t* image = 0;
opj_read_header (stream, decoder, &image);
- if (opj_decode (decoder, stream, image) == OPJ_FALSE) {
- opj_destroy_codec (decoder);
- opj_stream_destroy (stream);
+ auto const result = opj_decode(decoder, stream, image);
+ opj_destroy_codec (decoder);
+ opj_stream_destroy (stream);
+
+ switch (result) {
+ case OPJ_FAILURE:
if (format == OPJ_CODEC_J2K) {
boost::throw_exception (ReadError (String::compose ("could not decode JPEG2000 codestream of %1 bytes.", size)));
} else {
boost::throw_exception (ReadError (String::compose ("could not decode JP2 file of %1 bytes.", size)));
}
+ break;
+ case OPJ_CANCEL:
+ boost::throw_exception(Cancelled{});
+ default:
+ break;
}
- opj_destroy_codec (decoder);
- opj_stream_destroy (stream);
-
image->x1 = rint (float(image->x1) / pow (2.0f, reduce));
image->y1 = rint (float(image->y1) / pow (2.0f, reduce));
return std::make_shared<OpenJPEGImage>(image);
@@ -332,3 +341,31 @@ dcp::compress_j2k (shared_ptr<const OpenJPEGImage> xyz, int bandwidth, int frame
return enc;
}
+
+OpenJPEGCancel::OpenJPEGCancel()
+{
+ _cancel = opj_create_cancel();
+}
+
+
+OpenJPEGCancel::~OpenJPEGCancel()
+{
+ opj_destroy_cancel(_cancel);
+}
+
+
+void
+OpenJPEGCancel::cancel()
+{
+ ::opj_cancel(_cancel);
+}
+
+
+void
+OpenJPEGCancel::reset()
+{
+ ::opj_reset(_cancel);
+}
+
+
+
diff --git a/src/j2k_transcode.h b/src/j2k_transcode.h
index 1e149293..17613baf 100644
--- a/src/j2k_transcode.h
+++ b/src/j2k_transcode.h
@@ -32,22 +32,51 @@
*/
+#ifndef LIBDCP_J2K_TRANSCODE_H
+#define LIBDCP_J2K_TRANSCODE_H
+
+
/** @file src/j2k_transcode.h
* @brief Methods to encode and decode JPEG2000
*/
#include "array_data.h"
+#include "cancel.h"
#include <memory>
#include <stdint.h>
+typedef struct opj_cancel opj_cancel_t;
+
+
namespace dcp {
class OpenJPEGImage;
+class OpenJPEGCancel : public Cancel
+{
+public:
+ OpenJPEGCancel();
+ ~OpenJPEGCancel();
+
+ OpenJPEGCancel(OpenJPEGCancel const&) = delete;
+ OpenJPEGCancel& operator=(OpenJPEGCancel const&) = delete;
+
+ void cancel() override;
+ void reset() override;
+
+ opj_cancel_t* opj_cancel() {
+ return _cancel;
+ }
+
+private:
+ opj_cancel_t* _cancel;
+};
+
+
/** Decompress a JPEG2000 image to a bitmap
* @param data JPEG2000 data
* @param size Size of data in bytes
@@ -57,10 +86,10 @@ class OpenJPEGImage;
* This is useful for scaling 4K DCP images down to 2K.
* @return OpenJPEGImage
*/
-extern std::shared_ptr<OpenJPEGImage> decompress_j2k (uint8_t const * data, int64_t size, int reduce);
+extern std::shared_ptr<OpenJPEGImage> decompress_j2k(uint8_t const * data, int64_t size, int reduce, std::shared_ptr<OpenJPEGCancel> cancel = {});
-extern std::shared_ptr<OpenJPEGImage> decompress_j2k (Data const& data, int reduce);
-extern std::shared_ptr<OpenJPEGImage> decompress_j2k (std::shared_ptr<const Data> data, int reduce);
+extern std::shared_ptr<OpenJPEGImage> decompress_j2k (Data const& data, int reduce, std::shared_ptr<OpenJPEGCancel> cancel = {});
+extern std::shared_ptr<OpenJPEGImage> decompress_j2k (std::shared_ptr<const Data> data, int reduce, std::shared_ptr<OpenJPEGCancel> cancel = {});
/** @xyz Picture to compress. Parts of xyz's data WILL BE OVERWRITTEN by libopenjpeg so xyz cannot be re-used
* after this call; see opj_j2k_encode where if l_reuse_data is false it will set l_tilec->data = l_img_comp->data.
@@ -69,3 +98,7 @@ extern ArrayData compress_j2k (std::shared_ptr<const OpenJPEGImage>, int bandwid
}
+
+
+#endif
+
diff --git a/src/wscript b/src/wscript
index 8c6ae134..ab417d6e 100644
--- a/src/wscript
+++ b/src/wscript
@@ -147,6 +147,7 @@ def build(bld):
atmos_asset_writer.h
atmos_frame.h
behaviour.h
+ cancel.h
certificate.h
certificate_chain.h
chromaticity.h
diff --git a/wscript b/wscript
index 1fcdd119..a44f02b3 100644
--- a/wscript
+++ b/wscript
@@ -263,6 +263,15 @@ def configure(conf):
lib=['boost_date_time%s' % boost_lib_suffix, 'boost_system%s' % boost_lib_suffix],
uselib_store='BOOST_DATETIME')
+ conf.check_cxx(fragment="""
+ #include <boost/thread.hpp>\n
+ int main() { boost::thread t; }\n
+ """,
+ msg='Checking for boost threading library',
+ lib=['boost_thread%s' % boost_lib_suffix, 'boost_system%s' % boost_lib_suffix],
+ uselib_store='BOOST_THREAD')
+
+
conf.check_cfg(package='libavcodec', args='--cflags --libs', uselib_store='AVCODEC', mandatory=True)
conf.check_cfg(package='libavutil', args='--cflags --libs', uselib_store='AVUTIL', mandatory=True)