From de2af791bdfdcd653752cba970e59efc7bf810c7 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Mon, 21 Nov 2016 16:57:15 +0000 Subject: [PATCH] Basic grunt-work, untested and unfinished, but it compiles. --- doc/design/decoder_structures.tex | 1 + src/lib/analyse_audio_job.cc | 25 +- src/lib/analyse_audio_job.h | 3 +- src/lib/audio_decoder.cc | 17 +- src/lib/audio_decoder.h | 16 +- src/lib/audio_decoder_stream.cc | 106 -------- src/lib/audio_decoder_stream.h | 2 - src/lib/dcp_decoder.cc | 84 +------ src/lib/dcp_decoder.h | 8 +- src/lib/dcp_subtitle_decoder.cc | 51 +--- src/lib/dcp_subtitle_decoder.h | 5 +- src/lib/decoder.cc | 31 ++- src/lib/decoder.h | 30 +-- src/lib/decoder_part.cc | 6 - src/lib/decoder_part.h | 2 - src/lib/ffmpeg_content.cc | 22 -- src/lib/ffmpeg_content.h | 3 - src/lib/ffmpeg_decoder.cc | 53 +--- src/lib/ffmpeg_decoder.h | 8 +- src/lib/ffmpeg_subtitle_stream.cc | 36 --- src/lib/ffmpeg_subtitle_stream.h | 4 - src/lib/image_decoder.cc | 14 +- src/lib/image_decoder.h | 5 +- src/lib/player.cc | 404 +++++++----------------------- src/lib/player.h | 20 +- src/lib/subtitle_decoder.cc | 123 +-------- src/lib/subtitle_decoder.h | 37 +-- src/lib/text_subtitle_decoder.cc | 64 +---- src/lib/text_subtitle_decoder.h | 6 +- src/lib/transcoder.cc | 85 +++---- src/lib/transcoder.h | 9 + src/lib/video_decoder.cc | 321 +----------------------- src/lib/video_decoder.h | 32 +-- src/lib/video_mxf_decoder.cc | 16 +- src/lib/video_mxf_decoder.h | 5 +- src/tools/server_test.cc | 8 +- src/wx/film_viewer.cc | 137 ++++------ src/wx/film_viewer.h | 9 +- src/wx/subtitle_view.cc | 4 + test/audio_decoder_test.cc | 3 +- test/wscript | 21 +- 41 files changed, 375 insertions(+), 1461 deletions(-) diff --git a/doc/design/decoder_structures.tex b/doc/design/decoder_structures.tex index 3aa85b1ec..ebd25bfc3 100644 --- a/doc/design/decoder_structures.tex +++ b/doc/design/decoder_structures.tex @@ -154,6 +154,7 @@ or perhaps Questions: \begin{itemize} \item Video / audio frame or \texttt{ContentTime}? +\item Can all the subtitle period notation code go? \end{itemize} \end{document} diff --git a/src/lib/analyse_audio_job.cc b/src/lib/analyse_audio_job.cc index 4274cc350..1378b66a4 100644 --- a/src/lib/analyse_audio_job.cc +++ b/src/lib/analyse_audio_job.cc @@ -102,6 +102,7 @@ AnalyseAudioJob::run () player->set_ignore_video (); player->set_fast (); player->set_play_referenced (); + player->Audio.connect (bind (&AnalyseAudioJob::analyse, this, _1, _2)); DCPTime const start = _playlist->start().get_value_or (DCPTime ()); DCPTime const length = _playlist->length (); @@ -122,17 +123,7 @@ AnalyseAudioJob::run () if (has_any_audio) { _done = 0; - DCPTime const block = DCPTime::from_seconds (1.0 / 8); - for (DCPTime t = start; t < length; t += block) { - shared_ptr audio = player->get_audio (t, block, false); -#ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG - if (Config::instance()->analyse_ebur128 ()) { - _ebur128->process (audio); - } -#endif - analyse (audio); - set_progress ((t.seconds() - start.seconds()) / (length.seconds() - start.seconds())); - } + while (!player->pass ()) {} } vector sample_peak; @@ -172,8 +163,14 @@ AnalyseAudioJob::run () } void -AnalyseAudioJob::analyse (shared_ptr b) +AnalyseAudioJob::analyse (shared_ptr b, DCPTime time) { +#ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG + if (Config::instance()->analyse_ebur128 ()) { + _ebur128->process (b); + } +#endif + int const frames = b->frames (); int const channels = b->channels (); @@ -204,4 +201,8 @@ AnalyseAudioJob::analyse (shared_ptr b) } _done += frames; + + DCPTime const start = _playlist->start().get_value_or (DCPTime ()); + DCPTime const length = _playlist->length (); + set_progress ((time.seconds() - start.seconds()) / (length.seconds() - start.seconds())); } diff --git a/src/lib/analyse_audio_job.h b/src/lib/analyse_audio_job.h index ee20bedc4..7e5ea4719 100644 --- a/src/lib/analyse_audio_job.h +++ b/src/lib/analyse_audio_job.h @@ -25,6 +25,7 @@ #include "job.h" #include "audio_point.h" #include "types.h" +#include "dcpomatic_time.h" class AudioBuffers; class AudioAnalysis; @@ -55,7 +56,7 @@ public: } private: - void analyse (boost::shared_ptr); + void analyse (boost::shared_ptr, DCPTime time); boost::shared_ptr _playlist; diff --git a/src/lib/audio_decoder.cc b/src/lib/audio_decoder.cc index 1b1ae70c0..b866d3ecf 100644 --- a/src/lib/audio_decoder.cc +++ b/src/lib/audio_decoder.cc @@ -42,14 +42,8 @@ AudioDecoder::AudioDecoder (Decoder* parent, shared_ptr cont } } -ContentAudio -AudioDecoder::get (AudioStreamPtr stream, Frame frame, Frame length, bool accurate) -{ - return _streams[stream]->get (frame, length, accurate); -} - void -AudioDecoder::give (AudioStreamPtr stream, shared_ptr data, ContentTime time) +AudioDecoder::emit (AudioStreamPtr stream, shared_ptr data, ContentTime time) { if (ignore ()) { return; @@ -89,15 +83,6 @@ AudioDecoder::flush () } } -void -AudioDecoder::seek (ContentTime t, bool accurate) -{ - _log->log (String::compose ("AD seek to %1", to_string(t)), LogEntry::TYPE_DEBUG_DECODE); - for (StreamMap::const_iterator i = _streams.begin(); i != _streams.end(); ++i) { - i->second->seek (t, accurate); - } -} - void AudioDecoder::set_fast () { diff --git a/src/lib/audio_decoder.h b/src/lib/audio_decoder.h index cdb643cee..a777592c2 100644 --- a/src/lib/audio_decoder.h +++ b/src/lib/audio_decoder.h @@ -30,6 +30,7 @@ #include "audio_stream.h" #include "decoder_part.h" #include +#include class AudioBuffers; class AudioContent; @@ -44,19 +45,12 @@ class AudioDecoder : public boost::enable_shared_from_this, public public: AudioDecoder (Decoder* parent, boost::shared_ptr, boost::shared_ptr log); - /** Try to fetch some audio from a specific place in this content. - * @param frame Frame to start from (after resampling, if applicable) - * @param length Frames to get (after resampling, if applicable) - * @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. - */ - ContentAudio get (AudioStreamPtr stream, Frame time, Frame length, bool accurate); - void set_fast (); - - void give (AudioStreamPtr stream, boost::shared_ptr, ContentTime); void flush (); - void seek (ContentTime t, bool accurate); + + void emit (AudioStreamPtr stream, boost::shared_ptr, ContentTime); + + boost::signals2::signal Data; boost::optional position () const; diff --git a/src/lib/audio_decoder_stream.cc b/src/lib/audio_decoder_stream.cc index a82ebc4cf..8f0905e0d 100644 --- a/src/lib/audio_decoder_stream.cc +++ b/src/lib/audio_decoder_stream.cc @@ -66,102 +66,6 @@ AudioDecoderStream::reset_decoded () _decoded = ContentAudio (shared_ptr (new AudioBuffers (_stream->channels(), 0)), 0); } -ContentAudio -AudioDecoderStream::get (Frame frame, Frame length, bool accurate) -{ - shared_ptr dec; - - _log->log ( - String::compose ( - "ADS has request for %1 %2; has %3 %4", - frame, length, _decoded.frame, _decoded.audio->frames() - ), LogEntry::TYPE_DEBUG_DECODE - ); - - Frame const from = frame; - Frame const to = from + length; - Frame const have_from = _decoded.frame; - Frame const have_to = _decoded.frame + _decoded.audio->frames(); - - optional missing; - if (have_from > from || have_to < to) { - /* We need something */ - if (have_from <= from && from < have_to) { - missing = have_to; - } else { - missing = from; - } - } - - if (missing) { - optional pos = _audio_decoder->position (); - _log->log ( - String::compose ("ADS suggests seek to %1 (now at %2)", *missing, pos ? to_string(pos.get()) : "none"), - LogEntry::TYPE_DEBUG_DECODE - ); - _audio_decoder->maybe_seek (ContentTime::from_frames (*missing, _content->resampled_frame_rate()), accurate); - } - - /* Offset of the data that we want from the start of _decoded.audio - (to be set up shortly) - */ - Frame decoded_offset = 0; - - /* 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) { - /* Keep stuffing data into _decoded until we have enough data, or the subclass does not want to give us any more */ - while ( - (_decoded.frame > frame || (_decoded.frame + _decoded.audio->frames()) <= to) && - !_decoder->pass (Decoder::PASS_REASON_AUDIO, accurate) - ) - {} - - decoded_offset = frame - _decoded.frame; - - _log->log ( - String::compose ("Accurate ADS::get has offset %1 from request %2 and available %3", decoded_offset, frame, have_from), - LogEntry::TYPE_DEBUG_DECODE - ); - } else { - while ( - _decoded.audio->frames() < length && - !_decoder->pass (Decoder::PASS_REASON_AUDIO, accurate) - ) - {} - - /* Use decoded_offset of 0, as we don't really care what frames we return */ - } - - /* The amount of data available in _decoded.audio starting from `frame'. This could be -ve - if pass() returned true before we got enough data. - */ - Frame const available = _decoded.audio->frames() - decoded_offset; - - /* We will return either that, or the requested amount, whichever is smaller */ - Frame const to_return = max ((Frame) 0, min (available, length)); - - /* Copy our data to the output */ - shared_ptr out (new AudioBuffers (_decoded.audio->channels(), to_return)); - out->copy_from (_decoded.audio.get(), to_return, decoded_offset, 0); - - Frame const remaining = max ((Frame) 0, available - to_return); - - /* Clean up decoded; first, move the data after what we just returned to the start of the buffer */ - _decoded.audio->move (decoded_offset + to_return, 0, remaining); - /* And set up the number of frames we have left */ - _decoded.audio->set_frames (remaining); - /* Also bump where those frames are in terms of the content */ - _decoded.frame += decoded_offset + to_return; - - return ContentAudio (out, frame); -} - /** 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. @@ -255,16 +159,6 @@ AudioDecoderStream::flush () } } -void -AudioDecoderStream::seek (ContentTime t, bool accurate) -{ - _position.reset (); - reset_decoded (); - if (accurate) { - _seek_reference = t; - } -} - void AudioDecoderStream::set_fast () { diff --git a/src/lib/audio_decoder_stream.h b/src/lib/audio_decoder_stream.h index 9ec5c5a09..b2ab65ac0 100644 --- a/src/lib/audio_decoder_stream.h +++ b/src/lib/audio_decoder_stream.h @@ -37,10 +37,8 @@ class AudioDecoderStream public: AudioDecoderStream (boost::shared_ptr, AudioStreamPtr, Decoder* decoder, AudioDecoder* audio_decoder, boost::shared_ptr log); - ContentAudio get (Frame time, Frame length, bool accurate); void audio (boost::shared_ptr, ContentTime); void flush (); - void seek (ContentTime time, bool accurate); void set_fast (); boost::optional position () const; diff --git a/src/lib/dcp_decoder.cc b/src/lib/dcp_decoder.cc index 6d0d1def4..25c805d3f 100644 --- a/src/lib/dcp_decoder.cc +++ b/src/lib/dcp_decoder.cc @@ -56,15 +56,7 @@ DCPDecoder::DCPDecoder (shared_ptr c, shared_ptr log) video.reset (new VideoDecoder (this, c, log)); audio.reset (new AudioDecoder (this, c->audio, log)); - subtitle.reset ( - new SubtitleDecoder ( - this, - c->subtitle, - log, - bind (&DCPDecoder::image_subtitles_during, this, _1, _2), - bind (&DCPDecoder::text_subtitles_during, this, _1, _2) - ) - ); + subtitle.reset (new SubtitleDecoder (this, c->subtitle, log)); shared_ptr cpl; BOOST_FOREACH (shared_ptr i, cpls ()) { @@ -87,11 +79,11 @@ DCPDecoder::DCPDecoder (shared_ptr c, shared_ptr log) get_readers (); } -bool -DCPDecoder::pass (PassReason reason, bool) +void +DCPDecoder::pass () { if (_reel == _reels.end () || !_dcp_content->can_be_played ()) { - return true; + return; } double const vfr = _dcp_content->active_video_frame_rate (); @@ -99,24 +91,24 @@ DCPDecoder::pass (PassReason reason, bool) /* Frame within the (played part of the) reel that is coming up next */ int64_t const frame = _next.frames_round (vfr); - if ((_mono_reader || _stereo_reader) && reason != PASS_REASON_SUBTITLE && (_decode_referenced || !_dcp_content->reference_video())) { + if ((_mono_reader || _stereo_reader) && (_decode_referenced || !_dcp_content->reference_video())) { shared_ptr asset = (*_reel)->main_picture()->asset (); int64_t const entry_point = (*_reel)->main_picture()->entry_point (); if (_mono_reader) { - video->give ( + video->emit ( shared_ptr ( new J2KImageProxy (_mono_reader->get_frame (entry_point + frame), asset->size(), AV_PIX_FMT_XYZ12LE) ), _offset + frame ); } else { - video->give ( + video->emit ( shared_ptr ( new J2KImageProxy (_stereo_reader->get_frame (entry_point + frame), asset->size(), dcp::EYE_LEFT, AV_PIX_FMT_XYZ12LE)), _offset + frame ); - video->give ( + video->emit ( shared_ptr ( new J2KImageProxy (_stereo_reader->get_frame (entry_point + frame), asset->size(), dcp::EYE_RIGHT, AV_PIX_FMT_XYZ12LE)), _offset + frame @@ -124,7 +116,7 @@ DCPDecoder::pass (PassReason reason, bool) } } - if (_sound_reader && reason != PASS_REASON_SUBTITLE && (_decode_referenced || !_dcp_content->reference_audio())) { + if (_sound_reader && (_decode_referenced || !_dcp_content->reference_audio())) { int64_t const entry_point = (*_reel)->main_sound()->entry_point (); shared_ptr sf = _sound_reader->get_frame (entry_point + frame); uint8_t const * from = sf->data (); @@ -140,7 +132,7 @@ DCPDecoder::pass (PassReason reason, bool) } } - audio->give (_dcp_content->audio->stream(), data, ContentTime::from_frames (_offset, vfr) + _next); + audio->emit (_dcp_content->audio->stream(), data, ContentTime::from_frames (_offset, vfr) + _next); } if ((*_reel)->main_subtitle() && (_decode_referenced || !_dcp_content->reference_subtitle())) { @@ -153,7 +145,7 @@ DCPDecoder::pass (PassReason reason, bool) if (!subs.empty ()) { /* XXX: assuming that all `subs' are at the same time; maybe this is ok */ - subtitle->give_text ( + subtitle->emit_text ( ContentTimePeriod ( ContentTime::from_frames (_offset - entry_point, vfr) + ContentTime::from_seconds (subs.front().in().as_seconds ()), ContentTime::from_frames (_offset - entry_point, vfr) + ContentTime::from_seconds (subs.front().out().as_seconds ()) @@ -171,8 +163,6 @@ DCPDecoder::pass (PassReason reason, bool) _next = ContentTime (); } } - - return false; } void @@ -218,12 +208,8 @@ DCPDecoder::get_readers () } void -DCPDecoder::seek (ContentTime t, bool accurate) +DCPDecoder::seek (ContentTime t, bool) { - video->seek (t, accurate); - audio->seek (t, accurate); - subtitle->seek (t, accurate); - _reel = _reels.begin (); _offset = 0; get_readers (); @@ -236,52 +222,6 @@ DCPDecoder::seek (ContentTime t, bool accurate) _next = t; } - -list -DCPDecoder::image_subtitles_during (ContentTimePeriod, bool) const -{ - return list (); -} - -list -DCPDecoder::text_subtitles_during (ContentTimePeriod period, bool starting) const -{ - /* XXX: inefficient */ - - list ctp; - double const vfr = _dcp_content->active_video_frame_rate (); - - int offset = 0; - - BOOST_FOREACH (shared_ptr r, _reels) { - if (!r->main_subtitle ()) { - offset += r->main_picture()->duration(); - continue; - } - - int64_t const entry_point = r->main_subtitle()->entry_point (); - - list subs = r->main_subtitle()->asset()->subtitles_during ( - dcp::Time (period.from.seconds(), 1000) - dcp::Time (offset - entry_point, vfr, vfr), - dcp::Time (period.to.seconds(), 1000) - dcp::Time (offset - entry_point, vfr, vfr), - starting - ); - - BOOST_FOREACH (dcp::SubtitleString const & s, subs) { - ctp.push_back ( - ContentTimePeriod ( - ContentTime::from_seconds (s.in().as_seconds ()) + ContentTime::from_frames (offset - entry_point, vfr), - ContentTime::from_seconds (s.out().as_seconds ()) + ContentTime::from_frames (offset - entry_point, vfr) - ) - ); - } - - offset += r->main_subtitle()->duration(); - } - - return ctp; -} - void DCPDecoder::set_decode_referenced () { diff --git a/src/lib/dcp_decoder.h b/src/lib/dcp_decoder.h index b1b26056b..84deab101 100644 --- a/src/lib/dcp_decoder.h +++ b/src/lib/dcp_decoder.h @@ -47,17 +47,15 @@ public: void set_decode_referenced (); + void pass (); + void seek (ContentTime t, bool accurate); + private: friend struct dcp_subtitle_within_dcp_test; - bool pass (PassReason, bool accurate); - void seek (ContentTime t, bool accurate); void next_reel (); void get_readers (); - std::list image_subtitles_during (ContentTimePeriod, bool starting) const; - std::list text_subtitles_during (ContentTimePeriod, bool starting) const; - /** Time of next thing to return from pass relative to the start of _reel */ ContentTime _next; std::list > _reels; diff --git a/src/lib/dcp_subtitle_decoder.cc b/src/lib/dcp_subtitle_decoder.cc index 824ddba19..9db325401 100644 --- a/src/lib/dcp_subtitle_decoder.cc +++ b/src/lib/dcp_subtitle_decoder.cc @@ -30,15 +30,7 @@ using boost::bind; DCPSubtitleDecoder::DCPSubtitleDecoder (shared_ptr content, shared_ptr log) { - subtitle.reset ( - new SubtitleDecoder ( - this, - content->subtitle, - log, - bind (&DCPSubtitleDecoder::image_subtitles_during, this, _1, _2), - bind (&DCPSubtitleDecoder::text_subtitles_during, this, _1, _2) - ) - ); + subtitle.reset (new SubtitleDecoder (this, content->subtitle, log)); shared_ptr c (load (content->path (0))); _subtitles = c->subtitles (); @@ -46,10 +38,8 @@ DCPSubtitleDecoder::DCPSubtitleDecoder (shared_ptr con } void -DCPSubtitleDecoder::seek (ContentTime time, bool accurate) +DCPSubtitleDecoder::seek (ContentTime time, bool) { - subtitle->seek (time, accurate); - _next = _subtitles.begin (); list::const_iterator i = _subtitles.begin (); while (i != _subtitles.end() && ContentTime::from_seconds (_next->in().as_seconds()) < time) { @@ -57,11 +47,11 @@ DCPSubtitleDecoder::seek (ContentTime time, bool accurate) } } -bool -DCPSubtitleDecoder::pass (PassReason, bool) +void +DCPSubtitleDecoder::pass () { if (_next == _subtitles.end ()) { - return true; + return; } /* Gather all subtitles with the same time period that are next @@ -79,35 +69,8 @@ DCPSubtitleDecoder::pass (PassReason, bool) ++_next; } - subtitle->give_text (p, s); - - return false; -} - -list -DCPSubtitleDecoder::image_subtitles_during (ContentTimePeriod, bool) const -{ - return list (); -} - -list -DCPSubtitleDecoder::text_subtitles_during (ContentTimePeriod p, bool starting) const -{ - /* XXX: inefficient */ - - list d; - - for (list::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) { - ContentTimePeriod period = content_time_period (*i); - if ((starting && p.contains(period.from)) || (!starting && p.overlap(period))) { - d.push_back (period); - } - } - - d.sort (); - d.unique (); - - return d; + subtitle->emit_text (p, s); + subtitle->set_position (p.from); } ContentTimePeriod diff --git a/src/lib/dcp_subtitle_decoder.h b/src/lib/dcp_subtitle_decoder.h index b6e9aa63c..076dc3f3b 100644 --- a/src/lib/dcp_subtitle_decoder.h +++ b/src/lib/dcp_subtitle_decoder.h @@ -28,13 +28,10 @@ class DCPSubtitleDecoder : public DCPSubtitle, public Decoder public: DCPSubtitleDecoder (boost::shared_ptr, boost::shared_ptr log); -protected: - bool pass (PassReason, bool accurate); + void pass (); void seek (ContentTime time, bool accurate); private: - std::list image_subtitles_during (ContentTimePeriod, bool starting) const; - std::list text_subtitles_during (ContentTimePeriod, bool starting) const; ContentTimePeriod content_time_period (dcp::SubtitleString s) const; std::list _subtitles; diff --git a/src/lib/decoder.cc b/src/lib/decoder.cc index 114e2ebb4..785fb96f0 100644 --- a/src/lib/decoder.cc +++ b/src/lib/decoder.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2015 Carl Hetherington + Copyright (C) 2012-2016 Carl Hetherington This file is part of DCP-o-matic. @@ -19,19 +19,26 @@ */ #include "decoder.h" -#include "decoder_part.h" -#include +#include "video_decoder.h" +#include "audio_decoder.h" +#include "subtitle_decoder.h" -using std::cout; -using boost::optional; - -void -Decoder::maybe_seek (optional position, ContentTime time, bool accurate) +ContentTime +Decoder::position () const { - if (position && (time >= position.get() && time < (position.get() + ContentTime::from_seconds(1)))) { - /* No need to seek: caller should just pass() */ - return; + ContentTime pos; + + if (video && video->position()) { + pos = min (pos, video->position().get()); + } + + if (audio && audio->position()) { + pos = min (pos, audio->position().get()); + } + + if (subtitle && subtitle->position()) { + pos = min (pos, subtitle->position().get()); } - seek (time, accurate); + return pos; } diff --git a/src/lib/decoder.h b/src/lib/decoder.h index 3717d931d..f70eca8b3 100644 --- a/src/lib/decoder.h +++ b/src/lib/decoder.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2015 Carl Hetherington + Copyright (C) 2012-2016 Carl Hetherington This file is part of DCP-o-matic. @@ -47,32 +47,10 @@ public: boost::shared_ptr audio; boost::shared_ptr subtitle; - enum PassReason { - PASS_REASON_VIDEO, - PASS_REASON_AUDIO, - PASS_REASON_SUBTITLE - }; - - /** @return true if this decoder has already returned all its data and will give no more */ - virtual bool pass (PassReason, bool accurate) = 0; - - /** Ensure that any future get() calls return data that reflect - * changes in our content's settings. - */ - virtual void reset () {} - - void maybe_seek (boost::optional position, ContentTime time, bool accurate); - -private: - /** Seek so that the next pass() will yield the next thing - * (video/sound frame, subtitle etc.) at or after the requested - * time. Pass accurate = true to try harder to ensure that, at worst, - * the next thing we yield comes before `time'. This may entail - * seeking some way before `time' to be on the safe side. - * Alternatively, if seeking is 100% accurate for this decoder, - * it may seek to just the right spot. - */ + virtual void pass () = 0; virtual void seek (ContentTime time, bool accurate) = 0; + + ContentTime position () const; }; #endif diff --git a/src/lib/decoder_part.cc b/src/lib/decoder_part.cc index 6d53e4a77..d8f988388 100644 --- a/src/lib/decoder_part.cc +++ b/src/lib/decoder_part.cc @@ -30,9 +30,3 @@ DecoderPart::DecoderPart (Decoder* parent, shared_ptr log) { } - -void -DecoderPart::maybe_seek (ContentTime time, bool accurate) -{ - _parent->maybe_seek (position(), time, accurate); -} diff --git a/src/lib/decoder_part.h b/src/lib/decoder_part.h index f2ab96d15..36594773a 100644 --- a/src/lib/decoder_part.h +++ b/src/lib/decoder_part.h @@ -43,8 +43,6 @@ public: virtual boost::optional position () const = 0; - void maybe_seek (ContentTime time, bool accurate); - protected: Decoder* _parent; boost::shared_ptr _log; diff --git a/src/lib/ffmpeg_content.cc b/src/lib/ffmpeg_content.cc index 0e782c9db..44e1b2afb 100644 --- a/src/lib/ffmpeg_content.cc +++ b/src/lib/ffmpeg_content.cc @@ -411,28 +411,6 @@ FFmpegContent::identifier () const return s; } -list -FFmpegContent::image_subtitles_during (ContentTimePeriod period, bool starting) const -{ - shared_ptr stream = subtitle_stream (); - if (!stream) { - return list (); - } - - return stream->image_subtitles_during (period, starting); -} - -list -FFmpegContent::text_subtitles_during (ContentTimePeriod period, bool starting) const -{ - shared_ptr stream = subtitle_stream (); - if (!stream) { - return list (); - } - - return stream->text_subtitles_during (period, starting); -} - void FFmpegContent::set_default_colour_conversion () { diff --git a/src/lib/ffmpeg_content.h b/src/lib/ffmpeg_content.h index 91caac27c..f6553df1c 100644 --- a/src/lib/ffmpeg_content.h +++ b/src/lib/ffmpeg_content.h @@ -91,9 +91,6 @@ public: return _first_video; } - std::list image_subtitles_during (ContentTimePeriod, bool starting) const; - std::list text_subtitles_during (ContentTimePeriod, bool starting) const; - void signal_subtitle_stream_changed (); private: diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc index b6b6e594d..32903a20e 100644 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@ -94,15 +94,7 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr c, shared_ptr } if (c->subtitle) { - subtitle.reset ( - new SubtitleDecoder ( - this, - c->subtitle, - log, - bind (&FFmpegDecoder::image_subtitles_during, this, _1, _2), - bind (&FFmpegDecoder::text_subtitles_during, this, _1, _2) - ) - ); + subtitle.reset (new SubtitleDecoder (this, c->subtitle, log)); } } @@ -124,8 +116,8 @@ FFmpegDecoder::flush () } } -bool -FFmpegDecoder::pass (PassReason reason, bool accurate) +void +FFmpegDecoder::pass () { int r = av_read_frame (_format_context, &_packet); @@ -142,22 +134,21 @@ FFmpegDecoder::pass (PassReason reason, bool accurate) } flush (); - return true; + return; } int const si = _packet.stream_index; shared_ptr fc = _ffmpeg_content; - if (_video_stream && si == _video_stream.get() && !video->ignore() && (accurate || reason != PASS_REASON_SUBTITLE)) { + if (_video_stream && si == _video_stream.get() && !video->ignore()) { decode_video_packet (); } else if (fc->subtitle_stream() && fc->subtitle_stream()->uses_index (_format_context, si)) { decode_subtitle_packet (); - } else if (accurate || reason != PASS_REASON_SUBTITLE) { + } else { decode_audio_packet (); } av_packet_unref (&_packet); - return false; } /** @param data pointer to array of pointers to buffers. @@ -307,18 +298,6 @@ FFmpegDecoder::bytes_per_audio_sample (shared_ptr stream) con void FFmpegDecoder::seek (ContentTime time, bool accurate) { - if (video) { - video->seek (time, accurate); - } - - if (audio) { - audio->seek (time, accurate); - } - - if (subtitle) { - subtitle->seek (time, accurate); - } - /* If we are doing an `accurate' seek, we need to use pre-roll, as we don't really know what the seek will give us. */ @@ -428,7 +407,7 @@ FFmpegDecoder::decode_audio_packet () /* Give this data provided there is some, and its time is sane */ if (ct >= ContentTime() && data->frames() > 0) { - audio->give (*stream, data, ct); + audio->emit (*stream, data, ct); } } @@ -473,7 +452,7 @@ FFmpegDecoder::decode_video_packet () if (i->second != AV_NOPTS_VALUE) { double const pts = i->second * av_q2d (_format_context->streams[_video_stream.get()]->time_base) + _pts_offset.seconds (); - video->give ( + video->emit ( shared_ptr (new RawImageProxy (image)), llrint (pts * _ffmpeg_content->active_video_frame_rate ()) ); @@ -534,18 +513,6 @@ FFmpegDecoder::decode_subtitle_packet () avsubtitle_free (&sub); } -list -FFmpegDecoder::image_subtitles_during (ContentTimePeriod p, bool starting) const -{ - return _ffmpeg_content->image_subtitles_during (p, starting); -} - -list -FFmpegDecoder::text_subtitles_during (ContentTimePeriod p, bool starting) const -{ - return _ffmpeg_content->text_subtitles_during (p, starting); -} - void FFmpegDecoder::decode_bitmap_subtitle (AVSubtitleRect const * rect, ContentTimePeriod period) { @@ -616,7 +583,7 @@ FFmpegDecoder::decode_bitmap_subtitle (AVSubtitleRect const * rect, ContentTimeP static_cast (rect->h) / target_height ); - subtitle->give_image (period, image, scaled_rect); + subtitle->emit_image (period, image, scaled_rect); } void @@ -636,6 +603,6 @@ FFmpegDecoder::decode_ass_subtitle (string ass, ContentTimePeriod period) list raw = sub::SSAReader::parse_line (base, bits[9]); BOOST_FOREACH (sub::Subtitle const & i, sub::collect > (raw)) { - subtitle->give_text (period, i); + subtitle->emit_text (period, i); } } diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h index 76755c1fc..82472c164 100644 --- a/src/lib/ffmpeg_decoder.h +++ b/src/lib/ffmpeg_decoder.h @@ -46,11 +46,12 @@ class FFmpegDecoder : public FFmpeg, public Decoder public: FFmpegDecoder (boost::shared_ptr, boost::shared_ptr); + void pass (); + void seek (ContentTime time, bool); + private: friend struct ::ffmpeg_pts_offset_test; - bool pass (PassReason, bool accurate); - void seek (ContentTime time, bool); void flush (); AVSampleFormat audio_sample_format (boost::shared_ptr stream) const; @@ -66,9 +67,6 @@ private: void maybe_add_subtitle (); boost::shared_ptr deinterleave_audio (boost::shared_ptr stream) const; - std::list image_subtitles_during (ContentTimePeriod, bool starting) const; - std::list text_subtitles_during (ContentTimePeriod, bool starting) const; - boost::shared_ptr _log; std::list > _filter_graphs; diff --git a/src/lib/ffmpeg_subtitle_stream.cc b/src/lib/ffmpeg_subtitle_stream.cc index d389714e9..62accfaf8 100644 --- a/src/lib/ffmpeg_subtitle_stream.cc +++ b/src/lib/ffmpeg_subtitle_stream.cc @@ -132,42 +132,6 @@ FFmpegSubtitleStream::add_text_subtitle (string id, ContentTimePeriod period) _text_subtitles[id] = period; } -list -FFmpegSubtitleStream::image_subtitles_during (ContentTimePeriod period, bool starting) const -{ - return subtitles_during (period, starting, _image_subtitles); -} - -list -FFmpegSubtitleStream::text_subtitles_during (ContentTimePeriod period, bool starting) const -{ - return subtitles_during (period, starting, _text_subtitles); -} - -struct PeriodSorter -{ - bool operator() (ContentTimePeriod const & a, ContentTimePeriod const & b) { - return a.from < b.from; - } -}; - -list -FFmpegSubtitleStream::subtitles_during (ContentTimePeriod period, bool starting, PeriodMap const & subs) const -{ - list d; - - /* XXX: inefficient */ - for (map::const_iterator i = subs.begin(); i != subs.end(); ++i) { - if ((starting && period.contains(i->second.from)) || (!starting && period.overlap(i->second))) { - d.push_back (i->second); - } - } - - d.sort (PeriodSorter ()); - - return d; -} - ContentTime FFmpegSubtitleStream::find_subtitle_to (string id) const { diff --git a/src/lib/ffmpeg_subtitle_stream.h b/src/lib/ffmpeg_subtitle_stream.h index 7c4f8cd39..61334400a 100644 --- a/src/lib/ffmpeg_subtitle_stream.h +++ b/src/lib/ffmpeg_subtitle_stream.h @@ -37,9 +37,6 @@ public: void add_image_subtitle (std::string id, ContentTimePeriod period); void add_text_subtitle (std::string id, ContentTimePeriod period); void set_subtitle_to (std::string id, ContentTime to); - bool unknown_to (std::string id) const; - std::list image_subtitles_during (ContentTimePeriod period, bool starting) const; - std::list text_subtitles_during (ContentTimePeriod period, bool starting) const; ContentTime find_subtitle_to (std::string id) const; void add_offset (ContentTime offset); void set_colour (RGBA from, RGBA to); @@ -53,7 +50,6 @@ private: typedef std::map PeriodMap; void as_xml (xmlpp::Node *, PeriodMap const & subs, std::string node) const; - std::list subtitles_during (ContentTimePeriod period, bool starting, PeriodMap const & subs) const; PeriodMap _image_subtitles; PeriodMap _text_subtitles; diff --git a/src/lib/image_decoder.cc b/src/lib/image_decoder.cc index dae73663c..b0841688f 100644 --- a/src/lib/image_decoder.cc +++ b/src/lib/image_decoder.cc @@ -44,11 +44,11 @@ ImageDecoder::ImageDecoder (shared_ptr c, shared_ptr lo video.reset (new VideoDecoder (this, c, log)); } -bool -ImageDecoder::pass (PassReason, bool) +void +ImageDecoder::pass () { if (_frame_video_position >= _image_content->video->length()) { - return true; + return; } if (!_image_content->still() || !_image) { @@ -72,14 +72,14 @@ ImageDecoder::pass (PassReason, bool) } } - video->give (_image, _frame_video_position); + video->set_position (ContentTime::from_frames (_frame_video_position, _image_content->active_video_frame_rate ())); + video->emit (_image, _frame_video_position); ++_frame_video_position; - return false; + return; } void -ImageDecoder::seek (ContentTime time, bool accurate) +ImageDecoder::seek (ContentTime time, bool) { - video->seek (time, accurate); _frame_video_position = time.frames_round (_image_content->active_video_frame_rate ()); } diff --git a/src/lib/image_decoder.h b/src/lib/image_decoder.h index d023636bf..7978f34c8 100644 --- a/src/lib/image_decoder.h +++ b/src/lib/image_decoder.h @@ -33,10 +33,11 @@ public: return _image_content; } -private: - bool pass (Decoder::PassReason, bool); + void pass (); void seek (ContentTime, bool); +private: + boost::shared_ptr _image_content; boost::shared_ptr _image; Frame _frame_video_position; diff --git a/src/lib/player.cc b/src/lib/player.cc index 57bb0c3ee..3451b892c 100644 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@ -148,7 +148,21 @@ Player::setup_pieces () dcp->set_decode_referenced (); } - _pieces.push_back (shared_ptr (new Piece (i, decoder, frc))); + shared_ptr piece (new Piece (i, decoder, frc)); + _pieces.push_back (piece); + + if (decoder->video) { + decoder->video->Data.connect (bind (&Player::video, this, weak_ptr (piece), _1)); + } + + if (decoder->audio) { + decoder->audio->Data.connect (bind (&Player::audio, this, weak_ptr (piece), _1)); + } + + if (decoder->subtitle) { + decoder->subtitle->ImageData.connect (bind (&Player::image_subtitle, this, weak_ptr (piece), _1)); + decoder->subtitle->TextData.connect (bind (&Player::text_subtitle, this, weak_ptr (piece), _1)); + } } _have_valid_pieces = true; @@ -187,19 +201,7 @@ Player::playlist_content_changed (weak_ptr w, int property, bool freque property == SubtitleContentProperty::OUTLINE_WIDTH || property == SubtitleContentProperty::Y_SCALE || property == SubtitleContentProperty::FADE_IN || - property == SubtitleContentProperty::FADE_OUT - ) { - - /* These changes just need the pieces' decoders to be reset. - It's quite possible that other changes could be handled by - this branch rather than the _have_valid_pieces = false branch - above. This would make things a lot faster. - */ - - reset_pieces (); - Changed (frequent); - - } else if ( + property == SubtitleContentProperty::FADE_OUT || property == ContentProperty::VIDEO_FRAME_RATE || property == SubtitleContentProperty::USE || property == SubtitleContentProperty::X_OFFSET || @@ -318,225 +320,6 @@ Player::black_player_video_frame (DCPTime time) const ); } -/** @return All PlayerVideos at the given time. There may be none if the content - * at `time' is a DCP which we are passing through (i.e. referring to by reference) - * or 2 if we have 3D. - */ -list > -Player::get_video (DCPTime time, bool accurate) -{ - if (!_have_valid_pieces) { - setup_pieces (); - } - - /* Find subtitles for possible burn-in */ - - PlayerSubtitles ps = get_subtitles (time, DCPTime::from_frames (1, _film->video_frame_rate ()), false, true, accurate); - - list sub_images; - - /* Image subtitles */ - list c = transform_image_subtitles (ps.image); - copy (c.begin(), c.end(), back_inserter (sub_images)); - - /* Text subtitles (rendered to an image) */ - if (!ps.text.empty ()) { - list s = render_subtitles (ps.text, ps.fonts, _video_container_size, time); - copy (s.begin (), s.end (), back_inserter (sub_images)); - } - - optional subtitles; - if (!sub_images.empty ()) { - subtitles = merge (sub_images); - } - - /* Find pieces containing video which is happening now */ - - list > ov = overlaps ( - time, - time + DCPTime::from_frames (1, _film->video_frame_rate ()), - &has_video - ); - - list > pvf; - - if (ov.empty ()) { - /* No video content at this time */ - pvf.push_back (black_player_video_frame (time)); - } else { - /* Some video content at this time */ - shared_ptr last = *(ov.rbegin ()); - VideoFrameType const last_type = last->content->video->frame_type (); - - /* Get video from appropriate piece(s) */ - BOOST_FOREACH (shared_ptr piece, ov) { - - shared_ptr decoder = piece->decoder->video; - DCPOMATIC_ASSERT (decoder); - - shared_ptr dcp_content = dynamic_pointer_cast (piece->content); - if (dcp_content && dcp_content->reference_video () && !_play_referenced) { - continue; - } - - bool const use = - /* always use the last video */ - piece == last || - /* with a corresponding L/R eye if appropriate */ - (last_type == VIDEO_FRAME_TYPE_3D_LEFT && piece->content->video->frame_type() == VIDEO_FRAME_TYPE_3D_RIGHT) || - (last_type == VIDEO_FRAME_TYPE_3D_RIGHT && piece->content->video->frame_type() == VIDEO_FRAME_TYPE_3D_LEFT); - - if (use) { - /* We want to use this piece */ - list content_video = decoder->get (dcp_to_content_video (piece, time), accurate); - if (content_video.empty ()) { - pvf.push_back (black_player_video_frame (time)); - } else { - dcp::Size image_size = piece->content->video->scale().size ( - piece->content->video, _video_container_size, _film->frame_size () - ); - - for (list::const_iterator i = content_video.begin(); i != content_video.end(); ++i) { - pvf.push_back ( - shared_ptr ( - new PlayerVideo ( - i->image, - time, - piece->content->video->crop (), - piece->content->video->fade (i->frame.index()), - image_size, - _video_container_size, - i->frame.eyes(), - i->part, - piece->content->video->colour_conversion () - ) - ) - ); - } - } - } else { - /* Discard unused video */ - decoder->get (dcp_to_content_video (piece, time), accurate); - } - } - } - - if (subtitles) { - BOOST_FOREACH (shared_ptr p, pvf) { - p->set_subtitle (subtitles.get ()); - } - } - - return pvf; -} - -/** @return Audio data or 0 if the only audio data here is referenced DCP data */ -shared_ptr -Player::get_audio (DCPTime time, DCPTime length, bool accurate) -{ - if (!_have_valid_pieces) { - setup_pieces (); - } - - Frame const length_frames = length.frames_round (_film->audio_frame_rate ()); - - shared_ptr audio (new AudioBuffers (_film->audio_channels(), length_frames)); - audio->make_silent (); - - list > ov = overlaps (time, time + length, has_audio); - if (ov.empty ()) { - return audio; - } - - bool all_referenced = true; - BOOST_FOREACH (shared_ptr i, ov) { - shared_ptr dcp_content = dynamic_pointer_cast (i->content); - if (i->content->audio && (!dcp_content || !dcp_content->reference_audio ())) { - /* There is audio content which is not from a DCP or not set to be referenced */ - all_referenced = false; - } - } - - if (all_referenced && !_play_referenced) { - return shared_ptr (); - } - - BOOST_FOREACH (shared_ptr i, ov) { - - DCPOMATIC_ASSERT (i->content->audio); - shared_ptr decoder = i->decoder->audio; - DCPOMATIC_ASSERT (decoder); - - /* The time that we should request from the content */ - DCPTime request = time - DCPTime::from_seconds (i->content->audio->delay() / 1000.0); - Frame request_frames = length_frames; - DCPTime offset; - if (request < DCPTime ()) { - /* We went off the start of the content, so we will need to offset - the stuff we get back. - */ - offset = -request; - request_frames += request.frames_round (_film->audio_frame_rate ()); - if (request_frames < 0) { - request_frames = 0; - } - request = DCPTime (); - } - - Frame const content_frame = dcp_to_resampled_audio (i, request); - - BOOST_FOREACH (AudioStreamPtr j, i->content->audio->streams ()) { - - if (j->channels() == 0) { - /* Some content (e.g. DCPs) can have streams with no channels */ - continue; - } - - /* Audio from this piece's decoder stream (which might be more or less than what we asked for) */ - ContentAudio all = decoder->get (j, content_frame, request_frames, accurate); - - /* Gain */ - if (i->content->audio->gain() != 0) { - shared_ptr gain (new AudioBuffers (all.audio)); - gain->apply_gain (i->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 = j->mapping (); - for (int i = 0; i < map.input_channels(); ++i) { - for (int j = 0; j < _film->audio_channels(); ++j) { - if (map.get (i, j) > 0) { - dcp_mapped->accumulate_channel ( - all.audio.get(), - i, - j, - map.get (i, j) - ); - } - } - } - - if (_audio_processor) { - dcp_mapped = _audio_processor->run (dcp_mapped, _film->audio_channels ()); - } - - all.audio = dcp_mapped; - - audio->accumulate_frames ( - all.audio.get(), - content_frame - all.frame, - offset.frames_round (_film->audio_frame_rate()), - min (Frame (all.audio->frames()), request_frames) - ); - } - } - - return audio; -} - Frame Player::dcp_to_content_video (shared_ptr piece, DCPTime t) const { @@ -585,82 +368,6 @@ Player::content_subtitle_to_dcp (shared_ptr piece, ContentTime t) c return max (DCPTime (), DCPTime (t - piece->content->trim_start(), piece->frc) + piece->content->position()); } -/** @param burnt true to return only subtitles to be burnt, false to return only - * subtitles that should not be burnt. This parameter will be ignored if - * _always_burn_subtitles is true; in this case, all subtitles will be returned. - */ -PlayerSubtitles -Player::get_subtitles (DCPTime time, DCPTime length, bool starting, bool burnt, bool accurate) -{ - list > subs = overlaps (time, time + length, has_subtitle); - - PlayerSubtitles ps (time); - - for (list >::const_iterator j = subs.begin(); j != subs.end(); ++j) { - if (!(*j)->content->subtitle->use () || (!_always_burn_subtitles && (burnt != (*j)->content->subtitle->burn ()))) { - continue; - } - - shared_ptr dcp_content = dynamic_pointer_cast ((*j)->content); - if (dcp_content && dcp_content->reference_subtitle () && !_play_referenced) { - continue; - } - - shared_ptr subtitle_decoder = (*j)->decoder->subtitle; - ContentTime const from = dcp_to_content_subtitle (*j, time); - /* XXX: this video_frame_rate() should be the rate that the subtitle content has been prepared for */ - ContentTime const to = from + ContentTime::from_frames (1, _film->video_frame_rate ()); - - list image = subtitle_decoder->get_image (ContentTimePeriod (from, to), starting, accurate); - for (list::iterator i = image.begin(); i != image.end(); ++i) { - - /* Apply content's subtitle offsets */ - i->sub.rectangle.x += (*j)->content->subtitle->x_offset (); - i->sub.rectangle.y += (*j)->content->subtitle->y_offset (); - - /* Apply content's subtitle scale */ - i->sub.rectangle.width *= (*j)->content->subtitle->x_scale (); - i->sub.rectangle.height *= (*j)->content->subtitle->y_scale (); - - /* Apply a corrective translation to keep the subtitle centred after that scale */ - i->sub.rectangle.x -= i->sub.rectangle.width * ((*j)->content->subtitle->x_scale() - 1); - i->sub.rectangle.y -= i->sub.rectangle.height * ((*j)->content->subtitle->y_scale() - 1); - - ps.image.push_back (i->sub); - } - - list text = subtitle_decoder->get_text (ContentTimePeriod (from, to), starting, accurate); - BOOST_FOREACH (ContentTextSubtitle& ts, text) { - BOOST_FOREACH (dcp::SubtitleString s, ts.subs) { - s.set_h_position (s.h_position() + (*j)->content->subtitle->x_offset ()); - s.set_v_position (s.v_position() + (*j)->content->subtitle->y_offset ()); - float const xs = (*j)->content->subtitle->x_scale(); - float const ys = (*j)->content->subtitle->y_scale(); - float size = s.size(); - - /* Adjust size to express the common part of the scaling; - e.g. if xs = ys = 0.5 we scale size by 2. - */ - if (xs > 1e-5 && ys > 1e-5) { - size *= 1 / min (1 / xs, 1 / ys); - } - s.set_size (size); - - /* Then express aspect ratio changes */ - if (fabs (1.0 - xs / ys) > dcp::ASPECT_ADJUST_EPSILON) { - s.set_aspect_adjust (xs / ys); - } - s.set_in (dcp::Time(content_subtitle_to_dcp (*j, ts.period().from).seconds(), 1000)); - s.set_out (dcp::Time(content_subtitle_to_dcp (*j, ts.period().to).seconds(), 1000)); - ps.text.push_back (SubtitleString (s, (*j)->content->subtitle->outline_width())); - ps.add_fonts ((*j)->content->subtitle->fonts ()); - } - } - } - - return ps; -} - list > Player::get_subtitle_fonts () { @@ -803,10 +510,83 @@ Player::overlaps (DCPTime from, DCPTime to, boost::function va return overlaps; } -void -Player::reset_pieces () +bool +Player::pass () { + if (!_have_valid_pieces) { + setup_pieces (); + } + + shared_ptr earliest; + DCPTime earliest_position; BOOST_FOREACH (shared_ptr i, _pieces) { - i->decoder->reset (); + /* Convert i->decoder->position() to DCPTime and work out the earliest */ + } + + earliest->decoder->pass (); + + /* XXX: collect audio and maybe emit some */ +} + +void +Player::video (weak_ptr wp, ContentVideo video) +{ + shared_ptr piece = wp.lock (); + if (!piece) { + return; + } + + /* Get subs to burn in and burn them in */ + + + /* Fill gaps */ + + DCPTime time = content_video_to_dcp (piece, video.frame.index()); + + dcp::Size image_size = piece->content->video->scale().size ( + piece->content->video, _video_container_size, _film->frame_size () + ); + + Video ( + shared_ptr ( + new PlayerVideo ( + video.image, + time, + piece->content->video->crop (), + piece->content->video->fade (video.frame.index()), + image_size, + _video_container_size, + video.frame.eyes(), + video.part, + piece->content->video->colour_conversion () + ) + ) + ); + +} + +void +Player::audio (weak_ptr piece, ContentAudio video) +{ + /* Put into merge buffer */ +} + +void +Player::image_subtitle (weak_ptr piece, ContentImageSubtitle subtitle) +{ + /* Store for video to see */ +} + +void +Player::text_subtitle (weak_ptr piece, ContentTextSubtitle subtitle) +{ + /* Store for video to see, or emit */ +} + +void +Player::seek (DCPTime time, bool accurate) +{ + if (accurate) { + _last_video = time - DCPTime::from_frames (1, _film->video_frame_rate ()); } } diff --git a/src/lib/player.h b/src/lib/player.h index cb3403c3d..c0e0f9f70 100644 --- a/src/lib/player.h +++ b/src/lib/player.h @@ -26,6 +26,9 @@ #include "content.h" #include "position_image.h" #include "piece.h" +#include "content_video.h" +#include "content_audio.h" +#include "content_subtitle.h" #include #include #include @@ -48,9 +51,9 @@ class Player : public boost::enable_shared_from_this, public boost::nonc public: Player (boost::shared_ptr, boost::shared_ptr playlist); - std::list > get_video (DCPTime time, bool accurate); - boost::shared_ptr get_audio (DCPTime time, DCPTime length, bool accurate); - PlayerSubtitles get_subtitles (DCPTime time, DCPTime length, bool starting, bool burnt, bool accurate); + bool pass (); + void seek (DCPTime time, bool accurate); + std::list > get_subtitle_fonts (); std::list get_reel_assets (); @@ -70,6 +73,10 @@ public: */ boost::signals2::signal Changed; + boost::signals2::signal)> Video; + boost::signals2::signal, DCPTime)> Audio; + boost::signals2::signal Subtitle; + private: friend class PlayerWrapper; friend class Piece; @@ -79,7 +86,6 @@ private: friend struct player_time_calculation_test3; void setup_pieces (); - void reset_pieces (); void flush (); void film_changed (Film::Property); void playlist_changed (); @@ -93,6 +99,10 @@ private: DCPTime content_subtitle_to_dcp (boost::shared_ptr piece, ContentTime t) const; boost::shared_ptr black_player_video_frame (DCPTime) const; std::list > overlaps (DCPTime from, DCPTime to, boost::function valid); + void video (boost::weak_ptr, ContentVideo); + void audio (boost::weak_ptr, ContentAudio); + void image_subtitle (boost::weak_ptr, ContentImageSubtitle); + void text_subtitle (boost::weak_ptr, ContentTextSubtitle); boost::shared_ptr _film; boost::shared_ptr _playlist; @@ -118,6 +128,8 @@ private: /** true if we should `play' (i.e output) referenced DCP data (e.g. for preview) */ bool _play_referenced; + DCPTime _last_video; + boost::shared_ptr _audio_processor; boost::signals2::scoped_connection _film_changed_connection; diff --git a/src/lib/subtitle_decoder.cc b/src/lib/subtitle_decoder.cc index 3e6ee92f1..c437ec945 100644 --- a/src/lib/subtitle_decoder.cc +++ b/src/lib/subtitle_decoder.cc @@ -40,14 +40,10 @@ using boost::function; SubtitleDecoder::SubtitleDecoder ( Decoder* parent, shared_ptr c, - shared_ptr log, - function (ContentTimePeriod, bool)> image_during, - function (ContentTimePeriod, bool)> text_during + shared_ptr log ) : DecoderPart (parent, log) , _content (c) - , _image_during (image_during) - , _text_during (text_during) { } @@ -60,14 +56,13 @@ SubtitleDecoder::SubtitleDecoder ( * of the video frame) */ void -SubtitleDecoder::give_image (ContentTimePeriod period, shared_ptr image, dcpomatic::Rect rect) +SubtitleDecoder::emit_image (ContentTimePeriod period, shared_ptr image, dcpomatic::Rect rect) { - _decoded_image.push_back (ContentImageSubtitle (period, image, rect)); - _position = period.from; + ImageData (ContentImageSubtitle (period, image, rect)); } void -SubtitleDecoder::give_text (ContentTimePeriod period, list s) +SubtitleDecoder::emit_text (ContentTimePeriod period, list s) { /* We must escape < and > in strings, otherwise they might confuse our subtitle renderer (which uses some HTML-esque markup to do bold/italic etc.) @@ -79,115 +74,11 @@ SubtitleDecoder::give_text (ContentTimePeriod period, list i.set_text (t); } - _decoded_text.push_back (ContentTextSubtitle (period, s)); - _position = period.to; -} - -/** Get the subtitles that correspond to a given list of periods. - * @param subs Subtitles. - * @param sp Periods for which to extract subtitles from subs. - */ -template -list -SubtitleDecoder::get (list const & subs, list const & sp, ContentTimePeriod period, bool accurate) -{ - if (sp.empty ()) { - return list (); - } - - /* Find the time of the first subtitle we don't have in subs */ - optional missing; - BOOST_FOREACH (ContentTimePeriod i, sp) { - typename list::const_iterator j = subs.begin(); - while (j != subs.end() && j->period() != i) { - ++j; - } - if (j == subs.end ()) { - missing = i.from; - break; - } - } - - /* Suggest to our parent decoder that it might want to seek if we haven't got what we're being asked for */ - if (missing) { - _log->log ( - String::compose ( - "SD suggests seek to %1 from %2", - to_string (*missing), - position() ? to_string(*position()) : "nowhere"), - LogEntry::TYPE_DEBUG_DECODE); - maybe_seek (*missing, true); - } - - /* Now enough pass() calls will either: - * (a) give us what we want, or - * (b) hit the end of the decoder. - */ - while (!_parent->pass(Decoder::PASS_REASON_SUBTITLE, accurate) && (subs.empty() || (subs.back().period().to < sp.back().to))) {} - - /* Now look for what we wanted in the data we have collected */ - /* XXX: inefficient */ - - list out; - BOOST_FOREACH (ContentTimePeriod i, sp) { - typename list::const_iterator j = subs.begin(); - while (j != subs.end() && j->period() != i) { - ++j; - } - if (j != subs.end()) { - out.push_back (*j); - } - } - - /* Discard anything in _decoded_image_subtitles that is outside 5 seconds either side of period */ - - list::iterator i = _decoded_image.begin(); - while (i != _decoded_image.end()) { - list::iterator tmp = i; - ++tmp; - - if ( - i->period().to < (period.from - ContentTime::from_seconds (5)) || - i->period().from > (period.to + ContentTime::from_seconds (5)) - ) { - _decoded_image.erase (i); - } - - i = tmp; - } - - return out; -} - -list -SubtitleDecoder::get_text (ContentTimePeriod period, bool starting, bool accurate) -{ - return get (_decoded_text, _text_during (period, starting), period, accurate); -} - -list -SubtitleDecoder::get_image (ContentTimePeriod period, bool starting, bool accurate) -{ - return get (_decoded_image, _image_during (period, starting), period, accurate); -} - -void -SubtitleDecoder::seek (ContentTime t, bool) -{ - _log->log (String::compose ("SD seek to %1", to_string(t)), LogEntry::TYPE_DEBUG_DECODE); - reset (); - _position.reset (); -} - -void -SubtitleDecoder::reset () -{ - _decoded_text.clear (); - _decoded_image.clear (); + TextData (ContentTextSubtitle (period, s)); } void -SubtitleDecoder::give_text (ContentTimePeriod period, sub::Subtitle const & subtitle) +SubtitleDecoder::emit_text (ContentTimePeriod period, sub::Subtitle const & subtitle) { /* See if our next subtitle needs to be placed on screen by us */ bool needs_placement = false; @@ -295,5 +186,5 @@ SubtitleDecoder::give_text (ContentTimePeriod period, sub::Subtitle const & subt } } - give_text (period, out); + emit_text (period, out); } diff --git a/src/lib/subtitle_decoder.h b/src/lib/subtitle_decoder.h index e5e931669..904aaed78 100644 --- a/src/lib/subtitle_decoder.h +++ b/src/lib/subtitle_decoder.h @@ -27,6 +27,7 @@ #include "content_subtitle.h" #include "decoder_part.h" #include +#include namespace sub { class Subtitle; @@ -44,46 +45,22 @@ public: SubtitleDecoder ( Decoder* parent, boost::shared_ptr, - boost::shared_ptr log, - boost::function (ContentTimePeriod, bool)> image_during, - boost::function (ContentTimePeriod, bool)> text_during + boost::shared_ptr log ); - std::list get_image (ContentTimePeriod period, bool starting, bool accurate); - std::list get_text (ContentTimePeriod period, bool starting, bool accurate); - - void seek (ContentTime, bool); - void reset (); - - void give_image (ContentTimePeriod period, boost::shared_ptr, dcpomatic::Rect); - void give_text (ContentTimePeriod period, std::list); - void give_text (ContentTimePeriod period, sub::Subtitle const & subtitle); + void emit_image (ContentTimePeriod period, boost::shared_ptr, dcpomatic::Rect); + void emit_text (ContentTimePeriod period, std::list); + void emit_text (ContentTimePeriod period, sub::Subtitle const & subtitle); boost::shared_ptr content () const { return _content; } - boost::optional position () const { - return _position; - } - - void reset_position () { - _position.reset (); - } + boost::signals2::signal ImageData; + boost::signals2::signal TextData; private: - - std::list _decoded_image; - std::list _decoded_text; boost::shared_ptr _content; - - template - std::list get (std::list const & subs, std::list const & sp, ContentTimePeriod period, bool accurate); - - boost::function (ContentTimePeriod, bool)> _image_during; - boost::function (ContentTimePeriod, bool)> _text_during; - - boost::optional _position; }; #endif diff --git a/src/lib/text_subtitle_decoder.cc b/src/lib/text_subtitle_decoder.cc index dd64cb5d9..b59808728 100644 --- a/src/lib/text_subtitle_decoder.cc +++ b/src/lib/text_subtitle_decoder.cc @@ -38,73 +38,31 @@ TextSubtitleDecoder::TextSubtitleDecoder (shared_ptr : TextSubtitle (content) , _next (0) { - subtitle.reset ( - new SubtitleDecoder ( - this, - content->subtitle, - log, - bind (&TextSubtitleDecoder::image_subtitles_during, this, _1, _2), - bind (&TextSubtitleDecoder::text_subtitles_during, this, _1, _2) - ) - ); + subtitle.reset (new SubtitleDecoder (this, content->subtitle, log)); } void -TextSubtitleDecoder::seek (ContentTime time, bool accurate) +TextSubtitleDecoder::seek (ContentTime time, bool) { - subtitle->seek (time, accurate); - _next = 0; while (_next < _subtitles.size() && ContentTime::from_seconds (_subtitles[_next].from.all_as_seconds ()) < time) { ++_next; } } -bool -TextSubtitleDecoder::pass (PassReason, bool) +void +TextSubtitleDecoder::pass () { if (_next >= _subtitles.size ()) { - return true; + return; } ContentTimePeriod const p = content_time_period (_subtitles[_next]); - subtitle->give_text (p, _subtitles[_next]); + subtitle->emit_text (p, _subtitles[_next]); + subtitle->set_position (p.from); ++_next; - return false; -} - -list -TextSubtitleDecoder::image_subtitles_during (ContentTimePeriod, bool) const -{ - return list (); -} - -list -TextSubtitleDecoder::text_subtitles_during (ContentTimePeriod p, bool starting) const -{ - /* XXX: inefficient */ - - list d; - - /* Only take `during' (not starting) subs if they overlap more than half the requested period; - here's the threshold for being significant. - */ - ContentTime const significant (p.duration().get() / 2); - - for (vector::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) { - ContentTimePeriod t = content_time_period (*i); - if (starting && p.contains(t.from)) { - d.push_back (t); - } else if (!starting) { - optional const o = p.overlap (t); - if (o && o->duration() > significant) { - d.push_back (t); - } - } - } - - return d; + return; } ContentTimePeriod @@ -115,9 +73,3 @@ TextSubtitleDecoder::content_time_period (sub::Subtitle s) const ContentTime::from_seconds (s.to.all_as_seconds()) ); } - -void -TextSubtitleDecoder::reset () -{ - subtitle->reset (); -} diff --git a/src/lib/text_subtitle_decoder.h b/src/lib/text_subtitle_decoder.h index 5477e6f5f..01338683f 100644 --- a/src/lib/text_subtitle_decoder.h +++ b/src/lib/text_subtitle_decoder.h @@ -31,14 +31,10 @@ class TextSubtitleDecoder : public Decoder, public TextSubtitle public: TextSubtitleDecoder (boost::shared_ptr, boost::shared_ptr log); -protected: void seek (ContentTime time, bool accurate); - bool pass (PassReason, bool accurate); - void reset (); + void pass (); private: - std::list image_subtitles_during (ContentTimePeriod, bool starting) const; - std::list text_subtitles_during (ContentTimePeriod, bool starting) const; ContentTimePeriod content_time_period (sub::Subtitle s) const; size_t _next; diff --git a/src/lib/transcoder.cc b/src/lib/transcoder.cc index ee099c7df..da6b08c5f 100644 --- a/src/lib/transcoder.cc +++ b/src/lib/transcoder.cc @@ -61,8 +61,17 @@ Transcoder::Transcoder (shared_ptr film, weak_ptr j) , _writer (new Writer (film, j)) , _encoder (new Encoder (film, _writer)) , _finishing (false) + , _non_burnt_subtitles (false) { + _player->Video.connect (bind (&Transcoder::video, this, _1)); + _player->Audio.connect (bind (&Transcoder::audio, this, _1, _2)); + _player->Subtitle.connect (bind (&Transcoder::subtitle, this, _1)); + BOOST_FOREACH (shared_ptr c, _film->content ()) { + if (c->subtitle && c->subtitle->use() && !c->subtitle->burn()) { + _non_burnt_subtitles = true; + } + } } void @@ -77,54 +86,11 @@ Transcoder::go () job->sub (_("Encoding")); } - DCPTime const frame = DCPTime::from_frames (1, _film->video_frame_rate ()); - DCPTime const length = _film->length (); - - int burnt_subtitles = 0; - int non_burnt_subtitles = 0; - BOOST_FOREACH (shared_ptr c, _film->content ()) { - if (c->subtitle && c->subtitle->use()) { - if (c->subtitle->burn()) { - ++burnt_subtitles; - } else { - ++non_burnt_subtitles; - } - } - } - - if (non_burnt_subtitles) { + if (_non_burnt_subtitles) { _writer->write (_player->get_subtitle_fonts ()); } - for (DCPTime t; t < length; t += frame) { - - BOOST_FOREACH (shared_ptr i, _player->get_video (t, true)) { - if (!_film->three_d()) { - /* 2D DCP */ - if (i->eyes() == EYES_RIGHT) { - /* Discard right-eye images */ - continue; - } else if (i->eyes() == EYES_LEFT) { - /* Use left-eye images for both eyes */ - i->set_eyes (EYES_BOTH); - } - } - - _encoder->encode (i); - } - - _writer->write (_player->get_audio (t, frame, true)); - - if (non_burnt_subtitles) { - _writer->write (_player->get_subtitles (t, frame, true, false, true)); - } - - { - shared_ptr job = _job.lock (); - DCPOMATIC_ASSERT (job); - job->set_progress (float(t.get()) / length.get()); - } - } + while (!_player->pass ()) {} BOOST_FOREACH (ReferencedReelAsset i, _player->get_reel_assets ()) { _writer->write (i); @@ -135,6 +101,35 @@ Transcoder::go () _writer->finish (); } +void +Transcoder::video (shared_ptr data) +{ + if (!_film->three_d() && data->eyes() == EYES_LEFT) { + /* Use left-eye images for both eyes */ + data->set_eyes (EYES_BOTH); + } + + _encoder->encode (data); +} + +void +Transcoder::audio (shared_ptr data, DCPTime time) +{ + _writer->write (data); + + shared_ptr job = _job.lock (); + DCPOMATIC_ASSERT (job); + job->set_progress (float(time.get()) / _film->length().get()); +} + +void +Transcoder::subtitle (PlayerSubtitles data) +{ + if (_non_burnt_subtitles) { + _writer->write (data); + } +} + float Transcoder::current_encoding_rate () const { diff --git a/src/lib/transcoder.h b/src/lib/transcoder.h index 14f363661..1b20bbffc 100644 --- a/src/lib/transcoder.h +++ b/src/lib/transcoder.h @@ -19,6 +19,7 @@ */ #include "types.h" +#include "player_subtitles.h" #include class Film; @@ -26,6 +27,8 @@ class Encoder; class Player; class Writer; class Job; +class PlayerVideo; +class AudioBuffers; /** @class Transcoder */ class Transcoder : public boost::noncopyable @@ -44,10 +47,16 @@ public: } private: + + void video (boost::shared_ptr); + void audio (boost::shared_ptr, DCPTime); + void subtitle (PlayerSubtitles); + boost::shared_ptr _film; boost::weak_ptr _job; boost::shared_ptr _player; boost::shared_ptr _writer; boost::shared_ptr _encoder; bool _finishing; + bool _non_burnt_subtitles; }; diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc index 0e9ee0c1a..6ec479ad2 100644 --- a/src/lib/video_decoder.cc +++ b/src/lib/video_decoder.cc @@ -38,223 +38,9 @@ using boost::optional; VideoDecoder::VideoDecoder (Decoder* parent, shared_ptr c, shared_ptr log) : DecoderPart (parent, log) -#ifdef DCPOMATIC_DEBUG - , test_gaps (0) -#endif , _content (c) - , _last_seek_accurate (true) { - _black_image.reset (new Image (AV_PIX_FMT_RGB24, _content->video->size(), true)); - _black_image->make_black (); -} - -list -VideoDecoder::decoded (Frame frame) -{ - list output; - - BOOST_FOREACH (ContentVideo const & i, _decoded) { - if (i.frame.index() == frame) { - output.push_back (i); - } - } - - return output; -} - -/** Get all frames which exist in the content at a given frame index. - * @param frame Frame index. - * @param accurate true to try hard to return frames at the precise time that was requested, otherwise frames nearby may be returned. - * @return Frames; there may be none (if there is no video there), 1 for 2D or 2 for 3D. - */ -list -VideoDecoder::get (Frame frame, bool accurate) -{ - if (_no_data_frame && frame >= _no_data_frame.get()) { - return list (); - } - - _log->log (String::compose ("VD has request for %1", frame), LogEntry::TYPE_DEBUG_DECODE); - - /* See if we have frame, and suggest a seek if not */ - - list::const_iterator i = _decoded.begin (); - while (i != _decoded.end() && i->frame.index() != frame) { - _log->log (String::compose ("VD has stored %1 which is no good", i->frame.index()), LogEntry::TYPE_DEBUG_DECODE); - ++i; - } - - if (i == _decoded.end()) { - Frame seek_frame = frame; - if (_content->video->frame_type() == VIDEO_FRAME_TYPE_3D_ALTERNATE) { - /* 3D alternate is a special case as the frame index in the content is not the same - as the frame index we are talking about here. - */ - seek_frame *= 2; - } - _log->log (String::compose ("VD suggests seek to %1", seek_frame), LogEntry::TYPE_DEBUG_DECODE); - maybe_seek (ContentTime::from_frames (seek_frame, _content->active_video_frame_rate()), accurate); - } - - /* Work out the number of frames that we should return; we - must return all frames in our content at the requested `time' - (i.e. frame) - */ - unsigned int frames_wanted = 0; - switch (_content->video->frame_type()) { - case VIDEO_FRAME_TYPE_2D: - case VIDEO_FRAME_TYPE_3D_LEFT: - case VIDEO_FRAME_TYPE_3D_RIGHT: - frames_wanted = 1; - break; - case VIDEO_FRAME_TYPE_3D: - case VIDEO_FRAME_TYPE_3D_ALTERNATE: - case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT: - case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM: - frames_wanted = 2; - break; - default: - DCPOMATIC_ASSERT (false); - } - - list dec; - - /* Now enough pass() calls should either: - * (a) give us what we want, or - * (b) give us something after what we want, indicating that we will never get what we want, or - * (c) hit the end of the decoder. - */ - if (accurate) { - /* We are being accurate, so we want the right frame. - * This could all be one statement but it's split up for clarity. - */ - bool no_data = false; - - while (true) { - if (decoded(frame).size() == frames_wanted) { - /* We got what we want */ - break; - } - - if (_parent->pass (Decoder::PASS_REASON_VIDEO, accurate)) { - /* The decoder has nothing more for us */ - no_data = true; - break; - } - - if (!_decoded.empty() && _decoded.front().frame.index() > frame) { - /* We're never going to get the frame we want. Perhaps the caller is asking - * for a video frame before the content's video starts (if its audio - * begins before its video, for example). - */ - break; - } - } - - dec = decoded (frame); - - if (no_data && dec.empty()) { - _no_data_frame = frame; - } - - } else { - /* Any frame(s) will do: use the first one(s) that comes out of pass() */ - while (_decoded.size() < frames_wanted && !_parent->pass (Decoder::PASS_REASON_VIDEO, accurate)) {} - list::const_iterator i = _decoded.begin(); - unsigned int j = 0; - while (i != _decoded.end() && j < frames_wanted) { - dec.push_back (*i); - ++i; - ++j; - } - } - - /* Clean up _decoded; keep the frame we are returning, if any (which may have two images - for 3D), but nothing before that - */ - while (!_decoded.empty() && !dec.empty() && _decoded.front().frame.index() < dec.front().frame.index()) { - _log->log (String::compose ("VD discards %1", _decoded.front().frame.index()), LogEntry::TYPE_DEBUG_DECODE); - _decoded.pop_front (); - } - - return dec; -} - -/** Fill _decoded from `from' up to, but not including, `to' with - * a frame for one particular Eyes value (which could be EYES_BOTH, - * EYES_LEFT or EYES_RIGHT) - */ -void -VideoDecoder::fill_one_eye (Frame from, Frame to, Eyes eye) -{ - if (to == 0) { - /* Already OK */ - return; - } - - /* Fill with black... */ - shared_ptr filler_image (new RawImageProxy (_black_image)); - Part filler_part = PART_WHOLE; - - /* ...unless there's some video we can fill with */ - if (!_decoded.empty ()) { - filler_image = _decoded.back().image; - filler_part = _decoded.back().part; - } - - for (Frame i = from; i < to; ++i) { -#ifdef DCPOMATIC_DEBUG - test_gaps++; -#endif - _decoded.push_back ( - ContentVideo (filler_image, VideoFrame (i, eye), filler_part) - ); - } -} - -/** Fill _decoded from `from' up to, but not including, `to' - * adding both left and right eye frames. - */ -void -VideoDecoder::fill_both_eyes (VideoFrame from, VideoFrame to) -{ - /* Fill with black... */ - shared_ptr filler_left_image (new RawImageProxy (_black_image)); - shared_ptr filler_right_image (new RawImageProxy (_black_image)); - Part filler_left_part = PART_WHOLE; - Part filler_right_part = PART_WHOLE; - - /* ...unless there's some video we can fill with */ - for (list::const_reverse_iterator i = _decoded.rbegin(); i != _decoded.rend(); ++i) { - if (i->frame.eyes() == EYES_LEFT && !filler_left_image) { - filler_left_image = i->image; - filler_left_part = i->part; - } else if (i->frame.eyes() == EYES_RIGHT && !filler_right_image) { - filler_right_image = i->image; - filler_right_part = i->part; - } - - if (filler_left_image && filler_right_image) { - break; - } - } - - while (from != to) { -#ifdef DCPOMATIC_DEBUG - test_gaps++; -#endif - - _decoded.push_back ( - ContentVideo ( - from.eyes() == EYES_LEFT ? filler_left_image : filler_right_image, - from, - from.eyes() == EYES_LEFT ? filler_left_part : filler_right_part - ) - ); - - ++from; - } } /** Called by decoder classes when they have a video frame ready. @@ -267,128 +53,45 @@ VideoDecoder::fill_both_eyes (VideoFrame from, VideoFrame to) * and so on. */ void -VideoDecoder::give (shared_ptr image, Frame frame) +VideoDecoder::emit (shared_ptr image, Frame frame) { if (ignore ()) { return; } - _log->log (String::compose ("VD receives %1", frame), LogEntry::TYPE_DEBUG_DECODE); - _position = ContentTime::from_frames (frame, _content->active_video_frame_rate()); - /* Work out what we are going to push into _decoded next */ - list to_push; switch (_content->video->frame_type ()) { case VIDEO_FRAME_TYPE_2D: - to_push.push_back (ContentVideo (image, VideoFrame (frame, EYES_BOTH), PART_WHOLE)); + Data (ContentVideo (image, VideoFrame (frame, EYES_BOTH), PART_WHOLE)); break; case VIDEO_FRAME_TYPE_3D: { /* We receive the same frame index twice for 3D; hence we know which frame this one is. */ - bool const same = (!_decoded.empty() && frame == _decoded.back().frame.index()); - to_push.push_back (ContentVideo (image, VideoFrame (frame, same ? EYES_RIGHT : EYES_LEFT), PART_WHOLE)); + bool const same = (_last_emitted && _last_emitted.get() == frame); + Data (ContentVideo (image, VideoFrame (frame, same ? EYES_RIGHT : EYES_LEFT), PART_WHOLE)); + _last_emitted = frame; break; } case VIDEO_FRAME_TYPE_3D_ALTERNATE: - to_push.push_back (ContentVideo (image, VideoFrame (frame / 2, (frame % 2) ? EYES_RIGHT : EYES_LEFT), PART_WHOLE)); + Data (ContentVideo (image, VideoFrame (frame / 2, (frame % 2) ? EYES_RIGHT : EYES_LEFT), PART_WHOLE)); break; case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT: - to_push.push_back (ContentVideo (image, VideoFrame (frame, EYES_LEFT), PART_LEFT_HALF)); - to_push.push_back (ContentVideo (image, VideoFrame (frame, EYES_RIGHT), PART_RIGHT_HALF)); + Data (ContentVideo (image, VideoFrame (frame, EYES_LEFT), PART_LEFT_HALF)); + Data (ContentVideo (image, VideoFrame (frame, EYES_RIGHT), PART_RIGHT_HALF)); break; case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM: - to_push.push_back (ContentVideo (image, VideoFrame (frame, EYES_LEFT), PART_TOP_HALF)); - to_push.push_back (ContentVideo (image, VideoFrame (frame, EYES_RIGHT), PART_BOTTOM_HALF)); + Data (ContentVideo (image, VideoFrame (frame, EYES_LEFT), PART_TOP_HALF)); + Data (ContentVideo (image, VideoFrame (frame, EYES_RIGHT), PART_BOTTOM_HALF)); break; case VIDEO_FRAME_TYPE_3D_LEFT: - to_push.push_back (ContentVideo (image, VideoFrame (frame, EYES_LEFT), PART_WHOLE)); + Data (ContentVideo (image, VideoFrame (frame, EYES_LEFT), PART_WHOLE)); break; case VIDEO_FRAME_TYPE_3D_RIGHT: - to_push.push_back (ContentVideo (image, VideoFrame (frame, EYES_RIGHT), PART_WHOLE)); + Data (ContentVideo (image, VideoFrame (frame, EYES_RIGHT), PART_WHOLE)); break; default: DCPOMATIC_ASSERT (false); } - - /* Now VideoDecoder is required never to have gaps in the frames that it presents - via get_video(). Hence we need to fill in any gap between the last thing in _decoded - and the things we are about to push. - */ - - optional from; - - if (_decoded.empty() && _last_seek_time && _last_seek_accurate) { - from = VideoFrame ( - _last_seek_time->frames_round (_content->active_video_frame_rate ()), - _content->video->frame_type() == VIDEO_FRAME_TYPE_2D ? EYES_BOTH : EYES_LEFT - ); - } else if (!_decoded.empty ()) { - /* Get the last frame we have */ - from = _decoded.back().frame; - /* And move onto the first frame we need */ - ++(*from); - if (_content->video->frame_type() == VIDEO_FRAME_TYPE_3D_LEFT || _content->video->frame_type() == VIDEO_FRAME_TYPE_3D_RIGHT) { - /* The previous ++ will increment a 3D-left-eye to the same index right-eye. If we are dealing with - a single-eye source we need an extra ++ to move back to the same eye. - */ - ++(*from); - } - } - - /* If we've pre-rolled on a seek we may now receive out-of-order frames - (frames before the last seek time) which we can just ignore. - */ - if (from && (*from) > to_push.front().frame) { - return; - } - - unsigned int const max_decoded_size = 96; - - /* If _decoded is already `full' there is no point in adding anything more to it, - as the new stuff will just be removed again. - */ - if (_decoded.size() < max_decoded_size) { - if (from) { - switch (_content->video->frame_type ()) { - case VIDEO_FRAME_TYPE_2D: - fill_one_eye (from->index(), to_push.front().frame.index(), EYES_BOTH); - break; - case VIDEO_FRAME_TYPE_3D: - case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT: - case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM: - case VIDEO_FRAME_TYPE_3D_ALTERNATE: - fill_both_eyes (from.get(), to_push.front().frame); - break; - case VIDEO_FRAME_TYPE_3D_LEFT: - fill_one_eye (from->index(), to_push.front().frame.index(), EYES_LEFT); - break; - case VIDEO_FRAME_TYPE_3D_RIGHT: - fill_one_eye (from->index(), to_push.front().frame.index(), EYES_RIGHT); - break; - } - } - - copy (to_push.begin(), to_push.end(), back_inserter (_decoded)); - } - - /* We can't let this build up too much or we will run out of memory. There is a - `best' value for the allowed size of _decoded which balances memory use - with decoding efficiency (lack of seeks). Throwing away video frames here - is not a problem for correctness, so do it. - */ - while (_decoded.size() > max_decoded_size) { - _decoded.pop_back (); - } -} - -void -VideoDecoder::seek (ContentTime s, bool accurate) -{ - _log->log (String::compose ("VD seek to %1", to_string(s)), LogEntry::TYPE_DEBUG_DECODE); - _decoded.clear (); - _last_seek_time = s; - _last_seek_accurate = accurate; - _position.reset (); } diff --git a/src/lib/video_decoder.h b/src/lib/video_decoder.h index 156ee4222..08173d34d 100644 --- a/src/lib/video_decoder.h +++ b/src/lib/video_decoder.h @@ -46,44 +46,18 @@ class VideoDecoder : public DecoderPart public: VideoDecoder (Decoder* parent, boost::shared_ptr c, boost::shared_ptr log); - std::list get (Frame frame, bool accurate); - -#ifdef DCPOMATIC_DEBUG - int test_gaps; -#endif - friend struct video_decoder_fill_test1; friend struct video_decoder_fill_test2; friend struct ffmpeg_pts_offset_test; friend void ffmpeg_decoder_sequential_test_one (boost::filesystem::path file, float fps, int gaps, int video_length); - void seek (ContentTime time, bool accurate); - void give (boost::shared_ptr, Frame frame); + void emit (boost::shared_ptr, Frame frame); - boost::optional position () const { - return _position; - } - - void reset_position () { - _position.reset (); - } + boost::signals2::signal Data; private: - - std::list decoded (Frame frame); - void fill_one_eye (Frame from, Frame to, Eyes); - void fill_both_eyes (VideoFrame from, VideoFrame to); - boost::shared_ptr _content; - std::list _decoded; - boost::shared_ptr _black_image; - boost::optional _last_seek_time; - bool _last_seek_accurate; - /** if set, this is a frame for which we got no data because the Decoder said - * it has no more to give. - */ - boost::optional _no_data_frame; - boost::optional _position; + boost::optional _last_emitted; }; #endif diff --git a/src/lib/video_mxf_decoder.cc b/src/lib/video_mxf_decoder.cc index dc4f8d60b..95dd668ee 100644 --- a/src/lib/video_mxf_decoder.cc +++ b/src/lib/video_mxf_decoder.cc @@ -66,36 +66,34 @@ VideoMXFDecoder::VideoMXFDecoder (shared_ptr content, sha } } -bool -VideoMXFDecoder::pass (PassReason, bool) +void +VideoMXFDecoder::pass () { double const vfr = _content->active_video_frame_rate (); int64_t const frame = _next.frames_round (vfr); if (frame >= _content->video->length()) { - return true; + return; } if (_mono_reader) { - video->give ( + video->emit ( shared_ptr (new J2KImageProxy (_mono_reader->get_frame(frame), _size, AV_PIX_FMT_XYZ12LE)), frame ); } else { - video->give ( + video->emit ( shared_ptr (new J2KImageProxy (_stereo_reader->get_frame(frame), _size, dcp::EYE_LEFT, AV_PIX_FMT_XYZ12LE)), frame ); - video->give ( + video->emit ( shared_ptr (new J2KImageProxy (_stereo_reader->get_frame(frame), _size, dcp::EYE_RIGHT, AV_PIX_FMT_XYZ12LE)), frame ); } _next += ContentTime::from_frames (1, vfr); - return false; } void -VideoMXFDecoder::seek (ContentTime t, bool accurate) +VideoMXFDecoder::seek (ContentTime t, bool) { - video->seek (t, accurate); _next = t; } diff --git a/src/lib/video_mxf_decoder.h b/src/lib/video_mxf_decoder.h index 330d59ed8..3a93bbb06 100644 --- a/src/lib/video_mxf_decoder.h +++ b/src/lib/video_mxf_decoder.h @@ -30,10 +30,11 @@ class VideoMXFDecoder : public Decoder public: VideoMXFDecoder (boost::shared_ptr, boost::shared_ptr log); -private: - bool pass (PassReason, bool accurate); + void pass (); void seek (ContentTime t, bool accurate); +private: + boost::shared_ptr _content; /** Time of next thing to return from pass */ ContentTime _next; diff --git a/src/tools/server_test.cc b/src/tools/server_test.cc index cb3d49f31..3fa7ebb60 100644 --- a/src/tools/server_test.cc +++ b/src/tools/server_test.cc @@ -42,6 +42,7 @@ using std::string; using std::pair; using boost::shared_ptr; using boost::optional; +using boost::bind; using dcp::Data; static shared_ptr film; @@ -146,11 +147,8 @@ main (int argc, char* argv[]) film->read_metadata (); shared_ptr player (new Player (film, film->playlist ())); - - 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).front ()); - } + player->Video.connect (bind (&process_video, _1)); + while (!player->pass ()) {} } catch (std::exception& e) { cerr << "Error: " << e.what() << "\n"; } diff --git a/src/wx/film_viewer.cc b/src/wx/film_viewer.cc index 3c6138d46..beab8f321 100644 --- a/src/wx/film_viewer.cc +++ b/src/wx/film_viewer.cc @@ -79,7 +79,7 @@ FilmViewer::FilmViewer (wxWindow* p) , _play_button (new wxToggleButton (this, wxID_ANY, _("Play"))) , _coalesce_player_changes (false) , _pending_player_change (false) - , _last_get_accurate (true) + , _last_seek_accurate (true) { #ifndef __WXOSX__ _panel->SetDoubleBuffered (true); @@ -196,93 +196,56 @@ FilmViewer::refresh_panel () } void -FilmViewer::get (DCPTime p, bool accurate) +FilmViewer::video (shared_ptr pv) { if (!_player) { return; } - list > all_pv; - try { - all_pv = _player->get_video (p, accurate); - } catch (exception& e) { - error_dialog (this, wxString::Format (_("Could not get video for view (%s)"), std_to_wx(e.what()).data())); - } - - if (!all_pv.empty ()) { - try { - shared_ptr pv; - if (all_pv.size() == 2) { - /* We have 3D; choose the correct eye */ - if (_left_eye->GetValue()) { - if (all_pv.front()->eyes() == EYES_LEFT) { - pv = all_pv.front(); - } else { - pv = all_pv.back(); - } - } else { - if (all_pv.front()->eyes() == EYES_RIGHT) { - pv = all_pv.front(); - } else { - pv = all_pv.back(); - } - } - } else { - /* 2D; no choice to make */ - pv = all_pv.front (); - } - - /* In an ideal world, what we would do here is: - * - * 1. convert to XYZ exactly as we do in the DCP creation path. - * 2. convert back to RGB for the preview display, compensating - * for the monitor etc. etc. - * - * but this is inefficient if the source is RGB. Since we don't - * (currently) care too much about the precise accuracy of the preview's - * colour mapping (and we care more about its speed) we try to short- - * circuit this "ideal" situation in some cases. - * - * The content's specified colour conversion indicates the colourspace - * which the content is in (according to the user). - * - * PlayerVideo::image (bound to PlayerVideo::always_rgb) will take the source - * image and convert it (from whatever the user has said it is) to RGB. - */ - - _frame = pv->image ( - bind (&Log::dcp_log, _film->log().get(), _1, _2), - bind (&PlayerVideo::always_rgb, _1), - false, true - ); - - ImageChanged (pv); - - _position = pv->time (); - _inter_position = pv->inter_position (); - _inter_size = pv->inter_size (); - } catch (dcp::DCPReadError& e) { - /* This can happen on the following sequence of events: - * - load encrypted DCP - * - add KDM - * - DCP is examined again, which sets its "playable" flag to 1 - * - as a side effect of the exam, the viewer is updated using the old pieces - * - the DCPDecoder in the old piece gives us an encrypted frame - * - then, the pieces are re-made (but too late). - * - * I hope there's a better way to handle this ... - */ - _frame.reset (); - _position = p; + if (_film->three_d ()) { + if ((_left_eye->GetValue() && pv->eyes() == EYES_RIGHT) || (_right_eye->GetValue() && pv->eyes() == EYES_LEFT)) { + return; } - } else { - _frame.reset (); - _position = p; } + /* In an ideal world, what we would do here is: + * + * 1. convert to XYZ exactly as we do in the DCP creation path. + * 2. convert back to RGB for the preview display, compensating + * for the monitor etc. etc. + * + * but this is inefficient if the source is RGB. Since we don't + * (currently) care too much about the precise accuracy of the preview's + * colour mapping (and we care more about its speed) we try to short- + * circuit this "ideal" situation in some cases. + * + * The content's specified colour conversion indicates the colourspace + * which the content is in (according to the user). + * + * PlayerVideo::image (bound to PlayerVideo::always_rgb) will take the source + * image and convert it (from whatever the user has said it is) to RGB. + */ + + _frame = pv->image ( + bind (&Log::dcp_log, _film->log().get(), _1, _2), + bind (&PlayerVideo::always_rgb, _1), + false, true + ); + + ImageChanged (pv); + + _position = pv->time (); + _inter_position = pv->inter_position (); + _inter_size = pv->inter_size (); + refresh_panel (); +} - _last_get_accurate = accurate; +void +FilmViewer::get () +{ + Image const * current = _frame.get (); + while (!_player->pass() && _frame.get() == current) {} } void @@ -294,7 +257,7 @@ FilmViewer::timer () _play_button->SetValue (false); check_play_state (); } else { - get (_position + frame, true); + get (); } update_position_label (); @@ -351,7 +314,7 @@ FilmViewer::slider_moved () if (t >= _film->length ()) { t = _film->length() - DCPTime::from_frames (1, _film->video_frame_rate ()); } - get (t, false); + seek (t, false); update_position_label (); } @@ -485,7 +448,7 @@ FilmViewer::go_to (DCPTime t) t = _film->length (); } - get (t, true); + seek (t, true); update_position_label (); update_position_slider (); } @@ -551,14 +514,14 @@ FilmViewer::film_changed (Film::Property p) void FilmViewer::refresh () { - get (_position, _last_get_accurate); + seek (_position, _last_seek_accurate); } void FilmViewer::set_position (DCPTime p) { _position = p; - get (_position, true); + seek (p, true); update_position_label (); update_position_slider (); } @@ -602,3 +565,11 @@ FilmViewer::jump_to_selected_clicked () { Config::instance()->set_jump_to_selected (_jump_to_selected->GetValue ()); } + +void +FilmViewer::seek (DCPTime t, bool accurate) +{ + _player->seek (t, accurate); + _last_seek_accurate = accurate; + get (); +} diff --git a/src/wx/film_viewer.h b/src/wx/film_viewer.h index 0db4bccc5..563178ac3 100644 --- a/src/wx/film_viewer.h +++ b/src/wx/film_viewer.h @@ -67,7 +67,9 @@ private: void player_changed (bool); void update_position_label (); void update_position_slider (); - void get (DCPTime, bool); + void video (boost::shared_ptr); + void get (); + void seek (DCPTime t, bool accurate); void refresh_panel (); void setup_sensitivity (); void film_changed (Film::Property); @@ -105,12 +107,11 @@ private: dcp::Size _out_size; /** Size of the panel that we have available */ dcp::Size _panel_size; - /** true if the last call to ::get() was specified to be accurate; + /** true if the last call to Player::seek() was specified to be accurate; * this is used so that when re-fetching the current frame we * can get the same one that we got last time. */ - bool _last_get_accurate; - + bool _last_seek_accurate; boost::signals2::scoped_connection _film_connection; boost::signals2::scoped_connection _player_connection; }; diff --git a/src/wx/subtitle_view.cc b/src/wx/subtitle_view.cc index e3aaffdfc..6ec71fce5 100644 --- a/src/wx/subtitle_view.cc +++ b/src/wx/subtitle_view.cc @@ -66,6 +66,9 @@ SubtitleView::SubtitleView (wxWindow* parent, shared_ptr film, shared_ptr< sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder()); } +#if 0 + XXX + list subs = decoder->subtitle->get_text (ContentTimePeriod (ContentTime(), ContentTime::max ()), true, true); FrameRateChange const frc = film->active_frame_rate_change (position); int n = 0; @@ -81,6 +84,7 @@ SubtitleView::SubtitleView (wxWindow* parent, shared_ptr film, shared_ptr< ++n; } } +#endif SetSizerAndFit (sizer); } diff --git a/test/audio_decoder_test.cc b/test/audio_decoder_test.cc index 57852dcb9..6e92c48de 100644 --- a/test/audio_decoder_test.cc +++ b/test/audio_decoder_test.cc @@ -72,7 +72,7 @@ public: audio.reset (new AudioDecoder (this, content->audio, log)); } - bool pass (PassReason, bool) + bool pass () { Frame const N = min ( Frame (2000), @@ -94,7 +94,6 @@ public: void seek (ContentTime t, bool accurate) { - audio->seek (t, accurate); _position = t.frames_round (_test_audio_content->audio->resampled_frame_rate ()); } diff --git a/test/wscript b/test/wscript index 9b18867de..80f2c1c08 100644 --- a/test/wscript +++ b/test/wscript @@ -43,7 +43,6 @@ def build(bld): audio_analysis_test.cc audio_buffers_test.cc audio_delay_test.cc - audio_decoder_test.cc audio_filter_test.cc audio_mapping_test.cc audio_processor_test.cc @@ -51,14 +50,10 @@ def build(bld): black_fill_test.cc client_server_test.cc colour_conversion_test.cc - dcp_subtitle_test.cc dcpomatic_time_test.cc digest_test.cc ffmpeg_audio_test.cc - ffmpeg_audio_only_test.cc ffmpeg_dcp_test.cc - ffmpeg_decoder_seek_test.cc - ffmpeg_decoder_sequential_test.cc ffmpeg_examiner_test.cc ffmpeg_pts_offset_test.cc file_group_test.cc @@ -74,7 +69,6 @@ def build(bld): j2k_bandwidth_test.cc job_test.cc make_black_test.cc - player_test.cc pixel_formats_test.cc ratio_test.cc repeat_frame_test.cc @@ -85,7 +79,6 @@ def build(bld): render_subtitles_test.cc resampler_test.cc scaling_test.cc - seek_zero_test.cc silence_padding_test.cc skip_frame_test.cc srt_subtitle_test.cc @@ -95,11 +88,9 @@ def build(bld): threed_test.cc time_calculation_test.cc update_checker_test.cc - upmixer_a_test.cc util_test.cc vf_test.cc video_content_scale_test.cc - video_decoder_fill_test.cc video_frame_test.cc video_mxf_content_test.cc vf_kdm_test.cc @@ -109,5 +100,17 @@ def build(bld): # and others... # burnt_subtitle_test.cc + # XXX + # audio_decoder_test.cc + # dcp_subtitle_test.cc + # ffmpeg_audio_only_test.cc + # ffmpeg_decoder_seek_test.cc + # ffmpeg_decoder_sequential_test.cc + # silence_padding_test.cc + # player_test.cc + # seek_zero_test.cc + # upmixer_a_test.cc + # video_decoder_fill_test.cc + obj.target = 'unit-tests' obj.install_path = '' -- 2.30.2