\documentclass{article}
+\usepackage{amsmath}
\begin{document}
Here is what resampling we need to do. Content video is at $C_V$ fps, audio at $C_A$.
\textbf{Resample $C_A$ to the DCI rate.}
\section{Hard case 1}
+\label{sec:hard1}
$C_V$ is not a DCI rate, $C_A$ is, e.g.\ if $C_V = 25$, $C_A =
48\times{}10^3$. We will run the video at a nearby DCI rate $F_V$,
\medskip
\textbf{Resample $C_A$ to $C_V C_A / F_V$}
+\section{Hard case 2}
+
+Neither $C_V$ nor $C_A$ is not a DCI rate, e.g.\ if $C_V = 25$, $C_A =
+44.1\times{}10^3$. We will run the video at a nearby DCI rate $F_V$,
+meaning that it will run faster or slower than it should. We first
+resample the audio to a DCI rate $F_A$, then perform as with
+Section~\ref{sec:hard1} above.
+
+\medskip
+\textbf{Resample $C_A$ to $C_V F_A / F_V$}
+
+
+\section{The general case}
+
+Given a DCP running at $F_V$ and $F_A$ and a piece of content at $C_V$
+and $C_A$, resample the audio to $R_A$ where
+\begin{align*}
+R_A &= \frac{C_V F_A}{F_V}
+\end{align*}
\end{document}
{
return String::compose ("audio: channels %1, length %2, raw rate %3, out rate %4", audio_channels(), audio_length(), content_audio_frame_rate(), output_audio_frame_rate());
}
+
+/** Note: this is not particularly fast, as the FrameRateChange lookup
+ * is not very intelligent.
+ *
+ * @param t Some duration to convert.
+ * @param at The time within the DCP to get the active frame rate change from; i.e. a point at which
+ * the `controlling' video content is active.
+ */
+AudioContent::Frame
+AudioContent::time_to_content_audio_frames (Time t, Time at) const
+{
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+
+ /* Consider the case where we're running a 25fps video at 24fps (i.e. slow)
+ Our audio is at 44.1kHz. We will resample it to 48000 * 25 / 24 and then
+ run it at 48kHz (i.e. slow, to match).
+
+ After 1 second, we'll have run the equivalent of 44.1kHz * 24 / 25 samples
+ in the source.
+ */
+
+ return rint (t * content_audio_frame_rate() * film->active_frame_rate_change(at).speed_up / TIME_HZ);
+}
return _audio_delay;
}
+ Frame time_to_content_audio_frames (Time, Time) const;
+
private:
/** Gain to apply to audio in dB */
float _audio_gain;
if (_buffers->frames() == 0) {
return TimedAudioBuffers<T> ();
}
-
+
return TimedAudioBuffers<T> (_buffers, _last_pull);
}
+
+ void
+ clear (Time t)
+ {
+ _last_pull = t;
+ _buffers.reset (new AudioBuffers (_buffers->channels(), 0));
+ }
private:
boost::shared_ptr<AudioBuffers> _buffers;
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <boost/utility.hpp>
+#include "types.h"
class Film;
* cause the object to emit some data.
*/
virtual void pass () = 0;
+
+ /** 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.
+ */
+ virtual void seek (Time time, bool accurate) = 0;
+
virtual bool done () const = 0;
protected:
AVCodecContext *
FFmpeg::audio_codec_context () const
{
+ if (!_ffmpeg_content->audio_stream ()) {
+ return 0;
+ }
+
return _ffmpeg_content->audio_stream()->stream(_format_context)->codec;
}
/* Resample to a DCI-approved sample rate */
double t = dcp_audio_frame_rate (content_audio_frame_rate ());
- FrameRateConversion frc (video_frame_rate(), film->video_frame_rate());
+ FrameRateChange frc (video_frame_rate(), film->video_frame_rate());
/* Compensate if the DCP is being run at a different frame rate
to the source; that is, if the video is run such that it will
look different in the DCP compared to the source (slower or faster).
- skip/repeat doesn't come into effect here.
*/
if (frc.change_speed) {
- t *= video_frame_rate() * frc.factor() / film->video_frame_rate();
+ t /= frc.speed_up;
}
return rint (t);
shared_ptr<const Film> film = _film.lock ();
assert (film);
- FrameRateConversion frc (video_frame_rate (), film->video_frame_rate ());
+ FrameRateChange frc (video_frame_rate (), film->video_frame_rate ());
return video_length() * frc.factor() * TIME_HZ / film->video_frame_rate ();
}
return av_get_bytes_per_sample (audio_sample_format ());
}
-void
-FFmpegDecoder::seek (VideoContent::Frame frame, bool accurate)
+int
+FFmpegDecoder::minimal_run (boost::function<bool (int)> finished)
{
- double const time_base = av_q2d (_format_context->streams[_video_stream]->time_base);
+ int frames_read = 0;
+
+ while (!finished (frames_read)) {
+ int r = av_read_frame (_format_context, &_packet);
+ if (r < 0) {
+ return -1;
+ }
- /* If we are doing an accurate seek, our initial shot will be 5 frames (5 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.
- */
- int initial = frame;
+ ++frames_read;
- if (accurate) {
- initial -= 5;
- }
+ double const time_base = av_q2d (_format_context->streams[_packet.stream_index]->time_base);
+
+ if (_packet.stream_index == _video_stream) {
+
+ avcodec_get_frame_defaults (_frame);
+
+ int finished = 0;
+ r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet);
+ if (r >= 0 && finished) {
+ _video_position = rint (
+ (av_frame_get_best_effort_timestamp (_frame) * time_base + _video_pts_offset) * _ffmpeg_content->video_frame_rate()
+ );
+ }
+
+ } else if (_ffmpeg_content->audio_stream() && _packet.stream_index == _ffmpeg_content->audio_stream()->index (_format_context)) {
+
+ AVPacket copy_packet = _packet;
- if (initial < 0) {
- initial = 0;
+ while (copy_packet.size > 0) {
+
+ int finished;
+ r = avcodec_decode_audio4 (audio_codec_context(), _frame, &finished, ©_packet);
+ if (r >= 0 && finished) {
+ _audio_position = rint (
+ (av_frame_get_best_effort_timestamp (_frame) * time_base + _audio_pts_offset) *
+ _ffmpeg_content->audio_stream()->frame_rate
+ );
+ }
+
+ copy_packet.data += r;
+ copy_packet.size -= r;
+ }
+ }
+
+ av_free_packet (&_packet);
}
- /* Initial seek time in the stream's timebase */
- int64_t const initial_vt = ((initial / _ffmpeg_content->video_frame_rate()) - _video_pts_offset) / time_base;
+ return frames_read;
+}
+
+bool
+FFmpegDecoder::seek_overrun_finished (Time seek) const
+{
+ return (
+ _video_position >= _ffmpeg_content->time_to_content_video_frames (seek) ||
+ _audio_position >= _ffmpeg_content->time_to_content_audio_frames (seek, _ffmpeg_content->position())
+ );
+}
+
+bool
+FFmpegDecoder::seek_final_finished (int n, int done) const
+{
+ return n == done;
+}
+
+void
+FFmpegDecoder::seek_and_flush (Time t)
+{
+ int64_t const initial_v = ((_ffmpeg_content->time_to_content_video_frames (t) / _ffmpeg_content->video_frame_rate()) - _video_pts_offset) /
+ av_q2d (_format_context->streams[_video_stream]->time_base);
+
+ av_seek_frame (_format_context, _video_stream, initial_v, AVSEEK_FLAG_BACKWARD);
- av_seek_frame (_format_context, _video_stream, initial_vt, AVSEEK_FLAG_BACKWARD);
+ shared_ptr<FFmpegAudioStream> as = _ffmpeg_content->audio_stream ();
+ if (as) {
+ int64_t initial_a = ((_ffmpeg_content->time_to_content_audio_frames (t, t) / as->frame_rate) - _audio_pts_offset) /
+ av_q2d (as->stream(_format_context)->time_base);
+
+ av_seek_frame (_format_context, as->index (_format_context), initial_a, AVSEEK_FLAG_BACKWARD);
+ }
avcodec_flush_buffers (video_codec_context());
+ if (audio_codec_context ()) {
+ avcodec_flush_buffers (audio_codec_context ());
+ }
if (_subtitle_codec_context) {
avcodec_flush_buffers (_subtitle_codec_context);
}
+ _video_position = _ffmpeg_content->time_to_content_video_frames (t);
+ _audio_position = _ffmpeg_content->time_to_content_audio_frames (t, t);
+}
+
+void
+FFmpegDecoder::seek (Time time, bool accurate)
+{
+ /* If we are doing an accurate seek, our initial shot will be 200ms (200 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.
+ */
+
+ Time pre_roll = accurate ? (0.2 * TIME_HZ) : 0;
+ Time initial_seek = time - pre_roll;
+ if (initial_seek < 0) {
+ initial_seek = 0;
+ }
+
+ /* Initial seek time in the video stream's timebase */
+
+ seek_and_flush (initial_seek);
+
_just_sought = true;
- _video_position = frame;
- if (frame == 0 || !accurate) {
+ if (time == 0 || !accurate) {
/* We're already there, or we're as close as we need to be */
return;
}
- while (1) {
- int r = av_read_frame (_format_context, &_packet);
- if (r < 0) {
- return;
- }
+ int const N = minimal_run (boost::bind (&FFmpegDecoder::seek_overrun_finished, this, time));
- if (_packet.stream_index != _video_stream) {
- av_free_packet (&_packet);
- continue;
- }
-
- avcodec_get_frame_defaults (_frame);
-
- int finished = 0;
- r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet);
- if (r >= 0 && finished) {
- _video_position = rint (
- (av_frame_get_best_effort_timestamp (_frame) * time_base + _video_pts_offset) * _ffmpeg_content->video_frame_rate()
- );
-
- if (_video_position >= (frame - 1)) {
- av_free_packet (&_packet);
- break;
- }
- }
-
- av_free_packet (&_packet);
+ seek_and_flush (initial_seek);
+ if (N > 0) {
+ minimal_run (boost::bind (&FFmpegDecoder::seek_final_finished, this, N - 1, _1));
}
}
int frame_finished;
int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, ©_packet);
+
if (decode_result < 0) {
shared_ptr<const Film> film = _film.lock ();
assert (film);
~FFmpegDecoder ();
void pass ();
- void seek (VideoContent::Frame, bool);
+ void seek (Time time, bool);
bool done () const;
private:
void maybe_add_subtitle ();
boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t** data, int size);
+ bool seek_overrun_finished (Time) const;
+ bool seek_final_finished (int, int) const;
+ int minimal_run (boost::function<bool (int)>);
+ void seek_and_flush (int64_t);
+
AVCodecContext* _subtitle_codec_context; ///< may be 0 if there is no subtitle
AVCodec* _subtitle_codec; ///< may be 0 if there is no subtitle
return _playlist->content_paths_valid ();
}
+FrameRateChange
+Film::active_frame_rate_change (Time t) const
+{
+ return _playlist->active_frame_rate_change (t, video_frame_rate ());
+}
+
void
Film::playlist_content_changed (boost::weak_ptr<Content> c, int p)
{
bool has_subtitles () const;
OutputVideoFrame best_video_frame_rate () const;
bool content_paths_valid () const;
+ FrameRateChange active_frame_rate_change (Time) const;
libdcp::KDM
make_kdm (
shared_ptr<const Film> film = _film.lock ();
assert (film);
- FrameRateConversion frc (video_frame_rate(), film->video_frame_rate ());
+ FrameRateChange frc (video_frame_rate(), film->video_frame_rate ());
return video_length() * frc.factor() * TIME_HZ / video_frame_rate();
}
}
void
-ImageDecoder::seek (VideoContent::Frame frame, bool)
+ImageDecoder::seek (Time time, bool)
{
- _video_position = frame;
+ _video_position = _video_content->time_to_content_video_frames (time);
}
bool
/* Decoder */
void pass ();
- void seek (VideoContent::Frame, bool);
+ void seek (Time, bool);
bool done () const;
private:
_audio_position += _film->audio_frames_to_time (tb.audio->frames ());
}
}
-
+
return false;
}
shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
assert (content);
- FrameRateConversion frc (content->video_frame_rate(), _film->video_frame_rate());
+ FrameRateChange frc (content->video_frame_rate(), _film->video_frame_rate());
if (frc.skip && (frame % 2) == 1) {
return;
}
(*i)->video_position = (*i)->audio_position = vc->position() + s;
/* And seek the decoder */
- dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek (
- vc->time_to_content_video_frames (s + vc->trim_start ()), accurate
- );
-
+ dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek (s + vc->trim_start (), accurate);
(*i)->reset_repeat ();
}
_video_position = _audio_position = t;
-
- /* XXX: don't seek audio because we don't need to... */
+ _audio_merger.clear (t);
}
void
fd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
fd->Subtitle.connect (bind (&Player::process_subtitle, this, weak_ptr<Piece> (piece), _1, _2, _3, _4));
- fd->seek (fc->time_to_content_video_frames (fc->trim_start ()), true);
+ fd->seek (fc->trim_start (), true);
piece->decoder = fd;
}
return end;
}
+FrameRateChange
+Playlist::active_frame_rate_change (Time t, int dcp_video_frame_rate) const
+{
+ for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
+ shared_ptr<const VideoContent> vc = dynamic_pointer_cast<const VideoContent> (*i);
+ if (!vc) {
+ break;
+ }
+
+ if (vc->position() >= t && t < vc->end()) {
+ return FrameRateChange (vc->video_frame_rate(), dcp_video_frame_rate);
+ }
+ }
+
+ return FrameRateChange (dcp_video_frame_rate, dcp_video_frame_rate);
+}
+
void
Playlist::set_sequence_video (bool s)
{
#include <boost/enable_shared_from_this.hpp>
#include "ffmpeg_content.h"
#include "audio_mapping.h"
+#include "util.h"
class Content;
class FFmpegContent;
int best_dcp_frame_rate () const;
Time video_end () const;
+ FrameRateChange active_frame_rate_change (Time, int dcp_frame_rate) const;
void set_sequence_video (bool);
void maybe_sequence_video ();
{
return _audio_position >= _sndfile_content->audio_length ();
}
+
+void
+SndfileDecoder::seek (Time t, bool accurate)
+{
+ /* XXX */
+}
~SndfileDecoder ();
void pass ();
+ void seek (Time, bool);
bool done () const;
int audio_channels () const;
return channels[c];
}
-FrameRateConversion::FrameRateConversion (float source, int dcp)
+FrameRateChange::FrameRateChange (float source, int dcp)
: skip (false)
, repeat (1)
, change_speed (false)
repeat = round (dcp / source);
}
- change_speed = !about_equal (source * factor(), dcp);
+ speed_up = dcp / (source * factor());
+ change_speed = !about_equal (speed_up, 1.0);
if (!skip && repeat == 1 && !change_speed) {
description = _("Content and DCP have the same rate.\n");
extern boost::shared_ptr<const libdcp::Signer> make_signer ();
extern libdcp::Size fit_ratio_within (float ratio, libdcp::Size);
-struct FrameRateConversion
+struct FrameRateChange
{
- FrameRateConversion (float, int);
+ FrameRateChange (float, int);
/** @return factor by which to multiply a source frame rate
to get the effective rate after any skip or repeat has happened.
*/
bool change_speed;
+ /** Amount by which the video is being sped-up in the DCP; e.g. for a
+ * 24fps source in a 25fps DCP this would be 25/24.
+ */
+ float speed_up;
+
std::string description;
};
}
/** @param t A time offset from the start of this piece of content.
- * @return Corresponding frame index.
+ * @return Corresponding frame index, rounded up so that the frame index
+ * is that of the next complete frame which starts after `t'.
*/
VideoContent::Frame
VideoContent::time_to_content_video_frames (Time t) const
shared_ptr<const Film> film = _film.lock ();
assert (film);
- FrameRateConversion frc (video_frame_rate(), film->video_frame_rate());
-
/* Here we are converting from time (in the DCP) to a frame number in the content.
Hence we need to use the DCP's frame rate and the double/skip correction, not
- the source's rate.
+ the source's rate; source rate will be equal to DCP rate if we ignore
+ double/skip. There's no need to call Film::active_frame_rate_change() here
+ as we know that we are it (since we're video).
*/
- return t * film->video_frame_rate() / (frc.factor() * TIME_HZ);
+ FrameRateChange frc (video_frame_rate(), film->video_frame_rate());
+ return ceil (t * film->video_frame_rate() / (frc.factor() * TIME_HZ));
}
public:
VideoDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const VideoContent>);
- /** Seek so that the next pass() will yield (approximately) the requested frame.
- * Pass accurate = true to try harder to get close to the request.
- */
- virtual void seek (VideoContent::Frame frame, bool accurate) = 0;
-
/** Emitted when a video frame is ready.
* First parameter is the video image.
* Second parameter is the eye(s) which should see this image.
d << wxString::Format (_("Content frame rate %.4f\n"), vcs->video_frame_rate ());
++lines;
- FrameRateConversion frc (vcs->video_frame_rate(), _editor->film()->video_frame_rate ());
+ FrameRateChange frc (vcs->video_frame_rate(), _editor->film()->video_frame_rate ());
d << frc.description << "\n";
++lines;
--- /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 <boost/test/unit_test.hpp>
+#include "lib/player.h"
+#include "lib/ffmpeg_decoder.h"
+#include "lib/film.h"
+#include "lib/ratio.h"
+#include "test.h"
+
+using std::cout;
+using std::string;
+using std::stringstream;
+using boost::shared_ptr;
+
+#define FFMPEG_SEEK_TEST_DEBUG 1
+
+boost::optional<Time> first_video;
+boost::optional<Time> first_audio;
+
+static void
+process_video (shared_ptr<PlayerImage>, Eyes, ColourConversion, bool, Time t)
+{
+ if (!first_video) {
+ first_video = t;
+ }
+}
+
+static void
+process_audio (shared_ptr<const AudioBuffers>, Time t)
+{
+ if (!first_audio) {
+ first_audio = t;
+ }
+}
+
+static string
+print_time (Time t, float fps)
+{
+ stringstream s;
+ s << t << " " << (float(t) / TIME_HZ) << "s " << (float(t) * fps / TIME_HZ) << "f";
+ return s.str ();
+}
+
+static void
+check (shared_ptr<Player> p, Time t)
+{
+ first_video.reset ();
+ first_audio.reset ();
+
+#if FFMPEG_SEEK_TEST_DEBUG == 1
+ cout << "\n-- Seek to " << print_time (t, 24) << "\n";
+#endif
+
+ p->seek (t, true);
+ while (!first_video || !first_audio) {
+ p->pass ();
+ }
+
+#if FFMPEG_SEEK_TEST_DEBUG == 1
+ cout << "First video " << print_time (first_video.get(), 24) << "\n";
+ cout << "First audio " << print_time (first_audio.get(), 24) << "\n";
+#endif
+
+ BOOST_CHECK (first_video.get() >= t);
+ BOOST_CHECK (first_audio.get() >= t);
+}
+
+BOOST_AUTO_TEST_CASE (ffmpeg_seek_test)
+{
+ shared_ptr<Film> film = new_test_film ("ffmpeg_audio_test");
+ film->set_name ("ffmpeg_audio_test");
+ film->set_container (Ratio::from_id ("185"));
+ shared_ptr<FFmpegContent> c (new FFmpegContent (film, "test/data/staircase.mov"));
+ c->set_ratio (Ratio::from_id ("185"));
+ film->examine_and_add_content (c);
+
+ wait_for_jobs ();
+
+ shared_ptr<Player> player = film->make_player ();
+ player->Video.connect (boost::bind (&process_video, _1, _2, _3, _4, _5));
+ player->Audio.connect (boost::bind (&process_audio, _1, _2));
+
+ check (player, 0);
+ check (player, 0.1 * TIME_HZ);
+ check (player, 0.2 * TIME_HZ);
+ check (player, 0.3 * TIME_HZ);
+}
+
+
content->_video_frame_rate = 60;
int best = film->playlist()->best_dcp_frame_rate ();
- FrameRateConversion frc = FrameRateConversion (60, best);
+ FrameRateChange frc = FrameRateChange (60, best);
BOOST_CHECK_EQUAL (best, 30);
BOOST_CHECK_EQUAL (frc.skip, true);
BOOST_CHECK_EQUAL (frc.repeat, 1);
BOOST_CHECK_EQUAL (frc.change_speed, false);
+ BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
content->_video_frame_rate = 50;
best = film->playlist()->best_dcp_frame_rate ();
- frc = FrameRateConversion (50, best);
+ frc = FrameRateChange (50, best);
BOOST_CHECK_EQUAL (best, 25);
BOOST_CHECK_EQUAL (frc.skip, true);
BOOST_CHECK_EQUAL (frc.repeat, 1);
BOOST_CHECK_EQUAL (frc.change_speed, false);
+ BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
content->_video_frame_rate = 48;
best = film->playlist()->best_dcp_frame_rate ();
- frc = FrameRateConversion (48, best);
+ frc = FrameRateChange (48, best);
BOOST_CHECK_EQUAL (best, 24);
BOOST_CHECK_EQUAL (frc.skip, true);
BOOST_CHECK_EQUAL (frc.repeat, 1);
BOOST_CHECK_EQUAL (frc.change_speed, false);
+ BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
content->_video_frame_rate = 30;
best = film->playlist()->best_dcp_frame_rate ();
- frc = FrameRateConversion (30, best);
+ frc = FrameRateChange (30, best);
BOOST_CHECK_EQUAL (best, 30);
BOOST_CHECK_EQUAL (frc.skip, false);
BOOST_CHECK_EQUAL (frc.repeat, 1);
BOOST_CHECK_EQUAL (frc.change_speed, false);
+ BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
content->_video_frame_rate = 29.97;
best = film->playlist()->best_dcp_frame_rate ();
- frc = FrameRateConversion (29.97, best);
+ frc = FrameRateChange (29.97, best);
BOOST_CHECK_EQUAL (best, 30);
BOOST_CHECK_EQUAL (frc.skip, false);
BOOST_CHECK_EQUAL (frc.repeat, 1);
BOOST_CHECK_EQUAL (frc.change_speed, true);
+ BOOST_CHECK_CLOSE (frc.speed_up, 30 / 29.97, 0.1);
content->_video_frame_rate = 25;
best = film->playlist()->best_dcp_frame_rate ();
- frc = FrameRateConversion (25, best);
+ frc = FrameRateChange (25, best);
BOOST_CHECK_EQUAL (best, 25);
BOOST_CHECK_EQUAL (frc.skip, false);
BOOST_CHECK_EQUAL (frc.repeat, 1);
BOOST_CHECK_EQUAL (frc.change_speed, false);
+ BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
content->_video_frame_rate = 24;
best = film->playlist()->best_dcp_frame_rate ();
- frc = FrameRateConversion (24, best);
+ frc = FrameRateChange (24, best);
BOOST_CHECK_EQUAL (best, 24);
BOOST_CHECK_EQUAL (frc.skip, false);
BOOST_CHECK_EQUAL (frc.repeat, 1);
BOOST_CHECK_EQUAL (frc.change_speed, false);
+ BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
content->_video_frame_rate = 14.5;
best = film->playlist()->best_dcp_frame_rate ();
- frc = FrameRateConversion (14.5, best);
+ frc = FrameRateChange (14.5, best);
BOOST_CHECK_EQUAL (best, 30);
BOOST_CHECK_EQUAL (frc.skip, false);
BOOST_CHECK_EQUAL (frc.repeat, 2);
BOOST_CHECK_EQUAL (frc.change_speed, true);
+ BOOST_CHECK_CLOSE (frc.speed_up, 15 / 14.5, 0.1);
content->_video_frame_rate = 12.6;
best = film->playlist()->best_dcp_frame_rate ();
- frc = FrameRateConversion (12.6, best);
+ frc = FrameRateChange (12.6, best);
BOOST_CHECK_EQUAL (best, 25);
BOOST_CHECK_EQUAL (frc.skip, false);
BOOST_CHECK_EQUAL (frc.repeat, 2);
BOOST_CHECK_EQUAL (frc.change_speed, true);
+ BOOST_CHECK_CLOSE (frc.speed_up, 25 / 25.2, 0.1);
content->_video_frame_rate = 12.4;
best = film->playlist()->best_dcp_frame_rate ();
- frc = FrameRateConversion (12.4, best);
+ frc = FrameRateChange (12.4, best);
BOOST_CHECK_EQUAL (best, 25);
BOOST_CHECK_EQUAL (frc.skip, false);
BOOST_CHECK_EQUAL (frc.repeat, 2);
BOOST_CHECK_EQUAL (frc.change_speed, true);
+ BOOST_CHECK_CLOSE (frc.speed_up, 25 / 24.8, 0.1);
content->_video_frame_rate = 12;
best = film->playlist()->best_dcp_frame_rate ();
- frc = FrameRateConversion (12, best);
+ frc = FrameRateChange (12, best);
BOOST_CHECK_EQUAL (best, 24);
BOOST_CHECK_EQUAL (frc.skip, false);
BOOST_CHECK_EQUAL (frc.repeat, 2);
BOOST_CHECK_EQUAL (frc.change_speed, false);
+ BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
/* Now add some more rates and see if it will use them
in preference to skip/repeat.
content->_video_frame_rate = 60;
best = film->playlist()->best_dcp_frame_rate ();
- frc = FrameRateConversion (60, best);
+ frc = FrameRateChange (60, best);
BOOST_CHECK_EQUAL (best, 60);
BOOST_CHECK_EQUAL (frc.skip, false);
BOOST_CHECK_EQUAL (frc.repeat, 1);
BOOST_CHECK_EQUAL (frc.change_speed, false);
+ BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
content->_video_frame_rate = 50;
best = film->playlist()->best_dcp_frame_rate ();
- frc = FrameRateConversion (50, best);
+ frc = FrameRateChange (50, best);
BOOST_CHECK_EQUAL (best, 50);
BOOST_CHECK_EQUAL (frc.skip, false);
BOOST_CHECK_EQUAL (frc.repeat, 1);
BOOST_CHECK_EQUAL (frc.change_speed, false);
+ BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
content->_video_frame_rate = 48;
best = film->playlist()->best_dcp_frame_rate ();
- frc = FrameRateConversion (48, best);
+ frc = FrameRateChange (48, best);
BOOST_CHECK_EQUAL (best, 48);
BOOST_CHECK_EQUAL (frc.skip, false);
BOOST_CHECK_EQUAL (frc.repeat, 1);
BOOST_CHECK_EQUAL (frc.change_speed, false);
+ BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
/* Check some out-there conversions (not the best) */
- frc = FrameRateConversion (14.99, 24);
+ frc = FrameRateChange (14.99, 24);
BOOST_CHECK_EQUAL (frc.skip, false);
BOOST_CHECK_EQUAL (frc.repeat, 2);
BOOST_CHECK_EQUAL (frc.change_speed, true);
+ BOOST_CHECK_CLOSE (frc.speed_up, 2 * 14.99 / 24, 0.1);
/* Check some conversions with limited DCP targets */
content->_video_frame_rate = 25;
best = film->playlist()->best_dcp_frame_rate ();
- frc = FrameRateConversion (25, best);
+ frc = FrameRateChange (25, best);
BOOST_CHECK_EQUAL (best, 24);
BOOST_CHECK_EQUAL (frc.skip, false);
BOOST_CHECK_EQUAL (frc.repeat, 1);
BOOST_CHECK_EQUAL (frc.change_speed, true);
+ BOOST_CHECK_CLOSE (frc.speed_up, 24.0 / 25, 0.1);
}
-/* Test Playlist::best_dcp_frame_rate and FrameRateConversion
+/* Test Playlist::best_dcp_frame_rate and FrameRateChange
with two pieces of content.
*/
BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_double)
content->_video_frame_rate = 14.99;
film->set_video_frame_rate (25);
content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 16000, 0)));
- /* The FrameRateConversion within output_audio_frame_rate should choose to double-up
+ /* The FrameRateChange within output_audio_frame_rate should choose to double-up
the 14.99 fps video to 30 and then run it slow at 25.
*/
BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), rint (48000 * 2 * 14.99 / 25));
--- /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 <boost/test/unit_test.hpp>
+#include "lib/player.h"
+#include "lib/ffmpeg_decoder.h"
+#include "lib/film.h"
+#include "lib/ratio.h"
+#include "test.h"
+
+using std::cout;
+using std::string;
+using std::stringstream;
+using boost::shared_ptr;
+
+#define LONG_FFMPEG_SEEK_TEST_DEBUG 1
+
+boost::optional<Time> first_video;
+boost::optional<Time> first_audio;
+
+static void
+process_video (shared_ptr<PlayerImage>, Eyes, ColourConversion, bool, Time t)
+{
+ if (!first_video) {
+ first_video = t;
+ }
+}
+
+static void
+process_audio (shared_ptr<const AudioBuffers>, Time t)
+{
+ if (!first_audio) {
+ first_audio = t;
+ }
+}
+
+static string
+print_time (Time t, float fps)
+{
+ stringstream s;
+ s << t << " " << (float(t) / TIME_HZ) << "s " << (float(t) * fps / TIME_HZ) << "f";
+ return s.str ();
+}
+
+static void
+check (shared_ptr<Player> p, Time t)
+{
+ first_video.reset ();
+ first_audio.reset ();
+
+#if LONG_FFMPEG_SEEK_TEST_DEBUG == 1
+ cout << "\n-- Seek to " << print_time (t, 24) << "\n";
+#endif
+
+ p->seek (t, true);
+ while (!first_video || !first_audio) {
+ p->pass ();
+ }
+
+#if LONG_FFMPEG_SEEK_TEST_DEBUG == 1
+ cout << "First video " << print_time (first_video.get(), 24) << "\n";
+ cout << "First audio " << print_time (first_audio.get(), 24) << "\n";
+#endif
+
+ BOOST_CHECK (first_video.get() >= t);
+ BOOST_CHECK (first_audio.get() >= t);
+}
+
+BOOST_AUTO_TEST_CASE (long_ffmpeg_seek_test)
+{
+ shared_ptr<Film> film = new_test_film ("long_ffmpeg_audio_test");
+ film->set_name ("long_ffmpeg_audio_test");
+ film->set_container (Ratio::from_id ("185"));
+ shared_ptr<FFmpegContent> c (new FFmpegContent (film, "test/long_data/dolby_aurora.vob"));
+ c->set_ratio (Ratio::from_id ("185"));
+ film->examine_and_add_content (c);
+
+ wait_for_jobs ();
+
+ shared_ptr<Player> player = film->make_player ();
+ player->Video.connect (boost::bind (&process_video, _1, _2, _3, _4, _5));
+ player->Audio.connect (boost::bind (&process_audio, _1, _2));
+
+ for (float i = 0; i < 10; i += 0.1) {
+ check (player, i * TIME_HZ);
+ }
+}
+
+
struct TestConfig
{
- TestConfig()
+ TestConfig ()
{
- dcpomatic_setup();
+ dcpomatic_setup ();
Config::instance()->set_num_local_encoding_threads (1);
Config::instance()->set_server_port_base (61920);
""", msg = 'Checking for boost unit testing library', lib = 'boost_unit_test_framework%s' % boost_test_suffix, uselib_store = 'BOOST_TEST')
def build(bld):
- obj = bld(features = 'cxx cxxprogram')
+ obj = bld(features='cxx cxxprogram')
obj.name = 'unit-tests'
obj.uselib = 'BOOST_TEST DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML'
obj.use = 'libdcpomatic'
ffmpeg_dcp_test.cc
ffmpeg_examiner_test.cc
ffmpeg_pts_offset.cc
+ ffmpeg_seek_test.cc
file_group_test.cc
film_metadata_test.cc
frame_rate_test.cc
obj.target = 'unit-tests'
obj.install_path = ''
+
+ obj = bld(features='cxx cxxprogram')
+ obj.name = 'long-unit-tests'
+ obj.uselib = 'BOOST_TEST DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML'
+ obj.use = 'libdcpomatic'
+ obj.source = """
+ test.cc
+ long_ffmpeg_seek_test.cc
+ """
+
+ obj.target = 'long-unit-tests'
+ obj.install_path = ''