virtual ~Decoder () {}
protected:
- /** Seek so that the next peek() will yield the next thing
+ /** Seek so that the next pass() will yield the next thing
* (video/sound frame, subtitle etc.) at or after the requested
* time. Pass accurate = true to try harder to get close to
- * the request.
+ * the request. Note that seeking to time t may mean that
+ * the next pass() yields, for example, audio at time t and then
+ * video before it.
*/
virtual void seek (ContentTime time, bool accurate) = 0;
virtual bool pass () = 0;
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 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
avcodec_get_frame_defaults (_frame);
- int finished = 0;
- r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet);
- if (r >= 0 && finished) {
+ int got_picture = 0;
+ r = avcodec_decode_video2 (video_codec_context(), _frame, &got_picture, &_packet);
+ if (r >= 0 && got_picture) {
last_video = ContentTime::from_seconds (av_frame_get_best_effort_timestamp (_frame) * time_base) + _pts_offset;
}
AVPacket copy_packet = _packet;
while (copy_packet.size > 0) {
- int finished;
- r = avcodec_decode_audio4 (audio_codec_context(), _frame, &finished, &_packet);
- if (r >= 0 && finished) {
+ int got_frame;
+ r = avcodec_decode_audio4 (audio_codec_context(), _frame, &got_frame, &_packet);
+ if (r >= 0 && got_frame) {
last_audio = ContentTime::from_seconds (av_frame_get_best_effort_timestamp (_frame) * time_base) + _pts_offset;
}
VideoDecoder::seek (time, accurate);
AudioDecoder::seek (time, accurate);
- /* If we are doing an accurate seek, our initial shot will be 200ms (200 being
+ /* If we are doing an accurate seek, our initial shot will be 2s (2 being
a number plucked from the air) earlier than we want to end up. The loop below
will hopefully then step through to where we want to be.
*/
- ContentTime pre_roll = accurate ? ContentTime::from_seconds (0.2) : ContentTime (0);
+ ContentTime pre_roll = accurate ? ContentTime::from_seconds (2) : ContentTime (0);
ContentTime initial_seek = time - pre_roll;
if (initial_seek < ContentTime (0)) {
initial_seek = ContentTime (0);
AVSubtitleRect const * rect = sub.rects[0];
if (rect->type != SUBTITLE_BITMAP) {
- throw DecodeError (_("non-bitmap subtitles not yet supported"));
+ /* XXX */
+ // throw DecodeError (_("non-bitmap subtitles not yet supported"));
+ return;
}
/* Note RGBA is expressed little-endian, so the first byte in the word is R, second
int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
if (bet != AV_NOPTS_VALUE) {
- t = ContentTime (bet * av_q2d (s->time_base));
+ t = ContentTime::from_seconds (bet * av_q2d (s->time_base));
}
return t;
float
FFmpegExaminer::video_frame_rate () const
{
- AVStream* s = _format_context->streams[_video_stream];
-
- if (s->avg_frame_rate.num && s->avg_frame_rate.den) {
- return av_q2d (s->avg_frame_rate);
- }
-
- return av_q2d (s->r_frame_rate);
+ /* This use of r_frame_rate is debateable; there's a few different
+ * frame rates in the format context, but this one seems to be the most
+ * reliable.
+ */
+ return av_q2d (av_stream_get_r_frame_rate (_format_context->streams[_video_stream]));
}
dcp::Size
_approximate_size = true;
}
+shared_ptr<DCPVideo>
+Player::black_dcp_video (DCPTime time) const
+{
+ return shared_ptr<DCPVideo> (
+ new DCPVideo (
+ _black_image,
+ EYES_BOTH,
+ Crop (),
+ _video_container_size,
+ _video_container_size,
+ Scaler::from_id ("bicubic"),
+ Config::instance()->colour_conversions().front().conversion,
+ time
+ )
+ );
+}
+
shared_ptr<DCPVideo>
Player::get_video (DCPTime time, bool accurate)
{
list<shared_ptr<Piece> > ov = overlaps<VideoContent> (time);
if (ov.empty ()) {
- /* No video content at this time: return a black frame */
- return shared_ptr<DCPVideo> (
- new DCPVideo (
- _black_image,
- EYES_BOTH,
- Crop (),
- _video_container_size,
- _video_container_size,
- Scaler::from_id ("bicubic"),
- Config::instance()->colour_conversions().front().conversion,
- time
- )
- );
+ /* No video content at this time */
+ return black_dcp_video (time);
}
/* Create a DCPVideo from the content's video at this time */
assert (content);
optional<ContentVideo> dec = decoder->get_video (dcp_to_content_video (piece, time), accurate);
- assert (dec);
+ if (!dec) {
+ return black_dcp_video (time);
+ }
dcp::Size image_size = content->scale().size (content, _video_container_size, _film->frame_size ());
if (_approximate_size) {
VideoFrame dcp_to_content_video (boost::shared_ptr<const Piece> piece, DCPTime t) const;
AudioFrame dcp_to_content_audio (boost::shared_ptr<const Piece> piece, DCPTime t) const;
ContentTime dcp_to_content_subtitle (boost::shared_ptr<const Piece> piece, DCPTime t) const;
+ boost::shared_ptr<DCPVideo> black_dcp_video (DCPTime) const;
template<class C>
std::list<boost::shared_ptr<Piece> >
using boost::optional;
VideoDecoder::VideoDecoder (shared_ptr<const VideoContent> c)
+#ifdef DCPOMATIC_DEBUG
+ : test_gaps (0)
+ , _video_content (c)
+#else
: _video_content (c)
+#endif
{
}
optional<ContentVideo> dec;
- /* Now enough pass() calls will either:
+ /* Now enough pass() calls should either:
* (a) give us what we want, or
* (b) hit the end of the decoder.
*/
if (accurate) {
- /* We are being accurate, so we want the right frame */
- while (!decoded_video (frame) && !pass ()) {}
+ /* We are being accurate, so we want the right frame.
+ * This could all be one statement but it's split up for clarity.
+ */
+ while (true) {
+ if (decoded_video (frame)) {
+ /* We got what we want */
+ break;
+ }
+
+ if (pass ()) {
+ /* The decoder has nothing more for us */
+ break;
+ }
+
+ if (!_decoded_video.empty() && _decoded_video.front().frame > frame) {
+ /* We're never going to get the frame we want. Perhaps the caller is asking
+ * for a video frame before the content's video starts (if its audio
+ * begins before its video, for example).
+ */
+ break;
+ }
+ }
+
dec = decoded_video (frame);
} else {
/* Any frame will do: use the first one that comes out of pass() */
void
VideoDecoder::video (shared_ptr<const Image> image, VideoFrame frame)
{
+ /* We should not receive the same thing twice */
+ assert (_decoded_video.empty() || frame != _decoded_video.back().frame);
+
/* Fill in gaps */
/* XXX: 3D */
+
while (!_decoded_video.empty () && (_decoded_video.back().frame + 1) < frame) {
+#ifdef DCPOMATIC_DEBUG
+ test_gaps++;
+#endif
_decoded_video.push_back (
ContentVideo (
_decoded_video.back().image,
return _video_content;
}
+#ifdef DCPOMATIC_DEBUG
+ int test_gaps;
+#endif
+
protected:
void seek (ContentTime time, bool accurate);
--- /dev/null
+/*
+ Copyright (C) 2014 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 <vector>
+#include <boost/test/unit_test.hpp>
+#include <boost/filesystem.hpp>
+#include "lib/ffmpeg_content.h"
+#include "lib/ffmpeg_decoder.h"
+#include "lib/log.h"
+#include "lib/film.h"
+#include "test.h"
+
+using std::cerr;
+using std::vector;
+using boost::shared_ptr;
+using boost::optional;
+
+static void
+check (FFmpegDecoder& decoder, int frame)
+{
+ optional<ContentVideo> v;
+ v = decoder.get_video (frame, true);
+ BOOST_CHECK (v);
+ BOOST_CHECK_EQUAL (v->frame, frame);
+}
+
+static void
+test (boost::filesystem::path file, vector<int> frames)
+{
+ boost::filesystem::path path = private_data / file;
+ if (!boost::filesystem::exists (path)) {
+ cerr << "Skipping test: " << path.string() << " not found.\n";
+ return;
+ }
+
+ shared_ptr<Film> film = new_test_film ("ffmpeg_decoder_seek_test_" + file.string());
+ shared_ptr<FFmpegContent> content (new FFmpegContent (film, path));
+ film->examine_and_add_content (content);
+ wait_for_jobs ();
+ shared_ptr<Log> log (new NullLog);
+ FFmpegDecoder decoder (content, log);
+
+ for (vector<int>::const_iterator i = frames.begin(); i != frames.end(); ++i) {
+ check (decoder, *i);
+ }
+}
+
+BOOST_AUTO_TEST_CASE (ffmpeg_decoder_seek_test)
+{
+ vector<int> frames;
+
+ frames.clear ();
+ frames.push_back (0);
+ frames.push_back (42);
+ frames.push_back (999);
+ frames.push_back (0);
+
+ test ("boon_telly.mkv", frames);
+ test ("Sintel_Trailer1.480p.DivX_Plus_HD.mkv", frames);
+
+ frames.clear ();
+ frames.push_back (15);
+ frames.push_back (42);
+ frames.push_back (999);
+ frames.push_back (15);
+
+ test ("prophet_clip.mkv", frames);
+}
+
--- /dev/null
+/*
+ Copyright (C) 2014 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 <boost/test/unit_test.hpp>
+#include <boost/filesystem.hpp>
+#include "lib/ffmpeg_content.h"
+#include "lib/ffmpeg_decoder.h"
+#include "lib/log.h"
+#include "lib/film.h"
+#include "test.h"
+
+using std::cout;
+using std::cerr;
+using boost::shared_ptr;
+using boost::optional;
+
+/** @param black Frame index of first frame in the video */
+static void
+test (boost::filesystem::path file, float fps, int first)
+{
+ boost::filesystem::path path = private_data / file;
+ if (!boost::filesystem::exists (path)) {
+ cerr << "Skipping test: " << path.string() << " not found.\n";
+ return;
+ }
+
+ shared_ptr<Film> film = new_test_film ("ffmpeg_decoder_seek_test_" + file.string());
+ shared_ptr<FFmpegContent> content (new FFmpegContent (film, path));
+ film->examine_and_add_content (content);
+ wait_for_jobs ();
+ shared_ptr<Log> log (new NullLog);
+ FFmpegDecoder decoder (content, log);
+
+ BOOST_CHECK_CLOSE (decoder.video_content()->video_frame_rate(), fps, 0.01);
+
+ VideoFrame const N = decoder.video_content()->video_length().frames (decoder.video_content()->video_frame_rate ());
+ decoder.test_gaps = 0;
+ for (VideoFrame i = 0; i < N; ++i) {
+ optional<ContentVideo> v;
+ v = decoder.get_video (i, true);
+ if (i < first) {
+ BOOST_CHECK (!v);
+ } else {
+ BOOST_CHECK (v);
+ BOOST_CHECK_EQUAL (v->frame, i);
+ }
+ }
+ BOOST_CHECK_EQUAL (decoder.test_gaps, 0);
+}
+
+/** Check that the FFmpeg decoder produces sequential frames without gaps or dropped frames;
+ * (dropped frames being checked by assert() in VideoDecoder). Also that the decoder picks up frame rates correctly.
+ */
+BOOST_AUTO_TEST_CASE (ffmpeg_decoder_sequential_test)
+{
+ //test ("boon_telly.mkv", 29.97, 0);
+ //test ("Sintel_Trailer1.480p.DivX_Plus_HD.mkv", 24, 0);
+ test ("prophet_clip.mkv", 23.976, 12);
+}
+
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 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
using std::list;
using boost::shared_ptr;
+boost::filesystem::path private_data = boost::filesystem::path ("test") / boost::filesystem::path ("private");
+
class TestUISignaller : public UISignaller
{
public:
class Film;
class Image;
+extern boost::filesystem::path private_data;
+
extern void wait_for_jobs ();
extern boost::shared_ptr<Film> new_test_film (std::string);
extern void check_dcp (boost::filesystem::path, boost::filesystem::path);
BOOST_CHECK_EQUAL (DCPTime (1).round_up (DCPTime::HZ / 42), DCPTime (42));
BOOST_CHECK_EQUAL (DCPTime (42).round_up (DCPTime::HZ / 42), DCPTime (42));
BOOST_CHECK_EQUAL (DCPTime (43).round_up (DCPTime::HZ / 42), DCPTime (84));
+
+ /* Check that rounding up to non-integer frame rates works */
+ BOOST_CHECK_EQUAL (DCPTime (45312).round_up (29.976), DCPTime (48045));
}
colour_conversion_test.cc
ffmpeg_audio_test.cc
ffmpeg_dcp_test.cc
+ ffmpeg_decoder_seek_test.cc
+ ffmpeg_decoder_sequential_test.cc
ffmpeg_examiner_test.cc
ffmpeg_pts_offset.cc
file_group_test.cc
bld.recurse('src')
def tags(bld):
- os.system('etags src/lib/*.cc src/lib/*.h src/wx/*.cc src/wx/*.h src/tools/*.cc src/tools/*.h')
+ os.system('etags src/lib/*.cc src/lib/*.h src/wx/*.cc src/wx/*.h src/tools/*.cc src/tools/*.h test/*.cc')