X-Git-Url: https://main.carlh.net/gitweb/?p=dcpomatic.git;a=blobdiff_plain;f=src%2Flib%2Fplayer.cc;h=4f81199a5a49847394ff58834786624bbec2d4a0;hp=b939995ef7bbaa7c5fdc6953be645da0fe803295;hb=6ef1fc5f40567650ca9ef2b7644e4fdd97640ae6;hpb=e4dc7972ec35d891287598f33a1b4e1461ddc3fc diff --git a/src/lib/player.cc b/src/lib/player.cc index b939995ef..4f81199a5 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 @@ -60,8 +61,6 @@ #include "i18n.h" -#define LOG_GENERAL(...) _film->log()->log (String::compose (__VA_ARGS__), LogEntry::TYPE_GENERAL); - using std::list; using std::cout; using std::min; @@ -77,32 +76,57 @@ using boost::weak_ptr; using boost::dynamic_pointer_cast; using boost::optional; using boost::scoped_ptr; +using namespace dcpomatic; int const PlayerProperty::VIDEO_CONTAINER_SIZE = 700; 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) + : _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) +{ + construct (); +} -Player::Player (shared_ptr film, shared_ptr playlist) +Player::Player (shared_ptr film, shared_ptr playlist_) : _film (film) - , _playlist (playlist) - , _suspended (false) + , _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 () { _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 be first. */ - _playlist_change_connection = _playlist->Change.connect (bind (&Player::playlist_change, this, _1), boost::signals2::at_front); - _playlist_content_change_connection = _playlist->ContentChange.connect (bind(&Player::playlist_content_change, this, _1, _3, _4)); + _playlist_change_connection = playlist()->Change.connect (bind (&Player::playlist_change, this, _1), boost::signals2::at_front); + _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); @@ -123,16 +147,32 @@ Player::setup_pieces () setup_pieces_unlocked (); } + +bool +have_video (shared_ptr content) +{ + 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; _pieces.clear (); delete _shuffler; _shuffler = new Shuffler(); _shuffler->Video.connect(bind(&Player::video, this, _1, _2)); - BOOST_FOREACH (shared_ptr i, _playlist->content ()) { + BOOST_FOREACH (shared_ptr i, playlist()->content()) { if (!i->paths_valid ()) { continue; @@ -143,8 +183,16 @@ Player::setup_pieces_unlocked () continue; } - shared_ptr decoder = decoder_factory (i, _film->log(), _fast); - FrameRateChange frc (i->active_video_frame_rate(), _film->video_frame_rate()); + 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) { /* Not something that we can decode; e.g. Atmos content */ @@ -215,30 +263,29 @@ Player::setup_pieces_unlocked () } } - _black = Empty (_film->content(), _film->length(), bind(&Content::video, _1)); - _silent = Empty (_film->content(), _film->length(), bind(&Content::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; _last_audio_time = DCPTime (); - _suspended = false; } 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; } else if (type == CHANGE_TYPE_CANCELLED) { - _suspended = false; + --_suspended; } Change (type, property, frequent); @@ -307,39 +354,6 @@ Player::film_change (ChangeType type, Film::Property p) } } -list -Player::transform_bitmap_texts (list subs) const -{ - list all; - - for (list::const_iterator i = subs.begin(); i != subs.end(); ++i) { - if (!i->image) { - continue; - } - - /* We will scale the subtitle up to fit _video_container_size */ - dcp::Size scaled_size (i->rectangle.width * _video_container_size.width, i->rectangle.height * _video_container_size.height); - - all.push_back ( - PositionImage ( - i->image->scale ( - scaled_size, - dcp::YUV_TO_RGB_REC601, - i->image->pixel_format (), - true, - _fast - ), - Position ( - lrint (_video_container_size.width * i->rectangle.x), - lrint (_video_container_size.height * i->rectangle.y) - ) - ) - ); - } - - return all; -} - shared_ptr Player::black_player_video_frame (Eyes eyes) const { @@ -353,8 +367,10 @@ Player::black_player_video_frame (Eyes eyes) const eyes, PART_WHOLE, PresetColourConversion::all().front().conversion, + VIDEO_RANGE_FULL, boost::weak_ptr(), - boost::optional() + boost::optional(), + false ) ); } @@ -363,7 +379,7 @@ Frame Player::dcp_to_content_video (shared_ptr piece, DCPTime t) const { DCPTime s = t - piece->content->position (); - s = min (piece->content->length_after_trim(), s); + s = min (piece->content->length_after_trim(_film), s); s = max (DCPTime(), s + DCPTime (piece->content->trim_start(), piece->frc)); /* It might seem more logical here to convert s to a ContentTime (using the FrameRateChange) @@ -388,7 +404,7 @@ Frame Player::dcp_to_resampled_audio (shared_ptr piece, DCPTime t) const { DCPTime s = t - piece->content->position (); - s = min (piece->content->length_after_trim(), s); + 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 ()); } @@ -406,7 +422,7 @@ ContentTime Player::dcp_to_content_time (shared_ptr piece, DCPTime t) const { DCPTime s = t - piece->content->position (); - s = min (piece->content->length_after_trim(), s); + s = min (piece->content->length_after_trim(_film), s); return max (ContentTime (), ContentTime (s, piece->frc) + piece->content->trim_start()); } @@ -485,6 +501,19 @@ 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) +{ + DCPOMATIC_ASSERT (r); + r->set_entry_point (r->entry_point().get_value_or(0) + reel_trim_start); + r->set_duration (r->actual_duration() - reel_trim_start - reel_trim_end); + if (r->actual_duration() > 0) { + a.push_back ( + ReferencedReelAsset(r, DCPTimePeriod(from, from + DCPTime::from_frames(r->actual_duration(), ffr))) + ); + } +} + list Player::get_reel_assets () { @@ -492,7 +521,7 @@ Player::get_reel_assets () list a; - BOOST_FOREACH (shared_ptr i, _playlist->content ()) { + BOOST_FOREACH (shared_ptr i, playlist()->content()) { shared_ptr j = dynamic_pointer_cast (i); if (!j) { continue; @@ -500,64 +529,56 @@ Player::get_reel_assets () scoped_ptr decoder; try { - decoder.reset (new DCPDecoder (j, _film->log(), false)); + decoder.reset (new DCPDecoder (_film, j, false, false, shared_ptr())); } catch (...) { return a; } - int64_t offset = 0; + 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); + 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 */ + int64_t offset_from_end = 0; BOOST_FOREACH (shared_ptr k, decoder->reels()) { + /* Assume that main picture duration is the length of the reel */ + offset_from_end += k->main_picture()->actual_duration(); + } - 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); - int const ffr = _film->video_frame_rate (); + BOOST_FOREACH (shared_ptr k, decoder->reels()) { + + /* Assume that main picture duration is the length of the reel */ + int64_t const reel_duration = k->main_picture()->actual_duration(); - DCPTime const from = i->position() + DCPTime::from_frames (offset, _film->video_frame_rate()); + /* 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 ()) { - shared_ptr ra = k->main_picture (); - DCPOMATIC_ASSERT (ra); - ra->set_entry_point (ra->entry_point() + trim_start); - ra->set_duration (ra->duration() - trim_start - trim_end); - a.push_back ( - ReferencedReelAsset (ra, DCPTimePeriod (from, from + DCPTime::from_frames (ra->duration(), ffr))) - ); + maybe_add_asset (a, k->main_picture(), reel_trim_start, reel_trim_end, from, ffr); } if (j->reference_audio ()) { - shared_ptr ra = k->main_sound (); - DCPOMATIC_ASSERT (ra); - ra->set_entry_point (ra->entry_point() + trim_start); - ra->set_duration (ra->duration() - trim_start - trim_end); - a.push_back ( - ReferencedReelAsset (ra, DCPTimePeriod (from, from + DCPTime::from_frames (ra->duration(), ffr))) - ); + maybe_add_asset (a, k->main_sound(), reel_trim_start, reel_trim_end, from, ffr); } if (j->reference_text (TEXT_OPEN_SUBTITLE)) { - shared_ptr ra = k->main_subtitle (); - DCPOMATIC_ASSERT (ra); - ra->set_entry_point (ra->entry_point() + trim_start); - ra->set_duration (ra->duration() - trim_start - trim_end); - a.push_back ( - ReferencedReelAsset (ra, DCPTimePeriod (from, from + DCPTime::from_frames (ra->duration(), ffr))) - ); + maybe_add_asset (a, k->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()) { - DCPOMATIC_ASSERT (l); - l->set_entry_point (l->entry_point() + trim_start); - l->set_duration (l->duration() - trim_start - trim_end); - a.push_back ( - ReferencedReelAsset (l, DCPTimePeriod (from, from + DCPTime::from_frames (l->duration(), ffr))) - ); + maybe_add_asset (a, l, reel_trim_start, reel_trim_end, from, ffr); } } - /* Assume that main picture duration is the length of the reel */ - offset += k->main_picture()->duration (); + offset_from_start += reel_duration; + offset_from_end -= reel_duration; } } @@ -574,8 +595,8 @@ Player::pass () return false; } - if (_playlist->length() == 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; } @@ -591,7 +612,7 @@ Player::pass () } DCPTime const t = content_time_to_dcp (i, max(i->decoder->position(), i->content->trim_start())); - if (t > i->content->end()) { + if (t > i->content->end(_film)) { i->done = true; } else { @@ -618,20 +639,30 @@ 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; } switch (which) { case CONTENT: + { earliest_content->done = earliest_content->decoder->pass (); + shared_ptr 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 + we need to behave as though it was). + */ + _last_audio_time = dcp->end (_film); + } break; + } case BLACK: emit_video (black_player_video_frame(EYES_BOTH), _black.position()); _black.set_position (_black.position() + one_video_frame()); @@ -641,11 +672,18 @@ Player::pass () DCPTimePeriod period (_silent.period_at_position()); if (_last_audio_time) { /* Sometimes the thing that happened last finishes fractionally before - this silence. Bodge the start time of the silence to fix it. I'm - not sure if this is the right solution --- maybe the last thing should - be padded `forward' rather than this thing padding `back'. + 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. */ - period.from = min(period.from, *_last_audio_time); + 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()) { period.to = period.from + one_video_frame(); @@ -664,7 +702,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; @@ -714,8 +752,24 @@ Player::open_subtitles_for_frame (DCPTime time) const ) { /* Bitmap subtitles */ - list c = transform_bitmap_texts (j.bitmap); - copy (c.begin(), c.end(), back_inserter (captions)); + BOOST_FOREACH (BitmapText i, j.bitmap) { + if (!i.image) { + continue; + } + + /* i.image will already have been scaled to fit _video_container_size */ + dcp::Size scaled_size (i.rectangle.width * _video_container_size.width, i.rectangle.height * _video_container_size.height); + + captions.push_back ( + PositionImage ( + i.image, + Position ( + 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 ()) { @@ -739,7 +793,7 @@ Player::video (weak_ptr wp, ContentVideo video) return; } - FrameRateChange frc(piece->content->active_video_frame_rate(), _film->video_frame_rate()); + FrameRateChange frc (_film, piece->content); if (frc.skip && (video.frame % 2) == 1) { return; } @@ -758,44 +812,48 @@ Player::video (weak_ptr wp, ContentVideo video) /* Fill gaps that we discover now that we have some video which needs to be emitted. This is where we need to fill to. */ - DCPTime fill_to = min (time, piece->content->end()); + DCPTime fill_to = min (time, piece->content->end(_film)); if (_last_video_time) { DCPTime fill_from = max (*_last_video_time, piece->content->position()); - LastVideoMap::const_iterator 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; - } - if (fill_to == piece->content->end()) { - /* Don't fill after the end of the content */ - 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; - } - while (j < fill_to || eyes != fill_to_eyes) { - if (last != _last_video.end()) { - shared_ptr copy = last->second->shallow_copy(); - copy->set_eyes (eyes); - emit_video (copy, j); - } else { - emit_video (black_player_video_frame(eyes), j); + + /* 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); + if (_film->three_d()) { + Eyes fill_to_eyes = video.eyes; + if (fill_to_eyes == EYES_BOTH) { + fill_to_eyes = EYES_LEFT; } - if (eyes == EYES_RIGHT) { - j += one_video_frame(); + if (fill_to == piece->content->end(_film)) { + /* Don't fill after the end of the content */ + fill_to_eyes = EYES_LEFT; } - eyes = increment_eyes (eyes); - } - } else { - for (DCPTime j = fill_from; j < fill_to; j += one_video_frame()) { - if (last != _last_video.end()) { - emit_video (last->second, j); - } else { - emit_video (black_player_video_frame(EYES_BOTH), j); + DCPTime j = fill_from; + Eyes 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(); + copy->set_eyes (eyes); + emit_video (copy, j); + } else { + emit_video (black_player_video_frame(eyes), j); + } + if (eyes == EYES_RIGHT) { + j += one_video_frame(); + } + eyes = increment_eyes (eyes); + } + } else { + for (DCPTime j = fill_from; j < fill_to; j += one_video_frame()) { + if (last != _last_video.end()) { + emit_video (last->second, j); + } else { + emit_video (black_player_video_frame(EYES_BOTH), j); + } } } } @@ -805,22 +863,22 @@ Player::video (weak_ptr wp, ContentVideo video) new PlayerVideo ( video.image, piece->content->video->crop (), - piece->content->video->fade (video.frame), - piece->content->video->scale().size ( - piece->content->video, _video_container_size, _film->frame_size () - ), + piece->content->video->fade (_film, video.frame), + scale_for_display(piece->content->video->scaled_size(_film->frame_size()), _video_container_size, _film->frame_size()), _video_container_size, video.eyes, video.part, piece->content->video->colour_conversion(), + piece->content->video->range(), piece->content, - video.frame + video.frame, + false ) ); DCPTime t = time; for (int i = 0; i < frc.repeat; ++i) { - if (t < piece->content->end()) { + if (t < piece->content->end(_film)) { emit_video (_last_video[wp], t); } t += one_video_frame (); @@ -840,10 +898,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()); + 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()) { @@ -854,17 +914,15 @@ Player::audio (weak_ptr wp, AudioStreamPtr stream, ContentAudio content_a } content_audio.audio = cut.first; time = cut.second; - } else if (time > piece->content->end()) { + } else if (time > piece->content->end(_film)) { /* Discard it all */ return; - } else if (end > piece->content->end()) { - Frame const remaining_frames = DCPTime(piece->content->end() - time).frames_round(_film->audio_frame_rate()); + } else if (end > piece->content->end(_film)) { + 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); @@ -916,7 +974,17 @@ Player::bitmap_text_start (weak_ptr wp, weak_ptr wc, C subtitle.sub.rectangle.height *= text->y_scale (); PlayerText ps; - ps.bitmap.push_back (subtitle.sub); + shared_ptr 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; + 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())); _active_texts[text->type()].add_from (wc, ps, from); @@ -934,7 +1002,7 @@ Player::plain_text_start (weak_ptr wp, weak_ptr wc, Co PlayerText ps; DCPTime const from (content_time_to_dcp (piece, subtitle.from())); - if (from > piece->content->end()) { + if (from > piece->content->end(_film)) { return; } @@ -985,7 +1053,7 @@ Player::subtitle_stop (weak_ptr wp, weak_ptr wc, Conte DCPTime const dcp_to = content_time_to_dcp (piece, to); - if (dcp_to > piece->content->end()) { + if (dcp_to > piece->content->end(_film)) { return; } @@ -1024,10 +1092,14 @@ 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()) { + } else if (i->content->position() <= time && time < i->content->end(_film)) { /* During; seek to position */ i->decoder->seek (dcp_to_content_time (i, time), accurate); i->done = false; @@ -1096,13 +1168,13 @@ void Player::emit_audio (shared_ptr data, DCPTime time) { /* Log if the assert below is about to fail */ - if (_last_audio_time && time != *_last_audio_time) { + if (_last_audio_time && labs(time.get() - _last_audio_time->get()) > 1) { _film->log()->log(String::compose("Out-of-sequence emit %1 vs %2", to_string(time), to_string(*_last_audio_time)), LogEntry::TYPE_WARNING); } - /* This audio must follow on from the previous */ - DCPOMATIC_ASSERT (!_last_audio_time || time == *_last_audio_time); - Audio (data, time); + /* This audio must follow on from the previous, allowing for half a sample (at 48kHz) leeway */ + DCPOMATIC_ASSERT (!_last_audio_time || labs(time.get() - _last_audio_time->get()) < 2); + Audio (data, time, _film->audio_frame_rate()); _last_audio_time = time + DCPTime::from_frames (data->frames(), _film->audio_frame_rate()); } @@ -1143,8 +1215,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); } @@ -1183,3 +1254,11 @@ Player::content_time_to_dcp (shared_ptr content, ContentTime t) /* We couldn't find this content; perhaps things are being changed over */ return optional(); } + + +shared_ptr +Player::playlist () const +{ + return _playlist ? _playlist : _film->playlist(); +} +