Rename some classes.
authorCarl Hetherington <cth@carlh.net>
Wed, 10 May 2017 10:54:19 +0000 (11:54 +0100)
committerCarl Hetherington <cth@carlh.net>
Wed, 10 May 2017 10:54:19 +0000 (11:54 +0100)
24 files changed:
src/lib/dcp_encoder.cc [new file with mode: 0644]
src/lib/dcp_encoder.h [new file with mode: 0644]
src/lib/dcp_transcoder.cc [deleted file]
src/lib/dcp_transcoder.h [deleted file]
src/lib/encoder.cc
src/lib/encoder.h
src/lib/ffmpeg_encoder.cc [new file with mode: 0644]
src/lib/ffmpeg_encoder.h [new file with mode: 0644]
src/lib/ffmpeg_transcoder.cc [deleted file]
src/lib/ffmpeg_transcoder.h [deleted file]
src/lib/film.cc
src/lib/j2k_encoder.cc [new file with mode: 0644]
src/lib/j2k_encoder.h [new file with mode: 0644]
src/lib/transcode_job.cc
src/lib/transcode_job.h
src/lib/transcoder.cc [deleted file]
src/lib/transcoder.h [deleted file]
src/lib/wscript
src/tools/dcpomatic.cc
src/wx/export_dialog.cc
src/wx/export_dialog.h
test/ffmpeg_encoder_test.cc [new file with mode: 0644]
test/ffmpeg_transcoder_test.cc [deleted file]
test/wscript

diff --git a/src/lib/dcp_encoder.cc b/src/lib/dcp_encoder.cc
new file mode 100644 (file)
index 0000000..522f029
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+    Copyright (C) 2012-2017 Carl Hetherington <cth@carlh.net>
+
+    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 <http://www.gnu.org/licenses/>.
+
+*/
+
+/** @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 <boost/signals2.hpp>
+#include <boost/foreach.hpp>
+#include <iostream>
+
+#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<const Film> film, weak_ptr<Job> 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<const Content> 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 = _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<PlayerVideo> 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<AudioBuffers> data, DCPTime time)
+{
+       _writer->write (data);
+
+       shared_ptr<Job> 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 (file)
index 0000000..3879219
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+    Copyright (C) 2012-2017 Carl Hetherington <cth@carlh.net>
+
+    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 <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "types.h"
+#include "player_subtitles.h"
+#include "encoder.h"
+#include <boost/weak_ptr.hpp>
+
+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<const Film> film, boost::weak_ptr<Job> 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<PlayerVideo>, DCPTime);
+       void audio (boost::shared_ptr<AudioBuffers>, DCPTime);
+       void subtitle (PlayerSubtitles, DCPTimePeriod);
+
+       boost::shared_ptr<Writer> _writer;
+       boost::shared_ptr<J2KEncoder> _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 (file)
index aa3ef73..0000000
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
-    Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
-
-    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 <http://www.gnu.org/licenses/>.
-
-*/
-
-/** @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 <boost/signals2.hpp>
-#include <boost/foreach.hpp>
-#include <iostream>
-
-#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<const Film> film, weak_ptr<Job> job)
-       : Transcoder (film, job)
-       , _writer (new Writer (film, job))
-       , _encoder (new Encoder (film, _writer))
-       , _finishing (false)
-       , _non_burnt_subtitles (false)
-{
-       BOOST_FOREACH (shared_ptr<const Content> 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 = _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<PlayerVideo> 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<AudioBuffers> data, DCPTime time)
-{
-       _writer->write (data);
-
-       shared_ptr<Job> 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 (file)
index 7245651..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    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 <http://www.gnu.org/licenses/>.
-
-*/
-
-#include "types.h"
-#include "player_subtitles.h"
-#include "transcoder.h"
-#include <boost/weak_ptr.hpp>
-
-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<const Film> film, boost::weak_ptr<Job> 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<PlayerVideo>, DCPTime);
-       void audio (boost::shared_ptr<AudioBuffers>, DCPTime);
-       void subtitle (PlayerSubtitles, DCPTimePeriod);
-
-       boost::shared_ptr<Writer> _writer;
-       boost::shared_ptr<Encoder> _encoder;
-       bool _finishing;
-       bool _non_burnt_subtitles;
-};
index 2d916d4..3af80ef 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2017 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
 
 */
 
-/** @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 <libcxml/cxml.h>
-#include <boost/foreach.hpp>
-#include <iostream>
 
 #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<const Film> film, shared_ptr<Writer> writer)
+Encoder::Encoder (shared_ptr<const Film> film, weak_ptr<Job> 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<Encoder> 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> encoder)
-{
-       shared_ptr<Encoder> 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<shared_ptr<DCPVideo> >::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<PlayerVideo> 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<DCPVideo> (
-                                         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<boost::thread *>::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<EncodeServerDescription> 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<DCPVideo> 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<Data> 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<EncodeServerDescription> ()));
-                       _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));
 }
index 7e9bd49..79ad0ab 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2017 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
 #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 <boost/shared_ptr.hpp>
-#include <boost/thread/mutex.hpp>
-#include <boost/thread/condition.hpp>
-#include <boost/thread.hpp>
-#include <boost/optional.hpp>
-#include <boost/signals2.hpp>
-#include <boost/enable_shared_from_this.hpp>
-#include <list>
-#include <stdint.h>
+#include "types.h"
+#include "player_subtitles.h"
+#include <boost/weak_ptr.hpp>
 
 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<Encoder>
+/** @class Encoder */
+class Encoder : public boost::noncopyable
 {
 public:
-       Encoder (boost::shared_ptr<const Film> film, boost::shared_ptr<Writer> 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<PlayerVideo> 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<const Film> film, boost::weak_ptr<Job> job);
+       virtual ~Encoder () {}
 
-private:
+       virtual void go () = 0;
 
-       static void call_servers_list_changed (boost::weak_ptr<Encoder> 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<PlayerVideo>, DCPTime) = 0;
+       virtual void audio (boost::shared_ptr<AudioBuffers>, DCPTime) = 0;
+       virtual void subtitle (PlayerSubtitles, DCPTimePeriod) = 0;
 
-       void encoder_thread (boost::optional<EncodeServerDescription>);
-       void terminate_threads ();
-
-       /** Film that we are encoding */
        boost::shared_ptr<const Film> _film;
-
-       EventHistory _history;
-
-       /** Mutex for _threads */
-       mutable boost::mutex _threads_mutex;
-       std::list<boost::thread *> _threads;
-       mutable boost::mutex _queue_mutex;
-       std::list<boost::shared_ptr<DCPVideo> > _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> _writer;
-       Waker _waker;
-
-       boost::shared_ptr<PlayerVideo> _last_player_video;
-       boost::optional<DCPTime> _last_player_video_time;
-
-       boost::signals2::scoped_connection _server_found_connection;
+       boost::weak_ptr<Job> _job;
+       boost::shared_ptr<Player> _player;
 };
 
 #endif
diff --git a/src/lib/ffmpeg_encoder.cc b/src/lib/ffmpeg_encoder.cc
new file mode 100644 (file)
index 0000000..734c981
--- /dev/null
@@ -0,0 +1,231 @@
+/*
+    Copyright (C) 2017 Carl Hetherington <cth@carlh.net>
+
+    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 <http://www.gnu.org/licenses/>.
+
+*/
+
+#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 <iostream>
+
+#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<const Film> film, weak_ptr<Job> 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 = _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<PlayerVideo> video, DCPTime time)
+{
+       shared_ptr<Image> 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 = _job.lock ();
+       if (job) {
+               job->set_progress (float(time.get()) / _film->length().get());
+       }
+}
+
+void
+FFmpegEncoder::audio (shared_ptr<AudioBuffers> 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 (file)
index 0000000..bd5be45
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+    Copyright (C) 2017 Carl Hetherington <cth@carlh.net>
+
+    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 <http://www.gnu.org/licenses/>.
+
+*/
+
+#ifndef DCPOMATIC_FFMPEG_ENCODER_H
+#define DCPOMATIC_FFMPEG_ENCODER_H
+
+#include "encoder.h"
+#include "event_history.h"
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+}
+
+class FFmpegEncoder : public Encoder
+{
+public:
+       enum Format
+       {
+               FORMAT_PRORES,
+               FORMAT_H264
+       };
+
+       FFmpegEncoder (boost::shared_ptr<const Film> film, boost::weak_ptr<Job> 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<PlayerVideo>, DCPTime);
+       void audio (boost::shared_ptr<AudioBuffers>, 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 (file)
index 5bd0a17..0000000
+++ /dev/null
@@ -1,231 +0,0 @@
-/*
-    Copyright (C) 2017 Carl Hetherington <cth@carlh.net>
-
-    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 <http://www.gnu.org/licenses/>.
-
-*/
-
-#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 <iostream>
-
-#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<const Film> film, weak_ptr<Job> 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 = _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<PlayerVideo> video, DCPTime time)
-{
-       shared_ptr<Image> 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 = _job.lock ();
-       if (job) {
-               job->set_progress (float(time.get()) / _film->length().get());
-       }
-}
-
-void
-FFmpegTranscoder::audio (shared_ptr<AudioBuffers> 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 (file)
index a42de86..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
-    Copyright (C) 2017 Carl Hetherington <cth@carlh.net>
-
-    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 <http://www.gnu.org/licenses/>.
-
-*/
-
-#ifndef DCPOMATIC_FFMPEG_TRANSCODER_H
-#define DCPOMATIC_FFMPEG_TRANSCODER_H
-
-#include "transcoder.h"
-#include "event_history.h"
-extern "C" {
-#include <libavcodec/avcodec.h>
-#include <libavformat/avformat.h>
-}
-
-class FFmpegTranscoder : public Transcoder
-{
-public:
-       enum Format
-       {
-               FORMAT_PRORES,
-               FORMAT_H264
-       };
-
-       FFmpegTranscoder (boost::shared_ptr<const Film> film, boost::weak_ptr<Job> 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<PlayerVideo>, DCPTime);
-       void audio (boost::shared_ptr<AudioBuffers>, 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
index 8637085..57a4dec 100644 (file)
@@ -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<TranscodeJob> tj (new TranscodeJob (shared_from_this()));
-       tj->set_transcoder (shared_ptr<Transcoder> (new DCPTranscoder (shared_from_this(), tj)));
+       tj->set_encoder (shared_ptr<Encoder> (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 (file)
index 0000000..3c062dc
--- /dev/null
@@ -0,0 +1,420 @@
+/*
+    Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
+
+    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 <http://www.gnu.org/licenses/>.
+
+*/
+
+/** @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 <libcxml/cxml.h>
+#include <boost/foreach.hpp>
+#include <iostream>
+
+#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<const Film> film, shared_ptr<Writer> 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<J2KEncoder> 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<J2KEncoder> encoder)
+{
+       shared_ptr<J2KEncoder> 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<shared_ptr<DCPVideo> >::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<PlayerVideo> 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<DCPVideo> (
+                                         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<boost::thread *>::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<EncodeServerDescription> 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<DCPVideo> 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<Data> 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<EncodeServerDescription> ()));
+                       _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 (file)
index 0000000..cc8a854
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+    Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
+
+    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 <http://www.gnu.org/licenses/>.
+
+*/
+
+#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 <boost/shared_ptr.hpp>
+#include <boost/thread/mutex.hpp>
+#include <boost/thread/condition.hpp>
+#include <boost/thread.hpp>
+#include <boost/optional.hpp>
+#include <boost/signals2.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include <list>
+#include <stdint.h>
+
+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<J2KEncoder>
+{
+public:
+       J2KEncoder (boost::shared_ptr<const Film> film, boost::shared_ptr<Writer> 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<PlayerVideo> 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<J2KEncoder> encoder);
+
+       void frame_done ();
+
+       void encoder_thread (boost::optional<EncodeServerDescription>);
+       void terminate_threads ();
+
+       /** Film that we are encoding */
+       boost::shared_ptr<const Film> _film;
+
+       EventHistory _history;
+
+       /** Mutex for _threads */
+       mutable boost::mutex _threads_mutex;
+       std::list<boost::thread *> _threads;
+       mutable boost::mutex _queue_mutex;
+       std::list<boost::shared_ptr<DCPVideo> > _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> _writer;
+       Waker _waker;
+
+       boost::shared_ptr<PlayerVideo> _last_player_video;
+       boost::optional<DCPTime> _last_player_video_time;
+
+       boost::signals2::scoped_connection _server_found_connection;
+};
+
+#endif
index 52540c4..420f5a8 100644 (file)
  */
 
 #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 <iostream>
@@ -65,9 +65,9 @@ TranscodeJob::json_name () const
 }
 
 void
-TranscodeJob::set_transcoder (shared_ptr<Transcoder> t)
+TranscodeJob::set_encoder (shared_ptr<Encoder> 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<Transcoder> t = _transcoder;
+       /* _encoder might be destroyed by the job-runner thread */
+       shared_ptr<Encoder> 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;
 }
index ae1a758..4761152 100644 (file)
 #include "job.h"
 #include <boost/shared_ptr.hpp>
 
-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<Transcoder> t);
+       void set_encoder (boost::shared_ptr<Encoder> t);
 
 private:
        int remaining_time () const;
 
-       boost::shared_ptr<Transcoder> _transcoder;
+       boost::shared_ptr<Encoder> _encoder;
 };
diff --git a/src/lib/transcoder.cc b/src/lib/transcoder.cc
deleted file mode 100644 (file)
index c77bf77..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
-    Copyright (C) 2012-2017 Carl Hetherington <cth@carlh.net>
-
-    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 <http://www.gnu.org/licenses/>.
-
-*/
-
-/** @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<const Film> film, weak_ptr<Job> 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 (file)
index 413b4d9..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
-    Copyright (C) 2012-2017 Carl Hetherington <cth@carlh.net>
-
-    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 <http://www.gnu.org/licenses/>.
-
-*/
-
-#ifndef DCPOMATIC_TRANSCODER_H
-#define DCPOMATIC_TRANSCODER_H
-
-#include "types.h"
-#include "player_subtitles.h"
-#include <boost/weak_ptr.hpp>
-
-class Film;
-class Encoder;
-class Player;
-class Job;
-class PlayerVideo;
-class AudioBuffers;
-
-/** @class Transcoder */
-class Transcoder : public boost::noncopyable
-{
-public:
-       Transcoder (boost::shared_ptr<const Film> film, boost::weak_ptr<Job> 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<PlayerVideo>, DCPTime) = 0;
-       virtual void audio (boost::shared_ptr<AudioBuffers>, DCPTime) = 0;
-       virtual void subtitle (PlayerSubtitles, DCPTimePeriod) = 0;
-
-       boost::shared_ptr<const Film> _film;
-       boost::weak_ptr<Job> _job;
-       boost::shared_ptr<Player> _player;
-};
-
-#endif
index 22f104a..dc6e101 100644 (file)
@@ -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
index c193366..5346b10 100644 (file)
@@ -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 <dcp/exceptions.h>
 #include <dcp/raw_convert.h>
@@ -707,8 +707,7 @@ private:
                ExportDialog* d = new ExportDialog (this);
                if (d->ShowModal() == wxID_OK) {
                        shared_ptr<TranscodeJob> job (new TranscodeJob (_film));
-                       shared_ptr<FFmpegTranscoder> tx (new FFmpegTranscoder (_film, job, d->path(), d->format()));
-                       job->set_transcoder (tx);
+                       job->set_encoder (shared_ptr<FFmpegEncoder> (new FFmpegEncoder (_film, job, d->path(), d->format())));
                        JobManager::instance()->add (job);
                }
                d->Destroy ();
index 10c289d..52aa03c 100644 (file)
@@ -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);
index 04a516b..239e568 100644 (file)
@@ -19,7 +19,7 @@
 */
 
 #include "table_dialog.h"
-#include "lib/ffmpeg_transcoder.h"
+#include "lib/ffmpeg_encoder.h"
 #include <wx/wx.h>
 #include <boost/filesystem.hpp>
 
@@ -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 (file)
index 0000000..9049596
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+    Copyright (C) 2017 Carl Hetherington <cth@carlh.net>
+
+    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 <http://www.gnu.org/licenses/>.
+
+*/
+
+#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 <boost/test/unit_test.hpp>
+
+using boost::shared_ptr;
+
+BOOST_AUTO_TEST_CASE (ffmpeg_transcoder_basic_test)
+{
+       shared_ptr<Film> film = new_test_film ("ffmpeg_transcoder_basic_test");
+       film->set_name ("ffmpeg_transcoder_basic_test");
+       shared_ptr<FFmpegContent> 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> 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 (file)
index bf4f991..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
-    Copyright (C) 2017 Carl Hetherington <cth@carlh.net>
-
-    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 <http://www.gnu.org/licenses/>.
-
-*/
-
-#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 <boost/test/unit_test.hpp>
-
-using boost::shared_ptr;
-
-BOOST_AUTO_TEST_CASE (ffmpeg_transcoder_basic_test)
-{
-       shared_ptr<Film> film = new_test_film ("ffmpeg_transcoder_basic_test");
-       film->set_name ("ffmpeg_transcoder_basic_test");
-       shared_ptr<FFmpegContent> 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> job (new TranscodeJob (film));
-       FFmpegTranscoder transcoder (film, job, "build/test/ffmpeg_transcoder_basic_test/test.mov", FFmpegTranscoder::FORMAT_PRORES);
-       transcoder.go ();
-}
index 58d16ec..17e8479 100644 (file)
@@ -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