, _format_context (0)
, _frame (0)
, _video_stream (-1)
- , _video_codec_context (0)
- , _video_codec (0)
- , _audio_codec_context (0)
- , _audio_codec (0)
{
setup_general ();
setup_video ();
FFmpeg::~FFmpeg ()
{
boost::mutex::scoped_lock lm (_mutex);
-
- if (_audio_codec_context) {
- avcodec_close (_audio_codec_context);
- }
- if (_video_codec_context) {
- avcodec_close (_video_codec_context);
+ 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);
+ }
}
av_free (_frame);
{
boost::mutex::scoped_lock lm (_mutex);
- _video_codec_context = _format_context->streams[_video_stream]->codec;
- _video_codec = avcodec_find_decoder (_video_codec_context->codec_id);
+ AVCodecContext* context = _format_context->streams[_video_stream]->codec;
+ AVCodec* codec = avcodec_find_decoder (context->codec_id);
- if (_video_codec == 0) {
+ if (codec == 0) {
throw DecodeError (_("could not find video decoder"));
}
- if (avcodec_open2 (_video_codec_context, _video_codec, 0) < 0) {
+ if (avcodec_open2 (context, codec, 0) < 0) {
throw DecodeError (N_("could not open video decoder"));
}
}
FFmpeg::setup_audio ()
{
boost::mutex::scoped_lock lm (_mutex);
-
- if (!_ffmpeg_content->audio_stream ()) {
- return;
+
+ 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"));
+ }
}
+}
- _audio_codec_context = _format_context->streams[_ffmpeg_content->audio_stream()->id]->codec;
- _audio_codec = avcodec_find_decoder (_audio_codec_context->codec_id);
- if (_audio_codec == 0) {
- throw DecodeError (_("could not find audio decoder"));
- }
+AVCodecContext *
+FFmpeg::video_codec_context () const
+{
+ return _format_context->streams[_video_stream]->codec;
+}
- if (avcodec_open2 (_audio_codec_context, _audio_codec, 0) < 0) {
- throw DecodeError (N_("could not open audio decoder"));
- }
+AVCodecContext *
+FFmpeg::audio_codec_context () const
+{
+ return _format_context->streams[_ffmpeg_content->audio_stream()->id]->codec;
}
*/
+#ifndef DCPOMATIC_FFMPEG_H
+#define DCPOMATIC_FFMPEG_H
+
#include <vector>
#include <boost/shared_ptr.hpp>
#include <boost/thread/mutex.hpp>
}
protected:
+ AVCodecContext* video_codec_context () const;
+ AVCodecContext* audio_codec_context () const;
+
boost::shared_ptr<const FFmpegContent> _ffmpeg_content;
+
AVFormatContext* _format_context;
AVPacket _packet;
AVFrame* _frame;
- int _video_stream;
- AVCodecContext* _video_codec_context;
- AVCodec* _video_codec;
- AVCodecContext* _audio_codec_context; ///< may be 0 if there is no audio
- AVCodec* _audio_codec; ///< may be 0 if there is no audio
+ int _video_stream;
/* It would appear (though not completely verified) that one must have
a mutex around calls to avcodec_open* and avcodec_close... and here
void setup_video ();
void setup_audio ();
};
+
+#endif
for (list<shared_ptr<cxml::Node> >::iterator i = c.begin(); i != c.end(); ++i) {
_filters.push_back (Filter::from_id ((*i)->content ()));
}
+
+ _first_video = node->optional_number_child<Time> ("FirstVideo");
}
FFmpegContent::FFmpegContent (FFmpegContent const & o)
for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
node->add_child("Filter")->add_child_text ((*i)->id ());
}
+
+ if (_first_video) {
+ node->add_child("FirstVideo")->add_child_text (lexical_cast<string> (_first_video.get ()));
+ }
}
void
ContentVideoFrame video_length = 0;
video_length = examiner->video_length ();
- film->log()->log (String::compose ("Video length obtained from header as %1 frames", examiner->video_length ()));
+ film->log()->log (String::compose ("Video length obtained from header as %1 frames", video_length));
{
boost::mutex::scoped_lock lm (_mutex);
if (!_audio_streams.empty ()) {
_audio_stream = _audio_streams.front ();
}
+
+ _first_video = examiner->first_video ();
}
take_from_video_examiner (examiner);
frame_rate = node->number_child<int> ("FrameRate");
channels = node->number_child<int64_t> ("Channels");
mapping = AudioMapping (node->node_child ("Mapping"));
+ start = node->optional_number_child<Time> ("Start");
}
void
root->add_child("Id")->add_child_text (lexical_cast<string> (id));
root->add_child("FrameRate")->add_child_text (lexical_cast<string> (frame_rate));
root->add_child("Channels")->add_child_text (lexical_cast<string> (channels));
+ if (start) {
+ root->add_child("Start")->add_child_text (lexical_cast<string> (start));
+ }
mapping.as_xml (root->add_child("Mapping"));
}
int frame_rate;
int channels;
AudioMapping mapping;
+ boost::optional<Time> start;
};
extern bool operator== (FFmpegAudioStream const & a, FFmpegAudioStream const & b);
boost::shared_ptr<FFmpegSubtitleStream> _subtitle_stream;
std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams;
boost::shared_ptr<FFmpegAudioStream> _audio_stream;
+ boost::optional<Time> _first_video;
/** Video filters that should be used when generating DCPs */
std::vector<Filter const *> _filters;
};
AVSampleFormat
FFmpegDecoder::audio_sample_format () const
{
- if (_audio_codec_context == 0) {
+ if (!_ffmpeg_content->audio_stream()) {
return (AVSampleFormat) 0;
}
- return _audio_codec_context->sample_fmt;
+ return audio_codec_context()->sample_fmt;
}
int
int64_t const vt = t / (av_q2d (_format_context->streams[_video_stream]->time_base) * TIME_HZ);
av_seek_frame (_format_context, _video_stream, vt, backwards ? AVSEEK_FLAG_BACKWARD : 0);
- avcodec_flush_buffers (_video_codec_context);
+ avcodec_flush_buffers (video_codec_context());
if (_subtitle_codec_context) {
avcodec_flush_buffers (_subtitle_codec_context);
}
if (_packet.stream_index == _video_stream) {
int finished = 0;
- int const r = avcodec_decode_video2 (_video_codec_context, _frame, &finished, &_packet);
+ int const r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet);
if (r >= 0 && finished) {
int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
if (bet > vt) {
while (copy_packet.size > 0) {
int frame_finished;
- int const decode_result = avcodec_decode_audio4 (_audio_codec_context, _frame, &frame_finished, ©_packet);
+ int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, ©_packet);
if (decode_result >= 0) {
if (frame_finished) {
* av_frame_get_best_effort_timestamp(_frame);
int const data_size = av_samples_get_buffer_size (
- 0, _audio_codec_context->channels, _frame->nb_samples, audio_sample_format (), 1
+ 0, audio_codec_context()->channels, _frame->nb_samples, audio_sample_format (), 1
);
- assert (_audio_codec_context->channels == _ffmpeg_content->audio_channels());
+ assert (audio_codec_context()->channels == _ffmpeg_content->audio_channels());
audio (deinterleave_audio (_frame->data, data_size), source_pts_seconds * TIME_HZ);
}
FFmpegDecoder::decode_video_packet ()
{
int frame_finished;
- if (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) < 0 || !frame_finished) {
+ if (avcodec_decode_video2 (video_codec_context(), _frame, &frame_finished, &_packet) < 0 || !frame_finished) {
return false;
}
Time
FFmpegDecoder::position () const
{
- if (_decode_video && _decode_audio && _audio_codec_context) {
+ if (_decode_video && _decode_audio && _ffmpeg_content->audio_stream()) {
return min (_next_video, _next_audio);
}
- if (_decode_audio && _audio_codec_context) {
+ if (_decode_audio && _ffmpeg_content->audio_stream()) {
return _next_audio;
}
bool
FFmpegDecoder::done () const
{
- return (!_decode_audio || !_audio_codec_context || audio_done()) && (!_decode_video || video_done());
+ bool const ad = !_decode_audio || !_ffmpeg_content->audio_stream() || audio_done();
+ bool const vd = !_decode_video || video_done();
+ return ad && vd;
}
void
#include "ffmpeg_content.h"
using std::string;
+using std::cout;
using std::stringstream;
using boost::shared_ptr;
+using boost::optional;
FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c)
: FFmpeg (c)
}
}
+ /* Run through until we find the first audio (for each stream) and video */
+
+ while (1) {
+ int r = av_read_frame (_format_context, &_packet);
+ if (r < 0) {
+ break;
+ }
+
+ int frame_finished;
+ avcodec_get_frame_defaults (_frame);
+
+ cout << "got packet " << _packet.stream_index << "\n";
+
+ AVCodecContext* context = _format_context->streams[_packet.stream_index]->codec;
+
+ if (_packet.stream_index == _video_stream && !_first_video) {
+ if (avcodec_decode_video2 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
+ _first_video = frame_time (_video_stream);
+ }
+ } else {
+ for (size_t i = 0; i < _audio_streams.size(); ++i) {
+ if (_packet.stream_index == _audio_streams[i]->id && !_audio_streams[i]->start) {
+ if (avcodec_decode_audio4 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
+ _audio_streams[i]->start = frame_time (_audio_streams[i]->id);
+ }
+ }
+ }
+ }
+
+ bool have_all_audio = true;
+ size_t i = 0;
+ while (i < _audio_streams.size() && have_all_audio) {
+ have_all_audio = _audio_streams[i]->start;
+ ++i;
+ }
+
+ if (_first_video && have_all_audio) {
+ break;
+ }
+
+ av_free_packet (&_packet);
+ }
+}
+
+optional<Time>
+FFmpegExaminer::frame_time (int stream) const
+{
+ optional<Time> t;
+
+ int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
+ if (bet != AV_NOPTS_VALUE) {
+ t = bet * av_q2d (_format_context->streams[stream]->time_base) * TIME_HZ;
+ }
+
+ return t;
}
float
libdcp::Size
FFmpegExaminer::video_size () const
{
- return libdcp::Size (_video_codec_context->width, _video_codec_context->height);
+ return libdcp::Size (video_codec_context()->width, video_codec_context()->height);
}
/** @return Length (in video frames) according to our content's header */
*/
+#include <boost/optional.hpp>
#include "ffmpeg.h"
#include "video_examiner.h"
return _audio_streams;
}
+ boost::optional<Time> first_video () const {
+ return _first_video;
+ }
+
private:
std::string stream_name (AVStream* s) const;
+ boost::optional<Time> frame_time (int) const;
std::vector<boost::shared_ptr<FFmpegSubtitleStream> > _subtitle_streams;
std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams;
+ boost::optional<Time> _first_video;
};
--- /dev/null
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ 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
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "ffmpeg_examiner.h"
+
+BOOST_AUTO_TEST_CASE (ffmpeg_examiner_test)
+{
+ shared_ptr<Film> film = new_test_film ("ffmpeg_examiner_test");
+ shared_ptr<FFmpegContent> content (new FFmpegContent (film, "test/data/count300bd24.m2ts"));
+ shared_ptr<FFmpegExaminer> examiner (new FFmpegExaminer (content));
+
+ BOOST_CHECK_EQUAL (examiner->first_video().get(), 57604000);
+ BOOST_CHECK_EQUAL (examiner->audio_streams().size(), 1);
+ BOOST_CHECK_EQUAL (examiner->audio_streams()[0]->start.get(), 57600000);
+}
BOOST_CHECK (ref_dcp.equals (check_dcp, options, boost::bind (note, _1, _2)));
}
+#include "ffmpeg_examiner_test.cc"
#include "black_fill_test.cc"
#include "scaling_test.cc"
#include "ratio_test.cc"
#include "job_test.cc"
#include "client_server_test.cc"
#include "image_test.cc"
-