Add another believed-correct subtitle timing fix.
[dcpomatic.git] / src / lib / ffmpeg.cc
index 11a332bdb80c300b5e91478899aa00a9fdb15bf5..503f8e51cf13b146d25d81d443688428a87e5f97 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2013-2019 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
@@ -18,6 +18,7 @@
 
 */
 
+
 #include "ffmpeg.h"
 #include "ffmpeg_content.h"
 #include "film.h"
@@ -37,11 +38,11 @@ extern "C" {
 #include <libswscale/swscale.h>
 }
 #include <boost/algorithm/string.hpp>
-#include <boost/foreach.hpp>
 #include <iostream>
 
 #include "i18n.h"
 
+
 using std::string;
 using std::cout;
 using std::cerr;
@@ -51,46 +52,45 @@ using boost::optional;
 using dcp::raw_convert;
 using namespace dcpomatic;
 
+
 boost::mutex FFmpeg::_mutex;
 
+
 FFmpeg::FFmpeg (std::shared_ptr<const FFmpegContent> c)
        : _ffmpeg_content (c)
-       , _avio_buffer (0)
-       , _avio_buffer_size (4096)
-       , _avio_context (0)
-       , _format_context (0)
-       , _frame (0)
 {
        setup_general ();
        setup_decoders ();
 }
 
+
 FFmpeg::~FFmpeg ()
 {
        boost::mutex::scoped_lock lm (_mutex);
 
-DCPOMATIC_DISABLE_WARNINGS
-       for (uint32_t i = 0; i < _format_context->nb_streams; ++i) {
-               avcodec_close (_format_context->streams[i]->codec);
+       for (auto& i: _codec_context) {
+               avcodec_free_context (&i);
        }
-DCPOMATIC_ENABLE_WARNINGS
 
        av_frame_free (&_frame);
        avformat_close_input (&_format_context);
 }
 
+
 static int
 avio_read_wrapper (void* data, uint8_t* buffer, int amount)
 {
        return reinterpret_cast<FFmpeg*>(data)->avio_read (buffer, amount);
 }
 
+
 static int64_t
 avio_seek_wrapper (void* data, int64_t offset, int whence)
 {
        return reinterpret_cast<FFmpeg*>(data)->avio_seek (offset, whence);
 }
 
+
 void
 FFmpeg::ffmpeg_log_callback (void* ptr, int level, const char* fmt, va_list vl)
 {
@@ -106,6 +106,7 @@ FFmpeg::ffmpeg_log_callback (void* ptr, int level, const char* fmt, va_list vl)
        dcpomatic_log->log (String::compose ("FFmpeg: %1", str), LogEntry::TYPE_GENERAL);
 }
 
+
 void
 FFmpeg::setup_general ()
 {
@@ -115,7 +116,7 @@ FFmpeg::setup_general ()
        av_log_set_callback (FFmpeg::ffmpeg_log_callback);
 
        _file_group.set_paths (_ffmpeg_content->paths ());
-       _avio_buffer = static_cast<uint8_t*> (wrapped_av_malloc (_avio_buffer_size));
+       _avio_buffer = static_cast<uint8_t*> (wrapped_av_malloc(_avio_buffer_size));
        _avio_context = avio_alloc_context (_avio_buffer, _avio_buffer_size, 0, this, avio_read_wrapper, 0, avio_seek_wrapper);
        if (!_avio_context) {
                throw std::bad_alloc ();
@@ -126,7 +127,7 @@ FFmpeg::setup_general ()
        }
        _format_context->pb = _avio_context;
 
-       AVDictionary* options = 0;
+       AVDictionary* options = nullptr;
        int e = avformat_open_input (&_format_context, 0, 0, &options);
        if (e < 0) {
                throw OpenFileError (_ffmpeg_content->path(0).string(), e, OpenFileError::READ);
@@ -140,10 +141,9 @@ FFmpeg::setup_general ()
 
        optional<int> video_stream_undefined_frame_rate;
 
-DCPOMATIC_DISABLE_WARNINGS
        for (uint32_t i = 0; i < _format_context->nb_streams; ++i) {
-               AVStream* s = _format_context->streams[i];
-               if (s->codec->codec_type == AVMEDIA_TYPE_VIDEO && avcodec_find_decoder(s->codec->codec_id)) {
+               auto s = _format_context->streams[i];
+               if (s->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && avcodec_find_decoder(s->codecpar->codec_id)) {
                        if (s->avg_frame_rate.num > 0 && s->avg_frame_rate.den > 0) {
                                /* This is definitely our video stream */
                                _video_stream = i;
@@ -153,7 +153,6 @@ DCPOMATIC_DISABLE_WARNINGS
                        }
                }
        }
-DCPOMATIC_ENABLE_WARNINGS
 
        /* Files from iTunes sometimes have two video streams, one with the avg_frame_rate.num and .den set
           to zero.  Only use such a stream if there is no alternative.
@@ -191,23 +190,35 @@ DCPOMATIC_ENABLE_WARNINGS
 
        _frame = av_frame_alloc ();
        if (_frame == 0) {
-               throw DecodeError (N_("could not allocate frame"));
+               throw std::bad_alloc ();
        }
 }
 
+
 void
 FFmpeg::setup_decoders ()
 {
        boost::mutex::scoped_lock lm (_mutex);
 
-DCPOMATIC_DISABLE_WARNINGS
+       _codec_context.resize (_format_context->nb_streams);
        for (uint32_t i = 0; i < _format_context->nb_streams; ++i) {
-               AVCodecContext* context = _format_context->streams[i]->codec;
-
-               AVCodec* codec = avcodec_find_decoder (context->codec_id);
+               auto codec = avcodec_find_decoder (_format_context->streams[i]->codecpar->codec_id);
                if (codec) {
+                       auto context = avcodec_alloc_context3 (codec);
+                       if (!context) {
+                               throw std::bad_alloc ();
+                       }
+                       _codec_context[i] = context;
+
+                       int r = avcodec_parameters_to_context (context, _format_context->streams[i]->codecpar);
+                       if (r < 0) {
+                               throw DecodeError ("avcodec_parameters_to_context", "FFmpeg::setup_decoders", r);
+                       }
+
+                       context->thread_count = 8;
+                       context->thread_type = FF_THREAD_FRAME | FF_THREAD_SLICE;
 
-                       AVDictionary* options = 0;
+                       AVDictionary* options = nullptr;
                        /* This option disables decoding of DCA frame footers in our patched version
                           of FFmpeg.  I believe these footers are of no use to us, and they can cause
                           problems when FFmpeg fails to decode them (mantis #352).
@@ -220,37 +231,39 @@ DCPOMATIC_DISABLE_WARNINGS
                        /* Enable following of links in files */
                        av_dict_set_int (&options, "enable_drefs", 1, 0);
 
-                       if (avcodec_open2 (context, codec, &options) < 0) {
-                               throw DecodeError (N_("could not open decoder"));
+                       r = avcodec_open2 (context, codec, &options);
+                       if (r < 0) {
+                               throw DecodeError (N_("avcodec_open2"), N_("FFmpeg::setup_decoders"), r);
                        }
                } else {
                        dcpomatic_log->log (String::compose ("No codec found for stream %1", i), LogEntry::TYPE_WARNING);
                }
        }
-DCPOMATIC_ENABLE_WARNINGS
 }
 
-DCPOMATIC_DISABLE_WARNINGS
+
 AVCodecContext *
 FFmpeg::video_codec_context () const
 {
        if (!_video_stream) {
-               return 0;
+               return nullptr;
        }
 
-       return _format_context->streams[_video_stream.get()]->codec;
+       return _codec_context[_video_stream.get()];
 }
 
+
 AVCodecContext *
 FFmpeg::subtitle_codec_context () const
 {
-       if (!_ffmpeg_content->subtitle_stream ()) {
-               return 0;
+       auto str = _ffmpeg_content->subtitle_stream();
+       if (!str) {
+               return nullptr;
        }
 
-       return _ffmpeg_content->subtitle_stream()->stream(_format_context)->codec;
+       return _codec_context[str->index(_format_context)];
 }
-DCPOMATIC_ENABLE_WARNINGS
+
 
 int
 FFmpeg::avio_read (uint8_t* buffer, int const amount)
@@ -258,6 +271,7 @@ FFmpeg::avio_read (uint8_t* buffer, int const amount)
        return _file_group.read (buffer, amount);
 }
 
+
 int64_t
 FFmpeg::avio_seek (int64_t const pos, int whence)
 {
@@ -268,12 +282,13 @@ FFmpeg::avio_seek (int64_t const pos, int whence)
        return _file_group.seek (pos, whence);
 }
 
+
 FFmpegSubtitlePeriod
-FFmpeg::subtitle_period (AVSubtitle const & sub)
+FFmpeg::subtitle_period (AVPacket const* packet, AVStream const* stream, AVSubtitle const & sub)
 {
-       ContentTime const packet_time = ContentTime::from_seconds (static_cast<double> (sub.pts) / AV_TIME_BASE);
+       auto const packet_time = ContentTime::from_seconds (packet->pts * av_q2d(stream->time_base));
 
-       if (sub.end_display_time == static_cast<uint32_t> (-1)) {
+       if (sub.end_display_time == 0 || sub.end_display_time == static_cast<uint32_t>(-1)) {
                /* End time is not known */
                return FFmpegSubtitlePeriod (packet_time + ContentTime::from_seconds (sub.start_display_time / 1e3));
        }
@@ -284,13 +299,14 @@ FFmpeg::subtitle_period (AVSubtitle const & sub)
                );
 }
 
+
 /** Compute the pts offset to use given a set of audio streams and some video details.
  *  Sometimes these parameters will have just been determined by an Examiner, sometimes
  *  they will have been retrieved from a piece of Content, hence the need for this method
  *  in FFmpeg.
  */
 ContentTime
-FFmpeg::pts_offset (vector<shared_ptr<FFmpegAudioStream> > audio_streams, optional<ContentTime> first_video, double video_frame_rate) const
+FFmpeg::pts_offset (vector<shared_ptr<FFmpegAudioStream>> audio_streams, optional<ContentTime> first_video, double video_frame_rate) const
 {
        /* Audio and video frame PTS values may not start with 0.  We want
           to fiddle them so that:
@@ -308,13 +324,13 @@ FFmpeg::pts_offset (vector<shared_ptr<FFmpegAudioStream> > audio_streams, option
 
        /* First, make one of them start at 0 */
 
-       ContentTime po = ContentTime::min ();
+       auto po = ContentTime::min ();
 
        if (first_video) {
                po = - first_video.get ();
        }
 
-       BOOST_FOREACH (shared_ptr<FFmpegAudioStream> i, audio_streams) {
+       for (auto i: audio_streams) {
                if (i->first_audio) {
                        po = max (po, - i->first_audio.get ());
                }
@@ -330,7 +346,7 @@ FFmpeg::pts_offset (vector<shared_ptr<FFmpegAudioStream> > audio_streams, option
 
        /* Now adjust so that the video pts starts on a frame */
        if (first_video) {
-               ContentTime const fvc = first_video.get() + po;
+               auto const fvc = first_video.get() + po;
                po += fvc.ceil (video_frame_rate) - fvc;
        }