- set_video_container_size (fit_ratio_within (_film->container()->ratio (), _film->full_frame ()));
-}
-
-void
-Player::disable_video ()
-{
- _video = false;
-}
-
-void
-Player::disable_audio ()
-{
- _audio = false;
-}
-
-bool
-Player::pass ()
-{
- if (!_have_valid_pieces) {
- setup_pieces ();
- }
-
- /* Interrogate all our pieces to find the one with the earliest decoded data */
-
- shared_ptr<Piece> earliest_piece;
- shared_ptr<Decoded> earliest_decoded;
- DCPTime earliest_time = TIME_MAX;
- DCPTime earliest_audio = TIME_MAX;
-
- for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
-
- DCPTime const offset = (*i)->content->position() - (*i)->content->trim_start();
-
- bool done = false;
- shared_ptr<Decoded> dec;
- while (!done) {
- dec = (*i)->decoder->peek ();
- if (!dec) {
- /* Decoder has nothing else to give us */
- break;
- }
-
- dec->set_dcp_times (_film->video_frame_rate(), _film->audio_frame_rate(), (*i)->frc, offset);
- DCPTime const t = dec->dcp_time - offset;
- if (t >= ((*i)->content->full_length() - (*i)->content->trim_end ())) {
- /* In the end-trimmed part; decoder has nothing else to give us */
- dec.reset ();
- done = true;
- } else if (t >= (*i)->content->trim_start ()) {
- /* Within the un-trimmed part; everything's ok */
- done = true;
- } else {
- /* Within the start-trimmed part; get something else */
- (*i)->decoder->consume ();
- }
- }
-
- if (!dec) {
- continue;
- }
-
- if (dec->dcp_time < earliest_time) {
- earliest_piece = *i;
- earliest_decoded = dec;
- earliest_time = dec->dcp_time;
- }
-
- if (dynamic_pointer_cast<DecodedAudio> (dec) && dec->dcp_time < earliest_audio) {
- earliest_audio = dec->dcp_time;
- }
- }
-
- if (!earliest_piece) {
- flush ();
- return true;
- }
-
- if (earliest_audio != TIME_MAX) {
- TimedAudioBuffers<DCPTime> tb = _audio_merger.pull (max (int64_t (0), earliest_audio));
- Audio (tb.audio, tb.time);
- /* This assumes that the audio_frames_to_time conversion is exact
- so that there are no accumulated errors caused by rounding.
- */
- _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
- }
-
- /* Emit the earliest thing */
-
- shared_ptr<DecodedVideo> dv = dynamic_pointer_cast<DecodedVideo> (earliest_decoded);
- shared_ptr<DecodedAudio> da = dynamic_pointer_cast<DecodedAudio> (earliest_decoded);
- shared_ptr<DecodedImageSubtitle> dis = dynamic_pointer_cast<DecodedImageSubtitle> (earliest_decoded);
- shared_ptr<DecodedTextSubtitle> dts = dynamic_pointer_cast<DecodedTextSubtitle> (earliest_decoded);
-
-#if 0
- if (dv) {
- cout << "Video @ " << dv->dcp_time << " " << (double(dv->dcp_time) / TIME_HZ) << ".\n";
- } else if (da) {
- cout << "Audio.\n";
- } else if (dis) {
- cout << "Image sub.\n";
- } else if (dts) {
- cout << "Text sub.\n";
- }
-#endif
-
- /* Will be set to false if we shouldn't consume the peeked DecodedThing */
- bool consume = true;
-
- if (dv && _video) {
-
- if (_just_did_inaccurate_seek) {
-
- /* Just emit; no subtlety */
- emit_video (earliest_piece, dv);
- step_video_position (dv);
-
- } else if (dv->dcp_time > _video_position) {
-
- /* Too far ahead */
-
- list<shared_ptr<Piece> >::iterator i = _pieces.begin();
- while (i != _pieces.end() && ((*i)->content->position() >= _video_position || _video_position >= (*i)->content->end())) {
- ++i;
- }
-
- if (i == _pieces.end() || !_last_incoming_video.video || !_have_valid_pieces) {
- /* We're outside all video content */
- emit_black ();
- _statistics.video.black++;
- } else {
- /* We're inside some video; repeat the frame */
- _last_incoming_video.video->dcp_time = _video_position;
- emit_video (_last_incoming_video.weak_piece, _last_incoming_video.video);
- step_video_position (_last_incoming_video.video);
- _statistics.video.repeat++;
- }
-
- consume = false;
-
- } else if (dv->dcp_time == _video_position) {
- /* We're ok */
- emit_video (earliest_piece, dv);
- step_video_position (dv);
- _statistics.video.good++;
- } else {
- /* Too far behind: skip */
- _statistics.video.skip++;
- }
-
- _just_did_inaccurate_seek = false;
-
- } else if (da && _audio) {
-
- if (da->dcp_time > _audio_position) {
- /* Too far ahead */
- emit_silence (da->dcp_time - _audio_position);
- consume = false;
- _statistics.audio.silence += (da->dcp_time - _audio_position);
- } else if (da->dcp_time == _audio_position) {
- /* We're ok */
- emit_audio (earliest_piece, da);
- _statistics.audio.good += da->data->frames();
- } else {
- /* Too far behind: skip */
- _statistics.audio.skip += da->data->frames();
- }
-
- } else if (dis && _video) {
- _image_subtitle.piece = earliest_piece;
- _image_subtitle.subtitle = dis;
- update_subtitle_from_image ();
- } else if (dts && _video) {
- _text_subtitle.piece = earliest_piece;
- _text_subtitle.subtitle = dts;
- update_subtitle_from_text ();
- }
-
- if (consume) {
- earliest_piece->decoder->consume ();
- }
-
- return false;
-}
-
-void
-Player::emit_video (weak_ptr<Piece> weak_piece, shared_ptr<DecodedVideo> video)
-{
- /* Keep a note of what came in so that we can repeat it if required */
- _last_incoming_video.weak_piece = weak_piece;
- _last_incoming_video.video = video;
-
- shared_ptr<Piece> piece = weak_piece.lock ();
- if (!piece) {
- return;
- }
-
- shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
- assert (content);
-
- FrameRateChange frc (content->video_frame_rate(), _film->video_frame_rate());
-
- float const ratio = content->ratio() ? content->ratio()->ratio() : content->video_size_after_crop().ratio();
- dcp::Size image_size = fit_ratio_within (ratio, _video_container_size);
- if (_approximate_size) {
- image_size.width &= ~3;
- image_size.height &= ~3;
- }
-
- shared_ptr<PlayerImage> pi (
- new PlayerImage (
- video->image,
- content->crop(),
- image_size,
- _video_container_size,
- _film->scaler()
- )
- );
-
- if (
- _film->with_subtitles () &&
- _out_subtitle.image &&
- video->dcp_time >= _out_subtitle.from && video->dcp_time <= _out_subtitle.to
- ) {
-
- Position<int> const container_offset (
- (_video_container_size.width - image_size.width) / 2,
- (_video_container_size.height - image_size.height) / 2
- );
-
- pi->set_subtitle (_out_subtitle.image, _out_subtitle.position + container_offset);
- }
-
-
-#ifdef DCPOMATIC_DEBUG
- _last_video = piece->content;
-#endif
-
- Video (pi, video->eyes, content->colour_conversion(), video->same, video->dcp_time);
-
- _last_emit_was_black = false;
-}
-
-void
-Player::step_video_position (shared_ptr<DecodedVideo> video)
-{
- /* This is a bit of a hack; don't update _video_position if EYES_RIGHT is on its way */
- if (video->eyes != EYES_LEFT) {
- /* This assumes that the video_frames_to_time conversion is exact
- so that there are no accumulated errors caused by rounding.
- */
- _video_position += _film->video_frames_to_time (1);
- }
-}
-
-void
-Player::emit_audio (weak_ptr<Piece> weak_piece, shared_ptr<DecodedAudio> audio)
-{
- shared_ptr<Piece> piece = weak_piece.lock ();
- if (!piece) {
- return;
- }
-
- shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> (piece->content);
- assert (content);
-
- /* Gain */
- if (content->audio_gain() != 0) {
- shared_ptr<AudioBuffers> gain (new AudioBuffers (audio->data));
- gain->apply_gain (content->audio_gain ());
- audio->data = gain;
- }
-
- /* Remap channels */
- shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->data->frames()));
- dcp_mapped->make_silent ();
- AudioMapping map = content->audio_mapping ();
- for (int i = 0; i < map.content_channels(); ++i) {
- for (int j = 0; j < _film->audio_channels(); ++j) {
- if (map.get (i, static_cast<dcp::Channel> (j)) > 0) {
- dcp_mapped->accumulate_channel (
- audio->data.get(),
- i,
- static_cast<dcp::Channel> (j),
- map.get (i, static_cast<dcp::Channel> (j))
- );
- }
- }
- }
-
- audio->data = dcp_mapped;
-
- /* Delay */
- audio->dcp_time += content->audio_delay() * TIME_HZ / 1000;
- if (audio->dcp_time < 0) {
- int const frames = - audio->dcp_time * _film->audio_frame_rate() / TIME_HZ;
- if (frames >= audio->data->frames ()) {
- return;
- }
-
- shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->data->channels(), audio->data->frames() - frames));
- trimmed->copy_from (audio->data.get(), audio->data->frames() - frames, frames, 0);
-
- audio->data = trimmed;
- audio->dcp_time = 0;
- }
-
- _audio_merger.push (audio->data, audio->dcp_time);
-}
-
-void
-Player::flush ()
-{
- TimedAudioBuffers<DCPTime> tb = _audio_merger.flush ();
- if (_audio && tb.audio) {
- Audio (tb.audio, tb.time);
- _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
- }
-
- while (_video && _video_position < _audio_position) {
- emit_black ();
- }
-
- while (_audio && _audio_position < _video_position) {
- emit_silence (_video_position - _audio_position);
- }
-
-}
-
-/** Seek so that the next pass() will yield (approximately) the requested frame.
- * Pass accurate = true to try harder to get close to the request.
- * @return true on error
- */
-void
-Player::seek (DCPTime t, bool accurate)
-{
- if (!_have_valid_pieces) {
- setup_pieces ();
- }
-
- if (_pieces.empty ()) {
- return;
- }
-
- for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
- /* s is the offset of t from the start position of this content */
- DCPTime s = t - (*i)->content->position ();
- s = max (static_cast<DCPTime> (0), s);
- s = min ((*i)->content->length_after_trim(), s);
-
- /* Convert this to the content time */
- ContentTime ct = (s + (*i)->content->trim_start()) * (*i)->frc.speed_up;
-
- /* And seek the decoder */
- (*i)->decoder->seek (ct, accurate);
- }
-
- _video_position = time_round_up (t, TIME_HZ / _film->video_frame_rate());
- _audio_position = time_round_up (t, TIME_HZ / _film->audio_frame_rate());
-
- _audio_merger.clear (_audio_position);
-
- if (!accurate) {
- /* We just did an inaccurate seek, so it's likely that the next thing seen
- out of pass() will be a fair distance from _{video,audio}_position. Setting
- this flag stops pass() from trying to fix that: we assume that if it
- was an inaccurate seek then the caller does not care too much about
- inserting black/silence to keep the time tidy.
- */
- _just_did_inaccurate_seek = true;
- }