Various attempted fixes to audio sync.
[dcpomatic.git] / src / lib / player.cc
index e3d88a54c212dbe36e3225a1f337090d76050d13..3db2fe6c9eb19074be6f265f7fd4ffe6918ca61d 100644 (file)
@@ -18,6 +18,7 @@
 */
 
 #include <stdint.h>
+#include <algorithm>
 #include "player.h"
 #include "film.h"
 #include "ffmpeg_decoder.h"
@@ -107,13 +108,37 @@ Player::pass ()
 
        for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
 
-               shared_ptr<Decoded> dec = (*i)->decoder->peek ();
+               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;
+                       }
 
-               if (dec) {
-                       dec->set_dcp_times ((*i)->frc.speed_up, (*i)->content->position());
+                       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 && dec->dcp_time < earliest_time) {
+               if (!dec) {
+                       continue;
+               }
+
+               if (dec->dcp_time < earliest_time) {
                        earliest_piece = *i;
                        earliest_decoded = dec;
                        earliest_time = dec->dcp_time;
@@ -130,7 +155,7 @@ Player::pass ()
        }
 
        if (earliest_audio != TIME_MAX) {
-               TimedAudioBuffers<DCPTime> tb = _audio_merger.pull (earliest_audio);
+               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.
@@ -147,11 +172,6 @@ Player::pass ()
        /* Will be set to false if we shouldn't consume the peeked DecodedThing */
        bool consume = true;
 
-       /* This is the margin either side of _{video,audio}_position that we will accept
-          as a starting point for a frame consecutive to the previous.
-       */
-       DCPTime const margin = TIME_HZ / (2 * _film->video_frame_rate ());
-       
        if (dv && _video) {
 
                if (_just_did_inaccurate_seek) {
@@ -160,7 +180,7 @@ Player::pass ()
                        emit_video (earliest_piece, dv);
                        step_video_position (dv);
                        
-               } else if (dv->dcp_time - _video_position > margin) {
+               } else if (dv->dcp_time > _video_position) {
 
                        /* Too far ahead */
 
@@ -172,36 +192,43 @@ Player::pass ()
                        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 (abs (dv->dcp_time - _video_position) < margin) {
+               } 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 > margin) {
+               if (da->dcp_time > _audio_position) {
                        /* Too far ahead */
                        emit_silence (da->dcp_time - _audio_position);
                        consume = false;
-               } else if (abs (da->dcp_time - _audio_position) < margin) {
+                       _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 (ds && _video) {
@@ -304,10 +331,6 @@ Player::emit_audio (weak_ptr<Piece> weak_piece, shared_ptr<DecodedAudio> audio)
                audio->data = gain;
        }
 
-       if (content->trimmed (audio->dcp_time - content->position ())) {
-               return;
-       }
-
        /* Remap channels */
        shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->data->frames()));
        dcp_mapped->make_silent ();
@@ -342,16 +365,16 @@ void
 Player::flush ()
 {
        TimedAudioBuffers<DCPTime> tb = _audio_merger.flush ();
-       if (tb.audio) {
+       if (_audio && tb.audio) {
                Audio (tb.audio, tb.time);
                _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
        }
 
-       while (_video_position < _audio_position) {
+       while (_video && _video_position < _audio_position) {
                emit_black ();
        }
 
-       while (_audio_position < _video_position) {
+       while (_audio && _audio_position < _video_position) {
                emit_silence (_video_position - _audio_position);
        }
        
@@ -379,7 +402,7 @@ Player::seek (DCPTime t, bool accurate)
                s = min ((*i)->content->length_after_trim(), s);
 
                /* Convert this to the content time */
-               ContentTime ct = (s * (*i)->frc.speed_up) + (*i)->content->trim_start ();
+               ContentTime ct = (s + (*i)->content->trim_start()) * (*i)->frc.speed_up;
 
                /* And seek the decoder */
                (*i)->decoder->seek (ct, accurate);
@@ -469,7 +492,8 @@ Player::setup_pieces ()
                        }
                }
 
-               decoder->seek ((*i)->trim_start (), true);
+               ContentTime st = (*i)->trim_start() * frc->speed_up;
+               decoder->seek (st, true);
                
                _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder, frc.get ())));
        }
@@ -693,3 +717,15 @@ PlayerImage::image (AVPixelFormat format, bool aligned)
        return out;
 }
 
+void
+PlayerStatistics::dump (shared_ptr<Log> log) const
+{
+       log->log (String::compose ("Video: %1 good %2 skipped %3 black %4 repeat", video.good, video.skip, video.black, video.repeat));
+       log->log (String::compose ("Audio: %1 good %2 skipped %3 silence", audio.good, audio.skip, audio.silence));
+}
+
+PlayerStatistics const &
+Player::statistics () const
+{
+       return _statistics;
+}