X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Flib%2Fffmpeg.cc;h=8fa03fdffb552f3ddbd49042df0d41d084d5d6ed;hb=1858190cff2f960f3d1f0a5cc02c69da86088f5b;hp=5fc33348923c85d584af16fb980c6f5f50ad2dd1;hpb=85c65bd422742813992686c17a5e1b718cc3c449;p=dcpomatic.git diff --git a/src/lib/ffmpeg.cc b/src/lib/ffmpeg.cc index 5fc333489..8fa03fdff 100644 --- a/src/lib/ffmpeg.cc +++ b/src/lib/ffmpeg.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2013 Carl Hetherington + Copyright (C) 2013-2015 Carl Hetherington This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,26 +17,32 @@ */ +#include "ffmpeg.h" +#include "ffmpeg_content.h" +#include "film.h" +#include "exceptions.h" +#include "util.h" +#include "raw_convert.h" +#include "log.h" +#include "ffmpeg_subtitle_stream.h" +#include "compose.hpp" extern "C" { #include #include #include -#include } -#include "ffmpeg.h" -#include "ffmpeg_content.h" -#include "exceptions.h" -#include "util.h" +#include +#include #include "i18n.h" using std::string; using std::cout; -using std::stringstream; +using std::cerr; using boost::shared_ptr; -using boost::lexical_cast; boost::mutex FFmpeg::_mutex; +boost::weak_ptr FFmpeg::_ffmpeg_log; FFmpeg::FFmpeg (boost::shared_ptr c) : _ffmpeg_content (c) @@ -48,8 +54,7 @@ FFmpeg::FFmpeg (boost::shared_ptr c) , _video_stream (-1) { setup_general (); - setup_video (); - setup_audio (); + setup_decoders (); } FFmpeg::~FFmpeg () @@ -57,14 +62,10 @@ FFmpeg::~FFmpeg () boost::mutex::scoped_lock lm (_mutex); for (uint32_t i = 0; i < _format_context->nb_streams; ++i) { - AVCodecContext* context = _format_context->streams[i]->codec; - if (context->codec_type == AVMEDIA_TYPE_VIDEO || context->codec_type == AVMEDIA_TYPE_AUDIO) { - avcodec_close (context); - } + avcodec_close (_format_context->streams[i]->codec); } av_frame_free (&_frame); - avformat_close_input (&_format_context); } @@ -80,24 +81,50 @@ avio_seek_wrapper (void* data, int64_t offset, int whence) return reinterpret_cast(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); + shared_ptr log = _ffmpeg_log.lock (); + if (log) { + string str (line); + boost::algorithm::trim (str); + log->log (String::compose ("FFmpeg: %1", str), Log::TYPE_GENERAL); + } else { + cerr << line; + } +} + void FFmpeg::setup_general () { av_register_all (); + /* This might not work too well in some cases of multiple FFmpeg decoders, + but it's probably good enough. + */ + _ffmpeg_log = _ffmpeg_content->film()->log (); + av_log_set_callback (FFmpeg::ffmpeg_log_callback); + _file_group.set_paths (_ffmpeg_content->paths ()); _avio_buffer = static_cast (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); _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", lexical_cast (5 * 60 * 1e6).c_str(), 0); - av_dict_set (&options, "probesize", lexical_cast (5 * 60 * 1e6).c_str(), 0); - + av_dict_set (&options, "analyzeduration", raw_convert (5 * 60 * 1000000).c_str(), 0); + av_dict_set (&options, "probesize", raw_convert (5 * 60 * 1000000).c_str(), 0); + if (avformat_open_input (&_format_context, 0, 0, &options) < 0) { throw OpenFileError (_ffmpeg_content->path(0).string ()); } @@ -108,29 +135,48 @@ FFmpeg::setup_general () /* Find video stream */ + int video_stream_undefined_frame_rate = -1; + 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) { - _video_stream = i; + if (s->avg_frame_rate.num > 0 && s->avg_frame_rate.den > 0) { + /* This is definitely our video stream */ + _video_stream = i; + } else { + /* This is our video stream if we don't get a better offer */ + video_stream_undefined_frame_rate = i; + } } } + /* 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. + */ + if (_video_stream == -1 && video_stream_undefined_frame_rate != -1) { + _video_stream = video_stream_undefined_frame_rate; + } + if (_video_stream < 0) { throw DecodeError (N_("could not find video stream")); } - /* Hack: if the AVStreams have zero IDs, put some in. We - use the IDs so that we can cope with VOBs, in which streams + /* Hack: if the AVStreams have duplicate IDs, replace them with our + own. We use the IDs so that we can cope with VOBs, in which streams move about in index but remain with the same ID in different - VOBs. However, some files have all-zero IDs, hence this hack. + VOBs. However, some files have duplicate IDs, hence this hack. */ - - uint32_t i = 0; - while (i < _format_context->nb_streams && _format_context->streams[i]->id == 0) { - ++i; + + bool duplicates = false; + for (uint32_t i = 0; i < _format_context->nb_streams; ++i) { + for (uint32_t j = i + 1; j < _format_context->nb_streams; ++j) { + if (_format_context->streams[i]->id == _format_context->streams[j]->id) { + duplicates = true; + } + } } - if (i == _format_context->nb_streams) { + if (duplicates) { /* Put in our own IDs */ for (uint32_t i = 0; i < _format_context->nb_streams; ++i) { _format_context->streams[i]->id = i; @@ -144,46 +190,32 @@ FFmpeg::setup_general () } void -FFmpeg::setup_video () -{ - boost::mutex::scoped_lock lm (_mutex); - - assert (_video_stream >= 0); - AVCodecContext* context = _format_context->streams[_video_stream]->codec; - AVCodec* codec = avcodec_find_decoder (context->codec_id); - - if (codec == 0) { - throw DecodeError (_("could not find video decoder")); - } - - if (avcodec_open2 (context, codec, 0) < 0) { - throw DecodeError (N_("could not open video decoder")); - } -} - -void -FFmpeg::setup_audio () +FFmpeg::setup_decoders () { boost::mutex::scoped_lock lm (_mutex); for (uint32_t i = 0; i < _format_context->nb_streams; ++i) { AVCodecContext* context = _format_context->streams[i]->codec; - if (context->codec_type != AVMEDIA_TYPE_AUDIO) { - continue; - } - + AVCodec* codec = avcodec_find_decoder (context->codec_id); - if (codec == 0) { - throw DecodeError (_("could not find audio decoder")); - } - - if (avcodec_open2 (context, codec, 0) < 0) { - throw DecodeError (N_("could not open audio decoder")); + if (codec) { + + /* 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). + */ + AVDictionary* options = 0; + av_dict_set (&options, "disable_footer", "1", 0); + + if (avcodec_open2 (context, codec, &options) < 0) { + throw DecodeError (N_("could not open decoder")); + } } + + /* We are silently ignoring any failures to find suitable decoders here */ } } - AVCodecContext * FFmpeg::video_codec_context () const { @@ -191,13 +223,13 @@ FFmpeg::video_codec_context () const } AVCodecContext * -FFmpeg::audio_codec_context () const +FFmpeg::subtitle_codec_context () const { - if (!_ffmpeg_content->audio_stream ()) { + if (!_ffmpeg_content->subtitle_stream ()) { return 0; } - - return _ffmpeg_content->audio_stream()->stream(_format_context)->codec; + + return _ffmpeg_content->subtitle_stream()->stream(_format_context)->codec; } int @@ -212,6 +244,22 @@ FFmpeg::avio_seek (int64_t const pos, int whence) if (whence == AVSEEK_SIZE) { return _file_group.length (); } - + return _file_group.seek (pos, whence); } + +FFmpegSubtitlePeriod +FFmpeg::subtitle_period (AVSubtitle const & sub) +{ + ContentTime const packet_time = ContentTime::from_seconds (static_cast (sub.pts) / AV_TIME_BASE); + + if (sub.end_display_time == static_cast (-1)) { + /* End time is not known */ + return FFmpegSubtitlePeriod (packet_time + ContentTime::from_seconds (sub.start_display_time / 1e3)); + } + + return FFmpegSubtitlePeriod ( + packet_time + ContentTime::from_seconds (sub.start_display_time / 1e3), + packet_time + ContentTime::from_seconds (sub.end_display_time / 1e3) + ); +}