From ee77b3cf5f59f775e75e628aa28e8f2f9f941530 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Tue, 25 Mar 2014 09:41:36 +0000 Subject: [PATCH] It builds. --- src/lib/analyse_audio_job.cc | 11 +- src/lib/analyse_audio_job.h | 2 +- src/lib/audio_decoder.cc | 64 ++- src/lib/audio_decoder.h | 17 +- src/lib/audio_merger.cc | 92 ---- src/lib/audio_merger.h | 41 -- src/lib/dcpomatic_time.h | 4 + src/lib/decoded.h | 142 ------ src/lib/decoder.cc | 76 ---- src/lib/decoder.h | 25 +- src/lib/encoder.cc | 27 +- src/lib/encoder.h | 8 +- src/lib/ffmpeg_decoder.cc | 40 +- src/lib/ffmpeg_decoder.h | 11 +- src/lib/image.cc | 32 ++ src/lib/image.h | 4 + src/lib/image_decoder.cc | 18 +- src/lib/image_decoder.h | 4 +- src/lib/player.cc | 802 +++++++++++----------------------- src/lib/player.h | 149 +++---- src/lib/rect.h | 24 +- src/lib/render_subtitles.cc | 12 +- src/lib/render_subtitles.h | 7 +- src/lib/sndfile_decoder.cc | 1 - src/lib/sndfile_decoder.h | 2 +- src/lib/subrip_decoder.cc | 11 + src/lib/subrip_decoder.h | 3 +- src/lib/subtitle_decoder.cc | 34 +- src/lib/subtitle_decoder.h | 11 +- src/lib/transcoder.cc | 28 +- src/lib/types.h | 5 + src/lib/util.cc | 39 +- src/lib/video_decoder.cc | 72 ++- src/lib/video_decoder.h | 14 +- src/lib/writer.cc | 45 +- src/lib/writer.h | 2 - src/lib/wscript | 4 +- src/tools/server_test.cc | 25 +- src/wx/film_viewer.cc | 122 ++---- src/wx/film_viewer.h | 19 +- src/wx/subtitle_view.cc | 19 +- test/audio_merger_test.cc | 103 ----- test/ffmpeg_pts_offset.cc | 10 +- test/ffmpeg_seek_test.cc | 117 ----- test/long_ffmpeg_seek_test.cc | 105 ----- test/play_test.cc | 126 ------ test/seek_zero_test.cc | 15 +- test/subrip_test.cc | 9 +- test/wscript | 4 - 49 files changed, 732 insertions(+), 1825 deletions(-) delete mode 100644 src/lib/audio_merger.cc delete mode 100644 src/lib/audio_merger.h delete mode 100644 src/lib/decoded.h delete mode 100644 src/lib/decoder.cc delete mode 100644 test/audio_merger_test.cc delete mode 100644 test/ffmpeg_seek_test.cc delete mode 100644 test/long_ffmpeg_seek_test.cc delete mode 100644 test/play_test.cc diff --git a/src/lib/analyse_audio_job.cc b/src/lib/analyse_audio_job.cc index fc0abe0a3..af58e77ac 100644 --- a/src/lib/analyse_audio_job.cc +++ b/src/lib/analyse_audio_job.cc @@ -66,10 +66,7 @@ AnalyseAudioJob::run () shared_ptr playlist (new Playlist); playlist->add (content); shared_ptr player (new Player (_film, playlist)); - player->disable_video (); - player->Audio.connect (bind (&AnalyseAudioJob::audio, this, _1, _2)); - int64_t const len = _film->length().frames (_film->audio_frame_rate()); _samples_per_point = max (int64_t (1), len / _num_points); @@ -77,8 +74,10 @@ AnalyseAudioJob::run () _analysis.reset (new AudioAnalysis (_film->audio_channels ())); _done = 0; - while (!player->pass ()) { - set_progress (double (_done) / len); + DCPTime const block = DCPTime::from_seconds (1.0 / 8); + for (DCPTime t; t < _film->length(); t += block) { + analyse (player->get_audio (t, block, false)); + set_progress (t.seconds() / _film->length().seconds()); } _analysis->write (content->audio_analysis_path ()); @@ -88,7 +87,7 @@ AnalyseAudioJob::run () } void -AnalyseAudioJob::audio (shared_ptr b, DCPTime) +AnalyseAudioJob::analyse (shared_ptr b) { for (int i = 0; i < b->frames(); ++i) { for (int j = 0; j < b->channels(); ++j) { diff --git a/src/lib/analyse_audio_job.h b/src/lib/analyse_audio_job.h index 0a0be8fa0..d3c35b67c 100644 --- a/src/lib/analyse_audio_job.h +++ b/src/lib/analyse_audio_job.h @@ -35,7 +35,7 @@ public: void run (); private: - void audio (boost::shared_ptr, DCPTime); + void analyse (boost::shared_ptr); boost::weak_ptr _content; int64_t _done; diff --git a/src/lib/audio_decoder.cc b/src/lib/audio_decoder.cc index 32453cc13..616abf846 100644 --- a/src/lib/audio_decoder.cc +++ b/src/lib/audio_decoder.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 Carl Hetherington + Copyright (C) 2012-2014 Carl Hetherington This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,6 +23,7 @@ #include "log.h" #include "resampler.h" #include "util.h" +#include "film.h" #include "i18n.h" @@ -41,7 +42,47 @@ AudioDecoder::AudioDecoder (shared_ptr content) } } -/** Audio timestamping is made hard by many factors, but the final nail in the coffin is resampling. +shared_ptr +AudioDecoder::get_audio (AudioFrame frame, AudioFrame length, bool accurate) +{ + shared_ptr dec; + + AudioFrame const end = frame + length - 1; + + if (frame < _decoded_audio.frame || end > (_decoded_audio.frame + length * 4)) { + /* Either we have no decoded data, or what we do have is a long way from what we want: seek */ + seek (ContentTime::from_frames (frame, _audio_content->content_audio_frame_rate()), accurate); + } + + /* Now enough pass() calls will either: + * (a) give us what we want, or + * (b) hit the end of the decoder. + * + * If we are being accurate, we want the right frames, + * otherwise any frames will do. + */ + if (accurate) { + while (!pass() && _decoded_audio.audio->frames() < length) {} + } else { + while (!pass() && (_decoded_audio.frame > frame || (_decoded_audio.frame + _decoded_audio.audio->frames()) < end)) {} + } + + /* Clean up decoded */ + + AudioFrame const decoded_offset = frame - _decoded_audio.frame; + AudioFrame const amount_left = _decoded_audio.audio->frames() - decoded_offset; + _decoded_audio.audio->move (decoded_offset, 0, amount_left); + _decoded_audio.audio->set_frames (amount_left); + + shared_ptr out (new AudioBuffers (_decoded_audio.audio->channels(), length)); + out->copy_from (_decoded_audio.audio.get(), length, frame - _decoded_audio.frame, 0); + + return shared_ptr (new ContentAudio (out, frame)); +} + +/** Called by subclasses when audio data is ready. + * + * Audio timestamping is made hard by many factors, but perhaps the most entertaining is resampling. * We have to assume that we are feeding continuous data into the resampler, and so we get continuous * data out. Hence we do the timestamping here, post-resampler, just by counting samples. * @@ -56,13 +97,20 @@ AudioDecoder::audio (shared_ptr data, ContentTime time) } if (!_audio_position) { - _audio_position = time; + _audio_position = time.frames (_audio_content->output_audio_frame_rate ()); } - _pending.push_back (shared_ptr (new DecodedAudio (_audio_position.get (), data))); - _audio_position = _audio_position.get() + ContentTime (data->frames (), _audio_content->output_audio_frame_rate ()); + assert (_audio_position >= (_decoded_audio.frame + _decoded_audio.audio->frames())); + + /* Resize _decoded_audio to fit the new data */ + _decoded_audio.audio->ensure_size (_audio_position.get() + data->frames() - _decoded_audio.frame); + + /* Copy new data in */ + _decoded_audio.audio->copy_from (data.get(), data->frames(), 0, _audio_position.get() - _decoded_audio.frame); + _audio_position = _audio_position.get() + data->frames (); } +/* XXX: called? */ void AudioDecoder::flush () { @@ -70,11 +118,13 @@ AudioDecoder::flush () return; } + /* shared_ptr b = _resampler->flush (); if (b) { - _pending.push_back (shared_ptr (new DecodedAudio (_audio_position.get (), b))); - _audio_position = _audio_position.get() + ContentTime (b->frames (), _audio_content->output_audio_frame_rate ()); + _pending.push_back (shared_ptr (new DecodedAudio (b, _audio_position.get ()))); + _audio_position = _audio_position.get() + b->frames (); } + */ } void diff --git a/src/lib/audio_decoder.h b/src/lib/audio_decoder.h index 35d9f3560..c83c356b7 100644 --- a/src/lib/audio_decoder.h +++ b/src/lib/audio_decoder.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 Carl Hetherington + Copyright (C) 2012-2014 Carl Hetherington This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -27,7 +27,7 @@ #include "decoder.h" #include "content.h" #include "audio_content.h" -#include "decoded.h" +#include "content_audio.h" class AudioBuffers; class Resampler; @@ -44,16 +44,25 @@ public: return _audio_content; } - void seek (ContentTime time, bool accurate); + /** Try to fetch some audio from a specific place in this content. + * @param frame Frame to start from. + * @param length Frames to get. + * @param accurate true to try hard to return frames from exactly `frame', false if we don't mind nearby frames. + * @return Time-stamped audio data which may or may not be from the location (and of the length) requested. + */ + boost::shared_ptr get_audio (AudioFrame time, AudioFrame length, bool accurate); protected: + void seek (ContentTime time, bool accurate); void audio (boost::shared_ptr, ContentTime); void flush (); boost::shared_ptr _audio_content; boost::shared_ptr _resampler; - boost::optional _audio_position; + boost::optional _audio_position; + /** Currently-available decoded audio data */ + ContentAudio _decoded_audio; }; #endif diff --git a/src/lib/audio_merger.cc b/src/lib/audio_merger.cc deleted file mode 100644 index ad110a44f..000000000 --- a/src/lib/audio_merger.cc +++ /dev/null @@ -1,92 +0,0 @@ -/* - Copyright (C) 2013-2014 Carl Hetherington - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -#include "audio_buffers.h" -#include "audio_merger.h" - -using std::min; -using std::max; -using boost::shared_ptr; - -AudioMerger::AudioMerger (int channels, int frame_rate) - : _buffers (new AudioBuffers (channels, 0)) - , _frame_rate (frame_rate) - , _last_pull (0) -{ - -} - - -TimedAudioBuffers -AudioMerger::pull (DCPTime time) -{ - assert (time >= _last_pull); - - TimedAudioBuffers out; - - int64_t const to_return = DCPTime (time - _last_pull).frames (_frame_rate); - out.audio.reset (new AudioBuffers (_buffers->channels(), to_return)); - /* And this is how many we will get from our buffer */ - int64_t const to_return_from_buffers = min (to_return, int64_t (_buffers->frames ())); - - /* Copy the data that we have to the back end of the return buffer */ - out.audio->copy_from (_buffers.get(), to_return_from_buffers, 0, to_return - to_return_from_buffers); - /* Silence any gap at the start */ - out.audio->make_silent (0, to_return - to_return_from_buffers); - - out.time = _last_pull; - _last_pull = time; - - /* And remove the data we're returning from our buffers */ - if (_buffers->frames() > to_return_from_buffers) { - _buffers->move (to_return_from_buffers, 0, _buffers->frames() - to_return_from_buffers); - } - _buffers->set_frames (_buffers->frames() - to_return_from_buffers); - - return out; -} - -void -AudioMerger::push (shared_ptr audio, DCPTime time) -{ - assert (time >= _last_pull); - - int64_t frame = time.frames (_frame_rate); - int64_t after = max (int64_t (_buffers->frames()), frame + audio->frames() - _last_pull.frames (_frame_rate)); - _buffers->ensure_size (after); - _buffers->accumulate_frames (audio.get(), 0, frame - _last_pull.frames (_frame_rate), audio->frames ()); - _buffers->set_frames (after); -} - -TimedAudioBuffers -AudioMerger::flush () -{ - if (_buffers->frames() == 0) { - return TimedAudioBuffers (); - } - - return TimedAudioBuffers (_buffers, _last_pull); -} - -void -AudioMerger::clear (DCPTime t) -{ - _last_pull = t; - _buffers.reset (new AudioBuffers (_buffers->channels(), 0)); -} diff --git a/src/lib/audio_merger.h b/src/lib/audio_merger.h deleted file mode 100644 index 756e5ab31..000000000 --- a/src/lib/audio_merger.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - Copyright (C) 2013-2014 Carl Hetherington - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -#include "util.h" - -class AudioBuffers; - -class AudioMerger -{ -public: - AudioMerger (int channels, int frame_rate); - - /** Pull audio up to a given time; after this call, no more data can be pushed - * before the specified time. - */ - TimedAudioBuffers pull (DCPTime time); - void push (boost::shared_ptr audio, DCPTime time); - TimedAudioBuffers flush (); - void clear (DCPTime t); - -private: - boost::shared_ptr _buffers; - int _frame_rate; - DCPTime _last_pull; -}; diff --git a/src/lib/dcpomatic_time.h b/src/lib/dcpomatic_time.h index b19a94ad7..e0d47d9c8 100644 --- a/src/lib/dcpomatic_time.h +++ b/src/lib/dcpomatic_time.h @@ -138,6 +138,10 @@ public: static ContentTime from_frames (int64_t f, T r) { return ContentTime (f * HZ / r); } + + static ContentTime max () { + return ContentTime (INT64_MAX); + } }; std::ostream& operator<< (std::ostream& s, ContentTime t); diff --git a/src/lib/decoded.h b/src/lib/decoded.h deleted file mode 100644 index ebf4e57c1..000000000 --- a/src/lib/decoded.h +++ /dev/null @@ -1,142 +0,0 @@ -/* - Copyright (C) 2013 Carl Hetherington - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -#ifndef DCPOMATIC_LIB_DECODED_H -#define DCPOMATIC_LIB_DECODED_H - -#include -#include "types.h" -#include "rect.h" -#include "util.h" - -class Image; - -class Decoded -{ -public: - Decoded () - : content_time (0) - , dcp_time (0) - {} - - Decoded (ContentTime t) - : content_time (t) - , dcp_time (0) - {} - - virtual ~Decoded () {} - - virtual void set_dcp_times (FrameRateChange frc, DCPTime offset) - { - dcp_time = DCPTime (content_time, frc) + offset; - } - - ContentTime content_time; - DCPTime dcp_time; -}; - -/** One frame of video from a VideoDecoder */ -class DecodedVideo : public Decoded -{ -public: - DecodedVideo () - : eyes (EYES_BOTH) - , same (false) - {} - - DecodedVideo (ContentTime t, boost::shared_ptr im, Eyes e, bool s) - : Decoded (t) - , image (im) - , eyes (e) - , same (s) - {} - - boost::shared_ptr image; - Eyes eyes; - bool same; -}; - -class DecodedAudio : public Decoded -{ -public: - DecodedAudio (ContentTime t, boost::shared_ptr d) - : Decoded (t) - , data (d) - {} - - boost::shared_ptr data; -}; - -class DecodedImageSubtitle : public Decoded -{ -public: - DecodedImageSubtitle () - : content_time_to (0) - , dcp_time_to (0) - {} - - DecodedImageSubtitle (ContentTime f, ContentTime t, boost::shared_ptr im, dcpomatic::Rect r) - : Decoded (f) - , content_time_to (t) - , dcp_time_to (0) - , image (im) - , rect (r) - {} - - void set_dcp_times (FrameRateChange frc, DCPTime offset) - { - Decoded::set_dcp_times (frc, offset); - dcp_time_to = DCPTime (content_time_to, frc) + offset; - } - - ContentTime content_time_to; - DCPTime dcp_time_to; - boost::shared_ptr image; - dcpomatic::Rect rect; -}; - -class DecodedTextSubtitle : public Decoded -{ -public: - DecodedTextSubtitle () - : content_time_to (0) - , dcp_time_to (0) - {} - - /* Assuming that all subs are at the same time */ - DecodedTextSubtitle (std::list s) - : Decoded (ContentTime::from_seconds (s.front().in().to_ticks() * 4 / 1000.0)) - , content_time_to (ContentTime::from_seconds (s.front().out().to_ticks() * 4 / 1000.0)) - , subs (s) - { - - } - - void set_dcp_times (FrameRateChange frc, DCPTime offset) - { - Decoded::set_dcp_times (frc, offset); - dcp_time_to = DCPTime (content_time_to, frc) + offset; - } - - ContentTime content_time_to; - DCPTime dcp_time_to; - std::list subs; -}; - -#endif diff --git a/src/lib/decoder.cc b/src/lib/decoder.cc deleted file mode 100644 index 0901f73b0..000000000 --- a/src/lib/decoder.cc +++ /dev/null @@ -1,76 +0,0 @@ -/* - Copyright (C) 2012 Carl Hetherington - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -/** @file src/decoder.cc - * @brief Parent class for decoders of content. - */ - -#include "decoder.h" -#include "decoded.h" - -#include "i18n.h" - -using std::cout; -using boost::shared_ptr; - -/** @param o Decode options. - */ -Decoder::Decoder () - : _done (false) -{ - -} - -struct DecodedSorter -{ - bool operator() (shared_ptr a, shared_ptr b) - { - return a->dcp_time < b->dcp_time; - } -}; - -shared_ptr -Decoder::peek () -{ - while (!_done && _pending.empty ()) { - _done = pass (); - } - - if (_done && _pending.empty ()) { - return shared_ptr (); - } - - _pending.sort (DecodedSorter ()); - return _pending.front (); -} - -void -Decoder::consume () -{ - if (!_pending.empty ()) { - _pending.pop_front (); - } -} - -void -Decoder::seek (ContentTime, bool) -{ - _pending.clear (); - _done = false; -} diff --git a/src/lib/decoder.h b/src/lib/decoder.h index 0f14dbba7..df3ac4f39 100644 --- a/src/lib/decoder.h +++ b/src/lib/decoder.h @@ -31,6 +31,7 @@ #include "dcpomatic_time.h" class Decoded; +class Film; /** @class Decoder. * @brief Parent class for decoders of content. @@ -38,36 +39,16 @@ class Decoded; class Decoder : public boost::noncopyable { public: - Decoder (); virtual ~Decoder () {} +protected: /** Seek so that the next peek() will yield the next thing * (video/sound frame, subtitle etc.) at or after the requested * time. Pass accurate = true to try harder to get close to * the request. */ - virtual void seek (ContentTime time, bool accurate); - - boost::shared_ptr peek (); - - /* Consume the last peek()ed thing so that it won't be returned - * from the next peek(). - */ - void consume (); - -protected: - - /** Perform one decode pass of the content, which may or may not - * result in a complete quantum (Decoded object) of decoded stuff - * being added to _pending. - * @return true if the decoder is done (i.e. no more data will be - * produced by any future calls to pass() without a seek() first). - */ + virtual void seek (ContentTime time, bool accurate) = 0; virtual bool pass () = 0; - virtual void flush () {}; - - std::list > _pending; - bool _done; }; #endif diff --git a/src/lib/encoder.cc b/src/lib/encoder.cc index 7863859de..6b1c6a200 100644 --- a/src/lib/encoder.cc +++ b/src/lib/encoder.cc @@ -35,6 +35,7 @@ #include "writer.h" #include "server_finder.h" #include "player.h" +#include "dcp_video.h" #include "i18n.h" @@ -60,9 +61,7 @@ Encoder::Encoder (shared_ptr f, weak_ptr j) , _video_frames_out (0) , _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 () @@ -178,7 +177,7 @@ Encoder::frame_done () } void -Encoder::process_video (shared_ptr image, Eyes eyes, ColourConversion conversion, bool same) +Encoder::process_video (shared_ptr frame) { _waker.nudge (); @@ -205,28 +204,28 @@ Encoder::process_video (shared_ptr image, Eyes eyes, ColourConversi rethrow (); if (_writer->can_fake_write (_video_frames_out)) { - _writer->fake_write (_video_frames_out, eyes); - _have_a_real_frame[eyes] = false; - frame_done (); - } else if (same && _have_a_real_frame[eyes]) { - /* Use the last frame that we encoded. */ - _writer->repeat (_video_frames_out, eyes); + _writer->fake_write (_video_frames_out, frame->eyes ()); frame_done (); } else { /* Queue this new frame for encoding */ TIMING ("adding to queue of %1", _queue.size ()); _queue.push_back (shared_ptr ( new DCPVideoFrame ( - image->image(PIX_FMT_RGB24, false), _video_frames_out, eyes, conversion, _film->video_frame_rate(), - _film->j2k_bandwidth(), _film->resolution(), _film->log() + frame->image(PIX_FMT_RGB24, false), + _video_frames_out, + frame->eyes(), + frame->conversion(), + _film->video_frame_rate(), + _film->j2k_bandwidth(), + _film->resolution(), + _film->log() ) )); _condition.notify_all (); - _have_a_real_frame[eyes] = true; } - if (eyes != EYES_LEFT) { + if (frame->eyes() != EYES_LEFT) { ++_video_frames_out; } } diff --git a/src/lib/encoder.h b/src/lib/encoder.h index e0ee2d414..6c465f816 100644 --- a/src/lib/encoder.h +++ b/src/lib/encoder.h @@ -48,7 +48,7 @@ class EncodedData; class Writer; class Job; class ServerFinder; -class PlayerImage; +class DCPVideo; /** @class Encoder * @brief Encoder to J2K and WAV for DCP. @@ -67,10 +67,9 @@ public: void process_begin (); /** Call with a frame of video. - * @param i Video frame image. - * @param same true if i is the same as the last time we were called. + * @param f Video frame. */ - void process_video (boost::shared_ptr i, Eyes eyes, ColourConversion, bool same); + void process_video (boost::shared_ptr f); /** Call with some audio data */ void process_audio (boost::shared_ptr); @@ -106,7 +105,6 @@ private: /** Number of video frames written for the DCP so far */ int _video_frames_out; - bool _have_a_real_frame[EYES_COUNT]; bool _terminate; std::list > _queue; std::list _threads; diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc index 32b00a1d6..c9efd80fc 100644 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@ -57,17 +57,13 @@ using boost::optional; using boost::dynamic_pointer_cast; using dcp::Size; -FFmpegDecoder::FFmpegDecoder (shared_ptr c, shared_ptr log, bool video, bool audio, bool subtitles) +FFmpegDecoder::FFmpegDecoder (shared_ptr c, shared_ptr log) : VideoDecoder (c) , AudioDecoder (c) , FFmpeg (c) , _log (log) , _subtitle_codec_context (0) , _subtitle_codec (0) - , _decode_video (video) - , _decode_audio (audio) - , _decode_subtitles (subtitles) - , _pts_offset (0) { setup_subtitle (); @@ -83,8 +79,8 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr c, shared_ptr We will do pts_to_use = pts_from_ffmpeg + pts_offset; */ - bool const have_video = video && c->first_video(); - bool const have_audio = _decode_audio && c->audio_stream () && c->audio_stream()->first_audio; + bool const have_video = c->first_video(); + bool const have_audio = c->audio_stream () && c->audio_stream()->first_audio; /* First, make one of them start at 0 */ @@ -123,11 +119,9 @@ FFmpegDecoder::flush () /* XXX: should we reset _packet.data and size after each *_decode_* call? */ - if (_decode_video) { - while (decode_video_packet ()) {} - } + while (decode_video_packet ()) {} - if (_ffmpeg_content->audio_stream() && _decode_audio) { + if (_ffmpeg_content->audio_stream()) { decode_audio_packet (); AudioDecoder::flush (); } @@ -152,11 +146,11 @@ FFmpegDecoder::pass () int const si = _packet.stream_index; - if (si == _video_stream && _decode_video) { + if (si == _video_stream) { decode_video_packet (); - } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, si) && _decode_audio) { + } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, si)) { decode_audio_packet (); - } else if (_ffmpeg_content->subtitle_stream() && _ffmpeg_content->subtitle_stream()->uses_index (_format_context, si) && _decode_subtitles) { + } else if (_ffmpeg_content->subtitle_stream() && _ffmpeg_content->subtitle_stream()->uses_index (_format_context, si)) { decode_subtitle_packet (); } @@ -390,10 +384,8 @@ FFmpegDecoder::seek_and_flush (ContentTime t) void FFmpegDecoder::seek (ContentTime time, bool accurate) { - Decoder::seek (time, accurate); - if (_decode_audio) { - AudioDecoder::seek (time, accurate); - } + VideoDecoder::seek (time, accurate); + AudioDecoder::seek (time, accurate); /* If we are doing an accurate seek, our initial shot will be 200ms (200 being a number plucked from the air) earlier than we want to end up. The loop below @@ -492,7 +484,8 @@ FFmpegDecoder::decode_video_packet () shared_ptr image = i->first; if (i->second != AV_NOPTS_VALUE) { - video (image, false, ContentTime::from_seconds (i->second * av_q2d (_format_context->streams[_video_stream]->time_base)) + _pts_offset); + double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset.seconds (); + video (image, rint (pts * _ffmpeg_content->video_frame_rate ())); } else { _log->log ("Dropping frame without PTS"); } @@ -540,7 +533,7 @@ FFmpegDecoder::decode_subtitle_packet () indicate that the previous subtitle should stop. */ if (sub.num_rects <= 0) { - image_subtitle (shared_ptr (), dcpomatic::Rect (), ContentTime (), ContentTime ()); + image_subtitle (ContentTime (), ContentTime (), shared_ptr (), dcpomatic::Rect ()); return; } else if (sub.num_rects > 1) { throw DecodeError (_("multi-part subtitles not yet supported")); @@ -589,17 +582,16 @@ FFmpegDecoder::decode_subtitle_packet () dcp::Size const vs = _ffmpeg_content->video_size (); image_subtitle ( + from, + to, image, dcpomatic::Rect ( static_cast (rect->x) / vs.width, static_cast (rect->y) / vs.height, static_cast (rect->w) / vs.width, static_cast (rect->h) / vs.height - ), - from, - to + ) ); - avsubtitle_free (&sub); } diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h index 15fe5d9a4..2cda8f89d 100644 --- a/src/lib/ffmpeg_decoder.h +++ b/src/lib/ffmpeg_decoder.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 Carl Hetherington + Copyright (C) 2012-2014 Carl Hetherington This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -47,14 +47,13 @@ class ffmpeg_pts_offset_test; class FFmpegDecoder : public VideoDecoder, public AudioDecoder, public SubtitleDecoder, public FFmpeg { public: - FFmpegDecoder (boost::shared_ptr, boost::shared_ptr, bool video, bool audio, bool subtitles); + FFmpegDecoder (boost::shared_ptr, boost::shared_ptr); ~FFmpegDecoder (); - void seek (ContentTime time, bool); - private: friend class ::ffmpeg_pts_offset_test; + void seek (ContentTime time, bool); bool pass (); void flush (); @@ -82,9 +81,5 @@ private: std::list > _filter_graphs; boost::mutex _filter_graphs_mutex; - bool _decode_video; - bool _decode_audio; - bool _decode_subtitles; - ContentTime _pts_offset; }; diff --git a/src/lib/image.cc b/src/lib/image.cc index 926aefd36..432cfbd54 100644 --- a/src/lib/image.cc +++ b/src/lib/image.cc @@ -31,6 +31,7 @@ extern "C" { #include "exceptions.h" #include "scaler.h" #include "timer.h" +#include "rect.h" #include "i18n.h" @@ -38,6 +39,7 @@ using std::string; using std::min; using std::cout; using std::cerr; +using std::list; using boost::shared_ptr; using dcp::Size; @@ -342,6 +344,16 @@ Image::make_black () } } +void +Image::make_transparent () +{ + if (_pixel_format != PIX_FMT_RGBA) { + throw PixelFormatError ("make_transparent()", _pixel_format); + } + + memset (data()[0], 0, lines(0) * stride()[0]); +} + void Image::alpha_blend (shared_ptr other, Position position) { @@ -630,3 +642,23 @@ Image::aligned () const return _aligned; } +PositionImage +merge (list images) +{ + if (images.empty ()) { + return PositionImage (); + } + + dcpomatic::Rect all (images.front().position, images.front().image->size().width, images.front().image->size().height); + for (list::const_iterator i = images.begin(); i != images.end(); ++i) { + all.extend (dcpomatic::Rect (i->position, i->image->size().width, i->image->size().height)); + } + + shared_ptr merged (new Image (images.front().image->pixel_format (), dcp::Size (all.width, all.height), true)); + merged->make_transparent (); + for (list::const_iterator i = images.begin(); i != images.end(); ++i) { + merged->alpha_blend (i->image, i->position); + } + + return PositionImage (merged, all.position ()); +} diff --git a/src/lib/image.h b/src/lib/image.h index 5eba11041..23b88dd76 100644 --- a/src/lib/image.h +++ b/src/lib/image.h @@ -34,6 +34,7 @@ extern "C" { #include #include "util.h" #include "position.h" +#include "position_image.h" class Scaler; @@ -63,6 +64,7 @@ public: boost::shared_ptr crop_scale_window (Crop c, dcp::Size, dcp::Size, Scaler const *, AVPixelFormat, bool aligned) const; void make_black (); + void make_transparent (); void alpha_blend (boost::shared_ptr image, Position pos); void copy (boost::shared_ptr image, Position pos); @@ -89,4 +91,6 @@ private: bool _aligned; }; +extern PositionImage merge (std::list images); + #endif diff --git a/src/lib/image_decoder.cc b/src/lib/image_decoder.cc index 58ba732bb..5de0c8582 100644 --- a/src/lib/image_decoder.cc +++ b/src/lib/image_decoder.cc @@ -42,21 +42,19 @@ ImageDecoder::ImageDecoder (shared_ptr c) bool ImageDecoder::pass () { - if (_video_position >= _image_content->video_length ()) { + if (_video_position >= _image_content->video_length().frames (_image_content->video_frame_rate ())) { return true; } if (_image && _image_content->still ()) { - video (_image, true, _video_position); - _video_position += ContentTime::from_frames (1, _image_content->video_frame_rate ()); + video (_image, _video_position); + ++_video_position; return false; } Magick::Image* magick_image = 0; - boost::filesystem::path const path = _image_content->path ( - _image_content->still() ? 0 : _video_position.frames (_image_content->video_frame_rate ()) - ); + boost::filesystem::path const path = _image_content->path (_image_content->still() ? 0 : _video_position); try { magick_image = new Magick::Image (path.string ()); @@ -84,8 +82,8 @@ ImageDecoder::pass () delete magick_image; - video (_image, false, _video_position); - _video_position += ContentTime::from_frames (1, _image_content->video_frame_rate ()); + video (_image, _video_position); + ++_video_position; return false; } @@ -93,6 +91,6 @@ ImageDecoder::pass () void ImageDecoder::seek (ContentTime time, bool accurate) { - Decoder::seek (time, accurate); - _video_position = time; + VideoDecoder::seek (time, accurate); + _video_position = time.frames (_image_content->video_frame_rate ()); } diff --git a/src/lib/image_decoder.h b/src/lib/image_decoder.h index 13ffea13d..8d88df3de 100644 --- a/src/lib/image_decoder.h +++ b/src/lib/image_decoder.h @@ -28,7 +28,7 @@ class ImageContent; class ImageDecoder : public VideoDecoder { public: - ImageDecoder (boost::shared_ptr); + ImageDecoder (boost::shared_ptr c); boost::shared_ptr content () { return _image_content; @@ -41,6 +41,6 @@ private: boost::shared_ptr _image_content; boost::shared_ptr _image; - ContentTime _video_position; + VideoFrame _video_position; }; diff --git a/src/lib/player.cc b/src/lib/player.cc index f07bf4980..8902929cc 100644 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2013 Carl Hetherington + Copyright (C) 2013-2014 Carl Hetherington This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -38,45 +38,30 @@ #include "log.h" #include "scaler.h" #include "render_subtitles.h" +#include "dcp_video.h" +#include "config.h" +#include "content_video.h" using std::list; using std::cout; using std::min; using std::max; +using std::min; using std::vector; using std::pair; using std::map; +using std::make_pair; using boost::shared_ptr; using boost::weak_ptr; using boost::dynamic_pointer_cast; using boost::optional; -class Piece -{ -public: - Piece (shared_ptr c, shared_ptr d, FrameRateChange f) - : content (c) - , decoder (d) - , frc (f) - {} - - shared_ptr content; - shared_ptr decoder; - FrameRateChange frc; -}; - Player::Player (shared_ptr f, shared_ptr p) : _film (f) , _playlist (p) - , _video (true) - , _audio (true) , _have_valid_pieces (false) - , _video_position (0) - , _audio_position (0) - , _audio_merger (f->audio_channels(), f->audio_frame_rate ()) - , _last_emit_was_black (false) - , _just_did_inaccurate_seek (false) , _approximate_size (false) + , _burn_subtitles (false) { _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this)); _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2, _3)); @@ -84,364 +69,6 @@ Player::Player (shared_ptr f, shared_ptr p) set_video_container_size (_film->frame_size ()); } -void -Player::disable_video () -{ - _video = false; -} - -void -Player::disable_audio () -{ - _audio = false; -} - -bool -Player::pass () -{ - if (!_have_valid_pieces) { - setup_pieces (); - } - - /* Interrogate all our pieces to find the one with the earliest decoded data */ - - shared_ptr earliest_piece; - shared_ptr earliest_decoded; - DCPTime earliest_time = DCPTime::max (); - DCPTime earliest_audio = DCPTime::max (); - - for (list >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) { - - DCPTime const offset = (*i)->content->position() - (*i)->content->trim_start(); - - bool done = false; - shared_ptr dec; - while (!done) { - dec = (*i)->decoder->peek (); - if (!dec) { - /* Decoder has nothing else to give us */ - break; - } - - dec->set_dcp_times ((*i)->frc, offset); - DCPTime const t = dec->dcp_time - offset; - if (t >= ((*i)->content->full_length() - (*i)->content->trim_end ())) { - /* In the end-trimmed part; decoder has nothing else to give us */ - dec.reset (); - done = true; - } else if (t >= (*i)->content->trim_start ()) { - /* Within the un-trimmed part; everything's ok */ - done = true; - } else { - /* Within the start-trimmed part; get something else */ - (*i)->decoder->consume (); - } - } - - if (!dec) { - continue; - } - - if (dec->dcp_time < earliest_time) { - earliest_piece = *i; - earliest_decoded = dec; - earliest_time = dec->dcp_time; - } - - if (dynamic_pointer_cast (dec) && dec->dcp_time < earliest_audio) { - earliest_audio = dec->dcp_time; - } - } - - if (!earliest_piece) { - flush (); - return true; - } - - if (earliest_audio != DCPTime::max ()) { - if (earliest_audio.get() < 0) { - earliest_audio = DCPTime (); - } - TimedAudioBuffers tb = _audio_merger.pull (earliest_audio); - Audio (tb.audio, tb.time); - /* This assumes that the audio-frames-to-time conversion is exact - so that there are no accumulated errors caused by rounding. - */ - _audio_position += DCPTime::from_frames (tb.audio->frames(), _film->audio_frame_rate ()); - } - - /* Emit the earliest thing */ - - shared_ptr dv = dynamic_pointer_cast (earliest_decoded); - shared_ptr da = dynamic_pointer_cast (earliest_decoded); - shared_ptr dis = dynamic_pointer_cast (earliest_decoded); - shared_ptr dts = dynamic_pointer_cast (earliest_decoded); - - /* Will be set to false if we shouldn't consume the peeked DecodedThing */ - bool consume = true; - - if (dv && _video) { - - if (_just_did_inaccurate_seek) { - - /* Just emit; no subtlety */ - emit_video (earliest_piece, dv); - step_video_position (dv); - - } else if (dv->dcp_time > _video_position) { - - /* Too far ahead */ - - list >::iterator i = _pieces.begin(); - while (i != _pieces.end() && ((*i)->content->position() >= _video_position || _video_position >= (*i)->content->end())) { - ++i; - } - - if (i == _pieces.end() || !_last_incoming_video.video || !_have_valid_pieces) { - /* We're outside all video content */ - emit_black (); - _statistics.video.black++; - } else { - /* We're inside some video; repeat the frame */ - _last_incoming_video.video->dcp_time = _video_position; - emit_video (_last_incoming_video.weak_piece, _last_incoming_video.video); - step_video_position (_last_incoming_video.video); - _statistics.video.repeat++; - } - - consume = false; - - } else if (dv->dcp_time == _video_position) { - /* We're ok */ - emit_video (earliest_piece, dv); - step_video_position (dv); - _statistics.video.good++; - } else { - /* Too far behind: skip */ - _statistics.video.skip++; - } - - _just_did_inaccurate_seek = false; - - } else if (da && _audio) { - - if (da->dcp_time > _audio_position) { - /* Too far ahead */ - emit_silence (da->dcp_time - _audio_position); - consume = false; - _statistics.audio.silence += (da->dcp_time - _audio_position); - } else if (da->dcp_time == _audio_position) { - /* We're ok */ - emit_audio (earliest_piece, da); - _statistics.audio.good += da->data->frames(); - } else { - /* Too far behind: skip */ - _statistics.audio.skip += da->data->frames(); - } - - } else if (dis && _video) { - _image_subtitle.piece = earliest_piece; - _image_subtitle.subtitle = dis; - update_subtitle_from_image (); - } else if (dts && _video) { - _text_subtitle.piece = earliest_piece; - _text_subtitle.subtitle = dts; - update_subtitle_from_text (); - } - - if (consume) { - earliest_piece->decoder->consume (); - } - - return false; -} - -void -Player::emit_video (weak_ptr weak_piece, shared_ptr video) -{ - /* Keep a note of what came in so that we can repeat it if required */ - _last_incoming_video.weak_piece = weak_piece; - _last_incoming_video.video = video; - - shared_ptr piece = weak_piece.lock (); - if (!piece) { - return; - } - - shared_ptr content = dynamic_pointer_cast (piece->content); - assert (content); - - FrameRateChange frc (content->video_frame_rate(), _film->video_frame_rate()); - - dcp::Size image_size = content->scale().size (content, _video_container_size, _film->frame_size ()); - if (_approximate_size) { - image_size.width &= ~3; - image_size.height &= ~3; - } - - shared_ptr pi ( - new PlayerImage ( - video->image, - content->crop(), - image_size, - _video_container_size, - _film->scaler() - ) - ); - - if ( - _film->with_subtitles () && - _out_subtitle.image && - video->dcp_time >= _out_subtitle.from && video->dcp_time <= _out_subtitle.to - ) { - - Position const container_offset ( - (_video_container_size.width - image_size.width) / 2, - (_video_container_size.height - image_size.height) / 2 - ); - - pi->set_subtitle (_out_subtitle.image, _out_subtitle.position + container_offset); - } - - -#ifdef DCPOMATIC_DEBUG - _last_video = piece->content; -#endif - - Video (pi, video->eyes, content->colour_conversion(), video->same, video->dcp_time); - - _last_emit_was_black = false; -} - -void -Player::step_video_position (shared_ptr video) -{ - /* This is a bit of a hack; don't update _video_position if EYES_RIGHT is on its way */ - if (video->eyes != EYES_LEFT) { - /* This assumes that the video-frames-to-time conversion is exact - so that there are no accumulated errors caused by rounding. - */ - _video_position += DCPTime::from_frames (1, _film->video_frame_rate ()); - } -} - -void -Player::emit_audio (weak_ptr weak_piece, shared_ptr audio) -{ - shared_ptr piece = weak_piece.lock (); - if (!piece) { - return; - } - - shared_ptr content = dynamic_pointer_cast (piece->content); - assert (content); - - /* Gain */ - if (content->audio_gain() != 0) { - shared_ptr gain (new AudioBuffers (audio->data)); - gain->apply_gain (content->audio_gain ()); - audio->data = gain; - } - - /* Remap channels */ - shared_ptr dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->data->frames())); - dcp_mapped->make_silent (); - AudioMapping map = content->audio_mapping (); - for (int i = 0; i < map.content_channels(); ++i) { - for (int j = 0; j < _film->audio_channels(); ++j) { - if (map.get (i, static_cast (j)) > 0) { - dcp_mapped->accumulate_channel ( - audio->data.get(), - i, - static_cast (j), - map.get (i, static_cast (j)) - ); - } - } - } - - audio->data = dcp_mapped; - - /* Delay */ - audio->dcp_time += DCPTime::from_seconds (content->audio_delay() / 1000.0); - if (audio->dcp_time < DCPTime (0)) { - int const frames = - audio->dcp_time.frames (_film->audio_frame_rate()); - if (frames >= audio->data->frames ()) { - return; - } - - shared_ptr trimmed (new AudioBuffers (audio->data->channels(), audio->data->frames() - frames)); - trimmed->copy_from (audio->data.get(), audio->data->frames() - frames, frames, 0); - - audio->data = trimmed; - audio->dcp_time = DCPTime (); - } - - _audio_merger.push (audio->data, audio->dcp_time); -} - -void -Player::flush () -{ - TimedAudioBuffers tb = _audio_merger.flush (); - if (_audio && tb.audio) { - Audio (tb.audio, tb.time); - _audio_position += DCPTime::from_frames (tb.audio->frames (), _film->audio_frame_rate ()); - } - - while (_video && _video_position < _audio_position) { - emit_black (); - } - - while (_audio && _audio_position < _video_position) { - emit_silence (_video_position - _audio_position); - } -} - -/** Seek so that the next pass() will yield (approximately) the requested frame. - * Pass accurate = true to try harder to get close to the request. - * @return true on error - */ -void -Player::seek (DCPTime t, bool accurate) -{ - if (!_have_valid_pieces) { - setup_pieces (); - } - - if (_pieces.empty ()) { - return; - } - - for (list >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) { - /* s is the offset of t from the start position of this content */ - DCPTime s = t - (*i)->content->position (); - s = max (static_cast (0), s); - s = min ((*i)->content->length_after_trim(), s); - - /* Convert this to the content time */ - ContentTime ct (s + (*i)->content->trim_start(), (*i)->frc); - - /* And seek the decoder */ - (*i)->decoder->seek (ct, accurate); - } - - _video_position = t.round_up (_film->video_frame_rate()); - _audio_position = t.round_up (_film->audio_frame_rate()); - - _audio_merger.clear (_audio_position); - - if (!accurate) { - /* We just did an inaccurate seek, so it's likely that the next thing seen - out of pass() will be a fair distance from _{video,audio}_position. Setting - this flag stops pass() from trying to fix that: we assume that if it - was an inaccurate seek then the caller does not care too much about - inserting black/silence to keep the time tidy. - */ - _just_did_inaccurate_seek = true; - } -} - void Player::setup_pieces () { @@ -486,7 +113,7 @@ Player::setup_pieces () /* FFmpeg */ shared_ptr fc = dynamic_pointer_cast (*i); if (fc) { - decoder.reset (new FFmpegDecoder (fc, _film->log(), _video, _audio, _film->with_subtitles ())); + decoder.reset (new FFmpegDecoder (fc, _film->log())); frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate()); } @@ -522,19 +149,10 @@ Player::setup_pieces () frc = best_overlap_frc; } - ContentTime st ((*i)->trim_start(), frc.get ()); - decoder->seek (st, true); - _pieces.push_back (shared_ptr (new Piece (*i, decoder, frc.get ()))); } _have_valid_pieces = true; - - /* The Piece for the _last_incoming_video will no longer be valid */ - _last_incoming_video.video.reset (); - - _video_position = DCPTime (); - _audio_position = DCPTime (); } void @@ -546,9 +164,12 @@ Player::content_changed (weak_ptr w, int property, bool frequent) } if ( - property == ContentProperty::POSITION || property == ContentProperty::LENGTH || - property == ContentProperty::TRIM_START || property == ContentProperty::TRIM_END || - property == VideoContentProperty::VIDEO_FRAME_TYPE + property == ContentProperty::POSITION || + property == ContentProperty::LENGTH || + property == ContentProperty::TRIM_START || + property == ContentProperty::TRIM_END || + property == ContentProperty::PATH || + property == VideoContentProperty::VIDEO_FRAME_TYPE ) { _have_valid_pieces = false; @@ -557,24 +178,13 @@ Player::content_changed (weak_ptr w, int property, bool frequent) } else if ( property == SubtitleContentProperty::SUBTITLE_X_OFFSET || property == SubtitleContentProperty::SUBTITLE_Y_OFFSET || - property == SubtitleContentProperty::SUBTITLE_SCALE - ) { - - update_subtitle_from_image (); - update_subtitle_from_text (); - Changed (frequent); - - } else if ( - property == VideoContentProperty::VIDEO_CROP || property == VideoContentProperty::VIDEO_SCALE || + property == SubtitleContentProperty::SUBTITLE_SCALE || + property == VideoContentProperty::VIDEO_CROP || + property == VideoContentProperty::VIDEO_SCALE || property == VideoContentProperty::VIDEO_FRAME_RATE ) { Changed (frequent); - - } else if (property == ContentProperty::PATH) { - - _have_valid_pieces = false; - Changed (frequent); } } @@ -590,45 +200,8 @@ Player::set_video_container_size (dcp::Size s) { _video_container_size = s; - shared_ptr im (new Image (PIX_FMT_RGB24, _video_container_size, true)); - im->make_black (); - - _black_frame.reset ( - new PlayerImage ( - im, - Crop(), - _video_container_size, - _video_container_size, - Scaler::from_id ("bicubic") - ) - ); -} - -void -Player::emit_black () -{ -#ifdef DCPOMATIC_DEBUG - _last_video.reset (); -#endif - - Video (_black_frame, EYES_BOTH, ColourConversion(), _last_emit_was_black, _video_position); - _video_position += DCPTime::from_frames (1, _film->video_frame_rate ()); - _last_emit_was_black = true; -} - -void -Player::emit_silence (DCPTime most) -{ - if (most == DCPTime ()) { - return; - } - - DCPTime t = min (most, DCPTime::from_seconds (0.5)); - shared_ptr silence (new AudioBuffers (_film->audio_channels(), t.frames (_film->audio_frame_rate()))); - silence->make_silent (); - Audio (silence, _audio_position); - - _audio_position += t; + _black_image.reset (new Image (PIX_FMT_RGB24, _video_container_size, true)); + _black_image->make_black (); } void @@ -644,128 +217,277 @@ Player::film_changed (Film::Property p) } } -void -Player::update_subtitle_from_image () +list +Player::process_content_image_subtitles (shared_ptr content, list > subs) { - shared_ptr piece = _image_subtitle.piece.lock (); - if (!piece) { - return; - } + list all; + + for (list >::const_iterator i = subs.begin(); i != subs.end(); ++i) { + if (!(*i)->image) { + continue; + } - if (!_image_subtitle.subtitle->image) { - _out_subtitle.image.reset (); - return; + dcpomatic::Rect in_rect = (*i)->rectangle; + dcp::Size scaled_size; + + in_rect.x += content->subtitle_x_offset (); + in_rect.y += content->subtitle_y_offset (); + + /* We will scale the subtitle up to fit _video_container_size, and also by the additional subtitle_scale */ + scaled_size.width = in_rect.width * _video_container_size.width * content->subtitle_scale (); + scaled_size.height = in_rect.height * _video_container_size.height * content->subtitle_scale (); + + /* Then we need a corrective translation, consisting of two parts: + * + * 1. that which is the result of the scaling of the subtitle by _video_container_size; this will be + * rect.x * _video_container_size.width and rect.y * _video_container_size.height. + * + * 2. that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be + * (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and + * (height_before_subtitle_scale * (1 - subtitle_scale) / 2). + * + * Combining these two translations gives these expressions. + */ + + all.push_back ( + PositionImage ( + (*i)->image->scale ( + scaled_size, + Scaler::from_id ("bicubic"), + (*i)->image->pixel_format (), + true + ), + Position ( + rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - content->subtitle_scale ()) / 2))), + rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - content->subtitle_scale ()) / 2))) + ) + ) + ); } - shared_ptr sc = dynamic_pointer_cast (piece->content); - assert (sc); - - dcpomatic::Rect in_rect = _image_subtitle.subtitle->rect; - dcp::Size scaled_size; - - in_rect.x += sc->subtitle_x_offset (); - in_rect.y += sc->subtitle_y_offset (); - - /* We will scale the subtitle up to fit _video_container_size, and also by the additional subtitle_scale */ - scaled_size.width = in_rect.width * _video_container_size.width * sc->subtitle_scale (); - scaled_size.height = in_rect.height * _video_container_size.height * sc->subtitle_scale (); - - /* Then we need a corrective translation, consisting of two parts: - * - * 1. that which is the result of the scaling of the subtitle by _video_container_size; this will be - * rect.x * _video_container_size.width and rect.y * _video_container_size.height. - * - * 2. that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be - * (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and - * (height_before_subtitle_scale * (1 - subtitle_scale) / 2). - * - * Combining these two translations gives these expressions. - */ - - _out_subtitle.position.x = rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - sc->subtitle_scale ()) / 2))); - _out_subtitle.position.y = rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - sc->subtitle_scale ()) / 2))); - - _out_subtitle.image = _image_subtitle.subtitle->image->scale ( - scaled_size, - Scaler::from_id ("bicubic"), - _image_subtitle.subtitle->image->pixel_format (), - true - ); - - _out_subtitle.from = _image_subtitle.subtitle->dcp_time + piece->content->position (); - _out_subtitle.to = _image_subtitle.subtitle->dcp_time_to + piece->content->position (); + return all; } -/** Re-emit the last frame that was emitted, using current settings for crop, ratio, scaler and subtitles. - * @return false if this could not be done. - */ -bool -Player::repeat_last_video () +list +Player::process_content_text_subtitles (list > sub) { - if (!_last_incoming_video.video || !_have_valid_pieces) { - return false; + list all; + for (list >::const_iterator i = sub.begin(); i != sub.end(); ++i) { + if (!(*i)->subs.empty ()) { + all.push_back (render_subtitles ((*i)->subs, _video_container_size)); + } } - emit_video ( - _last_incoming_video.weak_piece, - _last_incoming_video.video - ); - - return true; + return all; } void -Player::update_subtitle_from_text () +Player::set_approximate_size () { - if (_text_subtitle.subtitle->subs.empty ()) { - _out_subtitle.image.reset (); - return; + _approximate_size = true; +} + +shared_ptr +Player::get_video (DCPTime time, bool accurate) +{ + if (!_have_valid_pieces) { + setup_pieces (); + } + + list > ov = overlaps (time); + if (ov.empty ()) { + /* No video content at this time: return a black frame */ + return shared_ptr ( + new DCPVideo ( + _black_image, + EYES_BOTH, + Crop (), + _video_container_size, + _video_container_size, + Scaler::from_id ("bicubic"), + Config::instance()->colour_conversions().front().conversion, + time + ) + ); + } + + /* Create a DCPVideo from the content's video at this time */ + + shared_ptr piece = ov.back (); + shared_ptr decoder = dynamic_pointer_cast (piece->decoder); + assert (decoder); + shared_ptr content = dynamic_pointer_cast (piece->content); + assert (content); + + shared_ptr dec = decoder->get_video (dcp_to_content_video (piece, time), accurate); + + dcp::Size image_size = content->scale().size (content, _video_container_size, _film->frame_size ()); + if (_approximate_size) { + image_size.width &= ~3; + image_size.height &= ~3; + } + + shared_ptr dcp_video ( + new DCPVideo ( + dec->image, + dec->eyes, + content->crop (), + image_size, + _video_container_size, + _film->scaler(), + content->colour_conversion (), + time + ) + ); + + /* Add subtitles */ + + ov = overlaps (time); + list sub_images; + + for (list >::const_iterator i = ov.begin(); i != ov.end(); ++i) { + shared_ptr subtitle_decoder = dynamic_pointer_cast ((*i)->decoder); + shared_ptr subtitle_content = dynamic_pointer_cast ((*i)->content); + ContentTime const from = dcp_to_content_subtitle (*i, time); + ContentTime const to = from + ContentTime::from_frames (1, content->video_frame_rate ()); + + list > image_subtitles = subtitle_decoder->get_image_subtitles (from, to); + if (!image_subtitles.empty ()) { + list im = process_content_image_subtitles ( + subtitle_content, + image_subtitles + ); + + copy (im.begin(), im.end(), back_inserter (sub_images)); + } + + if (_burn_subtitles) { + list > text_subtitles = subtitle_decoder->get_text_subtitles (from, to); + if (!text_subtitles.empty ()) { + list im = process_content_text_subtitles (text_subtitles); + copy (im.begin(), im.end(), back_inserter (sub_images)); + } + } } - render_subtitles (_text_subtitle.subtitle->subs, _video_container_size, _out_subtitle.image, _out_subtitle.position); + if (!sub_images.empty ()) { + dcp_video->set_subtitle (merge (sub_images)); + } + + return dcp_video; } -void -Player::set_approximate_size () +shared_ptr +Player::get_audio (DCPTime time, DCPTime length, bool accurate) { - _approximate_size = true; + if (!_have_valid_pieces) { + setup_pieces (); + } + + AudioFrame const length_frames = length.frames (_film->audio_frame_rate ()); + + shared_ptr audio (new AudioBuffers (_film->audio_channels(), length_frames)); + audio->make_silent (); + + list > ov = overlaps (time); + if (ov.empty ()) { + return audio; + } + + for (list >::iterator i = ov.begin(); i != ov.end(); ++i) { + + shared_ptr content = dynamic_pointer_cast ((*i)->content); + assert (content); + shared_ptr decoder = dynamic_pointer_cast ((*i)->decoder); + assert (decoder); + + AudioFrame const content_time = dcp_to_content_audio (*i, time); + + /* Audio from this piece's decoder (which might be more than what we asked for) */ + shared_ptr all = decoder->get_audio (content_time, length_frames, accurate); + + /* Gain */ + if (content->audio_gain() != 0) { + shared_ptr gain (new AudioBuffers (all->audio)); + gain->apply_gain (content->audio_gain ()); + all->audio = gain; + } + + /* Remap channels */ + shared_ptr dcp_mapped (new AudioBuffers (_film->audio_channels(), all->audio->frames())); + dcp_mapped->make_silent (); + AudioMapping map = content->audio_mapping (); + for (int i = 0; i < map.content_channels(); ++i) { + for (int j = 0; j < _film->audio_channels(); ++j) { + if (map.get (i, static_cast (j)) > 0) { + dcp_mapped->accumulate_channel ( + all->audio.get(), + i, + j, + map.get (i, static_cast (j)) + ); + } + } + } + + all->audio = dcp_mapped; + + /* Delay */ + /* XXX + audio->dcp_time += content->audio_delay() * TIME_HZ / 1000; + if (audio->dcp_time < 0) { + int const frames = - audio->dcp_time * _film->audio_frame_rate() / TIME_HZ; + if (frames >= audio->audio->frames ()) { + return; + } + + shared_ptr trimmed (new AudioBuffers (audio->audio->channels(), audio->audio->frames() - frames)); + trimmed->copy_from (audio->audio.get(), audio->audio->frames() - frames, frames, 0); + + audio->audio = trimmed; + audio->dcp_time = 0; + } + */ + + audio->accumulate_frames (all->audio.get(), all->frame - content_time, 0, min (AudioFrame (all->audio->frames()), length_frames)); + } + + return audio; } - -PlayerImage::PlayerImage ( - shared_ptr in, - Crop crop, - dcp::Size inter_size, - dcp::Size out_size, - Scaler const * scaler - ) - : _in (in) - , _crop (crop) - , _inter_size (inter_size) - , _out_size (out_size) - , _scaler (scaler) + +VideoFrame +Player::dcp_to_content_video (shared_ptr piece, DCPTime t) const { + /* s is the offset of t from the start position of this content */ + DCPTime s = t - piece->content->position (); + s = DCPTime (max (int64_t (0), s.get ())); + s = DCPTime (min (piece->content->length_after_trim().get(), s.get())); + /* Convert this to the content frame */ + return DCPTime (s + piece->content->trim_start()).frames (_film->video_frame_rate()) * piece->frc.factor (); } -void -PlayerImage::set_subtitle (shared_ptr image, Position pos) +AudioFrame +Player::dcp_to_content_audio (shared_ptr piece, DCPTime t) const { - _subtitle_image = image; - _subtitle_position = pos; + /* s is the offset of t from the start position of this content */ + DCPTime s = t - piece->content->position (); + s = DCPTime (max (int64_t (0), s.get ())); + s = DCPTime (min (piece->content->length_after_trim().get(), s.get())); + + /* Convert this to the content frame */ + return DCPTime (s + piece->content->trim_start()).frames (_film->audio_frame_rate()); } -shared_ptr -PlayerImage::image (AVPixelFormat format, bool aligned) +ContentTime +Player::dcp_to_content_subtitle (shared_ptr piece, DCPTime t) const { - shared_ptr out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, format, aligned); - - Position const container_offset ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.width) / 2); - - if (_subtitle_image) { - out->alpha_blend (_subtitle_image, _subtitle_position); - } + /* s is the offset of t from the start position of this content */ + DCPTime s = t - piece->content->position (); + s = DCPTime (max (int64_t (0), s.get ())); + s = DCPTime (min (piece->content->length_after_trim().get(), s.get())); - return out; + return ContentTime (s, piece->frc); } void diff --git a/src/lib/player.h b/src/lib/player.h index 6860807b3..be5cbddb4 100644 --- a/src/lib/player.h +++ b/src/lib/player.h @@ -27,9 +27,10 @@ #include "content.h" #include "film.h" #include "rect.h" -#include "audio_merger.h" #include "audio_content.h" -#include "decoded.h" +#include "dcpomatic_time.h" +#include "content_subtitle.h" +#include "position_image.h" class Job; class Film; @@ -37,29 +38,8 @@ class Playlist; class AudioContent; class Piece; class Image; - -/** @class PlayerImage - * @brief A wrapper for an Image which contains some pending operations; these may - * not be necessary if the receiver of the PlayerImage throws it away. - */ -class PlayerImage -{ -public: - PlayerImage (boost::shared_ptr, Crop, dcp::Size, dcp::Size, Scaler const *); - - void set_subtitle (boost::shared_ptr, Position); - - boost::shared_ptr image (AVPixelFormat, bool); - -private: - boost::shared_ptr _in; - Crop _crop; - dcp::Size _inter_size; - dcp::Size _out_size; - Scaler const * _scaler; - boost::shared_ptr _subtitle_image; - Position _subtitle_position; -}; +class DCPVideo; +class Decoder; class PlayerStatistics { @@ -92,45 +72,40 @@ public: void dump (boost::shared_ptr) const; }; - + +class Piece +{ +public: + Piece (boost::shared_ptr c, boost::shared_ptr d, FrameRateChange f) + : content (c) + , decoder (d) + , frc (f) + {} + + boost::shared_ptr content; + boost::shared_ptr decoder; + FrameRateChange frc; +}; + /** @class Player - * @brief A class which can `play' a Playlist; emitting its audio and video. + * @brief A class which can `play' a Playlist. */ - class Player : public boost::enable_shared_from_this, public boost::noncopyable { public: Player (boost::shared_ptr, boost::shared_ptr); - void disable_video (); - void disable_audio (); - - bool pass (); - void seek (DCPTime, bool); - - DCPTime video_position () const { - return _video_position; - } + boost::shared_ptr get_video (DCPTime time, bool accurate); + boost::shared_ptr get_audio (DCPTime time, DCPTime length, bool accurate); void set_video_container_size (dcp::Size); void set_approximate_size (); - - bool repeat_last_video (); + void set_burn_subtitles (bool burn) { + _burn_subtitles = burn; + } PlayerStatistics const & statistics () const; - /** Emitted when a video frame is ready. - * First parameter is the video image. - * Second parameter is the eye(s) that should see this image. - * Third parameter is the colour conversion that should be used for this image. - * Fourth parameter is true if the image is the same as the last one that was emitted. - * Fifth parameter is the time. - */ - boost::signals2::signal, Eyes, ColourConversion, bool, DCPTime)> Video; - - /** Emitted when some audio data is ready */ - boost::signals2::signal, DCPTime)> Audio; - /** Emitted when something has changed such that if we went back and emitted * the last frame again it would look different. This is not emitted after * a seek. @@ -146,67 +121,43 @@ private: void setup_pieces (); void playlist_changed (); void content_changed (boost::weak_ptr, int, bool); - void do_seek (DCPTime, bool); void flush (); - void emit_black (); - void emit_silence (DCPTime); void film_changed (Film::Property); - void update_subtitle_from_image (); + std::list process_content_image_subtitles ( + boost::shared_ptr, std::list > + ); + std::list process_content_text_subtitles (std::list >); void update_subtitle_from_text (); - void emit_video (boost::weak_ptr, boost::shared_ptr); - void emit_audio (boost::weak_ptr, boost::shared_ptr); - void step_video_position (boost::shared_ptr); - + VideoFrame dcp_to_content_video (boost::shared_ptr piece, DCPTime t) const; + AudioFrame dcp_to_content_audio (boost::shared_ptr piece, DCPTime t) const; + ContentTime dcp_to_content_subtitle (boost::shared_ptr piece, DCPTime t) const; + + template + std::list > + overlaps (DCPTime t) + { + std::list > overlaps; + for (typename std::list >::const_iterator i = _pieces.begin(); i != _pieces.end(); ++i) { + if (boost::dynamic_pointer_cast ((*i)->content) && (*i)->content->position() >= t && (*i)->content->end() < t) { + overlaps.push_back (*i); + } + } + + return overlaps; + } + boost::shared_ptr _film; boost::shared_ptr _playlist; - - bool _video; - bool _audio; /** Our pieces are ready to go; if this is false the pieces must be (re-)created before they are used */ bool _have_valid_pieces; std::list > _pieces; - /** The time after the last video that we emitted */ - DCPTime _video_position; - /** The time after the last audio that we emitted */ - DCPTime _audio_position; - - AudioMerger _audio_merger; - dcp::Size _video_container_size; - boost::shared_ptr _black_frame; - - struct { - boost::weak_ptr piece; - boost::shared_ptr subtitle; - } _image_subtitle; - - struct { - boost::weak_ptr piece; - boost::shared_ptr subtitle; - } _text_subtitle; - - struct { - Position position; - boost::shared_ptr image; - DCPTime from; - DCPTime to; - } _out_subtitle; - -#ifdef DCPOMATIC_DEBUG - boost::shared_ptr _last_video; -#endif - - bool _last_emit_was_black; - - struct { - boost::weak_ptr weak_piece; - boost::shared_ptr video; - } _last_incoming_video; + boost::shared_ptr _black_image; - bool _just_did_inaccurate_seek; bool _approximate_size; + bool _burn_subtitles; PlayerStatistics _statistics; diff --git a/src/lib/rect.h b/src/lib/rect.h index 6f4709c08..1feb8ad4f 100644 --- a/src/lib/rect.h +++ b/src/lib/rect.h @@ -42,6 +42,13 @@ public: , height (0) {} + Rect (Position p, T w_, T h_) + : x (p.x) + , y (p.y) + , width (w_) + , height (h_) + {} + Rect (T x_, T y_, T w_, T h_) : x (x_) , y (y_) @@ -54,11 +61,13 @@ public: T width; T height; - Position position () const { + Position position () const + { return Position (x, y); } - Rect intersection (Rect const & other) const { + Rect intersection (Rect const & other) const + { T const tx = max (x, other.x); T const ty = max (y, other.y); @@ -69,7 +78,16 @@ public: ); } - bool contains (Position p) const { + void extend (Rect const & other) + { + x = std::min (x, other.x); + y = std::min (y, other.y); + width = std::max (x + width, other.x + other.width) - x; + height = std::max (y + height, other.y + other.height) - y; + } + + bool contains (Position p) const + { return (p.x >= x && p.x <= (x + width) && p.y >= y && p.y <= (y + height)); } }; diff --git a/src/lib/render_subtitles.cc b/src/lib/render_subtitles.cc index c18cb4272..5364b8dfe 100644 --- a/src/lib/render_subtitles.cc +++ b/src/lib/render_subtitles.cc @@ -28,6 +28,7 @@ using std::cout; using std::string; using std::min; using std::max; +using std::pair; using boost::shared_ptr; using boost::optional; @@ -46,12 +47,11 @@ calculate_position (dcp::VAlign v_align, double v_position, int target_height, i return 0; } -void -render_subtitles (list subtitles, dcp::Size target, shared_ptr& image, Position& position) +PositionImage +render_subtitles (list subtitles, dcp::Size target) { if (subtitles.empty ()) { - image.reset (); - return; + return PositionImage (); } /* Estimate height that the subtitle image needs to be */ @@ -68,7 +68,7 @@ render_subtitles (list subtitles, dcp::Size target, shared_ top = top.get() - 32; bottom = bottom.get() + 32; - image.reset (new Image (PIX_FMT_RGBA, dcp::Size (target.width, bottom.get() - top.get ()), false)); + shared_ptr image (new Image (PIX_FMT_RGBA, dcp::Size (target.width, bottom.get() - top.get ()), false)); image->make_black (); Cairo::RefPtr surface = Cairo::ImageSurface::create ( @@ -146,5 +146,7 @@ render_subtitles (list subtitles, dcp::Size target, shared_ context->stroke (); } } + + return PositionImage (image, Position (0, top.get ())); } diff --git a/src/lib/render_subtitles.h b/src/lib/render_subtitles.h index 743339baf..d83dc119a 100644 --- a/src/lib/render_subtitles.h +++ b/src/lib/render_subtitles.h @@ -19,9 +19,6 @@ #include #include -#include "position.h" +#include "position_image.h" -class Image; - -void -render_subtitles (std::list, dcp::Size, boost::shared_ptr &, Position &); +PositionImage render_subtitles (std::list, dcp::Size); diff --git a/src/lib/sndfile_decoder.cc b/src/lib/sndfile_decoder.cc index 67bb25e0d..c37a84474 100644 --- a/src/lib/sndfile_decoder.cc +++ b/src/lib/sndfile_decoder.cc @@ -131,7 +131,6 @@ SndfileDecoder::audio_frame_rate () const void SndfileDecoder::seek (ContentTime t, bool accurate) { - Decoder::seek (t, accurate); AudioDecoder::seek (t, accurate); _done = t.frames (audio_frame_rate ()); diff --git a/src/lib/sndfile_decoder.h b/src/lib/sndfile_decoder.h index 1de123917..52590ef24 100644 --- a/src/lib/sndfile_decoder.h +++ b/src/lib/sndfile_decoder.h @@ -26,7 +26,7 @@ class SndfileContent; class SndfileDecoder : public AudioDecoder { public: - SndfileDecoder (boost::shared_ptr); + SndfileDecoder (boost::shared_ptr c); ~SndfileDecoder (); void seek (ContentTime, bool); diff --git a/src/lib/subrip_decoder.cc b/src/lib/subrip_decoder.cc index 47b6ea044..013c6fab7 100644 --- a/src/lib/subrip_decoder.cc +++ b/src/lib/subrip_decoder.cc @@ -30,6 +30,17 @@ SubRipDecoder::SubRipDecoder (shared_ptr content) } +void +SubRipDecoder::seek (ContentTime time, bool) +{ + _next = 0; + list::const_iterator i = _subtitles[_next].pieces.begin(); + while (i != _subtitles[_next].pieces.end() && _subtitles[_next].from < time) { + ++i; + } + +} + bool SubRipDecoder::pass () { diff --git a/src/lib/subrip_decoder.h b/src/lib/subrip_decoder.h index 6025c90e2..ca885a2ef 100644 --- a/src/lib/subrip_decoder.h +++ b/src/lib/subrip_decoder.h @@ -30,7 +30,8 @@ class SubRipDecoder : public SubtitleDecoder, public SubRip public: SubRipDecoder (boost::shared_ptr); -protected: +protected: + void seek (ContentTime time, bool accurate); bool pass (); private: diff --git a/src/lib/subtitle_decoder.cc b/src/lib/subtitle_decoder.cc index a9674fe92..3daa6e431 100644 --- a/src/lib/subtitle_decoder.cc +++ b/src/lib/subtitle_decoder.cc @@ -29,18 +29,44 @@ SubtitleDecoder::SubtitleDecoder () } - /** Called by subclasses when an image subtitle is ready. * Image may be 0 to say that there is no current subtitle. */ void -SubtitleDecoder::image_subtitle (shared_ptr image, dcpomatic::Rect rect, ContentTime from, ContentTime to) +SubtitleDecoder::image_subtitle (ContentTime from, ContentTime to, shared_ptr image, dcpomatic::Rect rect) { - _pending.push_back (shared_ptr (new DecodedImageSubtitle (from, to, image, rect))); + _decoded_image_subtitles.push_back (shared_ptr (new ContentImageSubtitle (from, to, image, rect))); } void SubtitleDecoder::text_subtitle (list s) { - _pending.push_back (shared_ptr (new DecodedTextSubtitle (s))); + _decoded_text_subtitles.push_back (shared_ptr (new ContentTextSubtitle (s))); +} + +template +list > +get (list > const & subs, ContentTime from, ContentTime to) +{ + /* XXX: inefficient */ + list > out; + for (typename list >::const_iterator i = subs.begin(); i != subs.end(); ++i) { + if ((*i)->from() <= to && (*i)->to() >= from) { + out.push_back (*i); + } + } + + return out; +} + +list > +SubtitleDecoder::get_text_subtitles (ContentTime from, ContentTime to) +{ + return get (_decoded_text_subtitles, from, to); +} + +list > +SubtitleDecoder::get_image_subtitles (ContentTime from, ContentTime to) +{ + return get (_decoded_image_subtitles, from, to); } diff --git a/src/lib/subtitle_decoder.h b/src/lib/subtitle_decoder.h index 78ee6801e..efa90fd92 100644 --- a/src/lib/subtitle_decoder.h +++ b/src/lib/subtitle_decoder.h @@ -20,12 +20,11 @@ #ifndef DCPOMATIC_SUBTITLE_DECODER_H #define DCPOMATIC_SUBTITLE_DECODER_H -#include #include #include "decoder.h" #include "rect.h" #include "types.h" -#include "decoded.h" +#include "content_subtitle.h" class Film; class DCPTimedSubtitle; @@ -36,9 +35,15 @@ class SubtitleDecoder : public virtual Decoder public: SubtitleDecoder (); + std::list > get_image_subtitles (ContentTime from, ContentTime to); + std::list > get_text_subtitles (ContentTime from, ContentTime to); + protected: - void image_subtitle (boost::shared_ptr, dcpomatic::Rect, ContentTime, ContentTime); + void image_subtitle (ContentTime from, ContentTime to, boost::shared_ptr, dcpomatic::Rect); void text_subtitle (std::list); + + std::list > _decoded_image_subtitles; + std::list > _decoded_text_subtitles; }; #endif diff --git a/src/lib/transcoder.cc b/src/lib/transcoder.cc index ba4d3b040..8146fc167 100644 --- a/src/lib/transcoder.cc +++ b/src/lib/transcoder.cc @@ -39,24 +39,6 @@ using boost::shared_ptr; using boost::weak_ptr; using boost::dynamic_pointer_cast; -static void -video_proxy (weak_ptr encoder, shared_ptr image, Eyes eyes, ColourConversion conversion, bool same) -{ - shared_ptr e = encoder.lock (); - if (e) { - e->process_video (image, eyes, conversion, same); - } -} - -static void -audio_proxy (weak_ptr encoder, shared_ptr audio) -{ - shared_ptr e = encoder.lock (); - if (e) { - e->process_audio (audio); - } -} - /** Construct a transcoder using a Decoder that we create and a supplied Encoder. * @param f Film that we are transcoding. * @param e Encoder to use. @@ -67,15 +49,19 @@ Transcoder::Transcoder (shared_ptr f, shared_ptr j) , _encoder (new Encoder (f, j)) , _finishing (false) { - _player->Video.connect (bind (video_proxy, _encoder, _1, _2, _3, _4)); - _player->Audio.connect (bind (audio_proxy, _encoder, _1)); + } void Transcoder::go () { _encoder->process_begin (); - while (!_player->pass ()) {} + + DCPTime const frame = DCPTime::from_frames (1, _film->video_frame_rate ()); + for (DCPTime t; t < _film->length(); t += frame) { + _encoder->process_video (_player->get_video (t, true)); + _encoder->process_audio (_player->get_audio (t, frame, true)); + } _finishing = true; _encoder->process_end (); diff --git a/src/lib/types.h b/src/lib/types.h index 4e1f59ca8..8c89ecd17 100644 --- a/src/lib/types.h +++ b/src/lib/types.h @@ -25,6 +25,7 @@ #include #include #include "dcpomatic_time.h" +#include "position.h" class Content; class VideoContent; @@ -45,6 +46,10 @@ typedef std::vector > AudioContentList; typedef std::vector > SubtitleContentList; typedef std::vector > FFmpegContentList; +typedef int64_t VideoFrame; +typedef int64_t AudioFrame; + +/* XXX -> DCPAudio */ struct TimedAudioBuffers { TimedAudioBuffers () diff --git a/src/lib/util.cc b/src/lib/util.cc index 5b3bd76ba..1339be73d 100644 --- a/src/lib/util.cc +++ b/src/lib/util.cc @@ -46,9 +46,9 @@ #include #include #include +#include #include #include -#include #include #include #include @@ -71,6 +71,7 @@ extern "C" { #include "job.h" #include "cross.h" #include "video_content.h" +#include "rect.h" #ifdef DCPOMATIC_WINDOWS #include "stack.hpp" #endif @@ -238,24 +239,6 @@ ffmpeg_version_to_string (int v) return s.str (); } -/** Return a user-readable string summarising the versions of our dependencies */ -string -dependency_version_summary () -{ - stringstream s; - s << N_("libopenjpeg ") << opj_version () << N_(", ") - << N_("libavcodec ") << ffmpeg_version_to_string (avcodec_version()) << N_(", ") - << N_("libavfilter ") << ffmpeg_version_to_string (avfilter_version()) << N_(", ") - << N_("libavformat ") << ffmpeg_version_to_string (avformat_version()) << N_(", ") - << N_("libavutil ") << ffmpeg_version_to_string (avutil_version()) << N_(", ") - << N_("libswscale ") << ffmpeg_version_to_string (swscale_version()) << N_(", ") - << MagickVersion << N_(", ") - << N_("libssh ") << ssh_version (0) << N_(", ") - << N_("libdcp ") << dcp::version << N_(" git ") << dcp::git_commit; - - return s.str (); -} - double seconds (struct timeval t) { @@ -956,3 +939,21 @@ divide_with_round (int64_t a, int64_t b) return a / b; } } + +/** Return a user-readable string summarising the versions of our dependencies */ +string +dependency_version_summary () +{ + stringstream s; + s << N_("libopenjpeg ") << opj_version () << N_(", ") + << N_("libavcodec ") << ffmpeg_version_to_string (avcodec_version()) << N_(", ") + << N_("libavfilter ") << ffmpeg_version_to_string (avfilter_version()) << N_(", ") + << N_("libavformat ") << ffmpeg_version_to_string (avformat_version()) << N_(", ") + << N_("libavutil ") << ffmpeg_version_to_string (avutil_version()) << N_(", ") + << N_("libswscale ") << ffmpeg_version_to_string (swscale_version()) << N_(", ") + << MagickVersion << N_(", ") + << N_("libssh ") << ssh_version (0) << N_(", ") + << N_("libdcp ") << dcp::version << N_(" git ") << dcp::git_commit; + + return s.str (); +} diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc index 144a494dc..f3d3a3104 100644 --- a/src/lib/video_decoder.cc +++ b/src/lib/video_decoder.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 Carl Hetherington + Copyright (C) 2012-2014 Carl Hetherington This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,10 +19,12 @@ #include "video_decoder.h" #include "image.h" +#include "content_video.h" #include "i18n.h" using std::cout; +using std::list; using boost::shared_ptr; using boost::optional; @@ -32,32 +34,86 @@ VideoDecoder::VideoDecoder (shared_ptr c) } +shared_ptr +VideoDecoder::decoded_video (VideoFrame frame) +{ + for (list >::const_iterator i = _decoded_video.begin(); i != _decoded_video.end(); ++i) { + if ((*i)->frame == frame) { + return *i; + } + } + + return shared_ptr (); +} + +shared_ptr +VideoDecoder::get_video (VideoFrame frame, bool accurate) +{ + if (_decoded_video.empty() || (frame < _decoded_video.front()->frame || frame > (_decoded_video.back()->frame + 1))) { + /* Either we have no decoded data, or what we do have is a long way from what we want: seek */ + seek (ContentTime::from_frames (frame, _video_content->video_frame_rate()), accurate); + } + + shared_ptr dec; + + /* Now enough pass() calls will either: + * (a) give us what we want, or + * (b) hit the end of the decoder. + */ + if (accurate) { + /* We are being accurate, so we want the right frame */ + while (!decoded_video (frame) && !pass ()) {} + dec = decoded_video (frame); + } else { + /* Any frame will do: use the first one that comes out of pass() */ + while (_decoded_video.empty() && !pass ()) {} + if (!_decoded_video.empty ()) { + dec = _decoded_video.front (); + } + } + + /* Clean up decoded_video */ + while (!_decoded_video.empty() && _decoded_video.front()->frame < (frame - 1)) { + _decoded_video.pop_front (); + } + + return dec; +} + + /** Called by subclasses when they have a video frame ready */ void -VideoDecoder::video (shared_ptr image, bool same, ContentTime time) +VideoDecoder::video (shared_ptr image, VideoFrame frame) { switch (_video_content->video_frame_type ()) { case VIDEO_FRAME_TYPE_2D: - _pending.push_back (shared_ptr (new DecodedVideo (time, image, EYES_BOTH, same))); + _decoded_video.push_back (shared_ptr (new ContentVideo (image, EYES_BOTH, frame))); break; case VIDEO_FRAME_TYPE_3D_ALTERNATE: - Video (image, (frame % 2) ? EYES_RIGHT : EYES_LEFT, same); + _decoded_video.push_back (shared_ptr (new ContentVideo (image, (frame % 2) ? EYES_RIGHT : EYES_LEFT, frame))); break; case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT: { int const half = image->size().width / 2; - _pending.push_back (shared_ptr (new DecodedVideo (time, image->crop (Crop (0, half, 0, 0), true), EYES_LEFT, same))); - _pending.push_back (shared_ptr (new DecodedVideo (time, image->crop (Crop (half, 0, 0, 0), true), EYES_RIGHT, same))); + _decoded_video.push_back (shared_ptr (new ContentVideo (image->crop (Crop (0, half, 0, 0), true), EYES_LEFT, frame))); + _decoded_video.push_back (shared_ptr (new ContentVideo (image->crop (Crop (half, 0, 0, 0), true), EYES_RIGHT, frame))); break; } case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM: { int const half = image->size().height / 2; - _pending.push_back (shared_ptr (new DecodedVideo (time, image->crop (Crop (0, 0, 0, half), true), EYES_LEFT, same))); - _pending.push_back (shared_ptr (new DecodedVideo (time, image->crop (Crop (0, 0, half, 0), true), EYES_RIGHT, same))); + _decoded_video.push_back (shared_ptr (new ContentVideo (image->crop (Crop (0, 0, 0, half), true), EYES_LEFT, frame))); + _decoded_video.push_back (shared_ptr (new ContentVideo (image->crop (Crop (0, 0, half, 0), true), EYES_RIGHT, frame))); break; } default: assert (false); } } + +void +VideoDecoder::seek (ContentTime, bool) +{ + _decoded_video.clear (); +} + diff --git a/src/lib/video_decoder.h b/src/lib/video_decoder.h index c3228e88d..7c7ec35b9 100644 --- a/src/lib/video_decoder.h +++ b/src/lib/video_decoder.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 Carl Hetherington + Copyright (C) 2012-2014 Carl Hetherington This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -25,15 +25,17 @@ #include "decoder.h" #include "video_content.h" #include "util.h" -#include "decoded.h" class VideoContent; class Image; +class ContentVideo; class VideoDecoder : public virtual Decoder { public: - VideoDecoder (boost::shared_ptr); + VideoDecoder (boost::shared_ptr c); + + boost::shared_ptr get_video (VideoFrame frame, bool accurate); boost::shared_ptr video_content () const { return _video_content; @@ -41,8 +43,12 @@ public: protected: - void video (boost::shared_ptr, bool, ContentTime); + void seek (ContentTime time, bool accurate); + void video (boost::shared_ptr, VideoFrame frame); + boost::shared_ptr decoded_video (VideoFrame frame); + boost::shared_ptr _video_content; + std::list > _decoded_video; }; #endif diff --git a/src/lib/writer.cc b/src/lib/writer.cc index 125efd644..202ec9a0f 100644 --- a/src/lib/writer.cc +++ b/src/lib/writer.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 Carl Hetherington + Copyright (C) 2012-2014 Carl Hetherington This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -71,7 +71,6 @@ Writer::Writer (shared_ptr f, weak_ptr j) , _last_written_eyes (EYES_RIGHT) , _full_written (0) , _fake_written (0) - , _repeat_written (0) , _pushed_to_disk (0) { /* Remove any old DCP */ @@ -277,18 +276,6 @@ try _last_written[qi.eyes].reset (); ++_fake_written; break; - case QueueItem::REPEAT: - { - _film->log()->log (String::compose (N_("Writer REPEAT-writes %1 to MXF"), qi.frame)); - dcp::FrameInfo fin = _picture_mxf_writer->write ( - _last_written[qi.eyes]->data(), - _last_written[qi.eyes]->size() - ); - - _last_written[qi.eyes]->write_info (_film, qi.frame, qi.eyes, fin); - ++_repeat_written; - break; - } } lock.lock (); @@ -305,7 +292,7 @@ try total *= 2; } if (total) { - job->set_progress (float (_full_written + _fake_written + _repeat_written) / total); + job->set_progress (float (_full_written + _fake_written) / total); } } @@ -454,36 +441,10 @@ Writer::finish () dcp.write_xml (_film->interop () ? dcp::INTEROP : dcp::SMPTE, meta, _film->is_signed() ? make_signer () : shared_ptr ()); _film->log()->log ( - String::compose (N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT; %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk) + String::compose (N_("Wrote %1 FULL, %2 FAKE, %3 pushed to disk"), _full_written, _fake_written, _pushed_to_disk) ); } -/** Tell the writer that frame `f' should be a repeat of the frame before it */ -void -Writer::repeat (int f, Eyes e) -{ - boost::mutex::scoped_lock lock (_mutex); - - while (_queued_full_in_memory > _maximum_frames_in_memory) { - _full_condition.wait (lock); - } - - QueueItem qi; - qi.type = QueueItem::REPEAT; - qi.frame = f; - if (_film->three_d() && e == EYES_BOTH) { - qi.eyes = EYES_LEFT; - _queue.push_back (qi); - qi.eyes = EYES_RIGHT; - _queue.push_back (qi); - } else { - qi.eyes = e; - _queue.push_back (qi); - } - - _empty_condition.notify_all (); -} - bool Writer::check_existing_picture_mxf_frame (FILE* mxf, int f, Eyes eyes) { diff --git a/src/lib/writer.h b/src/lib/writer.h index 9e95c0531..af535c004 100644 --- a/src/lib/writer.h +++ b/src/lib/writer.h @@ -138,8 +138,6 @@ private: int _full_written; /** number of FAKE written frames */ int _fake_written; - /** number of REPEAT written frames */ - int _repeat_written; /** number of frames pushed to disk and then recovered due to the limit of frames to be held in memory. */ diff --git a/src/lib/wscript b/src/lib/wscript index 5d27cfe45..00ed171a3 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -8,18 +8,18 @@ sources = """ audio_content.cc audio_decoder.cc audio_mapping.cc - audio_merger.cc cinema.cc colour_conversion.cc config.cc content.cc content_factory.cc + content_subtitle.cc cross.cc dci_metadata.cc dcp_content_type.cc + dcp_video.cc dcp_video_frame.cc dcpomatic_time.cc - decoder.cc dolby_cp750.cc encoder.cc examine_content_job.cc diff --git a/src/tools/server_test.cc b/src/tools/server_test.cc index 9b725cb86..ad3cf9481 100644 --- a/src/tools/server_test.cc +++ b/src/tools/server_test.cc @@ -34,6 +34,7 @@ #include "lib/log.h" #include "lib/video_decoder.h" #include "lib/player.h" +#include "lib/dcp_video.h" using std::cout; using std::cerr; @@ -44,23 +45,27 @@ using boost::shared_ptr; static shared_ptr film; static ServerDescription* server; static shared_ptr log_ (new FileLog ("servomatictest.log")); -static int frame = 0; +static int frame_count = 0; void -process_video (shared_ptr image, Eyes eyes, ColourConversion conversion, DCPTime) +process_video (shared_ptr frame) { shared_ptr local ( - new DCPVideoFrame (image->image (PIX_FMT_RGB24, false), frame, eyes, conversion, film->video_frame_rate(), 250000000, RESOLUTION_2K, log_) + new DCPVideoFrame ( + frame->image (PIX_FMT_RGB24, false), frame_count, frame->eyes(), frame->conversion(), film->video_frame_rate(), 250000000, RESOLUTION_2K, log_ + ) ); shared_ptr remote ( - new DCPVideoFrame (image->image (PIX_FMT_RGB24, false), frame, eyes, conversion, film->video_frame_rate(), 250000000, RESOLUTION_2K, log_) + new DCPVideoFrame ( + frame->image (PIX_FMT_RGB24, false), frame_count, frame->eyes(), frame->conversion(), film->video_frame_rate(), 250000000, RESOLUTION_2K, log_ + ) ); - cout << "Frame " << frame << ": "; + cout << "Frame " << frame_count << ": "; cout.flush (); - ++frame; + ++frame_count; shared_ptr local_encoded = local->encode_locally (); shared_ptr remote_encoded; @@ -148,12 +153,10 @@ main (int argc, char* argv[]) film->read_metadata (); shared_ptr player = film->make_player (); - player->disable_audio (); - player->Video.connect (boost::bind (process_video, _1, _2, _3, _5)); - bool done = false; - while (!done) { - done = player->pass (); + DCPTime const frame = DCPTime::from_frames (1, film->video_frame_rate ()); + for (DCPTime t; t < film->length(); t += frame) { + process_video (player->get_video (t, true)); } } catch (std::exception& e) { cerr << "Error: " << e.what() << "\n"; diff --git a/src/wx/film_viewer.cc b/src/wx/film_viewer.cc index a1cddf785..1d8a1d322 100644 --- a/src/wx/film_viewer.cc +++ b/src/wx/film_viewer.cc @@ -37,6 +37,7 @@ #include "lib/video_content.h" #include "lib/video_decoder.h" #include "lib/timer.h" +#include "lib/dcp_video.h" #include "film_viewer.h" #include "wx_util.h" @@ -62,7 +63,6 @@ FilmViewer::FilmViewer (shared_ptr f, wxWindow* p) , _frame_number (new wxStaticText (this, wxID_ANY, wxT(""))) , _timecode (new wxStaticText (this, wxID_ANY, wxT(""))) , _play_button (new wxToggleButton (this, wxID_ANY, _("Play"))) - , _got_frame (false) { #ifndef __WXOSX__ _panel->SetDoubleBuffered (true); @@ -136,31 +136,25 @@ FilmViewer::set_film (shared_ptr f) return; } - _player->disable_audio (); _player->set_approximate_size (); - _player->Video.connect (boost::bind (&FilmViewer::process_video, this, _1, _2, _5)); _player->Changed.connect (boost::bind (&FilmViewer::player_changed, this, _1)); calculate_sizes (); - fetch_next_frame (); + get (_position, true); } void -FilmViewer::fetch_current_frame_again () +FilmViewer::get (DCPTime p, bool accurate) { - if (!_player) { - return; + shared_ptr dcp_video = _player->get_video (p, accurate); + if (dcp_video) { + _frame = dcp_video->image (PIX_FMT_BGRA, true); + _frame = _frame->scale (_frame->size(), Scaler::from_id ("fastbilinear"), PIX_FMT_RGB24, false); + } else { + _frame.reset (); } - /* We could do this with a seek and a fetch_next_frame, but this is - a shortcut to make it quicker. - */ - - _got_frame = false; - if (!_player->repeat_last_video ()) { - fetch_next_frame (); - } - + set_position_text (p); _panel->Refresh (); _panel->Update (); } @@ -172,12 +166,12 @@ FilmViewer::timer () return; } - fetch_next_frame (); + get (_position + DCPTime::from_frames (1, _film->video_frame_rate ()), true); DCPTime const len = _film->length (); if (len.get ()) { - int const new_slider_position = 4096 * _player->video_position().get() / len.get(); + int const new_slider_position = 4096 * _position.get() / len.get(); if (new_slider_position != _slider->GetValue()) { _slider->SetValue (new_slider_position); } @@ -220,21 +214,16 @@ FilmViewer::paint_panel () void FilmViewer::slider_moved () { - if (_film && _player) { - try { - DCPTime t (_slider->GetValue() * _film->length().get() / 4096); - /* Ensure that we hit the end of the film at the end of the slider */ - if (t >= _film->length ()) { - t = _film->length() - DCPTime::from_frames (1, _film->video_frame_rate ()); - } - _player->seek (t, false); - fetch_next_frame (); - } catch (OpenFileError& e) { - /* There was a problem opening a content file; we'll let this slide as it - probably means a missing content file, which we're already taking care of. - */ - } + if (!_film || !_player) { + return; + } + + DCPTime t (_slider->GetValue() * _film->length().get() / 4096); + /* Ensure that we hit the end of the film at the end of the slider */ + if (t >= _film->length ()) { + t = _film->length() - DCPTime::from_frames (1, _film->video_frame_rate ()); } + get (t, false); } void @@ -243,7 +232,7 @@ FilmViewer::panel_sized (wxSizeEvent& ev) _panel_size.width = ev.GetSize().GetWidth(); _panel_size.height = ev.GetSize().GetHeight(); calculate_sizes (); - fetch_current_frame_again (); + get (_position, true); } void @@ -302,23 +291,6 @@ FilmViewer::check_play_state () } } -void -FilmViewer::process_video (shared_ptr image, Eyes eyes, DCPTime t) -{ - if (eyes == EYES_RIGHT) { - return; - } - - /* Going via BGRA here makes the scaler faster then using RGB24 directly (about - twice on x86 Linux). - */ - shared_ptr im = image->image (PIX_FMT_BGRA, true); - _frame = im->scale (im->size(), Scaler::from_id ("fastbilinear"), PIX_FMT_RGB24, false); - _got_frame = true; - - set_position_text (t); -} - void FilmViewer::set_position_text (DCPTime t) { @@ -343,35 +315,6 @@ FilmViewer::set_position_text (DCPTime t) _timecode->SetLabel (wxString::Format (wxT("%02d:%02d:%02d.%02d"), h, m, s, f)); } -/** Ask the player to emit its next frame, then update our display */ -void -FilmViewer::fetch_next_frame () -{ - /* Clear our frame in case we don't get a new one */ - _frame.reset (); - - if (!_player) { - return; - } - - _got_frame = false; - - try { - while (!_got_frame && !_player->pass ()) {} - } catch (DecodeError& e) { - _play_button->SetValue (false); - check_play_state (); - error_dialog (this, wxString::Format (_("Could not decode video for view (%s)"), std_to_wx(e.what()).data())); - } catch (OpenFileError& e) { - /* There was a problem opening a content file; we'll let this slide as it - probably means a missing content file, which we're already taking care of. - */ - } - - _panel->Refresh (); - _panel->Update (); -} - void FilmViewer::active_jobs_changed (bool a) { @@ -399,23 +342,12 @@ FilmViewer::back_clicked () return; } - /* Player::video_position is the time after the last frame that we received. - We want to see the one before it, so we need to go back 2. - */ - - DCPTime p = _player->video_position() - DCPTime::from_frames (2, _film->video_frame_rate ()); + DCPTime p = _position - DCPTime::from_frames (1, _film->video_frame_rate ()); if (p < DCPTime ()) { p = DCPTime (); } - - try { - _player->seek (p, true); - fetch_next_frame (); - } catch (OpenFileError& e) { - /* There was a problem opening a content file; we'll let this slide as it - probably means a missing content file, which we're already taking care of. - */ - } + + get (p, true); } void @@ -425,7 +357,7 @@ FilmViewer::forward_clicked () return; } - fetch_next_frame (); + get (_position + DCPTime::from_frames (1, _film->video_frame_rate ()), true); } void @@ -436,5 +368,5 @@ FilmViewer::player_changed (bool frequent) } calculate_sizes (); - fetch_current_frame_again (); + get (_position, true); } diff --git a/src/wx/film_viewer.h b/src/wx/film_viewer.h index 0a535df9f..52876030c 100644 --- a/src/wx/film_viewer.h +++ b/src/wx/film_viewer.h @@ -32,19 +32,6 @@ class PlayerImage; /** @class FilmViewer * @brief A wx widget to view a preview of a Film. - * - * The film takes the following path through the viewer: - * - * 1. fetch_next_frame() asks our _player to decode some data. If it does, process_video() - * will be called. - * - * 2. process_video() takes the image from the player (_frame). - * - * 3. fetch_next_frame() calls _panel->Refresh() and _panel->Update() which results in - * paint_panel() being called; this creates frame_bitmap from _frame and blits it to the display. - * - * fetch_current_frame_again() asks the player to re-emit its current frame on the next pass(), and then - * starts from step #1. */ class FilmViewer : public wxPanel { @@ -59,16 +46,14 @@ private: void slider_moved (); void play_clicked (); void timer (); - void process_video (boost::shared_ptr, Eyes, DCPTime); void calculate_sizes (); void check_play_state (); - void fetch_current_frame_again (); - void fetch_next_frame (); void active_jobs_changed (bool); void back_clicked (); void forward_clicked (); void player_changed (bool); void set_position_text (DCPTime); + void get (DCPTime, bool); boost::shared_ptr _film; boost::shared_ptr _player; @@ -84,7 +69,7 @@ private: wxTimer _timer; boost::shared_ptr _frame; - bool _got_frame; + DCPTime _position; /** Size of our output (including padding if we have any) */ dcp::Size _out_size; diff --git a/src/wx/subtitle_view.cc b/src/wx/subtitle_view.cc index f6fbd9a9a..adbc4a625 100644 --- a/src/wx/subtitle_view.cc +++ b/src/wx/subtitle_view.cc @@ -18,7 +18,7 @@ */ #include "lib/subrip_decoder.h" -#include "lib/decoded.h" +#include "lib/content_subtitle.h" #include "subtitle_view.h" using std::list; @@ -63,25 +63,16 @@ SubtitleView::SubtitleView (wxWindow* parent, shared_ptr content) } shared_ptr decoder (new SubRipDecoder (content)); + list > subs = decoder->get_text_subtitles (ContentTime(), ContentTime::max ()); int n = 0; - while (1) { - shared_ptr dec = decoder->peek (); - if (!dec) { - break; - } - - shared_ptr sub = dynamic_pointer_cast (dec); - assert (sub); - - for (list::const_iterator i = sub->subs.begin(); i != sub->subs.end(); ++i) { + for (list >::const_iterator i = subs.begin(); i != subs.end(); ++i) { + for (list::const_iterator j = (*i)->subs.begin(); j != (*i)->subs.end(); ++j) { wxListItem list_item; list_item.SetId (n); _list->InsertItem (list_item); - _list->SetItem (n, 2, i->text ()); + _list->SetItem (n, 2, j->text ()); ++n; } - - decoder->consume (); } SetSizerAndFit (sizer); diff --git a/test/audio_merger_test.cc b/test/audio_merger_test.cc deleted file mode 100644 index 2fa0ccea5..000000000 --- a/test/audio_merger_test.cc +++ /dev/null @@ -1,103 +0,0 @@ -/* - Copyright (C) 2013 Carl Hetherington - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -#include -#include -#include -#include -#include "lib/audio_merger.h" -#include "lib/audio_buffers.h" - -using boost::shared_ptr; -using boost::bind; - -static shared_ptr last_audio; - -BOOST_AUTO_TEST_CASE (audio_merger_test1) -{ - int const frame_rate = 48000; - AudioMerger merger (1, frame_rate); - - /* Push 64 samples, 0 -> 63 at time 0 */ - shared_ptr buffers (new AudioBuffers (1, 64)); - for (int i = 0; i < 64; ++i) { - buffers->data()[0][i] = i; - } - merger.push (buffers, DCPTime ()); - - /* Push 64 samples, 0 -> 63 at time 22 */ - merger.push (buffers, DCPTime::from_frames (22, frame_rate)); - - TimedAudioBuffers tb = merger.pull (DCPTime::from_frames (22, frame_rate)); - BOOST_CHECK (tb.audio != shared_ptr ()); - BOOST_CHECK_EQUAL (tb.audio->frames(), 22); - BOOST_CHECK_EQUAL (tb.time, DCPTime ()); - - /* And they should be a staircase */ - for (int i = 0; i < 22; ++i) { - BOOST_CHECK_EQUAL (tb.audio->data()[0][i], i); - } - - tb = merger.flush (); - - /* That flush should give us 64 samples at 22 */ - BOOST_CHECK_EQUAL (tb.audio->frames(), 64); - BOOST_CHECK_EQUAL (tb.time, DCPTime::from_frames (22, frame_rate)); - - /* Check the sample values */ - for (int i = 0; i < 64; ++i) { - int correct = i; - if (i < (64 - 22)) { - correct += i + 22; - } - BOOST_CHECK_EQUAL (tb.audio->data()[0][i], correct); - } -} - -BOOST_AUTO_TEST_CASE (audio_merger_test2) -{ - int const frame_rate = 48000; - AudioMerger merger (1, frame_rate); - - /* Push 64 samples, 0 -> 63 at time 9 */ - shared_ptr buffers (new AudioBuffers (1, 64)); - for (int i = 0; i < 64; ++i) { - buffers->data()[0][i] = i; - } - merger.push (buffers, DCPTime::from_frames (9, frame_rate)); - - TimedAudioBuffers tb = merger.pull (DCPTime::from_frames (9, frame_rate)); - BOOST_CHECK_EQUAL (tb.audio->frames(), 9); - BOOST_CHECK_EQUAL (tb.time, DCPTime ()); - - for (int i = 0; i < 9; ++i) { - BOOST_CHECK_EQUAL (tb.audio->data()[0][i], 0); - } - - tb = merger.flush (); - - /* That flush should give us 64 samples at 9 */ - BOOST_CHECK_EQUAL (tb.audio->frames(), 64); - BOOST_CHECK_EQUAL (tb.time, DCPTime::from_frames (9, frame_rate)); - - /* Check the sample values */ - for (int i = 0; i < 64; ++i) { - BOOST_CHECK_EQUAL (tb.audio->data()[0][i], i); - } -} diff --git a/test/ffmpeg_pts_offset.cc b/test/ffmpeg_pts_offset.cc index bada6a40b..dc391ed46 100644 --- a/test/ffmpeg_pts_offset.cc +++ b/test/ffmpeg_pts_offset.cc @@ -36,7 +36,7 @@ BOOST_AUTO_TEST_CASE (ffmpeg_pts_offset_test) /* Sound == video so no offset required */ content->_first_video = ContentTime (); content->_audio_stream->first_audio = ContentTime (); - FFmpegDecoder decoder (content, film->log(), true, true, true); + FFmpegDecoder decoder (content, film->log()); BOOST_CHECK_EQUAL (decoder._pts_offset, ContentTime ()); } @@ -44,7 +44,7 @@ BOOST_AUTO_TEST_CASE (ffmpeg_pts_offset_test) /* Common offset should be removed */ content->_first_video = ContentTime::from_seconds (600); content->_audio_stream->first_audio = ContentTime::from_seconds (600); - FFmpegDecoder decoder (content, film->log(), true, true, true); + FFmpegDecoder decoder (content, film->log()); BOOST_CHECK_EQUAL (decoder._pts_offset, ContentTime::from_seconds (-600)); } @@ -52,7 +52,7 @@ BOOST_AUTO_TEST_CASE (ffmpeg_pts_offset_test) /* Video is on a frame boundary */ content->_first_video = ContentTime::from_frames (1, 24); content->_audio_stream->first_audio = ContentTime (); - FFmpegDecoder decoder (content, film->log(),true, true, true); + FFmpegDecoder decoder (content, film->log()); BOOST_CHECK_EQUAL (decoder._pts_offset, ContentTime ()); } @@ -61,7 +61,7 @@ BOOST_AUTO_TEST_CASE (ffmpeg_pts_offset_test) double const frame = 1.0 / 24.0; content->_first_video = ContentTime::from_seconds (frame + 0.0215); content->_audio_stream->first_audio = ContentTime (); - FFmpegDecoder decoder (content, film->log(), true, true, true); + FFmpegDecoder decoder (content, film->log()); BOOST_CHECK_CLOSE (decoder._pts_offset.seconds(), (frame - 0.0215), 0.00001); } @@ -70,7 +70,7 @@ BOOST_AUTO_TEST_CASE (ffmpeg_pts_offset_test) double const frame = 1.0 / 24.0; content->_first_video = ContentTime::from_seconds (frame + 0.0215 + 4.1); content->_audio_stream->first_audio = ContentTime::from_seconds (4.1); - FFmpegDecoder decoder (content, film->log(), true, true, true); + FFmpegDecoder decoder (content, film->log()); BOOST_CHECK_EQUAL (decoder._pts_offset.seconds(), (frame - 0.0215) - 4.1); } } diff --git a/test/ffmpeg_seek_test.cc b/test/ffmpeg_seek_test.cc deleted file mode 100644 index c25a071de..000000000 --- a/test/ffmpeg_seek_test.cc +++ /dev/null @@ -1,117 +0,0 @@ -/* - Copyright (C) 2013 Carl Hetherington - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -/** @file test/ffmpeg_seek_test.cc - * @brief Test seek using Player with an FFmpegDecoder; note that the player - * can hide problems with FFmpegDecoder seeking as it will skip frames / insert - * black as it sees fit. - */ - -#include -#include "lib/player.h" -#include "lib/ffmpeg_decoder.h" -#include "lib/film.h" -#include "lib/ratio.h" -#include "test.h" - -using std::cout; -using std::string; -using std::stringstream; -using boost::shared_ptr; -using boost::optional; - -#define FFMPEG_SEEK_TEST_DEBUG 1 - -optional first_video; -optional first_audio; -shared_ptr film; - -static void -process_video (shared_ptr, Eyes, ColourConversion, bool, DCPTime t) -{ - if (!first_video) { - first_video = t; - } -} - -static void -process_audio (shared_ptr, DCPTime t) -{ - if (!first_audio) { - first_audio = t; - } -} - -static string -print_time (DCPTime t, float fps) -{ - stringstream s; - s << t.seconds() << "s " << t.frames (fps) << "f"; - return s.str (); -} - -static void -check (shared_ptr p, DCPTime t) -{ - first_video.reset (); - first_audio.reset (); - -#if FFMPEG_SEEK_TEST_DEBUG == 1 - cout << "\n-- Seek to " << print_time (t, 24) << "\n"; -#endif - - p->seek (t, true); - while (!first_video || !first_audio) { - p->pass (); - } - -#if FFMPEG_SEEK_TEST_DEBUG == 1 - cout << "First video " << print_time (first_video.get(), 24) << "\n"; - cout << "First audio " << print_time (first_audio.get(), 24) << "\n"; -#endif - - /* Outputs should be on or after seek time */ - BOOST_CHECK (first_video.get() >= t); - BOOST_CHECK (first_audio.get() >= t); - /* And should be rounded to frame boundaries */ - BOOST_CHECK_EQUAL (first_video.get(), first_video.get().round_up (film->video_frame_rate())); - BOOST_CHECK_EQUAL (first_audio.get(), first_audio.get().round_up (film->audio_frame_rate())); -} - -/* Test basic seeking */ -BOOST_AUTO_TEST_CASE (ffmpeg_seek_test) -{ - film = new_test_film ("ffmpeg_seek_test"); - film->set_name ("ffmpeg_seek_test"); - film->set_container (Ratio::from_id ("185")); - shared_ptr c (new FFmpegContent (film, "test/data/staircase.mov")); - c->set_scale (VideoContentScale (Ratio::from_id ("185"))); - film->examine_and_add_content (c); - - wait_for_jobs (); - - shared_ptr player = film->make_player (); - player->Video.connect (boost::bind (&process_video, _1, _2, _3, _4, _5)); - player->Audio.connect (boost::bind (&process_audio, _1, _2)); - - check (player, DCPTime::from_seconds (0)); - check (player, DCPTime::from_seconds (0.1)); - check (player, DCPTime::from_seconds (0.2)); - check (player, DCPTime::from_seconds (0.3)); -} diff --git a/test/long_ffmpeg_seek_test.cc b/test/long_ffmpeg_seek_test.cc deleted file mode 100644 index 58b039eee..000000000 --- a/test/long_ffmpeg_seek_test.cc +++ /dev/null @@ -1,105 +0,0 @@ -/* - Copyright (C) 2013 Carl Hetherington - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -#include -#include "lib/player.h" -#include "lib/ffmpeg_decoder.h" -#include "lib/film.h" -#include "lib/ratio.h" -#include "test.h" - -using std::cout; -using std::string; -using std::stringstream; -using boost::shared_ptr; - -#define LONG_FFMPEG_SEEK_TEST_DEBUG 1 - -boost::optional first_video; -boost::optional first_audio; - -static void -process_video (shared_ptr, Eyes, ColourConversion, bool, DCPTime t) -{ - if (!first_video) { - first_video = t; - } -} - -static void -process_audio (shared_ptr, DCPTime t) -{ - if (!first_audio) { - first_audio = t; - } -} - -static string -print_time (DCPTime t, float fps) -{ - stringstream s; - s << t << " " << t.seconds() << "s " << t.frames(fps) << "f"; - return s.str (); -} - -static void -check (shared_ptr p, DCPTime t) -{ - first_video.reset (); - first_audio.reset (); - -#if LONG_FFMPEG_SEEK_TEST_DEBUG == 1 - cout << "\n-- Seek to " << print_time (t, 24) << "\n"; -#endif - - p->seek (t, true); - while (!first_video || !first_audio) { - p->pass (); - } - -#if LONG_FFMPEG_SEEK_TEST_DEBUG == 1 - cout << "First video " << print_time (first_video.get(), 24) << "\n"; - cout << "First audio " << print_time (first_audio.get(), 24) << "\n"; -#endif - - BOOST_CHECK (first_video.get() >= t); - BOOST_CHECK (first_audio.get() >= t); -} - -BOOST_AUTO_TEST_CASE (long_ffmpeg_seek_test) -{ - shared_ptr film = new_test_film ("long_ffmpeg_audio_test"); - film->set_name ("long_ffmpeg_audio_test"); - film->set_container (Ratio::from_id ("185")); - shared_ptr c (new FFmpegContent (film, "test/long_data/dolby_aurora.vob")); - c->set_scale (VideoContentScale (Ratio::from_id ("185"))); - film->examine_and_add_content (c); - - wait_for_jobs (); - - shared_ptr player = film->make_player (); - player->Video.connect (boost::bind (&process_video, _1, _2, _3, _4, _5)); - player->Audio.connect (boost::bind (&process_audio, _1, _2)); - - for (float i = 0; i < 10; i += 0.1) { - check (player, DCPTime::from_seconds (i)); - } -} - - diff --git a/test/play_test.cc b/test/play_test.cc deleted file mode 100644 index ed3e497ed..000000000 --- a/test/play_test.cc +++ /dev/null @@ -1,126 +0,0 @@ -/* - Copyright (C) 2013 Carl Hetherington - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -#include -#include "lib/player.h" -#include "lib/ratio.h" -#include "lib/dcp_content_type.h" -#include "test.h" - -/* This test needs stuff in Player that is only included in debug mode */ -#ifdef DCPOMATIC_DEBUG - -using std::cout; -using boost::optional; -using boost::shared_ptr; - -struct Video -{ - boost::shared_ptr content; - boost::shared_ptr image; - DCPTime time; -}; - -class PlayerWrapper -{ -public: - PlayerWrapper (shared_ptr p) - : _player (p) - { - _player->Video.connect (bind (&PlayerWrapper::process_video, this, _1, _2, _5)); - } - - void process_video (shared_ptr i, bool, DCPTime t) - { - Video v; - v.content = _player->_last_video; - v.image = i->image (PIX_FMT_RGB24, false); - v.time = t; - _queue.push_front (v); - } - - optional