X-Git-Url: https://main.carlh.net/gitweb/?p=dcpomatic.git;a=blobdiff_plain;f=src%2Flib%2Fdcp_video.cc;h=6f32b6686dca1ac44eeeb4d8d7a575f4c1e004fe;hp=8ea5cec884a6db4de833e1bde8c1544679615f6b;hb=ad1ef39eda58b3a919ea3b7084401a0439409ec6;hpb=1dcfb3a26085ebb3703f40e2f51e43ce3d98be50 diff --git a/src/lib/dcp_video.cc b/src/lib/dcp_video.cc index 8ea5cec88..6f32b6686 100644 --- a/src/lib/dcp_video.cc +++ b/src/lib/dcp_video.cc @@ -1,79 +1,220 @@ /* - 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 "dcpomatic_log.h" +#include "cross.h" +#include "player_video.h" +#include "compose.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "i18n.h" +using std::string; +using std::cout; using boost::shared_ptr; +using dcp::Size; +using dcp::Data; +using dcp::raw_convert; -/** 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 ()) */ 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 ) - : _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) { } -void -DCPVideo::set_subtitle (PositionImage s) +DCPVideo::DCPVideo (shared_ptr frame, shared_ptr node) + : _frame (frame) { - _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 (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.reset (new dcp::OpenJPEGImage (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 () +{ + Data enc = compress_j2k ( + convert_to_xyz (_frame, boost::bind(&Log::dcp_log, dcpomatic_log.get(), _1, _2)), + _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. + * @param timeout timeout in seconds. + * @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 (ENCODE_FRAME_PORT)); + 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", thread_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", thread_id ()); + Data e (socket->read_uint32 ()); + LOG_TIMING("start-remote-receive thread=%1", thread_id ()); + socket->read (e.data().get(), e.size()); + LOG_TIMING("finish-remote-receive thread=%1", thread_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); +}