Fix typo in log message.
[dcpomatic.git] / src / lib / ffmpeg_decoder.cc
index 3202dcd3e7b11750213ef0284fa584e198b382d5..ba96d71ff5da78a9fe084d1bd56bbb2d82be56d4 100644 (file)
 
 */
 
+
 /** @file  src/ffmpeg_decoder.cc
  *  @brief A decoder using FFmpeg to decode content.
  */
 
-#include "filter.h"
-#include "exceptions.h"
-#include "image.h"
-#include "util.h"
-#include "log.h"
+
+#include "audio_buffers.h"
+#include "audio_content.h"
+#include "audio_decoder.h"
+#include "compose.hpp"
 #include "dcpomatic_log.h"
-#include "ffmpeg_decoder.h"
-#include "text_decoder.h"
+#include "exceptions.h"
 #include "ffmpeg_audio_stream.h"
-#include "ffmpeg_subtitle_stream.h"
-#include "video_filter_graph.h"
-#include "audio_buffers.h"
 #include "ffmpeg_content.h"
-#include "raw_image_proxy.h"
-#include "video_decoder.h"
+#include "ffmpeg_decoder.h"
+#include "ffmpeg_subtitle_stream.h"
 #include "film.h"
-#include "audio_decoder.h"
-#include "compose.hpp"
-#include "text_content.h"
-#include "audio_content.h"
+#include "filter.h"
 #include "frame_interval_checker.h"
+#include "image.h"
+#include "log.h"
+#include "raw_image_proxy.h"
+#include "text_content.h"
+#include "text_decoder.h"
+#include "util.h"
+#include "video_decoder.h"
+#include "video_filter_graph.h"
 #include <dcp/subtitle_string.h>
 #include <sub/ssa_reader.h>
 #include <sub/subtitle.h>
@@ -52,28 +54,22 @@ extern "C" {
 #include <libavformat/avformat.h>
 }
 #include <boost/algorithm/string.hpp>
-#include <vector>
 #include <iomanip>
 #include <iostream>
+#include <vector>
 #include <stdint.h>
 
 #include "i18n.h"
 
+
 using std::cout;
-using std::string;
-using std::vector;
-using std::list;
+using std::dynamic_pointer_cast;
+using std::make_shared;
 using std::min;
-using std::pair;
-using std::max;
-using std::map;
 using std::shared_ptr;
-using std::make_shared;
-using std::make_pair;
-using boost::is_any_of;
-using boost::split;
+using std::string;
+using std::vector;
 using boost::optional;
-using std::dynamic_pointer_cast;
 using dcp::Size;
 using namespace dcpomatic;
 
@@ -86,7 +82,7 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> film, shared_ptr<const FFmp
                video = make_shared<VideoDecoder>(this, c);
                _pts_offset = pts_offset (c->ffmpeg_audio_streams(), c->first_video(), c->active_video_frame_rate(film));
                /* It doesn't matter what size or pixel format this is, it just needs to be black */
-               _black_image.reset (new Image (AV_PIX_FMT_RGB24, dcp::Size (128, 128), true));
+               _black_image = make_shared<Image>(AV_PIX_FMT_RGB24, dcp::Size (128, 128), Image::Alignment::PADDED);
                _black_image->make_black ();
        } else {
                _pts_offset = {};
@@ -97,8 +93,10 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> film, shared_ptr<const FFmp
        }
 
        if (c->only_text()) {
-               /* XXX: this time here should be the time of the first subtitle, not 0 */
-               text.push_back (make_shared<TextDecoder>(this, c->only_text(), ContentTime()));
+               text.push_back (make_shared<TextDecoder>(this, c->only_text()));
+               /* XXX: we should be calling maybe_set_position() on this TextDecoder, but we can't easily find
+                * the time of the first subtitle at this point.
+                */
        }
 
        for (auto i: c->ffmpeg_audio_streams()) {
@@ -126,7 +124,7 @@ FFmpegDecoder::flush ()
                        /* EOF can happen if we've already sent a flush packet */
                        throw DecodeError (N_("avcodec_send_packet"), N_("FFmpegDecoder::flush"), r);
                }
-               r = avcodec_receive_frame (context, _frame);
+               r = avcodec_receive_frame (context, audio_frame(i));
                if (r >= 0) {
                        process_audio_frame (i);
                        did_something = true;
@@ -148,7 +146,7 @@ FFmpegDecoder::flush ()
                auto const f = full_length.frames_round (vfr);
                auto 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);
+                       video->emit (film(), make_shared<const RawImageProxy>(_black_image), v);
                        ++v;
                }
        }
@@ -427,12 +425,6 @@ FFmpegDecoder::seek (ContentTime time, bool accurate)
        for (auto& i: _next_time) {
                i.second = boost::optional<dcpomatic::ContentTime>();
        }
-
-       /* We find that we get some errors from av_send_packet after a seek.  Perhaps we should ignore
-        * all of them (which seems risky), or perhaps we should have some proper fix.  But instead
-        * let's ignore the next 2 errors.
-        */
-       _errors_to_ignore = 2;
 }
 
 
@@ -457,10 +449,13 @@ FFmpegDecoder::audio_stream_from_index (int index) const
 void
 FFmpegDecoder::process_audio_frame (shared_ptr<FFmpegAudioStream> stream)
 {
-       auto data = deinterleave_audio (_frame);
+       auto frame = audio_frame (stream);
+       auto data = deinterleave_audio (frame);
+
+       auto const time_base = stream->stream(_format_context)->time_base;
 
        ContentTime ct;
-       if (_frame->pts == AV_NOPTS_VALUE) {
+       if (frame->pts == AV_NOPTS_VALUE) {
                /* In some streams we see not every frame coming through with a timestamp; for those
                   that have AV_NOPTS_VALUE we need to work out the timestamp ourselves.  This is
                   particularly noticeable with TrueHD streams (see #1111).
@@ -470,9 +465,17 @@ FFmpegDecoder::process_audio_frame (shared_ptr<FFmpegAudioStream> stream)
                }
        } else {
                ct = ContentTime::from_seconds (
-                       _frame->best_effort_timestamp *
-                       av_q2d (stream->stream(_format_context)->time_base))
+                       frame->best_effort_timestamp *
+                       av_q2d(time_base))
                        + _pts_offset;
+               LOG_DEBUG_PLAYER(
+                       "Process audio with timestamp %1 (BET %2, timebase %3/%4, (PTS offset %5)",
+                       to_string(ct),
+                       frame->best_effort_timestamp,
+                       time_base.num,
+                       time_base.den,
+                       to_string(_pts_offset)
+                       );
        }
 
        _next_time[stream] = ct + ContentTime::from_frames(data->frames(), stream->frame_rate());
@@ -491,8 +494,8 @@ FFmpegDecoder::process_audio_frame (shared_ptr<FFmpegAudioStream> stream)
                        to_string(ct),
                        data->frames(),
                        stream->id(),
-                       _frame->best_effort_timestamp,
-                       av_q2d(stream->stream(_format_context)->time_base),
+                       frame->best_effort_timestamp,
+                       av_q2d(time_base),
                        to_string(_pts_offset)
                        );
        }
@@ -513,25 +516,18 @@ FFmpegDecoder::decode_and_process_audio_packet (AVPacket* packet)
        }
 
        auto context = _codec_context[stream->index(_format_context)];
+       auto frame = audio_frame (stream);
 
+       LOG_DEBUG_PLAYER("Send audio packet on stream %1", stream->index(_format_context));
        int r = avcodec_send_packet (context, packet);
        if (r < 0) {
-               /* We could cope with AVERROR(EAGAIN) and re-send the packet but I think it should never happen.
-                * Likewise I think AVERROR_EOF should not happen.
-                */
-               if (_errors_to_ignore > 0) {
-                       /* We see errors here after a seek, which is hopefully to be nothing to worry about */
-                       --_errors_to_ignore;
-                       LOG_GENERAL("Ignoring error %1 avcodec_send_packet after seek; will ignore %2 more", r, _errors_to_ignore);
-                       return;
-               }
-               throw DecodeError (N_("avcodec_send_packet"), N_("FFmpegDecoder::decode_and_process_audio_packet"), r);
+               LOG_WARNING("avcodec_send_packet returned %1 for an audio packet", r);
        }
-
        while (r >= 0) {
-               r = avcodec_receive_frame (context, _frame);
+               r = avcodec_receive_frame (context, frame);
                if (r == AVERROR(EAGAIN)) {
                        /* More input is required */
+                       LOG_DEBUG_PLAYER_NC("EAGAIN after trying to receive audio frame");
                        return;
                }
 
@@ -551,46 +547,58 @@ FFmpegDecoder::decode_and_process_video_packet (AVPacket* packet)
 
        auto context = video_codec_context();
 
-       int r = avcodec_send_packet (context, packet);
-       if (r < 0 && !(r == AVERROR_EOF && !packet)) {
-               /* We could cope with AVERROR(EAGAIN) and re-send the packet but I think it should never happen.
-                * AVERROR_EOF can happen during flush if we've already sent a flush packet.
-                */
-               throw DecodeError (N_("avcodec_send_packet"), N_("FFmpegDecoder::decode_and_process_video_packet"), r);
-       }
+       bool pending = false;
+       do {
+               int r = avcodec_send_packet (context, packet);
+               if (r < 0) {
+                       LOG_WARNING("avcodec_send_packet returned %1 for a video packet", r);
+               }
 
-       r = avcodec_receive_frame (context, _frame);
-       if (r == AVERROR(EAGAIN) || r == AVERROR_EOF || (r < 0 && !packet)) {
-               /* More input is required, no more frames are coming, or we are flushing and there was
-                * some error which we just want to ignore.
-                */
-               return false;
-       } else if (r < 0) {
-               throw DecodeError (N_("avcodec_receive_frame"), N_("FFmpeg::decode_and_process_video_packet"), r);
-       }
+               /* EAGAIN means we should call avcodec_receive_frame and then re-send the same packet */
+               pending = r == AVERROR(EAGAIN);
+
+               while (true) {
+                       r = avcodec_receive_frame (context, _video_frame);
+                       if (r == AVERROR(EAGAIN) || r == AVERROR_EOF || (r < 0 && !packet)) {
+                               /* More input is required, no more frames are coming, or we are flushing and there was
+                                * some error which we just want to ignore.
+                                */
+                               return false;
+                       } else if (r < 0) {
+                               throw DecodeError (N_("avcodec_receive_frame"), N_("FFmpeg::decode_and_process_video_packet"), r);
+                       }
+
+                       process_video_frame ();
+               }
+       } while (pending);
 
-       /* We assume we'll only get one frame here, which I think is safe */
+       return true;
+}
 
+
+void
+FFmpegDecoder::process_video_frame ()
+{
        boost::mutex::scoped_lock lm (_filter_graphs_mutex);
 
        shared_ptr<VideoFilterGraph> graph;
 
        auto i = _filter_graphs.begin();
-       while (i != _filter_graphs.end() && !(*i)->can_process (dcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) {
+       while (i != _filter_graphs.end() && !(*i)->can_process(dcp::Size(_video_frame->width, _video_frame->height), (AVPixelFormat) _video_frame->format)) {
                ++i;
        }
 
        if (i == _filter_graphs.end ()) {
                dcp::Fraction vfr (lrint(_ffmpeg_content->video_frame_rate().get() * 1000), 1000);
-               graph = make_shared<VideoFilterGraph>(dcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format, vfr);
+               graph = make_shared<VideoFilterGraph>(dcp::Size(_video_frame->width, _video_frame->height), (AVPixelFormat) _video_frame->format, vfr);
                graph->setup (_ffmpeg_content->filters ());
                _filter_graphs.push_back (graph);
-               LOG_GENERAL (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format);
+               LOG_GENERAL (N_("New graph for %1x%2, pixel format %3"), _video_frame->width, _video_frame->height, _video_frame->format);
        } else {
                graph = *i;
        }
 
-       auto images = graph->process (_frame);
+       auto images = graph->process (_video_frame);
 
        for (auto const& i: images) {
 
@@ -608,8 +616,6 @@ FFmpegDecoder::decode_and_process_video_packet (AVPacket* packet)
                        LOG_WARNING_NC ("Dropping frame without PTS");
                }
        }
-
-       return true;
 }
 
 
@@ -622,12 +628,14 @@ FFmpegDecoder::decode_and_process_subtitle_packet (AVPacket* packet)
                return;
        }
 
+       auto sub_period = subtitle_period (packet, ffmpeg_content()->subtitle_stream()->stream(_format_context), sub);
+
        /* Stop any current subtitle, either at the time it was supposed to stop, or now if now is sooner */
        if (_have_current_subtitle) {
                if (_current_subtitle_to) {
-                       only_text()->emit_stop (min(*_current_subtitle_to, subtitle_period(sub).from + _pts_offset));
+                       only_text()->emit_stop (min(*_current_subtitle_to, sub_period.from + _pts_offset));
                } else {
-                       only_text()->emit_stop (subtitle_period(sub).from + _pts_offset);
+                       only_text()->emit_stop (sub_period.from + _pts_offset);
                }
                _have_current_subtitle = false;
        }
@@ -641,7 +649,6 @@ FFmpegDecoder::decode_and_process_subtitle_packet (AVPacket* packet)
        /* Subtitle PTS (within the source, not taking into account any of the
           source that we may have chopped off for the DCP).
        */
-       auto sub_period = subtitle_period (sub);
        ContentTime from;
        from = sub_period.from + _pts_offset;
        if (sub_period.to) {
@@ -651,6 +658,7 @@ FFmpegDecoder::decode_and_process_subtitle_packet (AVPacket* packet)
                _have_current_subtitle = true;
        }
 
+       ContentBitmapText bitmap_text(from);
        for (unsigned int i = 0; i < sub.num_rects; ++i) {
                auto const rect = sub.rects[i];
 
@@ -658,7 +666,7 @@ FFmpegDecoder::decode_and_process_subtitle_packet (AVPacket* packet)
                case SUBTITLE_NONE:
                        break;
                case SUBTITLE_BITMAP:
-                       process_bitmap_subtitle (rect, from);
+                       bitmap_text.subs.push_back(process_bitmap_subtitle(rect));
                        break;
                case SUBTITLE_TEXT:
                        cout << "XXX: SUBTITLE_TEXT " << rect->text << "\n";
@@ -669,6 +677,10 @@ FFmpegDecoder::decode_and_process_subtitle_packet (AVPacket* packet)
                }
        }
 
+       if (!bitmap_text.subs.empty()) {
+               only_text()->emit_bitmap_start(bitmap_text);
+       }
+
        if (_current_subtitle_to) {
                only_text()->emit_stop (*_current_subtitle_to);
        }
@@ -677,13 +689,13 @@ FFmpegDecoder::decode_and_process_subtitle_packet (AVPacket* packet)
 }
 
 
-void
-FFmpegDecoder::process_bitmap_subtitle (AVSubtitleRect const * rect, ContentTime from)
+BitmapText
+FFmpegDecoder::process_bitmap_subtitle (AVSubtitleRect const * rect)
 {
        /* Note BGRA is expressed little-endian, so the first byte in the word is B, second
           G, third R, fourth A.
        */
-       auto image = make_shared<Image>(AV_PIX_FMT_BGRA, dcp::Size (rect->w, rect->h), true);
+       auto image = make_shared<Image>(AV_PIX_FMT_BGRA, dcp::Size (rect->w, rect->h), Image::Alignment::PADDED);
 
 #ifdef DCPOMATIC_HAVE_AVSUBTITLERECT_PICT
        /* Start of the first line in the subtitle */
@@ -761,7 +773,7 @@ FFmpegDecoder::process_bitmap_subtitle (AVSubtitleRect const * rect, ContentTime
                static_cast<double>(rect->h) / target_height
                );
 
-       only_text()->emit_bitmap_start (from, image, scaled_rect);
+       return { image, scaled_rect };
 }
 
 
@@ -791,7 +803,8 @@ FFmpegDecoder::process_ass_subtitle (string ass, ContentTime from)
                base,
                text,
                _ffmpeg_content->video->size().width,
-               _ffmpeg_content->video->size().height
+               _ffmpeg_content->video->size().height,
+               sub::Colour(1, 1, 1)
                );
 
        for (auto const& i: sub::collect<vector<sub::Subtitle>>(raw)) {