Don't trust video timestamps from FFmpegDecoder.
authorCarl Hetherington <cth@carlh.net>
Sun, 10 Nov 2019 21:59:39 +0000 (22:59 +0100)
committerCarl Hetherington <cth@carlh.net>
Sat, 15 Feb 2020 00:53:19 +0000 (01:53 +0100)
Back-ported from 98342fb53eae4d32440fc69c279f2ca0fef785b5 in v2.15.x.

src/lib/audio_decoder.cc
src/lib/audio_decoder.h
src/lib/decoder_part.h
src/lib/ffmpeg_decoder.cc
src/lib/text_decoder.h
src/lib/video_decoder.cc
src/lib/video_decoder.h

index 440510ce562c9dbb13304a75f5f3a24e6f471eb2..2ab527f59bc4bf535d5160f79db8be1288b04983 100644 (file)
@@ -110,7 +110,7 @@ AudioDecoder::stream_position (shared_ptr<const Film> film, AudioStreamPtr strea
        return ContentTime::from_frames (i->second, _content->resampled_frame_rate(film));
 }
 
-ContentTime
+optional<ContentTime>
 AudioDecoder::position (shared_ptr<const Film> film) const
 {
        optional<ContentTime> p;
@@ -121,7 +121,7 @@ AudioDecoder::position (shared_ptr<const Film> film) const
                }
        }
 
-       return p.get_value_or(ContentTime());
+       return p;
 }
 
 void
index 50e361e8f19f80cb181bc8a55d3e80f1aa73da8c..002d839e9ffd725f344351a90cee98a4a0731501 100644 (file)
@@ -47,7 +47,7 @@ class AudioDecoder : public boost::enable_shared_from_this<AudioDecoder>, public
 public:
        AudioDecoder (Decoder* parent, boost::shared_ptr<const AudioContent> content, bool fast);
 
-       ContentTime position (boost::shared_ptr<const Film> film) const;
+       boost::optional<ContentTime> position (boost::shared_ptr<const Film> film) const;
        void emit (boost::shared_ptr<const Film> film, AudioStreamPtr stream, boost::shared_ptr<const AudioBuffers>, ContentTime);
        void seek ();
        void flush ();
index 7ba2cb2eb67c81f4ee8159e890ebbefce0d09f10..e763b73133394907afc32596ffcb87b690fd68ce 100644 (file)
@@ -34,7 +34,7 @@ public:
        DecoderPart (Decoder* parent);
        virtual ~DecoderPart () {}
 
-       virtual ContentTime position (boost::shared_ptr<const Film> film) const = 0;
+       virtual boost::optional<ContentTime> position (boost::shared_ptr<const Film> film) const = 0;
        virtual void seek () = 0;
 
        void set_ignore (bool i) {
index 0f39f7727348eadfd124689e5e1e858ab50c0ef6..fd248c39a67a0b319a3ddc0cb910ad2bc3d02920 100644 (file)
@@ -126,7 +126,7 @@ FFmpegDecoder::flush ()
        if (video) {
                double const vfr = _ffmpeg_content->video_frame_rate().get();
                Frame const f = full_length.frames_round (vfr);
-               Frame v = video->position(film()).frames_round(vfr) + 1;
+               Frame v = video->position(film()).get_value_or(ContentTime()).frames_round(vfr) + 1;
                while (v < f) {
                        video->emit (film(), shared_ptr<const ImageProxy> (new RawImageProxy (_black_image)), v);
                        ++v;
index a82f43f51af2e3a1ddd8bf1178366241cae51add..a320e146b9f68adb5500ebb6bed678795a4b14c6 100644 (file)
@@ -44,7 +44,7 @@ public:
                ContentTime first
                );
 
-       ContentTime position (boost::shared_ptr<const Film>) const {
+       boost::optional<ContentTime> position (boost::shared_ptr<const Film>) const {
                return _position;
        }
 
index de1df727f13838563484bb9d348835c1ba248c2f..46611bbedf2a7ae87ccc55898ac7c9a148d81f73 100644 (file)
@@ -53,12 +53,44 @@ VideoDecoder::VideoDecoder (Decoder* parent, shared_ptr<const Content> c)
  *  and so on.
  */
 void
-VideoDecoder::emit (shared_ptr<const Film> film, shared_ptr<const ImageProxy> image, Frame frame)
+VideoDecoder::emit (shared_ptr<const Film> film, shared_ptr<const ImageProxy> image, Frame decoder_frame)
 {
        if (ignore ()) {
                return;
        }
 
+       double const afr = _content->active_video_frame_rate(film);
+
+       Frame frame;
+       if (!_position) {
+               /* This is the first data we have received since initialisation or seek.  Set
+                  the position based on the frame that was given.  After this first time
+                  we just cound frames, since (as with audio) it seems that ContentTimes
+                  are unreliable from FFmpegDecoder.  They are much better than audio times
+                  but still we get the occasional one which is duplicated.  In this case
+                  ffmpeg seems to carry on regardless, processing the video frame as normal.
+                  If we drop the frame with the duplicated timestamp we obviously lose sync.
+               */
+               _position = ContentTime::from_frames (decoder_frame, afr);
+               if (_content->video->frame_type() == VIDEO_FRAME_TYPE_3D_ALTERNATE) {
+                       frame = decoder_frame / 2;
+                       _last_emitted_eyes = EYES_RIGHT;
+               } else {
+                       frame = decoder_frame;
+               }
+       } else {
+               if (_content->video->frame_type() == VIDEO_FRAME_TYPE_3D_ALTERNATE) {
+                       DCPOMATIC_ASSERT (_last_emitted_eyes);
+                       if (_last_emitted_eyes.get() == EYES_RIGHT) {
+                               frame = _position->frames_round(afr) + 1;
+                       } else {
+                               frame = _position->frames_round(afr);
+                       }
+               } else {
+                       frame = _position->frames_round(afr) + 1;
+               }
+       }
+
        switch (_content->video->frame_type ()) {
        case VIDEO_FRAME_TYPE_2D:
                Data (ContentVideo (image, frame, EYES_BOTH, PART_WHOLE));
@@ -90,9 +122,13 @@ VideoDecoder::emit (shared_ptr<const Film> film, shared_ptr<const ImageProxy> im
                break;
        }
        case VIDEO_FRAME_TYPE_3D_ALTERNATE:
-               Data (ContentVideo (image, frame / 2, (frame % 2) ? EYES_RIGHT : EYES_LEFT, PART_WHOLE));
-               frame /= 2;
+       {
+               DCPOMATIC_ASSERT (_last_emitted_eyes);
+               Eyes const eyes = _last_emitted_eyes.get() == EYES_LEFT ? EYES_RIGHT : EYES_LEFT;
+               Data (ContentVideo (image, frame, eyes, PART_WHOLE));
+               _last_emitted_eyes = eyes;
                break;
+       }
        case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT:
                Data (ContentVideo (image, frame, EYES_LEFT, PART_LEFT_HALF));
                Data (ContentVideo (image, frame, EYES_RIGHT, PART_RIGHT_HALF));
@@ -111,7 +147,7 @@ VideoDecoder::emit (shared_ptr<const Film> film, shared_ptr<const ImageProxy> im
                DCPOMATIC_ASSERT (false);
        }
 
-       _position = ContentTime::from_frames (frame, _content->active_video_frame_rate(film));
+       _position = ContentTime::from_frames (frame, afr);
 }
 
 void
index ed56feea05284ccc785e74ba1167b2049cf6322f..98a8e7b7a259a9960f277b4c708217b0c911ebb1 100644 (file)
@@ -51,7 +51,7 @@ public:
        friend struct ffmpeg_pts_offset_test;
        friend void ffmpeg_decoder_sequential_test_one (boost::filesystem::path file, float fps, int gaps, int video_length);
 
-       ContentTime position (boost::shared_ptr<const Film>) const {
+       boost::optional<ContentTime> position (boost::shared_ptr<const Film>) const {
                return _position;
        }
 
@@ -63,10 +63,10 @@ public:
 
 private:
        boost::shared_ptr<const Content> _content;
-       /** Frame of last thing to be emitted */
+       /** Frame of last thing to be emitted; only used for 3D */
        boost::optional<Frame> _last_emitted_frame;
        boost::optional<Eyes> _last_emitted_eyes;
-       ContentTime _position;
+       boost::optional<ContentTime> _position;
 };
 
 #endif