diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib/cpu_j2k_frame_encoder.cc | 77 | ||||
| -rw-r--r-- | src/lib/dcp_video.cc | 171 | ||||
| -rw-r--r-- | src/lib/dcp_video.h | 20 | ||||
| -rw-r--r-- | src/lib/encode_server.cc | 40 | ||||
| -rw-r--r-- | src/lib/j2k_encoder.cc | 17 | ||||
| -rw-r--r-- | src/lib/remote_j2k_frame_encoder.cc | 62 | ||||
| -rw-r--r-- | src/lib/remote_j2k_frame_encoder.h | 4 | ||||
| -rw-r--r-- | src/tools/server_test.cc | 22 |
8 files changed, 191 insertions, 222 deletions
diff --git a/src/lib/cpu_j2k_frame_encoder.cc b/src/lib/cpu_j2k_frame_encoder.cc index dcfbe9dcf..5970f89c5 100644 --- a/src/lib/cpu_j2k_frame_encoder.cc +++ b/src/lib/cpu_j2k_frame_encoder.cc @@ -19,16 +19,25 @@ */ +#include "colour_conversion.h" +#include "config.h" #include "cpu_j2k_frame_encoder.h" #include "cross.h" #include "dcp_video.h" +#include "dcpomatic_assert.h" #include "dcpomatic_log.h" +#include "rng.h" +#include <dcp/openjpeg_image.h> +#include <dcp/j2k_transcode.h> #include "i18n.h" using std::shared_ptr; using boost::optional; +#if BOOST_VERSION >= 106100 +using namespace boost::placeholders; +#endif using dcp::ArrayData; @@ -39,7 +48,73 @@ CPUJ2KFrameEncoder::encode (DCPVideo const& vf) try { LOG_TIMING("start-local-encode thread=%1 frame=%2", thread_id(), vf.index()); - encoded = vf.encode_locally(); + auto const comment = Config::instance()->dcp_j2k_comment(); + + /* This was empirically derived by a user: see #1902 */ + int const minimum_size = 16384; + LOG_GENERAL ("Using minimum frame size %1", minimum_size); + + auto xyz = convert_to_xyz (vf.frame(), boost::bind(&Log::dcp_log, dcpomatic_log.get(), _1, _2)); + int noise_amount = 2; + int pixel_skip = 16; + while (true) { + encoded = dcp::compress_j2k ( + xyz, + vf.j2k_bandwidth(), + vf.frames_per_second(), + vf.eyes() == Eyes::LEFT || vf.eyes() == Eyes::RIGHT, + vf.resolution() == Resolution::FOUR_K, + comment.empty() ? "libdcp" : comment + ); + + if (encoded->size() >= minimum_size) { + LOG_GENERAL (N_("Frame %1 encoded size was OK (%2)"), vf.index(), encoded->size()); + break; + } + + LOG_GENERAL (N_("Frame %1 encoded size was small (%2); adding noise at level %3 with pixel skip %4"), vf.index(), encoded->size(), noise_amount, pixel_skip); + + /* 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 (vf.frame(), boost::bind(&Log::dcp_log, dcpomatic_log.get(), _1, _2)); + auto size = xyz->size (); + auto pixels = size.width * size.height; + dcpomatic::RNG rng(42); + for (auto c = 0; c < 3; ++c) { + auto p = xyz->data(c); + auto e = xyz->data(c) + pixels; + while (p < e) { + *p = std::min(4095, std::max(0, *p + (rng.get() % noise_amount))); + p += pixel_skip; + } + } + + if (pixel_skip > 1) { + --pixel_skip; + } else { + ++noise_amount; + } + /* Something's gone badly wrong if this much noise doesn't help */ + DCPOMATIC_ASSERT (noise_amount < 16); + } + + switch (vf.eyes()) { + case Eyes::BOTH: + LOG_DEBUG_ENCODE (N_("Finished locally-encoded frame %1 for mono"), vf.index()); + break; + case Eyes::LEFT: + LOG_DEBUG_ENCODE (N_("Finished locally-encoded frame %1 for L"), vf.index()); + break; + case Eyes::RIGHT: + LOG_DEBUG_ENCODE (N_("Finished locally-encoded frame %1 for R"), vf.index()); + break; + default: + break; + } + LOG_TIMING("finish-local-encode thread=%1 frame=%2", thread_id(), vf.index()); } catch (std::exception& e) { /* This is very bad, so don't cope with it, just pass it on */ diff --git a/src/lib/dcp_video.cc b/src/lib/dcp_video.cc index 2cecee95b..555362665 100644 --- a/src/lib/dcp_video.cc +++ b/src/lib/dcp_video.cc @@ -30,33 +30,10 @@ */ -#include "colour_conversion.h" -#include "compose.hpp" -#include "config.h" #include "cross.h" #include "dcp_video.h" -#include "dcpomatic_log.h" -#include "dcpomatic_socket.h" -#include "encode_server_description.h" -#include "exceptions.h" -#include "image.h" -#include "log.h" #include "player_video.h" -#include "rng.h" #include <libcxml/cxml.h> -#include <dcp/raw_convert.h> -#include <dcp/openjpeg_image.h> -#include <dcp/rgb_xyz.h> -#include <dcp/j2k_transcode.h> -#include <dcp/warnings.h> -LIBDCP_DISABLE_WARNINGS -#include <libxml++/libxml++.h> -LIBDCP_ENABLE_WARNINGS -#include <boost/asio.hpp> -#include <boost/thread.hpp> -#include <stdint.h> -#include <iomanip> -#include <iostream> #include "i18n.h" @@ -65,11 +42,6 @@ using std::cout; using std::make_shared; using std::shared_ptr; using std::string; -using dcp::ArrayData; -using dcp::raw_convert; -#if BOOST_VERSION >= 106100 -using namespace boost::placeholders; -#endif #define DCI_COEFFICENT (48.0 / 52.37) @@ -102,149 +74,6 @@ DCPVideo::DCPVideo (shared_ptr<const PlayerVideo> frame, shared_ptr<const cxml:: } -/** J2K-encode this frame on the local host. - * @return Encoded data. - */ -ArrayData -DCPVideo::encode_locally () const -{ - auto const comment = Config::instance()->dcp_j2k_comment(); - - ArrayData enc = {}; - /* This was empirically derived by a user: see #1902 */ - int const minimum_size = 16384; - LOG_GENERAL ("Using minimum frame size %1", minimum_size); - - auto xyz = convert_to_xyz (_frame, boost::bind(&Log::dcp_log, dcpomatic_log.get(), _1, _2)); - int noise_amount = 2; - int pixel_skip = 16; - 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) { - LOG_GENERAL (N_("Frame %1 encoded size was OK (%2)"), _index, enc.size()); - break; - } - - LOG_GENERAL (N_("Frame %1 encoded size was small (%2); adding noise at level %3 with pixel skip %4"), _index, enc.size(), noise_amount, pixel_skip); - - /* 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; - dcpomatic::RNG rng(42); - for (auto c = 0; c < 3; ++c) { - auto p = xyz->data(c); - auto e = xyz->data(c) + pixels; - while (p < e) { - *p = std::min(4095, std::max(0, *p + (rng.get() % noise_amount))); - p += pixel_skip; - } - } - - if (pixel_skip > 1) { - --pixel_skip; - } else { - ++noise_amount; - } - /* Something's gone badly wrong if this much noise doesn't help */ - DCPOMATIC_ASSERT (noise_amount < 16); - } - - switch (_frame->eyes()) { - case Eyes::BOTH: - LOG_DEBUG_ENCODE (N_("Finished locally-encoded frame %1 for mono"), _index); - break; - case Eyes::LEFT: - LOG_DEBUG_ENCODE (N_("Finished locally-encoded frame %1 for L"), _index); - break; - case Eyes::RIGHT: - LOG_DEBUG_ENCODE (N_("Finished locally-encoded frame %1 for R"), _index); - break; - default: - break; - } - - return enc; -} - -/** Send this frame to a remote server for J2K encoding, then read the result. - * @param serv Server to send to. - * @param timeout timeout in seconds. - * @return Encoded data. - */ -ArrayData -DCPVideo::encode_remotely (EncodeServerDescription serv, int timeout) const -{ - boost::asio::io_service io_service; - boost::asio::ip::tcp::resolver resolver (io_service); - boost::asio::ip::tcp::resolver::query query (serv.host_name(), raw_convert<string> (ENCODE_FRAME_PORT)); - boost::asio::ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve (query); - - auto socket = make_shared<Socket>(timeout); - - socket->connect (*endpoint_iterator); - - /* Collect all XML metadata */ - xmlpp::Document doc; - auto root = doc.create_root_node ("EncodingRequest"); - root->add_child("Version")->add_child_text (raw_convert<string> (SERVER_LINK_VERSION)); - add_metadata (root); - - LOG_DEBUG_ENCODE (N_("Sending frame %1 to remote"), _index); - - { - Socket::WriteDigestScope ds (socket); - - /* Send XML metadata */ - auto xml = doc.write_to_string ("UTF-8"); - socket->write (xml.length() + 1); - socket->write ((uint8_t *) xml.c_str(), xml.bytes() + 1); - - /* Send binary data */ - LOG_TIMING("start-remote-send thread=%1", thread_id ()); - _frame->write_to_socket (socket); - } - - /* Read the response (JPEG2000-encoded data); this blocks until the data - is ready and sent back. - */ - Socket::ReadDigestScope ds (socket); - LOG_TIMING("start-remote-encode thread=%1", thread_id ()); - ArrayData e (socket->read_uint32 ()); - LOG_TIMING("start-remote-receive thread=%1", thread_id ()); - socket->read (e.data(), e.size()); - LOG_TIMING("finish-remote-receive thread=%1", thread_id ()); - if (!ds.check()) { - throw NetworkError ("Checksums do not match"); - } - - LOG_DEBUG_ENCODE (N_("Finished remotely-encoded frame %1"), _index); - - return e; -} - -void -DCPVideo::add_metadata (xmlpp::Element* el) const -{ - el->add_child("Index")->add_child_text (raw_convert<string> (_index)); - el->add_child("FramesPerSecond")->add_child_text (raw_convert<string> (_frames_per_second)); - el->add_child("J2KBandwidth")->add_child_text (raw_convert<string> (_j2k_bandwidth)); - el->add_child("Resolution")->add_child_text (raw_convert<string> (int (_resolution))); - _frame->add_metadata (el); -} - Eyes DCPVideo::eyes () const { diff --git a/src/lib/dcp_video.h b/src/lib/dcp_video.h index 14440deb9..75aa2e117 100644 --- a/src/lib/dcp_video.h +++ b/src/lib/dcp_video.h @@ -48,21 +48,31 @@ public: DCPVideo (DCPVideo const&) = default; DCPVideo& operator= (DCPVideo const&) = default; - dcp::ArrayData encode_locally () const; - dcp::ArrayData encode_remotely (EncodeServerDescription, int timeout = 30) const; + std::shared_ptr<const PlayerVideo> frame() const { + return _frame; + } int index () const { return _index; } + int frames_per_second() const { + return _frames_per_second; + } + + int j2k_bandwidth() const { + return _j2k_bandwidth; + } + + Resolution resolution() const { + return _resolution; + } + Eyes eyes () const; bool same (std::shared_ptr<const DCPVideo> other) const; private: - - void add_metadata (xmlpp::Element *) const; - std::shared_ptr<const PlayerVideo> _frame; int _index; ///< frame index within the DCP's intrinsic duration int _frames_per_second; ///< Frames per second that we will use for the DCP diff --git a/src/lib/encode_server.cc b/src/lib/encode_server.cc index d326c767b..47c28654a 100644 --- a/src/lib/encode_server.cc +++ b/src/lib/encode_server.cc @@ -25,18 +25,19 @@ */ -#include "encode_server.h" -#include "util.h" -#include "dcpomatic_socket.h" -#include "image.h" -#include "dcp_video.h" +#include "compose.hpp" #include "config.h" +#include "cpu_j2k_frame_encoder.h" #include "cross.h" -#include "player_video.h" -#include "compose.hpp" -#include "log.h" +#include "dcp_video.h" #include "dcpomatic_log.h" +#include "dcpomatic_socket.h" +#include "encode_server.h" #include "encoded_log_entry.h" +#include "image.h" +#include "log.h" +#include "player_video.h" +#include "util.h" #include "version.h" #include <dcp/raw_convert.h> #include <dcp/warnings.h> @@ -49,25 +50,25 @@ LIBDCP_ENABLE_WARNINGS #ifdef HAVE_VALGRIND_H #include <valgrind/memcheck.h> #endif +#include <iostream> #include <string> #include <vector> -#include <iostream> #include "i18n.h" -using std::string; -using std::vector; -using std::list; -using std::cout; using std::cerr; +using std::cout; using std::fixed; -using std::shared_ptr; +using std::list; using std::make_shared; -using boost::thread; +using std::shared_ptr; +using std::string; +using std::vector; using boost::bind; -using boost::scoped_array; using boost::optional; +using boost::scoped_array; +using boost::thread; using dcp::ArrayData; using dcp::Size; using dcp::raw_convert; @@ -151,14 +152,15 @@ EncodeServer::process (shared_ptr<Socket> socket, struct timeval& after_read, st gettimeofday (&after_read, 0); - auto encoded = dcp_video_frame.encode_locally (); + CPUJ2KFrameEncoder cpu; + auto encoded = cpu.encode(dcp_video_frame); gettimeofday (&after_encode, 0); try { Socket::WriteDigestScope ds (socket); - socket->write (encoded.size()); - socket->write (encoded.data(), encoded.size()); + socket->write (encoded->size()); + socket->write (encoded->data(), encoded->size()); } catch (std::exception& e) { cerr << "Send failed; frame " << dcp_video_frame.index() << "\n"; LOG_ERROR ("Send failed; frame %1", dcp_video_frame.index()); diff --git a/src/lib/j2k_encoder.cc b/src/lib/j2k_encoder.cc index 47d8b1295..f9969927f 100644 --- a/src/lib/j2k_encoder.cc +++ b/src/lib/j2k_encoder.cc @@ -137,14 +137,11 @@ J2KEncoder::end () So just mop up anything left in the queue here. */ + CPUJ2KFrameEncoder cpu; for (auto const& i: _queue) { LOG_GENERAL(N_("Encode left-over frame %1"), i.index()); try { - _writer->write ( - make_shared<dcp::ArrayData>(i.encode_locally()), - i.index(), - i.eyes() - ); + _writer->write(make_shared<dcp::ArrayData>(*cpu.encode(i)), i.index(), i.eyes()); frame_done (); } catch (std::exception& e) { LOG_ERROR (N_("Local encode failed (%1)"), e.what ()); @@ -290,12 +287,6 @@ try return; } - /* Number of seconds that we currently wait between attempts - to connect to the server; not relevant for localhost - encodings. - */ - int remote_backoff = 0; - while (true) { LOG_TIMING ("encoder-sleep thread=%1", thread_id ()); @@ -332,10 +323,6 @@ try } } - if (remote_backoff > 0) { - boost::this_thread::sleep (boost::posix_time::seconds (remote_backoff)); - } - /* The queue might not be full any more, so notify anything that is waiting on that */ lock.lock (); _full_condition.notify_all (); diff --git a/src/lib/remote_j2k_frame_encoder.cc b/src/lib/remote_j2k_frame_encoder.cc index 3917cc07b..adaef495e 100644 --- a/src/lib/remote_j2k_frame_encoder.cc +++ b/src/lib/remote_j2k_frame_encoder.cc @@ -19,17 +19,30 @@ */ +#include "config.h" #include "cross.h" #include "dcp_video.h" #include "dcpomatic_log.h" +#include "dcpomatic_socket.h" +#include "exceptions.h" +#include "player_video.h" #include "remote_j2k_frame_encoder.h" +#include <dcp/raw_convert.h> +#include <dcp/warnings.h> +LIBDCP_DISABLE_WARNINGS +#include <libxml++/libxml++.h> +LIBDCP_ENABLE_WARNINGS +#include <boost/asio.hpp> +#include <boost/thread.hpp> #include "i18n.h" using std::make_shared; using std::shared_ptr; +using std::string; using boost::optional; +using dcp::ArrayData; using dcp::Data; @@ -39,7 +52,54 @@ RemoteJ2KFrameEncoder::encode(DCPVideo const& vf) optional<dcp::ArrayData> encoded; try { - encoded = vf.encode_remotely(_server); + boost::asio::io_service io_service; + boost::asio::ip::tcp::resolver resolver (io_service); + boost::asio::ip::tcp::resolver::query query (_server.host_name(), dcp::raw_convert<string>(ENCODE_FRAME_PORT)); + auto endpoint_iterator = resolver.resolve (query); + + auto socket = make_shared<Socket>(_timeout); + + socket->connect (*endpoint_iterator); + + /* Collect all XML metadata */ + xmlpp::Document doc; + auto root = doc.create_root_node ("EncodingRequest"); + root->add_child("Version")->add_child_text(dcp::raw_convert<string>(SERVER_LINK_VERSION)); + root->add_child("Index")->add_child_text(dcp::raw_convert<string>(vf.index())); + root->add_child("FramesPerSecond")->add_child_text(dcp::raw_convert<string>(vf.frames_per_second())); + root->add_child("J2KBandwidth")->add_child_text(dcp::raw_convert<string>(vf.j2k_bandwidth())); + root->add_child("Resolution")->add_child_text(dcp::raw_convert<string>(int(vf.resolution()))); + vf.frame()->add_metadata(root); + + LOG_DEBUG_ENCODE(N_("Sending frame %1 to remote"), vf.index()); + + { + Socket::WriteDigestScope ds (socket); + + /* Send XML metadata */ + auto xml = doc.write_to_string ("UTF-8"); + socket->write (xml.length() + 1); + socket->write (reinterpret_cast<uint8_t const *>(xml.c_str()), xml.bytes() + 1); + + /* Send binary data */ + LOG_TIMING("start-remote-send thread=%1", thread_id()); + vf.frame()->write_to_socket(socket); + } + + /* Read the response (JPEG2000-encoded data); this blocks until the data + is ready and sent back. + */ + Socket::ReadDigestScope ds (socket); + LOG_TIMING("start-remote-encode thread=%1", thread_id()); + encoded = ArrayData(socket->read_uint32()); + LOG_TIMING("start-remote-receive thread=%1", thread_id()); + socket->read (encoded->data(), encoded->size()); + LOG_TIMING("finish-remote-receive thread=%1", thread_id()); + if (!ds.check()) { + throw NetworkError ("Checksums do not match"); + } + + LOG_DEBUG_ENCODE (N_("Finished remotely-encoded frame %1"), vf.index()); if (_remote_backoff > 0) { LOG_GENERAL ("%1 was lost, but now she is found; removing backoff", _server.host_name()); diff --git a/src/lib/remote_j2k_frame_encoder.h b/src/lib/remote_j2k_frame_encoder.h index e532782b2..4a8fd0ae6 100644 --- a/src/lib/remote_j2k_frame_encoder.h +++ b/src/lib/remote_j2k_frame_encoder.h @@ -29,8 +29,9 @@ class RemoteJ2KFrameEncoder : public J2KFrameEncoder { public: - RemoteJ2KFrameEncoder(EncodeServerDescription s) + RemoteJ2KFrameEncoder(EncodeServerDescription s, int timeout = 30) : _server(s) + , _timeout(timeout) {} boost::optional<dcp::ArrayData> encode (DCPVideo const &) override; @@ -38,6 +39,7 @@ public: private: EncodeServerDescription _server; + int _timeout; int _remote_backoff = 0; }; diff --git a/src/tools/server_test.cc b/src/tools/server_test.cc index ff3295599..9831605e0 100644 --- a/src/tools/server_test.cc +++ b/src/tools/server_test.cc @@ -19,6 +19,7 @@ */ +#include "lib/cpu_j2k_frame_encoder.h" #include "lib/dcp_video.h" #include "lib/decoder.h" #include "lib/encode_server.h" @@ -30,6 +31,7 @@ #include "lib/player.h" #include "lib/player_video.h" #include "lib/ratio.h" +#include "lib/remote_j2k_frame_encoder.h" #include "lib/util.h" #include "lib/video_decoder.h" #include <getopt.h> @@ -60,20 +62,22 @@ static int frame_count = 0; void process_video (shared_ptr<PlayerVideo> pvf) { - auto local = make_shared<DCPVideo>(pvf, frame_count, film->video_frame_rate(), 250000000, Resolution::TWO_K); - auto remote = make_shared<DCPVideo>(pvf, frame_count, film->video_frame_rate(), 250000000, Resolution::TWO_K); + auto local = DCPVideo(pvf, frame_count, film->video_frame_rate(), 250000000, Resolution::TWO_K); + auto remote = DCPVideo(pvf, frame_count, film->video_frame_rate(), 250000000, Resolution::TWO_K); cout << "Frame " << frame_count << ": "; cout.flush (); ++frame_count; - auto local_encoded = local->encode_locally (); - ArrayData remote_encoded; + CPUJ2KFrameEncoder cpu; + auto local_encoded = cpu.encode(local); + optional<dcp::ArrayData> remote_encoded; string remote_error; try { - remote_encoded = remote->encode_remotely (*server); + RemoteJ2KFrameEncoder encoder(*server); + remote_encoded = encoder.encode(remote); } catch (NetworkError& e) { remote_error = e.what (); } @@ -83,14 +87,14 @@ process_video (shared_ptr<PlayerVideo> pvf) return; } - if (local_encoded.size() != remote_encoded.size()) { + if (local_encoded->size() != remote_encoded->size()) { cout << "\033[0;31msizes differ\033[0m\n"; return; } - auto p = local_encoded.data(); - auto q = remote_encoded.data(); - for (int i = 0; i < local_encoded.size(); ++i) { + auto p = local_encoded->data(); + auto q = remote_encoded->data(); + for (int i = 0; i < local_encoded->size(); ++i) { if (*p++ != *q++) { cout << "\033[0;31mdata differ\033[0m at byte " << i << "\n"; return; |
