From: Carl Hetherington Date: Wed, 10 May 2017 10:54:19 +0000 (+0100) Subject: Rename some classes. X-Git-Tag: v2.11.4~28 X-Git-Url: https://main.carlh.net/gitweb/?p=dcpomatic.git;a=commitdiff_plain;h=d8ea1796f34ff894b148a0af78c0a547e0496ee1 Rename some classes. --- diff --git a/src/lib/dcp_encoder.cc b/src/lib/dcp_encoder.cc new file mode 100644 index 000000000..522f02947 --- /dev/null +++ b/src/lib/dcp_encoder.cc @@ -0,0 +1,137 @@ +/* + Copyright (C) 2012-2017 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + +/** @file src/dcp_encoder.cc + * @brief A class which takes a Film and some Options, then uses those to encode the film into a DCP. + * + * A decoder is selected according to the content type, and the encoder can be specified + * as a parameter to the constructor. + */ + +#include "dcp_encoder.h" +#include "j2k_encoder.h" +#include "film.h" +#include "video_decoder.h" +#include "audio_decoder.h" +#include "player.h" +#include "job.h" +#include "writer.h" +#include "compose.hpp" +#include "referenced_reel_asset.h" +#include "subtitle_content.h" +#include "player_video.h" +#include +#include +#include + +#include "i18n.h" + +using std::string; +using std::cout; +using std::list; +using boost::shared_ptr; +using boost::weak_ptr; +using boost::dynamic_pointer_cast; + +/** Construct a DCP encoder. + * @param film Film that we are encoding. + * @param job Job that this encoder is being used in. + */ +DCPEncoder::DCPEncoder (shared_ptr film, weak_ptr job) + : Encoder (film, job) + , _writer (new Writer (film, job)) + , _j2k_encoder (new J2KEncoder (film, _writer)) + , _finishing (false) + , _non_burnt_subtitles (false) +{ + BOOST_FOREACH (shared_ptr c, _film->content ()) { + if (c->subtitle && c->subtitle->use() && !c->subtitle->burn()) { + _non_burnt_subtitles = true; + } + } +} + +void +DCPEncoder::go () +{ + _writer->start (); + _j2k_encoder->begin (); + + { + shared_ptr job = _job.lock (); + DCPOMATIC_ASSERT (job); + job->sub (_("Encoding")); + } + + if (_non_burnt_subtitles) { + _writer->write (_player->get_subtitle_fonts ()); + } + + while (!_player->pass ()) {} + + BOOST_FOREACH (ReferencedReelAsset i, _player->get_reel_assets ()) { + _writer->write (i); + } + + _finishing = true; + _j2k_encoder->end (); + _writer->finish (); +} + +void +DCPEncoder::video (shared_ptr data, DCPTime time) +{ + if (!_film->three_d() && data->eyes() == EYES_LEFT) { + /* Use left-eye images for both eyes */ + data->set_eyes (EYES_BOTH); + } + + _j2k_encoder->encode (data, time); +} + +void +DCPEncoder::audio (shared_ptr data, DCPTime time) +{ + _writer->write (data); + + shared_ptr job = _job.lock (); + DCPOMATIC_ASSERT (job); + job->set_progress (float(time.get()) / _film->length().get()); +} + +void +DCPEncoder::subtitle (PlayerSubtitles data, DCPTimePeriod period) +{ + if (_non_burnt_subtitles) { + _writer->write (data, period); + } +} + +float +DCPEncoder::current_rate () const +{ + return _j2k_encoder->current_encoding_rate (); +} + +Frame +DCPEncoder::frames_done () const +{ + return _j2k_encoder->video_frames_enqueued (); +} diff --git a/src/lib/dcp_encoder.h b/src/lib/dcp_encoder.h new file mode 100644 index 000000000..387921972 --- /dev/null +++ b/src/lib/dcp_encoder.h @@ -0,0 +1,60 @@ +/* + Copyright (C) 2012-2017 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + +#include "types.h" +#include "player_subtitles.h" +#include "encoder.h" +#include + +class Film; +class J2KEncoder; +class Player; +class Writer; +class Job; +class PlayerVideo; +class AudioBuffers; + +/** @class DCPEncoder */ +class DCPEncoder : public Encoder +{ +public: + DCPEncoder (boost::shared_ptr film, boost::weak_ptr job); + + void go (); + + float current_rate () const; + Frame frames_done () const; + + /** @return true if we are in the process of calling Encoder::process_end */ + bool finishing () const { + return _finishing; + } + +private: + + void video (boost::shared_ptr, DCPTime); + void audio (boost::shared_ptr, DCPTime); + void subtitle (PlayerSubtitles, DCPTimePeriod); + + boost::shared_ptr _writer; + boost::shared_ptr _j2k_encoder; + bool _finishing; + bool _non_burnt_subtitles; +}; diff --git a/src/lib/dcp_transcoder.cc b/src/lib/dcp_transcoder.cc deleted file mode 100644 index aa3ef73b5..000000000 --- a/src/lib/dcp_transcoder.cc +++ /dev/null @@ -1,137 +0,0 @@ -/* - Copyright (C) 2012-2016 Carl Hetherington - - This file is part of DCP-o-matic. - - DCP-o-matic is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - DCP-o-matic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with DCP-o-matic. If not, see . - -*/ - -/** @file src/dcp_transcoder.cc - * @brief A class which takes a Film and some Options, then uses those to transcode the film into a DCP. - * - * A decoder is selected according to the content type, and the encoder can be specified - * as a parameter to the constructor. - */ - -#include "dcp_transcoder.h" -#include "encoder.h" -#include "film.h" -#include "video_decoder.h" -#include "audio_decoder.h" -#include "player.h" -#include "job.h" -#include "writer.h" -#include "compose.hpp" -#include "referenced_reel_asset.h" -#include "subtitle_content.h" -#include "player_video.h" -#include -#include -#include - -#include "i18n.h" - -using std::string; -using std::cout; -using std::list; -using boost::shared_ptr; -using boost::weak_ptr; -using boost::dynamic_pointer_cast; - -/** Construct a DCP transcoder. - * @param film Film that we are transcoding. - * @param job Job that this transcoder is being used in. - */ -DCPTranscoder::DCPTranscoder (shared_ptr film, weak_ptr job) - : Transcoder (film, job) - , _writer (new Writer (film, job)) - , _encoder (new Encoder (film, _writer)) - , _finishing (false) - , _non_burnt_subtitles (false) -{ - BOOST_FOREACH (shared_ptr c, _film->content ()) { - if (c->subtitle && c->subtitle->use() && !c->subtitle->burn()) { - _non_burnt_subtitles = true; - } - } -} - -void -DCPTranscoder::go () -{ - _writer->start (); - _encoder->begin (); - - { - shared_ptr job = _job.lock (); - DCPOMATIC_ASSERT (job); - job->sub (_("Encoding")); - } - - if (_non_burnt_subtitles) { - _writer->write (_player->get_subtitle_fonts ()); - } - - while (!_player->pass ()) {} - - BOOST_FOREACH (ReferencedReelAsset i, _player->get_reel_assets ()) { - _writer->write (i); - } - - _finishing = true; - _encoder->end (); - _writer->finish (); -} - -void -DCPTranscoder::video (shared_ptr data, DCPTime time) -{ - if (!_film->three_d() && data->eyes() == EYES_LEFT) { - /* Use left-eye images for both eyes */ - data->set_eyes (EYES_BOTH); - } - - _encoder->encode (data, time); -} - -void -DCPTranscoder::audio (shared_ptr data, DCPTime time) -{ - _writer->write (data); - - shared_ptr job = _job.lock (); - DCPOMATIC_ASSERT (job); - job->set_progress (float(time.get()) / _film->length().get()); -} - -void -DCPTranscoder::subtitle (PlayerSubtitles data, DCPTimePeriod period) -{ - if (_non_burnt_subtitles) { - _writer->write (data, period); - } -} - -float -DCPTranscoder::current_rate () const -{ - return _encoder->current_encoding_rate (); -} - -Frame -DCPTranscoder::frames_done () const -{ - return _encoder->video_frames_enqueued (); -} diff --git a/src/lib/dcp_transcoder.h b/src/lib/dcp_transcoder.h deleted file mode 100644 index 724565101..000000000 --- a/src/lib/dcp_transcoder.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - Copyright (C) 2012 Carl Hetherington - - This file is part of DCP-o-matic. - - DCP-o-matic is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - DCP-o-matic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with DCP-o-matic. If not, see . - -*/ - -#include "types.h" -#include "player_subtitles.h" -#include "transcoder.h" -#include - -class Film; -class Encoder; -class Player; -class Writer; -class Job; -class PlayerVideo; -class AudioBuffers; - -/** @class DCPTranscoder */ -class DCPTranscoder : public Transcoder -{ -public: - DCPTranscoder (boost::shared_ptr film, boost::weak_ptr job); - - void go (); - - float current_rate () const; - Frame frames_done () const; - - /** @return true if we are in the process of calling Encoder::process_end */ - bool finishing () const { - return _finishing; - } - -private: - - void video (boost::shared_ptr, DCPTime); - void audio (boost::shared_ptr, DCPTime); - void subtitle (PlayerSubtitles, DCPTimePeriod); - - boost::shared_ptr _writer; - boost::shared_ptr _encoder; - bool _finishing; - bool _non_burnt_subtitles; -}; diff --git a/src/lib/encoder.cc b/src/lib/encoder.cc index 2d916d47e..3af80efa5 100644 --- a/src/lib/encoder.cc +++ b/src/lib/encoder.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2015 Carl Hetherington + Copyright (C) 2012-2017 Carl Hetherington This file is part of DCP-o-matic. @@ -18,403 +18,32 @@ */ -/** @file src/encoder.h - * @brief Parent class for classes which can encode video and audio frames. +/** @file src/encoder.cc + * @brief A class which takes a Film and some Options, then uses those to encode the film + * into some output format. + * + * A decoder is selected according to the content type, and the encoder can be specified + * as a parameter to the constructor. */ #include "encoder.h" -#include "util.h" -#include "film.h" -#include "log.h" -#include "config.h" -#include "dcp_video.h" -#include "cross.h" -#include "writer.h" -#include "encode_server_finder.h" #include "player.h" -#include "player_video.h" -#include "encode_server_description.h" -#include "compose.hpp" -#include -#include -#include #include "i18n.h" -#define LOG_GENERAL(...) _film->log()->log (String::compose (__VA_ARGS__), LogEntry::TYPE_GENERAL); -#define LOG_GENERAL_NC(...) _film->log()->log (__VA_ARGS__, LogEntry::TYPE_GENERAL); -#define LOG_ERROR(...) _film->log()->log (String::compose (__VA_ARGS__), LogEntry::TYPE_ERROR); -#define LOG_TIMING(...) _film->log()->log (String::compose (__VA_ARGS__), LogEntry::TYPE_TIMING); -#define LOG_DEBUG_ENCODE(...) _film->log()->log (String::compose (__VA_ARGS__), LogEntry::TYPE_DEBUG_ENCODE); - -using std::list; -using std::cout; -using boost::shared_ptr; using boost::weak_ptr; -using boost::optional; -using dcp::Data; +using boost::shared_ptr; -/** @param film Film that we are encoding. - * @param writer Writer that we are using. +/** Construct an encoder. + * @param film Film that we are encoding. + * @param job Job that this encoder is being used in. */ -Encoder::Encoder (shared_ptr film, shared_ptr writer) +Encoder::Encoder (shared_ptr film, weak_ptr job) : _film (film) - , _history (200) - , _writer (writer) -{ - servers_list_changed (); -} - -Encoder::~Encoder () -{ - try { - terminate_threads (); - } catch (...) { - /* Destructors must not throw exceptions; anything bad - happening now is too late to worry about anyway, - I think. - */ - } -} - -void -Encoder::begin () -{ - weak_ptr wp = shared_from_this (); - _server_found_connection = EncodeServerFinder::instance()->ServersListChanged.connect ( - boost::bind (&Encoder::call_servers_list_changed, wp) - ); -} - -/* We don't want the servers-list-changed callback trying to do things - during destruction of Encoder, and I think this is the neatest way - to achieve that. -*/ -void -Encoder::call_servers_list_changed (weak_ptr encoder) -{ - shared_ptr e = encoder.lock (); - if (e) { - e->servers_list_changed (); - } -} - -void -Encoder::end () -{ - boost::mutex::scoped_lock lock (_queue_mutex); - - LOG_GENERAL (N_("Clearing queue of %1"), _queue.size ()); - - /* Keep waking workers until the queue is empty */ - while (!_queue.empty ()) { - rethrow (); - _empty_condition.notify_all (); - _full_condition.wait (lock); - } - - lock.unlock (); - - LOG_GENERAL_NC (N_("Terminating encoder threads")); - - terminate_threads (); - - LOG_GENERAL (N_("Mopping up %1"), _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_threads - - So just mop up anything left in the queue here. - */ - - for (list >::iterator i = _queue.begin(); i != _queue.end(); ++i) { - LOG_GENERAL (N_("Encode left-over frame %1"), (*i)->index ()); - try { - _writer->write ( - (*i)->encode_locally (boost::bind (&Log::dcp_log, _film->log().get(), _1, _2)), - (*i)->index (), - (*i)->eyes () - ); - frame_done (); - } catch (std::exception& e) { - LOG_ERROR (N_("Local encode failed (%1)"), e.what ()); - } - } -} - -/** @return an estimate of the current number of frames we are encoding per second, - * or 0 if not known. - */ -float -Encoder::current_encoding_rate () const -{ - return _history.rate (); -} - -/** @return Number of video frames that have been queued for encoding */ -int -Encoder::video_frames_enqueued () const -{ - if (!_last_player_video_time) { - return 0; - } - - return _last_player_video_time->frames_floor (_film->video_frame_rate ()); -} - -/** Should be called when a frame has been encoded successfully */ -void -Encoder::frame_done () -{ - _history.event (); -} - -/** Called to request encoding of the next video frame in the DCP. This is called in order, - * so each time the supplied frame is the one after the previous one. - * pv represents one video frame, and could be empty if there is nothing to encode - * for this DCP frame. - * - * @param pv PlayerVideo to encode. - * @param time Time of \p pv within the DCP. - */ -void -Encoder::encode (shared_ptr pv, DCPTime time) -{ - _waker.nudge (); - - size_t threads = 0; - { - boost::mutex::scoped_lock threads_lock (_threads_mutex); - threads = _threads.size (); - } - - boost::mutex::scoped_lock queue_lock (_queue_mutex); - - /* Wait until the queue has gone down a bit. Allow one thing in the queue even - when there are no threads. - */ - while (_queue.size() >= (threads * 2) + 1) { - LOG_TIMING ("decoder-sleep queue=%1 threads=%2", _queue.size(), threads); - _full_condition.wait (queue_lock); - LOG_TIMING ("decoder-wake queue=%1 threads=%2", _queue.size(), threads); - } - - _writer->rethrow (); - /* Re-throw any exception raised by one of our threads. If more - than one has thrown an exception, only one will be rethrown, I think; - but then, if that happens something has gone badly wrong. - */ - rethrow (); - - Frame const position = time.frames_floor(_film->video_frame_rate()); - - if (_writer->can_fake_write (position)) { - /* We can fake-write this frame */ - LOG_DEBUG_ENCODE("Frame @ %1 FAKE", to_string(time)); - _writer->fake_write (position, pv->eyes ()); - frame_done (); - } else if (pv->has_j2k ()) { - LOG_DEBUG_ENCODE("Frame @ %1 J2K", to_string(time)); - /* This frame already has JPEG2000 data, so just write it */ - _writer->write (pv->j2k(), position, pv->eyes ()); - } else if (_last_player_video && _writer->can_repeat(position) && pv->same (_last_player_video)) { - LOG_DEBUG_ENCODE("Frame @ %1 REPEAT", to_string(time)); - _writer->repeat (position, pv->eyes ()); - } else { - LOG_DEBUG_ENCODE("Frame @ %1 ENCODE", to_string(time)); - /* Queue this new frame for encoding */ - LOG_TIMING ("add-frame-to-queue queue=%1", _queue.size ()); - _queue.push_back (shared_ptr ( - new DCPVideo ( - pv, - position, - _film->video_frame_rate(), - _film->j2k_bandwidth(), - _film->resolution(), - _film->log() - ) - )); - - /* The queue might not be empty any more, so notify anything which is - waiting on that. - */ - _empty_condition.notify_all (); - } - - _last_player_video = pv; - _last_player_video_time = time; -} - -void -Encoder::terminate_threads () -{ - boost::mutex::scoped_lock threads_lock (_threads_mutex); - - int n = 0; - for (list::iterator i = _threads.begin(); i != _threads.end(); ++i) { - LOG_GENERAL ("Terminating thread %1 of %2", n + 1, _threads.size ()); - (*i)->interrupt (); - DCPOMATIC_ASSERT ((*i)->joinable ()); - try { - (*i)->join (); - } catch (boost::thread_interrupted& e) { - /* This is to be expected */ - } - delete *i; - LOG_GENERAL_NC ("Thread terminated"); - ++n; - } - - _threads.clear (); -} - -void -Encoder::encoder_thread (optional server) -try + , _job (job) + , _player (new Player (film, film->playlist ())) { - if (server) { - LOG_TIMING ("start-encoder-thread thread=%1 server=%2", thread_id (), server->host_name ()); - } else { - LOG_TIMING ("start-encoder-thread thread=%1 server=localhost", thread_id ()); - } - - /* Number of seconds that we currently wait between attempts - to connect to the server; not relevant for localhost - encodings. - */ - int remote_backoff = 0; - - while (true) { - - LOG_TIMING ("encoder-sleep thread=%1", thread_id ()); - boost::mutex::scoped_lock lock (_queue_mutex); - while (_queue.empty ()) { - _empty_condition.wait (lock); - } - - LOG_TIMING ("encoder-wake thread=%1 queue=%2", thread_id(), _queue.size()); - shared_ptr vf = _queue.front (); - - /* We're about to commit to either encoding this frame or putting it back onto the queue, - so we must not be interrupted until one or other of these things have happened. This - block has thread interruption disabled. - */ - { - boost::this_thread::disable_interruption dis; - - LOG_TIMING ("encoder-pop thread=%1 frame=%2 eyes=%3", thread_id(), vf->index(), (int) vf->eyes ()); - _queue.pop_front (); - - lock.unlock (); - - optional encoded; - - /* We need to encode this input */ - if (server) { - try { - encoded = vf->encode_remotely (server.get ()); - - if (remote_backoff > 0) { - LOG_GENERAL ("%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; - } - LOG_ERROR ( - N_("Remote encode of %1 on %2 failed (%3); thread sleeping for %4s"), - vf->index(), server->host_name(), e.what(), remote_backoff - ); - } - - } else { - try { - LOG_TIMING ("start-local-encode thread=%1 frame=%2", thread_id(), vf->index()); - encoded = vf->encode_locally (boost::bind (&Log::dcp_log, _film->log().get(), _1, _2)); - LOG_TIMING ("finish-local-encode thread=%1 frame=%2", thread_id(), vf->index()); - } catch (std::exception& e) { - /* This is very bad, so don't cope with it, just pass it on */ - LOG_ERROR (N_("Local encode failed (%1)"), e.what ()); - throw; - } - } - - if (encoded) { - _writer->write (encoded.get(), vf->index (), vf->eyes ()); - frame_done (); - } else { - lock.lock (); - LOG_GENERAL (N_("[%1] Encoder thread pushes frame %2 back onto queue after failure"), thread_id(), vf->index()); - _queue.push_front (vf); - lock.unlock (); - } - } - - if (remote_backoff > 0) { - boost::this_thread::sleep (boost::posix_time::seconds (remote_backoff)); - } - - /* The queue might not be full any more, so notify anything that is waiting on that */ - lock.lock (); - _full_condition.notify_all (); - } -} -catch (boost::thread_interrupted& e) { - /* Ignore these and just stop the thread */ - _full_condition.notify_all (); -} -catch (...) -{ - store_current (); - /* Wake anything waiting on _full_condition so it can see the exception */ - _full_condition.notify_all (); -} - -void -Encoder::servers_list_changed () -{ - terminate_threads (); - - /* XXX: could re-use threads */ - - boost::mutex::scoped_lock lm (_threads_mutex); - -#ifdef BOOST_THREAD_PLATFORM_WIN32 - OSVERSIONINFO info; - info.dwOSVersionInfoSize = sizeof (OSVERSIONINFO); - GetVersionEx (&info); - bool const windows_xp = (info.dwMajorVersion == 5 && info.dwMinorVersion == 1); - if (windows_xp) { - LOG_GENERAL_NC (N_("Setting thread affinity for Windows XP")); - } -#endif - - if (!Config::instance()->only_servers_encode ()) { - for (int i = 0; i < Config::instance()->master_encoding_threads (); ++i) { - boost::thread* t = new boost::thread (boost::bind (&Encoder::encoder_thread, this, optional ())); - _threads.push_back (t); -#ifdef BOOST_THREAD_PLATFORM_WIN32 - if (windows_xp) { - SetThreadAffinityMask (t->native_handle(), 1 << i); - } -#endif - } - } - - BOOST_FOREACH (EncodeServerDescription i, EncodeServerFinder::instance()->servers ()) { - LOG_GENERAL (N_("Adding %1 worker threads for remote %2"), i.threads(), i.host_name ()); - for (int j = 0; j < i.threads(); ++j) { - _threads.push_back (new boost::thread (boost::bind (&Encoder::encoder_thread, this, i))); - } - } - - _writer->set_encoder_threads (_threads.size ()); + _player->Video.connect (bind (&Encoder::video, this, _1, _2)); + _player->Audio.connect (bind (&Encoder::audio, this, _1, _2)); + _player->Subtitle.connect (bind (&Encoder::subtitle, this, _1, _2)); } diff --git a/src/lib/encoder.h b/src/lib/encoder.h index 7e9bd497a..79ad0ab44 100644 --- a/src/lib/encoder.h +++ b/src/lib/encoder.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2016 Carl Hetherington + Copyright (C) 2012-2017 Carl Hetherington This file is part of DCP-o-matic. @@ -21,89 +21,40 @@ #ifndef DCPOMATIC_ENCODER_H #define DCPOMATIC_ENCODER_H -/** @file src/encoder.h - * @brief Encoder class. - */ - -#include "util.h" -#include "cross.h" -#include "event_history.h" -#include "exception_store.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "types.h" +#include "player_subtitles.h" +#include class Film; -class EncodeServerDescription; -class DCPVideo; -class Writer; +class Encoder; +class Player; class Job; class PlayerVideo; +class AudioBuffers; -/** @class Encoder - * @brief Class to manage encoding to JPEG2000. - * - * This class keeps a queue of frames to be encoded and distributes - * the work around threads and encoding servers. - */ - -class Encoder : public boost::noncopyable, public ExceptionStore, public boost::enable_shared_from_this +/** @class Encoder */ +class Encoder : public boost::noncopyable { public: - Encoder (boost::shared_ptr film, boost::shared_ptr writer); - ~Encoder (); - - /** Called to indicate that a processing run is about to begin */ - void begin (); - - /** Called to pass a bit of video to be encoded as the next DCP frame */ - void encode (boost::shared_ptr pv, DCPTime time); - - /** Called when a processing run has finished */ - void end (); - - float current_encoding_rate () const; - int video_frames_enqueued () const; - - void servers_list_changed (); + Encoder (boost::shared_ptr film, boost::weak_ptr job); + virtual ~Encoder () {} -private: + virtual void go () = 0; - static void call_servers_list_changed (boost::weak_ptr encoder); + /** @return the current frame rate over the last short while */ + virtual float current_rate () const = 0; + /** @return the number of frames that are done */ + virtual Frame frames_done () const = 0; + virtual bool finishing () const = 0; - void frame_done (); +protected: + virtual void video (boost::shared_ptr, DCPTime) = 0; + virtual void audio (boost::shared_ptr, DCPTime) = 0; + virtual void subtitle (PlayerSubtitles, DCPTimePeriod) = 0; - void encoder_thread (boost::optional); - void terminate_threads (); - - /** Film that we are encoding */ boost::shared_ptr _film; - - EventHistory _history; - - /** Mutex for _threads */ - mutable boost::mutex _threads_mutex; - std::list _threads; - mutable boost::mutex _queue_mutex; - std::list > _queue; - /** condition to manage thread wakeups when we have nothing to do */ - boost::condition _empty_condition; - /** condition to manage thread wakeups when we have too much to do */ - boost::condition _full_condition; - - boost::shared_ptr _writer; - Waker _waker; - - boost::shared_ptr _last_player_video; - boost::optional _last_player_video_time; - - boost::signals2::scoped_connection _server_found_connection; + boost::weak_ptr _job; + boost::shared_ptr _player; }; #endif diff --git a/src/lib/ffmpeg_encoder.cc b/src/lib/ffmpeg_encoder.cc new file mode 100644 index 000000000..734c9810d --- /dev/null +++ b/src/lib/ffmpeg_encoder.cc @@ -0,0 +1,231 @@ +/* + Copyright (C) 2017 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + +#include "ffmpeg_encoder.h" +#include "film.h" +#include "job.h" +#include "player.h" +#include "player_video.h" +#include "log.h" +#include "image.h" +#include "compose.hpp" +#include + +#include "i18n.h" + +using std::string; +using std::runtime_error; +using std::cout; +using boost::shared_ptr; +using boost::bind; +using boost::weak_ptr; + +static AVPixelFormat +force_pixel_format (AVPixelFormat, AVPixelFormat out) +{ + return out; +} + +FFmpegEncoder::FFmpegEncoder (shared_ptr film, weak_ptr job, boost::filesystem::path output, Format format) + : Encoder (film, job) + , _history (1000) + , _output (output) +{ + switch (format) { + case FORMAT_PRORES: + _pixel_format = AV_PIX_FMT_YUV422P10; + _codec_name = "prores_ks"; + break; + case FORMAT_H264: + _pixel_format = AV_PIX_FMT_YUV420P; + _codec_name = "libx264"; + break; + } +} + +void +FFmpegEncoder::go () +{ + AVCodec* codec = avcodec_find_encoder_by_name (_codec_name.c_str()); + if (!codec) { + throw runtime_error (String::compose ("could not find FFmpeg encoder %1", _codec_name)); + } + + _codec_context = avcodec_alloc_context3 (codec); + if (!_codec_context) { + throw runtime_error ("could not allocate FFmpeg context"); + } + + avcodec_get_context_defaults3 (_codec_context, codec); + + /* Variable quantisation */ + _codec_context->global_quality = 0; + _codec_context->width = _film->frame_size().width; + _codec_context->height = _film->frame_size().height; + _codec_context->time_base = (AVRational) { 1, _film->video_frame_rate() }; + _codec_context->pix_fmt = _pixel_format; + _codec_context->flags |= CODEC_FLAG_QSCALE | CODEC_FLAG_GLOBAL_HEADER; + + avformat_alloc_output_context2 (&_format_context, 0, 0, _output.string().c_str()); + if (!_format_context) { + throw runtime_error ("could not allocate FFmpeg format context"); + } + + _video_stream = avformat_new_stream (_format_context, codec); + if (!_video_stream) { + throw runtime_error ("could not create FFmpeg output video stream"); + } + + /* Note: needs to increment with each stream */ + _video_stream->id = 0; + _video_stream->codec = _codec_context; + + AVDictionary* options = 0; + av_dict_set (&options, "profile", "3", 0); + av_dict_set (&options, "threads", "auto", 0); + + if (avcodec_open2 (_codec_context, codec, &options) < 0) { + throw runtime_error ("could not open FFmpeg codec"); + } + + if (avio_open (&_format_context->pb, _output.c_str(), AVIO_FLAG_WRITE) < 0) { + throw runtime_error ("could not open FFmpeg output file"); + } + + if (avformat_write_header (_format_context, &options) < 0) { + throw runtime_error ("could not write header to FFmpeg output file"); + } + + { + shared_ptr job = _job.lock (); + DCPOMATIC_ASSERT (job); + job->sub (_("Encoding")); + } + + while (!_player->pass ()) {} + + while (true) { + AVPacket packet; + av_init_packet (&packet); + packet.data = 0; + packet.size = 0; + + int got_packet; + avcodec_encode_video2 (_codec_context, &packet, 0, &got_packet); + if (!got_packet) { + break; + } + + packet.stream_index = 0; + av_interleaved_write_frame (_format_context, &packet); + av_packet_unref (&packet); + } + + av_write_trailer (_format_context); + + avcodec_close (_codec_context); + avio_close (_format_context->pb); + avformat_free_context (_format_context); +} + +void +FFmpegEncoder::video (shared_ptr video, DCPTime time) +{ + shared_ptr image = video->image ( + bind (&Log::dcp_log, _film->log().get(), _1, _2), + bind (&force_pixel_format, _1, _pixel_format), + true, + false + ); + + AVFrame* frame = av_frame_alloc (); + + for (int i = 0; i < 3; ++i) { + size_t const size = image->stride()[i] * image->size().height; + AVBufferRef* buffer = av_buffer_alloc (size); + /* XXX: inefficient */ + memcpy (buffer->data, image->data()[i], size); + frame->buf[i] = av_buffer_ref (buffer); + frame->data[i] = buffer->data; + frame->linesize[i] = image->stride()[i]; + av_buffer_unref (&buffer); + } + + frame->width = image->size().width; + frame->height = image->size().height; + frame->format = _pixel_format; + frame->pts = time.seconds() / av_q2d (_video_stream->time_base); + + AVPacket packet; + av_init_packet (&packet); + packet.data = 0; + packet.size = 0; + + int got_packet; + if (avcodec_encode_video2 (_codec_context, &packet, frame, &got_packet) < 0) { + throw EncodeError ("FFmpeg video encode failed"); + } + + if (got_packet && packet.size) { + /* XXX: this should not be hard-wired */ + packet.stream_index = 0; + av_interleaved_write_frame (_format_context, &packet); + av_packet_unref (&packet); + } + + av_frame_free (&frame); + + _history.event (); + + { + boost::mutex::scoped_lock lm (_mutex); + _last_time = time; + } + + shared_ptr job = _job.lock (); + if (job) { + job->set_progress (float(time.get()) / _film->length().get()); + } +} + +void +FFmpegEncoder::audio (shared_ptr audio, DCPTime time) +{ + +} + +void +FFmpegEncoder::subtitle (PlayerSubtitles subs, DCPTimePeriod period) +{ + +} + +float +FFmpegEncoder::current_rate () const +{ + return _history.rate (); +} + +Frame +FFmpegEncoder::frames_done () const +{ + boost::mutex::scoped_lock lm (_mutex); + return _last_time.frames_round (_film->video_frame_rate ()); +} diff --git a/src/lib/ffmpeg_encoder.h b/src/lib/ffmpeg_encoder.h new file mode 100644 index 000000000..bd5be4545 --- /dev/null +++ b/src/lib/ffmpeg_encoder.h @@ -0,0 +1,69 @@ +/* + Copyright (C) 2017 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + +#ifndef DCPOMATIC_FFMPEG_ENCODER_H +#define DCPOMATIC_FFMPEG_ENCODER_H + +#include "encoder.h" +#include "event_history.h" +extern "C" { +#include +#include +} + +class FFmpegEncoder : public Encoder +{ +public: + enum Format + { + FORMAT_PRORES, + FORMAT_H264 + }; + + FFmpegEncoder (boost::shared_ptr film, boost::weak_ptr job, boost::filesystem::path output, Format format); + + void go (); + + float current_rate () const; + Frame frames_done () const; + bool finishing () const { + return false; + } + +private: + void video (boost::shared_ptr, DCPTime); + void audio (boost::shared_ptr, DCPTime); + void subtitle (PlayerSubtitles, DCPTimePeriod); + + AVCodecContext* _codec_context; + AVFormatContext* _format_context; + AVStream* _video_stream; + AVPixelFormat _pixel_format; + std::string _codec_name; + + mutable boost::mutex _mutex; + DCPTime _last_time; + + EventHistory _history; + + boost::filesystem::path _output; +}; + +#endif diff --git a/src/lib/ffmpeg_transcoder.cc b/src/lib/ffmpeg_transcoder.cc deleted file mode 100644 index 5bd0a1747..000000000 --- a/src/lib/ffmpeg_transcoder.cc +++ /dev/null @@ -1,231 +0,0 @@ -/* - Copyright (C) 2017 Carl Hetherington - - This file is part of DCP-o-matic. - - DCP-o-matic is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - DCP-o-matic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with DCP-o-matic. If not, see . - -*/ - -#include "ffmpeg_transcoder.h" -#include "film.h" -#include "job.h" -#include "player.h" -#include "player_video.h" -#include "log.h" -#include "image.h" -#include "compose.hpp" -#include - -#include "i18n.h" - -using std::string; -using std::runtime_error; -using std::cout; -using boost::shared_ptr; -using boost::bind; -using boost::weak_ptr; - -static AVPixelFormat -force_pixel_format (AVPixelFormat, AVPixelFormat out) -{ - return out; -} - -FFmpegTranscoder::FFmpegTranscoder (shared_ptr film, weak_ptr job, boost::filesystem::path output, Format format) - : Transcoder (film, job) - , _history (1000) - , _output (output) -{ - switch (format) { - case FORMAT_PRORES: - _pixel_format = AV_PIX_FMT_YUV422P10; - _codec_name = "prores_ks"; - break; - case FORMAT_H264: - _pixel_format = AV_PIX_FMT_YUV420P; - _codec_name = "libx264"; - break; - } -} - -void -FFmpegTranscoder::go () -{ - AVCodec* codec = avcodec_find_encoder_by_name (_codec_name.c_str()); - if (!codec) { - throw runtime_error (String::compose ("could not find FFmpeg encoder %1", _codec_name)); - } - - _codec_context = avcodec_alloc_context3 (codec); - if (!_codec_context) { - throw runtime_error ("could not allocate FFmpeg context"); - } - - avcodec_get_context_defaults3 (_codec_context, codec); - - /* Variable quantisation */ - _codec_context->global_quality = 0; - _codec_context->width = _film->frame_size().width; - _codec_context->height = _film->frame_size().height; - _codec_context->time_base = (AVRational) { 1, _film->video_frame_rate() }; - _codec_context->pix_fmt = _pixel_format; - _codec_context->flags |= CODEC_FLAG_QSCALE | CODEC_FLAG_GLOBAL_HEADER; - - avformat_alloc_output_context2 (&_format_context, 0, 0, _output.string().c_str()); - if (!_format_context) { - throw runtime_error ("could not allocate FFmpeg format context"); - } - - _video_stream = avformat_new_stream (_format_context, codec); - if (!_video_stream) { - throw runtime_error ("could not create FFmpeg output video stream"); - } - - /* Note: needs to increment with each stream */ - _video_stream->id = 0; - _video_stream->codec = _codec_context; - - AVDictionary* options = 0; - av_dict_set (&options, "profile", "3", 0); - av_dict_set (&options, "threads", "auto", 0); - - if (avcodec_open2 (_codec_context, codec, &options) < 0) { - throw runtime_error ("could not open FFmpeg codec"); - } - - if (avio_open (&_format_context->pb, _output.c_str(), AVIO_FLAG_WRITE) < 0) { - throw runtime_error ("could not open FFmpeg output file"); - } - - if (avformat_write_header (_format_context, &options) < 0) { - throw runtime_error ("could not write header to FFmpeg output file"); - } - - { - shared_ptr job = _job.lock (); - DCPOMATIC_ASSERT (job); - job->sub (_("Encoding")); - } - - while (!_player->pass ()) {} - - while (true) { - AVPacket packet; - av_init_packet (&packet); - packet.data = 0; - packet.size = 0; - - int got_packet; - avcodec_encode_video2 (_codec_context, &packet, 0, &got_packet); - if (!got_packet) { - break; - } - - packet.stream_index = 0; - av_interleaved_write_frame (_format_context, &packet); - av_packet_unref (&packet); - } - - av_write_trailer (_format_context); - - avcodec_close (_codec_context); - avio_close (_format_context->pb); - avformat_free_context (_format_context); -} - -void -FFmpegTranscoder::video (shared_ptr video, DCPTime time) -{ - shared_ptr image = video->image ( - bind (&Log::dcp_log, _film->log().get(), _1, _2), - bind (&force_pixel_format, _1, _pixel_format), - true, - false - ); - - AVFrame* frame = av_frame_alloc (); - - for (int i = 0; i < 3; ++i) { - size_t const size = image->stride()[i] * image->size().height; - AVBufferRef* buffer = av_buffer_alloc (size); - /* XXX: inefficient */ - memcpy (buffer->data, image->data()[i], size); - frame->buf[i] = av_buffer_ref (buffer); - frame->data[i] = buffer->data; - frame->linesize[i] = image->stride()[i]; - av_buffer_unref (&buffer); - } - - frame->width = image->size().width; - frame->height = image->size().height; - frame->format = _pixel_format; - frame->pts = time.seconds() / av_q2d (_video_stream->time_base); - - AVPacket packet; - av_init_packet (&packet); - packet.data = 0; - packet.size = 0; - - int got_packet; - if (avcodec_encode_video2 (_codec_context, &packet, frame, &got_packet) < 0) { - throw EncodeError ("FFmpeg video encode failed"); - } - - if (got_packet && packet.size) { - /* XXX: this should not be hard-wired */ - packet.stream_index = 0; - av_interleaved_write_frame (_format_context, &packet); - av_packet_unref (&packet); - } - - av_frame_free (&frame); - - _history.event (); - - { - boost::mutex::scoped_lock lm (_mutex); - _last_time = time; - } - - shared_ptr job = _job.lock (); - if (job) { - job->set_progress (float(time.get()) / _film->length().get()); - } -} - -void -FFmpegTranscoder::audio (shared_ptr audio, DCPTime time) -{ - -} - -void -FFmpegTranscoder::subtitle (PlayerSubtitles subs, DCPTimePeriod period) -{ - -} - -float -FFmpegTranscoder::current_rate () const -{ - return _history.rate (); -} - -Frame -FFmpegTranscoder::frames_done () const -{ - boost::mutex::scoped_lock lm (_mutex); - return _last_time.frames_round (_film->video_frame_rate ()); -} diff --git a/src/lib/ffmpeg_transcoder.h b/src/lib/ffmpeg_transcoder.h deleted file mode 100644 index a42de86b0..000000000 --- a/src/lib/ffmpeg_transcoder.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - Copyright (C) 2017 Carl Hetherington - - This file is part of DCP-o-matic. - - DCP-o-matic is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - DCP-o-matic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with DCP-o-matic. If not, see . - -*/ - -#ifndef DCPOMATIC_FFMPEG_TRANSCODER_H -#define DCPOMATIC_FFMPEG_TRANSCODER_H - -#include "transcoder.h" -#include "event_history.h" -extern "C" { -#include -#include -} - -class FFmpegTranscoder : public Transcoder -{ -public: - enum Format - { - FORMAT_PRORES, - FORMAT_H264 - }; - - FFmpegTranscoder (boost::shared_ptr film, boost::weak_ptr job, boost::filesystem::path output, Format format); - - void go (); - - float current_rate () const; - Frame frames_done () const; - bool finishing () const { - return false; - } - -private: - void video (boost::shared_ptr, DCPTime); - void audio (boost::shared_ptr, DCPTime); - void subtitle (PlayerSubtitles, DCPTimePeriod); - - AVCodecContext* _codec_context; - AVFormatContext* _format_context; - AVStream* _video_stream; - AVPixelFormat _pixel_format; - std::string _codec_name; - - mutable boost::mutex _mutex; - DCPTime _last_time; - - EventHistory _history; - - boost::filesystem::path _output; -}; - -#endif diff --git a/src/lib/film.cc b/src/lib/film.cc index 8637085af..57a4decd1 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -27,7 +27,7 @@ #include "job.h" #include "util.h" #include "job_manager.h" -#include "dcp_transcoder.h" +#include "dcp_encoder.h" #include "transcode_job.h" #include "upload_job.h" #include "null_log.h" @@ -343,7 +343,7 @@ Film::make_dcp () LOG_GENERAL ("J2K bandwidth %1", j2k_bandwidth()); shared_ptr tj (new TranscodeJob (shared_from_this())); - tj->set_transcoder (shared_ptr (new DCPTranscoder (shared_from_this(), tj))); + tj->set_encoder (shared_ptr (new DCPEncoder (shared_from_this(), tj))); JobManager::instance()->add (tj); } diff --git a/src/lib/j2k_encoder.cc b/src/lib/j2k_encoder.cc new file mode 100644 index 000000000..3c062dcaa --- /dev/null +++ b/src/lib/j2k_encoder.cc @@ -0,0 +1,420 @@ +/* + Copyright (C) 2012-2015 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + +/** @file src/j2k_encoder.cc + * @brief J2K encoder class. + */ + +#include "j2k_encoder.h" +#include "util.h" +#include "film.h" +#include "log.h" +#include "config.h" +#include "dcp_video.h" +#include "cross.h" +#include "writer.h" +#include "encode_server_finder.h" +#include "player.h" +#include "player_video.h" +#include "encode_server_description.h" +#include "compose.hpp" +#include +#include +#include + +#include "i18n.h" + +#define LOG_GENERAL(...) _film->log()->log (String::compose (__VA_ARGS__), LogEntry::TYPE_GENERAL); +#define LOG_GENERAL_NC(...) _film->log()->log (__VA_ARGS__, LogEntry::TYPE_GENERAL); +#define LOG_ERROR(...) _film->log()->log (String::compose (__VA_ARGS__), LogEntry::TYPE_ERROR); +#define LOG_TIMING(...) _film->log()->log (String::compose (__VA_ARGS__), LogEntry::TYPE_TIMING); +#define LOG_DEBUG_ENCODE(...) _film->log()->log (String::compose (__VA_ARGS__), LogEntry::TYPE_DEBUG_ENCODE); + +using std::list; +using std::cout; +using boost::shared_ptr; +using boost::weak_ptr; +using boost::optional; +using dcp::Data; + +/** @param film Film that we are encoding. + * @param writer Writer that we are using. + */ +J2KEncoder::J2KEncoder (shared_ptr film, shared_ptr writer) + : _film (film) + , _history (200) + , _writer (writer) +{ + servers_list_changed (); +} + +J2KEncoder::~J2KEncoder () +{ + try { + terminate_threads (); + } catch (...) { + /* Destructors must not throw exceptions; anything bad + happening now is too late to worry about anyway, + I think. + */ + } +} + +void +J2KEncoder::begin () +{ + weak_ptr wp = shared_from_this (); + _server_found_connection = EncodeServerFinder::instance()->ServersListChanged.connect ( + boost::bind (&J2KEncoder::call_servers_list_changed, wp) + ); +} + +/* We don't want the servers-list-changed callback trying to do things + during destruction of J2KEncoder, and I think this is the neatest way + to achieve that. +*/ +void +J2KEncoder::call_servers_list_changed (weak_ptr encoder) +{ + shared_ptr e = encoder.lock (); + if (e) { + e->servers_list_changed (); + } +} + +void +J2KEncoder::end () +{ + boost::mutex::scoped_lock lock (_queue_mutex); + + LOG_GENERAL (N_("Clearing queue of %1"), _queue.size ()); + + /* Keep waking workers until the queue is empty */ + while (!_queue.empty ()) { + rethrow (); + _empty_condition.notify_all (); + _full_condition.wait (lock); + } + + lock.unlock (); + + LOG_GENERAL_NC (N_("Terminating encoder threads")); + + terminate_threads (); + + LOG_GENERAL (N_("Mopping up %1"), _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_threads + + So just mop up anything left in the queue here. + */ + + for (list >::iterator i = _queue.begin(); i != _queue.end(); ++i) { + LOG_GENERAL (N_("Encode left-over frame %1"), (*i)->index ()); + try { + _writer->write ( + (*i)->encode_locally (boost::bind (&Log::dcp_log, _film->log().get(), _1, _2)), + (*i)->index (), + (*i)->eyes () + ); + frame_done (); + } catch (std::exception& e) { + LOG_ERROR (N_("Local encode failed (%1)"), e.what ()); + } + } +} + +/** @return an estimate of the current number of frames we are encoding per second, + * or 0 if not known. + */ +float +J2KEncoder::current_encoding_rate () const +{ + return _history.rate (); +} + +/** @return Number of video frames that have been queued for encoding */ +int +J2KEncoder::video_frames_enqueued () const +{ + if (!_last_player_video_time) { + return 0; + } + + return _last_player_video_time->frames_floor (_film->video_frame_rate ()); +} + +/** Should be called when a frame has been encoded successfully */ +void +J2KEncoder::frame_done () +{ + _history.event (); +} + +/** Called to request encoding of the next video frame in the DCP. This is called in order, + * so each time the supplied frame is the one after the previous one. + * pv represents one video frame, and could be empty if there is nothing to encode + * for this DCP frame. + * + * @param pv PlayerVideo to encode. + * @param time Time of \p pv within the DCP. + */ +void +J2KEncoder::encode (shared_ptr pv, DCPTime time) +{ + _waker.nudge (); + + size_t threads = 0; + { + boost::mutex::scoped_lock threads_lock (_threads_mutex); + threads = _threads.size (); + } + + boost::mutex::scoped_lock queue_lock (_queue_mutex); + + /* Wait until the queue has gone down a bit. Allow one thing in the queue even + when there are no threads. + */ + while (_queue.size() >= (threads * 2) + 1) { + LOG_TIMING ("decoder-sleep queue=%1 threads=%2", _queue.size(), threads); + _full_condition.wait (queue_lock); + LOG_TIMING ("decoder-wake queue=%1 threads=%2", _queue.size(), threads); + } + + _writer->rethrow (); + /* Re-throw any exception raised by one of our threads. If more + than one has thrown an exception, only one will be rethrown, I think; + but then, if that happens something has gone badly wrong. + */ + rethrow (); + + Frame const position = time.frames_floor(_film->video_frame_rate()); + + if (_writer->can_fake_write (position)) { + /* We can fake-write this frame */ + LOG_DEBUG_ENCODE("Frame @ %1 FAKE", to_string(time)); + _writer->fake_write (position, pv->eyes ()); + frame_done (); + } else if (pv->has_j2k ()) { + LOG_DEBUG_ENCODE("Frame @ %1 J2K", to_string(time)); + /* This frame already has J2K data, so just write it */ + _writer->write (pv->j2k(), position, pv->eyes ()); + } else if (_last_player_video && _writer->can_repeat(position) && pv->same (_last_player_video)) { + LOG_DEBUG_ENCODE("Frame @ %1 REPEAT", to_string(time)); + _writer->repeat (position, pv->eyes ()); + } else { + LOG_DEBUG_ENCODE("Frame @ %1 ENCODE", to_string(time)); + /* Queue this new frame for encoding */ + LOG_TIMING ("add-frame-to-queue queue=%1", _queue.size ()); + _queue.push_back (shared_ptr ( + new DCPVideo ( + pv, + position, + _film->video_frame_rate(), + _film->j2k_bandwidth(), + _film->resolution(), + _film->log() + ) + )); + + /* The queue might not be empty any more, so notify anything which is + waiting on that. + */ + _empty_condition.notify_all (); + } + + _last_player_video = pv; + _last_player_video_time = time; +} + +void +J2KEncoder::terminate_threads () +{ + boost::mutex::scoped_lock threads_lock (_threads_mutex); + + int n = 0; + for (list::iterator i = _threads.begin(); i != _threads.end(); ++i) { + LOG_GENERAL ("Terminating thread %1 of %2", n + 1, _threads.size ()); + (*i)->interrupt (); + DCPOMATIC_ASSERT ((*i)->joinable ()); + try { + (*i)->join (); + } catch (boost::thread_interrupted& e) { + /* This is to be expected */ + } + delete *i; + LOG_GENERAL_NC ("Thread terminated"); + ++n; + } + + _threads.clear (); +} + +void +J2KEncoder::encoder_thread (optional server) +try +{ + if (server) { + LOG_TIMING ("start-encoder-thread thread=%1 server=%2", thread_id (), server->host_name ()); + } else { + LOG_TIMING ("start-encoder-thread thread=%1 server=localhost", thread_id ()); + } + + /* Number of seconds that we currently wait between attempts + to connect to the server; not relevant for localhost + encodings. + */ + int remote_backoff = 0; + + while (true) { + + LOG_TIMING ("encoder-sleep thread=%1", thread_id ()); + boost::mutex::scoped_lock lock (_queue_mutex); + while (_queue.empty ()) { + _empty_condition.wait (lock); + } + + LOG_TIMING ("encoder-wake thread=%1 queue=%2", thread_id(), _queue.size()); + shared_ptr vf = _queue.front (); + + /* We're about to commit to either encoding this frame or putting it back onto the queue, + so we must not be interrupted until one or other of these things have happened. This + block has thread interruption disabled. + */ + { + boost::this_thread::disable_interruption dis; + + LOG_TIMING ("encoder-pop thread=%1 frame=%2 eyes=%3", thread_id(), vf->index(), (int) vf->eyes ()); + _queue.pop_front (); + + lock.unlock (); + + optional encoded; + + /* We need to encode this input */ + if (server) { + try { + encoded = vf->encode_remotely (server.get ()); + + if (remote_backoff > 0) { + LOG_GENERAL ("%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; + } + LOG_ERROR ( + N_("Remote encode of %1 on %2 failed (%3); thread sleeping for %4s"), + vf->index(), server->host_name(), e.what(), remote_backoff + ); + } + + } else { + try { + LOG_TIMING ("start-local-encode thread=%1 frame=%2", thread_id(), vf->index()); + encoded = vf->encode_locally (boost::bind (&Log::dcp_log, _film->log().get(), _1, _2)); + LOG_TIMING ("finish-local-encode thread=%1 frame=%2", thread_id(), vf->index()); + } catch (std::exception& e) { + /* This is very bad, so don't cope with it, just pass it on */ + LOG_ERROR (N_("Local encode failed (%1)"), e.what ()); + throw; + } + } + + if (encoded) { + _writer->write (encoded.get(), vf->index (), vf->eyes ()); + frame_done (); + } else { + lock.lock (); + LOG_GENERAL (N_("[%1] J2KEncoder thread pushes frame %2 back onto queue after failure"), thread_id(), vf->index()); + _queue.push_front (vf); + lock.unlock (); + } + } + + if (remote_backoff > 0) { + boost::this_thread::sleep (boost::posix_time::seconds (remote_backoff)); + } + + /* The queue might not be full any more, so notify anything that is waiting on that */ + lock.lock (); + _full_condition.notify_all (); + } +} +catch (boost::thread_interrupted& e) { + /* Ignore these and just stop the thread */ + _full_condition.notify_all (); +} +catch (...) +{ + store_current (); + /* Wake anything waiting on _full_condition so it can see the exception */ + _full_condition.notify_all (); +} + +void +J2KEncoder::servers_list_changed () +{ + terminate_threads (); + + /* XXX: could re-use threads */ + + boost::mutex::scoped_lock lm (_threads_mutex); + +#ifdef BOOST_THREAD_PLATFORM_WIN32 + OSVERSIONINFO info; + info.dwOSVersionInfoSize = sizeof (OSVERSIONINFO); + GetVersionEx (&info); + bool const windows_xp = (info.dwMajorVersion == 5 && info.dwMinorVersion == 1); + if (windows_xp) { + LOG_GENERAL_NC (N_("Setting thread affinity for Windows XP")); + } +#endif + + if (!Config::instance()->only_servers_encode ()) { + for (int i = 0; i < Config::instance()->master_encoding_threads (); ++i) { + boost::thread* t = new boost::thread (boost::bind (&J2KEncoder::encoder_thread, this, optional ())); + _threads.push_back (t); +#ifdef BOOST_THREAD_PLATFORM_WIN32 + if (windows_xp) { + SetThreadAffinityMask (t->native_handle(), 1 << i); + } +#endif + } + } + + BOOST_FOREACH (EncodeServerDescription i, EncodeServerFinder::instance()->servers ()) { + LOG_GENERAL (N_("Adding %1 worker threads for remote %2"), i.threads(), i.host_name ()); + for (int j = 0; j < i.threads(); ++j) { + _threads.push_back (new boost::thread (boost::bind (&J2KEncoder::encoder_thread, this, i))); + } + } + + _writer->set_encoder_threads (_threads.size ()); +} diff --git a/src/lib/j2k_encoder.h b/src/lib/j2k_encoder.h new file mode 100644 index 000000000..cc8a854eb --- /dev/null +++ b/src/lib/j2k_encoder.h @@ -0,0 +1,109 @@ +/* + Copyright (C) 2012-2016 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + +#ifndef DCPOMATIC_J2K_ENCODER_H +#define DCPOMATIC_J2K_ENCODER_H + +/** @file src/j2k_encoder.h + * @brief J2KEncoder class. + */ + +#include "util.h" +#include "cross.h" +#include "event_history.h" +#include "exception_store.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class Film; +class EncodeServerDescription; +class DCPVideo; +class Writer; +class Job; +class PlayerVideo; + +/** @class J2KEncoder + * @brief Class to manage encoding to J2K. + * + * This class keeps a queue of frames to be encoded and distributes + * the work around threads and encoding servers. + */ + +class J2KEncoder : public boost::noncopyable, public ExceptionStore, public boost::enable_shared_from_this +{ +public: + J2KEncoder (boost::shared_ptr film, boost::shared_ptr writer); + ~J2KEncoder (); + + /** Called to indicate that a processing run is about to begin */ + void begin (); + + /** Called to pass a bit of video to be encoded as the next DCP frame */ + void encode (boost::shared_ptr pv, DCPTime time); + + /** Called when a processing run has finished */ + void end (); + + float current_encoding_rate () const; + int video_frames_enqueued () const; + + void servers_list_changed (); + +private: + + static void call_servers_list_changed (boost::weak_ptr encoder); + + void frame_done (); + + void encoder_thread (boost::optional); + void terminate_threads (); + + /** Film that we are encoding */ + boost::shared_ptr _film; + + EventHistory _history; + + /** Mutex for _threads */ + mutable boost::mutex _threads_mutex; + std::list _threads; + mutable boost::mutex _queue_mutex; + std::list > _queue; + /** condition to manage thread wakeups when we have nothing to do */ + boost::condition _empty_condition; + /** condition to manage thread wakeups when we have too much to do */ + boost::condition _full_condition; + + boost::shared_ptr _writer; + Waker _waker; + + boost::shared_ptr _last_player_video; + boost::optional _last_player_video_time; + + boost::signals2::scoped_connection _server_found_connection; +}; + +#endif diff --git a/src/lib/transcode_job.cc b/src/lib/transcode_job.cc index 52540c4e7..420f5a8d2 100644 --- a/src/lib/transcode_job.cc +++ b/src/lib/transcode_job.cc @@ -23,11 +23,11 @@ */ #include "transcode_job.h" -#include "dcp_transcoder.h" +#include "dcp_encoder.h" #include "upload_job.h" #include "job_manager.h" #include "film.h" -#include "transcoder.h" +#include "encoder.h" #include "log.h" #include "compose.hpp" #include @@ -65,9 +65,9 @@ TranscodeJob::json_name () const } void -TranscodeJob::set_transcoder (shared_ptr t) +TranscodeJob::set_encoder (shared_ptr e) { - _transcoder = t; + _encoder = e; } void @@ -78,8 +78,8 @@ TranscodeJob::run () gettimeofday (&start, 0); LOG_GENERAL_NC (N_("Transcode job starting")); - DCPOMATIC_ASSERT (_transcoder); - _transcoder->go (); + DCPOMATIC_ASSERT (_encoder); + _encoder->go (); set_progress (1); set_state (FINISHED_OK); @@ -88,11 +88,11 @@ TranscodeJob::run () float fps = 0; if (finish.tv_sec != start.tv_sec) { - fps = _transcoder->frames_done() / (finish.tv_sec - start.tv_sec); + fps = _encoder->frames_done() / (finish.tv_sec - start.tv_sec); } LOG_GENERAL (N_("Transcode job completed successfully: %1 fps"), fps); - _transcoder.reset (); + _encoder.reset (); /* XXX: this shouldn't be here */ if (_film->upload_after_make_dcp ()) { @@ -101,7 +101,7 @@ TranscodeJob::run () } } catch (...) { - _transcoder.reset (); + _encoder.reset (); throw; } } @@ -109,23 +109,23 @@ TranscodeJob::run () string TranscodeJob::status () const { - if (!_transcoder) { + if (!_encoder) { return Job::status (); } char buffer[256]; - if (finished() || _transcoder->finishing()) { + if (finished() || _encoder->finishing()) { strncpy (buffer, Job::status().c_str(), 256); } else { snprintf ( buffer, sizeof(buffer), "%s; %" PRId64 "/%" PRId64 " frames", Job::status().c_str(), - _transcoder->frames_done(), + _encoder->frames_done(), _film->length().frames_round (_film->video_frame_rate ()) ); - float const fps = _transcoder->current_rate (); + float const fps = _encoder->current_rate (); if (fps) { char fps_buffer[64]; /// TRANSLATORS: fps here is an abbreviation for frames per second @@ -141,22 +141,22 @@ TranscodeJob::status () const int TranscodeJob::remaining_time () const { - /* _transcoder might be destroyed by the job-runner thread */ - shared_ptr t = _transcoder; + /* _encoder might be destroyed by the job-runner thread */ + shared_ptr e = _encoder; - if (!t || t->finishing()) { + if (!e || e->finishing()) { /* We aren't doing any actual encoding so just use the job's guess */ return Job::remaining_time (); } /* We're encoding so guess based on the current encoding rate */ - float fps = t->current_rate (); + float fps = e->current_rate (); if (fps == 0) { return 0; } /* Compute approximate proposed length here, as it's only here that we need it */ - return (_film->length().frames_round (_film->video_frame_rate ()) - t->frames_done()) / fps; + return (_film->length().frames_round (_film->video_frame_rate ()) - e->frames_done()) / fps; } diff --git a/src/lib/transcode_job.h b/src/lib/transcode_job.h index ae1a75804..47611525a 100644 --- a/src/lib/transcode_job.h +++ b/src/lib/transcode_job.h @@ -25,10 +25,10 @@ #include "job.h" #include -class Transcoder; +class Encoder; /** @class TranscodeJob - * @brief A job which transcodes from one format to another. + * @brief A job which transcodes a Film to another format. */ class TranscodeJob : public Job { @@ -40,10 +40,10 @@ public: void run (); std::string status () const; - void set_transcoder (boost::shared_ptr t); + void set_encoder (boost::shared_ptr t); private: int remaining_time () const; - boost::shared_ptr _transcoder; + boost::shared_ptr _encoder; }; diff --git a/src/lib/transcoder.cc b/src/lib/transcoder.cc deleted file mode 100644 index c77bf7724..000000000 --- a/src/lib/transcoder.cc +++ /dev/null @@ -1,48 +0,0 @@ -/* - Copyright (C) 2012-2017 Carl Hetherington - - This file is part of DCP-o-matic. - - DCP-o-matic is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - DCP-o-matic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with DCP-o-matic. If not, see . - -*/ - -/** @file src/transcoder.cc - * @brief A class which takes a Film and some Options, then uses those to transcode the film. - * - * A decoder is selected according to the content type, and the encoder can be specified - * as a parameter to the constructor. - */ - -#include "transcoder.h" -#include "player.h" - -#include "i18n.h" - -using boost::weak_ptr; -using boost::shared_ptr; - -/** Construct a transcoder. - * @param film Film that we are transcoding. - * @param job Job that this transcoder is being used in. - */ -Transcoder::Transcoder (shared_ptr film, weak_ptr job) - : _film (film) - , _job (job) - , _player (new Player (film, film->playlist ())) -{ - _player->Video.connect (bind (&Transcoder::video, this, _1, _2)); - _player->Audio.connect (bind (&Transcoder::audio, this, _1, _2)); - _player->Subtitle.connect (bind (&Transcoder::subtitle, this, _1, _2)); -} diff --git a/src/lib/transcoder.h b/src/lib/transcoder.h deleted file mode 100644 index 413b4d9be..000000000 --- a/src/lib/transcoder.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - Copyright (C) 2012-2017 Carl Hetherington - - This file is part of DCP-o-matic. - - DCP-o-matic is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - DCP-o-matic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with DCP-o-matic. If not, see . - -*/ - -#ifndef DCPOMATIC_TRANSCODER_H -#define DCPOMATIC_TRANSCODER_H - -#include "types.h" -#include "player_subtitles.h" -#include - -class Film; -class Encoder; -class Player; -class Job; -class PlayerVideo; -class AudioBuffers; - -/** @class Transcoder */ -class Transcoder : public boost::noncopyable -{ -public: - Transcoder (boost::shared_ptr film, boost::weak_ptr job); - virtual ~Transcoder () {} - - virtual void go () = 0; - - /** @return the current frame rate over the last short while */ - virtual float current_rate () const = 0; - /** @return the number of frames that are done */ - virtual Frame frames_done () const = 0; - virtual bool finishing () const = 0; - -protected: - virtual void video (boost::shared_ptr, DCPTime) = 0; - virtual void audio (boost::shared_ptr, DCPTime) = 0; - virtual void subtitle (PlayerSubtitles, DCPTimePeriod) = 0; - - boost::shared_ptr _film; - boost::weak_ptr _job; - boost::shared_ptr _player; -}; - -#endif diff --git a/src/lib/wscript b/src/lib/wscript index 22f104a14..dc6e1017a 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -52,11 +52,11 @@ sources = """ dcp_content.cc dcp_content_type.cc dcp_decoder.cc + dcp_encoder.cc dcp_examiner.cc dcp_subtitle.cc dcp_subtitle_content.cc dcp_subtitle_decoder.cc - dcp_transcoder.cc dcp_video.cc dcpomatic_socket.cc dcpomatic_time.cc @@ -81,10 +81,10 @@ sources = """ ffmpeg_audio_stream.cc ffmpeg_content.cc ffmpeg_decoder.cc + ffmpeg_encoder.cc ffmpeg_examiner.cc ffmpeg_stream.cc ffmpeg_subtitle_stream.cc - ffmpeg_transcoder.cc film.cc filter.cc font.cc @@ -102,6 +102,7 @@ sources = """ j2k_image_proxy.cc job.cc job_manager.cc + j2k_encoder.cc json_server.cc log.cc log_entry.cc @@ -134,7 +135,6 @@ sources = """ text_subtitle_decoder.cc timer.cc transcode_job.cc - transcoder.cc types.cc signal_manager.cc update_checker.cc diff --git a/src/tools/dcpomatic.cc b/src/tools/dcpomatic.cc index c19336631..5346b106c 100644 --- a/src/tools/dcpomatic.cc +++ b/src/tools/dcpomatic.cc @@ -64,7 +64,7 @@ #include "lib/dcpomatic_socket.h" #include "lib/hints.h" #include "lib/dcp_content.h" -#include "lib/ffmpeg_transcoder.h" +#include "lib/ffmpeg_encoder.h" #include "lib/transcode_job.h" #include #include @@ -707,8 +707,7 @@ private: ExportDialog* d = new ExportDialog (this); if (d->ShowModal() == wxID_OK) { shared_ptr job (new TranscodeJob (_film)); - shared_ptr tx (new FFmpegTranscoder (_film, job, d->path(), d->format())); - job->set_transcoder (tx); + job->set_encoder (shared_ptr (new FFmpegEncoder (_film, job, d->path(), d->format()))); JobManager::instance()->add (job); } d->Destroy (); diff --git a/src/wx/export_dialog.cc b/src/wx/export_dialog.cc index 10c289d82..52aa03c20 100644 --- a/src/wx/export_dialog.cc +++ b/src/wx/export_dialog.cc @@ -37,9 +37,9 @@ wxString format_filters[] = { _("MP4 files (*.mp4)|*.mp4"), }; -FFmpegTranscoder::Format formats[] = { - FFmpegTranscoder::FORMAT_PRORES, - FFmpegTranscoder::FORMAT_H264, +FFmpegEncoder::Format formats[] = { + FFmpegEncoder::FORMAT_PRORES, + FFmpegEncoder::FORMAT_H264, }; ExportDialog::ExportDialog (wxWindow* parent) @@ -76,7 +76,7 @@ ExportDialog::path () const return wx_to_std (_file->GetPath ()); } -FFmpegTranscoder::Format +FFmpegEncoder::Format ExportDialog::format () const { DCPOMATIC_ASSERT (_format->GetSelection() >= 0 && _format->GetSelection() < FORMATS); diff --git a/src/wx/export_dialog.h b/src/wx/export_dialog.h index 04a516bca..239e56829 100644 --- a/src/wx/export_dialog.h +++ b/src/wx/export_dialog.h @@ -19,7 +19,7 @@ */ #include "table_dialog.h" -#include "lib/ffmpeg_transcoder.h" +#include "lib/ffmpeg_encoder.h" #include #include @@ -31,7 +31,7 @@ public: ExportDialog (wxWindow* parent); boost::filesystem::path path () const; - FFmpegTranscoder::Format format () const; + FFmpegEncoder::Format format () const; private: void format_changed (); diff --git a/test/ffmpeg_encoder_test.cc b/test/ffmpeg_encoder_test.cc new file mode 100644 index 000000000..9049596fa --- /dev/null +++ b/test/ffmpeg_encoder_test.cc @@ -0,0 +1,45 @@ +/* + Copyright (C) 2017 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + +#include "lib/ffmpeg_encoder.h" +#include "lib/film.h" +#include "lib/ffmpeg_content.h" +#include "lib/ratio.h" +#include "lib/transcode_job.h" +#include "test.h" +#include + +using boost::shared_ptr; + +BOOST_AUTO_TEST_CASE (ffmpeg_transcoder_basic_test) +{ + shared_ptr film = new_test_film ("ffmpeg_transcoder_basic_test"); + film->set_name ("ffmpeg_transcoder_basic_test"); + shared_ptr c (new FFmpegContent (film, "test/data/test.mp4")); + film->set_container (Ratio::from_id ("185")); + film->set_audio_channels (6); + + film->examine_and_add_content (c); + wait_for_jobs (); + + shared_ptr job (new TranscodeJob (film)); + FFmpegEncoder encoder (film, job, "build/test/ffmpeg_encoder_basic_test/test.mov", FFmpegEncoder::FORMAT_PRORES); + encoder.go (); +} diff --git a/test/ffmpeg_transcoder_test.cc b/test/ffmpeg_transcoder_test.cc deleted file mode 100644 index bf4f991f8..000000000 --- a/test/ffmpeg_transcoder_test.cc +++ /dev/null @@ -1,45 +0,0 @@ -/* - Copyright (C) 2017 Carl Hetherington - - This file is part of DCP-o-matic. - - DCP-o-matic is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - DCP-o-matic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with DCP-o-matic. If not, see . - -*/ - -#include "lib/ffmpeg_transcoder.h" -#include "lib/film.h" -#include "lib/ffmpeg_content.h" -#include "lib/ratio.h" -#include "lib/transcode_job.h" -#include "test.h" -#include - -using boost::shared_ptr; - -BOOST_AUTO_TEST_CASE (ffmpeg_transcoder_basic_test) -{ - shared_ptr film = new_test_film ("ffmpeg_transcoder_basic_test"); - film->set_name ("ffmpeg_transcoder_basic_test"); - shared_ptr c (new FFmpegContent (film, "test/data/test.mp4")); - film->set_container (Ratio::from_id ("185")); - film->set_audio_channels (6); - - film->examine_and_add_content (c); - wait_for_jobs (); - - shared_ptr job (new TranscodeJob (film)); - FFmpegTranscoder transcoder (film, job, "build/test/ffmpeg_transcoder_basic_test/test.mov", FFmpegTranscoder::FORMAT_PRORES); - transcoder.go (); -} diff --git a/test/wscript b/test/wscript index 58d16ec65..17e8479cb 100644 --- a/test/wscript +++ b/test/wscript @@ -58,9 +58,9 @@ def build(bld): ffmpeg_dcp_test.cc ffmpeg_decoder_seek_test.cc ffmpeg_decoder_sequential_test.cc + ffmpeg_encoder_test.cc ffmpeg_examiner_test.cc ffmpeg_pts_offset_test.cc - ffmpeg_transcoder_test.cc file_group_test.cc file_log_test.cc file_naming_test.cc