*/
+
/** @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;
FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> film, shared_ptr<const FFmpegContent> c, bool fast)
: FFmpeg (c)
, Decoder (film)
- , _have_current_subtitle (false)
{
if (c->video && c->video->use()) {
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 = {};
bool did_something = false;
if (video) {
- AVPacket packet;
- av_init_packet (&packet);
- packet.data = nullptr;
- packet.size = 0;
- if (decode_and_process_video_packet(&packet)) {
+ if (decode_and_process_video_packet(nullptr)) {
did_something = true;
}
}
for (auto i: ffmpeg_content()->ffmpeg_audio_streams()) {
- AVPacket packet;
- av_init_packet (&packet);
- packet.data = nullptr;
- packet.size = 0;
- auto result = decode_audio_packet (i, &packet);
- if (result.second) {
+ auto context = _codec_context[i->index(_format_context)];
+ int r = avcodec_send_packet (context, nullptr);
+ if (r < 0 && r != AVERROR_EOF) {
+ /* 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, audio_frame(i));
+ if (r >= 0) {
process_audio_frame (i);
did_something = true;
}
* Only the first buffer will be used for non-planar data, otherwise there will be one per channel.
*/
shared_ptr<AudioBuffers>
-FFmpegDecoder::deinterleave_audio (shared_ptr<FFmpegAudioStream> stream) const
+FFmpegDecoder::deinterleave_audio (AVFrame* frame)
{
- DCPOMATIC_ASSERT (bytes_per_audio_sample (stream));
+ auto format = static_cast<AVSampleFormat>(frame->format);
-DCPOMATIC_DISABLE_WARNINGS
- int const size = av_samples_get_buffer_size (
- 0, stream->stream(_format_context)->codec->channels, _frame->nb_samples, audio_sample_format (stream), 1
- );
-DCPOMATIC_ENABLE_WARNINGS
-
- /* XXX: can't we just use _frame->nb_samples directly here? */
/* XXX: can't we use swr_convert() to do the format conversion? */
- /* Deinterleave and convert to float */
-
- /* 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 (stream);
- int const channels = stream->channels();
- int const frames = total_samples / channels;
+ int const channels = frame->channels;
+ int const frames = frame->nb_samples;
+ int const total_samples = frames * channels;
auto audio = make_shared<AudioBuffers>(channels, frames);
auto data = audio->data();
- switch (audio_sample_format (stream)) {
+ switch (format) {
case AV_SAMPLE_FMT_U8:
{
- uint8_t* p = reinterpret_cast<uint8_t *> (_frame->data[0]);
+ auto p = reinterpret_cast<uint8_t *> (frame->data[0]);
int sample = 0;
int channel = 0;
for (int i = 0; i < total_samples; ++i) {
case AV_SAMPLE_FMT_S16:
{
- int16_t* p = reinterpret_cast<int16_t *> (_frame->data[0]);
+ auto p = reinterpret_cast<int16_t *> (frame->data[0]);
int sample = 0;
int channel = 0;
for (int i = 0; i < total_samples; ++i) {
case AV_SAMPLE_FMT_S16P:
{
- int16_t** p = reinterpret_cast<int16_t **> (_frame->data);
+ auto p = reinterpret_cast<int16_t **> (frame->data);
for (int i = 0; i < channels; ++i) {
for (int j = 0; j < frames; ++j) {
data[i][j] = static_cast<float>(p[i][j]) / (1 << 15);
case AV_SAMPLE_FMT_S32:
{
- int32_t* p = reinterpret_cast<int32_t *> (_frame->data[0]);
+ auto p = reinterpret_cast<int32_t *> (frame->data[0]);
int sample = 0;
int channel = 0;
for (int i = 0; i < total_samples; ++i) {
case AV_SAMPLE_FMT_S32P:
{
- int32_t** p = reinterpret_cast<int32_t **> (_frame->data);
+ auto p = reinterpret_cast<int32_t **> (frame->data);
for (int i = 0; i < channels; ++i) {
for (int j = 0; j < frames; ++j) {
data[i][j] = static_cast<float>(p[i][j]) / 2147483648;
case AV_SAMPLE_FMT_FLT:
{
- float* p = reinterpret_cast<float*> (_frame->data[0]);
+ auto p = reinterpret_cast<float*> (frame->data[0]);
int sample = 0;
int channel = 0;
for (int i = 0; i < total_samples; ++i) {
case AV_SAMPLE_FMT_FLTP:
{
- float** p = reinterpret_cast<float**> (_frame->data);
- DCPOMATIC_ASSERT (_frame->channels <= channels);
- /* Sometimes there aren't as many channels in the _frame as in the stream */
- for (int i = 0; i < _frame->channels; ++i) {
+ auto p = reinterpret_cast<float**> (frame->data);
+ DCPOMATIC_ASSERT (frame->channels <= channels);
+ /* Sometimes there aren't as many channels in the frame as in the stream */
+ for (int i = 0; i < frame->channels; ++i) {
memcpy (data[i], p[i], frames * sizeof(float));
}
- for (int i = _frame->channels; i < channels; ++i) {
+ for (int i = frame->channels; i < channels; ++i) {
audio->make_silent (i);
}
}
break;
default:
- throw DecodeError (String::compose (_("Unrecognised audio sample format (%1)"), static_cast<int> (audio_sample_format (stream))));
+ throw DecodeError (String::compose(_("Unrecognised audio sample format (%1)"), static_cast<int>(format)));
}
return audio;
AVSampleFormat
FFmpegDecoder::audio_sample_format (shared_ptr<FFmpegAudioStream> stream) const
{
-DCPOMATIC_DISABLE_WARNINGS
- return stream->stream (_format_context)->codec->sample_fmt;
-DCPOMATIC_ENABLE_WARNINGS
+ return static_cast<AVSampleFormat>(stream->stream(_format_context)->codecpar->format);
}
avcodec_flush_buffers (video_codec_context());
}
-DCPOMATIC_DISABLE_WARNINGS
for (auto i: ffmpeg_content()->ffmpeg_audio_streams()) {
- avcodec_flush_buffers (i->stream(_format_context)->codec);
+ avcodec_flush_buffers (_codec_context[i->index(_format_context)]);
}
-DCPOMATIC_ENABLE_WARNINGS
if (subtitle_codec_context ()) {
avcodec_flush_buffers (subtitle_codec_context ());
void
FFmpegDecoder::process_audio_frame (shared_ptr<FFmpegAudioStream> stream)
{
- auto data = deinterleave_audio (stream);
+ 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)
);
}
-pair<int, bool>
-FFmpegDecoder::decode_audio_packet (shared_ptr<FFmpegAudioStream> stream, AVPacket* packet)
-{
- int frame_finished;
- DCPOMATIC_DISABLE_WARNINGS
- int decode_result = avcodec_decode_audio4 (stream->stream(_format_context)->codec, _frame, &frame_finished, packet);
- DCPOMATIC_ENABLE_WARNINGS
- if (decode_result < 0) {
- /* 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);
- }
- return make_pair(decode_result, frame_finished);
-}
-
-
void
FFmpegDecoder::decode_and_process_audio_packet (AVPacket* packet)
{
return;
}
- /* Audio packets can contain multiple frames, so we may have to call avcodec_decode_audio4
- several times. Make a simple copy so we can alter data and size.
- */
- AVPacket copy_packet = *packet;
-
- while (copy_packet.size > 0) {
- auto result = decode_audio_packet (stream, ©_packet);
- if (result.first < 0) {
- /* 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.
+ auto context = _codec_context[stream->index(_format_context)];
+ auto frame = audio_frame (stream);
- Returning from the method here caused mantis #352.
- */
- }
-
- if (result.second) {
- process_audio_frame (stream);
- }
-
- if (result.first) {
- break;
+ int r = avcodec_send_packet (context, packet);
+ if (r < 0) {
+ LOG_WARNING("avcodec_send_packet returned %1 for an audio packet", r);
+ }
+ while (r >= 0) {
+ r = avcodec_receive_frame (context, frame);
+ if (r == AVERROR(EAGAIN)) {
+ /* More input is required */
+ return;
}
- copy_packet.data += result.first;
- copy_packet.size -= result.first;
+ /* We choose to be relaxed here about other errors; it seems that there may be valid
+ * data to decode even if an error occurred. #352 may be related (though this was
+ * when we were using an old version of the FFmpeg API).
+ */
+ process_audio_frame (stream);
}
}
{
DCPOMATIC_ASSERT (_video_stream);
- int frame_finished;
-DCPOMATIC_DISABLE_WARNINGS
- if (avcodec_decode_video2 (video_codec_context(), _frame, &frame_finished, packet) < 0 || !frame_finished) {
- return false;
- }
-DCPOMATIC_ENABLE_WARNINGS
+ auto context = video_codec_context();
+
+ 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);
+ }
+
+ /* 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;
+}
+
+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;
}
if (sub.num_rects <= 0) {
/* Nothing new in this subtitle */
+ avsubtitle_free (&sub);
return;
}
/* 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) {
/* 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 */