summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2013-07-22 16:23:23 +0100
committerCarl Hetherington <cth@carlh.net>2013-07-22 16:23:23 +0100
commit8349f0c97d98c0b7550ff4c76ad25f8f06270d6a (patch)
treebf441e13142b2810ac4e46cf94d4204ec4e037d5 /src/lib
parent75712cfaf2a8ec8904d7d9552c542a2245bbbc17 (diff)
Basics of front-end 3D (as far as viewer, at least).
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/dcp_video_frame.cc32
-rw-r--r--src/lib/dcp_video_frame.h11
-rw-r--r--src/lib/encoder.cc23
-rw-r--r--src/lib/encoder.h4
-rw-r--r--src/lib/ffmpeg_decoder.cc2
-rw-r--r--src/lib/film.cc48
-rw-r--r--src/lib/film.h18
-rw-r--r--src/lib/player.cc15
-rw-r--r--src/lib/player.h9
-rw-r--r--src/lib/server.cc3
-rw-r--r--src/lib/still_image_decoder.cc2
-rw-r--r--src/lib/transcoder.cc6
-rw-r--r--src/lib/types.h9
-rw-r--r--src/lib/video_content.cc3
-rw-r--r--src/lib/video_decoder.cc17
-rw-r--r--src/lib/video_decoder.h10
-rw-r--r--src/lib/writer.cc103
-rw-r--r--src/lib/writer.h11
18 files changed, 232 insertions, 94 deletions
diff --git a/src/lib/dcp_video_frame.cc b/src/lib/dcp_video_frame.cc
index ef8db7d23..10f1e4ad1 100644
--- a/src/lib/dcp_video_frame.cc
+++ b/src/lib/dcp_video_frame.cc
@@ -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);
}
diff --git a/src/lib/dcp_video_frame.h b/src/lib/dcp_video_frame.h
index 4c545b6ef..e786d9b61 100644
--- a/src/lib/dcp_video_frame.h
+++ b/src/lib/dcp_video_frame.h
@@ -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
diff --git a/src/lib/encoder.cc b/src/lib/encoder.cc
index 718ae55a4..8e61a0d60 100644
--- a/src/lib/encoder.cc
+++ b/src/lib/encoder.cc
@@ -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 ();
diff --git a/src/lib/encoder.h b/src/lib/encoder.h
index a6e5932be..a866a77f1 100644
--- a/src/lib/encoder.h
+++ b/src/lib/encoder.h
@@ -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;
diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc
index 11cea8fb1..ea32d102d 100644
--- a/src/lib/ffmpeg_decoder.cc
+++ b/src/lib/ffmpeg_decoder.cc
@@ -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)
diff --git a/src/lib/film.cc b/src/lib/film.cc
index e39e94cfc..40c696256 100644
--- a/src/lib/film.cc
+++ b/src/lib/film.cc
@@ -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();
}
@@ -636,6 +650,16 @@ Film::set_dcp_audio_channels (int c)
}
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";
diff --git a/src/lib/film.h b/src/lib/film.h
index 99c214ab7..5aff6f0be 100644
--- a/src/lib/film.h
+++ b/src/lib/film.h
@@ -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 */
diff --git a/src/lib/player.cc b/src/lib/player.cc
index 98a31ae74..20beda945 100644
--- a/src/lib/player.cc
+++ b/src/lib/player.cc
@@ -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);
}
diff --git a/src/lib/player.h b/src/lib/player.h
index 568c7a7a1..8b28f010d 100644
--- a/src/lib/player.h
+++ b/src/lib/player.h
@@ -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 ();
diff --git a/src/lib/server.cc b/src/lib/server.cc
index 9af0883a7..37a076a54 100644
--- a/src/lib/server.cc
+++ b/src/lib/server.cc
@@ -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 ();
diff --git a/src/lib/still_image_decoder.cc b/src/lib/still_image_decoder.cc
index 21cc83f54..10e34427b 100644
--- a/src/lib/still_image_decoder.cc
+++ b/src/lib/still_image_decoder.cc
@@ -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)
{
diff --git a/src/lib/transcoder.cc b/src/lib/transcoder.cc
index 7022965bd..3002ef61c 100644
--- a/src/lib/transcoder.cc
+++ b/src/lib/transcoder.cc
@@ -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));
}
diff --git a/src/lib/types.h b/src/lib/types.h
index b1b359810..d6136fc3e 100644
--- a/src/lib/types.h
+++ b/src/lib/types.h
@@ -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;
diff --git a/src/lib/video_content.cc b/src/lib/video_content.cc
index 7eca53c3d..076cd6ec6 100644
--- a/src/lib/video_content.cc
+++ b/src/lib/video_content.cc
@@ -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);
diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc
index 38d5dfcb8..eaa4534e4 100644
--- a/src/lib/video_decoder.cc
+++ b/src/lib/video_decoder.cc
@@ -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;
}
diff --git a/src/lib/video_decoder.h b/src/lib/video_decoder.h
index 26a11c805..142320a04 100644
--- a/src/lib/video_decoder.h
+++ b/src/lib/video_decoder.h
@@ -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;
};
diff --git a/src/lib/writer.cc b/src/lib/writer.cc
index c5360a122..122dcd716 100644
--- a/src/lib/writer.cc
+++ b/src/lib/writer.cc
@@ -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;
}
diff --git a/src/lib/writer.h b/src/lib/writer.h
index 1e5d86489..023107d97 100644
--- a/src/lib/writer.h
+++ b/src/lib/writer.h
@@ -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
*/