Merge branch '2.0' of ssh://main.carlh.net/home/carl/git/dcpomatic into 2.0
[dcpomatic.git] / src / lib / ffmpeg_decoder.cc
index 0dae2a4edc41be11d70855d94e93b9594dc27c39..e90c33c80a848bca3c3faecbd7032c1df72d0526 100644 (file)
@@ -23,7 +23,6 @@
 
 #include <stdexcept>
 #include <vector>
-#include <sstream>
 #include <iomanip>
 #include <iostream>
 #include <stdint.h>
@@ -43,22 +42,24 @@ extern "C" {
 #include "filter_graph.h"
 #include "audio_buffers.h"
 #include "ffmpeg_content.h"
-#include "image_proxy.h"
+#include "raw_image_proxy.h"
 #include "film.h"
+#include "timer.h"
 
 #include "i18n.h"
 
 #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;
+using std::make_pair;
 using boost::shared_ptr;
 using boost::optional;
 using boost::dynamic_pointer_cast;
@@ -70,8 +71,6 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const FFmpegContent> c, shared_ptr<Log>
        , SubtitleDecoder (c)
        , FFmpeg (c)
        , _log (log)
-       , _subtitle_codec_context (0)
-       , _subtitle_codec (0)
 {
        /* Audio and video frame PTS values may not start with 0.  We want
           to fiddle them so that:
@@ -106,15 +105,6 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const FFmpegContent> c, shared_ptr<Log>
        }
 }
 
-FFmpegDecoder::~FFmpegDecoder ()
-{
-       boost::mutex::scoped_lock lm (_mutex);
-
-       if (_subtitle_codec_context) {
-               avcodec_close (_subtitle_codec_context);
-       }
-}
-
 void
 FFmpegDecoder::flush ()
 {
@@ -151,7 +141,7 @@ FFmpegDecoder::pass ()
        }
 
        int const si = _packet.stream_index;
-       
+
        if (si == _video_stream) {
                decode_video_packet ();
        } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, si)) {
@@ -293,75 +283,24 @@ FFmpegDecoder::bytes_per_audio_sample () const
        return av_get_bytes_per_sample (audio_sample_format ());
 }
 
-int
-FFmpegDecoder::minimal_run (boost::function<bool (optional<ContentTime>, optional<ContentTime>, int)> finished)
-{
-       int frames_read = 0;
-       optional<ContentTime> last_video;
-       optional<ContentTime> last_audio;
-
-       while (!finished (last_video, last_audio, frames_read)) {
-               int r = av_read_frame (_format_context, &_packet);
-               if (r < 0) {
-                       /* We should flush our decoders here, possibly yielding a few more frames,
-                          but the consequence of having to do that is too hideous to contemplate.
-                          Instead we give up and say that you can't seek too close to the end
-                          of a file.
-                       */
-                       return frames_read;
-               }
-
-               ++frames_read;
-
-               double const time_base = av_q2d (_format_context->streams[_packet.stream_index]->time_base);
-
-               if (_packet.stream_index == _video_stream) {
-
-                       avcodec_get_frame_defaults (_frame);
-                       
-                       int got_picture = 0;
-                       r = avcodec_decode_video2 (video_codec_context(), _frame, &got_picture, &_packet);
-                       if (r >= 0 && got_picture) {
-                               last_video = ContentTime::from_seconds (av_frame_get_best_effort_timestamp (_frame) * time_base) + _pts_offset;
-                       }
-
-               } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, _packet.stream_index)) {
-                       AVPacket copy_packet = _packet;
-                       while (copy_packet.size > 0) {
-
-                               int got_frame;
-                               r = avcodec_decode_audio4 (audio_codec_context(), _frame, &got_frame, &_packet);
-                               if (r >= 0 && got_frame) {
-                                       last_audio = ContentTime::from_seconds (av_frame_get_best_effort_timestamp (_frame) * time_base) + _pts_offset;
-                               }
-                                       
-                               copy_packet.data += r;
-                               copy_packet.size -= r;
-                       }
-               }
-               
-               av_free_packet (&_packet);
-       }
-
-       return frames_read;
-}
-
-bool
-FFmpegDecoder::seek_overrun_finished (ContentTime seek, optional<ContentTime> last_video, optional<ContentTime> last_audio) const
+void
+FFmpegDecoder::seek (ContentTime time, bool accurate)
 {
-       return (last_video && last_video.get() >= seek) || (last_audio && last_audio.get() >= seek);
-}
+       VideoDecoder::seek (time, accurate);
+       AudioDecoder::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.
+       */
 
-bool
-FFmpegDecoder::seek_final_finished (int n, int done) const
-{
-       return n == done;
-}
+       ContentTime pre_roll = accurate ? ContentTime::from_seconds (2) : ContentTime (0);
+       time -= pre_roll;
 
-void
-FFmpegDecoder::seek_and_flush (ContentTime t)
-{
-       ContentTime const u = t - _pts_offset;
+       /* 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 ()) {
@@ -370,54 +309,14 @@ FFmpegDecoder::seek_and_flush (ContentTime t)
                        );
        }
 
-       /* Ridiculous empirical hack */
-       s--;
-       if (s < 0) {
-               s = 0;
-       }
-
        av_seek_frame (_format_context, _video_stream, s, 0);
 
        avcodec_flush_buffers (video_codec_context());
        if (audio_codec_context ()) {
                avcodec_flush_buffers (audio_codec_context ());
        }
-       if (_subtitle_codec_context) {
-               avcodec_flush_buffers (_subtitle_codec_context);
-       }
-}
-
-void
-FFmpegDecoder::seek (ContentTime time, bool accurate)
-{
-       VideoDecoder::seek (time, accurate);
-       AudioDecoder::seek (time, accurate);
-       
-       /* If we are doing an accurate seek, our initial shot will be 2s (2 being
-          a number plucked from the air) earlier than we want to end up.  The loop below
-          will hopefully then step through to where we want to be.
-       */
-
-       ContentTime pre_roll = accurate ? ContentTime::from_seconds (2) : ContentTime (0);
-       ContentTime initial_seek = time - pre_roll;
-       if (initial_seek < ContentTime (0)) {
-               initial_seek = ContentTime (0);
-       }
-
-       /* Initial seek time in the video stream's timebase */
-
-       seek_and_flush (initial_seek);
-
-       if (!accurate) {
-               /* That'll do */
-               return;
-       }
-
-       int const N = minimal_run (boost::bind (&FFmpegDecoder::seek_overrun_finished, this, time, _1, _2));
-
-       seek_and_flush (initial_seek);
-       if (N > 0) {
-               minimal_run (boost::bind (&FFmpegDecoder::seek_final_finished, this, N - 1, _3));
+       if (subtitle_codec_context ()) {
+               avcodec_flush_buffers (subtitle_codec_context ());
        }
 }
 
@@ -433,11 +332,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) {
@@ -492,11 +401,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");
                }
        }
 
@@ -508,7 +417,7 @@ FFmpegDecoder::decode_subtitle_packet ()
 {
        int got_subtitle;
        AVSubtitle sub;
-       if (avcodec_decode_subtitle2 (_subtitle_codec_context, &sub, &got_subtitle, &_packet) < 0 || !got_subtitle) {
+       if (avcodec_decode_subtitle2 (subtitle_codec_context(), &sub, &got_subtitle, &_packet) < 0 || !got_subtitle) {
                return;
        }
 
@@ -575,3 +484,9 @@ FFmpegDecoder::decode_subtitle_packet ()
        
        avsubtitle_free (&sub);
 }
+
+list<ContentTimePeriod>
+FFmpegDecoder::subtitles_during (ContentTimePeriod p, bool starting) const
+{
+       return _ffmpeg_content->subtitles_during (p, starting);
+}