X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Flib%2Fplayer.cc;h=07447d531db7036ed61ef1ef414d18e9cc1f7685;hb=86102d30bf0aad89115bbeb3d8aaa2a27a0aa432;hp=f79e379d245d9c8aa3c904c0a2ece29b71993352;hpb=92784c9c28c48859578cd6e75aa01d5657d0c341;p=dcpomatic.git diff --git a/src/lib/player.cc b/src/lib/player.cc index f79e379d2..07447d531 100644 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2013-2018 Carl Hetherington + Copyright (C) 2013-2020 Carl Hetherington This file is part of DCP-o-matic. @@ -48,6 +48,7 @@ #include "image_decoder.h" #include "compose.hpp" #include "shuffler.h" +#include "timer.h" #include #include #include @@ -82,19 +83,22 @@ int const PlayerProperty::PLAYLIST = 701; int const PlayerProperty::FILM_CONTAINER = 702; 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, shared_ptr playlist) +Player::Player (shared_ptr film, shared_ptr playlist, DCPTime playback_length) : _film (film) , _playlist (playlist) - , _suspended (false) + , _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) + , _playback_length (playback_length) { _film_changed_connection = _film->Change.connect (bind (&Player::film_change, this, _1, _2)); /* The butler must hear about this first, so since we are proxying this through to the butler we must @@ -122,21 +126,32 @@ Player::setup_pieces () setup_pieces_unlocked (); } + +void +Player::set_playback_length (DCPTime len) +{ + Change (CHANGE_TYPE_PENDING, PlayerProperty::PLAYBACK_LENGTH, false); + _playback_length = len; + Change (CHANGE_TYPE_DONE, PlayerProperty::PLAYBACK_LENGTH, false); + setup_pieces (); +} + bool -have_video (shared_ptr piece) +have_video (shared_ptr content) { - return piece->decoder && piece->decoder->video; + return static_cast(content->video); } bool -have_audio (shared_ptr piece) +have_audio (shared_ptr content) { - return piece->decoder && piece->decoder->audio; + return static_cast(content->audio); } void Player::setup_pieces_unlocked () { + list > old_pieces = _pieces; _pieces.clear (); delete _shuffler; @@ -154,7 +169,15 @@ Player::setup_pieces_unlocked () continue; } - shared_ptr decoder = decoder_factory (_film, i, _fast); + shared_ptr old_decoder; + BOOST_FOREACH (shared_ptr 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); if (!decoder) { @@ -226,8 +249,8 @@ Player::setup_pieces_unlocked () } } - _black = Empty (_film, _pieces, bind(&have_video, _1)); - _silent = Empty (_film, _pieces, bind(&have_audio, _1)); + _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; @@ -238,19 +261,17 @@ void Player::playlist_content_change (ChangeType type, int property, bool frequent) { if (type == CHANGE_TYPE_PENDING) { - boost::mutex::scoped_lock lm (_mutex); /* 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 = true; + ++_suspended; } else if (type == CHANGE_TYPE_DONE) { /* A change in our content has gone through. Re-build our pieces. */ setup_pieces (); - _suspended = false; + --_suspended; } else if (type == CHANGE_TYPE_CANCELLED) { - boost::mutex::scoped_lock lm (_mutex); - _suspended = false; + --_suspended; } Change (type, property, frequent); @@ -334,7 +355,8 @@ Player::black_player_video_frame (Eyes eyes) const PresetColourConversion::all().front().conversion, VIDEO_RANGE_FULL, boost::weak_ptr(), - boost::optional() + boost::optional(), + false ) ); } @@ -493,7 +515,7 @@ Player::get_reel_assets () scoped_ptr decoder; try { - decoder.reset (new DCPDecoder (_film, j, false)); + decoder.reset (new DCPDecoder (_film, j, false, false, shared_ptr())); } catch (...) { return a; } @@ -559,8 +581,8 @@ Player::pass () return false; } - if (_playlist->length(_film) == DCPTime()) { - /* Special case of an empty Film; just give one black frame */ + if (_playback_length == DCPTime()) { + /* Special; just give one black frame */ emit_video (black_player_video_frame(EYES_BOTH), DCPTime()); return true; } @@ -603,12 +625,12 @@ Player::pass () which = CONTENT; } - if (!_black.done() && (!earliest_time || _black.position() < *earliest_time)) { + if (!_black.done() && !_ignore_video && (!earliest_time || _black.position() < *earliest_time)) { earliest_time = _black.position (); which = BLACK; } - if (!_silent.done() && (!earliest_time || _silent.position() < *earliest_time)) { + if (!_silent.done() && !_ignore_audio && (!earliest_time || _silent.position() < *earliest_time)) { earliest_time = _silent.position (); which = SILENT; } @@ -637,8 +659,16 @@ Player::pass () if (_last_audio_time) { /* Sometimes the thing that happened last finishes fractionally before or after this silence. Bodge the start time of the silence to fix it. + I think this is nothing to worry about since we will just add or + remove a little silence at the end of some content. */ - DCPOMATIC_ASSERT (labs(period.from.get() - _last_audio_time->get()) < 2); + int64_t const error = labs(period.from.get() - _last_audio_time->get()); + /* Let's not worry about less than a frame at 24fps */ + int64_t const too_much_error = DCPTime::from_frames(1, 24).get(); + if (error >= too_much_error) { + _film->log()->log(String::compose("Silence starting before or after last audio by %1", error), LogEntry::TYPE_ERROR); + } + DCPOMATIC_ASSERT (error < too_much_error); period.from = *_last_audio_time; } if (period.duration() > one_video_frame()) { @@ -658,7 +688,7 @@ Player::pass () /* 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. */ - DCPTime pull_to = _film->length (); + 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; @@ -829,7 +859,8 @@ Player::video (weak_ptr wp, ContentVideo video) piece->content->video->colour_conversion(), piece->content->video->range(), piece->content, - video.frame + video.frame, + false ) ); @@ -855,10 +886,12 @@ Player::audio (weak_ptr wp, AudioStreamPtr stream, ContentAudio content_a shared_ptr 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); /* And the end of this block in the DCP */ - DCPTime end = time + DCPTime::from_frames(content_audio.audio->frames(), content->resampled_frame_rate(_film)); + DCPTime 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()) { @@ -873,13 +906,11 @@ Player::audio (weak_ptr wp, AudioStreamPtr stream, ContentAudio content_a /* Discard it all */ return; } else if (end > piece->content->end(_film)) { - Frame const remaining_frames = DCPTime(piece->content->end(_film) - time).frames_round(_film->audio_frame_rate()); + Frame const remaining_frames = DCPTime(piece->content->end(_film) - time).frames_round(rfr); if (remaining_frames == 0) { return; } - shared_ptr cut (new AudioBuffers (content_audio.audio->channels(), remaining_frames)); - cut->copy_from (content_audio.audio.get(), remaining_frames, 0, 0); - content_audio.audio = cut; + content_audio.audio.reset (new AudioBuffers(content_audio.audio, remaining_frames, 0)); } DCPOMATIC_ASSERT (content_audio.audio->frames() > 0); @@ -932,8 +963,15 @@ Player::bitmap_text_start (weak_ptr wp, weak_ptr wc, C PlayerText ps; shared_ptr image = subtitle.sub.image; + /* We will scale the subtitle up to fit _video_container_size */ - dcp::Size scaled_size (subtitle.sub.rectangle.width * _video_container_size.width, subtitle.sub.rectangle.height * _video_container_size.height); + int const width = subtitle.sub.rectangle.width * _video_container_size.width; + int const height = subtitle.sub.rectangle.height * _video_container_size.height; + if (width == 0 || height == 0) { + return; + } + + 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)); DCPTime from (content_time_to_dcp (piece, subtitle.from())); @@ -1042,8 +1080,12 @@ Player::seek (DCPTime time, bool accurate) BOOST_FOREACH (shared_ptr i, _pieces) { if (time < i->content->position()) { - /* Before; seek to the start of the content */ - i->decoder->seek (dcp_to_content_time (i, i->content->position()), accurate); + /* 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 + content we may not start right at the beginning of the next, causing a gap (if the next content has + been trimmed to a point between keyframes, or something). + */ + i->decoder->seek (dcp_to_content_time (i, i->content->position()), true); i->done = false; } else if (i->content->position() <= time && time < i->content->end(_film)) { /* During; seek to position */ @@ -1161,8 +1203,7 @@ Player::discard_audio (shared_ptr audio, DCPTime time, DCPTi if (remaining_frames <= 0) { return make_pair(shared_ptr(), DCPTime()); } - shared_ptr cut (new AudioBuffers (audio->channels(), remaining_frames)); - cut->copy_from (audio.get(), remaining_frames, discard_frames, 0); + shared_ptr cut (new AudioBuffers(audio, remaining_frames, discard_frames)); return make_pair(cut, time + discard_time); }