diff options
| author | Carl Hetherington <cth@carlh.net> | 2013-12-11 10:31:18 +0000 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2013-12-11 10:31:18 +0000 |
| commit | ea910e250a0fb3b0ad3ce0cf32dd27b24c17cd1d (patch) | |
| tree | 32119d2a04532d3162c656df7c7e715b289c6e61 | |
| parent | 3e5df964ada49e10a880a200c985cc309ccecb64 (diff) | |
Various work on better seeking (and seeking of audio).
| -rw-r--r-- | doc/design/resampling.tex | 21 | ||||
| -rw-r--r-- | src/lib/audio_content.cc | 24 | ||||
| -rw-r--r-- | src/lib/audio_content.h | 2 | ||||
| -rw-r--r-- | src/lib/audio_merger.h | 9 | ||||
| -rw-r--r-- | src/lib/decoder.h | 9 | ||||
| -rw-r--r-- | src/lib/ffmpeg.cc | 4 | ||||
| -rw-r--r-- | src/lib/ffmpeg_content.cc | 7 | ||||
| -rw-r--r-- | src/lib/ffmpeg_decoder.cc | 151 | ||||
| -rw-r--r-- | src/lib/ffmpeg_decoder.h | 7 | ||||
| -rw-r--r-- | src/lib/film.cc | 6 | ||||
| -rw-r--r-- | src/lib/film.h | 1 | ||||
| -rw-r--r-- | src/lib/image_content.cc | 2 | ||||
| -rw-r--r-- | src/lib/image_decoder.cc | 4 | ||||
| -rw-r--r-- | src/lib/image_decoder.h | 2 | ||||
| -rw-r--r-- | src/lib/player.cc | 14 | ||||
| -rw-r--r-- | src/lib/playlist.cc | 17 | ||||
| -rw-r--r-- | src/lib/playlist.h | 2 | ||||
| -rw-r--r-- | src/lib/sndfile_decoder.cc | 6 | ||||
| -rw-r--r-- | src/lib/sndfile_decoder.h | 1 | ||||
| -rw-r--r-- | src/lib/util.cc | 5 | ||||
| -rw-r--r-- | src/lib/util.h | 9 | ||||
| -rw-r--r-- | src/lib/video_content.cc | 12 | ||||
| -rw-r--r-- | src/lib/video_decoder.h | 5 | ||||
| -rw-r--r-- | src/wx/video_panel.cc | 2 | ||||
| -rw-r--r-- | test/ffmpeg_seek_test.cc | 106 | ||||
| -rw-r--r-- | test/frame_rate_test.cc | 52 | ||||
| -rw-r--r-- | test/long_ffmpeg_seek_test.cc | 105 | ||||
| -rw-r--r-- | test/test.cc | 4 | ||||
| -rw-r--r-- | test/wscript | 15 |
29 files changed, 505 insertions, 99 deletions
diff --git a/doc/design/resampling.tex b/doc/design/resampling.tex index 44aeee9b1..cf9cfb1ed 100644 --- a/doc/design/resampling.tex +++ b/doc/design/resampling.tex @@ -1,4 +1,5 @@ \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$. @@ -18,6 +19,7 @@ $C_V$ is a DCI rate, $C_A$ is not. e.g.\ if $C_V = 24$, $C_A = 44.1\times{}10^3 \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$, @@ -31,5 +33,24 @@ resample audio to $25 * 48\times{}10^3 / 24 = 50\times{}10^3$. \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} diff --git a/src/lib/audio_content.cc b/src/lib/audio_content.cc index 97372b962..0c4586681 100644 --- a/src/lib/audio_content.cc +++ b/src/lib/audio_content.cc @@ -148,3 +148,27 @@ AudioContent::technical_summary () const { 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); +} diff --git a/src/lib/audio_content.h b/src/lib/audio_content.h index ca4a1f234..10114e10d 100644 --- a/src/lib/audio_content.h +++ b/src/lib/audio_content.h @@ -74,6 +74,8 @@ public: return _audio_delay; } + Frame time_to_content_audio_frames (Time, Time) const; + private: /** Gain to apply to audio in dB */ float _audio_gain; diff --git a/src/lib/audio_merger.h b/src/lib/audio_merger.h index 226601e0e..6ad33fb37 100644 --- a/src/lib/audio_merger.h +++ b/src/lib/audio_merger.h @@ -97,9 +97,16 @@ public: 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; diff --git a/src/lib/decoder.h b/src/lib/decoder.h index d67592ed8..908d3aae5 100644 --- a/src/lib/decoder.h +++ b/src/lib/decoder.h @@ -27,6 +27,7 @@ #include <boost/shared_ptr.hpp> #include <boost/weak_ptr.hpp> #include <boost/utility.hpp> +#include "types.h" class Film; @@ -43,6 +44,14 @@ public: * 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: diff --git a/src/lib/ffmpeg.cc b/src/lib/ffmpeg.cc index e5e5f317a..53d12419a 100644 --- a/src/lib/ffmpeg.cc +++ b/src/lib/ffmpeg.cc @@ -191,6 +191,10 @@ FFmpeg::video_codec_context () const AVCodecContext * FFmpeg::audio_codec_context () const { + if (!_ffmpeg_content->audio_stream ()) { + return 0; + } + return _ffmpeg_content->audio_stream()->stream(_format_context)->codec; } diff --git a/src/lib/ffmpeg_content.cc b/src/lib/ffmpeg_content.cc index 9533315a5..b6df2e929 100644 --- a/src/lib/ffmpeg_content.cc +++ b/src/lib/ffmpeg_content.cc @@ -310,16 +310,15 @@ FFmpegContent::output_audio_frame_rate () const /* 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); @@ -452,7 +451,7 @@ FFmpegContent::full_length () const 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 (); } diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc index 5a1b78762..ed0574c61 100644 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@ -297,70 +297,132 @@ FFmpegDecoder::bytes_per_audio_sample () const 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)); } } @@ -377,6 +439,7 @@ FFmpegDecoder::decode_audio_packet () 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); diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h index 11f83ed97..f54ee2496 100644 --- a/src/lib/ffmpeg_decoder.h +++ b/src/lib/ffmpeg_decoder.h @@ -52,7 +52,7 @@ public: ~FFmpegDecoder (); void pass (); - void seek (VideoContent::Frame, bool); + void seek (Time time, bool); bool done () const; private: @@ -74,6 +74,11 @@ 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 diff --git a/src/lib/film.cc b/src/lib/film.cc index 5946d5bec..b61991a45 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -866,6 +866,12 @@ Film::content_paths_valid () const 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) { diff --git a/src/lib/film.h b/src/lib/film.h index 4b07f84a9..6573bd5b7 100644 --- a/src/lib/film.h +++ b/src/lib/film.h @@ -115,6 +115,7 @@ public: 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 ( diff --git a/src/lib/image_content.cc b/src/lib/image_content.cc index b05fa6b8d..8eba33f3a 100644 --- a/src/lib/image_content.cc +++ b/src/lib/image_content.cc @@ -126,7 +126,7 @@ ImageContent::full_length () const 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(); } diff --git a/src/lib/image_decoder.cc b/src/lib/image_decoder.cc index fb6053ae5..bf3bc344b 100644 --- a/src/lib/image_decoder.cc +++ b/src/lib/image_decoder.cc @@ -77,9 +77,9 @@ ImageDecoder::pass () } 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 diff --git a/src/lib/image_decoder.h b/src/lib/image_decoder.h index c7500243e..1f5f0b9d2 100644 --- a/src/lib/image_decoder.h +++ b/src/lib/image_decoder.h @@ -37,7 +37,7 @@ public: /* Decoder */ void pass (); - void seek (VideoContent::Frame, bool); + void seek (Time, bool); bool done () const; private: diff --git a/src/lib/player.cc b/src/lib/player.cc index 7f500b3d6..184c9811f 100644 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@ -235,7 +235,7 @@ Player::pass () _audio_position += _film->audio_frames_to_time (tb.audio->frames ()); } } - + return false; } @@ -259,7 +259,7 @@ Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image 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; } @@ -420,16 +420,12 @@ Player::seek (Time t, bool accurate) (*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 @@ -456,7 +452,7 @@ Player::setup_pieces () 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; } diff --git a/src/lib/playlist.cc b/src/lib/playlist.cc index 37b290218..581235adc 100644 --- a/src/lib/playlist.cc +++ b/src/lib/playlist.cc @@ -292,6 +292,23 @@ Playlist::video_end () const 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) { diff --git a/src/lib/playlist.h b/src/lib/playlist.h index f87b3397b..d3cf50a27 100644 --- a/src/lib/playlist.h +++ b/src/lib/playlist.h @@ -25,6 +25,7 @@ #include <boost/enable_shared_from_this.hpp> #include "ffmpeg_content.h" #include "audio_mapping.h" +#include "util.h" class Content; class FFmpegContent; @@ -74,6 +75,7 @@ public: 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 (); diff --git a/src/lib/sndfile_decoder.cc b/src/lib/sndfile_decoder.cc index e10f4f568..0cca25257 100644 --- a/src/lib/sndfile_decoder.cc +++ b/src/lib/sndfile_decoder.cc @@ -118,3 +118,9 @@ SndfileDecoder::done () const { return _audio_position >= _sndfile_content->audio_length (); } + +void +SndfileDecoder::seek (Time t, bool accurate) +{ + /* XXX */ +} diff --git a/src/lib/sndfile_decoder.h b/src/lib/sndfile_decoder.h index 77fa6d177..c3db1c294 100644 --- a/src/lib/sndfile_decoder.h +++ b/src/lib/sndfile_decoder.h @@ -30,6 +30,7 @@ public: ~SndfileDecoder (); void pass (); + void seek (Time, bool); bool done () const; int audio_channels () const; diff --git a/src/lib/util.cc b/src/lib/util.cc index ddc0a2974..d5a07192c 100644 --- a/src/lib/util.cc +++ b/src/lib/util.cc @@ -772,7 +772,7 @@ audio_channel_name (int c) return channels[c]; } -FrameRateConversion::FrameRateConversion (float source, int dcp) +FrameRateChange::FrameRateChange (float source, int dcp) : skip (false) , repeat (1) , change_speed (false) @@ -790,7 +790,8 @@ FrameRateConversion::FrameRateConversion (float source, int dcp) 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"); diff --git a/src/lib/util.h b/src/lib/util.h index 7dcd920b7..9b201a50e 100644 --- a/src/lib/util.h +++ b/src/lib/util.h @@ -79,9 +79,9 @@ extern std::string tidy_for_filename (std::string); 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. @@ -109,6 +109,11 @@ struct FrameRateConversion */ 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; }; diff --git a/src/lib/video_content.cc b/src/lib/video_content.cc index 0a19ffd69..c61a2455f 100644 --- a/src/lib/video_content.cc +++ b/src/lib/video_content.cc @@ -351,7 +351,8 @@ VideoContent::video_size_after_crop () const } /** @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 @@ -359,11 +360,12 @@ 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)); } diff --git a/src/lib/video_decoder.h b/src/lib/video_decoder.h index 142320a04..01319e481 100644 --- a/src/lib/video_decoder.h +++ b/src/lib/video_decoder.h @@ -34,11 +34,6 @@ class VideoDecoder : public virtual Decoder 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. diff --git a/src/wx/video_panel.cc b/src/wx/video_panel.cc index 5d841b0cc..4605abae7 100644 --- a/src/wx/video_panel.cc +++ b/src/wx/video_panel.cc @@ -332,7 +332,7 @@ VideoPanel::setup_description () 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; diff --git a/test/ffmpeg_seek_test.cc b/test/ffmpeg_seek_test.cc new file mode 100644 index 000000000..7014bf1b0 --- /dev/null +++ b/test/ffmpeg_seek_test.cc @@ -0,0 +1,106 @@ +/* + 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); +} + + diff --git a/test/frame_rate_test.cc b/test/frame_rate_test.cc index fdfdcf452..7d3904e74 100644 --- a/test/frame_rate_test.cc +++ b/test/frame_rate_test.cc @@ -47,91 +47,102 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) 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. @@ -144,34 +155,38 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) 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 */ @@ -181,14 +196,15 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) 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) @@ -266,7 +282,7 @@ BOOST_AUTO_TEST_CASE (audio_sampling_rate_test) 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)); diff --git a/test/long_ffmpeg_seek_test.cc b/test/long_ffmpeg_seek_test.cc new file mode 100644 index 000000000..5f7f6c2f2 --- /dev/null +++ b/test/long_ffmpeg_seek_test.cc @@ -0,0 +1,105 @@ +/* + 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); + } +} + + diff --git a/test/test.cc b/test/test.cc index 22dea1fc4..92ce95656 100644 --- a/test/test.cc +++ b/test/test.cc @@ -53,9 +53,9 @@ public: struct TestConfig { - TestConfig() + TestConfig () { - dcpomatic_setup(); + dcpomatic_setup (); Config::instance()->set_num_local_encoding_threads (1); Config::instance()->set_server_port_base (61920); diff --git a/test/wscript b/test/wscript index 4ae49b087..df9aa88d3 100644 --- a/test/wscript +++ b/test/wscript @@ -10,7 +10,7 @@ def configure(conf): """, 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' @@ -26,6 +26,7 @@ def build(bld): 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 @@ -46,3 +47,15 @@ def build(bld): 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 = '' |
