/*
- 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.
*/
+
+#include "compose.hpp"
+#include "config.h"
+#include "dcpomatic_log.h"
+#include "digester.h"
+#include "exceptions.h"
#include "ffmpeg.h"
+#include "ffmpeg_audio_stream.h"
#include "ffmpeg_content.h"
+#include "ffmpeg_subtitle_stream.h"
#include "film.h"
-#include "exceptions.h"
-#include "util.h"
#include "log.h"
-#include "dcpomatic_log.h"
-#include "ffmpeg_subtitle_stream.h"
-#include "ffmpeg_audio_stream.h"
-#include "decrypted_ecinema_kdm.h"
-#include "digester.h"
-#include "compose.hpp"
-#include "config.h"
+#include "memory_util.h"
+#include "util.h"
#include <dcp/raw_convert.h>
extern "C" {
#include <libavcodec/avcodec.h>
#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;
using std::vector;
-using boost::shared_ptr;
+using std::shared_ptr;
using boost::optional;
using dcp::raw_convert;
using namespace dcpomatic;
+
boost::mutex FFmpeg::_mutex;
-FFmpeg::FFmpeg (boost::shared_ptr<const FFmpegContent> c)
+
+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);
- 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);
+ }
+
+ av_frame_free (&_video_frame);
+ for (auto& audio_frame: _audio_frame) {
+ av_frame_free (&audio_frame.second);
}
- 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)
-{
- if (level > AV_LOG_WARNING) {
- return;
- }
-
- char line[1024];
- static int prefix = 0;
- av_log_format_line (ptr, level, fmt, vl, line, sizeof (line), &prefix);
- string str (line);
- boost::algorithm::trim (str);
- dcpomatic_log->log (String::compose ("FFmpeg: %1", str), LogEntry::TYPE_GENERAL);
-}
void
FFmpeg::setup_general ()
{
- /* This might not work too well in some cases of multiple FFmpeg decoders,
- but it's probably good enough.
- */
- 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 ();
+ }
_format_context = avformat_alloc_context ();
- _format_context->pb = _avio_context;
-
- AVDictionary* options = 0;
- /* These durations are in microseconds, and represent how far into the content file
- we will look for streams.
- */
- av_dict_set (&options, "analyzeduration", raw_convert<string> (5 * 60 * 1000000).c_str(), 0);
- av_dict_set (&options, "probesize", raw_convert<string> (5 * 60 * 1000000).c_str(), 0);
- if (_ffmpeg_content->kdm()) {
- DecryptedECinemaKDM kdm (_ffmpeg_content->kdm().get(), Config::instance()->decryption_chain()->key().get());
- av_dict_set (&options, "decryption_key", kdm.key().hex().c_str(), 0);
+ if (!_format_context) {
+ throw std::bad_alloc ();
}
+ _format_context->pb = _avio_context;
+ AVDictionary* options = nullptr;
int e = avformat_open_input (&_format_context, 0, 0, &options);
if (e < 0) {
- throw OpenFileError (_ffmpeg_content->path(0).string(), e, true);
+ throw OpenFileError (_ffmpeg_content->path(0).string(), e, OpenFileError::READ);
}
if (avformat_find_stream_info (_format_context, 0) < 0) {
optional<int> video_stream_undefined_frame_rate;
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)) {
+ auto const frame_rate = av_q2d(s->avg_frame_rate);
+ if (frame_rate < 1 || frame_rate > 1000) {
+ /* Ignore video streams with crazy frame rates. These are usually things like album art on MP3s. */
+ continue;
+ }
if (s->avg_frame_rate.num > 0 && s->avg_frame_rate.den > 0) {
/* This is definitely our video stream */
_video_stream = i;
}
}
- _frame = av_frame_alloc ();
- if (_frame == 0) {
- throw DecodeError (N_("could not allocate frame"));
+ _video_frame = av_frame_alloc ();
+ if (_video_frame == nullptr) {
+ throw std::bad_alloc ();
}
}
+
void
FFmpeg::setup_decoders ()
{
boost::mutex::scoped_lock lm (_mutex);
+ _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);
+ }
- AVDictionary* options = 0;
+ context->thread_count = 8;
+ context->thread_type = FF_THREAD_FRAME | FF_THREAD_SLICE;
+
+ 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).
/* 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);
}
}
+
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)];
}
+
int
FFmpeg::avio_read (uint8_t* buffer, int const amount)
{
- return _file_group.read (buffer, amount);
+ auto result = _file_group.read(buffer, amount);
+ if (result.eof && result.bytes_read == 0) {
+ return AVERROR_EOF;
+ }
+ return result.bytes_read;
}
+
int64_t
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));
+ auto const start = packet_time + ContentTime::from_seconds(sub.start_display_time / 1e3);
- if (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));
+ if (sub.end_display_time == 0 || sub.end_display_time == static_cast<uint32_t>(-1)) {
+ /* End time is not in the AVSubtitle; perhaps we can use the AVPacket's duration */
+ if (packet->duration) {
+ return FFmpegSubtitlePeriod(start, start + ContentTime::from_seconds(packet->duration * av_q2d(stream->time_base)));
+ } else {
+ return FFmpegSubtitlePeriod(start);
+ }
}
- return FFmpegSubtitlePeriod (
- packet_time + ContentTime::from_seconds (sub.start_display_time / 1e3),
- packet_time + ContentTime::from_seconds (sub.end_display_time / 1e3)
- );
+ return FFmpegSubtitlePeriod (start, packet_time + ContentTime::from_seconds(sub.end_display_time / 1e3));
}
+
/** 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:
/* 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 ());
}
/* 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;
}
return po;
}
+
+
+AVFrame *
+FFmpeg::audio_frame (shared_ptr<const FFmpegAudioStream> stream)
+{
+ auto iter = _audio_frame.find(stream);
+ if (iter != _audio_frame.end()) {
+ return iter->second;
+ }
+
+ auto frame = av_frame_alloc ();
+ if (frame == nullptr) {
+ throw std::bad_alloc();
+ }
+
+ _audio_frame[stream] = frame;
+ return frame;
+}
+