X-Git-Url: https://main.carlh.net/gitweb/?p=dcpomatic.git;a=blobdiff_plain;f=src%2Flib%2Fplayer.cc;h=df10ec14612f0d348cc27b70eea3d0402bbd7f39;hp=059465724def5c930af32ce1b3612712fbbe261c;hb=b56ca8250b8eae123c0992a50f5cabe99e655763;hpb=15a83d720780d58f905d40f8493cdcb86596eaee diff --git a/src/lib/player.cc b/src/lib/player.cc index 059465724..df10ec146 100644 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2013-2020 Carl Hetherington + Copyright (C) 2013-2021 Carl Hetherington This file is part of DCP-o-matic. @@ -18,11 +18,14 @@ */ + +#include "atmos_decoder.h" #include "player.h" #include "film.h" #include "audio_buffers.h" #include "content_audio.h" #include "dcp_content.h" +#include "dcpomatic_log.h" #include "job.h" #include "image.h" #include "raw_image_proxy.h" @@ -54,30 +57,35 @@ #include #include #include -#include #include #include #include #include "i18n.h" -using std::list; + +using std::copy; using std::cout; -using std::min; +using std::dynamic_pointer_cast; +using std::list; +using std::make_pair; +using std::make_shared; using std::max; using std::min; -using std::vector; +using std::min; using std::pair; -using std::map; -using std::make_pair; -using std::copy; -using boost::shared_ptr; -using boost::weak_ptr; -using boost::dynamic_pointer_cast; +using std::shared_ptr; +using std::vector; +using std::weak_ptr; +using std::make_shared; using boost::optional; using boost::scoped_ptr; +#if BOOST_VERSION >= 106100 +using namespace boost::placeholders; +#endif using namespace dcpomatic; + int const PlayerProperty::VIDEO_CONTAINER_SIZE = 700; int const PlayerProperty::PLAYLIST = 701; int const PlayerProperty::FILM_CONTAINER = 702; @@ -85,39 +93,29 @@ int const PlayerProperty::FILM_VIDEO_FRAME_RATE = 703; int const PlayerProperty::DCP_DECODE_REDUCTION = 704; int const PlayerProperty::PLAYBACK_LENGTH = 705; -Player::Player (shared_ptr film) + +Player::Player (shared_ptr film, Image::Alignment subtitle_alignment) : _film (film) , _suspended (0) - , _ignore_video (false) - , _ignore_audio (false) - , _ignore_text (false) - , _always_burn_open_subtitles (false) - , _fast (false) , _tolerant (film->tolerant()) - , _play_referenced (false) , _audio_merger (_film->audio_frame_rate()) - , _shuffler (0) + , _subtitle_alignment (subtitle_alignment) { construct (); } + Player::Player (shared_ptr film, shared_ptr playlist_) : _film (film) , _playlist (playlist_) , _suspended (0) - , _ignore_video (false) - , _ignore_audio (false) - , _ignore_text (false) - , _always_burn_open_subtitles (false) - , _fast (false) , _tolerant (film->tolerant()) - , _play_referenced (false) , _audio_merger (_film->audio_frame_rate()) - , _shuffler (0) { construct (); } + void Player::construct () { @@ -129,16 +127,12 @@ Player::construct () _playlist_content_change_connection = playlist()->ContentChange.connect (bind(&Player::playlist_content_change, this, _1, _3, _4)); set_video_container_size (_film->frame_size ()); - film_change (CHANGE_TYPE_DONE, Film::AUDIO_PROCESSOR); + film_change (ChangeType::DONE, Film::Property::AUDIO_PROCESSOR); setup_pieces (); seek (DCPTime (), true); } -Player::~Player () -{ - delete _shuffler; -} void Player::setup_pieces () @@ -151,28 +145,29 @@ Player::setup_pieces () bool have_video (shared_ptr content) { - return static_cast(content->video); + return static_cast(content->video) && content->video->use(); } + bool have_audio (shared_ptr content) { return static_cast(content->audio); } + void Player::setup_pieces_unlocked () { _playback_length = _playlist ? _playlist->length(_film) : _film->length(); - list > old_pieces = _pieces; + auto old_pieces = _pieces; _pieces.clear (); - delete _shuffler; - _shuffler = new Shuffler(); + _shuffler.reset (new Shuffler()); _shuffler->Video.connect(bind(&Player::video, this, _1, _2)); - BOOST_FOREACH (shared_ptr i, playlist()->content()) { + for (auto i: playlist()->content()) { if (!i->paths_valid ()) { continue; @@ -184,20 +179,17 @@ Player::setup_pieces_unlocked () } shared_ptr old_decoder; - BOOST_FOREACH (shared_ptr j, old_pieces) { + for (auto j: old_pieces) { if (j->content == i) { old_decoder = j->decoder; break; } } - shared_ptr decoder = decoder_factory (_film, i, _fast, _tolerant, old_decoder); - FrameRateChange frc (_film, i); + auto decoder = decoder_factory (_film, i, _fast, _tolerant, old_decoder); + DCPOMATIC_ASSERT (decoder); - if (!decoder) { - /* Not something that we can decode; e.g. Atmos content */ - continue; - } + FrameRateChange frc (_film, i); if (decoder->video && _ignore_video) { decoder->video->set_ignore (true); @@ -208,12 +200,12 @@ Player::setup_pieces_unlocked () } if (_ignore_text) { - BOOST_FOREACH (shared_ptr i, decoder->text) { + for (auto i: decoder->text) { i->set_ignore (true); } } - shared_ptr dcp = dynamic_pointer_cast (decoder); + auto dcp = dynamic_pointer_cast (decoder); if (dcp) { dcp->set_decode_referenced (_play_referenced); if (_play_referenced) { @@ -221,15 +213,15 @@ Player::setup_pieces_unlocked () } } - shared_ptr piece (new Piece (i, decoder, frc)); + auto piece = make_shared(i, decoder, frc); _pieces.push_back (piece); if (decoder->video) { - if (i->video->frame_type() == VIDEO_FRAME_TYPE_3D_LEFT || i->video->frame_type() == VIDEO_FRAME_TYPE_3D_RIGHT) { + if (i->video->frame_type() == VideoFrameType::THREE_D_LEFT || i->video->frame_type() == VideoFrameType::THREE_D_RIGHT) { /* We need a Shuffler to cope with 3D L/R video data arriving out of sequence */ - decoder->video->Data.connect (bind (&Shuffler::video, _shuffler, weak_ptr(piece), _1)); + decoder->video->Data.connect (bind(&Shuffler::video, _shuffler.get(), weak_ptr(piece), _1)); } else { - decoder->video->Data.connect (bind (&Player::video, this, weak_ptr(piece), _1)); + decoder->video->Data.connect (bind(&Player::video, this, weak_ptr(piece), _1)); } } @@ -237,7 +229,7 @@ Player::setup_pieces_unlocked () decoder->audio->Data.connect (bind (&Player::audio, this, weak_ptr (piece), _1, _2)); } - list >::const_iterator j = decoder->text.begin(); + auto j = decoder->text.begin(); while (j != decoder->text.end()) { (*j)->BitmapStart.connect ( @@ -252,77 +244,111 @@ Player::setup_pieces_unlocked () ++j; } + + if (decoder->atmos) { + decoder->atmos->Data.connect (bind(&Player::atmos, this, weak_ptr(piece), _1)); + } } _stream_states.clear (); - BOOST_FOREACH (shared_ptr i, _pieces) { + for (auto i: _pieces) { if (i->content->audio) { - BOOST_FOREACH (AudioStreamPtr j, i->content->audio->streams()) { + for (auto j: i->content->audio->streams()) { _stream_states[j] = StreamState (i, i->content->position ()); } } } + for (auto i = _pieces.begin(); i != _pieces.end(); ++i) { + if (auto video = (*i)->content->video) { + if (video->use() && video->frame_type() != VideoFrameType::THREE_D_LEFT && video->frame_type() != VideoFrameType::THREE_D_RIGHT) { + /* Look for content later in the content list with in-use video that overlaps this */ + auto period = DCPTimePeriod((*i)->content->position(), (*i)->content->end(_film)); + auto j = i; + ++j; + for (; j != _pieces.end(); ++j) { + if ((*j)->content->video && (*j)->content->video->use()) { + (*i)->ignore_video = DCPTimePeriod((*j)->content->position(), (*j)->content->end(_film)).overlap(period); + } + } + } + } + } + _black = Empty (_film, playlist(), bind(&have_video, _1), _playback_length); _silent = Empty (_film, playlist(), bind(&have_audio, _1), _playback_length); - _last_video_time = DCPTime (); - _last_video_eyes = EYES_BOTH; - _last_audio_time = DCPTime (); + _last_video_time = boost::optional(); + _last_video_eyes = Eyes::BOTH; + _last_audio_time = boost::optional(); } + void Player::playlist_content_change (ChangeType type, int property, bool frequent) { - if (type == CHANGE_TYPE_PENDING) { - /* The player content is probably about to change, so we can't carry on - until that has happened and we've rebuilt our pieces. Stop pass() - and seek() from working until then. - */ - ++_suspended; - } else if (type == CHANGE_TYPE_DONE) { - /* A change in our content has gone through. Re-build our pieces. */ - setup_pieces (); - --_suspended; - } else if (type == CHANGE_TYPE_CANCELLED) { - --_suspended; + if (property == VideoContentProperty::CROP) { + if (type == ChangeType::DONE) { + auto const vcs = video_container_size(); + boost::mutex::scoped_lock lm (_mutex); + for (auto const& i: _delay) { + i.first->reset_metadata (_film, vcs); + } + } + } else { + if (type == ChangeType::PENDING) { + /* The player content is probably about to change, so we can't carry on + until that has happened and we've rebuilt our pieces. Stop pass() + and seek() from working until then. + */ + ++_suspended; + } else if (type == ChangeType::DONE) { + /* A change in our content has gone through. Re-build our pieces. */ + setup_pieces (); + --_suspended; + } else if (type == ChangeType::CANCELLED) { + --_suspended; + } } Change (type, property, frequent); } + void Player::set_video_container_size (dcp::Size s) { - Change (CHANGE_TYPE_PENDING, PlayerProperty::VIDEO_CONTAINER_SIZE, false); + Change (ChangeType::PENDING, PlayerProperty::VIDEO_CONTAINER_SIZE, false); { boost::mutex::scoped_lock lm (_mutex); if (s == _video_container_size) { lm.unlock (); - Change (CHANGE_TYPE_CANCELLED, PlayerProperty::VIDEO_CONTAINER_SIZE, false); + Change (ChangeType::CANCELLED, PlayerProperty::VIDEO_CONTAINER_SIZE, false); return; } _video_container_size = s; - _black_image.reset (new Image (AV_PIX_FMT_RGB24, _video_container_size, true)); + _black_image = make_shared(AV_PIX_FMT_RGB24, _video_container_size, Image::Alignment::PADDED); _black_image->make_black (); } - Change (CHANGE_TYPE_DONE, PlayerProperty::VIDEO_CONTAINER_SIZE, false); + Change (ChangeType::DONE, PlayerProperty::VIDEO_CONTAINER_SIZE, false); } + void Player::playlist_change (ChangeType type) { - if (type == CHANGE_TYPE_DONE) { + if (type == ChangeType::DONE) { setup_pieces (); } Change (type, PlayerProperty::PLAYLIST, false); } + void Player::film_change (ChangeType type, Film::Property p) { @@ -331,54 +357,54 @@ Player::film_change (ChangeType type, Film::Property p) last time we were run. */ - if (p == Film::CONTAINER) { + if (p == Film::Property::CONTAINER) { Change (type, PlayerProperty::FILM_CONTAINER, false); - } else if (p == Film::VIDEO_FRAME_RATE) { + } else if (p == Film::Property::VIDEO_FRAME_RATE) { /* Pieces contain a FrameRateChange which contains the DCP frame rate, so we need new pieces here. */ - if (type == CHANGE_TYPE_DONE) { + if (type == ChangeType::DONE) { setup_pieces (); } Change (type, PlayerProperty::FILM_VIDEO_FRAME_RATE, false); - } else if (p == Film::AUDIO_PROCESSOR) { - if (type == CHANGE_TYPE_DONE && _film->audio_processor ()) { + } else if (p == Film::Property::AUDIO_PROCESSOR) { + if (type == ChangeType::DONE && _film->audio_processor ()) { boost::mutex::scoped_lock lm (_mutex); _audio_processor = _film->audio_processor()->clone (_film->audio_frame_rate ()); } - } else if (p == Film::AUDIO_CHANNELS) { - if (type == CHANGE_TYPE_DONE) { + } else if (p == Film::Property::AUDIO_CHANNELS) { + if (type == ChangeType::DONE) { boost::mutex::scoped_lock lm (_mutex); _audio_merger.clear (); } } } + shared_ptr Player::black_player_video_frame (Eyes eyes) const { - return shared_ptr ( - new PlayerVideo ( - shared_ptr (new RawImageProxy (_black_image)), - Crop (), - optional (), - _video_container_size, - _video_container_size, - eyes, - PART_WHOLE, - PresetColourConversion::all().front().conversion, - VIDEO_RANGE_FULL, - boost::weak_ptr(), - boost::optional(), - false - ) + return std::make_shared ( + std::make_shared(_black_image), + Crop(), + optional(), + _video_container_size, + _video_container_size, + eyes, + Part::WHOLE, + PresetColourConversion::all().front().conversion, + VideoRange::FULL, + std::weak_ptr(), + boost::optional(), + false ); } + Frame Player::dcp_to_content_video (shared_ptr piece, DCPTime t) const { - DCPTime s = t - piece->content->position (); + auto s = t - piece->content->position (); s = min (piece->content->length_after_trim(_film), s); s = max (DCPTime(), s + DCPTime (piece->content->trim_start(), piece->frc)); @@ -392,23 +418,26 @@ Player::dcp_to_content_video (shared_ptr piece, DCPTime t) const return s.frames_floor (piece->frc.dcp) / piece->frc.factor (); } + DCPTime Player::content_video_to_dcp (shared_ptr piece, Frame f) const { /* See comment in dcp_to_content_video */ - DCPTime const d = DCPTime::from_frames (f * piece->frc.factor(), piece->frc.dcp) - DCPTime(piece->content->trim_start(), piece->frc); + auto const d = DCPTime::from_frames (f * piece->frc.factor(), piece->frc.dcp) - DCPTime(piece->content->trim_start(), piece->frc); return d + piece->content->position(); } + Frame Player::dcp_to_resampled_audio (shared_ptr piece, DCPTime t) const { - DCPTime s = t - piece->content->position (); + auto s = t - piece->content->position (); s = min (piece->content->length_after_trim(_film), s); /* See notes in dcp_to_content_video */ - return max (DCPTime (), DCPTime (piece->content->trim_start (), piece->frc) + s).frames_floor (_film->audio_frame_rate ()); + return max (DCPTime(), DCPTime(piece->content->trim_start(), piece->frc) + s).frames_floor(_film->audio_frame_rate()); } + DCPTime Player::resampled_audio_to_dcp (shared_ptr piece, Frame f) const { @@ -418,39 +447,41 @@ Player::resampled_audio_to_dcp (shared_ptr piece, Frame f) const + piece->content->position(); } + ContentTime Player::dcp_to_content_time (shared_ptr piece, DCPTime t) const { - DCPTime s = t - piece->content->position (); + auto s = t - piece->content->position (); s = min (piece->content->length_after_trim(_film), s); return max (ContentTime (), ContentTime (s, piece->frc) + piece->content->trim_start()); } + DCPTime Player::content_time_to_dcp (shared_ptr piece, ContentTime t) const { - return max (DCPTime (), DCPTime (t - piece->content->trim_start(), piece->frc) + piece->content->position()); + return max (DCPTime(), DCPTime(t - piece->content->trim_start(), piece->frc) + piece->content->position()); } -list > + +vector Player::get_subtitle_fonts () { boost::mutex::scoped_lock lm (_mutex); - list > fonts; - BOOST_FOREACH (shared_ptr i, _pieces) { - BOOST_FOREACH (shared_ptr j, i->content->text) { - /* XXX: things may go wrong if there are duplicate font IDs - with different font files. - */ - list > f = j->fonts (); - copy (f.begin(), f.end(), back_inserter (fonts)); - } + vector fonts; + for (auto i: _pieces) { + /* XXX: things may go wrong if there are duplicate font IDs + with different font files. + */ + auto f = i->decoder->fonts (); + copy (f.begin(), f.end(), back_inserter(fonts)); } return fonts; } + /** Set this player never to produce any video data */ void Player::set_ignore_video () @@ -460,6 +491,7 @@ Player::set_ignore_video () setup_pieces_unlocked (); } + void Player::set_ignore_audio () { @@ -468,6 +500,7 @@ Player::set_ignore_audio () setup_pieces_unlocked (); } + void Player::set_ignore_text () { @@ -476,6 +509,7 @@ Player::set_ignore_text () setup_pieces_unlocked (); } + /** Set the player to always burn open texts into the image regardless of the content settings */ void Player::set_always_burn_open_subtitles () @@ -484,6 +518,7 @@ Player::set_always_burn_open_subtitles () _always_burn_open_subtitles = true; } + /** Sets up the player to be faster, possibly at the expense of quality */ void Player::set_fast () @@ -493,6 +528,7 @@ Player::set_fast () setup_pieces_unlocked (); } + void Player::set_play_referenced () { @@ -501,6 +537,7 @@ Player::set_play_referenced () setup_pieces_unlocked (); } + static void maybe_add_asset (list& a, shared_ptr r, Frame reel_trim_start, Frame reel_trim_end, DCPTime from, int const ffr) { @@ -514,66 +551,67 @@ maybe_add_asset (list& a, shared_ptr r, Fra } } + list Player::get_reel_assets () { /* Does not require a lock on _mutex as it's only called from DCPEncoder */ - list a; + list reel_assets; - BOOST_FOREACH (shared_ptr i, playlist()->content()) { - shared_ptr j = dynamic_pointer_cast (i); - if (!j) { + for (auto content: playlist()->content()) { + auto dcp = dynamic_pointer_cast(content); + if (!dcp) { continue; } scoped_ptr decoder; try { - decoder.reset (new DCPDecoder (_film, j, false, false, shared_ptr())); + decoder.reset (new DCPDecoder(_film, dcp, false, false, shared_ptr())); } catch (...) { - return a; + return reel_assets; } - DCPOMATIC_ASSERT (j->video_frame_rate ()); - double const cfr = j->video_frame_rate().get(); - Frame const trim_start = j->trim_start().frames_round (cfr); - Frame const trim_end = j->trim_end().frames_round (cfr); + DCPOMATIC_ASSERT (dcp->video_frame_rate()); + double const cfr = dcp->video_frame_rate().get(); + Frame const trim_start = dcp->trim_start().frames_round(cfr); + Frame const trim_end = dcp->trim_end().frames_round(cfr); int const ffr = _film->video_frame_rate (); /* position in the asset from the start */ int64_t offset_from_start = 0; - /* position in the asset from the end */ + /* position i the asset from the end */ int64_t offset_from_end = 0; - BOOST_FOREACH (shared_ptr k, decoder->reels()) { + for (auto k: decoder->reels()) { /* Assume that main picture duration is the length of the reel */ offset_from_end += k->main_picture()->actual_duration(); } - BOOST_FOREACH (shared_ptr k, decoder->reels()) { + for (auto reel: decoder->reels()) { /* Assume that main picture duration is the length of the reel */ - int64_t const reel_duration = k->main_picture()->actual_duration(); + int64_t const reel_duration = reel->main_picture()->actual_duration(); /* See doc/design/trim_reels.svg */ Frame const reel_trim_start = min(reel_duration, max(int64_t(0), trim_start - offset_from_start)); Frame const reel_trim_end = min(reel_duration, max(int64_t(0), reel_duration - (offset_from_end - trim_end))); - DCPTime const from = i->position() + DCPTime::from_frames (offset_from_start, _film->video_frame_rate()); - if (j->reference_video ()) { - maybe_add_asset (a, k->main_picture(), reel_trim_start, reel_trim_end, from, ffr); + auto const from = content->position() + DCPTime::from_frames(offset_from_start, ffr); + if (dcp->reference_video()) { + maybe_add_asset (reel_assets, reel->main_picture(), reel_trim_start, reel_trim_end, from, ffr); } - if (j->reference_audio ()) { - maybe_add_asset (a, k->main_sound(), reel_trim_start, reel_trim_end, from, ffr); + if (dcp->reference_audio()) { + maybe_add_asset (reel_assets, reel->main_sound(), reel_trim_start, reel_trim_end, from, ffr); } - if (j->reference_text (TEXT_OPEN_SUBTITLE)) { - maybe_add_asset (a, k->main_subtitle(), reel_trim_start, reel_trim_end, from, ffr); + if (dcp->reference_text(TextType::OPEN_SUBTITLE)) { + maybe_add_asset (reel_assets, reel->main_subtitle(), reel_trim_start, reel_trim_end, from, ffr); } - if (j->reference_text (TEXT_CLOSED_CAPTION)) { - BOOST_FOREACH (shared_ptr l, k->closed_captions()) { - maybe_add_asset (a, l, reel_trim_start, reel_trim_end, from, ffr); + if (dcp->reference_text(TextType::CLOSED_CAPTION)) { + for (auto caption: reel->closed_captions()) { + maybe_add_asset (reel_assets, caption, reel_trim_start, reel_trim_end, from, ffr); } } @@ -582,9 +620,10 @@ Player::get_reel_assets () } } - return a; + return reel_assets; } + bool Player::pass () { @@ -592,12 +631,13 @@ Player::pass () if (_suspended) { /* We can't pass in this state */ + LOG_DEBUG_PLAYER_NC ("Player is suspended"); return false; } if (_playback_length == DCPTime()) { /* Special; just give one black frame */ - emit_video (black_player_video_frame(EYES_BOTH), DCPTime()); + emit_video (black_player_video_frame(Eyes::BOTH), DCPTime()); return true; } @@ -606,12 +646,12 @@ Player::pass () shared_ptr earliest_content; optional earliest_time; - BOOST_FOREACH (shared_ptr i, _pieces) { + for (auto i: _pieces) { if (i->done) { continue; } - DCPTime const t = content_time_to_dcp (i, max(i->decoder->position(), i->content->trim_start())); + auto const t = content_time_to_dcp (i, max(i->decoder->position(), i->content->trim_start())); if (t > i->content->end(_film)) { i->done = true; } else { @@ -652,8 +692,9 @@ Player::pass () switch (which) { case CONTENT: { + LOG_DEBUG_PLAYER ("Calling pass() on %1", earliest_content->content->path(0)); earliest_content->done = earliest_content->decoder->pass (); - shared_ptr dcp = dynamic_pointer_cast(earliest_content->content); + auto dcp = dynamic_pointer_cast(earliest_content->content); if (dcp && !_play_referenced && dcp->reference_audio()) { /* We are skipping some referenced DCP audio content, so we need to update _last_audio_time to `hide' the fact that no audio was emitted during the referenced DCP (though @@ -664,11 +705,13 @@ Player::pass () break; } case BLACK: - emit_video (black_player_video_frame(EYES_BOTH), _black.position()); + LOG_DEBUG_PLAYER ("Emit black for gap at %1", to_string(_black.position())); + emit_video (black_player_video_frame(Eyes::BOTH), _black.position()); _black.set_position (_black.position() + one_video_frame()); break; case SILENT: { + LOG_DEBUG_PLAYER ("Emit silence for gap at %1", to_string(_silent.position())); DCPTimePeriod period (_silent.period_at_position()); if (_last_audio_time) { /* Sometimes the thing that happened last finishes fractionally before @@ -700,23 +743,52 @@ Player::pass () /* Emit any audio that is ready */ /* Work out the time before which the audio is definitely all here. This is the earliest last_push_end of one - of our streams, or the position of the _silent. + of our streams, or the position of the _silent. First, though we choose only streams that are less than + ignore_streams_behind seconds behind the furthest ahead (we assume that if a stream has fallen that far + behind it has finished). This is so that we don't withhold audio indefinitely awaiting data from a stream + that will never come, causing bugs like #2101. */ - DCPTime pull_to = _playback_length; - for (map::const_iterator i = _stream_states.begin(); i != _stream_states.end(); ++i) { - if (!i->second.piece->done && i->second.last_push_end < pull_to) { - pull_to = i->second.last_push_end; + constexpr int ignore_streams_behind = 5; + + using state_pair = std::pair; + + /* Find the 'leading' stream (i.e. the one that pushed data most recently) */ + auto latest_last_push_end = std::max_element( + _stream_states.begin(), + _stream_states.end(), + [](state_pair const& a, state_pair const& b) { return a.second.last_push_end < b.second.last_push_end; } + ); + + if (latest_last_push_end != _stream_states.end()) { + LOG_DEBUG_PLAYER("Leading stream is in %1 at %2", latest_last_push_end->second.piece->content->path(0), to_string(latest_last_push_end->second.last_push_end)); + } + + /* Now make a list of those streams that are less than ignore_streams_behind behind the leader */ + std::map alive_stream_states; + for (auto const& i: _stream_states) { + if ((latest_last_push_end->second.last_push_end - i.second.last_push_end) < dcpomatic::DCPTime::from_seconds(ignore_streams_behind)) { + alive_stream_states.insert(i); + } else { + LOG_DEBUG_PLAYER("Ignoring stream %1 because it is too far behind", i.second.piece->content->path(0)); + } + } + + auto pull_to = _playback_length; + for (auto const& i: alive_stream_states) { + if (!i.second.piece->done && i.second.last_push_end < pull_to) { + pull_to = i.second.last_push_end; } } if (!_silent.done() && _silent.position() < pull_to) { pull_to = _silent.position(); } - list, DCPTime> > audio = _audio_merger.pull (pull_to); - for (list, DCPTime> >::iterator i = audio.begin(); i != audio.end(); ++i) { + LOG_DEBUG_PLAYER("Emitting audio up to %1", to_string(pull_to)); + auto audio = _audio_merger.pull (pull_to); + for (auto i = audio.begin(); i != audio.end(); ++i) { if (_last_audio_time && i->second < *_last_audio_time) { /* This new data comes before the last we emitted (or the last seek); discard it */ - pair, DCPTime> cut = discard_audio (i->first, i->second, *_last_audio_time); + auto cut = discard_audio (i->first, i->second, *_last_audio_time); if (!cut.first) { continue; } @@ -731,14 +803,15 @@ Player::pass () if (done) { _shuffler->flush (); - for (list, DCPTime> >::const_iterator i = _delay.begin(); i != _delay.end(); ++i) { - do_emit_video(i->first, i->second); + for (auto const& i: _delay) { + do_emit_video(i.first, i.second); } } return done; } + /** @return Open subtitles for the frame at the given time, converted to images */ optional Player::open_subtitles_for_frame (DCPTime time) const @@ -746,13 +819,13 @@ Player::open_subtitles_for_frame (DCPTime time) const list captions; int const vfr = _film->video_frame_rate(); - BOOST_FOREACH ( - PlayerText j, - _active_texts[TEXT_OPEN_SUBTITLE].get_burnt(DCPTimePeriod(time, time + DCPTime::from_frames(1, vfr)), _always_burn_open_subtitles) + for ( + auto j: + _active_texts[static_cast(TextType::OPEN_SUBTITLE)].get_burnt(DCPTimePeriod(time, time + DCPTime::from_frames(1, vfr)), _always_burn_open_subtitles) ) { /* Bitmap subtitles */ - BOOST_FOREACH (BitmapText i, j.bitmap) { + for (auto i: j.bitmap) { if (!i.image) { continue; } @@ -764,35 +837,44 @@ Player::open_subtitles_for_frame (DCPTime time) const PositionImage ( i.image, Position ( - lrint (_video_container_size.width * i.rectangle.x), - lrint (_video_container_size.height * i.rectangle.y) + lrint(_video_container_size.width * i.rectangle.x), + lrint(_video_container_size.height * i.rectangle.y) ) ) ); } /* String subtitles (rendered to an image) */ - if (!j.string.empty ()) { - list s = render_text (j.string, j.fonts, _video_container_size, time, vfr); + if (!j.string.empty()) { + auto s = render_text (j.string, j.fonts, _video_container_size, time, vfr); copy (s.begin(), s.end(), back_inserter (captions)); } } - if (captions.empty ()) { - return optional (); + if (captions.empty()) { + return {}; } - return merge (captions); + return merge (captions, _subtitle_alignment); } + void Player::video (weak_ptr wp, ContentVideo video) { - shared_ptr piece = wp.lock (); + if (_suspended) { + return; + } + + auto piece = wp.lock (); if (!piece) { return; } + if (!piece->content->video->use()) { + return; + } + FrameRateChange frc (_film, piece->content); if (frc.skip && (video.frame % 2) == 1) { return; @@ -800,6 +882,7 @@ Player::video (weak_ptr wp, ContentVideo video) /* Time of the first frame we will emit */ DCPTime const time = content_video_to_dcp (piece, video.frame); + LOG_DEBUG_PLAYER("Received video frame %1 at %2", video.frame, to_string(time)); /* Discard if it's before the content's period or the last accurate seek. We can't discard if it's after the content's period here as in that case we still need to fill any gap between @@ -809,6 +892,10 @@ Player::video (weak_ptr wp, ContentVideo video) return; } + if (piece->ignore_video && piece->ignore_video->contains(time)) { + return; + } + /* Fill gaps that we discover now that we have some video which needs to be emitted. This is where we need to fill to. */ @@ -819,30 +906,32 @@ Player::video (weak_ptr wp, ContentVideo video) /* Fill if we have more than half a frame to do */ if ((fill_to - fill_from) > one_video_frame() / 2) { - LastVideoMap::const_iterator last = _last_video.find (wp); + auto last = _last_video.find (wp); if (_film->three_d()) { - Eyes fill_to_eyes = video.eyes; - if (fill_to_eyes == EYES_BOTH) { - fill_to_eyes = EYES_LEFT; + auto fill_to_eyes = video.eyes; + if (fill_to_eyes == Eyes::BOTH) { + fill_to_eyes = Eyes::LEFT; } if (fill_to == piece->content->end(_film)) { /* Don't fill after the end of the content */ - fill_to_eyes = EYES_LEFT; + fill_to_eyes = Eyes::LEFT; } - DCPTime j = fill_from; - Eyes eyes = _last_video_eyes.get_value_or(EYES_LEFT); - if (eyes == EYES_BOTH) { - eyes = EYES_LEFT; + auto j = fill_from; + auto eyes = _last_video_eyes.get_value_or(Eyes::LEFT); + if (eyes == Eyes::BOTH) { + eyes = Eyes::LEFT; } while (j < fill_to || eyes != fill_to_eyes) { if (last != _last_video.end()) { - shared_ptr copy = last->second->shallow_copy(); + LOG_DEBUG_PLAYER("Fill using last video at %1 in 3D mode", to_string(j)); + auto copy = last->second->shallow_copy(); copy->set_eyes (eyes); emit_video (copy, j); } else { + LOG_DEBUG_PLAYER("Fill using black at %1 in 3D mode", to_string(j)); emit_video (black_player_video_frame(eyes), j); } - if (eyes == EYES_RIGHT) { + if (eyes == Eyes::RIGHT) { j += one_video_frame(); } eyes = increment_eyes (eyes); @@ -852,28 +941,33 @@ Player::video (weak_ptr wp, ContentVideo video) if (last != _last_video.end()) { emit_video (last->second, j); } else { - emit_video (black_player_video_frame(EYES_BOTH), j); + emit_video (black_player_video_frame(Eyes::BOTH), j); } } } } } - _last_video[wp].reset ( - new PlayerVideo ( - video.image, - piece->content->video->crop (), - piece->content->video->fade (_film, video.frame), - scale_for_display(piece->content->video->scaled_size(_film->frame_size()), _video_container_size, _film->frame_size()), + auto const content_video = piece->content->video; + + _last_video[wp] = std::make_shared( + video.image, + content_video->actual_crop(), + content_video->fade (_film, video.frame), + scale_for_display( + content_video->scaled_size(_film->frame_size()), _video_container_size, - video.eyes, - video.part, - piece->content->video->colour_conversion(), - piece->content->video->range(), - piece->content, - video.frame, - false - ) + _film->frame_size(), + content_video->pixel_quanta() + ), + _video_container_size, + video.eyes, + video.part, + content_video->colour_conversion(), + content_video->range(), + piece->content, + video.frame, + false ); DCPTime t = time; @@ -885,29 +979,36 @@ Player::video (weak_ptr wp, ContentVideo video) } } + void Player::audio (weak_ptr wp, AudioStreamPtr stream, ContentAudio content_audio) { + if (_suspended) { + return; + } + DCPOMATIC_ASSERT (content_audio.audio->frames() > 0); - shared_ptr piece = wp.lock (); + auto piece = wp.lock (); if (!piece) { return; } - shared_ptr content = piece->content->audio; + auto content = piece->content->audio; DCPOMATIC_ASSERT (content); int const rfr = content->resampled_frame_rate (_film); /* Compute time in the DCP */ - DCPTime time = resampled_audio_to_dcp (piece, content_audio.frame); + auto time = resampled_audio_to_dcp (piece, content_audio.frame); + LOG_DEBUG_PLAYER("Received audio frame %1 at %2", content_audio.frame, to_string(time)); + /* And the end of this block in the DCP */ - DCPTime end = time + DCPTime::from_frames(content_audio.audio->frames(), rfr); + auto end = time + DCPTime::from_frames(content_audio.audio->frames(), rfr); /* Remove anything that comes before the start or after the end of the content */ if (time < piece->content->position()) { - pair, DCPTime> cut = discard_audio (content_audio.audio, time, piece->content->position()); + auto cut = discard_audio (content_audio.audio, time, piece->content->position()); if (!cut.first) { /* This audio is entirely discarded */ return; @@ -922,7 +1023,7 @@ Player::audio (weak_ptr wp, AudioStreamPtr stream, ContentAudio content_a if (remaining_frames == 0) { return; } - content_audio.audio.reset (new AudioBuffers(content_audio.audio, remaining_frames, 0)); + content_audio.audio = make_shared(content_audio.audio, remaining_frames, 0); } DCPOMATIC_ASSERT (content_audio.audio->frames() > 0); @@ -930,8 +1031,8 @@ Player::audio (weak_ptr wp, AudioStreamPtr stream, ContentAudio content_a /* Gain */ if (content->gain() != 0) { - shared_ptr gain (new AudioBuffers (content_audio.audio)); - gain->apply_gain (content->gain ()); + auto gain = make_shared(content_audio.audio); + gain->apply_gain (content->gain()); content_audio.audio = gain; } @@ -952,11 +1053,16 @@ Player::audio (weak_ptr wp, AudioStreamPtr stream, ContentAudio content_a _stream_states[stream].last_push_end = time + DCPTime::from_frames (content_audio.audio->frames(), _film->audio_frame_rate()); } + void Player::bitmap_text_start (weak_ptr wp, weak_ptr wc, ContentBitmapText subtitle) { - shared_ptr piece = wp.lock (); - shared_ptr text = wc.lock (); + if (_suspended) { + return; + } + + auto piece = wp.lock (); + auto text = wc.lock (); if (!piece || !text) { return; } @@ -974,7 +1080,7 @@ Player::bitmap_text_start (weak_ptr wp, weak_ptr wc, C subtitle.sub.rectangle.height *= text->y_scale (); PlayerText ps; - shared_ptr image = subtitle.sub.image; + auto image = subtitle.sub.image; /* We will scale the subtitle up to fit _video_container_size */ int const width = subtitle.sub.rectangle.width * _video_container_size.width; @@ -984,17 +1090,22 @@ Player::bitmap_text_start (weak_ptr wp, weak_ptr wc, C } dcp::Size scaled_size (width, height); - ps.bitmap.push_back (BitmapText(image->scale(scaled_size, dcp::YUV_TO_RGB_REC601, image->pixel_format(), true, _fast), subtitle.sub.rectangle)); + ps.bitmap.push_back (BitmapText(image->scale(scaled_size, dcp::YUVToRGB::REC601, image->pixel_format(), Image::Alignment::PADDED, _fast), subtitle.sub.rectangle)); DCPTime from (content_time_to_dcp (piece, subtitle.from())); - _active_texts[text->type()].add_from (wc, ps, from); + _active_texts[static_cast(text->type())].add_from (wc, ps, from); } + void Player::plain_text_start (weak_ptr wp, weak_ptr wc, ContentStringText subtitle) { - shared_ptr piece = wp.lock (); - shared_ptr text = wc.lock (); + if (_suspended) { + return; + } + + auto piece = wp.lock (); + auto text = wc.lock (); if (!piece || !text) { return; } @@ -1006,7 +1117,7 @@ Player::plain_text_start (weak_ptr wp, weak_ptr wc, Co return; } - BOOST_FOREACH (dcp::SubtitleString s, subtitle.subs) { + for (auto s: subtitle.subs) { s.set_h_position (s.h_position() + text->x_offset ()); s.set_v_position (s.v_position() + text->y_offset ()); float const xs = text->x_scale(); @@ -1031,18 +1142,23 @@ Player::plain_text_start (weak_ptr wp, weak_ptr wc, Co ps.add_fonts (text->fonts ()); } - _active_texts[text->type()].add_from (wc, ps, from); + _active_texts[static_cast(text->type())].add_from (wc, ps, from); } + void Player::subtitle_stop (weak_ptr wp, weak_ptr wc, ContentTime to) { - shared_ptr text = wc.lock (); + if (_suspended) { + return; + } + + auto text = wc.lock (); if (!text) { return; } - if (!_active_texts[text->type()].have(wc)) { + if (!_active_texts[static_cast(text->type())].have(wc)) { return; } @@ -1057,18 +1173,20 @@ Player::subtitle_stop (weak_ptr wp, weak_ptr wc, Conte return; } - pair from = _active_texts[text->type()].add_to (wc, dcp_to); + auto from = _active_texts[static_cast(text->type())].add_to (wc, dcp_to); - bool const always = (text->type() == TEXT_OPEN_SUBTITLE && _always_burn_open_subtitles); + bool const always = (text->type() == TextType::OPEN_SUBTITLE && _always_burn_open_subtitles); if (text->use() && !always && !text->burn()) { Text (from.first, text->type(), text->dcp_track().get_value_or(DCPTextTrack()), DCPTimePeriod (from.second, dcp_to)); } } + void Player::seek (DCPTime time, bool accurate) { boost::mutex::scoped_lock lm (_mutex); + LOG_DEBUG_PLAYER("Seek to %1 (%2accurate)", to_string(time), accurate ? "" : "in"); if (_suspended) { /* We can't seek in this state */ @@ -1086,11 +1204,11 @@ Player::seek (DCPTime time, bool accurate) } _audio_merger.clear (); - for (int i = 0; i < TEXT_COUNT; ++i) { + for (int i = 0; i < static_cast(TextType::COUNT); ++i) { _active_texts[i].clear (); } - BOOST_FOREACH (shared_ptr i, _pieces) { + for (auto i: _pieces) { if (time < i->content->position()) { /* Before; seek to the start of the content. Even if this request is for an inaccurate seek we must seek this (following) content accurately, otherwise when we come to the end of the current @@ -1111,7 +1229,7 @@ Player::seek (DCPTime time, bool accurate) if (accurate) { _last_video_time = time; - _last_video_eyes = EYES_LEFT; + _last_video_eyes = Eyes::LEFT; _last_audio_time = time; } else { _last_video_time = optional(); @@ -1125,15 +1243,26 @@ Player::seek (DCPTime time, bool accurate) _last_video.clear (); } + void Player::emit_video (shared_ptr pv, DCPTime time) { + if (!_film->three_d()) { + if (pv->eyes() == Eyes::LEFT) { + /* Use left-eye images for both eyes... */ + pv->set_eyes (Eyes::BOTH); + } else if (pv->eyes() == Eyes::RIGHT) { + /* ...and discard the right */ + return; + } + } + /* We need a delay to give a little wiggle room to ensure that relevent subtitles arrive at the player before the video that requires them. */ _delay.push_back (make_pair (pv, time)); - if (pv->eyes() == EYES_BOTH || pv->eyes() == EYES_RIGHT) { + if (pv->eyes() == Eyes::BOTH || pv->eyes() == Eyes::RIGHT) { _last_video_time = time + one_video_frame(); } _last_video_eyes = increment_eyes (pv->eyes()); @@ -1142,21 +1271,22 @@ Player::emit_video (shared_ptr pv, DCPTime time) return; } - pair, DCPTime> to_do = _delay.front(); + auto to_do = _delay.front(); _delay.pop_front(); do_emit_video (to_do.first, to_do.second); } + void Player::do_emit_video (shared_ptr pv, DCPTime time) { - if (pv->eyes() == EYES_BOTH || pv->eyes() == EYES_RIGHT) { - for (int i = 0; i < TEXT_COUNT; ++i) { + if (pv->eyes() == Eyes::BOTH || pv->eyes() == Eyes::RIGHT) { + for (int i = 0; i < static_cast(TextType::COUNT); ++i) { _active_texts[i].clear_before (time); } } - optional subtitles = open_subtitles_for_frame (time); + auto subtitles = open_subtitles_for_frame (time); if (subtitles) { pv->set_text (subtitles.get ()); } @@ -1164,6 +1294,7 @@ Player::do_emit_video (shared_ptr pv, DCPTime time) Video (pv, time); } + void Player::emit_audio (shared_ptr data, DCPTime time) { @@ -1178,6 +1309,7 @@ Player::emit_audio (shared_ptr data, DCPTime time) _last_audio_time = time + DCPTime::from_frames (data->frames(), _film->audio_frame_rate()); } + void Player::fill_audio (DCPTimePeriod period) { @@ -1192,7 +1324,7 @@ Player::fill_audio (DCPTimePeriod period) DCPTime block = min (DCPTime::from_seconds (0.5), period.to - t); Frame const samples = block.frames_round(_film->audio_frame_rate()); if (samples) { - shared_ptr silence (new AudioBuffers (_film->audio_channels(), samples)); + auto silence = make_shared(_film->audio_channels(), samples); silence->make_silent (); emit_audio (silence, t); } @@ -1200,36 +1332,39 @@ Player::fill_audio (DCPTimePeriod period) } } + DCPTime Player::one_video_frame () const { return DCPTime::from_frames (1, _film->video_frame_rate ()); } + pair, DCPTime> Player::discard_audio (shared_ptr audio, DCPTime time, DCPTime discard_to) const { - DCPTime const discard_time = discard_to - time; - Frame const discard_frames = discard_time.frames_round(_film->audio_frame_rate()); - Frame remaining_frames = audio->frames() - discard_frames; + auto const discard_time = discard_to - time; + auto const discard_frames = discard_time.frames_round(_film->audio_frame_rate()); + auto remaining_frames = audio->frames() - discard_frames; if (remaining_frames <= 0) { return make_pair(shared_ptr(), DCPTime()); } - shared_ptr cut (new AudioBuffers(audio, remaining_frames, discard_frames)); + auto cut = make_shared(audio, remaining_frames, discard_frames); return make_pair(cut, time + discard_time); } + void Player::set_dcp_decode_reduction (optional reduction) { - Change (CHANGE_TYPE_PENDING, PlayerProperty::DCP_DECODE_REDUCTION, false); + Change (ChangeType::PENDING, PlayerProperty::DCP_DECODE_REDUCTION, false); { boost::mutex::scoped_lock lm (_mutex); if (reduction == _dcp_decode_reduction) { lm.unlock (); - Change (CHANGE_TYPE_CANCELLED, PlayerProperty::DCP_DECODE_REDUCTION, false); + Change (ChangeType::CANCELLED, PlayerProperty::DCP_DECODE_REDUCTION, false); return; } @@ -1237,22 +1372,23 @@ Player::set_dcp_decode_reduction (optional reduction) setup_pieces_unlocked (); } - Change (CHANGE_TYPE_DONE, PlayerProperty::DCP_DECODE_REDUCTION, false); + Change (ChangeType::DONE, PlayerProperty::DCP_DECODE_REDUCTION, false); } + optional Player::content_time_to_dcp (shared_ptr content, ContentTime t) { boost::mutex::scoped_lock lm (_mutex); - BOOST_FOREACH (shared_ptr i, _pieces) { + for (auto i: _pieces) { if (i->content == content) { return content_time_to_dcp (i, t); } } /* We couldn't find this content; perhaps things are being changed over */ - return optional(); + return {}; } @@ -1262,3 +1398,14 @@ Player::playlist () const return _playlist ? _playlist : _film->playlist(); } + +void +Player::atmos (weak_ptr, ContentAtmos data) +{ + if (_suspended) { + return; + } + + Atmos (data.data, DCPTime::from_frames(data.frame, _film->video_frame_rate()), data.metadata); +} +