Add fastvideo J2K encoding backend.
authorCarl Hetherington <cth@carlh.net>
Sun, 21 Nov 2021 13:32:45 +0000 (14:32 +0100)
committerCarl Hetherington <cth@carlh.net>
Wed, 24 Nov 2021 23:55:02 +0000 (00:55 +0100)
25 files changed:
run/dcpomatic
run/dcpomatic_cli
run/dcpomatic_player
run/tests
src/lib/barrier.cc [new file with mode: 0644]
src/lib/barrier.h [new file with mode: 0644]
src/lib/config.cc
src/lib/config.h
src/lib/exceptions.cc
src/lib/exceptions.h
src/lib/j2k_encoder.cc
src/lib/j2k_encoder.h
src/lib/j2k_encoder_cpu_backend.h
src/lib/j2k_encoder_fastvideo_backend.cc [new file with mode: 0644]
src/lib/j2k_encoder_fastvideo_backend.h [new file with mode: 0644]
src/lib/j2k_encoder_remote_backend.cc
src/lib/j2k_encoder_remote_backend.h
src/lib/wscript
src/tools/wscript
src/wx/full_config_dialog.cc
src/wx/wscript
test/barrier_test.cc [new file with mode: 0644]
test/fastvideo_test.cc [new file with mode: 0644]
test/wscript
wscript

index 6de2989ae35a05ed10bf60b6099078d04db10d1a..c1d7b98a468b48510fa7788fa6f539a189ece42b 100755 (executable)
@@ -2,7 +2,7 @@
 
 DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
 build=$DIR/../build
-export LD_LIBRARY_PATH=$build/src/lib:$build/src/wx:$build/src/asdcplib/src:$LD_LIBRARY_PATH
+export LD_LIBRARY_PATH=$build/src/lib:$build/src/wx:$build/src/asdcplib/src:/home/carl/fastvideo/current/fastvideo_sdk/lib:/home/carl/fastvideo/current/bin:$LD_LIBRARY_PATH
 export DYLD_LIBRARY_PATH=$build/src/lib:$build/src/wx:$build/src/asdcplib/src:/Users/ci/osx-environment/x86_64/10.10/lib
 export DCPOMATIC_GRAPHICS=$DIR/../graphics
 binary=$build/src/tools/dcpomatic2
index b24090db9d91cf6c5475e3bbfbfab8772714689e..511084de171809327e0587683c8537a1fcd8ebc8 100755 (executable)
@@ -2,7 +2,7 @@
 
 cd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"/..
 
-export LD_LIBRARY_PATH=build/src/lib:build/src:/home/c.hetherington/lib:$LD_LIBRARY_PATH
+export LD_LIBRARY_PATH=build/src/lib:build/src:/home/carl/fastvideo/current/fastvideo_sdk/lib:/home/carl/fastvideo/current/bin:$LD_LIBRARY_PATH
 if [ "$1" == "--debug" ]; then
     shift
     gdb --args build/src/tools/dcpomatic2_cli "$@"
index 168fa9bb10f131c63405f855f5fcdceca81181ba..dfc1ccaf83c4ab2bf38237b415465345fa2cd183 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/bash
 
-export LD_LIBRARY_PATH=build/src/lib:build/src/wx:build/src/asdcplib/src:$LD_LIBRARY_PATH
+export LD_LIBRARY_PATH=build/src/lib:build/src:/home/carl/fastvideo/current/fastvideo_sdk/lib:/home/carl/fastvideo/current/bin:$LD_LIBRARY_PATH
 export DYLD_LIBRARY_PATH=$build/src/lib:$build/src/wx:$build/src/asdcplib/src:/Users/ci/osx-environment/x86_64/10.10/lib
 export DCPOMATIC_GRAPHICS=graphics
 if [ "$1" == "--debug" ]; then
index 0c5c8c0c358fc46a8071b7569bc5f3ab2d62007d..73a3f59926d96be19e62932b1f098622f2270f2c 100755 (executable)
--- a/run/tests
+++ b/run/tests
@@ -3,7 +3,7 @@
 # e.g. --run_tests=foo
 
 if [ "$(uname)" == "Linux" ]; then 
-  export LD_LIBRARY_PATH=build/src/lib:$LD_LIBRARY_PATH
+  export LD_LIBRARY_PATH=build/src/lib:build/src:/home/carl/fastvideo/current/fastvideo_sdk/lib:/home/carl/fastvideo/current/bin:$LD_LIBRARY_PATH
   rm -f build/test/dcpomatic2_openssl
   # This must be our patched openssl or tests will fail
   if [ ! -f build/test/dcpomatic2_openssl ]; then 
diff --git a/src/lib/barrier.cc b/src/lib/barrier.cc
new file mode 100644 (file)
index 0000000..c00fdf0
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+    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 "barrier.h"
+
+
+Barrier::Barrier (int count)
+       : _count(count)
+{
+
+}
+
+
+void
+Barrier::wait ()
+{
+       boost::mutex::scoped_lock lm (_mutex);
+       ++_waiting;
+       if (_waiting >= _count) {
+               _condition.notify_all();
+               _waiting = 0;
+       } else {
+               _condition.wait(lm);
+       }
+}
+
+
+void
+Barrier::lower ()
+{
+       boost::mutex::scoped_lock lm (_mutex);
+       _count = 0;
+       _condition.notify_all();
+}
+
diff --git a/src/lib/barrier.h b/src/lib/barrier.h
new file mode 100644 (file)
index 0000000..3d05c7d
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+    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 <boost/thread/condition.hpp>
+#include <boost/thread/mutex.hpp>
+
+
+class Barrier
+{
+public:
+       Barrier (int count);
+       Barrier (Barrier const&) = delete;
+       Barrier& operator= (Barrier const&) = delete;
+
+       void wait ();
+       void lower ();
+
+private:
+       boost::mutex _mutex;
+       boost::condition _condition;
+       int _count;
+       int _waiting = 0;
+};
+
+
index 78f35e82127b812f119529dac16d47e939b65ad3..9c31c062128e6e1205eec9b22f861cc39c851cd3 100644 (file)
@@ -80,6 +80,7 @@ Config::Config ()
 void
 Config::set_defaults ()
 {
+       _encoding_backend = EncodingBackend::CPU;
        _master_encoding_threads = max (2U, boost::thread::hardware_concurrency ());
        _server_encoding_threads = max (2U, boost::thread::hardware_concurrency ());
        _server_port_base = 6192;
@@ -238,6 +239,10 @@ try
                backup ();
        }
 
+       if (auto encoding_backend = f.optional_string_child("EncodingBackend")) {
+               _encoding_backend = *encoding_backend == "cpu" ? EncodingBackend::CPU : EncodingBackend::FASTVIDEO;
+       }
+
        if (f.optional_number_child<int>("NumLocalEncodingThreads")) {
                _master_encoding_threads = _server_encoding_threads = f.optional_number_child<int>("NumLocalEncodingThreads").get();
        } else {
@@ -609,6 +614,7 @@ Config::write_config () const
 
        /* [XML] Version The version number of the configuration file format. */
        root->add_child("Version")->add_child_text (raw_convert<string>(_current_version));
+       root->add_child("EncodingBackend")->add_child_text(_encoding_backend == EncodingBackend::CPU ? "cpu" : "fastvideo");
        /* [XML] MasterEncodingThreads Number of encoding threads to use when running as master. */
        root->add_child("MasterEncodingThreads")->add_child_text (raw_convert<string> (_master_encoding_threads));
        /* [XML] ServerEncodingThreads Number of encoding threads to use when running as server. */
index 215e6a4dbcbe33caf7680a3243021f727f513ae9..26624ff0493e4a6e8579afac64799cbbe128ae81 100644 (file)
@@ -50,6 +50,15 @@ class DKDMRecipient;
 class Config : public State
 {
 public:
+       enum class EncodingBackend {
+               CPU,
+               FASTVIDEO
+       };
+
+       EncodingBackend encoding_backend() const {
+               return _encoding_backend;
+       }
+
        /** @return number of threads which a master DoM should use for J2K encoding on the local machine */
        int master_encoding_threads () const {
                return _master_encoding_threads;
@@ -545,6 +554,10 @@ public:
 
        /* SET (mostly) */
 
+       void set_encoding_backend (EncodingBackend backend) {
+               maybe_set (_encoding_backend, backend);
+       }
+
        void set_master_encoding_threads (int n) {
                maybe_set (_master_encoding_threads, n);
        }
@@ -1122,6 +1135,7 @@ private:
                changed (prop);
        }
 
+       EncodingBackend _encoding_backend;
        /** number of threads which a master DoM should use for J2K encoding on the local machine */
        int _master_encoding_threads;
        /** number of threads which a server should use for J2K encoding on the local machine */
index 3f87a2ebe142893bba046fca25075b7423716faa..630df7d5c14fc1e5037b27742ecc66476f78ffae 100644 (file)
@@ -163,3 +163,12 @@ VerifyError::VerifyError (string m, int n)
 
 }
 
+
+#ifdef DCPOMATIC_FASTVIDEO
+FastvideoError::FastvideoError (string s, string message)
+        : runtime_error (String::compose("Fastvideo error in %1 (%2)", s, message))
+{
+
+}
+#endif
+
index 9b7837a46bef89c695f0be7097a254566b8d8939..3e841a354dc492b5dfc0ea40d144960f3aaab0c9 100644 (file)
@@ -32,6 +32,9 @@
 extern "C" {
 #include <libavutil/pixfmt.h>
 }
+#ifdef DCPOMATIC_FASTVIDEO
+#include <common/EnumToStringSdk.h>
+#endif
 #include <boost/filesystem.hpp>
 #include <boost/optional.hpp>
 #include <cstring>
@@ -447,4 +450,18 @@ public:
 };
 
 
+#ifdef DCPOMATIC_FASTVIDEO
+class FastvideoError : public std::runtime_error
+{
+public:
+       template <class T>
+        FastvideoError (std::string s, T code)
+               : std::runtime_error (String::compose("Fastvideo error in %1 (%2)", s, EnumToString(code)))
+       {}
+
+        FastvideoError (std::string s, std::string message);
+};
+#endif
+
+
 #endif
index fae7752cf47e59eab272e84a39c4f11e9a101c26..70f3c32d4983e96573ebabc8f1ebbe77c34acd7a 100644 (file)
@@ -26,6 +26,9 @@
 
 #include "j2k_encoder_cpu_backend.h"
 #include "j2k_encoder_remote_backend.h"
+#ifdef DCPOMATIC_FASTVIDEO
+#include "j2k_encoder_fastvideo_backend.h"
+#endif
 #include "j2k_encoder.h"
 #include "util.h"
 #include "film.h"
@@ -60,6 +63,9 @@ using namespace dcpomatic;
  */
 J2KEncoder::J2KEncoder (shared_ptr<const Film> film, shared_ptr<Writer> writer)
        : _film (film)
+#ifdef DCPOMATIC_FASTVIDEO
+       , _gpu_barrier (Config::instance()->master_encoding_threads())
+#endif
        , _history (200)
        , _writer (writer)
 {
@@ -105,6 +111,10 @@ J2KEncoder::end ()
 
        LOG_GENERAL (N_("Clearing queue of %1"), _queue.size ());
 
+#ifdef DCPOMATIC_FASTVIDEO
+       _gpu_barrier.lower();
+#endif
+
        /* Keep waking workers until the queue is empty */
        while (!_queue.empty ()) {
                rethrow ();
@@ -341,20 +351,12 @@ catch (...)
 }
 
 
-void
-J2KEncoder::servers_list_changed ()
-try
+int
+J2KEncoder::create_cpu_threads ()
 {
-       boost::mutex::scoped_lock lm (_threads_mutex);
-
-       terminate_threads ();
-       _threads = make_shared<boost::thread_group>();
-
-       _frames_in_parallel = 0;
-
-       /* XXX: could re-use threads */
+       int parallel = 0;
 
-       if (!Config::instance()->only_servers_encode ()) {
+       if (!Config::instance()->only_servers_encode()) {
                auto backend = std::make_shared<J2KEncoderCPUBackend>();
                for (int i = 0; i < Config::instance()->master_encoding_threads (); ++i) {
 #ifdef DCPOMATIC_LINUX
@@ -363,7 +365,7 @@ try
 #else
                        _threads->create_thread(boost::bind(&J2KEncoder::encoder_thread, this, backend));
 #endif
-                       _frames_in_parallel += backend->quantity();
+                       parallel += backend->quantity();
                }
        }
 
@@ -377,11 +379,54 @@ try
                LOG_GENERAL (N_("Adding %1 worker threads for remote %2"), i.threads(), i.host_name());
                for (int j = 0; j < i.threads(); ++j) {
                        _threads->create_thread(boost::bind(&J2KEncoder::encoder_thread, this, backend));
-                       _frames_in_parallel += backend->quantity();
+                       parallel += backend->quantity();
                }
        }
 
-       _writer->set_encoder_threads (_threads->size());
+       return parallel;
+}
+
+
+#ifdef DCPOMATIC_FASTVIDEO
+int
+J2KEncoder::create_fastvideo_threads ()
+{
+       int parallel = 0;
+       for (int i = 0; i < Config::instance()->master_encoding_threads(); ++i) {
+               auto backend = std::make_shared<J2KEncoderFastvideoBackend>(_gpu_barrier);
+               _threads->create_thread(boost::bind(&J2KEncoder::encoder_thread, this, backend));
+               parallel += backend->quantity();
+       }
+       return parallel;
+}
+#endif
+
+
+void
+J2KEncoder::servers_list_changed ()
+try
+{
+       boost::mutex::scoped_lock lm (_threads_mutex);
+
+       terminate_threads ();
+       _threads = make_shared<boost::thread_group>();
+
+       /* XXX: could re-use threads */
+
+#ifdef DCPOMATIC_FASTVIDEO
+       switch (Config::instance()->encoding_backend()) {
+               case Config::EncodingBackend::CPU:
+                       _frames_in_parallel = create_cpu_threads();
+                       break;
+               case Config::EncodingBackend::FASTVIDEO:
+                       _frames_in_parallel = create_fastvideo_threads();
+                       break;
+       }
+#else
+       _frames_in_parallel = create_cpu_threads();
+#endif
+
+       _writer->set_encoder_threads (_frames_in_parallel);
 }
 catch (...) {
        terminate_threads ();
index 2139eee3d5c174c254cb0368952dfa6ccd40a4da..22000424d7fedc22b26207bb37928f9fcdc5cba5 100644 (file)
@@ -28,6 +28,7 @@
  */
 
 
+#include "barrier.h"
 #include "cross.h"
 #include "event_history.h"
 #include "exception_store.h"
@@ -86,10 +87,15 @@ private:
        void frame_done ();
 
        void encoder_thread (std::shared_ptr<J2KEncoderBackend> backend);
+       int create_cpu_threads ();
        void terminate_threads ();
 
        std::shared_ptr<const Film> _film;
 
+#ifdef DCPOMATIC_FASTVIDEO
+       int create_fastvideo_threads ();
+       Barrier _gpu_barrier;
+#endif
 
        EventHistory _history;
 
index 82b8aeb489e45422ef97dd28191b447b03ea4b40..c099054542433ed78e1ecd9b21d95b490e68c3c4 100644 (file)
@@ -29,9 +29,6 @@
 class J2KEncoderCPUBackend : public J2KEncoderBackend
 {
 public:
-       J2KEncoderCPUBackend () = default;
-       J2KEncoderCPUBackend (J2KEncoderCPUBackend&& other) = default;
-
        std::vector<dcp::ArrayData> encode (std::vector<DCPVideo> const& video) override;
 };
 
diff --git a/src/lib/j2k_encoder_fastvideo_backend.cc b/src/lib/j2k_encoder_fastvideo_backend.cc
new file mode 100644 (file)
index 0000000..68707ad
--- /dev/null
@@ -0,0 +1,211 @@
+/*
+    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 "barrier.h"
+#include "dcp_video.h"
+#include "dcpomatic_assert.h"
+#include "dcpomatic_log.h"
+#include "exceptions.h"
+#include "image.h"
+#include "j2k_encoder_fastvideo_backend.h"
+#include "player_video.h"
+#include <dcp/rgb_xyz.h>
+#include <fastvideo_encoder_j2k.h>
+#include <fastvideo_sdk.h>
+#include <common/EnumToStringSdk.h>
+
+
+using std::vector;
+using boost::optional;
+using dcp::ArrayData;
+#if BOOST_VERSION >= 106100
+using namespace boost::placeholders;
+#endif
+
+
+
+J2KEncoderFastvideoBackend::J2KEncoderFastvideoBackend (Barrier& barrier)
+       : _barrier (barrier)
+{
+       fastDeviceProperty* device_property;
+       int devices;
+       auto r = fastGetDevices(&device_property, &devices);
+       if (r != FAST_OK) {
+               throw FastvideoError ("GetDevices", r);
+       }
+
+       fastSdkParametersHandle_t sdk_parameters;
+       r = fastGetSdkParametersHandle(&sdk_parameters);
+       if (r != FAST_OK) {
+               throw FastvideoError ("GetSdkParametersHandle", r);
+       }
+       r = fastEncoderJ2kLibraryInit(sdk_parameters);
+       if (r != FAST_OK) {
+               throw FastvideoError ("DecoderJ2kLibraryInit", r);
+       }
+       fastTraceCreate("/home/carl/trace.log");
+}
+
+
+J2KEncoderFastvideoBackend::~J2KEncoderFastvideoBackend ()
+{
+       if (_setup_done) {
+               fastFree (_xyz_buffer);
+               fastEncoderJ2kDestroy (_encoder);
+               fastImportFromHostDestroy (_adapter);
+       }
+}
+
+
+void
+J2KEncoderFastvideoBackend::setup (dcp::Size size)
+{
+       auto r = fastImportFromHostCreate(
+               &_adapter, FAST_RGB12, size.width, size.height, &_src_buffer
+               );
+       if (r != FAST_OK) {
+               throw FastvideoError ("ImportFromHostCreate", r);
+       }
+
+       fastEncoderJ2kStaticParameters_t parameters;
+       parameters.lossless = false;
+       parameters.pcrdEnabled = true;
+       parameters.dwtLevels = 6;
+       parameters.codeblockSize = 32;
+       parameters.maxQuality = 1.5;
+       parameters.compressionRatio = 1;
+       parameters.info = false;
+       parameters.tier2Threads = 4;
+       parameters.tileWidth = size.width;
+       parameters.tileHeight = size.height;
+       parameters.noMCT = false;
+       parameters.ss1_x = 1;
+       parameters.ss1_y = 1;
+       parameters.ss2_x = 1;
+       parameters.ss2_y = 1;
+       parameters.ss3_x = 1;
+       parameters.ss3_y = 1;
+       parameters.yuvSubsampledFormat = false;
+
+        r = fastEncoderJ2kCreate(
+                &_encoder, &parameters, FAST_RGB12, size.width, size.height, quantity(), _src_buffer
+               );
+       if (r != FAST_OK) {
+               throw FastvideoError ("EncoderJ2kCreate", r);
+       }
+
+        bool success = false;
+
+        r = fastEncoderJ2kIsInitialized(_encoder, &success);
+       if (r != FAST_OK || !success) {
+               throw FastvideoError ("EncoderJ2kIsInitialized", r);
+       }
+
+       _xyz_buffer_stride = size.width * 6;
+       _xyz_buffer_stride += 4 - (_xyz_buffer_stride % 4);
+       r = fastMalloc(reinterpret_cast<void**>(&_xyz_buffer), _xyz_buffer_stride * size.height);
+       if (r != FAST_OK) {
+               throw FastvideoError ("Malloc", r);
+       }
+}
+
+
+vector<ArrayData>
+J2KEncoderFastvideoBackend::encode (vector<DCPVideo> const& video)
+{
+       DCPOMATIC_ASSERT (static_cast<int>(video.size()) <= quantity());
+       _barrier.wait();
+
+       if (!_setup_done) {
+               setup (video.front().frame()->out_size());
+               _setup_done = true;
+       }
+
+       for (auto const& i: video) {
+               auto image = i.frame()->image(boost::bind(&PlayerVideo::keep_xyz_or_rgb, _1), VideoRange::FULL, false);
+               if (i.frame()->colour_conversion()) {
+                       dcp::rgb_to_xyz (
+                               image->data()[0],
+                               image->size(),
+                               image->stride()[0],
+                               _xyz_buffer,
+                               _xyz_buffer_stride,
+                               i.frame()->colour_conversion().get()
+                               );
+               } else {
+                       /* XXX */
+               }
+
+               auto r = fastImportFromHostCopy(
+                       _adapter,
+                       _xyz_buffer,
+                       image->size().width,
+                       _xyz_buffer_stride,
+                       image->size().height
+                       );
+               if (r != FAST_OK) {
+                       throw FastvideoError ("ImportFromHostCopy", r);
+               }
+
+               fastEncoderJ2kDynamicParameters_t dynamic_parameters;
+               dynamic_parameters.targetStreamSize = i.j2k_bandwidth() / (8 * i.frames_per_second());
+               dynamic_parameters.quality = 1.0;
+               dynamic_parameters.writeHeader = false;
+
+               r = fastEncoderJ2kAddImageToBatch(
+                       _encoder,
+                       &dynamic_parameters,
+                       image->size().width,
+                       image->size().height
+               );
+               if (r != FAST_OK) {
+                       throw FastvideoError ("EncoderJ2kAddImageToBatch", r);
+               }
+       }
+
+
+       fastEncoderJ2kReport_t report;
+       fastEncoderJ2kOutput_t output;
+       int constexpr max_j2k_size = 1024 * 1024 * 2;
+       output.bufferSize = max_j2k_size;
+       dcp::ArrayData data(output.bufferSize);
+       output.byteStream = data.data();
+
+       auto r = fastEncoderJ2kTransformBatch(_encoder, &output, &report);
+       if (r != FAST_OK) {
+               fastTraceClose();
+               throw FastvideoError ("EncoderJ2KTransformBatch", r);
+       }
+
+       vector<dcp::ArrayData> encoded;
+       for (size_t i = 0; i < video.size(); ++i) {
+               data.set_size (output.streamSize);
+               encoded.push_back (data);
+               data = dcp::ArrayData(output.bufferSize);
+               output.byteStream = data.data();
+               int images_left = 0;
+               r = fastEncoderJ2kGetNextEncodedImage (_encoder, &output, &report, &images_left);
+       }
+
+       return encoded;
+}
+
+
diff --git a/src/lib/j2k_encoder_fastvideo_backend.h b/src/lib/j2k_encoder_fastvideo_backend.h
new file mode 100644 (file)
index 0000000..0e5ccb3
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+    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/>.
+
+*/
+
+
+#ifndef DCPOMATIC_J2K_ENCODER_FASTVIDEO_BACKEND_H
+#define DCPOMATIC_J2K_ENCODER_FASTVIDEO_BACKEND_H
+
+
+#include "j2k_encoder_backend.h"
+#include <dcp/util.h>
+#include <fastvideo_encoder_j2k.h>
+#include <fastvideo_sdk.h>
+#include <boost/thread.hpp>
+
+
+class Barrier;
+
+
+class J2KEncoderFastvideoBackend : public J2KEncoderBackend
+{
+public:
+       J2KEncoderFastvideoBackend (Barrier& barrier);
+       ~J2KEncoderFastvideoBackend ();
+
+       std::vector<dcp::ArrayData> encode (std::vector<DCPVideo> const& video) override;
+
+       int quantity () const override {
+               return 1;
+       }
+
+private:
+       void setup (dcp::Size size);
+
+       Barrier& _barrier;
+       bool _setup_done = false;
+       fastImportFromHostHandle_t _adapter;
+       fastDeviceSurfaceBufferHandle_t _src_buffer;
+       fastEncoderJ2kHandle_t _encoder;
+       uint16_t* _xyz_buffer;
+       int _xyz_buffer_stride;
+};
+
+
+#endif
+
+
index df5d6e30d621da4e2aadb6b32bbd06991dd98594..b50ddad7be75a9fa10580ab9d7480b77fe03dae7 100644 (file)
@@ -38,22 +38,11 @@ DCPOMATIC_ENABLE_WARNINGS
 
 
 using std::make_shared;
-using std::shared_ptr;
 using std::string;
-using std::unique_ptr;
 using std::vector;
-using boost::optional;
 using dcp::raw_convert;
 
 
-J2KEncoderRemoteBackend::J2KEncoderRemoteBackend (J2KEncoderRemoteBackend&& other)
-       : _server (other._server)
-       , _backoff (other._backoff)
-{
-
-}
-
-
 vector<dcp::ArrayData>
 J2KEncoderRemoteBackend::encode (vector<DCPVideo> const& all_video)
 {
index 962944bbeca20754804dfdd43a68718aef8c9036..b034825d381aaa1c21daf1de69f59c5f9e8de9e9 100644 (file)
@@ -35,8 +35,6 @@ public:
                , _timeout (timeout)
        {}
 
-       J2KEncoderRemoteBackend (J2KEncoderRemoteBackend&& other);
-
        std::vector<dcp::ArrayData> encode (std::vector<DCPVideo> const& video) override;
 
 private:
index 9d7e1d11b06d410e41a93f6ff03f477c64f60d35..80eb7829ef643b9b760a32cb4312a0493e2d221c 100644 (file)
@@ -44,6 +44,7 @@ sources = """
           audio_processor.cc
           audio_ring_buffers.cc
           audio_stream.cc
+          barrier.cc
           butler.cc
           text_content.cc
           text_decoder.cc
@@ -226,6 +227,9 @@ def build(bld):
         obj.source += ' cross_linux.cc'
     if bld.env.STATIC_DCPOMATIC:
         obj.uselib += ' XMLPP'
+    if bld.env.ENABLE_FASTVIDEO:
+        obj.uselib += ' FASTVIDEO'
+        obj.source += ' j2k_encoder_fastvideo_backend.cc'
 
     obj.target = 'dcpomatic2'
 
index 018689e03d86058d92d3c7381b0d82bf48f99fa7..39c8279e759a537658ff7c1037fe9e06e9a6366c 100644 (file)
@@ -41,6 +41,8 @@ def build(bld):
         uselib += 'WINSOCK2 DBGHELP SHLWAPI MSWSOCK BOOST_LOCALE WINSOCK2 OLE32 DSOUND WINMM KSUSER SETUPAPI '
     if bld.env.TARGET_LINUX:
         uselib += 'DL '
+    if bld.env.ENABLE_FASTVIDEO:
+        uselib += ' FASTVIDEO'
 
     cli_tools = ['dcpomatic_cli', 'dcpomatic_server_cli', 'server_test', 'dcpomatic_kdm_cli', 'dcpomatic_create']
     if bld.env.ENABLE_DISK and not bld.env.DISABLE_GUI:
index db499f575118b0294f1db6079a4222f7cf9fe3fc..d10b01d8ecb5c1a041fa1ff09c58b106313ab417 100644 (file)
@@ -94,6 +94,13 @@ private:
                int r = 0;
                add_language_controls (table, r);
 
+#ifdef DCPOMATIC_FASTVIDEO
+               add_label_to_sizer (table, _panel, _("Encode using"), true, wxGBPosition(r, 0));
+               _encoding_backend = new wxChoice (_panel, wxID_ANY);
+               table->Add (_encoding_backend, wxGBPosition(r, 1));
+               ++r;
+#endif
+
                add_label_to_sizer (table, _panel, _("Number of threads DCP-o-matic should use"), true, wxGBPosition (r, 0));
                _master_encoding_threads = new wxSpinCtrl (_panel);
                table->Add (_master_encoding_threads, wxGBPosition (r, 1));
@@ -128,6 +135,11 @@ private:
 
                add_update_controls (table, r);
 
+#ifdef DCPOMATIC_FASTVIDEO
+               _encoding_backend->Append ("CPU");
+               _encoding_backend->Append ("GPU");
+               _encoding_backend->Bind (wxEVT_CHOICE, boost::bind(&FullGeneralPage::encoding_backend_changed, this));
+#endif
                _config_file->Bind  (wxEVT_FILEPICKER_CHANGED, boost::bind(&FullGeneralPage::config_file_changed,  this));
                _cinemas_file->Bind (wxEVT_FILEPICKER_CHANGED, boost::bind(&FullGeneralPage::cinemas_file_changed, this));
 
@@ -147,6 +159,10 @@ private:
        {
                auto config = Config::instance ();
 
+#ifdef DCPOMATIC_FASTVIDEO
+               checked_set (_encoding_backend, Config::instance()->encoding_backend() == Config::EncodingBackend::CPU ? 0 : 1);
+#endif
+
                checked_set (_master_encoding_threads, config->master_encoding_threads ());
                checked_set (_server_encoding_threads, config->server_encoding_threads ());
 #ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
@@ -159,6 +175,11 @@ private:
                GeneralPage::config_changed ();
        }
 
+       void encoding_backend_changed ()
+       {
+               Config::instance()->set_encoding_backend(_encoding_backend->GetSelection() == 0 ? Config::EncodingBackend::CPU : Config::EncodingBackend::FASTVIDEO);
+       }
+
        void export_cinemas_file ()
        {
                auto d = new wxFileDialog (
@@ -225,6 +246,9 @@ private:
                Config::instance()->set_cinemas_file (wx_to_std (_cinemas_file->GetPath ()));
        }
 
+#ifdef DCPOMATIC_FASTVIDEO
+       wxChoice* _encoding_backend;
+#endif
        wxSpinCtrl* _master_encoding_threads;
        wxSpinCtrl* _server_encoding_threads;
        FilePickerCtrl* _config_file;
index 50c078f2b0f1d50a108d8707727215c19bc8d4a9..757772183d6edadb315745699cda56ed0a91d2ea 100644 (file)
@@ -316,6 +316,8 @@ def build(bld):
         obj.uselib += 'WINSOCK2 OLE32 DSOUND WINMM KSUSER GL GLU GLEW '
     if bld.env.TARGET_OSX:
         obj.framework = ['CoreAudio', 'OpenGL']
+    if bld.env.ENABLE_FASTVIDEO:
+        obj.uselib += ' FASTVIDEO'
     obj.use = 'libdcpomatic2'
     obj.source = sources
     obj.target = 'dcpomatic2-wx'
diff --git a/test/barrier_test.cc b/test/barrier_test.cc
new file mode 100644 (file)
index 0000000..c7bfbb8
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+    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/barrier.h"
+#include "lib/cross.h"
+#include <boost/test/unit_test.hpp>
+#include <boost/thread/thread.hpp>
+
+
+BOOST_AUTO_TEST_CASE (barrier_test)
+{
+       int constexpr num_threads = 128;
+
+       Barrier barrier (num_threads);
+
+       boost::mutex mutex;
+       int count = 0;
+
+       auto thread_body = [&barrier, &mutex, &count]() {
+               barrier.wait();
+               boost::mutex::scoped_lock lm(mutex);
+               ++count;
+       };
+
+       std::vector<boost::thread*> threads;
+       for (int i = 0; i < num_threads - 1; ++i) {
+               threads.push_back(new boost::thread(thread_body));
+       }
+
+       dcpomatic_sleep_seconds(5);
+
+       BOOST_CHECK_EQUAL (count, 0);
+
+       threads.push_back(new boost::thread(thread_body));
+       dcpomatic_sleep_seconds(5);
+
+       BOOST_CHECK_EQUAL (count, num_threads);
+
+       for (auto i: threads) {
+               i->join();
+               delete i;
+       }
+}
+
+
+BOOST_AUTO_TEST_CASE (barrier_test_lower)
+{
+       int constexpr num_threads = 128;
+
+       Barrier barrier (num_threads);
+
+       boost::mutex mutex;
+       int count = 0;
+
+       auto thread_body = [&barrier, &mutex, &count]() {
+               barrier.wait();
+               boost::mutex::scoped_lock lm(mutex);
+               ++count;
+       };
+
+       std::vector<boost::thread*> threads;
+       for (int i = 0; i < num_threads / 2; ++i) {
+               threads.push_back(new boost::thread(thread_body));
+       }
+
+       dcpomatic_sleep_seconds(5);
+
+       BOOST_CHECK_EQUAL (count, 0);
+
+       barrier.lower();
+       dcpomatic_sleep_seconds(5);
+
+       BOOST_CHECK_EQUAL (count, num_threads / 2);
+
+       for (auto i: threads) {
+               i->join();
+               delete i;
+       }
+}
+
diff --git a/test/fastvideo_test.cc b/test/fastvideo_test.cc
new file mode 100644 (file)
index 0000000..23369de
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+    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/barrier.h"
+#include "lib/colour_conversion.h"
+#include "lib/dcp_video.h"
+#include "lib/ffmpeg_image_proxy.h"
+#include "lib/player_video.h"
+#include "lib/j2k_encoder_fastvideo_backend.h"
+#include "test.h"
+#include <boost/test/unit_test.hpp>
+
+
+BOOST_AUTO_TEST_CASE (fastvideo_frame_size_test)
+{
+       auto test = [](int j2k_bandwidth) {
+               Barrier barrier (1);
+               J2KEncoderFastvideoBackend backend (barrier);
+
+               auto image_proxy = std::make_shared<FFmpegImageProxy>(TestPaths::private_data() / "prophet_frame.tiff");
+               auto player_video = std::make_shared<PlayerVideo> (
+                       image_proxy,
+                       Crop(),
+                       boost::optional<double>(),
+                       dcp::Size(1998, 1080),
+                       dcp::Size(1998, 1080),
+                       Eyes::BOTH,
+                       Part::WHOLE,
+                       PresetColourConversion::from_id("rec709").conversion,
+                       VideoRange::FULL,
+                       std::weak_ptr<Content>(),
+                       boost::optional<Frame>(),
+                       false
+                       );
+
+               DCPVideo dcp_video (
+                       player_video,
+                       0,
+                       24,
+                       j2k_bandwidth,
+                       Resolution::TWO_K
+                       );
+
+               auto result = backend.encode ({dcp_video});
+               return result[0].size() * 8L * 24 / 1000000.0f;
+       };
+
+       auto bandwidth = std::vector<int>{ 50, 100, 150, 200, 250 };
+
+       for (auto b: bandwidth) {
+               auto mbps = test(b * 1000000.0f);
+               BOOST_CHECK_CLOSE (mbps, b, 5);
+       }
+}
+
index ff6895d9a935036ccd7ce8d6ddbbfdbc5b9a26e5..d039e083e1e3360c2a27fcb1461598ed63f1255c 100644 (file)
@@ -40,6 +40,8 @@ def build(bld):
         obj.uselib += 'WINSOCK2 DBGHELP SHLWAPI MSWSOCK BOOST_LOCALE '
     if bld.env.TARGET_LINUX:
         obj.uselib += 'DL '
+    if bld.env.ENABLE_FASTVIDEO:
+        obj.uselib += ' FASTVIDEO'
     obj.use    = 'libdcpomatic2'
     obj.source = """
                  4k_test.cc
@@ -53,6 +55,7 @@ def build(bld):
                  audio_processor_test.cc
                  audio_processor_delay_test.cc
                  audio_ring_buffers_test.cc
+                 barrier_test.cc
                  butler_test.cc
                  cinema_sound_processor_test.cc
                  client_server_test.cc
@@ -152,6 +155,9 @@ def build(bld):
         obj.source += " disk_writer_test.cc"
         obj.uselib += "LWEXT4 NANOMSG "
 
+    if bld.env.ENABLE_FASTVIDEO:
+        obj.source += " fastvideo_test.cc"
+
     # Some difference in font rendering between the test machine and others...
     # burnt_subtitle_test.cc
     # This one doesn't check anything
diff --git a/wscript b/wscript
index 5c729bd4326c0428471a3a7f7d0038c98524bf28..de116f158988f9432d8726a6dd33a167c482a12f 100644 (file)
--- a/wscript
+++ b/wscript
@@ -77,6 +77,7 @@ def options(opt):
     opt.add_option('--enable-disk',       action='store_true', default=False, help='build dcpomatic2_disk tool; requires Boost process, lwext4 and nanomsg libraries')
     opt.add_option('--warnings-are-errors', action='store_true', default=False, help='build with -Werror')
     opt.add_option('--wx-config',         help='path to wx-config')
+    opt.add_option('--fastvideo-sdk',     help='path to fastvideo SDK (containing fastvideo_sdk directory)')
 
 def configure(conf):
     conf.load('compiler_cxx')
@@ -94,6 +95,7 @@ def configure(conf):
     conf.env.DEBUG = conf.options.enable_debug
     conf.env.STATIC_DCPOMATIC = conf.options.static_dcpomatic
     conf.env.ENABLE_DISK = conf.options.enable_disk
+    conf.env.ENABLE_FASTVIDEO = conf.options.fastvideo_sdk is not None
     if conf.options.destdir == '':
         conf.env.INSTALL_PREFIX = conf.options.prefix
     else:
@@ -542,6 +544,17 @@ def configure(conf):
                            lib=deps,
                            uselib_store='BOOST_PROCESS')
 
+    # fastvideo
+    if conf.options.fastvideo_sdk is not None:
+        conf.env.INCLUDES_FASTVIDEO = [
+            os.path.join(conf.options.fastvideo_sdk),
+            os.path.join(conf.options.fastvideo_sdk, "core_samples"),
+            os.path.join(conf.options.fastvideo_sdk, "fastvideo_sdk", "inc")]
+        conf.env.LIBPATH_FASTVIDEO = [ os.path.join(conf.options.fastvideo_sdk, "fastvideo_sdk", "lib") ]
+        conf.env.LIB_FASTVIDEO = [ 'fastvideo_sdk', 'fastvideo_decoder_j2k', 'fastvideo_encoder_j2k', 'cuda', 'cudart', 'omp5' ]
+        conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_FASTVIDEO')
+
+
     # Other stuff
 
     conf.find_program('msgfmt', var='MSGFMT')