Don't bother decoding video frames when we're seeking around trying to find subtitles.
[dcpomatic.git] / src / lib / ffmpeg_decoder.cc
index dd47d306a6e54c0611e0abde6b6ab802cab4e77b..6122a547ed3b9f816345b0f092ccdc95c403ab88 100644 (file)
@@ -23,7 +23,6 @@
 
 #include <stdexcept>
 #include <vector>
-#include <sstream>
 #include <iomanip>
 #include <iostream>
 #include <stdint.h>
@@ -51,12 +50,12 @@ extern "C" {
 
 #define LOG_GENERAL(...) _video_content->film()->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL);
 #define LOG_ERROR(...) _video_content->film()->log()->log (String::compose (__VA_ARGS__), Log::TYPE_ERROR);
-#define LOG_WARNING(...) _video_content->film()->log()->log (__VA_ARGS__, Log::TYPE_WARNING);
+#define LOG_WARNING_NC(...) _video_content->film()->log()->log (__VA_ARGS__, Log::TYPE_WARNING);
+#define LOG_WARNING(...) _video_content->film()->log()->log (String::compose (__VA_ARGS__), Log::TYPE_WARNING);
 
 using std::cout;
 using std::string;
 using std::vector;
-using std::stringstream;
 using std::list;
 using std::min;
 using std::pair;
@@ -98,6 +97,14 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const FFmpegContent> c, shared_ptr<Log>
                _pts_offset = - c->audio_stream()->first_audio.get();
        }
 
+       /* If _pts_offset is positive we would be pushing things from a -ve PTS to be played.
+          I don't think we ever want to do that, as it seems things at -ve PTS are not meant
+          to be seen (use for alignment bars etc.); see mantis #418.
+       */
+       if (_pts_offset > ContentTime ()) {
+               _pts_offset = ContentTime ();
+       }
+
        /* Now adjust both so that the video pts starts on a frame */
        if (have_video && have_audio) {
                ContentTime first_video = c->first_video().get() + _pts_offset;
@@ -125,29 +132,34 @@ FFmpegDecoder::flush ()
 }
 
 bool
-FFmpegDecoder::pass ()
+FFmpegDecoder::pass (PassReason reason)
 {
        int r = av_read_frame (_format_context, &_packet);
 
-       if (r < 0) {
+       /* AVERROR_INVALIDDATA can apparently be returned sometimes even when av_read_frame
+          has pretty-much succeeded (and hence generated data which should be processed).
+          Hence it makes sense to continue here in that case.
+       */
+       if (r < 0 && r != AVERROR_INVALIDDATA) {
                if (r != AVERROR_EOF) {
                        /* Maybe we should fail here, but for now we'll just finish off instead */
                        char buf[256];
                        av_strerror (r, buf, sizeof(buf));
                        LOG_ERROR (N_("error on av_read_frame (%1) (%2)"), buf, r);
                }
-
+               
                flush ();
                return true;
        }
 
        int const si = _packet.stream_index;
+       shared_ptr<const FFmpegContent> fc = _ffmpeg_content;
 
-       if (si == _video_stream) {
+       if (si == _video_stream && !_ignore_video && reason != PASS_REASON_SUBTITLE) {
                decode_video_packet ();
-       } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, si)) {
+       } else if (fc->audio_stream() && fc->audio_stream()->uses_index (_format_context, si) && reason != PASS_REASON_SUBTITLE) {
                decode_audio_packet ();
-       } else if (_ffmpeg_content->subtitle_stream() && _ffmpeg_content->subtitle_stream()->uses_index (_format_context, si)) {
+       } else if (fc->subtitle_stream() && fc->subtitle_stream()->uses_index (_format_context, si)) {
                decode_subtitle_packet ();
        }
 
@@ -161,13 +173,14 @@ FFmpegDecoder::pass ()
 shared_ptr<AudioBuffers>
 FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
 {
-       assert (_ffmpeg_content->audio_channels());
-       assert (bytes_per_audio_sample());
+       DCPOMATIC_ASSERT (_ffmpeg_content->audio_channels());
+       DCPOMATIC_ASSERT (bytes_per_audio_sample());
 
        /* Deinterleave and convert to float */
 
-       assert ((size % (bytes_per_audio_sample() * _ffmpeg_content->audio_channels())) == 0);
-
+       /* total_samples and frames will be rounded down here, so if there are stray samples at the end
+          of the block that do not form a complete sample or frame they will be dropped.
+       */
        int const total_samples = size / bytes_per_audio_sample();
        int const frames = total_samples / _ffmpeg_content->audio_channels();
        shared_ptr<AudioBuffers> audio (new AudioBuffers (_ffmpeg_content->audio_channels(), frames));
@@ -289,33 +302,21 @@ FFmpegDecoder::seek (ContentTime time, bool accurate)
 {
        VideoDecoder::seek (time, accurate);
        AudioDecoder::seek (time, accurate);
-       
+       SubtitleDecoder::seek (time, accurate);
+
        /* If we are doing an `accurate' seek, we need to use pre-roll, as
           we don't really know what the seek will give us.
        */
 
        ContentTime pre_roll = accurate ? ContentTime::from_seconds (2) : ContentTime (0);
        time -= pre_roll;
-       if (time < ContentTime (0)) {
-               time = ContentTime (0);
-       }
 
+       /* XXX: it seems debatable whether PTS should be used here...
+          http://www.mjbshaw.com/2012/04/seeking-in-ffmpeg-know-your-timestamp.html
+       */
+       
        ContentTime const u = time - _pts_offset;
-       int64_t s = u.seconds() / av_q2d (_format_context->streams[_video_stream]->time_base);
-
-       if (_ffmpeg_content->audio_stream ()) {
-               s = min (
-                       s, int64_t (u.seconds() / av_q2d (_ffmpeg_content->audio_stream()->stream(_format_context)->time_base))
-                       );
-       }
-
-       /* Ridiculous empirical hack */
-       s--;
-       if (s < 0) {
-               s = 0;
-       }
-
-       av_seek_frame (_format_context, _video_stream, s, 0);
+       av_seek_frame (_format_context, _video_stream, u.seconds() / av_q2d (_format_context->streams[_video_stream]->time_base), 0);
 
        avcodec_flush_buffers (video_codec_context());
        if (audio_codec_context ()) {
@@ -338,11 +339,21 @@ FFmpegDecoder::decode_audio_packet ()
        while (copy_packet.size > 0) {
 
                int frame_finished;
-               int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, &copy_packet);
-
+               int decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, &copy_packet);
                if (decode_result < 0) {
-                       LOG_ERROR ("avcodec_decode_audio4 failed (%1)", decode_result);
-                       return;
+                       /* avcodec_decode_audio4 can sometimes return an error even though it has decoded
+                          some valid data; for example dca_subframe_footer can return AVERROR_INVALIDDATA
+                          if it overreads the auxiliary data.  ffplay carries on if frame_finished is true,
+                          even in the face of such an error, so I think we should too.
+
+                          Returning from the method here caused mantis #352.
+                       */
+                       LOG_WARNING ("avcodec_decode_audio4 failed (%1)", decode_result);
+
+                       /* Fudge decode_result so that we come out of the while loop when
+                          we've processed this data.
+                       */
+                       decode_result = copy_packet.size;
                }
 
                if (frame_finished) {
@@ -397,11 +408,11 @@ FFmpegDecoder::decode_video_packet ()
                if (i->second != AV_NOPTS_VALUE) {
                        double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset.seconds ();
                        video (
-                               shared_ptr<ImageProxy> (new RawImageProxy (image, _video_content->film()->log())),
+                               shared_ptr<ImageProxy> (new RawImageProxy (image)),
                                rint (pts * _ffmpeg_content->video_frame_rate ())
                                );
                } else {
-                       LOG_WARNING ("Dropping frame without PTS");
+                       LOG_WARNING_NC ("Dropping frame without PTS");
                }
        }
 
@@ -416,28 +427,34 @@ FFmpegDecoder::decode_subtitle_packet ()
        if (avcodec_decode_subtitle2 (subtitle_codec_context(), &sub, &got_subtitle, &_packet) < 0 || !got_subtitle) {
                return;
        }
-
-       /* Sometimes we get an empty AVSubtitle, which is used by some codecs to
-          indicate that the previous subtitle should stop.
-       */
+       
        if (sub.num_rects <= 0) {
-               image_subtitle (ContentTimePeriod (), shared_ptr<Image> (), dcpomatic::Rect<double> ());
+               /* Sometimes we get an empty AVSubtitle, which is used by some codecs to
+                  indicate that the previous subtitle should stop.  We can ignore it here.
+               */
                return;
        } else if (sub.num_rects > 1) {
                throw DecodeError (_("multi-part subtitles not yet supported"));
        }
-               
+
        /* Subtitle PTS (within the source, not taking into account any of the
-          source that we may have chopped off for the DCP)
+          source that we may have chopped off for the DCP).
        */
-       ContentTimePeriod period = subtitle_period (sub) + _pts_offset;
-
+       FFmpegSubtitlePeriod sub_period = subtitle_period (sub);
+       ContentTimePeriod period;
+       period.from = sub_period.from + _pts_offset;
+       if (sub_period.to) {
+               /* We already know the subtitle period `to' time */
+               period.to = sub_period.to.get() + _pts_offset;
+       } else {
+               /* We have to look up the `to' time in the stream's records */
+               period.to = ffmpeg_content()->subtitle_stream()->find_subtitle_to (sub_period.from);
+       }
+       
        AVSubtitleRect const * rect = sub.rects[0];
 
        if (rect->type != SUBTITLE_BITMAP) {
-               /* XXX */
-               // throw DecodeError (_("non-bitmap subtitles not yet supported"));
-               return;
+               throw DecodeError (_("non-bitmap subtitles not yet supported"));
        }
 
        /* Note RGBA is expressed little-endian, so the first byte in the word is R, second
@@ -466,23 +483,26 @@ FFmpegDecoder::decode_subtitle_packet ()
        }
 
        dcp::Size const vs = _ffmpeg_content->video_size ();
-
-       image_subtitle (
-               period,
-               image,
-               dcpomatic::Rect<double> (
-                       static_cast<double> (rect->x) / vs.width,
-                       static_cast<double> (rect->y) / vs.height,
-                       static_cast<double> (rect->w) / vs.width,
-                       static_cast<double> (rect->h) / vs.height
-                       )
+       dcpomatic::Rect<double> const scaled_rect (
+               static_cast<double> (rect->x) / vs.width,
+               static_cast<double> (rect->y) / vs.height,
+               static_cast<double> (rect->w) / vs.width,
+               static_cast<double> (rect->h) / vs.height
                );
+
+       image_subtitle (ContentTimePeriod (period.from, period.to), image, scaled_rect);
        
        avsubtitle_free (&sub);
 }
 
 list<ContentTimePeriod>
-FFmpegDecoder::subtitles_during (ContentTimePeriod p, bool starting) const
+FFmpegDecoder::image_subtitles_during (ContentTimePeriod p, bool starting) const
 {
        return _ffmpeg_content->subtitles_during (p, starting);
 }
+
+list<ContentTimePeriod>
+FFmpegDecoder::text_subtitles_during (ContentTimePeriod, bool) const
+{
+       return list<ContentTimePeriod> ();
+}