summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/lib/j2k_encoder.cc45
-rw-r--r--src/lib/j2k_encoder.h15
-rw-r--r--src/lib/kakadu.cc58
-rw-r--r--src/lib/kakadu.h55
-rw-r--r--src/lib/kakadu_j2k_encoder_thread.cc78
-rw-r--r--src/lib/kakadu_j2k_encoder_thread.h37
-rw-r--r--src/lib/wscript3
-rw-r--r--test/j2k_encode_threading_test.cc12
8 files changed, 291 insertions, 12 deletions
diff --git a/src/lib/j2k_encoder.cc b/src/lib/j2k_encoder.cc
index e3e3c881b..4acf6158b 100644
--- a/src/lib/j2k_encoder.cc
+++ b/src/lib/j2k_encoder.cc
@@ -35,6 +35,9 @@
#include "grok/context.h"
#include "grok_j2k_encoder_thread.h"
#endif
+#ifdef DCPOMATIC_KAKADU
+#include "kakadu_j2k_encoder_thread.h"
+#endif
#include "openjpeg_j2k_encoder_thread.h"
#include "remote_j2k_encoder_thread.h"
#include "j2k_encoder.h"
@@ -104,6 +107,15 @@ J2KEncoder::J2KEncoder(shared_ptr<const Film> film, Writer& writer)
_context = new grk_plugin::GrokContext(_dcpomatic_context);
}
#endif
+
+#ifdef DCPOMATIC_KAKADU
+ try {
+ _kakadu_shared_memory = new KakaduSharedMemory();
+ LOG_GENERAL("Found Kakadu server");
+ } catch (boost::interprocess::interprocess_exception& e) {
+ LOG_ERROR("Could not find Kakadu server ({})", e.what());
+ }
+#endif
}
@@ -127,6 +139,10 @@ J2KEncoder::~J2KEncoder()
delete _context;
delete _dcpomatic_context;
#endif
+
+#ifdef DCPOMATIC_KAKADU
+ delete _kakadu_shared_memory;
+#endif
}
@@ -140,11 +156,12 @@ J2KEncoder::servers_list_changed()
auto const grok_enable = false;
#endif
- auto const openjpeg = (grok_enable || config->only_servers_encode()) ? 0 : config->master_encoding_threads();
+ auto const openjpeg = 0;// (grok_enable || config->only_servers_encode()) ? 0 : config->master_encoding_threads();
+ auto const kakadu = 1;
auto const gpu = grok_enable ? config->master_encoding_threads() : 0;
LOG_GENERAL("Thread counts from: grok={}, only_servers={}, master={}", grok_enable ? "yes" : "no", config->only_servers_encode() ? "yes" : "no", config->master_encoding_threads());
- remake_threads(openjpeg, gpu, EncodeServerFinder::instance()->servers());
+ remake_threads(openjpeg, kakadu, gpu, EncodeServerFinder::instance()->servers());
}
@@ -362,10 +379,10 @@ J2KEncoder::terminate_threads()
void
-J2KEncoder::remake_threads(int openjpeg, int gpu, list<EncodeServerDescription> servers)
+J2KEncoder::remake_threads(int openjpeg, int kakadu, int gpu, list<EncodeServerDescription> servers)
{
- LOG_GENERAL("Making threads: OpenJPEG={}, GPU={}, Remote={}", openjpeg, gpu, servers.size());
- if ((openjpeg + gpu + servers.size()) == 0) {
+ LOG_GENERAL("Making threads: OpenJPEG={}, Kakadu={}, GPU={}, Remote={}", openjpeg, kakadu, gpu, servers.size());
+ if ((openjpeg + kakadu + gpu + servers.size()) == 0) {
/* Make at least one thread, even if all else fails. Maybe we are configured
* for "only servers encode" but no servers have been registered yet.
*/
@@ -404,6 +421,24 @@ J2KEncoder::remake_threads(int openjpeg, int gpu, list<EncodeServerDescription>
remove_threads(openjpeg, current_openjpeg_threads, is_openjpeg_thread);
+#ifdef DCPOMATIC_KAKADU
+ /* Kakadu (on CPU) */
+
+ auto const is_kakadu_thread = [](shared_ptr<J2KEncoderThread> thread) {
+ return static_cast<bool>(dynamic_pointer_cast<KakaduJ2KEncoderThread>(thread));
+ };
+
+ auto const current_kakadu_threads = std::count_if(_threads.begin(), _threads.end(), is_kakadu_thread);
+
+ for (auto i = current_kakadu_threads; i < kakadu; ++i) {
+ auto thread = make_shared<KakaduJ2KEncoderThread>(*this);
+ thread->start();
+ _threads.push_back(thread);
+ }
+
+ remove_threads(kakadu, current_kakadu_threads, is_kakadu_thread);
+#endif
+
#ifdef DCPOMATIC_GROK
/* GPU */
diff --git a/src/lib/j2k_encoder.h b/src/lib/j2k_encoder.h
index 18f7be9e4..130214c2d 100644
--- a/src/lib/j2k_encoder.h
+++ b/src/lib/j2k_encoder.h
@@ -33,6 +33,9 @@
#include "event_history.h"
#include "exception_store.h"
#include "j2k_encoder_thread.h"
+#ifdef DCPOMATIC_KAKADU
+#include "kakadu.h"
+#endif
#include "writer.h"
#include "video_encoder.h"
#include <dcp/warnings.h>
@@ -94,6 +97,12 @@ public:
void retry(DCPVideo frame);
void write(std::shared_ptr<const dcp::Data> data, int index, Eyes eyes);
+#ifdef DCPOMATIC_KAKADU
+ KakaduSharedMemory* kakadu_shared_memory() {
+ return _kakadu_shared_memory;
+ }
+#endif
+
private:
friend struct ::local_threads_created_and_destroyed;
friend struct ::remote_threads_created_and_destroyed;
@@ -101,7 +110,7 @@ private:
void frame_done();
void servers_list_changed();
- void remake_threads(int openjpeg, int gpu, std::list<EncodeServerDescription> servers);
+ void remake_threads(int openjpeg, int kakadu, int gpu, std::list<EncodeServerDescription> servers);
void terminate_threads();
boost::mutex _threads_mutex;
@@ -126,6 +135,10 @@ private:
std::atomic<bool> _give_up;
#endif
+#ifdef DCPOMATIC_KAKADU
+ KakaduSharedMemory* _kakadu_shared_memory = nullptr;
+#endif
+
bool _ending = false;
};
diff --git a/src/lib/kakadu.cc b/src/lib/kakadu.cc
new file mode 100644
index 000000000..32b05e6e8
--- /dev/null
+++ b/src/lib/kakadu.cc
@@ -0,0 +1,58 @@
+/*
+ Copyright (C) 2026 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 "kakadu.h"
+
+
+KakaduSharedMemory::KakaduSharedMemory()
+ : _segment(boost::interprocess::open_only, "dcpomatic:kakadu")
+{
+
+}
+
+
+KakaduSharedMemory::~KakaduSharedMemory()
+{
+
+}
+
+
+KakaduSharedMetadata*
+KakaduSharedMemory::metadata()
+{
+ return _segment.find<KakaduSharedMetadata>("metadata").first;
+}
+
+
+uint16_t*
+KakaduSharedMemory::xyz()
+{
+ return _segment.find<uint16_t>("xyz").first;
+}
+
+
+uint8_t*
+KakaduSharedMemory::j2k()
+{
+ return _segment.find<uint8_t>("j2k").first;
+}
+
+
diff --git a/src/lib/kakadu.h b/src/lib/kakadu.h
new file mode 100644
index 000000000..e9e3e2edf
--- /dev/null
+++ b/src/lib/kakadu.h
@@ -0,0 +1,55 @@
+/*
+ Copyright (C) 2026 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 <boost/interprocess/managed_shared_memory.hpp>
+#include <boost/interprocess/sync/interprocess_mutex.hpp>
+#include <boost/interprocess/sync/interprocess_condition.hpp>
+
+
+struct KakaduSharedMetadata
+{
+ boost::interprocess::interprocess_mutex mutex;
+ bool request = false;
+ boost::interprocess::interprocess_condition request_condition;
+ bool reply = false;
+ int j2k_size = 0;
+ boost::interprocess::interprocess_condition reply_condition;
+};
+
+
+
+class KakaduSharedMemory
+{
+public:
+ KakaduSharedMemory();
+ ~KakaduSharedMemory();
+
+ KakaduSharedMemory(KakaduSharedMemory const&) = delete;
+ KakaduSharedMemory& operator=(KakaduSharedMemory const&) = delete;
+
+ KakaduSharedMetadata* metadata();
+ uint16_t* xyz();
+ uint8_t* j2k();
+
+private:
+ boost::interprocess::managed_shared_memory _segment;
+};
+
diff --git a/src/lib/kakadu_j2k_encoder_thread.cc b/src/lib/kakadu_j2k_encoder_thread.cc
new file mode 100644
index 000000000..d8f2a0530
--- /dev/null
+++ b/src/lib/kakadu_j2k_encoder_thread.cc
@@ -0,0 +1,78 @@
+/*
+ Copyright (C) 2026 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 "cross.h"
+#include "dcpomatic_log.h"
+#include "dcp_video.h"
+#include "j2k_encoder.h"
+#include "kakadu_j2k_encoder_thread.h"
+#include "util.h"
+#include <boost/interprocess/sync/scoped_lock.hpp>
+
+#include "i18n.h"
+
+
+using std::make_shared;
+using std::shared_ptr;
+
+
+KakaduJ2KEncoderThread::KakaduJ2KEncoderThread(J2KEncoder& encoder)
+ : J2KSyncEncoderThread(encoder)
+{
+
+}
+
+
+void
+KakaduJ2KEncoderThread::log_thread_start() const
+{
+ start_of_thread("KakaduJ2KEncoder");
+ LOG_TIMING("start-encoder-thread thread={} server=localhost", thread_id());
+}
+
+
+shared_ptr<dcp::ArrayData>
+KakaduJ2KEncoderThread::encode(DCPVideo const& frame)
+{
+ auto shmem = _encoder.kakadu_shared_memory();
+ if (!shmem) {
+ LOG_ERROR(N_("Kakadu encode failed (no shared memory)"));
+ return {};
+ }
+
+ auto metadata = shmem->metadata();
+
+ std::cout << "Send an image.\n";
+ boost::interprocess::scoped_lock lm(metadata->mutex);
+ frame.convert_to_xyz(shmem->xyz());
+
+ metadata->request = true;
+ metadata->request_condition.notify_one();
+
+ while (!metadata->reply) {
+ metadata->reply_condition.wait(lm);
+ }
+ metadata->reply = false;
+ std::cout << "reading " << metadata->j2k_size << "\n";
+
+ return make_shared<dcp::ArrayData>(shmem->j2k(), metadata->j2k_size);
+}
+
diff --git a/src/lib/kakadu_j2k_encoder_thread.h b/src/lib/kakadu_j2k_encoder_thread.h
new file mode 100644
index 000000000..aac9d338a
--- /dev/null
+++ b/src/lib/kakadu_j2k_encoder_thread.h
@@ -0,0 +1,37 @@
+/*
+ Copyright (C) 2026 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 "j2k_sync_encoder_thread.h"
+#include <dcp/data.h>
+
+
+class DCPVideo;
+
+
+class KakaduJ2KEncoderThread : public J2KSyncEncoderThread
+{
+public:
+ KakaduJ2KEncoderThread(J2KEncoder& encoder);
+
+ void log_thread_start() const override;
+ std::shared_ptr<dcp::ArrayData> encode(DCPVideo const& frame) override;
+};
+
diff --git a/src/lib/wscript b/src/lib/wscript
index 79393c425..b4fb17eb3 100644
--- a/src/lib/wscript
+++ b/src/lib/wscript
@@ -276,6 +276,9 @@ def build(bld):
if bld.env.ENABLE_GROK:
obj.source += ' grok_j2k_encoder_thread.cc grok/util.cc'
+ if bld.env.ENABLE_KAKADU:
+ obj.source += ' kakadu.cc kakadu_j2k_encoder_thread.cc'
+
if bld.env.TARGET_WINDOWS_64 or bld.env.TARGET_WINDOWS_32:
obj.uselib += ' WINSOCK2 DBGHELP SHLWAPI MSWSOCK BOOST_LOCALE SETUPAPI OLE32 UUID'
obj.source += ' cross_windows.cc i18n_setup_windows.cc'
diff --git a/test/j2k_encode_threading_test.cc b/test/j2k_encode_threading_test.cc
index e6f10ee07..4495a3ada 100644
--- a/test/j2k_encode_threading_test.cc
+++ b/test/j2k_encode_threading_test.cc
@@ -50,10 +50,10 @@ BOOST_AUTO_TEST_CASE(local_threads_created_and_destroyed)
Writer writer(film, {}, "foo");
J2KEncoder encoder(film, writer);
- encoder.remake_threads(32, 0, {});
+ encoder.remake_threads(32, 0, 0, {});
BOOST_CHECK_EQUAL(encoder._threads.size(), 32U);
- encoder.remake_threads(9, 0, {});
+ encoder.remake_threads(9, 0, 0, {});
BOOST_CHECK_EQUAL(encoder._threads.size(), 9U);
encoder.end();
@@ -73,7 +73,7 @@ BOOST_AUTO_TEST_CASE(remote_threads_created_and_destroyed)
{ "sheila", 14, SERVER_LINK_VERSION },
};
- encoder.remake_threads(0, 0, servers);
+ encoder.remake_threads(0, 0, 0, servers);
BOOST_CHECK_EQUAL(encoder._threads.size(), 7U + 2U + 14U);
servers = {
@@ -82,7 +82,7 @@ BOOST_AUTO_TEST_CASE(remote_threads_created_and_destroyed)
{ "sheila", 14, SERVER_LINK_VERSION },
};
- encoder.remake_threads(0, 0, servers);
+ encoder.remake_threads(0, 0, 0, servers);
BOOST_CHECK_EQUAL(encoder._threads.size(), 7U + 5U + 14U);
servers = {
@@ -91,7 +91,7 @@ BOOST_AUTO_TEST_CASE(remote_threads_created_and_destroyed)
{ "sheila", 11, SERVER_LINK_VERSION },
};
- encoder.remake_threads(0, 0, servers);
+ encoder.remake_threads(0, 0, 0, servers);
BOOST_CHECK_EQUAL(encoder._threads.size(), 11U);
}
@@ -105,7 +105,7 @@ BOOST_AUTO_TEST_CASE(frames_not_lost_when_threads_disappear)
while (JobManager::instance()->work_to_do()) {
if (auto encoder = dynamic_cast<J2KEncoder*>(dynamic_pointer_cast<DCPFilmEncoder>(job->_encoder)->_encoder.get())) {
- encoder->remake_threads((rand() % 7) + 1, 0, {});
+ encoder->remake_threads((rand() % 7) + 1, 0, 0, {});
dcpomatic_sleep_seconds(1);
}
}