diff options
| author | Carl Hetherington <cth@carlh.net> | 2012-12-19 23:50:17 +0000 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2012-12-19 23:50:17 +0000 |
| commit | 2f56f38ce56b36f20d59593f56981e7ed330c484 (patch) | |
| tree | 1889f6eff9545010815775671df54064bc796201 /src/lib | |
| parent | 13337c62d8c0d052ba0377af9c00fe1d940be3cc (diff) | |
Re-work again so that there's just one encoder; various tweaks to still-image-with-audio.
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/ab_transcode_job.cc | 4 | ||||
| -rw-r--r-- | src/lib/ab_transcoder.cc | 4 | ||||
| -rw-r--r-- | src/lib/combiner.cc | 6 | ||||
| -rw-r--r-- | src/lib/combiner.h | 4 | ||||
| -rw-r--r-- | src/lib/encoder.cc | 217 | ||||
| -rw-r--r-- | src/lib/encoder.h | 29 | ||||
| -rw-r--r-- | src/lib/encoder_factory.cc | 39 | ||||
| -rw-r--r-- | src/lib/encoder_factory.h | 30 | ||||
| -rw-r--r-- | src/lib/exceptions.h | 16 | ||||
| -rw-r--r-- | src/lib/ffmpeg_decoder.cc | 12 | ||||
| -rw-r--r-- | src/lib/ffmpeg_decoder.h | 1 | ||||
| -rw-r--r-- | src/lib/film.cc | 33 | ||||
| -rw-r--r-- | src/lib/film.h | 10 | ||||
| -rw-r--r-- | src/lib/imagemagick_decoder.cc | 7 | ||||
| -rw-r--r-- | src/lib/j2k_still_encoder.cc | 95 | ||||
| -rw-r--r-- | src/lib/j2k_still_encoder.h | 44 | ||||
| -rw-r--r-- | src/lib/j2k_video_encoder.cc | 259 | ||||
| -rw-r--r-- | src/lib/j2k_video_encoder.h | 61 | ||||
| -rw-r--r-- | src/lib/matcher.cc | 6 | ||||
| -rw-r--r-- | src/lib/matcher.h | 2 | ||||
| -rw-r--r-- | src/lib/options.h | 6 | ||||
| -rw-r--r-- | src/lib/transcode_job.cc | 3 | ||||
| -rw-r--r-- | src/lib/video_decoder.cc | 8 | ||||
| -rw-r--r-- | src/lib/video_decoder.h | 2 | ||||
| -rw-r--r-- | src/lib/video_sink.h | 3 | ||||
| -rw-r--r-- | src/lib/video_source.cc | 2 | ||||
| -rw-r--r-- | src/lib/video_source.h | 5 | ||||
| -rw-r--r-- | src/lib/wscript | 3 |
28 files changed, 306 insertions, 605 deletions
diff --git a/src/lib/ab_transcode_job.cc b/src/lib/ab_transcode_job.cc index da9ce7933..a6233c185 100644 --- a/src/lib/ab_transcode_job.cc +++ b/src/lib/ab_transcode_job.cc @@ -23,8 +23,8 @@ #include "format.h" #include "filter.h" #include "ab_transcoder.h" -#include "encoder_factory.h" #include "config.h" +#include "encoder.h" using std::string; using boost::shared_ptr; @@ -53,7 +53,7 @@ ABTranscodeJob::run () { try { /* _film_b is the one with reference filters */ - ABTranscoder w (_film_b, _film, _decode_opt, this, encoder_factory (_film, _encode_opt)); + ABTranscoder w (_film_b, _film, _decode_opt, this, shared_ptr<Encoder> (new Encoder (_film, _encode_opt))); w.go (); set_progress (1); set_state (FINISHED_OK); diff --git a/src/lib/ab_transcoder.cc b/src/lib/ab_transcoder.cc index d85f078a5..53af43b5d 100644 --- a/src/lib/ab_transcoder.cc +++ b/src/lib/ab_transcoder.cc @@ -70,8 +70,8 @@ ABTranscoder::ABTranscoder ( _db.video->set_subtitle_stream (_film_a->subtitle_stream ()); _da.audio->set_audio_stream (_film_a->audio_stream ()); - _da.video->Video.connect (bind (&Combiner::process_video, _combiner, _1, _2)); - _db.video->Video.connect (bind (&Combiner::process_video_b, _combiner, _1, _2)); + _da.video->Video.connect (bind (&Combiner::process_video, _combiner, _1, _2, _3)); + _db.video->Video.connect (bind (&Combiner::process_video_b, _combiner, _1, _2, _3)); if (_matcher) { _combiner->connect_video (_matcher); diff --git a/src/lib/combiner.cc b/src/lib/combiner.cc index b85dbf288..d5f55026b 100644 --- a/src/lib/combiner.cc +++ b/src/lib/combiner.cc @@ -33,7 +33,7 @@ Combiner::Combiner (Log* log) * @param sub Subtitle (which will be ignored) */ void -Combiner::process_video (shared_ptr<Image> image, shared_ptr<Subtitle> sub) +Combiner::process_video (shared_ptr<Image> image, bool, shared_ptr<Subtitle> sub) { _image = image; } @@ -43,7 +43,7 @@ Combiner::process_video (shared_ptr<Image> image, shared_ptr<Subtitle> sub) * @param sub Subtitle (which will be put onto the whole frame) */ void -Combiner::process_video_b (shared_ptr<Image> image, shared_ptr<Subtitle> sub) +Combiner::process_video_b (shared_ptr<Image> image, bool, shared_ptr<Subtitle> sub) { /* Copy the right half of this image into our _image */ /* XXX: this should probably be in the Image class */ @@ -62,6 +62,6 @@ Combiner::process_video_b (shared_ptr<Image> image, shared_ptr<Subtitle> sub) } } - Video (_image, sub); + Video (_image, false, sub); _image.reset (); } diff --git a/src/lib/combiner.h b/src/lib/combiner.h index 78c9889b5..7fad1aeae 100644 --- a/src/lib/combiner.h +++ b/src/lib/combiner.h @@ -33,8 +33,8 @@ class Combiner : public VideoProcessor public: Combiner (Log* log); - void process_video (boost::shared_ptr<Image> i, boost::shared_ptr<Subtitle> s); - void process_video_b (boost::shared_ptr<Image> i, boost::shared_ptr<Subtitle> s); + void process_video (boost::shared_ptr<Image> i, bool, boost::shared_ptr<Subtitle> s); + void process_video_b (boost::shared_ptr<Image> i, bool, boost::shared_ptr<Subtitle> s); private: /** The image that we are currently working on */ diff --git a/src/lib/encoder.cc b/src/lib/encoder.cc index f352f5a52..7a475a859 100644 --- a/src/lib/encoder.cc +++ b/src/lib/encoder.cc @@ -22,16 +22,25 @@ */ #include <boost/filesystem.hpp> +#include <boost/lexical_cast.hpp> #include "encoder.h" #include "util.h" #include "options.h" #include "film.h" #include "log.h" #include "exceptions.h" +#include "filter.h" +#include "config.h" +#include "dcp_video_frame.h" +#include "server.h" +#include "cross.h" using std::pair; +using std::string; using std::stringstream; using std::vector; +using std::list; +using std::cout; using namespace boost; int const Encoder::_history_size = 25; @@ -49,6 +58,7 @@ Encoder::Encoder (shared_ptr<const Film> f, shared_ptr<const EncodeOptions> o) , _swr_context (0) #endif , _audio_frames_written (0) + , _process_end (false) { if (_film->audio_stream()) { /* Create sound output files with .tmp suffixes; we will rename @@ -72,6 +82,7 @@ Encoder::Encoder (shared_ptr<const Film> f, shared_ptr<const EncodeOptions> o) Encoder::~Encoder () { close_sound_files (); + terminate_worker_threads (); } void @@ -105,6 +116,18 @@ Encoder::process_begin () _swr_context = 0; #endif } + + for (int i = 0; i < Config::instance()->num_local_encoding_threads (); ++i) { + _worker_threads.push_back (new boost::thread (boost::bind (&Encoder::encoder_thread, this, (ServerDescription *) 0))); + } + + vector<ServerDescription*> servers = Config::instance()->servers (); + + for (vector<ServerDescription*>::iterator i = servers.begin(); i != servers.end(); ++i) { + for (int j = 0; j < (*i)->threads (); ++j) { + _worker_threads.push_back (new boost::thread (boost::bind (&Encoder::encoder_thread, this, *i))); + } + } } @@ -146,6 +169,43 @@ Encoder::process_end () boost::filesystem::rename (_opt->multichannel_audio_out_path (i, true), _opt->multichannel_audio_out_path (i, false)); } } + + boost::mutex::scoped_lock lock (_worker_mutex); + + _film->log()->log ("Clearing queue of " + lexical_cast<string> (_queue.size ())); + + /* Keep waking workers until the queue is empty */ + while (!_queue.empty ()) { + _film->log()->log ("Waking with " + lexical_cast<string> (_queue.size ()), Log::VERBOSE); + _worker_condition.notify_all (); + _worker_condition.wait (lock); + } + + lock.unlock (); + + terminate_worker_threads (); + + _film->log()->log ("Mopping up " + lexical_cast<string> (_queue.size())); + + /* The following sequence of events can occur in the above code: + 1. a remote worker takes the last image off the queue + 2. the loop above terminates + 3. the remote worker fails to encode the image and puts it back on the queue + 4. the remote worker is then terminated by terminate_worker_threads + + So just mop up anything left in the queue here. + */ + + for (list<shared_ptr<DCPVideoFrame> >::iterator i = _queue.begin(); i != _queue.end(); ++i) { + _film->log()->log (String::compose ("Encode left-over frame %1", (*i)->frame ())); + try { + shared_ptr<EncodedData> e = (*i)->encode_locally (); + e->write (_opt, (*i)->frame ()); + frame_done (); + } catch (std::exception& e) { + _film->log()->log (String::compose ("Local encode failed (%1)", e.what ())); + } + } } /** @return an estimate of the current number of frames we are encoding per second, @@ -209,7 +269,7 @@ Encoder::frame_skipped () } void -Encoder::process_video (shared_ptr<Image> i, boost::shared_ptr<Subtitle> s) +Encoder::process_video (shared_ptr<Image> image, bool same, boost::shared_ptr<Subtitle> sub) { if (_opt->video_skip != 0 && (_video_frame % _opt->video_skip) != 0) { ++_video_frame; @@ -224,7 +284,47 @@ Encoder::process_video (shared_ptr<Image> i, boost::shared_ptr<Subtitle> s) } } - do_process_video (i, s); + boost::mutex::scoped_lock lock (_worker_mutex); + + /* Wait until the queue has gone down a bit */ + while (_queue.size() >= _worker_threads.size() * 2 && !_process_end) { + TIMING ("decoder sleeps with queue of %1", _queue.size()); + _worker_condition.wait (lock); + TIMING ("decoder wakes with queue of %1", _queue.size()); + } + + if (_process_end) { + return; + } + + /* Only do the processing if we don't already have a file for this frame */ + if (boost::filesystem::exists (_opt->frame_out_path (_video_frame, false))) { + frame_skipped (); + return; + } + + if (same) { + /* Use the last frame that we encoded */ + assert (_last_real_frame); + link (_opt->frame_out_path (_last_real_frame.get(), false), _opt->frame_out_path (_video_frame, false)); + link (_opt->hash_out_path (_last_real_frame.get(), false), _opt->hash_out_path (_video_frame, false)); + } else { + /* Queue this new frame for encoding */ + pair<string, string> const s = Filter::ffmpeg_strings (_film->filters()); + TIMING ("adding to queue of %1", _queue.size ()); + _queue.push_back (boost::shared_ptr<DCPVideoFrame> ( + new DCPVideoFrame ( + image, sub, _opt->out_size, _opt->padding, _film->subtitle_offset(), _film->subtitle_scale(), + _film->scaler(), _video_frame, _film->frames_per_second(), s.second, + Config::instance()->colour_lut_index (), Config::instance()->j2k_bandwidth (), + _film->log() + ) + )); + + _worker_condition.notify_all (); + _last_real_frame = _video_frame; + } + ++_video_frame; } @@ -232,7 +332,6 @@ void Encoder::process_audio (shared_ptr<AudioBuffers> data) { if (_opt->audio_range) { - shared_ptr<AudioBuffers> trimmed (new AudioBuffers (*data.get ())); /* Range that we are encoding */ @@ -306,3 +405,115 @@ Encoder::close_sound_files () _sound_files.clear (); } +void +Encoder::terminate_worker_threads () +{ + boost::mutex::scoped_lock lock (_worker_mutex); + _process_end = true; + _worker_condition.notify_all (); + lock.unlock (); + + for (list<boost::thread *>::iterator i = _worker_threads.begin(); i != _worker_threads.end(); ++i) { + (*i)->join (); + delete *i; + } +} + +void +Encoder::encoder_thread (ServerDescription* server) +{ + /* Number of seconds that we currently wait between attempts + to connect to the server; not relevant for localhost + encodings. + */ + int remote_backoff = 0; + + while (1) { + + TIMING ("encoder thread %1 sleeps", boost::this_thread::get_id()); + boost::mutex::scoped_lock lock (_worker_mutex); + while (_queue.empty () && !_process_end) { + _worker_condition.wait (lock); + } + + if (_process_end) { + return; + } + + TIMING ("encoder thread %1 wakes with queue of %2", boost::this_thread::get_id(), _queue.size()); + boost::shared_ptr<DCPVideoFrame> vf = _queue.front (); + _film->log()->log (String::compose ("Encoder thread %1 pops frame %2 from queue", boost::this_thread::get_id(), vf->frame()), Log::VERBOSE); + _queue.pop_front (); + + lock.unlock (); + + shared_ptr<EncodedData> encoded; + + if (server) { + try { + encoded = vf->encode_remotely (server); + + if (remote_backoff > 0) { + _film->log()->log (String::compose ("%1 was lost, but now she is found; removing backoff", server->host_name ())); + } + + /* This job succeeded, so remove any backoff */ + remote_backoff = 0; + + } catch (std::exception& e) { + if (remote_backoff < 60) { + /* back off more */ + remote_backoff += 10; + } + _film->log()->log ( + String::compose ( + "Remote encode of %1 on %2 failed (%3); thread sleeping for %4s", + vf->frame(), 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()); + encoded = vf->encode_locally (); + TIMING ("encoder thread %1 finishes local encode of %2", boost::this_thread::get_id(), vf->frame()); + } catch (std::exception& e) { + _film->log()->log (String::compose ("Local encode failed (%1)", e.what ())); + } + } + + if (encoded) { + encoded->write (_opt, vf->frame ()); + frame_done (); + } else { + lock.lock (); + _film->log()->log ( + String::compose ("Encoder thread %1 pushes frame %2 back onto queue after failure", boost::this_thread::get_id(), vf->frame()) + ); + _queue.push_front (vf); + lock.unlock (); + } + + if (remote_backoff > 0) { + dvdomatic_sleep (remote_backoff); + } + + lock.lock (); + _worker_condition.notify_all (); + } +} + +void +Encoder::link (string a, string b) const +{ +#ifdef DVDOMATIC_POSIX + int const r = symlink (a.c_str(), b.c_str()); + if (r) { + throw EncodeError (String::compose ("could not create symlink from %1 to %2", a, b)); + } +#endif + +#ifdef DVDOMATIC_WINDOWS + boost::filesystem::copy_file (a, b); +#endif +} diff --git a/src/lib/encoder.h b/src/lib/encoder.h index f355daff7..8ab4479ef 100644 --- a/src/lib/encoder.h +++ b/src/lib/encoder.h @@ -26,6 +26,8 @@ #include <boost/shared_ptr.hpp> #include <boost/thread/mutex.hpp> +#include <boost/thread/condition.hpp> +#include <boost/thread.hpp> #include <list> #include <stdint.h> extern "C" { @@ -46,15 +48,14 @@ class Image; class Subtitle; class AudioBuffers; class Film; +class ServerDescription; +class DCPVideoFrame; /** @class Encoder - * @brief Parent class for classes which can encode video and audio frames. + * @brief Encoder to J2K and WAV for DCP. * * Video is supplied to process_video as YUV frames, and audio * is supplied as uncompressed PCM in blocks of various sizes. - * - * The subclass is expected to encode the video and/or audio in - * some way and write it to disk. */ class Encoder : public VideoSink, public AudioSink @@ -68,9 +69,10 @@ public: /** Call with a frame of video. * @param i Video frame image. + * @param same true if i is the same as the last time we were called. * @param s A subtitle that should be on this frame, or 0. */ - void process_video (boost::shared_ptr<Image> i, boost::shared_ptr<Subtitle> s); + void process_video (boost::shared_ptr<Image> i, bool same, boost::shared_ptr<Subtitle> s); /** Call with some audio data */ void process_audio (boost::shared_ptr<AudioBuffers>); @@ -83,12 +85,6 @@ public: SourceFrame video_frame () const; protected: - - /** Called with a frame of video. - * @param i Video frame image. - * @param s A subtitle that should be on this frame, or 0. - */ - virtual void do_process_video (boost::shared_ptr<Image> i, boost::shared_ptr<Subtitle> s) = 0; void frame_done (); void frame_skipped (); @@ -118,12 +114,23 @@ private: void close_sound_files (); void write_audio (boost::shared_ptr<const AudioBuffers> audio); + void encoder_thread (ServerDescription *); + void terminate_worker_threads (); + void link (std::string, std::string) const; + #if HAVE_SWRESAMPLE SwrContext* _swr_context; #endif std::vector<SNDFILE*> _sound_files; int64_t _audio_frames_written; + + boost::optional<int> _last_real_frame; + bool _process_end; + std::list<boost::shared_ptr<DCPVideoFrame> > _queue; + std::list<boost::thread *> _worker_threads; + mutable boost::mutex _worker_mutex; + boost::condition _worker_condition; }; #endif diff --git a/src/lib/encoder_factory.cc b/src/lib/encoder_factory.cc deleted file mode 100644 index 8384ecee3..000000000 --- a/src/lib/encoder_factory.cc +++ /dev/null @@ -1,39 +0,0 @@ -/* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> - - 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. - -*/ - -/** @file src/encoder_factory.cc - * @brief A method to create an appropriate encoder for some content. - */ - -#include <boost/filesystem.hpp> -#include "j2k_video_encoder.h" -#include "j2k_still_encoder.h" -#include "film.h" - -using boost::shared_ptr; - -shared_ptr<Encoder> -encoder_factory (shared_ptr<const Film> f, shared_ptr<const EncodeOptions> o) -{ - if (!boost::filesystem::is_directory (f->content_path()) && f->content_type() == STILL) { - return shared_ptr<Encoder> (new J2KStillEncoder (f, o)); - } - - return shared_ptr<Encoder> (new J2KVideoEncoder (f, o)); -} diff --git a/src/lib/encoder_factory.h b/src/lib/encoder_factory.h deleted file mode 100644 index 5ac5c9559..000000000 --- a/src/lib/encoder_factory.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> - - 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. - -*/ - -/** @file src/encoder_factory.h - * @brief A method to create an appropriate encoder for some content. - */ - -class Encoder; -class EncodeOptions; -class Job; -class Log; -class Film; - -extern boost::shared_ptr<Encoder> encoder_factory (boost::shared_ptr<const Film>, boost::shared_ptr<const EncodeOptions>); diff --git a/src/lib/exceptions.h b/src/lib/exceptions.h index 8ef09875b..bf8e85f0b 100644 --- a/src/lib/exceptions.h +++ b/src/lib/exceptions.h @@ -224,19 +224,3 @@ public: : StringError (s) {} }; - -class PlayError : public StringError -{ -public: - PlayError (std::string s) - : StringError (s) - {} -}; - -class DVDError : public StringError -{ -public: - DVDError (std::string s) - : StringError (s) - {} -}; diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc index 60bb3271e..a19f26ad7 100644 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@ -572,10 +572,8 @@ FFmpegDecoder::filter_and_emit_video (AVFrame* frame) list<shared_ptr<Image> > images = graph->process (frame); - double const st = av_frame_get_best_effort_timestamp(_frame) * av_q2d (_format_context->streams[_video_stream]->time_base); - for (list<shared_ptr<Image> >::iterator i = images.begin(); i != images.end(); ++i) { - emit_video (*i, st); + emit_video (*i, frame_time ()); } } @@ -694,7 +692,7 @@ FFmpegDecoder::out_with_sync () String::compose ( "Extra video frame inserted at %1s; source frame %2, source PTS %3 (at %4 fps)", out_pts_seconds, video_frame(), source_pts_seconds, frames_per_second() - ) + ) ); } } @@ -733,3 +731,9 @@ FFmpegDecoder::length () const return (double(_format_context->duration) / AV_TIME_BASE) * frames_per_second(); } +double +FFmpegDecoder::frame_time () const +{ + return av_frame_get_best_effort_timestamp(_frame) * av_q2d (_format_context->streams[_video_stream]->time_base); +} + diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h index 89534a38c..2fb8675f9 100644 --- a/src/lib/ffmpeg_decoder.h +++ b/src/lib/ffmpeg_decoder.h @@ -113,6 +113,7 @@ private: void out_with_sync (); void filter_and_emit_video (AVFrame *); + double frame_time () const; void setup_general (); void setup_video (); diff --git a/src/lib/film.cc b/src/lib/film.cc index 9da15a73b..e7687fe3c 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -131,8 +131,10 @@ Film::Film (string d, bool must_exist) } _external_audio_stream = ExternalAudioStream::create (); - - read_metadata (); + + if (must_exist) { + read_metadata (); + } _log = new FileLog (file ("log")); set_dci_date_today (); @@ -267,8 +269,18 @@ Film::make_dcp (bool transcode) oe->video_range = make_pair (dcp_trim_start(), dcp_trim_start() + dcp_length().get()); if (audio_stream()) { oe->audio_range = make_pair ( - video_frames_to_audio_frames (oe->video_range.get().first, audio_stream()->sample_rate(), frames_per_second()), - video_frames_to_audio_frames (oe->video_range.get().second, audio_stream()->sample_rate(), frames_per_second()) + + video_frames_to_audio_frames ( + oe->video_range.get().first, + dcp_audio_sample_rate (audio_stream()->sample_rate()), + dcp_frame_rate (frames_per_second()).frames_per_second + ), + + video_frames_to_audio_frames ( + oe->video_range.get().second, + dcp_audio_sample_rate (audio_stream()->sample_rate()), + dcp_frame_rate (frames_per_second()).frames_per_second + ) ); } @@ -449,8 +461,12 @@ Film::read_metadata () boost::optional<int> audio_sample_rate; boost::optional<int> audio_stream_index; boost::optional<int> subtitle_stream_index; - + ifstream f (file ("metadata").c_str()); + if (!f.good()) { + throw OpenFileError (file("metadata")); + } + multimap<string, string> kv = read_key_value (f); /* We need version before anything else */ @@ -675,7 +691,7 @@ Film::target_audio_sample_rate () const return rint (t); } -boost::optional<SourceFrame> +boost::optional<int> Film::dcp_length () const { if (content_type() == STILL) { @@ -683,7 +699,7 @@ Film::dcp_length () const } if (!length()) { - return boost::optional<SourceFrame> (); + return boost::optional<int> (); } return length().get() - dcp_trim_start() - dcp_trim_end(); @@ -850,6 +866,9 @@ Film::set_content (string c) _content_audio_stream = shared_ptr<AudioStream> (); _subtitle_stream = shared_ptr<SubtitleStream> (); + /* Start off using content audio */ + set_use_content_audio (true); + /* Create a temporary decoder so that we can get information about the content. */ diff --git a/src/lib/film.h b/src/lib/film.h index 536855b1f..b8a824233 100644 --- a/src/lib/film.h +++ b/src/lib/film.h @@ -88,7 +88,7 @@ public: void read_metadata (); Size cropped_size (Size) const; - boost::optional<SourceFrame> dcp_length () const; + boost::optional<int> dcp_length () const; std::string dci_name () const; std::string dcp_name () const; @@ -412,10 +412,10 @@ private: std::vector<Filter const *> _filters; /** Scaler algorithm to use */ Scaler const * _scaler; - /** Frames to trim off the start of the source */ - SourceFrame _dcp_trim_start; - /** Frames to trim off the end of the source */ - SourceFrame _dcp_trim_end; + /** Frames to trim off the start of the DCP */ + int _dcp_trim_start; + /** Frames to trim off the end of the DCP */ + int _dcp_trim_end; /** true to create an A/B comparison DCP, where the left half of the image is the video without any filters or post-processing, and the right half has the specified filters and post-processing. diff --git a/src/lib/imagemagick_decoder.cc b/src/lib/imagemagick_decoder.cc index 131eaa500..bad1fb813 100644 --- a/src/lib/imagemagick_decoder.cc +++ b/src/lib/imagemagick_decoder.cc @@ -70,7 +70,12 @@ bool ImageMagickDecoder::pass () { if (_iter == _files.end()) { - return true; + if (!_film->dcp_length() || video_frame() >= _film->dcp_length().get()) { + return true; + } + + repeat_last_video (); + return false; } Magick::Image* magick_image = new Magick::Image (_film->content_path ()); diff --git a/src/lib/j2k_still_encoder.cc b/src/lib/j2k_still_encoder.cc deleted file mode 100644 index 968257691..000000000 --- a/src/lib/j2k_still_encoder.cc +++ /dev/null @@ -1,95 +0,0 @@ -/* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> - - 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. - -*/ - -/** @file src/j2k_still_encoder.cc - * @brief An encoder which writes JPEG2000 files for a single still source image. - */ - -#include <sstream> -#include <stdexcept> -#include <iomanip> -#include <iostream> -#include <boost/filesystem.hpp> -#include <sndfile.h> -#include <openjpeg.h> -#include "j2k_still_encoder.h" -#include "config.h" -#include "options.h" -#include "exceptions.h" -#include "dcp_video_frame.h" -#include "filter.h" -#include "log.h" -#include "imagemagick_decoder.h" -#include "film.h" - -using std::string; -using std::pair; -using boost::shared_ptr; - -J2KStillEncoder::J2KStillEncoder (shared_ptr<const Film> f, shared_ptr<const EncodeOptions> o) - : Encoder (f, o) -{ - -} - -void -J2KStillEncoder::do_process_video (shared_ptr<Image> yuv, shared_ptr<Subtitle> sub) -{ - pair<string, string> const s = Filter::ffmpeg_strings (_film->filters()); - DCPVideoFrame* f = new DCPVideoFrame ( - yuv, sub, _opt->out_size, _opt->padding, _film->subtitle_offset(), _film->subtitle_scale(), _film->scaler(), 0, _film->frames_per_second(), s.second, - Config::instance()->colour_lut_index(), Config::instance()->j2k_bandwidth(), - _film->log() - ); - - if (!boost::filesystem::exists (_opt->frame_out_path (0, false))) { - boost::shared_ptr<EncodedData> e = f->encode_locally (); - e->write (_opt, 0); - } - - string const real = _opt->frame_out_path (0, false); - string const real_hash = _opt->hash_out_path (0, false); - for (int i = 1; i < (_film->still_duration() * _film->frames_per_second()); ++i) { - - if (!boost::filesystem::exists (_opt->frame_out_path (i, false))) { - link (real, _opt->frame_out_path (i, false)); - } - - if (!boost::filesystem::exists (_opt->hash_out_path (i, false))) { - link (real_hash, _opt->hash_out_path (i, false)); - } - - frame_done (); - } -} - -void -J2KStillEncoder::link (string a, string b) const -{ -#ifdef DVDOMATIC_POSIX - int const r = symlink (a.c_str(), b.c_str()); - if (r) { - throw EncodeError ("could not create symlink"); - } -#endif - -#ifdef DVDOMATIC_WINDOWS - boost::filesystem::copy_file (a, b); -#endif -} diff --git a/src/lib/j2k_still_encoder.h b/src/lib/j2k_still_encoder.h deleted file mode 100644 index ad2a5d277..000000000 --- a/src/lib/j2k_still_encoder.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> - - 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. - -*/ - -/** @file src/j2k_still_encoder.h - * @brief An encoder which writes JPEG2000 files for a single still source image. - */ - -#include <list> -#include <vector> -#include "encoder.h" - -class Image; -class Log; -class EncodeOptions; - -/** @class J2KStillEncoder - * @brief An encoder which writes repeated JPEG2000 files from a single decoded input. - */ -class J2KStillEncoder : public Encoder -{ -public: - J2KStillEncoder (boost::shared_ptr<const Film>, boost::shared_ptr<const EncodeOptions>); - -private: - void do_process_video (boost::shared_ptr<Image>, boost::shared_ptr<Subtitle>); - - void link (std::string, std::string) const; -}; diff --git a/src/lib/j2k_video_encoder.cc b/src/lib/j2k_video_encoder.cc deleted file mode 100644 index 0f010b933..000000000 --- a/src/lib/j2k_video_encoder.cc +++ /dev/null @@ -1,259 +0,0 @@ -/* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> - - 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. - -*/ - -/** @file src/j2k_video_encoder.cc - * @brief An encoder which writes JPEG2000 files, where they are video (ie not still). - */ - -#include <sstream> -#include <stdexcept> -#include <iomanip> -#include <iostream> -#include <boost/thread.hpp> -#include <boost/filesystem.hpp> -#include <boost/lexical_cast.hpp> -#include <sndfile.h> -#include <openjpeg.h> -#include "j2k_video_encoder.h" -#include "config.h" -#include "options.h" -#include "exceptions.h" -#include "dcp_video_frame.h" -#include "server.h" -#include "filter.h" -#include "log.h" -#include "cross.h" -#include "film.h" - -using std::string; -using std::stringstream; -using std::list; -using std::vector; -using std::pair; -using std::cout; -using boost::shared_ptr; -using boost::thread; -using boost::lexical_cast; - -J2KVideoEncoder::J2KVideoEncoder (shared_ptr<const Film> f, shared_ptr<const EncodeOptions> o) - : Encoder (f, o) - , _process_end (false) -{ - -} - -J2KVideoEncoder::~J2KVideoEncoder () -{ - terminate_worker_threads (); -} - -void -J2KVideoEncoder::terminate_worker_threads () -{ - boost::mutex::scoped_lock lock (_worker_mutex); - _process_end = true; - _worker_condition.notify_all (); - lock.unlock (); - - for (list<boost::thread *>::iterator i = _worker_threads.begin(); i != _worker_threads.end(); ++i) { - (*i)->join (); - delete *i; - } -} - -void -J2KVideoEncoder::do_process_video (shared_ptr<Image> yuv, shared_ptr<Subtitle> sub) -{ - boost::mutex::scoped_lock lock (_worker_mutex); - - /* Wait until the queue has gone down a bit */ - while (_queue.size() >= _worker_threads.size() * 2 && !_process_end) { - TIMING ("decoder sleeps with queue of %1", _queue.size()); - _worker_condition.wait (lock); - TIMING ("decoder wakes with queue of %1", _queue.size()); - } - - if (_process_end) { - return; - } - - /* Only do the processing if we don't already have a file for this frame */ - if (!boost::filesystem::exists (_opt->frame_out_path (_video_frame, false))) { - pair<string, string> const s = Filter::ffmpeg_strings (_film->filters()); - TIMING ("adding to queue of %1", _queue.size ()); - _queue.push_back (boost::shared_ptr<DCPVideoFrame> ( - new DCPVideoFrame ( - yuv, sub, _opt->out_size, _opt->padding, _film->subtitle_offset(), _film->subtitle_scale(), - _film->scaler(), _video_frame, _film->frames_per_second(), s.second, - Config::instance()->colour_lut_index (), Config::instance()->j2k_bandwidth (), - _film->log() - ) - )); - - _worker_condition.notify_all (); - } else { - frame_skipped (); - } -} - -void -J2KVideoEncoder::encoder_thread (ServerDescription* server) -{ - /* Number of seconds that we currently wait between attempts - to connect to the server; not relevant for localhost - encodings. - */ - int remote_backoff = 0; - - while (1) { - - TIMING ("encoder thread %1 sleeps", boost::this_thread::get_id()); - boost::mutex::scoped_lock lock (_worker_mutex); - while (_queue.empty () && !_process_end) { - _worker_condition.wait (lock); - } - - if (_process_end) { - return; - } - - TIMING ("encoder thread %1 wakes with queue of %2", boost::this_thread::get_id(), _queue.size()); - boost::shared_ptr<DCPVideoFrame> vf = _queue.front (); - _film->log()->log (String::compose ("Encoder thread %1 pops frame %2 from queue", boost::this_thread::get_id(), vf->frame()), Log::VERBOSE); - _queue.pop_front (); - - lock.unlock (); - - shared_ptr<EncodedData> encoded; - - if (server) { - try { - encoded = vf->encode_remotely (server); - - if (remote_backoff > 0) { - _film->log()->log (String::compose ("%1 was lost, but now she is found; removing backoff", server->host_name ())); - } - - /* This job succeeded, so remove any backoff */ - remote_backoff = 0; - - } catch (std::exception& e) { - if (remote_backoff < 60) { - /* back off more */ - remote_backoff += 10; - } - _film->log()->log ( - String::compose ( - "Remote encode of %1 on %2 failed (%3); thread sleeping for %4s", - vf->frame(), 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()); - encoded = vf->encode_locally (); - TIMING ("encoder thread %1 finishes local encode of %2", boost::this_thread::get_id(), vf->frame()); - } catch (std::exception& e) { - _film->log()->log (String::compose ("Local encode failed (%1)", e.what ())); - } - } - - if (encoded) { - encoded->write (_opt, vf->frame ()); - frame_done (); - } else { - lock.lock (); - _film->log()->log ( - String::compose ("Encoder thread %1 pushes frame %2 back onto queue after failure", boost::this_thread::get_id(), vf->frame()) - ); - _queue.push_front (vf); - lock.unlock (); - } - - if (remote_backoff > 0) { - dvdomatic_sleep (remote_backoff); - } - - lock.lock (); - _worker_condition.notify_all (); - } -} - -void -J2KVideoEncoder::process_begin () -{ - Encoder::process_begin (); - - for (int i = 0; i < Config::instance()->num_local_encoding_threads (); ++i) { - _worker_threads.push_back (new boost::thread (boost::bind (&J2KVideoEncoder::encoder_thread, this, (ServerDescription *) 0))); - } - - vector<ServerDescription*> servers = Config::instance()->servers (); - - for (vector<ServerDescription*>::iterator i = servers.begin(); i != servers.end(); ++i) { - for (int j = 0; j < (*i)->threads (); ++j) { - _worker_threads.push_back (new boost::thread (boost::bind (&J2KVideoEncoder::encoder_thread, this, *i))); - } - } -} - -void -J2KVideoEncoder::process_end () -{ - Encoder::process_end (); - - boost::mutex::scoped_lock lock (_worker_mutex); - - _film->log()->log ("Clearing queue of " + lexical_cast<string> (_queue.size ())); - - /* Keep waking workers until the queue is empty */ - while (!_queue.empty ()) { - _film->log()->log ("Waking with " + lexical_cast<string> (_queue.size ()), Log::VERBOSE); - _worker_condition.notify_all (); - _worker_condition.wait (lock); - } - - lock.unlock (); - - terminate_worker_threads (); - - _film->log()->log ("Mopping up " + lexical_cast<string> (_queue.size())); - - /* The following sequence of events can occur in the above code: - 1. a remote worker takes the last image off the queue - 2. the loop above terminates - 3. the remote worker fails to encode the image and puts it back on the queue - 4. the remote worker is then terminated by terminate_worker_threads - - So just mop up anything left in the queue here. - */ - - for (list<shared_ptr<DCPVideoFrame> >::iterator i = _queue.begin(); i != _queue.end(); ++i) { - _film->log()->log (String::compose ("Encode left-over frame %1", (*i)->frame ())); - try { - shared_ptr<EncodedData> e = (*i)->encode_locally (); - e->write (_opt, (*i)->frame ()); - frame_done (); - } catch (std::exception& e) { - _film->log()->log (String::compose ("Local encode failed (%1)", e.what ())); - } - } -} - diff --git a/src/lib/j2k_video_encoder.h b/src/lib/j2k_video_encoder.h deleted file mode 100644 index 604110576..000000000 --- a/src/lib/j2k_video_encoder.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - Copyright (C) 2012 Carl Hetherington <cth@carlh.net> - - 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. - -*/ - -/** @file src/j2k_video_encoder.h - * @brief An encoder which writes JPEG2000 files, where they are video (ie not still). - */ - -#include <list> -#include <vector> -#include <boost/thread/condition.hpp> -#include <boost/thread/mutex.hpp> -#include <boost/thread.hpp> -#include "encoder.h" - -class ServerDescription; -class DCPVideoFrame; -class Image; -class Log; -class Subtitle; - -/** @class J2KVideoEncoder - * @brief An encoder which writes JPEG2000 files, where they are video (ie not still). - */ -class J2KVideoEncoder : public Encoder -{ -public: - J2KVideoEncoder (boost::shared_ptr<const Film>, boost::shared_ptr<const EncodeOptions>); - ~J2KVideoEncoder (); - - void process_begin (); - void process_end (); - -private: - - void do_process_video (boost::shared_ptr<Image>, boost::shared_ptr<Subtitle>); - - void encoder_thread (ServerDescription *); - void terminate_worker_threads (); - - bool _process_end; - std::list<boost::shared_ptr<DCPVideoFrame> > _queue; - std::list<boost::thread *> _worker_threads; - mutable boost::mutex _worker_mutex; - boost::condition _worker_condition; -}; diff --git a/src/lib/matcher.cc b/src/lib/matcher.cc index 2dd36c11e..2b7a080fc 100644 --- a/src/lib/matcher.cc +++ b/src/lib/matcher.cc @@ -35,9 +35,9 @@ Matcher::Matcher (Log* log, int sample_rate, float frames_per_second) } void -Matcher::process_video (boost::shared_ptr<Image> i, boost::shared_ptr<Subtitle> s) +Matcher::process_video (boost::shared_ptr<Image> i, bool same, boost::shared_ptr<Subtitle> s) { - Video (i, s); + Video (i, same, s); _video_frames++; _pixel_format = i->pixel_format (); @@ -84,7 +84,7 @@ Matcher::process_end () shared_ptr<Image> black (new SimpleImage (_pixel_format.get(), _size.get(), false)); black->make_black (); for (int i = 0; i < black_video_frames; ++i) { - Video (black, shared_ptr<Subtitle>()); + Video (black, i != 0, shared_ptr<Subtitle>()); } /* Now recompute our check value */ diff --git a/src/lib/matcher.h b/src/lib/matcher.h index 03efd5cc0..9bd30fe62 100644 --- a/src/lib/matcher.h +++ b/src/lib/matcher.h @@ -24,7 +24,7 @@ class Matcher : public AudioVideoProcessor { public: Matcher (Log* log, int sample_rate, float frames_per_second); - void process_video (boost::shared_ptr<Image> i, boost::shared_ptr<Subtitle> s); + void process_video (boost::shared_ptr<Image> i, bool, boost::shared_ptr<Subtitle> s); void process_audio (boost::shared_ptr<AudioBuffers>); void process_end (); diff --git a/src/lib/options.h b/src/lib/options.h index 10cdfa8cd..55b066a2d 100644 --- a/src/lib/options.h +++ b/src/lib/options.h @@ -93,9 +93,9 @@ public: Size out_size; ///< size of output images int padding; ///< number of pixels of padding (in terms of the output size) each side of the image - /** Range of video frames to decode */ - boost::optional<std::pair<SourceFrame, SourceFrame> > video_range; - /** Range of audio frames to decode */ + /** Range of video frames to encode (in DCP frames) */ + boost::optional<std::pair<int, int> > video_range; + /** Range of audio frames to decode (in the DCP's sampling rate) */ boost::optional<std::pair<int64_t, int64_t> > audio_range; /** Skip frames such that we don't decode any frame where (index % decode_video_skip) != 0; e.g. diff --git a/src/lib/transcode_job.cc b/src/lib/transcode_job.cc index b429c4646..dfb9b1071 100644 --- a/src/lib/transcode_job.cc +++ b/src/lib/transcode_job.cc @@ -28,7 +28,6 @@ #include "format.h" #include "transcoder.h" #include "log.h" -#include "encoder_factory.h" #include "encoder.h" using std::string; @@ -63,7 +62,7 @@ TranscodeJob::run () _film->log()->log ("Transcode job starting"); _film->log()->log (String::compose ("Audio delay is %1ms", _film->audio_delay())); - _encoder = encoder_factory (_film, _encode_opt); + _encoder.reset (new Encoder (_film, _encode_opt)); Transcoder w (_film, _decode_opt, this, _encoder); w.go (); set_progress (1); diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc index e723610b3..e0a7576ee 100644 --- a/src/lib/video_decoder.cc +++ b/src/lib/video_decoder.cc @@ -49,7 +49,7 @@ VideoDecoder::emit_video (shared_ptr<Image> image, double t) sub = _timed_subtitle->subtitle (); } - signal_video (image, sub); + signal_video (image, false, sub); _last_source_time = t; } @@ -61,14 +61,14 @@ VideoDecoder::repeat_last_video () _last_image->make_black (); } - signal_video (_last_image, _last_subtitle); + signal_video (_last_image, true, _last_subtitle); } void -VideoDecoder::signal_video (shared_ptr<Image> image, shared_ptr<Subtitle> sub) +VideoDecoder::signal_video (shared_ptr<Image> image, bool same, shared_ptr<Subtitle> sub) { TIMING ("Decoder emits %1", _video_frame); - Video (image, sub); + Video (image, same, sub); ++_video_frame; _last_image = image; diff --git a/src/lib/video_decoder.h b/src/lib/video_decoder.h index 97bbb0e48..7726d2057 100644 --- a/src/lib/video_decoder.h +++ b/src/lib/video_decoder.h @@ -75,7 +75,7 @@ protected: std::vector<boost::shared_ptr<SubtitleStream> > _subtitle_streams; private: - void signal_video (boost::shared_ptr<Image>, boost::shared_ptr<Subtitle>); + void signal_video (boost::shared_ptr<Image>, bool, boost::shared_ptr<Subtitle>); int _video_frame; double _last_source_time; diff --git a/src/lib/video_sink.h b/src/lib/video_sink.h index 78500c643..7c128cf73 100644 --- a/src/lib/video_sink.h +++ b/src/lib/video_sink.h @@ -31,9 +31,10 @@ class VideoSink public: /** Call with a frame of video. * @param i Video frame image. + * @param same true if i is the same as last time we were called. * @param s A subtitle that should be on this frame, or 0. */ - virtual void process_video (boost::shared_ptr<Image> i, boost::shared_ptr<Subtitle> s) = 0; + virtual void process_video (boost::shared_ptr<Image> i, bool same, boost::shared_ptr<Subtitle> s) = 0; }; #endif diff --git a/src/lib/video_source.cc b/src/lib/video_source.cc index c6bd4a5cf..56742e2b4 100644 --- a/src/lib/video_source.cc +++ b/src/lib/video_source.cc @@ -26,5 +26,5 @@ using boost::bind; void VideoSource::connect_video (shared_ptr<VideoSink> s) { - Video.connect (bind (&VideoSink::process_video, s, _1, _2)); + Video.connect (bind (&VideoSink::process_video, s, _1, _2, _3)); } diff --git a/src/lib/video_source.h b/src/lib/video_source.h index d53589e2e..893629160 100644 --- a/src/lib/video_source.h +++ b/src/lib/video_source.h @@ -41,9 +41,10 @@ public: /** Emitted when a video frame is ready. * First parameter is the video image. - * Second parameter is either 0 or a subtitle that should be on this frame. + * Second parameter is true if the image is the same as the last one that was emitted. + * Third parameter is either 0 or a subtitle that should be on this frame. */ - boost::signals2::signal<void (boost::shared_ptr<Image>, boost::shared_ptr<Subtitle>)> Video; + boost::signals2::signal<void (boost::shared_ptr<Image>, bool, boost::shared_ptr<Subtitle>)> Video; void connect_video (boost::shared_ptr<VideoSink>); }; diff --git a/src/lib/wscript b/src/lib/wscript index d13de4f3b..b2b639f06 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -25,7 +25,6 @@ def build(bld): delay_line.cc dolby_cp750.cc encoder.cc - encoder_factory.cc examine_content_job.cc external_audio_decoder.cc filter_graph.cc @@ -37,8 +36,6 @@ def build(bld): gain.cc image.cc imagemagick_decoder.cc - j2k_still_encoder.cc - j2k_video_encoder.cc job.cc job_manager.cc log.cc |
