X-Git-Url: https://main.carlh.net/gitweb/?p=dcpomatic.git;a=blobdiff_plain;f=src%2Flib%2Fdcp_video.cc;h=be69f3eedc1255740f221379b53c229a8833688b;hp=8ea5cec884a6db4de833e1bde8c1544679615f6b;hb=a8a0dfd1b21de6c0facf965ab119833ff6f790bf;hpb=b666a794a130386bc01ede2143ef40bd6973eb32 diff --git a/src/lib/dcp_video.cc b/src/lib/dcp_video.cc index 8ea5cec88..be69f3eed 100644 --- a/src/lib/dcp_video.cc +++ b/src/lib/dcp_video.cc @@ -1,79 +1,225 @@ /* - Copyright (C) 2013-2014 Carl Hetherington + Copyright (C) 2012-2016 Carl Hetherington - This program is free software; you can redistribute it and/or modify + 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. - This program is distributed in the hope that it will be useful, + 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 this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + along with DCP-o-matic. If not, see . */ +/** @file src/dcp_video_frame.cc + * @brief A single frame of video destined for a DCP. + * + * Given an Image and some settings, this class knows how to encode + * the image to J2K either on the local host or on a remote server. + * + * Objects of this class are used for the queue that we keep + * of images that require encoding. + */ + #include "dcp_video.h" +#include "config.h" +#include "exceptions.h" +#include "encode_server_description.h" +#include "dcpomatic_socket.h" #include "image.h" +#include "log.h" +#include "cross.h" +#include "player_video.h" +#include "raw_convert.h" +#include "compose.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOG_GENERAL(...) _log->log (String::compose (__VA_ARGS__), LogEntry::TYPE_GENERAL); +#define LOG_DEBUG_ENCODE(...) _log->log (String::compose (__VA_ARGS__), LogEntry::TYPE_DEBUG_ENCODE); +#define LOG_TIMING(...) _log->log (String::compose (__VA_ARGS__), LogEntry::TYPE_TIMING); +#include "i18n.h" + +using std::string; +using std::cout; using boost::shared_ptr; +using dcp::Size; +using dcp::Data; -/** From ContentVideo: - * @param in Image. - * @param eyes Eye(s) that the Image is for. - * - * From Content: - * @param crop Crop to apply. - * @param inter_size - * @param out_size - * @param scaler Scaler to use. - * @param conversion Colour conversion to use. - * - * @param time DCP time. +#define DCI_COEFFICENT (48.0 / 52.37) + +/** Construct a DCP video frame. + * @param frame Input frame. + * @param index Index of the frame within the DCP. + * @param bw J2K bandwidth to use (see Config::j2k_bandwidth ()) + * @param l Log to write to. */ DCPVideo::DCPVideo ( - shared_ptr in, - Eyes eyes, - Crop crop, - dcp::Size inter_size, - dcp::Size out_size, - Scaler const * scaler, - ColourConversion conversion, - DCPTime time + shared_ptr frame, int index, int dcp_fps, int bw, Resolution r, shared_ptr l ) - : _in (in) - , _eyes (eyes) - , _crop (crop) - , _inter_size (inter_size) - , _out_size (out_size) - , _scaler (scaler) - , _conversion (conversion) - , _time (time) + : _frame (frame) + , _index (index) + , _frames_per_second (dcp_fps) + , _j2k_bandwidth (bw) + , _resolution (r) + , _log (l) { } -void -DCPVideo::set_subtitle (PositionImage s) +DCPVideo::DCPVideo (shared_ptr frame, shared_ptr node, shared_ptr log) + : _frame (frame) + , _log (log) { - _subtitle = s; + _index = node->number_child ("Index"); + _frames_per_second = node->number_child ("FramesPerSecond"); + _j2k_bandwidth = node->number_child ("J2KBandwidth"); + _resolution = Resolution (node->optional_number_child("Resolution").get_value_or (RESOLUTION_2K)); } -shared_ptr -DCPVideo::image (AVPixelFormat format, bool aligned) const +shared_ptr +DCPVideo::convert_to_xyz (shared_ptr frame, dcp::NoteHandler note) { - shared_ptr out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, format, aligned); - - Position const container_offset ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.width) / 2); + shared_ptr xyz; - if (_subtitle.image) { - out->alpha_blend (_subtitle.image, _subtitle.position); + shared_ptr image = frame->image (note, bind (&PlayerVideo::keep_xyz_or_rgb, _1), true, false); + if (frame->colour_conversion()) { + xyz = dcp::rgb_to_xyz ( + image->data()[0], + image->size(), + image->stride()[0], + frame->colour_conversion().get(), + note + ); + } else { + xyz = dcp::xyz_to_xyz (image->data()[0], image->size(), image->stride()[0]); } - return out; + return xyz; } +/** J2K-encode this frame on the local host. + * @return Encoded data. + */ +Data +DCPVideo::encode_locally (dcp::NoteHandler note) +{ + Data enc = compress_j2k ( + convert_to_xyz (_frame, note), + _j2k_bandwidth, + _frames_per_second, + _frame->eyes() == EYES_LEFT || _frame->eyes() == EYES_RIGHT, + _resolution == RESOLUTION_4K + ); + + 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. + * @return Encoded data. + */ +Data +DCPVideo::encode_remotely (EncodeServerDescription serv, int timeout) +{ + 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 (Config::instance()->server_port_base ())); + boost::asio::ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve (query); + + shared_ptr socket (new Socket (timeout)); + + socket->connect (*endpoint_iterator); + + /* Collect all XML metadata */ + xmlpp::Document doc; + xmlpp::Element* root = doc.create_root_node ("EncodingRequest"); + root->add_child("Version")->add_child_text (raw_convert (SERVER_LINK_VERSION)); + add_metadata (root); + + LOG_DEBUG_ENCODE (N_("Sending frame %1 to remote"), _index); + + /* Send XML metadata */ + string xml = doc.write_to_string ("UTF-8"); + socket->write (xml.length() + 1); + socket->write ((uint8_t *) xml.c_str(), xml.length() + 1); + + /* Send binary data */ + LOG_TIMING("start-remote-send thread=%1", boost::this_thread::get_id()); + _frame->send_binary (socket); + + /* Read the response (JPEG2000-encoded data); this blocks until the data + is ready and sent back. + */ + LOG_TIMING("start-remote-encode thread=%1", boost::this_thread::get_id ()); + Data e (socket->read_uint32 ()); + LOG_TIMING("start-remote-receive thread=%1", boost::this_thread::get_id ()); + socket->read (e.data().get(), e.size()); + LOG_TIMING("finish-remote-receive thread=%1", boost::this_thread::get_id ()); + + 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 (_index)); + el->add_child("FramesPerSecond")->add_child_text (raw_convert (_frames_per_second)); + el->add_child("J2KBandwidth")->add_child_text (raw_convert (_j2k_bandwidth)); + el->add_child("Resolution")->add_child_text (raw_convert (int (_resolution))); + _frame->add_metadata (el); +} + +Eyes +DCPVideo::eyes () const +{ + return _frame->eyes (); +} + +/** @return true if this DCPVideo is definitely the same as another; + * (apart from the frame index), false if it is probably not. + */ +bool +DCPVideo::same (shared_ptr other) const +{ + if (_frames_per_second != other->_frames_per_second || + _j2k_bandwidth != other->_j2k_bandwidth || + _resolution != other->_resolution) { + return false; + } + + return _frame->same (other->_frame); +}