From: Carl Hetherington Date: Tue, 20 May 2014 12:23:26 +0000 (+0100) Subject: Merge master. X-Git-Tag: v2.0.48~819 X-Git-Url: https://git.carlh.net/gitweb/?p=dcpomatic.git;a=commitdiff_plain;h=39bc73fe192f932ed6695eb87b19de446e8b4f55;hp=-c Merge master. --- 39bc73fe192f932ed6695eb87b19de446e8b4f55 diff --combined ChangeLog index 33b7e2e21,da42e7ba1..6f31dd5d2 --- a/ChangeLog +++ b/ChangeLog @@@ -1,7 -1,12 +1,16 @@@ +2014-03-07 Carl Hetherington + + * Add subtitle view. + + 2014-05-19 Carl Hetherington + + * Version 1.69.9 released. + + 2014-05-19 Carl Hetherington + + * Decode image sources in the multi-threaded part + of the transcoder, rather than the single-threaded. + 2014-05-16 Carl Hetherington * Version 1.69.8 released. diff --combined src/lib/colour_conversion.cc index 73ee72249,5f17f9184..48fd6ed9c --- a/src/lib/colour_conversion.cc +++ b/src/lib/colour_conversion.cc @@@ -18,8 -18,8 +18,8 @@@ */ #include -#include -#include +#include +#include #include #include "config.h" #include "colour_conversion.h" @@@ -29,11 -29,12 +29,12 @@@ using std::list; using std::string; + using std::stringstream; using std::cout; using std::vector; using boost::shared_ptr; using boost::optional; -using libdcp::raw_convert; +using dcp::raw_convert; ColourConversion::ColourConversion () : input_gamma (2.4) @@@ -43,7 -44,7 +44,7 @@@ { for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { - matrix (i, j) = libdcp::colour_matrix::srgb_to_xyz[i][j]; + matrix (i, j) = dcp::colour_matrix::srgb_to_xyz[i][j]; } } } diff --combined src/lib/content_video.h index 20b5b8dec,000000000..a7f73597c mode 100644,000000..100644 --- a/src/lib/content_video.h +++ b/src/lib/content_video.h @@@ -1,46 -1,0 +1,48 @@@ +/* + Copyright (C) 2013-2014 Carl Hetherington + + This program 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, + 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. + +*/ + +#ifndef DCPOMATIC_CONTENT_VIDEO_H +#define DCPOMATIC_CONTENT_VIDEO_H + - class Image; ++class ImageProxy; + +/** @class ContentVideo + * @brief A frame of video straight out of some content. + */ +class ContentVideo +{ +public: + ContentVideo () + : eyes (EYES_BOTH) + {} + - ContentVideo (boost::shared_ptr i, Eyes e, VideoFrame f) ++ ContentVideo (boost::shared_ptr i, Eyes e, Part p, VideoFrame f) + : image (i) + , eyes (e) ++ , part (p) + , frame (f) + {} + - boost::shared_ptr image; ++ boost::shared_ptr image; + Eyes eyes; ++ Part part; + VideoFrame frame; +}; + +#endif diff --combined src/lib/dcp_video_frame.cc index d860c3195,5cd6a118e..d154ba96b --- a/src/lib/dcp_video_frame.cc +++ b/src/lib/dcp_video_frame.cc @@@ -42,12 -42,14 +42,13 @@@ #include #include #include +#include + #include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include "film.h" #include "dcp_video_frame.h" @@@ -59,6 -61,7 +60,7 @@@ #include "image.h" #include "log.h" #include "cross.h" + #include "player_video_frame.h" #include "i18n.h" @@@ -66,25 -69,22 +68,23 @@@ using std::string using std::stringstream; using std::cout; using boost::shared_ptr; -using libdcp::Size; -using libdcp::raw_convert; +using boost::lexical_cast; +using dcp::Size; +using dcp::raw_convert; #define DCI_COEFFICENT (48.0 / 52.37) /** Construct a DCP video frame. - * @param input Input image. - * @param f Index of the frame within the DCP. + * @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. */ DCPVideoFrame::DCPVideoFrame ( - shared_ptr image, int f, Eyes eyes, ColourConversion c, int dcp_fps, int bw, Resolution r, shared_ptr l + shared_ptr frame, int index, int dcp_fps, int bw, Resolution r, shared_ptr l ) - : _image (image) - , _frame (f) - , _eyes (eyes) - , _conversion (c) + : _frame (frame) + , _index (index) , _frames_per_second (dcp_fps) , _j2k_bandwidth (bw) , _resolution (r) @@@ -93,22 -93,11 +93,11 @@@ } - DCPVideoFrame::DCPVideoFrame (shared_ptr image, cxml::ConstNodePtr node, shared_ptr log) - : _image (image) + DCPVideoFrame::DCPVideoFrame (shared_ptr frame, shared_ptr node, shared_ptr log) + : _frame (frame) , _log (log) { - _frame = node->number_child ("Frame"); - string const eyes = node->string_child ("Eyes"); - if (eyes == "Both") { - _eyes = EYES_BOTH; - } else if (eyes == "Left") { - _eyes = EYES_LEFT; - } else if (eyes == "Right") { - _eyes = EYES_RIGHT; - } else { - assert (false); - } - _conversion = ColourConversion (node->node_child ("ColourConversion")); + _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)); @@@ -120,28 -109,47 +109,44 @@@ shared_ptr DCPVideoFrame::encode_locally () { - shared_ptr in_lut; - in_lut = dcp::GammaLUT::cache.get (12, _conversion.input_gamma, _conversion.input_gamma_linearised); - shared_ptr in_lut; - if (_frame->colour_conversion().input_gamma_linearised) { - in_lut = libdcp::SRGBLinearisedGammaLUT::cache.get (12, _frame->colour_conversion().input_gamma); - } else { - in_lut = libdcp::GammaLUT::cache.get (12, _frame->colour_conversion().input_gamma); - } -- ++ shared_ptr in_lut = dcp::GammaLUT::cache.get ( ++ 12, _frame->colour_conversion().input_gamma, _frame->colour_conversion().input_gamma_linearised ++ ); ++ /* XXX: libdcp should probably use boost */ double matrix[3][3]; for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { - matrix[i][j] = _conversion.matrix (i, j); + matrix[i][j] = _frame->colour_conversion().matrix (i, j); } } - + - shared_ptr xyz = libdcp::rgb_to_xyz ( + shared_ptr xyz = dcp::rgb_to_xyz ( - _image, + _frame->image(), in_lut, - dcp::GammaLUT::cache.get (16, 1 / _conversion.output_gamma, false), - libdcp::GammaLUT::cache.get (16, 1 / _frame->colour_conversion().output_gamma), ++ dcp::GammaLUT::cache.get (16, 1 / _frame->colour_conversion().output_gamma, false), matrix ); + + { + MD5_CTX md5_context; + MD5_Init (&md5_context); + MD5_Update (&md5_context, xyz->data(0), 1998 * 1080 * 4); + MD5_Update (&md5_context, xyz->data(1), 1998 * 1080 * 4); + MD5_Update (&md5_context, xyz->data(2), 1998 * 1080 * 4); + unsigned char digest[MD5_DIGEST_LENGTH]; + MD5_Final (digest, &md5_context); + stringstream s; + for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) { + s << std::hex << std::setfill('0') << std::setw(2) << ((int) digest[i]); + } + } + /* Set the max image and component sizes based on frame_rate */ int max_cs_len = ((float) _j2k_bandwidth) / 8 / _frames_per_second; - if (_eyes == EYES_LEFT || _eyes == EYES_RIGHT) { + if (_frame->eyes() == EYES_LEFT || _frame->eyes() == EYES_RIGHT) { /* In 3D we have only half the normal bandwidth per eye */ max_cs_len /= 2; } @@@ -240,15 -248,15 +245,15 @@@ throw EncodeError (N_("JPEG2000 encoding failed")); } - switch (_eyes) { + switch (_frame->eyes()) { case EYES_BOTH: - _log->log (String::compose (N_("Finished locally-encoded frame %1 for mono"), _frame)); + _log->log (String::compose (N_("Finished locally-encoded frame %1 for mono"), _index)); break; case EYES_LEFT: - _log->log (String::compose (N_("Finished locally-encoded frame %1 for L"), _frame)); + _log->log (String::compose (N_("Finished locally-encoded frame %1 for L"), _index)); break; case EYES_RIGHT: - _log->log (String::compose (N_("Finished locally-encoded frame %1 for R"), _frame)); + _log->log (String::compose (N_("Finished locally-encoded frame %1 for R"), _index)); break; default: break; @@@ -279,28 -287,30 +284,30 @@@ DCPVideoFrame::encode_remotely (ServerD 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)); - root->add_child("Width")->add_child_text (raw_convert (_image->size().width)); - root->add_child("Height")->add_child_text (raw_convert (_image->size().height)); add_metadata (root); + _log->log (String::compose (N_("Sending frame %1 to remote"), _index)); + + /* Send XML metadata */ stringstream xml; doc.write_to_stream (xml, "UTF-8"); - - _log->log (String::compose (N_("Sending frame %1 to remote"), _frame)); - socket->write (xml.str().length() + 1); socket->write ((uint8_t *) xml.str().c_str(), xml.str().length() + 1); - _image->write_to_socket (socket); + /* Send binary data */ + _frame->send_binary (socket); + /* Read the response (JPEG2000-encoded data); this blocks until the data + is ready and sent back. + */ shared_ptr e (new RemotelyEncodedData (socket->read_uint32 ())); socket->read (e->data(), e->size()); - _log->log (String::compose (N_("Finished remotely-encoded frame %1"), _frame)); + _log->log (String::compose (N_("Finished remotely-encoded frame %1"), _index)); return e; } @@@ -308,27 -318,17 +315,17 @@@ void DCPVideoFrame::add_metadata (xmlpp::Element* el) const { - el->add_child("Frame")->add_child_text (raw_convert (_frame)); - - switch (_eyes) { - case EYES_BOTH: - el->add_child("Eyes")->add_child_text ("Both"); - break; - case EYES_LEFT: - el->add_child("Eyes")->add_child_text ("Left"); - break; - case EYES_RIGHT: - el->add_child("Eyes")->add_child_text ("Right"); - break; - default: - assert (false); - } - - _conversion.as_xml (el->add_child("ColourConversion")); - + 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 + DCPVideoFrame::eyes () const + { + return _frame->eyes (); } EncodedData::EncodedData (int s) @@@ -388,7 -388,7 +385,7 @@@ EncodedData::write (shared_ptr film, int frame, Eyes eyes, libdcp::FrameInfo fin) const +EncodedData::write_info (shared_ptr film, int frame, Eyes eyes, dcp::FrameInfo fin) const { boost::filesystem::path const info = film->info_path (frame, eyes); FILE* h = fopen_boost (info, "w"); diff --combined src/lib/dcp_video_frame.h index c51a3f02b,e4006d986..7393efde6 --- a/src/lib/dcp_video_frame.h +++ b/src/lib/dcp_video_frame.h @@@ -18,7 -18,9 +18,7 @@@ */ -#include -#include -#include +#include #include "util.h" /** @file src/dcp_video_frame.h @@@ -31,6 -33,7 +31,7 @@@ class Scaler class Image; class Log; class Subtitle; + class PlayerVideoFrame; /** @class EncodedData * @brief Container for J2K-encoded data. @@@ -47,7 -50,7 +48,7 @@@ public void send (boost::shared_ptr socket); void write (boost::shared_ptr, int, Eyes) const; - void write_info (boost::shared_ptr, int, Eyes, libdcp::FrameInfo) const; + void write_info (boost::shared_ptr, int, Eyes, dcp::FrameInfo) const; /** @return data */ uint8_t* data () const { @@@ -100,28 -103,24 +101,24 @@@ public class DCPVideoFrame : public boost::noncopyable { public: - DCPVideoFrame (boost::shared_ptr, int, Eyes, ColourConversion, int, int, Resolution, boost::shared_ptr); - DCPVideoFrame (boost::shared_ptr, cxml::ConstNodePtr, boost::shared_ptr); + DCPVideoFrame (boost::shared_ptr, int, int, int, Resolution, boost::shared_ptr); - DCPVideoFrame (boost::shared_ptr, boost::shared_ptr, boost::shared_ptr); ++ DCPVideoFrame (boost::shared_ptr, cxml::ConstNodePtr, boost::shared_ptr); boost::shared_ptr encode_locally (); boost::shared_ptr encode_remotely (ServerDescription); - Eyes eyes () const { - return _eyes; - } - - int frame () const { - return _frame; + int index () const { + return _index; } + + Eyes eyes () const; private: void add_metadata (xmlpp::Element *) const; - boost::shared_ptr _image; - int _frame; ///< frame index within the DCP's intrinsic duration - Eyes _eyes; - ColourConversion _conversion; + boost::shared_ptr _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 int _j2k_bandwidth; ///< J2K bandwidth to use Resolution _resolution; ///< Resolution (2K or 4K) diff --combined src/lib/encoder.cc index b83cbc10a,4fc2d7f81..2364b67a7 --- a/src/lib/encoder.cc +++ b/src/lib/encoder.cc @@@ -35,7 -35,7 +35,7 @@@ #include "writer.h" #include "server_finder.h" #include "player.h" - #include "dcp_video.h" + #include "player_video_frame.h" #include "i18n.h" @@@ -61,7 -61,9 +61,7 @@@ Encoder::Encoder (shared_ptr >::iterator i = _queue.begin(); i != _queue.end(); ++i) { - _film->log()->log (String::compose (N_("Encode left-over frame %1"), (*i)->frame ())); + _film->log()->log (String::compose (N_("Encode left-over frame %1"), (*i)->index ())); try { - _writer->write ((*i)->encode_locally(), (*i)->frame (), (*i)->eyes ()); + _writer->write ((*i)->encode_locally(), (*i)->index (), (*i)->eyes ()); frame_done (); } catch (std::exception& e) { _film->log()->log (String::compose (N_("Local encode failed (%1)"), e.what ())); @@@ -178,7 -180,7 +178,7 @@@ Encoder::frame_done ( } void - Encoder::process_video (shared_ptr frame) -Encoder::process_video (shared_ptr pvf, bool same) ++Encoder::process_video (shared_ptr pvf) { _waker.nudge (); @@@ -205,28 -207,28 +205,26 @@@ rethrow (); if (_writer->can_fake_write (_video_frames_out)) { - _writer->fake_write (_video_frames_out, frame->eyes ()); + _writer->fake_write (_video_frames_out, pvf->eyes ()); - _have_a_real_frame[pvf->eyes()] = false; - frame_done (); - } else if (same && _have_a_real_frame[pvf->eyes()]) { - /* Use the last frame that we encoded. */ - _writer->repeat (_video_frames_out, pvf->eyes()); frame_done (); } else { /* Queue this new frame for encoding */ TIMING ("adding to queue of %1", _queue.size ()); _queue.push_back (shared_ptr ( new DCPVideoFrame ( - frame->image(PIX_FMT_RGB24, false), - pvf, _video_frames_out, _film->video_frame_rate(), - _film->j2k_bandwidth(), _film->resolution(), _film->log() ++ pvf, + _video_frames_out, - frame->eyes(), - frame->conversion(), + _film->video_frame_rate(), + _film->j2k_bandwidth(), + _film->resolution(), + _film->log() ) )); _condition.notify_all (); - _have_a_real_frame[pvf->eyes()] = true; } - if (frame->eyes() != EYES_LEFT) { + if (pvf->eyes() != EYES_LEFT) { ++_video_frames_out; } } @@@ -280,7 -282,7 +278,7 @@@ tr TIMING ("encoder thread %1 wakes with queue of %2", boost::this_thread::get_id(), _queue.size()); shared_ptr vf = _queue.front (); - TIMING ("encoder thread %1 pops frame %2 (%3) from queue", boost::this_thread::get_id(), vf->frame(), vf->eyes ()); + TIMING ("encoder thread %1 pops frame %2 (%3) from queue", boost::this_thread::get_id(), vf->index(), vf->eyes ()); _queue.pop_front (); lock.unlock (); @@@ -306,27 -308,27 +304,27 @@@ _film->log()->log ( String::compose ( N_("Remote encode of %1 on %2 failed (%3); thread sleeping for %4s"), - vf->frame(), server->host_name(), e.what(), remote_backoff) + vf->index(), server->host_name(), e.what(), remote_backoff) ); } } else { try { - TIMING ("encoder thread %1 begins local encode of %2", boost::this_thread::get_id(), vf->frame()); + TIMING ("encoder thread %1 begins local encode of %2", boost::this_thread::get_id(), vf->index()); encoded = vf->encode_locally (); - TIMING ("encoder thread %1 finishes local encode of %2", boost::this_thread::get_id(), vf->frame()); + TIMING ("encoder thread %1 finishes local encode of %2", boost::this_thread::get_id(), vf->index()); } catch (std::exception& e) { _film->log()->log (String::compose (N_("Local encode failed (%1)"), e.what ())); } } if (encoded) { - _writer->write (encoded, vf->frame (), vf->eyes ()); + _writer->write (encoded, vf->index (), vf->eyes ()); frame_done (); } else { lock.lock (); _film->log()->log ( - String::compose (N_("Encoder thread %1 pushes frame %2 back onto queue after failure"), boost::this_thread::get_id(), vf->frame()) + String::compose (N_("Encoder thread %1 pushes frame %2 back onto queue after failure"), boost::this_thread::get_id(), vf->index()) ); _queue.push_front (vf); lock.unlock (); diff --combined src/lib/encoder.h index 6c465f816,a8ee220aa..ac1d74c57 --- a/src/lib/encoder.h +++ b/src/lib/encoder.h @@@ -1,5 -1,5 +1,5 @@@ /* - Copyright (C) 2012 Carl Hetherington + Copyright (C) 2012-2014 Carl Hetherington This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@@ -48,7 -48,7 +48,7 @@@ class EncodedData class Writer; class Job; class ServerFinder; - class DCPVideo; + class PlayerVideoFrame; /** @class Encoder * @brief Encoder to J2K and WAV for DCP. @@@ -67,9 -67,10 +67,9 @@@ public void process_begin (); /** Call with a frame of video. - * @param pvf Video frame image. - * @param same true if pvf is the same as the last time we were called. + * @param f Video frame. */ - void process_video (boost::shared_ptr f); - void process_video (boost::shared_ptr pvf, bool same); ++ void process_video (boost::shared_ptr f); /** Call with some audio data */ void process_audio (boost::shared_ptr); @@@ -105,6 -106,7 +105,6 @@@ private /** Number of video frames written for the DCP so far */ int _video_frames_out; - bool _have_a_real_frame[EYES_COUNT]; bool _terminate; std::list > _queue; std::list _threads; diff --combined src/lib/ffmpeg_decoder.cc index 9ae5f0485,7a5bf8ba8..d251a3744 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@@ -1,5 -1,5 +1,5 @@@ /* - Copyright (C) 2012 Carl Hetherington + Copyright (C) 2012-2014 Carl Hetherington This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@@ -32,6 -32,7 +32,6 @@@ extern "C" #include #include } -#include "film.h" #include "filter.h" #include "exceptions.h" #include "image.h" @@@ -41,6 -42,7 +41,7 @@@ #include "filter_graph.h" #include "audio_buffers.h" #include "ffmpeg_content.h" + #include "image_proxy.h" #include "i18n.h" @@@ -54,15 -56,20 +55,15 @@@ using std::pair using boost::shared_ptr; using boost::optional; using boost::dynamic_pointer_cast; -using libdcp::Size; +using dcp::Size; -FFmpegDecoder::FFmpegDecoder (shared_ptr f, shared_ptr c, bool video, bool audio) - : Decoder (f) - , VideoDecoder (f, c) - , AudioDecoder (f, c) - , SubtitleDecoder (f) +FFmpegDecoder::FFmpegDecoder (shared_ptr c, shared_ptr log) + : VideoDecoder (c) + , AudioDecoder (c) , FFmpeg (c) + , _log (log) , _subtitle_codec_context (0) , _subtitle_codec (0) - , _decode_video (video) - , _decode_audio (audio) - , _pts_offset (0) - , _just_sought (false) { setup_subtitle (); @@@ -75,11 -82,13 +76,11 @@@ Then we remove big initial gaps in PTS and we allow our insertion of black frames to work. - We will do: - audio_pts_to_use = audio_pts_from_ffmpeg + pts_offset; - video_pts_to_use = video_pts_from_ffmpeg + pts_offset; + We will do pts_to_use = pts_from_ffmpeg + pts_offset; */ - bool const have_video = video && c->first_video(); - bool const have_audio = audio && c->audio_stream() && c->audio_stream()->first_audio; + bool const have_video = c->first_video(); + bool const have_audio = c->audio_stream () && c->audio_stream()->first_audio; /* First, make one of them start at 0 */ @@@ -93,9 -102,15 +94,9 @@@ /* Now adjust both so that the video pts starts on a frame */ if (have_video && have_audio) { - double first_video = c->first_video().get() + _pts_offset; - double const old_first_video = first_video; - - /* Round the first video up to a frame boundary */ - if (fabs (rint (first_video * c->video_frame_rate()) - first_video * c->video_frame_rate()) > 1e-6) { - first_video = ceil (first_video * c->video_frame_rate()) / c->video_frame_rate (); - } - - _pts_offset += first_video - old_first_video; + ContentTime first_video = c->first_video().get() + _pts_offset; + ContentTime const old_first_video = first_video; + _pts_offset += first_video.round_up (c->video_frame_rate ()) - old_first_video; } } @@@ -118,15 -133,20 +119,15 @@@ FFmpegDecoder::flush ( /* XXX: should we reset _packet.data and size after each *_decode_* call? */ - if (_decode_video) { - while (decode_video_packet ()) {} - } + while (decode_video_packet ()) {} - if (_ffmpeg_content->audio_stream() && _decode_audio) { + if (_ffmpeg_content->audio_stream()) { decode_audio_packet (); + AudioDecoder::flush (); } - - /* Stop us being asked for any more data */ - _video_position = _ffmpeg_content->video_length_after_3d_combine (); - _audio_position = _ffmpeg_content->audio_length (); } -void +bool FFmpegDecoder::pass () { int r = av_read_frame (_format_context, &_packet); @@@ -136,25 -156,29 +137,25 @@@ /* Maybe we should fail here, but for now we'll just finish off instead */ char buf[256]; av_strerror (r, buf, sizeof(buf)); - shared_ptr film = _film.lock (); - assert (film); - film->log()->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r)); + _log->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r)); } flush (); - return; + return true; } - shared_ptr film = _film.lock (); - assert (film); - int const si = _packet.stream_index; - if (si == _video_stream && _decode_video) { + if (si == _video_stream) { decode_video_packet (); - } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, si) && _decode_audio) { + } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, si)) { decode_audio_packet (); - } else if (_ffmpeg_content->subtitle_stream() && _ffmpeg_content->subtitle_stream()->uses_index (_format_context, si) && film->with_subtitles ()) { + } else if (_ffmpeg_content->subtitle_stream() && _ffmpeg_content->subtitle_stream()->uses_index (_format_context, si)) { decode_subtitle_packet (); } av_free_packet (&_packet); + return false; } /** @param data pointer to array of pointers to buffers. @@@ -286,131 -310,77 +287,131 @@@ FFmpegDecoder::bytes_per_audio_sample ( return av_get_bytes_per_sample (audio_sample_format ()); } -void -FFmpegDecoder::seek (VideoContent::Frame frame, bool accurate) +int +FFmpegDecoder::minimal_run (boost::function, optional, int)> finished) { - double const time_base = av_q2d (_format_context->streams[_video_stream]->time_base); + int frames_read = 0; + optional last_video; + optional last_audio; - /* If we are doing an accurate seek, our initial shot will be 5 frames (5 being - a number plucked from the air) earlier than we want to end up. The loop below - will hopefully then step through to where we want to be. - */ - int initial = frame; + while (!finished (last_video, last_audio, frames_read)) { + int r = av_read_frame (_format_context, &_packet); + if (r < 0) { + /* We should flush our decoders here, possibly yielding a few more frames, + but the consequence of having to do that is too hideous to contemplate. + Instead we give up and say that you can't seek too close to the end + of a file. + */ + return frames_read; + } + + ++frames_read; + + double const time_base = av_q2d (_format_context->streams[_packet.stream_index]->time_base); + + if (_packet.stream_index == _video_stream) { + + avcodec_get_frame_defaults (_frame); + + int got_picture = 0; + r = avcodec_decode_video2 (video_codec_context(), _frame, &got_picture, &_packet); + if (r >= 0 && got_picture) { + last_video = ContentTime::from_seconds (av_frame_get_best_effort_timestamp (_frame) * time_base) + _pts_offset; + } + + } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, _packet.stream_index)) { + AVPacket copy_packet = _packet; + while (copy_packet.size > 0) { - if (accurate) { - initial -= 5; + int got_frame; + r = avcodec_decode_audio4 (audio_codec_context(), _frame, &got_frame, &_packet); + if (r >= 0 && got_frame) { + last_audio = ContentTime::from_seconds (av_frame_get_best_effort_timestamp (_frame) * time_base) + _pts_offset; + } + + copy_packet.data += r; + copy_packet.size -= r; + } + } + + av_free_packet (&_packet); } - if (initial < 0) { - initial = 0; + return frames_read; +} + +bool +FFmpegDecoder::seek_overrun_finished (ContentTime seek, optional last_video, optional last_audio) const +{ + return (last_video && last_video.get() >= seek) || (last_audio && last_audio.get() >= seek); +} + +bool +FFmpegDecoder::seek_final_finished (int n, int done) const +{ + return n == done; +} + +void +FFmpegDecoder::seek_and_flush (ContentTime t) +{ + ContentTime const u = t - _pts_offset; + int64_t s = u.seconds() / av_q2d (_format_context->streams[_video_stream]->time_base); + + if (_ffmpeg_content->audio_stream ()) { + s = min ( + s, int64_t (u.seconds() / av_q2d (_ffmpeg_content->audio_stream()->stream(_format_context)->time_base)) + ); } - /* Initial seek time in the stream's timebase */ - int64_t const initial_vt = ((initial / _ffmpeg_content->video_frame_rate()) - _pts_offset) / time_base; + /* Ridiculous empirical hack */ + s--; + if (s < 0) { + s = 0; + } - av_seek_frame (_format_context, _video_stream, initial_vt, AVSEEK_FLAG_BACKWARD); + av_seek_frame (_format_context, _video_stream, s, 0); avcodec_flush_buffers (video_codec_context()); + if (audio_codec_context ()) { + avcodec_flush_buffers (audio_codec_context ()); + } if (_subtitle_codec_context) { avcodec_flush_buffers (_subtitle_codec_context); } +} - /* This !accurate is piling hack upon hack; setting _just_sought to true - even with accurate == true defeats our attempt to align the start - of the video and audio. Here we disable that defeat when accurate == true - i.e. when we are making a DCP rather than just previewing one. - Ewww. This should be gone in 2.0. +void +FFmpegDecoder::seek (ContentTime time, bool accurate) +{ + VideoDecoder::seek (time, accurate); + AudioDecoder::seek (time, accurate); + + /* If we are doing an accurate seek, our initial shot will be 2s (2 being + a number plucked from the air) earlier than we want to end up. The loop below + will hopefully then step through to where we want to be. */ - if (!accurate) { - _just_sought = true; + + ContentTime pre_roll = accurate ? ContentTime::from_seconds (2) : ContentTime (0); + ContentTime initial_seek = time - pre_roll; + if (initial_seek < ContentTime (0)) { + initial_seek = ContentTime (0); } - - _video_position = frame; - - if (frame == 0 || !accurate) { - /* We're already there, or we're as close as we need to be */ + + /* Initial seek time in the video stream's timebase */ + + seek_and_flush (initial_seek); + + if (!accurate) { + /* That'll do */ return; } - while (1) { - int r = av_read_frame (_format_context, &_packet); - if (r < 0) { - return; - } - - if (_packet.stream_index != _video_stream) { - av_free_packet (&_packet); - continue; - } - - int finished = 0; - r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet); - if (r >= 0 && finished) { - _video_position = rint ( - (av_frame_get_best_effort_timestamp (_frame) * time_base + _pts_offset) * _ffmpeg_content->video_frame_rate() - ); + int const N = minimal_run (boost::bind (&FFmpegDecoder::seek_overrun_finished, this, time, _1, _2)); - if (_video_position >= (frame - 1)) { - av_free_packet (&_packet); - break; - } - } - - av_free_packet (&_packet); + seek_and_flush (initial_seek); + if (N > 0) { + minimal_run (boost::bind (&FFmpegDecoder::seek_final_finished, this, N - 1, _3)); } } @@@ -427,23 -397,39 +428,23 @@@ FFmpegDecoder::decode_audio_packet ( int frame_finished; int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, ©_packet); + if (decode_result < 0) { - shared_ptr film = _film.lock (); - assert (film); - film->log()->log (String::compose ("avcodec_decode_audio4 failed (%1)", decode_result)); + _log->log (String::compose ("avcodec_decode_audio4 failed (%1)", decode_result)); return; } if (frame_finished) { - - if (_audio_position == 0) { - /* Where we are in the source, in seconds */ - double const pts = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base) - * av_frame_get_best_effort_timestamp(_frame) + _pts_offset; - - if (pts > 0) { - /* Emit some silence */ - shared_ptr silence ( - new AudioBuffers ( - _ffmpeg_content->audio_channels(), - pts * _ffmpeg_content->content_audio_frame_rate() - ) - ); - - silence->make_silent (); - audio (silence, _audio_position); - } - } + ContentTime const ct = ContentTime::from_seconds ( + av_frame_get_best_effort_timestamp (_frame) * + av_q2d (_ffmpeg_content->audio_stream()->stream (_format_context)->time_base)) + + _pts_offset; int const data_size = av_samples_get_buffer_size ( 0, audio_codec_context()->channels, _frame->nb_samples, audio_sample_format (), 1 ); - - audio (deinterleave_audio (_frame->data, data_size), _audio_position); + + audio (deinterleave_audio (_frame->data, data_size), ct); } copy_packet.data += decode_result; @@@ -464,14 -450,18 +465,14 @@@ FFmpegDecoder::decode_video_packet ( shared_ptr graph; list >::iterator i = _filter_graphs.begin(); - while (i != _filter_graphs.end() && !(*i)->can_process (libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) { + while (i != _filter_graphs.end() && !(*i)->can_process (dcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) { ++i; } if (i == _filter_graphs.end ()) { - shared_ptr film = _film.lock (); - assert (film); - - graph.reset (new FilterGraph (_ffmpeg_content, libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)); + graph.reset (new FilterGraph (_ffmpeg_content, dcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)); _filter_graphs.push_back (graph); - - film->log()->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format)); + _log->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format)); } else { graph = *i; } @@@ -483,10 -473,49 +484,10 @@@ shared_ptr image = i->first; if (i->second != AV_NOPTS_VALUE) { - - double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset; - - if (_just_sought) { - /* We just did a seek, so disable any attempts to correct for where we - are / should be. - */ - _video_position = rint (pts * _ffmpeg_content->video_frame_rate ()); - _just_sought = false; - } - - double const next = _video_position / _ffmpeg_content->video_frame_rate(); - double const one_frame = 1 / _ffmpeg_content->video_frame_rate (); - double delta = pts - next; - - while (delta > one_frame) { - /* This PTS is more than one frame forward in time of where we think we should be; emit - a black frame. - */ - - /* XXX: I think this should be a copy of the last frame... */ - boost::shared_ptr black ( - new Image ( - static_cast (_frame->format), - libdcp::Size (video_codec_context()->width, video_codec_context()->height), - true - ) - ); - - black->make_black (); - video (shared_ptr (new RawImageProxy (image)), false, _video_position); - delta -= one_frame; - } - - if (delta > -one_frame) { - /* This PTS is within a frame of being right; emit this (otherwise it will be dropped) */ - video (shared_ptr (new RawImageProxy (image)), false, _video_position); - } - + double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset.seconds (); - video (image, rint (pts * _ffmpeg_content->video_frame_rate ())); ++ video (shared_ptr (new RawImageProxy (image)), rint (pts * _ffmpeg_content->video_frame_rate ())); } else { - shared_ptr film = _film.lock (); - assert (film); - film->log()->log ("Dropping frame without PTS"); + _log->log ("Dropping frame without PTS"); } } @@@ -519,6 -548,14 +520,6 @@@ FFmpegDecoder::setup_subtitle ( } } -bool -FFmpegDecoder::done () const -{ - bool const vd = !_decode_video || (_video_position >= _ffmpeg_content->video_length()); - bool const ad = !_decode_audio || !_ffmpeg_content->audio_stream() || (_audio_position >= _ffmpeg_content->audio_length()); - return vd && ad; -} - void FFmpegDecoder::decode_subtitle_packet () { @@@ -532,33 -569,31 +533,33 @@@ indicate that the previous subtitle should stop. */ if (sub.num_rects <= 0) { - subtitle (shared_ptr (), dcpomatic::Rect (), 0, 0); + image_subtitle (ContentTime (), ContentTime (), shared_ptr (), dcpomatic::Rect ()); return; } else if (sub.num_rects > 1) { throw DecodeError (_("multi-part subtitles not yet supported")); } - /* Subtitle PTS in seconds (within the source, not taking into account any of the + /* Subtitle PTS (within the source, not taking into account any of the source that we may have chopped off for the DCP) */ - double const packet_time = (static_cast (sub.pts ) / AV_TIME_BASE) + _pts_offset; - + ContentTime packet_time = ContentTime::from_seconds (static_cast (sub.pts) / AV_TIME_BASE) + _pts_offset; + /* hence start time for this sub */ - Time const from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ; - Time const to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ; + ContentTime const from = packet_time + ContentTime::from_seconds (sub.start_display_time / 1e3); + ContentTime const to = packet_time + ContentTime::from_seconds (sub.end_display_time / 1e3); AVSubtitleRect const * rect = sub.rects[0]; if (rect->type != SUBTITLE_BITMAP) { - throw DecodeError (_("non-bitmap subtitles not yet supported")); + /* XXX */ + // throw DecodeError (_("non-bitmap subtitles not yet supported")); + return; } /* Note RGBA is expressed little-endian, so the first byte in the word is R, second G, third B, fourth A. */ - shared_ptr image (new Image (PIX_FMT_RGBA, libdcp::Size (rect->w, rect->h), true)); + shared_ptr image (new Image (PIX_FMT_RGBA, dcp::Size (rect->w, rect->h), true)); /* Start of the first line in the subtitle */ uint8_t* sub_p = rect->pict.data[0]; @@@ -580,19 -615,20 +581,19 @@@ out_p += image->stride()[0] / sizeof (uint32_t); } - libdcp::Size const vs = _ffmpeg_content->video_size (); + dcp::Size const vs = _ffmpeg_content->video_size (); - subtitle ( + image_subtitle ( + from, + to, image, dcpomatic::Rect ( static_cast (rect->x) / vs.width, static_cast (rect->y) / vs.height, static_cast (rect->w) / vs.width, static_cast (rect->h) / vs.height - ), - from, - to + ) ); - avsubtitle_free (&sub); } diff --combined src/lib/image.cc index 432cfbd54,1fa55e242..d4ec6f99a --- a/src/lib/image.cc +++ b/src/lib/image.cc @@@ -1,5 -1,5 +1,5 @@@ /* - Copyright (C) 2012 Carl Hetherington + Copyright (C) 2012-2014 Carl Hetherington This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@@ -22,6 -22,7 +22,7 @@@ */ #include + #include extern "C" { #include #include @@@ -30,8 -31,6 +31,8 @@@ #include "image.h" #include "exceptions.h" #include "scaler.h" +#include "timer.h" +#include "rect.h" #include "i18n.h" @@@ -39,9 -38,9 +40,10 @@@ using std::string using std::min; using std::cout; using std::cerr; +using std::list; + using std::stringstream; using boost::shared_ptr; -using libdcp::Size; +using dcp::Size; int Image::line_factor (int n) const @@@ -85,7 -84,7 +87,7 @@@ Image::components () cons /** Crop this image, scale it to `inter_size' and then place it in a black frame of `out_size' */ shared_ptr -Image::crop_scale_window (Crop crop, libdcp::Size inter_size, libdcp::Size out_size, Scaler const * scaler, AVPixelFormat out_format, bool out_aligned) const +Image::crop_scale_window (Crop crop, dcp::Size inter_size, dcp::Size out_size, Scaler const * scaler, AVPixelFormat out_format, bool out_aligned) const { assert (scaler); /* Empirical testing suggests that sws_scale() will crash if @@@ -101,13 -100,13 +103,13 @@@ out->make_black (); /* Size of the image after any crop */ - libdcp::Size const cropped_size = crop.apply (size ()); + dcp::Size const cropped_size = crop.apply (size ()); /* Scale context for a scale from cropped_size to inter_size */ struct SwsContext* scale_context = sws_getContext ( - cropped_size.width, cropped_size.height, pixel_format(), - inter_size.width, inter_size.height, out_format, - scaler->ffmpeg_id (), 0, 0, 0 + cropped_size.width, cropped_size.height, pixel_format(), + inter_size.width, inter_size.height, out_format, + scaler->ffmpeg_id (), 0, 0, 0 ); if (!scale_context) { @@@ -141,7 -140,7 +143,7 @@@ } shared_ptr -Image::scale (libdcp::Size out_size, Scaler const * scaler, AVPixelFormat out_format, bool out_aligned) const +Image::scale (dcp::Size out_size, Scaler const * scaler, AVPixelFormat out_format, bool out_aligned) const { assert (scaler); /* Empirical testing suggests that sws_scale() will crash if @@@ -172,7 -171,7 +174,7 @@@ shared_ptr Image::crop (Crop crop, bool aligned) const { - libdcp::Size cropped_size = crop.apply (size ()); + dcp::Size cropped_size = crop.apply (size ()); shared_ptr out (new Image (pixel_format(), cropped_size, aligned)); for (int c = 0; c < components(); ++c) { @@@ -344,31 -343,11 +346,31 @@@ Image::make_black ( } } +void +Image::make_transparent () +{ + if (_pixel_format != PIX_FMT_RGBA) { + throw PixelFormatError ("make_transparent()", _pixel_format); + } + + memset (data()[0], 0, lines(0) * stride()[0]); +} + void Image::alpha_blend (shared_ptr other, Position position) { - /* Only implemented for RGBA onto RGB24 so far */ - assert (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGBA); + int this_bpp = 0; + int other_bpp = 0; + + if (_pixel_format == PIX_FMT_BGRA && other->pixel_format() == PIX_FMT_RGBA) { + this_bpp = 4; + other_bpp = 4; + } else if (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGBA) { + this_bpp = 3; + other_bpp = 4; + } else { + assert (false); + } int start_tx = position.x; int start_ox = 0; @@@ -387,15 -366,15 +389,15 @@@ } for (int ty = start_ty, oy = start_oy; ty < size().height && oy < other->size().height; ++ty, ++oy) { - uint8_t* tp = data()[0] + ty * stride()[0] + position.x * 3; + uint8_t* tp = data()[0] + ty * stride()[0] + position.x * this_bpp; uint8_t* op = other->data()[0] + oy * other->stride()[0]; for (int tx = start_tx, ox = start_ox; tx < size().width && ox < other->size().width; ++tx, ++ox) { float const alpha = float (op[3]) / 255; tp[0] = (tp[0] * (1 - alpha)) + op[0] * alpha; tp[1] = (tp[1] * (1 - alpha)) + op[1] * alpha; tp[2] = (tp[2] * (1 - alpha)) + op[2] * alpha; - tp += 3; - op += 4; + tp += this_bpp; + op += other_bpp; } } } @@@ -479,8 -458,8 +481,8 @@@ Image::bytes_per_pixel (int c) cons * @param p Pixel format. * @param s Size in pixels. */ -Image::Image (AVPixelFormat p, libdcp::Size s, bool aligned) - : libdcp::Image (s) +Image::Image (AVPixelFormat p, dcp::Size s, bool aligned) + : dcp::Image (s) , _pixel_format (p) , _aligned (aligned) { @@@ -517,7 -496,7 +519,7 @@@ Image::allocate ( } Image::Image (Image const & other) - : libdcp::Image (other) + : dcp::Image (other) , _pixel_format (other._pixel_format) , _aligned (other._aligned) { @@@ -535,7 -514,7 +537,7 @@@ } Image::Image (AVFrame* frame) - : libdcp::Image (libdcp::Size (frame->width, frame->height)) + : dcp::Image (dcp::Size (frame->width, frame->height)) , _pixel_format (static_cast (frame->format)) , _aligned (true) { @@@ -554,7 -533,7 +556,7 @@@ } Image::Image (shared_ptr other, bool aligned) - : libdcp::Image (other) + : dcp::Image (other) , _pixel_format (other->_pixel_format) , _aligned (aligned) { @@@ -587,7 -566,7 +589,7 @@@ Image::operator= (Image const & other void Image::swap (Image & other) { - libdcp::Image::swap (other); + dcp::Image::swap (other); std::swap (_pixel_format, other._pixel_format); @@@ -630,7 -609,7 +632,7 @@@ Image::stride () cons return _stride; } -libdcp::Size +dcp::Size Image::size () const { return _size; @@@ -642,23 -621,24 +644,44 @@@ Image::aligned () cons return _aligned; } +PositionImage +merge (list images) +{ + if (images.empty ()) { + return PositionImage (); + } + + dcpomatic::Rect all (images.front().position, images.front().image->size().width, images.front().image->size().height); + for (list::const_iterator i = images.begin(); i != images.end(); ++i) { + all.extend (dcpomatic::Rect (i->position, i->image->size().width, i->image->size().height)); + } + + shared_ptr merged (new Image (images.front().image->pixel_format (), dcp::Size (all.width, all.height), true)); + merged->make_transparent (); + for (list::const_iterator i = images.begin(); i != images.end(); ++i) { + merged->alpha_blend (i->image, i->position); + } + + return PositionImage (merged, all.position ()); +} ++ + string + Image::digest () const + { + MD5_CTX md5_context; + MD5_Init (&md5_context); + + for (int i = 0; i < components(); ++i) { + MD5_Update (&md5_context, data()[i], line_size()[i]); + } + + unsigned char digest[MD5_DIGEST_LENGTH]; + MD5_Final (digest, &md5_context); + + stringstream s; + for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) { + s << std::hex << std::setfill('0') << std::setw(2) << ((int) digest[i]); + } + + return s.str (); + } - diff --combined src/lib/image.h index 23b88dd76,f83bf6998..23c85e92b --- a/src/lib/image.h +++ b/src/lib/image.h @@@ -31,17 -31,16 +31,17 @@@ extern "C" #include #include } -#include +#include #include "util.h" #include "position.h" +#include "position_image.h" class Scaler; -class Image : public libdcp::Image +class Image : public dcp::Image { public: - Image (AVPixelFormat, libdcp::Size, bool); + Image (AVPixelFormat, dcp::Size, bool); Image (AVFrame *); Image (Image const &); Image (boost::shared_ptr, bool); @@@ -51,20 -50,19 +51,20 @@@ uint8_t ** data () const; int * line_size () const; int * stride () const; - libdcp::Size size () const; + dcp::Size size () const; bool aligned () const; int components () const; int line_factor (int) const; int lines (int) const; - boost::shared_ptr scale (libdcp::Size, Scaler const *, AVPixelFormat, bool aligned) const; + boost::shared_ptr scale (dcp::Size, Scaler const *, AVPixelFormat, bool aligned) const; boost::shared_ptr crop (Crop c, bool aligned) const; - boost::shared_ptr crop_scale_window (Crop c, libdcp::Size, libdcp::Size, Scaler const *, AVPixelFormat, bool aligned) const; + boost::shared_ptr crop_scale_window (Crop c, dcp::Size, dcp::Size, Scaler const *, AVPixelFormat, bool aligned) const; void make_black (); + void make_transparent (); void alpha_blend (boost::shared_ptr image, Position pos); void copy (boost::shared_ptr image, Position pos); @@@ -75,6 -73,8 +75,8 @@@ return _pixel_format; } + std::string digest () const; + private: friend class pixel_formats_test; @@@ -91,6 -91,4 +93,6 @@@ bool _aligned; }; +extern PositionImage merge (std::list images); + #endif diff --combined src/lib/image_decoder.cc index 5de0c8582,d33b64cd4..9f83d1d89 --- a/src/lib/image_decoder.cc +++ b/src/lib/image_decoder.cc @@@ -23,6 -23,7 +23,7 @@@ #include "image_content.h" #include "image_decoder.h" #include "image.h" + #include "image_proxy.h" #include "film.h" #include "exceptions.h" @@@ -30,67 -31,40 +31,35 @@@ using std::cout; using boost::shared_ptr; -using libdcp::Size; +using dcp::Size; -ImageDecoder::ImageDecoder (shared_ptr f, shared_ptr c) - : Decoder (f) - , VideoDecoder (f, c) +ImageDecoder::ImageDecoder (shared_ptr c) + : VideoDecoder (c) , _image_content (c) { } -void +bool ImageDecoder::pass () { - if (_video_position >= _image_content->video_length ()) { - return; + if (_video_position >= _image_content->video_length().frames (_image_content->video_frame_rate ())) { + return true; } -- if (_image && _image_content->still ()) { - video (_image, _video_position); - ++_video_position; - return false; - video (_image, true, _video_position); - return; ++ if (!_image_content->still() || !_image) { ++ /* Either we need an image or we are using moving images, so load one */ ++ _image.reset (new MagickImageProxy (_image_content->path (_image_content->still() ? 0 : _video_position))); } - - Magick::Image* magick_image = 0; - - boost::filesystem::path const path = _image_content->path (_image_content->still() ? 0 : _video_position); - - try { - magick_image = new Magick::Image (path.string ()); - } catch (...) { - throw OpenFileError (path); - } - - dcp::Size size (magick_image->columns(), magick_image->rows()); - - _image.reset (new Image (PIX_FMT_RGB24, size, true)); - - using namespace MagickCore; - - uint8_t* p = _image->data()[0]; - for (int y = 0; y < size.height; ++y) { - uint8_t* q = p; - for (int x = 0; x < size.width; ++x) { - Magick::Color c = magick_image->pixelColor (x, y); - *q++ = c.redQuantum() * 255 / QuantumRange; - *q++ = c.greenQuantum() * 255 / QuantumRange; - *q++ = c.blueQuantum() * 255 / QuantumRange; - } - p += _image->stride()[0]; - } - - delete magick_image; -- - _image.reset (new MagickImageProxy (_image_content->path (_image_content->still() ? 0 : _video_position))); - video (_image, false, _video_position); ++ + video (_image, _video_position); + ++_video_position; - + return false; } void -ImageDecoder::seek (VideoContent::Frame frame, bool) -{ - _video_position = frame; -} - -bool -ImageDecoder::done () const +ImageDecoder::seek (ContentTime time, bool accurate) { - return _video_position >= _image_content->video_length (); + VideoDecoder::seek (time, accurate); + _video_position = time.frames (_image_content->video_frame_rate ()); } diff --combined src/lib/image_decoder.h index 8d88df3de,5b82dd85c..242f69477 --- a/src/lib/image_decoder.h +++ b/src/lib/image_decoder.h @@@ -28,19 -28,20 +28,19 @@@ class ImageContent class ImageDecoder : public VideoDecoder { public: - ImageDecoder (boost::shared_ptr, boost::shared_ptr); + ImageDecoder (boost::shared_ptr c); boost::shared_ptr content () { return _image_content; } - /* Decoder */ - - void pass (); - void seek (VideoContent::Frame, bool); - bool done () const; + void seek (ContentTime, bool); private: + bool pass (); + boost::shared_ptr _image_content; - boost::shared_ptr _image; + boost::shared_ptr _image; + VideoFrame _video_position; }; diff --combined src/lib/image_proxy.cc index 000000000,47ac5d372..c74e846c9 mode 000000,100644..100644 --- a/src/lib/image_proxy.cc +++ b/src/lib/image_proxy.cc @@@ -1,0 -1,161 +1,161 @@@ + /* + Copyright (C) 2014 Carl Hetherington + + This program 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, + 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. + + */ + + #include -#include -#include ++#include ++#include + #include "image_proxy.h" + #include "image.h" + #include "exceptions.h" + #include "cross.h" + + #include "i18n.h" + + using std::cout; + using std::string; + using std::stringstream; + using boost::shared_ptr; + + RawImageProxy::RawImageProxy (shared_ptr image) + : _image (image) + { + + } + + RawImageProxy::RawImageProxy (shared_ptr xml, shared_ptr socket) + { - libdcp::Size size ( ++ dcp::Size size ( + xml->number_child ("Width"), xml->number_child ("Height") + ); + + _image.reset (new Image (PIX_FMT_RGB24, size, true)); + _image->read_from_socket (socket); + } + + shared_ptr + RawImageProxy::image () const + { + return _image; + } + + void + RawImageProxy::add_metadata (xmlpp::Node* node) const + { + node->add_child("Type")->add_child_text (N_("Raw")); - node->add_child("Width")->add_child_text (libdcp::raw_convert (_image->size().width)); - node->add_child("Height")->add_child_text (libdcp::raw_convert (_image->size().height)); ++ node->add_child("Width")->add_child_text (dcp::raw_convert (_image->size().width)); ++ node->add_child("Height")->add_child_text (dcp::raw_convert (_image->size().height)); + } + + void + RawImageProxy::send_binary (shared_ptr socket) const + { + _image->write_to_socket (socket); + } + + MagickImageProxy::MagickImageProxy (boost::filesystem::path path) + { + /* Read the file into a Blob */ + + boost::uintmax_t const size = boost::filesystem::file_size (path); + FILE* f = fopen_boost (path, "rb"); + if (!f) { + throw OpenFileError (path); + } + + uint8_t* data = new uint8_t[size]; + if (fread (data, 1, size, f) != size) { + delete[] data; + throw ReadFileError (path); + } + + fclose (f); + _blob.update (data, size); + delete[] data; + } + + MagickImageProxy::MagickImageProxy (shared_ptr, shared_ptr socket) + { + uint32_t const size = socket->read_uint32 (); + uint8_t* data = new uint8_t[size]; + socket->read (data, size); + _blob.update (data, size); + delete[] data; + } + + shared_ptr + MagickImageProxy::image () const + { + if (_image) { + return _image; + } + + Magick::Image* magick_image = 0; + try { + magick_image = new Magick::Image (_blob); + } catch (...) { + throw DecodeError (_("Could not decode image file")); + } + - libdcp::Size size (magick_image->columns(), magick_image->rows()); ++ dcp::Size size (magick_image->columns(), magick_image->rows()); + + _image.reset (new Image (PIX_FMT_RGB24, size, true)); + + using namespace MagickCore; + + uint8_t* p = _image->data()[0]; + for (int y = 0; y < size.height; ++y) { + uint8_t* q = p; + for (int x = 0; x < size.width; ++x) { + Magick::Color c = magick_image->pixelColor (x, y); + *q++ = c.redQuantum() * 255 / QuantumRange; + *q++ = c.greenQuantum() * 255 / QuantumRange; + *q++ = c.blueQuantum() * 255 / QuantumRange; + } + p += _image->stride()[0]; + } + + delete magick_image; + + return _image; + } + + void + MagickImageProxy::add_metadata (xmlpp::Node* node) const + { + node->add_child("Type")->add_child_text (N_("Magick")); + } + + void + MagickImageProxy::send_binary (shared_ptr socket) const + { + socket->write (_blob.length ()); + socket->write ((uint8_t *) _blob.data (), _blob.length ()); + } + + shared_ptr + image_proxy_factory (shared_ptr xml, shared_ptr socket) + { + if (xml->string_child("Type") == N_("Raw")) { + return shared_ptr (new RawImageProxy (xml, socket)); + } else if (xml->string_child("Type") == N_("Magick")) { + return shared_ptr (new MagickImageProxy (xml, socket)); + } + + throw NetworkError (_("Unexpected image type received by server")); + } diff --combined src/lib/player.cc index 75b550093,9f0f380e3..ab0d8f356 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@@ -18,50 -18,46 +18,51 @@@ */ #include +#include #include "player.h" #include "film.h" #include "ffmpeg_decoder.h" +#include "audio_buffers.h" #include "ffmpeg_content.h" #include "image_decoder.h" #include "image_content.h" #include "sndfile_decoder.h" #include "sndfile_content.h" #include "subtitle_content.h" +#include "subrip_decoder.h" +#include "subrip_content.h" #include "playlist.h" #include "job.h" #include "image.h" + #include "image_proxy.h" #include "ratio.h" -#include "resampler.h" #include "log.h" #include "scaler.h" +#include "render_subtitles.h" - #include "dcp_video.h" +#include "config.h" +#include "content_video.h" + #include "player_video_frame.h" using std::list; using std::cout; using std::min; using std::max; +using std::min; using std::vector; using std::pair; using std::map; +using std::make_pair; using boost::shared_ptr; using boost::weak_ptr; using boost::dynamic_pointer_cast; +using boost::optional; Player::Player (shared_ptr f, shared_ptr p) : _film (f) , _playlist (p) - , _video (true) - , _audio (true) , _have_valid_pieces (false) - , _video_position (0) - , _audio_position (0) - , _audio_merger (f->audio_channels(), bind (&Film::time_to_audio_frames, f.get(), _1), bind (&Film::audio_frames_to_time, f.get(), _1)) - , _last_emit_was_black (false) + , _approximate_size (false) + , _burn_subtitles (false) { _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this)); _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2, _3)); @@@ -70,479 -66,572 +71,479 @@@ } void -Player::disable_video () -{ - _video = false; -} - -void -Player::disable_audio () +Player::setup_pieces () { - _audio = false; -} + list > old_pieces = _pieces; + _pieces.clear (); -bool -Player::pass () -{ - if (!_have_valid_pieces) { - setup_pieces (); - } + ContentList content = _playlist->content (); - Time earliest_t = TIME_MAX; - shared_ptr earliest; - enum { - VIDEO, - AUDIO - } type = VIDEO; + for (ContentList::iterator i = content.begin(); i != content.end(); ++i) { - for (list >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) { - if ((*i)->decoder->done ()) { + if (!(*i)->paths_valid ()) { continue; } - - shared_ptr vd = dynamic_pointer_cast ((*i)->decoder); - shared_ptr ad = dynamic_pointer_cast ((*i)->decoder); - - if (_video && vd) { - if ((*i)->video_position < earliest_t) { - earliest_t = (*i)->video_position; - earliest = *i; - type = VIDEO; + + shared_ptr decoder; + optional frc; + + /* Work out a FrameRateChange for the best overlap video for this content, in case we need it below */ + DCPTime best_overlap_t; + shared_ptr best_overlap; + for (ContentList::iterator j = content.begin(); j != content.end(); ++j) { + shared_ptr vc = dynamic_pointer_cast (*j); + if (!vc) { + continue; } - } - - if (_audio && ad && ad->has_audio ()) { - if ((*i)->audio_position < earliest_t) { - earliest_t = (*i)->audio_position; - earliest = *i; - type = AUDIO; + + DCPTime const overlap = max (vc->position(), (*i)->position()) - min (vc->end(), (*i)->end()); + if (overlap > best_overlap_t) { + best_overlap = vc; + best_overlap_t = overlap; } } - } - if (!earliest) { - flush (); - return true; - } - - switch (type) { - case VIDEO: - if (earliest_t > _video_position) { - emit_black (); + optional best_overlap_frc; + if (best_overlap) { + best_overlap_frc = FrameRateChange (best_overlap->video_frame_rate(), _film->video_frame_rate ()); } else { - if (earliest->repeating ()) { - earliest->repeat (this); - } else { - earliest->decoder->pass (); - } + /* No video overlap; e.g. if the DCP is just audio */ + best_overlap_frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ()); } - break; - case AUDIO: - if (earliest_t > _audio_position) { - emit_silence (_film->time_to_audio_frames (earliest_t - _audio_position)); - } else { - earliest->decoder->pass (); - - if (earliest->decoder->done()) { - shared_ptr ac = dynamic_pointer_cast (earliest->content); - assert (ac); - shared_ptr re = resampler (ac, false); - if (re) { - shared_ptr b = re->flush (); - if (b->frames ()) { - process_audio (earliest, b, ac->audio_length ()); - } - } - } + /* FFmpeg */ + shared_ptr fc = dynamic_pointer_cast (*i); + if (fc) { + decoder.reset (new FFmpegDecoder (fc, _film->log())); + frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate()); } - break; - } - if (_audio) { - boost::optional