Allow repeat-frame to work with 3D.
[dcpomatic.git] / src / lib / player.cc
index a711c80c03521ad24aa2dd7fb21372b87911c85d..de221fef357c17ed849725f9d57376f07786b8b0 100644 (file)
@@ -47,7 +47,6 @@
 #include "content_subtitle.h"
 #include "dcp_decoder.h"
 #include "image_decoder.h"
-#include "resampler.h"
 #include "compose.hpp"
 #include <dcp/reel.h>
 #include <dcp/reel_sound_asset.h>
@@ -87,7 +86,6 @@ Player::Player (shared_ptr<const Film> film, shared_ptr<const Playlist> playlist
        , _always_burn_subtitles (false)
        , _fast (false)
        , _play_referenced (false)
-       , _last_seek_accurate (true)
        , _audio_merger (_film->audio_frame_rate())
 {
        _film_changed_connection = _film->Changed.connect (bind (&Player::film_changed, this, _1));
@@ -172,8 +170,8 @@ Player::setup_pieces ()
                }
        }
 
-       _last_video_time = optional<DCPTime> ();
-       _last_audio_time = optional<DCPTime> ();
+       _last_video_time = DCPTime ();
+       _last_audio_time = DCPTime ();
        _have_valid_pieces = true;
 }
 
@@ -520,7 +518,7 @@ Player::pass ()
 
        BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
                if (!i->done) {
-                       DCPTime const t = i->content->position() + DCPTime (i->decoder->position(), i->frc);
+                       DCPTime const t = content_time_to_dcp (i, i->decoder->position());
                        if (!earliest || t < earliest_content) {
                                earliest_content = t;
                                earliest = i;
@@ -528,38 +526,28 @@ Player::pass ()
                }
        }
 
-       if (earliest) {
-               earliest->done = earliest->decoder->pass ();
-               if (earliest->done && earliest->content->audio) {
-                       /* Flush the Player audio system for this piece */
-                       BOOST_FOREACH (AudioStreamPtr i, earliest->content->audio->streams()) {
-                               audio_flush (earliest, i);
-                       }
-               }
-       }
-
        /* Fill towards the next thing that might happen (or the end of the playlist).  This is to fill gaps between content,
           NOT to fill gaps within content (the latter is done in ::video())
        */
-       DCPTime fill_towards = earliest ? earliest_content : _playlist->length();
+       DCPTime fill_towards = earliest ? earliest_content : _playlist->length().ceil(_film->video_frame_rate());
 
        /* Work out where to fill video from */
        optional<DCPTime> video_fill_from;
-       if (_last_video_time && !_playlist->video_content_at(*_last_video_time)) {
-               /* No seek; fill from the last video time */
+       if (_last_video_time) {
+               /* Fill from the last video or seek time */
                video_fill_from = _last_video_time;
-       } else if (_last_seek_time && !_playlist->video_content_at(*_last_seek_time)) {
-               /* Seek into an empty area; fill from the seek time */
-               video_fill_from = _last_seek_time;
        }
 
        bool filled = false;
        /* Fill some black if we would emit before the earliest piece of content.  This is so we act like a phantom
           Piece which emits black in spaces (we only emit if we are the earliest thing)
        */
-       if (earliest && video_fill_from && *video_fill_from < earliest_content && ((fill_towards - *video_fill_from)) > one_video_frame()) {
-               emit_video (black_player_video_frame(), *video_fill_from);
-               filled = true;
+       if (video_fill_from && (!earliest || *video_fill_from < earliest_content) && ((fill_towards - *video_fill_from)) >= one_video_frame()) {
+               list<DCPTimePeriod> p = subtract(DCPTimePeriod(*video_fill_from, *video_fill_from + one_video_frame()), _no_video);
+               if (!p.empty ()) {
+                       emit_video (black_player_video_frame(), p.front().from);
+                       filled = true;
+               }
        } else if (_playlist->length() == DCPTime()) {
                /* Special case of an empty Film; just give one black frame */
                emit_video (black_player_video_frame(), DCPTime());
@@ -567,37 +555,42 @@ Player::pass ()
        }
 
        optional<DCPTime> audio_fill_from;
-       if (_last_audio_time && !_playlist->audio_content_at(*_last_audio_time)) {
-               /* No seek; fill from the last thing that happened */
+       if (_last_audio_time) {
+               /* Fill from the last audio or seek time */
                audio_fill_from = _last_audio_time;
-       } else if (_last_seek_time && !_playlist->audio_content_at(*_last_seek_time)) {
-               /* Seek into an empty area; fill from the seek time */
-               audio_fill_from = _last_seek_time;
        }
 
-       if (audio_fill_from && audio_fill_from < fill_towards) {
-               DCPTimePeriod period (*audio_fill_from, fill_towards);
+       DCPTime audio_fill_towards = fill_towards;
+       if (earliest && earliest->content->audio) {
+               audio_fill_towards += DCPTime::from_seconds (earliest->content->audio->delay() / 1000.0);
+       }
+
+       if (audio_fill_from && audio_fill_from < audio_fill_towards) {
+               DCPTimePeriod period (*audio_fill_from, audio_fill_towards);
                if (period.duration() > one_video_frame()) {
                        period.to = period.from + one_video_frame();
                }
-               fill_audio (period);
-               filled = true;
+               list<DCPTimePeriod> p = subtract(period, _no_audio);
+               if (!p.empty ()) {
+                       fill_audio (p.front());
+                       filled = true;
+               }
        }
 
-       if (!earliest && !filled) {
-               return true;
+       if (earliest) {
+               earliest->done = earliest->decoder->pass ();
        }
 
        /* Emit any audio that is ready */
 
-       DCPTime pull_from = _playlist->length ();
+       DCPTime pull_to = _playlist->length ();
        for (map<AudioStreamPtr, StreamState>::const_iterator i = _stream_states.begin(); i != _stream_states.end(); ++i) {
-               if (!i->second.piece->done && i->second.last_push_end < pull_from) {
-                       pull_from = i->second.last_push_end;
+               if (!i->second.piece->done && i->second.last_push_end < pull_to) {
+                       pull_to = i->second.last_push_end;
                }
        }
 
-       list<pair<shared_ptr<AudioBuffers>, DCPTime> > audio = _audio_merger.pull (pull_from);
+       list<pair<shared_ptr<AudioBuffers>, DCPTime> > audio = _audio_merger.pull (pull_to);
        for (list<pair<shared_ptr<AudioBuffers>, DCPTime> >::iterator i = audio.begin(); i != audio.end(); ++i) {
                if (_last_audio_time && i->second < *_last_audio_time) {
                        /* There has been an accurate seek and we have received some audio before the seek time;
@@ -617,7 +610,7 @@ Player::pass ()
                emit_audio (i->first, i->second);
        }
 
-       return false;
+       return !earliest && !filled;
 }
 
 optional<PositionImage>
@@ -666,20 +659,24 @@ Player::video (weak_ptr<Piece> wp, ContentVideo video)
        if (
                time < piece->content->position() ||
                time >= piece->content->end() ||
-               (_last_seek_time && _last_seek_accurate && time < *_last_seek_time)) {
+               (_last_video_time && time < *_last_video_time)) {
                return;
        }
 
-       /* Fill gaps caused by (the hopefully rare event of) a decoder not emitting contiguous video.  We have to do this here
-          as in the problematic case we are about to emit a frame which is not contiguous with the previous.
-       */
+       /* Fill gaps that we discover now that we have some video which needs to be emitted */
 
+       optional<DCPTime> fill_to;
        if (_last_video_time) {
+               fill_to = _last_video_time;
+       }
+
+       if (fill_to) {
                /* XXX: this may not work for 3D */
-               BOOST_FOREACH (DCPTimePeriod i, subtract(DCPTimePeriod (*_last_video_time, time), _no_video)) {
+               BOOST_FOREACH (DCPTimePeriod i, subtract(DCPTimePeriod (*fill_to, time), _no_video)) {
                        for (DCPTime j = i.from; j < i.to; j += one_video_frame()) {
-                               if (_last_video) {
-                                       emit_video (shared_ptr<PlayerVideo> (new PlayerVideo (*_last_video)), j);
+                               LastVideoMap::const_iterator k = _last_video.find (wp);
+                               if (k != _last_video.end ()) {
+                                       emit_video (k->second, j);
                                } else {
                                        emit_video (black_player_video_frame(), j);
                                }
@@ -687,7 +684,7 @@ Player::video (weak_ptr<Piece> wp, ContentVideo video)
                }
        }
 
-       _last_video.reset (
+       _last_video[wp].reset (
                new PlayerVideo (
                        video.image,
                        piece->content->video->crop (),
@@ -702,33 +699,7 @@ Player::video (weak_ptr<Piece> wp, ContentVideo video)
                        )
                );
 
-       emit_video (_last_video, time);
-}
-
-void
-Player::audio_flush (shared_ptr<Piece> piece, AudioStreamPtr stream)
-{
-       shared_ptr<AudioContent> content = piece->content->audio;
-       DCPOMATIC_ASSERT (content);
-
-       shared_ptr<Resampler> r = resampler (content, stream, false);
-       if (!r) {
-               return;
-       }
-
-       pair<shared_ptr<const AudioBuffers>, Frame> ro = r->flush ();
-       if (ro.first->frames() == 0) {
-               return;
-       }
-
-       ContentAudio content_audio;
-       content_audio.audio = ro.first;
-       content_audio.frame = ro.second;
-
-       /* Compute time in the DCP */
-       DCPTime time = resampled_audio_to_dcp (piece, content_audio.frame) + DCPTime::from_seconds (content->delay() / 1000.0);
-
-       audio_transform (content, stream, content_audio, time);
+       emit_video (_last_video[wp], time);
 }
 
 /** Do our common processing on some audio */
@@ -792,17 +763,6 @@ Player::audio (weak_ptr<Piece> wp, AudioStreamPtr stream, ContentAudio content_a
        shared_ptr<AudioContent> content = piece->content->audio;
        DCPOMATIC_ASSERT (content);
 
-       /* Resample */
-       if (stream->frame_rate() != content->resampled_frame_rate()) {
-               shared_ptr<Resampler> r = resampler (content, stream, true);
-               pair<shared_ptr<const AudioBuffers>, Frame> ro = r->run (content_audio.audio, content_audio.frame);
-               if (ro.first->frames() == 0) {
-                       return;
-               }
-               content_audio.audio = ro.first;
-               content_audio.frame = ro.second;
-       }
-
        /* Compute time in the DCP */
        DCPTime time = resampled_audio_to_dcp (piece, content_audio.frame) + DCPTime::from_seconds (content->delay() / 1000.0);
        /* And the end of this block in the DCP */
@@ -927,11 +887,6 @@ Player::seek (DCPTime time, bool accurate)
                _audio_processor->flush ();
        }
 
-       for (ResamplerMap::iterator i = _resamplers.begin(); i != _resamplers.end(); ++i) {
-               i->second->flush ();
-               i->second->reset ();
-       }
-
        _audio_merger.clear ();
        _active_subtitles.clear ();
 
@@ -950,37 +905,13 @@ Player::seek (DCPTime time, bool accurate)
                }
        }
 
-       _last_video_time = optional<DCPTime> ();
-       _last_audio_time = optional<DCPTime> ();
-       _last_seek_time = time;
-       _last_seek_accurate = accurate;
-}
-
-shared_ptr<Resampler>
-Player::resampler (shared_ptr<const AudioContent> content, AudioStreamPtr stream, bool create)
-{
-       ResamplerMap::const_iterator i = _resamplers.find (make_pair (content, stream));
-       if (i != _resamplers.end ()) {
-               return i->second;
+       if (accurate) {
+               _last_video_time = time;
+               _last_audio_time = time;
+       } else {
+               _last_video_time = optional<DCPTime>();
+               _last_audio_time = optional<DCPTime>();
        }
-
-       if (!create) {
-               return shared_ptr<Resampler> ();
-       }
-
-       LOG_GENERAL (
-               "Creating new resampler from %1 to %2 with %3 channels",
-               stream->frame_rate(),
-               content->resampled_frame_rate(),
-               stream->channels()
-               );
-
-       shared_ptr<Resampler> r (
-               new Resampler (stream->frame_rate(), content->resampled_frame_rate(), stream->channels())
-               );
-
-       _resamplers[make_pair(content, stream)] = r;
-       return r;
 }
 
 void
@@ -990,9 +921,13 @@ Player::emit_video (shared_ptr<PlayerVideo> pv, DCPTime time)
        if (subtitles) {
                pv->set_subtitle (subtitles.get ());
        }
+
        Video (pv, time);
-       _last_video_time = time + one_video_frame();
-       _active_subtitles.clear_before (time);
+
+       if (pv->eyes() == EYES_BOTH || pv->eyes() == EYES_RIGHT) {
+               _last_video_time = time + one_video_frame();
+               _active_subtitles.clear_before (time);
+       }
 }
 
 void
@@ -1005,6 +940,12 @@ Player::emit_audio (shared_ptr<AudioBuffers> data, DCPTime time)
 void
 Player::fill_audio (DCPTimePeriod period)
 {
+       if (period.from == period.to) {
+               return;
+       }
+
+       DCPOMATIC_ASSERT (period.from < period.to);
+
        BOOST_FOREACH (DCPTimePeriod i, subtract(period, _no_audio)) {
                DCPTime t = i.from;
                while (t < i.to) {