Basics of front-end 3D (as far as viewer, at least).
authorCarl Hetherington <cth@carlh.net>
Mon, 22 Jul 2013 15:23:23 +0000 (16:23 +0100)
committerCarl Hetherington <cth@carlh.net>
Mon, 22 Jul 2013 15:23:23 +0000 (16:23 +0100)
25 files changed:
src/lib/dcp_video_frame.cc
src/lib/dcp_video_frame.h
src/lib/encoder.cc
src/lib/encoder.h
src/lib/ffmpeg_decoder.cc
src/lib/film.cc
src/lib/film.h
src/lib/player.cc
src/lib/player.h
src/lib/server.cc
src/lib/still_image_decoder.cc
src/lib/transcoder.cc
src/lib/types.h
src/lib/video_content.cc
src/lib/video_decoder.cc
src/lib/video_decoder.h
src/lib/writer.cc
src/lib/writer.h
src/wx/audio_mapping_view.cc
src/wx/film_editor.cc
src/wx/film_editor.h
src/wx/film_viewer.cc
src/wx/film_viewer.h
src/wx/video_panel.cc
test/client_server_test.cc

index ef8db7d23b09e23743d4b331a8c9a69d84896096..10f1e4ad170816ff1e44b15f982051f0f8a5ba92 100644 (file)
@@ -77,10 +77,11 @@ using libdcp::Size;
  *  @param l Log to write to.
  */
 DCPVideoFrame::DCPVideoFrame (
-       shared_ptr<const Image> image, int f, int dcp_fps, int bw, shared_ptr<Log> l
+       shared_ptr<const Image> image, int f, Eyes eyes, int dcp_fps, int bw, shared_ptr<Log> l
        )
        : _image (image)
        , _frame (f)
+       , _eyes (eyes)
        , _frames_per_second (dcp_fps)
        , _j2k_bandwidth (bw)
        , _log (l)
@@ -102,7 +103,11 @@ DCPVideoFrame::encode_locally ()
                );
                
        /* Set the max image and component sizes based on frame_rate */
-       int const max_cs_len = ((float) _j2k_bandwidth) / 8 / _frames_per_second;
+       int max_cs_len = ((float) _j2k_bandwidth) / 8 / _frames_per_second;
+       if (_eyes == EYES_LEFT || _eyes == EYES_RIGHT) {
+               /* In 3D we have only half the normal bandwidth per eye */
+               max_cs_len /= 2;
+       }
        int const max_comp_size = max_cs_len / 1.25;
 
        /* get a J2K compressor handle */
@@ -208,12 +213,13 @@ DCPVideoFrame::encode_remotely (ServerDescription const * serv)
        socket->connect (*endpoint_iterator);
 
        stringstream s;
-       s << N_("encode please\n")
-         << N_("width ") << _image->size().width << N_("\n")
-         << N_("height ") << _image->size().height << N_("\n")
-         << N_("frame ") << _frame << N_("\n")
-         << N_("frames_per_second ") << _frames_per_second << N_("\n")
-         << N_("j2k_bandwidth ") << _j2k_bandwidth << N_("\n");
+       s << "encode please\n"
+         << "width " << _image->size().width << "\n"
+         << "height " << _image->size().height << "\n"
+         << "eyes " << static_cast<int> (_eyes) << "\n"
+         << "frame " << _frame << "\n"
+         << "frames_per_second " << _frames_per_second << "\n"
+         << "j2k_bandwidth " << _j2k_bandwidth << "\n";
 
        _log->log (String::compose (
                           N_("Sending to remote; pixel format %1, components %2, lines (%3,%4,%5), line sizes (%6,%7,%8)"),
@@ -272,9 +278,9 @@ EncodedData::~EncodedData ()
  *  @param frame DCP frame index.
  */
 void
-EncodedData::write (shared_ptr<const Film> film, int frame) const
+EncodedData::write (shared_ptr<const Film> film, int frame, Eyes eyes) const
 {
-       string const tmp_j2c = film->j2c_path (frame, true);
+       string const tmp_j2c = film->j2c_path (frame, eyes, true);
 
        FILE* f = fopen (tmp_j2c.c_str (), N_("wb"));
        
@@ -285,16 +291,16 @@ EncodedData::write (shared_ptr<const Film> film, int frame) const
        fwrite (_data, 1, _size, f);
        fclose (f);
 
-       string const real_j2c = film->j2c_path (frame, false);
+       string const real_j2c = film->j2c_path (frame, eyes, false);
 
        /* Rename the file from foo.j2c.tmp to foo.j2c now that it is complete */
        boost::filesystem::rename (tmp_j2c, real_j2c);
 }
 
 void
-EncodedData::write_info (shared_ptr<const Film> film, int frame, libdcp::FrameInfo fin) const
+EncodedData::write_info (shared_ptr<const Film> film, int frame, Eyes eyes, libdcp::FrameInfo fin) const
 {
-       string const info = film->info_path (frame);
+       string const info = film->info_path (frame, eyes);
        ofstream h (info.c_str());
        fin.write (h);
 }
index 4c545b6ef06b09a64d842e130dacb8557e5e0e85..e786d9b610d943560b8cf0ac29cf9587c7fb1c97 100644 (file)
@@ -47,8 +47,8 @@ public:
        virtual ~EncodedData ();
 
        void send (boost::shared_ptr<Socket> socket);
-       void write (boost::shared_ptr<const Film>, int) const;
-       void write_info (boost::shared_ptr<const Film>, int, libdcp::FrameInfo) const;
+       void write (boost::shared_ptr<const Film>, int, Eyes) const;
+       void write_info (boost::shared_ptr<const Film>, int, Eyes, libdcp::FrameInfo) const;
 
        /** @return data */
        uint8_t* data () const {
@@ -101,11 +101,15 @@ public:
 class DCPVideoFrame : public boost::noncopyable
 {
 public:
-       DCPVideoFrame (boost::shared_ptr<const Image>, int, int, int, boost::shared_ptr<Log>);
+       DCPVideoFrame (boost::shared_ptr<const Image>, int, Eyes, int, int, boost::shared_ptr<Log>);
 
        boost::shared_ptr<EncodedData> encode_locally ();
        boost::shared_ptr<EncodedData> encode_remotely (ServerDescription const *);
 
+       Eyes eyes () const {
+               return _eyes;
+       }
+       
        int frame () const {
                return _frame;
        }
@@ -113,6 +117,7 @@ public:
 private:
        boost::shared_ptr<const Image> _image;
        int _frame;                      ///< frame index within the DCP's intrinsic duration
+       Eyes _eyes;
        int _frames_per_second;          ///< Frames per second that we will use for the DCP
        int _j2k_bandwidth;              ///< J2K bandwidth to use
 
index 718ae55a4570773be52426e07e43230cb77e9bb8..8e61a0d60d7a303231d0e3f4ef85221e84d2c4f3 100644 (file)
@@ -52,10 +52,11 @@ Encoder::Encoder (shared_ptr<const Film> f, shared_ptr<Job> j)
        : _film (f)
        , _job (j)
        , _video_frames_out (0)
-       , _have_a_real_frame (false)
        , _terminate (false)
 {
-       
+       _have_a_real_frame[EYES_BOTH] = false;
+       _have_a_real_frame[EYES_LEFT] = false;
+       _have_a_real_frame[EYES_RIGHT] = false;
 }
 
 Encoder::~Encoder ()
@@ -117,7 +118,7 @@ Encoder::process_end ()
        for (list<shared_ptr<DCPVideoFrame> >::iterator i = _queue.begin(); i != _queue.end(); ++i) {
                _film->log()->log (String::compose (N_("Encode left-over frame %1"), (*i)->frame ()));
                try {
-                       _writer->write ((*i)->encode_locally(), (*i)->frame ());
+                       _writer->write ((*i)->encode_locally(), (*i)->frame (), (*i)->eyes ());
                        frame_done ();
                } catch (std::exception& e) {
                        _film->log()->log (String::compose (N_("Local encode failed (%1)"), e.what ()));
@@ -170,7 +171,7 @@ Encoder::frame_done ()
 }
 
 void
-Encoder::process_video (shared_ptr<const Image> image, bool same)
+Encoder::process_video (shared_ptr<const Image> image, Eyes eyes, bool same)
 {
        boost::mutex::scoped_lock lock (_mutex);
 
@@ -190,25 +191,25 @@ Encoder::process_video (shared_ptr<const Image> image, bool same)
        }
 
        if (_writer->can_fake_write (_video_frames_out)) {
-               _writer->fake_write (_video_frames_out);
-               _have_a_real_frame = false;
+               _writer->fake_write (_video_frames_out, eyes);
+               _have_a_real_frame[eyes] = false;
                frame_done ();
-       } else if (same && _have_a_real_frame) {
+       } else if (same && _have_a_real_frame[eyes]) {
                /* Use the last frame that we encoded. */
-               _writer->repeat (_video_frames_out);
+               _writer->repeat (_video_frames_out, eyes);
                frame_done ();
        } else {
                /* Queue this new frame for encoding */
                TIMING ("adding to queue of %1", _queue.size ());
                _queue.push_back (shared_ptr<DCPVideoFrame> (
                                          new DCPVideoFrame (
-                                                 image, _video_frames_out, _film->dcp_video_frame_rate(),
+                                                 image, _video_frames_out, eyes, _film->dcp_video_frame_rate(),
                                                  _film->j2k_bandwidth(), _film->log()
                                                  )
                                          ));
                
                _condition.notify_all ();
-               _have_a_real_frame = true;
+               _have_a_real_frame[eyes] = true;
        }
 
        ++_video_frames_out;
@@ -302,7 +303,7 @@ Encoder::encoder_thread (ServerDescription* server)
                }
 
                if (encoded) {
-                       _writer->write (encoded, vf->frame ());
+                       _writer->write (encoded, vf->frame (), vf->eyes ());
                        frame_done ();
                } else {
                        lock.lock ();
index a6e5932be9d53cb4ed4c3c661d60d91d705139f9..a866a77f19c43998e0f96572e5a71a80d657861e 100644 (file)
@@ -66,7 +66,7 @@ public:
         *  @param i Video frame image.
         *  @param same true if i is the same as the last time we were called.
         */
-       void process_video (boost::shared_ptr<const Image> i, bool same);
+       void process_video (boost::shared_ptr<const Image> i, Eyes eyes, bool same);
 
        /** Call with some audio data */
        void process_audio (boost::shared_ptr<const AudioBuffers>);
@@ -100,7 +100,7 @@ private:
        /** Number of video frames written for the DCP so far */
        int _video_frames_out;
 
-       bool _have_a_real_frame;
+       bool _have_a_real_frame[EYES_COUNT];
        bool _terminate;
        std::list<boost::shared_ptr<DCPVideoFrame> > _queue;
        std::list<boost::thread *> _threads;
index 11cea8fb140dbcde881c23740a76bb2c3a41a72a..ea32d102d6a7aacba93720896f266bfc657a32d5 100644 (file)
@@ -59,7 +59,7 @@ using libdcp::Size;
 
 FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegContent> c, bool video, bool audio)
        : Decoder (f)
-       , VideoDecoder (f)
+       , VideoDecoder (f, c)
        , AudioDecoder (f)
        , SubtitleDecoder (f)
        , FFmpeg (c)
index e39e94cfcbb4e3500d06fb1bc22ec74458175b4e..40c6962563e29d4856029479fdef3e7cc7bf448c 100644 (file)
@@ -95,6 +95,7 @@ Film::Film (string d)
        , _dci_metadata (Config::instance()->default_dci_metadata ())
        , _dcp_video_frame_rate (24)
        , _dcp_audio_channels (MAX_AUDIO_CHANNELS)
+       , _dcp_3d (false)
        , _sequence_video (true)
        , _dirty (false)
 {
@@ -141,6 +142,10 @@ Film::video_identifier () const
          << "_" << scaler()->id()
          << "_" << j2k_bandwidth();
 
+       if (_dcp_3d) {
+               s << "_3D";
+       }
+
        return s.str ();
 }
          
@@ -327,6 +332,7 @@ Film::write_metadata () const
        root->add_child("DCPVideoFrameRate")->add_child_text (lexical_cast<string> (_dcp_video_frame_rate));
        root->add_child("DCIDate")->add_child_text (boost::gregorian::to_iso_string (_dci_date));
        root->add_child("DCPAudioChannels")->add_child_text (lexical_cast<string> (_dcp_audio_channels));
+       root->add_child("DCP3D")->add_child_text (_dcp_3d ? "1" : "0");
        root->add_child("SequenceVideo")->add_child_text (_sequence_video ? "1" : "0");
        _playlist->as_xml (root->add_child ("Playlist"));
 
@@ -439,6 +445,14 @@ Film::dci_name (bool if_created_now) const
                d << "_" << dcp_content_type()->dci_name();
        }
 
+       if (dcp_3d ()) {
+               d << "-3D";
+       }
+
+       if (dcp_video_frame_rate() != 24) {
+               d << "-" << dcp_video_frame_rate();
+       }
+
        if (container()) {
                d << "_" << container()->dci_name();
        }
@@ -635,6 +649,16 @@ Film::set_dcp_audio_channels (int c)
        signal_changed (DCP_AUDIO_CHANNELS);
 }
 
+void
+Film::set_dcp_3d (bool t)
+{
+       {
+               boost::mutex::scoped_lock lm (_state_mutex);
+               _dcp_3d = t;
+       }
+       signal_changed (DCP_3D);
+}
+
 void
 Film::signal_changed (Property p)
 {
@@ -667,15 +691,23 @@ Film::set_dci_date_today ()
 }
 
 string
-Film::info_path (int f) const
+Film::info_path (int f, Eyes e) const
 {
        boost::filesystem::path p;
        p /= info_dir ();
 
        stringstream s;
        s.width (8);
-       s << setfill('0') << f << ".md5";
+       s << setfill('0') << f;
+
+       if (e == EYES_LEFT) {
+               s << ".L";
+       } else if (e == EYES_RIGHT) {
+               s << ".R";
+       }
 
+       s << ".md5";
+       
        p /= s.str();
 
        /* info_dir() will already have added any initial bit of the path,
@@ -685,7 +717,7 @@ Film::info_path (int f) const
 }
 
 string
-Film::j2c_path (int f, bool t) const
+Film::j2c_path (int f, Eyes e, bool t) const
 {
        boost::filesystem::path p;
        p /= "j2c";
@@ -693,7 +725,15 @@ Film::j2c_path (int f, bool t) const
 
        stringstream s;
        s.width (8);
-       s << setfill('0') << f << ".j2c";
+       s << setfill('0') << f;
+
+       if (e == EYES_LEFT) {
+               s << ".L";
+       } else if (e == EYES_RIGHT) {
+               s << ".R";
+       }
+       
+       s << ".j2c";
 
        if (t) {
                s << ".tmp";
index 99c214ab7bd18776fe74aa54073975151bc3a801..5aff6f0be31ef1c00176c1e67d7b2aa6de93953b 100644 (file)
@@ -55,8 +55,8 @@ public:
        Film (std::string d);
 
        std::string info_dir () const;
-       std::string j2c_path (int f, bool t) const;
-       std::string info_path (int f) const;
+       std::string j2c_path (int, Eyes, bool) const;
+       std::string info_path (int, Eyes) const;
        std::string internal_video_mxf_dir () const;
        std::string internal_video_mxf_filename () const;
        boost::filesystem::path audio_analysis_path (boost::shared_ptr<const AudioContent>) const;
@@ -130,7 +130,9 @@ public:
                DCI_METADATA,
                DCP_VIDEO_FRAME_RATE,
                DCP_AUDIO_CHANNELS,
-               SEQUENCE_VIDEO
+               /** The setting of _dcp_3d has been changed */
+               DCP_3D,
+               SEQUENCE_VIDEO,
        };
 
 
@@ -197,6 +199,11 @@ public:
                return _dcp_audio_channels;
        }
 
+       bool dcp_3d () const {
+               boost::mutex::scoped_lock lm (_state_mutex);
+               return _dcp_3d;
+       }
+
        bool sequence_video () const {
                boost::mutex::scoped_lock lm (_state_mutex);
                return _sequence_video;
@@ -220,6 +227,7 @@ public:
        void set_dci_metadata (DCIMetadata);
        void set_dcp_video_frame_rate (int);
        void set_dcp_audio_channels (int);
+       void set_dcp_3d (bool);
        void set_dci_date_today ();
        void set_sequence_video (bool);
 
@@ -275,6 +283,10 @@ private:
        /** The date that we should use in a DCI name */
        boost::gregorian::date _dci_date;
        int _dcp_audio_channels;
+       /** If true, the DCP will be written in 3D mode; otherwise in 2D.
+           This will be regardless of what content is on the playlist.
+       */
+       bool _dcp_3d;
        bool _sequence_video;
 
        /** true if our state has changed since we last saved it */
index 98a31ae74ca10945650ac62a6d37556b90f9f418..20beda9453d11c3a0c253bc71b0fa9033fe049a9 100644 (file)
@@ -202,7 +202,7 @@ Player::pass ()
 }
 
 void
-Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image, bool same, VideoContent::Frame frame)
+Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image, Eyes eyes, bool same, VideoContent::Frame frame)
 {
        shared_ptr<Piece> piece = weak_piece.lock ();
        if (!piece) {
@@ -242,11 +242,11 @@ Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image
        _last_video = piece->content;
 #endif 
 
-       Video (work_image, same, time);
+       Video (work_image, eyes, same, time);
        time += TIME_HZ / _film->dcp_video_frame_rate();
 
        if (frc.repeat) {
-               Video (work_image, true, time);
+               Video (work_image, eyes, true, time);
                time += TIME_HZ / _film->dcp_video_frame_rate();
        }
 
@@ -414,7 +414,7 @@ Player::setup_pieces ()
                if (fc) {
                        shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (_film, fc, _video, _audio));
                        
-                       fd->Video.connect (bind (&Player::process_video, this, piece, _1, _2, _3));
+                       fd->Video.connect (bind (&Player::process_video, this, piece, _1, _2, _3, _4));
                        fd->Audio.connect (bind (&Player::process_audio, this, piece, _1, _2));
                        fd->Subtitle.connect (bind (&Player::process_subtitle, this, piece, _1, _2, _3, _4));
 
@@ -435,7 +435,7 @@ Player::setup_pieces ()
 
                        if (!id) {
                                id.reset (new StillImageDecoder (_film, ic));
-                               id->Video.connect (bind (&Player::process_video, this, piece, _1, _2, _3));
+                               id->Video.connect (bind (&Player::process_video, this, piece, _1, _2, _3, _4));
                        }
 
                        piece->decoder = id;
@@ -479,6 +479,9 @@ Player::content_changed (weak_ptr<Content> w, int property, bool frequent)
        } else if (property == SubtitleContentProperty::SUBTITLE_OFFSET || property == SubtitleContentProperty::SUBTITLE_SCALE) {
                update_subtitle ();
                Changed (frequent);
+       } else if (property == VideoContentProperty::VIDEO_FRAME_TYPE) {
+               cout << "vft change.\n";
+               Changed (frequent);
        }
 }
 
@@ -518,7 +521,7 @@ Player::emit_black ()
 #endif
        
        /* XXX: use same here */
-       Video (_black_frame, false, _video_position);
+       Video (_black_frame, EYES_BOTH, false, _video_position);
        _video_position += _film->video_frames_to_time (1);
 }
 
index 568c7a7a116d26da36c27b602bd0a40d51f36ce9..8b28f010de67facb93f108c0eaaa5015f3c4e2e8 100644 (file)
@@ -60,10 +60,11 @@ public:
 
        /** Emitted when a video frame is ready.
         *  First parameter is the video image.
-        *  Second parameter is true if the image is the same as the last one that was emitted.
-        *  Third parameter is the time.
+        *  Second parameter is the eye(s) that should see this image.
+        *  Third parameter is true if the image is the same as the last one that was emitted.
+        *  Fourth parameter is the time.
         */
-       boost::signals2::signal<void (boost::shared_ptr<const Image>, bool, Time)> Video;
+       boost::signals2::signal<void (boost::shared_ptr<const Image>, Eyes, bool, Time)> Video;
        
        /** Emitted when some audio data is ready */
        boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, Time)> Audio;
@@ -79,7 +80,7 @@ public:
 private:
        friend class PlayerWrapper;
 
-       void process_video (boost::weak_ptr<Piece>, boost::shared_ptr<const Image>, bool, VideoContent::Frame);
+       void process_video (boost::weak_ptr<Piece>, boost::shared_ptr<const Image>, Eyes, bool, VideoContent::Frame);
        void process_audio (boost::weak_ptr<Piece>, boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
        void process_subtitle (boost::weak_ptr<Piece>, boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time);
        void setup_pieces ();
index 9af0883a7ae7c8bcdee206bda24eab402255c207..37a076a5485d55e6174e3e7355a74027b049bfc9 100644 (file)
@@ -105,13 +105,14 @@ Server::process (shared_ptr<Socket> socket)
        int frame = get_required_int (kv, "frame");
        int frames_per_second = get_required_int (kv, "frames_per_second");
        int j2k_bandwidth = get_required_int (kv, "j2k_bandwidth");
+       Eyes eyes = static_cast<Eyes> (get_required_int (kv, "eyes"));
 
        shared_ptr<Image> image (new Image (PIX_FMT_RGB24, size, true));
 
        image->read_from_socket (socket);
 
        DCPVideoFrame dcp_video_frame (
-               image, frame, frames_per_second, j2k_bandwidth, _log
+               image, frame, eyes, frames_per_second, j2k_bandwidth, _log
                );
        
        shared_ptr<EncodedData> encoded = dcp_video_frame.encode_locally ();
index 21cc83f5408e4a4299b142f3e9f3ebd0ea0dd12f..10e34427bc45edba311724650530d09111720c02 100644 (file)
@@ -34,7 +34,7 @@ using libdcp::Size;
 
 StillImageDecoder::StillImageDecoder (shared_ptr<const Film> f, shared_ptr<const StillImageContent> c)
        : Decoder (f)
-       , VideoDecoder (f)
+       , VideoDecoder (f, c)
        , StillImage (c)
 {
 
index 7022965bd47d5e7bd13d7386b9045758c44fc928..3002ef61c9c0f776d3b714bf326cb14ff7073d97 100644 (file)
@@ -40,11 +40,11 @@ using boost::weak_ptr;
 using boost::dynamic_pointer_cast;
 
 static void
-video_proxy (weak_ptr<Encoder> encoder, shared_ptr<const Image> image, bool same)
+video_proxy (weak_ptr<Encoder> encoder, shared_ptr<const Image> image, Eyes eyes, bool same)
 {
        shared_ptr<Encoder> e = encoder.lock ();
        if (e) {
-               e->process_video (image, same);
+               e->process_video (image, eyes, same);
        }
 }
 
@@ -67,7 +67,7 @@ Transcoder::Transcoder (shared_ptr<const Film> f, shared_ptr<Job> j)
        , _player (f->make_player ())
        , _encoder (new Encoder (f, j))
 {
-       _player->Video.connect (bind (video_proxy, _encoder, _1, _2));
+       _player->Video.connect (bind (video_proxy, _encoder, _1, _2, _3));
        _player->Audio.connect (bind (audio_proxy, _encoder, _1));
 }
 
index b1b359810e87219ff7c67777f447e2f52a965848..d6136fc3e2778d08dbbba65392827e704a2c3bc3 100644 (file)
@@ -40,12 +40,21 @@ enum VideoFrameType
        VIDEO_FRAME_TYPE_3D_LEFT_RIGHT
 };
 
+enum Eyes
+{
+       EYES_BOTH,
+       EYES_LEFT,
+       EYES_RIGHT,
+       EYES_COUNT
+};
+
 /** @struct Crop
  *  @brief A description of the crop of an image or video.
  */
 struct Crop
 {
        Crop () : left (0), right (0), top (0), bottom (0) {}
+       Crop (int l, int r, int t, int b) : left (l), right (r), top (t), bottom (b) {}
 
        /** Number of pixels to remove from the left-hand side */
        int left;
index 7eca53c3de8de202aad5c38d5f6c5e934a06e3aa..076cd6ec676765556723c7231ddc8e21e514a74f 100644 (file)
@@ -35,6 +35,7 @@ int const VideoContentProperty::VIDEO_RATIO    = 4;
 using std::string;
 using std::stringstream;
 using std::setprecision;
+using std::cout;
 using boost::shared_ptr;
 using boost::lexical_cast;
 using boost::optional;
@@ -231,7 +232,7 @@ VideoContent::set_video_frame_type (VideoFrameType t)
 {
        {
                boost::mutex::scoped_lock lm (_mutex);
-               _video_frame_rate = t;
+               _video_frame_type = t;
        }
 
        signal_changed (VideoContentProperty::VIDEO_FRAME_TYPE);
index 38d5dfcb87298a4ec28f782c89c3ba430551edc0..eaa4534e44b64ab6c6e503b06bfae280e8dde08e 100644 (file)
@@ -25,8 +25,9 @@
 using std::cout;
 using boost::shared_ptr;
 
-VideoDecoder::VideoDecoder (shared_ptr<const Film> f)
+VideoDecoder::VideoDecoder (shared_ptr<const Film> f, shared_ptr<const VideoContent> c)
        : Decoder (f)
+       , _video_content (c)
        , _video_position (0)
 {
 
@@ -35,7 +36,19 @@ VideoDecoder::VideoDecoder (shared_ptr<const Film> f)
 void
 VideoDecoder::video (shared_ptr<const Image> image, bool same, VideoContent::Frame frame)
 {
-       Video (image, same, frame);
+       switch (_video_content->video_frame_type ()) {
+       case VIDEO_FRAME_TYPE_2D:
+               Video (image, EYES_BOTH, same, frame);
+               break;
+       case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT:
+       {
+               int const half = image->size().width / 2;
+               Video (image->crop (Crop (0, half, 0, 0), true), EYES_LEFT, same, frame);
+               Video (image->crop (Crop (half, 0, 0, 0), true), EYES_RIGHT, same, frame);
+               break;
+       }
+       }
+       
        _video_position = frame + 1;
 }
 
index 26a11c805ceccb1ba82a669fc3d6e4473c82531b..142320a049c8e7a23bf7aff2d618149d6259941d 100644 (file)
@@ -32,7 +32,7 @@ class Image;
 class VideoDecoder : public virtual Decoder
 {
 public:
-       VideoDecoder (boost::shared_ptr<const Film>);
+       VideoDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const VideoContent>);
 
        /** Seek so that the next pass() will yield (approximately) the requested frame.
         *  Pass accurate = true to try harder to get close to the request.
@@ -41,14 +41,16 @@ public:
 
        /** Emitted when a video frame is ready.
         *  First parameter is the video image.
-        *  Second parameter is true if the image is the same as the last one that was emitted.
-        *  Third parameter is the frame within our source.
+        *  Second parameter is the eye(s) which should see this image.
+        *  Third parameter is true if the image is the same as the last one that was emitted for this Eyes value.
+        *  Fourth parameter is the frame within our source.
         */
-       boost::signals2::signal<void (boost::shared_ptr<const Image>, bool, VideoContent::Frame)> Video;
+       boost::signals2::signal<void (boost::shared_ptr<const Image>, Eyes, bool, VideoContent::Frame)> Video;
        
 protected:
 
        void video (boost::shared_ptr<const Image>, bool, VideoContent::Frame);
+       boost::shared_ptr<const VideoContent> _video_content;
        VideoContent::Frame _video_position;
 };
 
index c5360a122f4e0afa277abec1cdd24a5ead2dd2ed..122dcd716021e3e1e030fae1904537bcb68f8202 100644 (file)
@@ -99,7 +99,7 @@ Writer::Writer (shared_ptr<const Film> f, shared_ptr<Job> j)
 }
 
 void
-Writer::write (shared_ptr<const EncodedData> encoded, int frame)
+Writer::write (shared_ptr<const EncodedData> encoded, int frame, Eyes eyes)
 {
        boost::mutex::scoped_lock lock (_mutex);
 
@@ -107,6 +107,7 @@ Writer::write (shared_ptr<const EncodedData> encoded, int frame)
        qi.type = QueueItem::FULL;
        qi.encoded = encoded;
        qi.frame = frame;
+       qi.eyes = eyes;
        _queue.push_back (qi);
        ++_queued_full_in_memory;
 
@@ -114,17 +115,18 @@ Writer::write (shared_ptr<const EncodedData> encoded, int frame)
 }
 
 void
-Writer::fake_write (int frame)
+Writer::fake_write (int frame, Eyes eyes)
 {
        boost::mutex::scoped_lock lock (_mutex);
 
-       ifstream ifi (_film->info_path (frame).c_str());
+       ifstream ifi (_film->info_path (frame, eyes).c_str());
        libdcp::FrameInfo info (ifi);
        
        QueueItem qi;
        qi.type = QueueItem::FAKE;
        qi.size = info.size;
        qi.frame = frame;
+       qi.eyes = eyes;
        _queue.push_back (qi);
 
        _condition.notify_all ();
@@ -137,6 +139,23 @@ Writer::write (shared_ptr<const AudioBuffers> audio)
        _sound_asset_writer->write (audio->data(), audio->frames());
 }
 
+/** This must be called from Writer::thread() with an appropriate lock held */
+bool
+Writer::have_sequenced_image_at_queue_head () const
+{
+       if (_queue.empty ()) {
+               return false;
+       }
+
+       /* We assume that we will get either all 2D frames or all 3D frames, not a mixture */
+       
+       bool const eyes_ok = (_queue.front().eyes == EYES_BOTH) ||
+               (_queue.front().eyes == EYES_LEFT && _last_written_eyes == EYES_RIGHT) ||
+               (_queue.front().eyes == EYES_RIGHT && _last_written_eyes == EYES_LEFT);
+       
+       return _queue.front().frame == (_last_written_frame + 1) && eyes_ok;
+}
+
 void
 Writer::thread ()
 try
@@ -149,10 +168,7 @@ try
                        
                        _queue.sort ();
                        
-                       if (_finish ||
-                           _queued_full_in_memory > _maximum_frames_in_memory ||
-                           (!_queue.empty() && _queue.front().frame == (_last_written_frame + 1))) {
-                               
+                       if (_finish || _queued_full_in_memory > _maximum_frames_in_memory || have_sequenced_image_at_queue_head ()) {
                                break;
                        }
                        
@@ -166,7 +182,7 @@ try
                }
 
                /* Write any frames that we can write; i.e. those that are in sequence */
-               while (!_queue.empty() && _queue.front().frame == (_last_written_frame + 1)) {
+               while (have_sequenced_image_at_queue_head ()) {
                        QueueItem qi = _queue.front ();
                        _queue.pop_front ();
                        if (qi.type == QueueItem::FULL && qi.encoded) {
@@ -179,10 +195,10 @@ try
                        {
                                _film->log()->log (String::compose (N_("Writer FULL-writes %1 to MXF"), qi.frame));
                                if (!qi.encoded) {
-                                       qi.encoded.reset (new EncodedData (_film->j2c_path (qi.frame, false)));
+                                       qi.encoded.reset (new EncodedData (_film->j2c_path (qi.frame, qi.eyes, false)));
                                }
                                libdcp::FrameInfo const fin = _picture_asset_writer->write (qi.encoded->data(), qi.encoded->size());
-                               qi.encoded->write_info (_film, qi.frame, fin);
+                               qi.encoded->write_info (_film, qi.frame, qi.eyes, fin);
                                _last_written = qi.encoded;
                                ++_full_written;
                                break;
@@ -197,7 +213,7 @@ try
                        {
                                _film->log()->log (String::compose (N_("Writer REPEAT-writes %1 to MXF"), qi.frame));
                                libdcp::FrameInfo const fin = _picture_asset_writer->write (_last_written->data(), _last_written->size());
-                               _last_written->write_info (_film, qi.frame, fin);
+                               _last_written->write_info (_film, qi.frame, qi.eyes, fin);
                                ++_repeat_written;
                                break;
                        }
@@ -231,7 +247,7 @@ try
                        
                        lock.unlock ();
                        _film->log()->log (String::compose (N_("Writer full (awaiting %1); pushes %2 to disk"), _last_written_frame + 1, qi.frame));
-                       qi.encoded->write (_film, qi.frame);
+                       qi.encoded->write (_film, qi.frame, qi.eyes);
                        lock.lock ();
                        qi.encoded.reset ();
                        --_queued_full_in_memory;
@@ -324,19 +340,44 @@ Writer::finish ()
 
 /** Tell the writer that frame `f' should be a repeat of the frame before it */
 void
-Writer::repeat (int f)
+Writer::repeat (int f, Eyes e)
 {
        boost::mutex::scoped_lock lock (_mutex);
 
        QueueItem qi;
        qi.type = QueueItem::REPEAT;
        qi.frame = f;
+       qi.eyes = e;
        
        _queue.push_back (qi);
 
        _condition.notify_all ();
 }
 
+bool
+Writer::check_existing_picture_mxf_frame (FILE* mxf, int f, Eyes eyes)
+{
+       /* Read the frame info as written */
+       ifstream ifi (_film->info_path (f, eyes).c_str());
+       libdcp::FrameInfo info (ifi);
+       
+       /* Read the data from the MXF and hash it */
+       fseek (mxf, info.offset, SEEK_SET);
+       EncodedData data (info.size);
+       size_t const read = fread (data.data(), 1, data.size(), mxf);
+       if (read != static_cast<size_t> (data.size ())) {
+               _film->log()->log (String::compose ("Existing frame %1 is incomplete", f));
+               return false;
+       }
+       
+       string const existing_hash = md5_digest (data.data(), data.size());
+       if (existing_hash != info.hash) {
+               _film->log()->log (String::compose ("Existing frame %1 failed hash check", f));
+               return false;
+       }
+
+       return true;
+}
 
 void
 Writer::check_existing_picture_mxf ()
@@ -353,23 +394,17 @@ Writer::check_existing_picture_mxf ()
 
        while (1) {
 
-               /* Read the frame info as written */
-               ifstream ifi (_film->info_path (_first_nonexistant_frame).c_str());
-               libdcp::FrameInfo info (ifi);
-
-               /* Read the data from the MXF and hash it */
-               fseek (mxf, info.offset, SEEK_SET);
-               EncodedData data (info.size);
-               size_t const read = fread (data.data(), 1, data.size(), mxf);
-               if (read != static_cast<size_t> (data.size ())) {
-                       _film->log()->log (String::compose ("Existing frame %1 is incomplete", _first_nonexistant_frame));
-                       break;
-               }
-               
-               string const existing_hash = md5_digest (data.data(), data.size());
-               if (existing_hash != info.hash) {
-                       _film->log()->log (String::compose ("Existing frame %1 failed hash check", _first_nonexistant_frame));
-                       break;
+               if (_film->dcp_3d ()) {
+                       if (!check_existing_picture_mxf_frame (mxf, _first_nonexistant_frame, EYES_LEFT)) {
+                               break;
+                       }
+                       if (!check_existing_picture_mxf_frame (mxf, _first_nonexistant_frame, EYES_RIGHT)) {
+                               break;
+                       }
+               } else {
+                       if (!check_existing_picture_mxf_frame (mxf, _first_nonexistant_frame, EYES_BOTH)) {
+                               break;
+                       }
                }
 
                _film->log()->log (String::compose ("Have existing frame %1", _first_nonexistant_frame));
@@ -394,11 +429,15 @@ Writer::can_fake_write (int frame) const
 bool
 operator< (QueueItem const & a, QueueItem const & b)
 {
-       return a.frame < b.frame;
+       if (a.frame != b.frame) {
+               return a.frame < b.frame;
+       }
+       
+       return a.eyes == EYES_LEFT && b.eyes == EYES_RIGHT;
 }
 
 bool
 operator== (QueueItem const & a, QueueItem const & b)
 {
-       return a.frame == b.frame;
+       return a.frame == b.frame && a.eyes == b.eyes;
 }
index 1e5d864898e8457380ce5622520b7579f8141da5..023107d9756555be0e144b9b363801e670cda33e 100644 (file)
@@ -22,6 +22,7 @@
 #include <boost/thread.hpp>
 #include <boost/thread/condition.hpp>
 #include "exceptions.h"
+#include "types.h"
 
 class Film;
 class EncodedData;
@@ -56,6 +57,7 @@ public:
        int size;
        /** frame index */
        int frame;
+       Eyes eyes;
 };
 
 bool operator< (QueueItem const & a, QueueItem const & b);
@@ -68,16 +70,18 @@ public:
 
        bool can_fake_write (int) const;
        
-       void write (boost::shared_ptr<const EncodedData>, int);
-       void fake_write (int);
+       void write (boost::shared_ptr<const EncodedData>, int, Eyes);
+       void fake_write (int, Eyes);
        void write (boost::shared_ptr<const AudioBuffers>);
-       void repeat (int f);
+       void repeat (int f, Eyes);
        void finish ();
 
 private:
 
        void thread ();
        void check_existing_picture_mxf ();
+       bool check_existing_picture_mxf_frame (FILE *, int, Eyes);
+       bool have_sequenced_image_at_queue_head () const;
 
        /** our Film */
        boost::shared_ptr<const Film> _film;
@@ -101,6 +105,7 @@ private:
        boost::shared_ptr<const EncodedData> _last_written;
        /** the index of the last written frame */
        int _last_written_frame;
+       Eyes _last_written_eyes;
        /** maximum number of frames to hold in memory, for when we are managing
            ordering
        */
index 45d11cf80bf635c49ef72467b41eff2e467c17ce..f5d0ecae26e67a54715ecfe093f8bd4fc0b8e1e1 100644 (file)
@@ -109,15 +109,12 @@ AudioMappingView::left_click (wxGridEvent& ev)
        }
        
        if (_grid->GetCellValue (ev.GetRow(), ev.GetCol()) == wxT("1")) {
-               cout << "set " << ev.GetRow() << " " << ev.GetCol() << " to 0.\n";
                _grid->SetCellValue (ev.GetRow(), ev.GetCol(), wxT("0"));
        } else {
-               cout << "set " << ev.GetRow() << " " << ev.GetCol() << " to 1.\n";
                _grid->SetCellValue (ev.GetRow(), ev.GetCol(), wxT("1"));
        }
 
        _map = AudioMapping (_map.content_channels ());
-       cout << "was: " << _map.dcp_to_content(libdcp::CENTRE).size() << "\n";
        
        for (int i = 0; i < _grid->GetNumberRows(); ++i) {
                for (int j = 1; j < _grid->GetNumberCols(); ++j) {
@@ -127,7 +124,6 @@ AudioMappingView::left_click (wxGridEvent& ev)
                }
        }
 
-       cout << "changed: " << _map.dcp_to_content(libdcp::CENTRE).size() << "\n";
        Changed (_map);
 }
 
index 39935927f2ad498554f48f5f97e12f1f1ae75718..048791a10442e4a4a8c5dcf3850d89ba1635f85b 100644 (file)
@@ -151,6 +151,10 @@ FilmEditor::make_dcp_panel ()
        grid->Add (_dcp_audio_channels, wxGBPosition (r, 1));
        ++r;
 
+       _dcp_3d = new wxCheckBox (_dcp_panel, wxID_ANY, _("3D"));
+       grid->Add (_dcp_3d, wxGBPosition (r, 0), wxGBSpan (1, 2));
+       ++r;
+
        add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Resolution"), true, wxGBPosition (r, 0));
        _dcp_resolution = new wxChoice (_dcp_panel, wxID_ANY);
        grid->Add (_dcp_resolution, wxGBPosition (r, 1));
@@ -219,6 +223,7 @@ FilmEditor::connect_to_widgets ()
        _j2k_bandwidth->Connect          (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::j2k_bandwidth_changed), 0, this);
        _dcp_resolution->Connect         (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::dcp_resolution_changed), 0, this);
        _sequence_video->Connect         (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED,     wxCommandEventHandler (FilmEditor::sequence_video_changed), 0, this);
+       _dcp_3d->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&FilmEditor::dcp_3d_changed, this));
 }
 
 void
@@ -403,6 +408,9 @@ FilmEditor::film_changed (Film::Property p)
        case Film::SEQUENCE_VIDEO:
                checked_set (_sequence_video, _film->sequence_video ());
                break;
+       case Film::DCP_3D:
+               checked_set (_dcp_3d, _film->dcp_3d ());
+               break;
        }
 }
 
@@ -521,6 +529,7 @@ FilmEditor::set_film (shared_ptr<Film> f)
        film_changed (Film::DCP_VIDEO_FRAME_RATE);
        film_changed (Film::DCP_AUDIO_CHANNELS);
        film_changed (Film::SEQUENCE_VIDEO);
+       film_changed (Film::DCP_3D);
 
        if (!_film->content().empty ()) {
                set_selection (_film->content().front ());
@@ -549,6 +558,7 @@ FilmEditor::set_general_sensitivity (bool s)
        _sequence_video->Enable (s);
        _dcp_resolution->Enable (s);
        _scaler->Enable (s);
+       _dcp_3d->Enable (s);
 
        /* Set the panels in the content notebook */
        for (list<FilmEditorPanel*>::iterator i = _panels.begin(); i != _panels.end(); ++i) {
@@ -824,3 +834,13 @@ FilmEditor::content_right_click (wxListEvent& ev)
        cl.push_back (selected_content ());
        _menu.popup (cl, ev.GetPoint ());
 }
+
+void
+FilmEditor::dcp_3d_changed ()
+{
+       if (!_film) {
+               return;
+       }
+
+       _film->set_dcp_3d (_dcp_3d->GetValue ());
+}
index 66913643eb49fe8d48c6d7c6a1340c3bee0e82d0..9a9ba2906155b99c1d9703b321bc49ae5a7e8677 100644 (file)
@@ -89,6 +89,7 @@ private:
        void dcp_resolution_changed (wxCommandEvent &);
        void sequence_video_changed (wxCommandEvent &);
        void content_right_click (wxListEvent &);
+       void dcp_3d_changed ();
 
        /* Handle changes to the model */
        void film_changed (Film::Property);
@@ -135,6 +136,7 @@ private:
        wxChoice* _dcp_frame_rate;
        wxSpinCtrl* _dcp_audio_channels;
        wxButton* _best_dcp_frame_rate;
+       wxCheckBox* _dcp_3d;
        wxChoice* _dcp_resolution;
 
        ContentMenu _menu;
index 42654bde5acea733548b60aac9ecf24bde3becf1..b9d554c686b77ccad5af2fc9a8f463c750340501 100644 (file)
@@ -131,7 +131,7 @@ FilmViewer::set_film (shared_ptr<Film> f)
 
        _player = f->make_player ();
        _player->disable_audio ();
-       _player->Video.connect (boost::bind (&FilmViewer::process_video, this, _1, _3));
+       _player->Video.connect (boost::bind (&FilmViewer::process_video, this, _1, _2, _4));
        _player->Changed.connect (boost::bind (&FilmViewer::player_changed, this, _1));
 
        calculate_sizes ();
@@ -280,8 +280,12 @@ FilmViewer::check_play_state ()
 }
 
 void
-FilmViewer::process_video (shared_ptr<const Image> image, Time t)
+FilmViewer::process_video (shared_ptr<const Image> image, Eyes eyes, Time t)
 {
+       if (eyes == EYES_RIGHT) {
+               return;
+       }
+       
        if (_got_frame) {
                /* This is an additional frame emitted by a single pass.  Store it. */
                _queue.push_front (make_pair (image, t));
@@ -332,7 +336,7 @@ FilmViewer::fetch_next_frame ()
        _got_frame = false;
        
        if (!_queue.empty ()) {
-               process_video (_queue.back().first, _queue.back().second);
+               process_video (_queue.back().first, EYES_BOTH, _queue.back().second);
                _queue.pop_back ();
        } else {
                try {
index b45e53d319911c0525768a20a4464fa5a1f57622..af58b5467cb5b2ad633b91432dcab05b9fbb8b0a 100644 (file)
@@ -58,7 +58,7 @@ private:
        void slider_moved (wxScrollEvent &);
        void play_clicked (wxCommandEvent &);
        void timer (wxTimerEvent &);
-       void process_video (boost::shared_ptr<const Image>, Time);
+       void process_video (boost::shared_ptr<const Image>, Eyes, Time);
        void calculate_sizes ();
        void check_play_state ();
        void fetch_current_frame_again ();
index e08edfa5a919a20e1ac22153a6cd07e832abc9f0..ce0c869ffbfc464a9bcf10d2dfb5a42391298952 100644 (file)
@@ -28,6 +28,7 @@
 using std::vector;
 using std::string;
 using std::pair;
+using std::cout;
 using boost::shared_ptr;
 using boost::dynamic_pointer_cast;
 using boost::bind;
@@ -103,7 +104,7 @@ VideoPanel::VideoPanel (FilmEditor* e)
        _frame_type->Append (_("2D"));
        _frame_type->Append (_("3D left/right"));
 
-       _frame_type->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, bind (&VideoPanel::frame_type_changed, this));
+       _frame_type->Bind (wxEVT_COMMAND_CHOICE_SELECTED, bind (&VideoPanel::frame_type_changed, this));
        _left_crop->Connect      (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (VideoPanel::left_crop_changed), 0, this);
        _right_crop->Connect     (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (VideoPanel::right_crop_changed), 0, this);
        _top_crop->Connect       (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (VideoPanel::top_crop_changed), 0, this);
@@ -180,7 +181,7 @@ VideoPanel::film_content_changed (shared_ptr<Content> c, int property)
        shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c);
 
        if (property == VideoContentProperty::VIDEO_FRAME_TYPE) {
-               checked_set (_frame_type, vc->video_frame_type ());
+               checked_set (_frame_type, vc ? vc->video_frame_type () : VIDEO_FRAME_TYPE_2D);
        } else if (property == VideoContentProperty::VIDEO_CROP) {
                checked_set (_left_crop,   vc ? vc->crop().left : 0);
                checked_set (_right_crop,  vc ? vc->crop().right        : 0);
index 7b5b2b7eff1b3c14788616f6b1fbc23d69d313fa..2dc1545d603dd48192299276800f3f7977fd3dd5 100644 (file)
@@ -75,6 +75,7 @@ BOOST_AUTO_TEST_CASE (client_server_test)
                new DCPVideoFrame (
                        image,
                        0,
+                       EYES_BOTH,
                        24,
                        200000000,
                        log