*/
+
/** @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>
#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;
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 = {};
/* 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;
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;
}
}
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;
}
void
FFmpegDecoder::process_audio_frame (shared_ptr<FFmpegAudioStream> stream)
{
- auto data = deinterleave_audio (_frame);
+ auto frame = audio_frame (stream);
+ auto data = deinterleave_audio (frame);
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).
}
} else {
ct = ContentTime::from_seconds (
- _frame->best_effort_timestamp *
+ frame->best_effort_timestamp *
av_q2d (stream->stream(_format_context)->time_base))
+ _pts_offset;
}
to_string(ct),
data->frames(),
stream->id(),
- _frame->best_effort_timestamp,
+ frame->best_effort_timestamp,
av_q2d(stream->stream(_format_context)->time_base),
to_string(_pts_offset)
);
}
auto context = _codec_context[stream->index(_format_context)];
+ auto frame = audio_frame (stream);
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 */
return;
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);
+
+ return true;
+}
- /* We assume we'll only get one frame here, which I think is safe */
+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) {
LOG_WARNING_NC ("Dropping frame without PTS");
}
}
-
- return true;
}
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;
}
/* 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) {
_have_current_subtitle = true;
}
+ ContentBitmapText bitmap_text(from);
for (unsigned int i = 0; i < sub.num_rects; ++i) {
auto const rect = sub.rects[i];
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";
}
}
+ if (!bitmap_text.subs.empty()) {
+ only_text()->emit_bitmap_start(bitmap_text);
+ }
+
if (_current_subtitle_to) {
only_text()->emit_stop (*_current_subtitle_to);
}
}
-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 */
static_cast<double>(rect->h) / target_height
);
- only_text()->emit_bitmap_start (from, image, scaled_rect);
+ return { image, scaled_rect };
}