From 1b1bc528ee5ca1fee1bd33f9fb6f79cd551e3b33 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Tue, 4 Mar 2014 20:22:47 +0000 Subject: [PATCH] New DCPTime/ContentTime types. --- src/lib/analyse_audio_job.cc | 5 +- src/lib/analyse_audio_job.h | 3 +- src/lib/audio_content.cc | 24 ---- src/lib/audio_content.h | 4 +- src/lib/audio_decoder.cc | 11 +- src/lib/audio_decoder.h | 2 +- src/lib/audio_merger.cc | 92 +++++++++++++++ src/lib/audio_merger.h | 97 ++------------- src/lib/content.cc | 12 +- src/lib/content.h | 3 +- src/lib/dcpomatic_time.cc | 35 ++++++ src/lib/dcpomatic_time.h | 214 ++++++++++++++++++++++++++++++++++ src/lib/decoded.h | 87 +++++++------- src/lib/decoder.h | 1 + src/lib/ffmpeg_content.cc | 29 ++--- src/lib/ffmpeg_content.h | 2 +- src/lib/ffmpeg_decoder.cc | 38 +++--- src/lib/ffmpeg_decoder.h | 2 +- src/lib/ffmpeg_examiner.cc | 14 +-- src/lib/ffmpeg_examiner.h | 8 +- src/lib/film.cc | 30 +---- src/lib/film.h | 9 +- src/lib/frame_rate_change.cc | 91 +++++++++++++++ src/lib/frame_rate_change.h | 58 +++++++++ src/lib/image_content.cc | 6 +- src/lib/image_content.h | 2 +- src/lib/image_decoder.cc | 14 ++- src/lib/image_decoder.h | 2 +- src/lib/image_examiner.cc | 5 +- src/lib/image_examiner.h | 4 +- src/lib/player.cc | 64 +++++----- src/lib/player.h | 4 +- src/lib/playlist.cc | 12 +- src/lib/sndfile_content.cc | 13 +-- src/lib/sndfile_content.h | 4 +- src/lib/sndfile_decoder.cc | 8 +- src/lib/sndfile_decoder.h | 6 +- src/lib/subrip.cc | 12 +- src/lib/subrip_content.cc | 6 +- src/lib/subrip_decoder.cc | 4 +- src/lib/subrip_subtitle.h | 1 + src/lib/subtitle_decoder.cc | 2 +- src/lib/transcode_job.cc | 4 +- src/lib/types.h | 6 - src/lib/util.cc | 84 ------------- src/lib/util.h | 42 ------- src/lib/video_content.cc | 21 +--- src/lib/video_content.h | 8 +- src/lib/video_decoder.cc | 12 +- src/lib/video_decoder.h | 2 +- src/lib/video_examiner.h | 2 +- src/lib/writer.cc | 3 +- src/lib/wscript | 3 + src/wx/film_editor.cc | 2 +- src/wx/film_editor.h | 6 +- src/wx/film_viewer.cc | 12 +- src/wx/properties_dialog.cc | 4 +- src/wx/timecode.cc | 24 ++-- src/wx/timeline.cc | 124 ++++++++++---------- src/wx/timeline.h | 16 +-- src/wx/timeline_dialog.cc | 8 +- src/wx/timeline_dialog.h | 6 +- src/wx/timing_panel.cc | 17 +-- test/audio_merger_test.cc | 24 ++-- test/black_fill_test.cc | 8 +- test/ffmpeg_seek_test.cc | 14 +-- test/long_ffmpeg_seek_test.cc | 4 +- test/play_test.cc | 6 +- test/scaling_test.cc | 2 +- test/seek_zero_test.cc | 4 +- test/subrip_test.cc | 32 ++--- test/util_test.cc | 20 ++-- 72 files changed, 876 insertions(+), 684 deletions(-) create mode 100644 src/lib/audio_merger.cc create mode 100644 src/lib/dcpomatic_time.cc create mode 100644 src/lib/dcpomatic_time.h create mode 100644 src/lib/frame_rate_change.cc create mode 100644 src/lib/frame_rate_change.h diff --git a/src/lib/analyse_audio_job.cc b/src/lib/analyse_audio_job.cc index a6926bc14..fc0abe0a3 100644 --- a/src/lib/analyse_audio_job.cc +++ b/src/lib/analyse_audio_job.cc @@ -18,6 +18,7 @@ */ #include "audio_analysis.h" +#include "audio_buffers.h" #include "analyse_audio_job.h" #include "compose.hpp" #include "film.h" @@ -69,13 +70,13 @@ AnalyseAudioJob::run () player->Audio.connect (bind (&AnalyseAudioJob::audio, this, _1, _2)); - _samples_per_point = max (int64_t (1), _film->time_to_audio_frames (_film->length()) / _num_points); + int64_t const len = _film->length().frames (_film->audio_frame_rate()); + _samples_per_point = max (int64_t (1), len / _num_points); _current.resize (_film->audio_channels ()); _analysis.reset (new AudioAnalysis (_film->audio_channels ())); _done = 0; - AudioFrame const len = _film->time_to_audio_frames (_film->length ()); while (!player->pass ()) { set_progress (double (_done) / len); } diff --git a/src/lib/analyse_audio_job.h b/src/lib/analyse_audio_job.h index 3d3900a51..0a0be8fa0 100644 --- a/src/lib/analyse_audio_job.h +++ b/src/lib/analyse_audio_job.h @@ -20,6 +20,7 @@ #include "job.h" #include "audio_analysis.h" #include "types.h" +#include "dcpomatic_time.h" class AudioBuffers; class AudioContent; @@ -37,7 +38,7 @@ private: void audio (boost::shared_ptr, DCPTime); boost::weak_ptr _content; - AudioFrame _done; + int64_t _done; int64_t _samples_per_point; std::vector _current; diff --git a/src/lib/audio_content.cc b/src/lib/audio_content.cc index 3c0d13ba9..1def7e5cc 100644 --- a/src/lib/audio_content.cc +++ b/src/lib/audio_content.cc @@ -149,27 +149,3 @@ 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. - */ -AudioFrame -AudioContent::time_to_content_audio_frames (DCPTime t, DCPTime at) const -{ - shared_ptr 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 0b2ee2e46..18d88bccb 100644 --- a/src/lib/audio_content.h +++ b/src/lib/audio_content.h @@ -52,7 +52,7 @@ public: std::string technical_summary () const; virtual int audio_channels () const = 0; - virtual AudioFrame audio_length () const = 0; + virtual ContentTime audio_length () const = 0; virtual int content_audio_frame_rate () const = 0; virtual int output_audio_frame_rate () const = 0; virtual AudioMapping audio_mapping () const = 0; @@ -74,8 +74,6 @@ public: return _audio_delay; } - Frame time_to_content_audio_frames (DCPTime, DCPTime) const; - private: /** Gain to apply to audio in dB */ float _audio_gain; diff --git a/src/lib/audio_decoder.cc b/src/lib/audio_decoder.cc index 8d3b0e128..b0d0cc22f 100644 --- a/src/lib/audio_decoder.cc +++ b/src/lib/audio_decoder.cc @@ -60,12 +60,11 @@ AudioDecoder::audio (shared_ptr data, ContentTime time) if (!_audio_position) { shared_ptr film = _film.lock (); assert (film); - FrameRateChange frc = film->active_frame_rate_change (_audio_content->position ()); - _audio_position = (double (time) / frc.speed_up) * film->audio_frame_rate() / TIME_HZ; + _audio_position = time; } - _pending.push_back (shared_ptr (new DecodedAudio (data, _audio_position.get ()))); - _audio_position = _audio_position.get() + data->frames (); + _pending.push_back (shared_ptr (new DecodedAudio (_audio_position.get (), data))); + _audio_position = _audio_position.get() + ContentTime (data->frames (), _audio_content->output_audio_frame_rate ()); } void @@ -77,8 +76,8 @@ AudioDecoder::flush () shared_ptr b = _resampler->flush (); if (b) { - _pending.push_back (shared_ptr (new DecodedAudio (b, _audio_position.get ()))); - _audio_position = _audio_position.get() + b->frames (); + _pending.push_back (shared_ptr (new DecodedAudio (_audio_position.get (), b))); + _audio_position = _audio_position.get() + ContentTime (b->frames (), _audio_content->output_audio_frame_rate ()); } } diff --git a/src/lib/audio_decoder.h b/src/lib/audio_decoder.h index bb3aafccd..5b68a51a1 100644 --- a/src/lib/audio_decoder.h +++ b/src/lib/audio_decoder.h @@ -53,7 +53,7 @@ protected: boost::shared_ptr _audio_content; boost::shared_ptr _resampler; - boost::optional _audio_position; + boost::optional _audio_position; }; #endif diff --git a/src/lib/audio_merger.cc b/src/lib/audio_merger.cc new file mode 100644 index 000000000..d50420c31 --- /dev/null +++ b/src/lib/audio_merger.cc @@ -0,0 +1,92 @@ +/* + Copyright (C) 2013-2014 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + 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 "audio_buffers.h" +#include "audio_merger.h" + +using std::min; +using std::max; +using boost::shared_ptr; + +AudioMerger::AudioMerger (int channels, int frame_rate) + : _buffers (new AudioBuffers (channels, 0)) + , _frame_rate (frame_rate) + , _last_pull (0) +{ + +} + + +TimedAudioBuffers +AudioMerger::pull (DCPTime time) +{ + assert (time >= _last_pull); + + TimedAudioBuffers out; + + int64_t const to_return = DCPTime (time - _last_pull).frames (_frame_rate); + out.audio.reset (new AudioBuffers (_buffers->channels(), to_return)); + /* And this is how many we will get from our buffer */ + int64_t const to_return_from_buffers = min (to_return, int64_t (_buffers->frames ())); + + /* Copy the data that we have to the back end of the return buffer */ + out.audio->copy_from (_buffers.get(), to_return_from_buffers, 0, to_return - to_return_from_buffers); + /* Silence any gap at the start */ + out.audio->make_silent (0, to_return - to_return_from_buffers); + + out.time = _last_pull; + _last_pull = time; + + /* And remove the data we're returning from our buffers */ + if (_buffers->frames() > to_return_from_buffers) { + _buffers->move (to_return_from_buffers, 0, _buffers->frames() - to_return_from_buffers); + } + _buffers->set_frames (_buffers->frames() - to_return_from_buffers); + + return out; +} + +void +AudioMerger::push (shared_ptr audio, DCPTime time) +{ + assert (time >= _last_pull); + + int64_t frame = time.frames (_frame_rate); + int64_t after = max (int64_t (_buffers->frames()), frame + audio->frames() - _last_pull.frames (_frame_rate)); + _buffers->ensure_size (after); + _buffers->accumulate_frames (audio.get(), 0, frame - _last_pull.frames (_frame_rate), audio->frames ()); + _buffers->set_frames (after); +} + +TimedAudioBuffers +AudioMerger::flush () +{ + if (_buffers->frames() == 0) { + return TimedAudioBuffers (); + } + + return TimedAudioBuffers (_buffers, _last_pull); +} + +void +AudioMerger::clear (DCPTime t) +{ + _last_pull = t; + _buffers.reset (new AudioBuffers (_buffers->channels(), 0)); +} diff --git a/src/lib/audio_merger.h b/src/lib/audio_merger.h index f068b504e..121b21095 100644 --- a/src/lib/audio_merger.h +++ b/src/lib/audio_merger.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2013 Carl Hetherington + Copyright (C) 2013-2014 Carl Hetherington This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,102 +17,25 @@ */ -#include "audio_buffers.h" #include "util.h" -template +class AudioBuffers; + class AudioMerger { public: - AudioMerger (int channels, boost::function t_to_f, boost::function f_to_t) - : _buffers (new AudioBuffers (channels, 0)) - , _last_pull (0) - , _t_to_f (t_to_f) - , _f_to_t (f_to_t) - {} + AudioMerger (int channels, int frame_rate); /** Pull audio up to a given time; after this call, no more data can be pushed * before the specified time. */ - TimedAudioBuffers - pull (T time) - { - assert (time >= _last_pull); - - TimedAudioBuffers out; - - F const to_return = _t_to_f (time - _last_pull); - out.audio.reset (new AudioBuffers (_buffers->channels(), to_return)); - /* And this is how many we will get from our buffer */ - F const to_return_from_buffers = min (to_return, _buffers->frames ()); - - /* Copy the data that we have to the back end of the return buffer */ - out.audio->copy_from (_buffers.get(), to_return_from_buffers, 0, to_return - to_return_from_buffers); - /* Silence any gap at the start */ - out.audio->make_silent (0, to_return - to_return_from_buffers); - - out.time = _last_pull; - _last_pull = time; - - /* And remove the data we're returning from our buffers */ - if (_buffers->frames() > to_return_from_buffers) { - _buffers->move (to_return_from_buffers, 0, _buffers->frames() - to_return_from_buffers); - } - _buffers->set_frames (_buffers->frames() - to_return_from_buffers); - - return out; - } - - void - push (boost::shared_ptr audio, T time) - { - assert (time >= _last_pull); - - F frame = _t_to_f (time); - F after = max (_buffers->frames(), frame + audio->frames() - _t_to_f (_last_pull)); - _buffers->ensure_size (after); - _buffers->accumulate_frames (audio.get(), 0, frame - _t_to_f (_last_pull), audio->frames ()); - _buffers->set_frames (after); - } - - F min (F a, int b) - { - if (a < b) { - return a; - } - - return b; - } - - F max (int a, F b) - { - if (a > b) { - return a; - } - - return b; - } - - TimedAudioBuffers - flush () - { - if (_buffers->frames() == 0) { - return TimedAudioBuffers (); - } - - return TimedAudioBuffers (_buffers, _last_pull); - } - - void - clear (DCPTime t) - { - _last_pull = t; - _buffers.reset (new AudioBuffers (_buffers->channels(), 0)); - } + TimedAudioBuffers pull (DCPTime time); + void push (boost::shared_ptr audio, DCPTime time); + TimedAudioBuffers flush (); + void clear (DCPTime t); private: boost::shared_ptr _buffers; - T _last_pull; - boost::function _t_to_f; - boost::function _f_to_t; + int _frame_rate; + DCPTime _last_pull; }; diff --git a/src/lib/content.cc b/src/lib/content.cc index 8e3b99da8..4493c67c0 100644 --- a/src/lib/content.cc +++ b/src/lib/content.cc @@ -83,9 +83,9 @@ Content::Content (shared_ptr f, shared_ptr node) _paths.push_back ((*i)->content ()); } _digest = node->string_child ("Digest"); - _position = node->number_child ("Position"); - _trim_start = node->number_child ("TrimStart"); - _trim_end = node->number_child ("TrimEnd"); + _position = DCPTime (node->number_child ("Position")); + _trim_start = DCPTime (node->number_child ("TrimStart")); + _trim_end = DCPTime (node->number_child ("TrimEnd")); } Content::Content (shared_ptr f, vector > c) @@ -119,9 +119,9 @@ Content::as_xml (xmlpp::Node* node) const node->add_child("Path")->add_child_text (i->string ()); } node->add_child("Digest")->add_child_text (_digest); - node->add_child("Position")->add_child_text (lexical_cast (_position)); - node->add_child("TrimStart")->add_child_text (lexical_cast (_trim_start)); - node->add_child("TrimEnd")->add_child_text (lexical_cast (_trim_end)); + node->add_child("Position")->add_child_text (lexical_cast (_position.get ())); + node->add_child("TrimStart")->add_child_text (lexical_cast (_trim_start.get ())); + node->add_child("TrimEnd")->add_child_text (lexical_cast (_trim_end.get ())); } void diff --git a/src/lib/content.h b/src/lib/content.h index 78a41e306..fc3a531fa 100644 --- a/src/lib/content.h +++ b/src/lib/content.h @@ -27,6 +27,7 @@ #include #include #include "types.h" +#include "dcpomatic_time.h" namespace cxml { class Node; @@ -120,7 +121,7 @@ public: } DCPTime end () const { - return position() + length_after_trim() - 1; + return position() + length_after_trim(); } DCPTime length_after_trim () const; diff --git a/src/lib/dcpomatic_time.cc b/src/lib/dcpomatic_time.cc new file mode 100644 index 000000000..5344915c0 --- /dev/null +++ b/src/lib/dcpomatic_time.cc @@ -0,0 +1,35 @@ +/* + Copyright (C) 2014 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + 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 "dcpomatic_time.h" + +ContentTime::ContentTime (DCPTime d, FrameRateChange f) + : Time (rint (d.get() * f.speed_up)) +{ + +} + +DCPTime min (DCPTime a, DCPTime b) +{ + if (a < b) { + return a; + } + + return b; +} diff --git a/src/lib/dcpomatic_time.h b/src/lib/dcpomatic_time.h new file mode 100644 index 000000000..05b4e1a5d --- /dev/null +++ b/src/lib/dcpomatic_time.h @@ -0,0 +1,214 @@ +/* + Copyright (C) 2014 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + 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. + +*/ + +#ifndef DCPOMATIC_TIME_H +#define DCPOMATIC_TIME_H + +#include +#include +#include "frame_rate_change.h" + +class dcpomatic_round_up_test; + +class Time; + +/** A time in seconds, expressed as a number scaled up by Time::HZ. */ +class Time +{ +public: + Time () + : _t (0) + {} + + explicit Time (int64_t t) + : _t (t) + {} + + virtual ~Time () {} + + int64_t get () const { + return _t; + } + + double seconds () const { + return double (_t) / HZ; + } + + template + int64_t frames (T r) const { + return rint (_t * r / HZ); + } + + operator bool () const { + return _t != 0; + } + +protected: + friend class dcptime_round_up_test; + + int64_t _t; + static const int HZ = 96000; +}; + +class DCPTime; + +class ContentTime : public Time +{ +public: + ContentTime () : Time () {} + explicit ContentTime (int64_t t) : Time (t) {} + ContentTime (int64_t n, int64_t d) : Time (n * HZ / d) {} + ContentTime (DCPTime d, FrameRateChange f); + + bool operator< (ContentTime const & o) const { + return _t < o._t; + } + + bool operator<= (ContentTime const & o) const { + return _t <= o._t; + } + + bool operator== (ContentTime const & o) const { + return _t == o._t; + } + + bool operator!= (ContentTime const & o) const { + return _t != o._t; + } + + bool operator>= (ContentTime const & o) const { + return _t >= o._t; + } + + bool operator> (ContentTime const & o) const { + return _t > o._t; + } + + ContentTime operator+ (ContentTime const & o) const { + return ContentTime (_t + o._t); + } + + ContentTime & operator+= (ContentTime const & o) { + _t += o._t; + return *this; + } + + ContentTime operator- (ContentTime const & o) const { + return ContentTime (_t - o._t); + } + + ContentTime & operator-= (ContentTime const & o) { + _t -= o._t; + return *this; + } + + static ContentTime from_seconds (double s) { + return ContentTime (s * HZ); + } + + template + static ContentTime from_frames (int64_t f, T r) { + return ContentTime (f * HZ / r); + } +}; + +class DCPTime : public Time +{ +public: + DCPTime () : Time () {} + explicit DCPTime (int64_t t) : Time (t) {} + DCPTime (ContentTime t, FrameRateChange c) : Time (rint (t.get() / c.speed_up)) {} + + bool operator< (DCPTime const & o) const { + return _t < o._t; + } + + bool operator<= (DCPTime const & o) const { + return _t <= o._t; + } + + bool operator== (DCPTime const & o) const { + return _t == o._t; + } + + bool operator!= (DCPTime const & o) const { + return _t != o._t; + } + + bool operator>= (DCPTime const & o) const { + return _t >= o._t; + } + + bool operator> (DCPTime const & o) const { + return _t > o._t; + } + + DCPTime operator+ (DCPTime const & o) const { + return DCPTime (_t + o._t); + } + + DCPTime & operator+= (DCPTime const & o) { + _t += o._t; + return *this; + } + + DCPTime operator- (DCPTime const & o) const { + return DCPTime (_t - o._t); + } + + DCPTime & operator-= (DCPTime const & o) { + _t -= o._t; + return *this; + } + + /** Round up to the nearest sampling interval + * at some sampling rate. + * @param r Sampling rate. + */ + DCPTime round_up (int r) { + int64_t const n = HZ / r; + int64_t const a = _t + n - 1; + return DCPTime (a - (a % n)); + } + + DCPTime abs () const { + return DCPTime (std::abs (_t)); + } + + static DCPTime from_seconds (double s) { + return DCPTime (s * HZ); + } + + template + static DCPTime from_frames (int64_t f, T r) { + return DCPTime (f * HZ / r); + } + + static DCPTime delta () { + return DCPTime (1); + } + + static DCPTime max () { + return DCPTime (INT64_MAX); + } +}; + +DCPTime min (DCPTime a, DCPTime b); + +#endif diff --git a/src/lib/decoded.h b/src/lib/decoded.h index 2325b29c8..30fe89988 100644 --- a/src/lib/decoded.h +++ b/src/lib/decoded.h @@ -31,13 +31,23 @@ class Decoded { public: Decoded () - : dcp_time (0) + : content_time (0) + , dcp_time (0) + {} + + Decoded (ContentTime t) + : content_time (t) + , dcp_time (0) {} virtual ~Decoded () {} - virtual void set_dcp_times (VideoFrame, AudioFrame, FrameRateChange, DCPTime) = 0; + virtual void set_dcp_times (FrameRateChange frc, DCPTime offset) + { + dcp_time = DCPTime (content_time, frc) + offset; + } + ContentTime content_time; DCPTime dcp_time; }; @@ -48,98 +58,85 @@ public: DecodedVideo () : eyes (EYES_BOTH) , same (false) - , frame (0) {} - DecodedVideo (boost::shared_ptr im, Eyes e, bool s, VideoFrame f) - : image (im) + DecodedVideo (ContentTime t, boost::shared_ptr im, Eyes e, bool s) + : Decoded (t) + , image (im) , eyes (e) , same (s) - , frame (f) {} - void set_dcp_times (VideoFrame video_frame_rate, AudioFrame, FrameRateChange frc, DCPTime offset) - { - dcp_time = frame * TIME_HZ * frc.factor() / video_frame_rate + offset; - } - boost::shared_ptr image; Eyes eyes; bool same; - VideoFrame frame; }; class DecodedAudio : public Decoded { public: - DecodedAudio (boost::shared_ptr d, AudioFrame f) - : data (d) - , frame (f) + DecodedAudio (ContentTime t, boost::shared_ptr d) + : Decoded (t) + , data (d) {} - - void set_dcp_times (VideoFrame, AudioFrame audio_frame_rate, FrameRateChange, DCPTime offset) - { - dcp_time = frame * TIME_HZ / audio_frame_rate + offset; - } boost::shared_ptr data; - AudioFrame frame; }; class DecodedImageSubtitle : public Decoded { public: DecodedImageSubtitle () - : content_time (0) - , content_time_to (0) + : content_time_to (0) , dcp_time_to (0) {} - DecodedImageSubtitle (boost::shared_ptr im, dcpomatic::Rect r, ContentTime f, ContentTime t) - : image (im) - , rect (r) - , content_time (f) + DecodedImageSubtitle (ContentTime f, ContentTime t, boost::shared_ptr im, dcpomatic::Rect r) + : Decoded (f) , content_time_to (t) , dcp_time_to (0) + , image (im) + , rect (r) {} - void set_dcp_times (VideoFrame, AudioFrame, FrameRateChange frc, DCPTime offset) + void set_dcp_times (FrameRateChange frc, DCPTime offset) { - dcp_time = rint (content_time / frc.speed_up) + offset; - dcp_time_to = rint (content_time_to / frc.speed_up) + offset; + Decoded::set_dcp_times (frc, offset); + dcp_time_to = DCPTime (content_time_to, frc) + offset; } - boost::shared_ptr image; - dcpomatic::Rect rect; - ContentTime content_time; ContentTime content_time_to; DCPTime dcp_time_to; + boost::shared_ptr image; + dcpomatic::Rect rect; }; class DecodedTextSubtitle : public Decoded { public: DecodedTextSubtitle () - : dcp_time_to (0) + : content_time_to (0) + , dcp_time_to (0) {} + /* Assuming that all subs are at the same time */ DecodedTextSubtitle (std::list s) - : subs (s) - {} - - void set_dcp_times (VideoFrame, AudioFrame, FrameRateChange frc, DCPTime offset) + : Decoded (ContentTime::from_seconds (subs.front().in().to_ticks() * 4 / 1000.0)) + , content_time_to (ContentTime::from_seconds (subs.front().out().to_ticks() * 4 / 1000.0)) + , subs (s) { - if (subs.empty ()) { - return; - } + + } - /* Assuming that all subs are at the same time */ - dcp_time = rint (subs.front().in().to_ticks() * 4 * TIME_HZ / frc.speed_up) + offset; - dcp_time_to = rint (subs.front().out().to_ticks() * 4 * TIME_HZ / frc.speed_up) + offset; + void set_dcp_times (FrameRateChange frc, DCPTime offset) + { + Decoded::set_dcp_times (frc, offset); + dcp_time_to = DCPTime (content_time_to, frc) + offset; } - std::list subs; + ContentTime content_time_to; DCPTime dcp_time_to; + std::list subs; }; #endif diff --git a/src/lib/decoder.h b/src/lib/decoder.h index 562073c1e..aeb39d0b2 100644 --- a/src/lib/decoder.h +++ b/src/lib/decoder.h @@ -28,6 +28,7 @@ #include #include #include "types.h" +#include "dcpomatic_time.h" class Film; class Decoded; diff --git a/src/lib/ffmpeg_content.cc b/src/lib/ffmpeg_content.cc index 3df1ba57e..4a6cf9e04 100644 --- a/src/lib/ffmpeg_content.cc +++ b/src/lib/ffmpeg_content.cc @@ -163,9 +163,8 @@ FFmpegContent::examine (shared_ptr job) shared_ptr examiner (new FFmpegExaminer (shared_from_this ())); - VideoFrame video_length = 0; - video_length = examiner->video_length (); - film->log()->log (String::compose ("Video length obtained from header as %1 frames", video_length)); + ContentTime video_length = examiner->video_length (); + film->log()->log (String::compose ("Video length obtained from header as %1 frames", video_length.frames (video_frame_rate ()))); { boost::mutex::scoped_lock lm (_mutex); @@ -228,7 +227,7 @@ FFmpegContent::technical_summary () const string FFmpegContent::information () const { - if (video_length() == 0 || video_frame_rate() == 0) { + if (video_length() == ContentTime (0) || video_frame_rate() == ContentTime (0)) { return ""; } @@ -262,19 +261,17 @@ FFmpegContent::set_audio_stream (shared_ptr s) signal_changed (FFmpegContentProperty::AUDIO_STREAM); } -AudioFrame +ContentTime FFmpegContent::audio_length () const { - int const cafr = content_audio_frame_rate (); - int const vfr = video_frame_rate (); - VideoFrame const vl = video_length (); - - boost::mutex::scoped_lock lm (_mutex); - if (!_audio_stream) { - return 0; + { + boost::mutex::scoped_lock lm (_mutex); + if (!_audio_stream) { + return ContentTime (); + } } - - return video_frames_to_audio_frames (vl, cafr, vfr); + + return video_length(); } int @@ -421,9 +418,7 @@ FFmpegContent::full_length () const { shared_ptr film = _film.lock (); assert (film); - - FrameRateChange frc (video_frame_rate (), film->video_frame_rate ()); - return video_length() * frc.factor() * TIME_HZ / film->video_frame_rate (); + return DCPTime (video_length(), FrameRateChange (video_frame_rate (), film->video_frame_rate ())); } AudioMapping diff --git a/src/lib/ffmpeg_content.h b/src/lib/ffmpeg_content.h index d588fd2ee..d93660cd0 100644 --- a/src/lib/ffmpeg_content.h +++ b/src/lib/ffmpeg_content.h @@ -145,7 +145,7 @@ public: /* AudioContent */ int audio_channels () const; - AudioFrame audio_length () const; + ContentTime audio_length () const; int content_audio_frame_rate () const; int output_audio_frame_rate () const; AudioMapping audio_mapping () const; diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc index d9729ee1a..97251b6c2 100644 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@ -317,9 +317,7 @@ FFmpegDecoder::minimal_run (boost::function, optiona int finished = 0; r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet); if (r >= 0 && finished) { - last_video = rint ( - (av_frame_get_best_effort_timestamp (_frame) * time_base + _pts_offset) * TIME_HZ - ); + last_video = ContentTime::from_seconds (av_frame_get_best_effort_timestamp (_frame) * time_base + _pts_offset); } } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, _packet.stream_index)) { @@ -329,9 +327,7 @@ FFmpegDecoder::minimal_run (boost::function, optiona int finished; r = avcodec_decode_audio4 (audio_codec_context(), _frame, &finished, &_packet); if (r >= 0 && finished) { - last_audio = rint ( - (av_frame_get_best_effort_timestamp (_frame) * time_base + _pts_offset) * TIME_HZ - ); + last_audio = ContentTime::from_seconds (av_frame_get_best_effort_timestamp (_frame) * time_base + _pts_offset); } copy_packet.data += r; @@ -360,20 +356,14 @@ FFmpegDecoder::seek_final_finished (int n, int done) const void FFmpegDecoder::seek_and_flush (ContentTime t) { - int64_t s = ((double (t) / TIME_HZ) - _pts_offset) / - av_q2d (_format_context->streams[_video_stream]->time_base); + int64_t s = (t.seconds() - _pts_offset) / av_q2d (_format_context->streams[_video_stream]->time_base); if (_ffmpeg_content->audio_stream ()) { s = min ( - s, int64_t ( - ((double (t) / TIME_HZ) - _pts_offset) / - av_q2d (_ffmpeg_content->audio_stream()->stream(_format_context)->time_base) - ) + s, int64_t ((t.seconds() - _pts_offset) / av_q2d (_ffmpeg_content->audio_stream()->stream(_format_context)->time_base)) ); } - cout << "S&F " << t << "\n"; - /* Ridiculous empirical hack */ s--; if (s < 0) { @@ -404,10 +394,10 @@ FFmpegDecoder::seek (ContentTime time, bool accurate) will hopefully then step through to where we want to be. */ - ContentTime pre_roll = accurate ? (0.2 * TIME_HZ) : 0; + ContentTime pre_roll = accurate ? ContentTime::from_seconds (0.2) : ContentTime (0); ContentTime initial_seek = time - pre_roll; - if (initial_seek < 0) { - initial_seek = 0; + if (initial_seek < ContentTime (0)) { + initial_seek = ContentTime (0); } /* Initial seek time in the video stream's timebase */ @@ -449,11 +439,11 @@ FFmpegDecoder::decode_audio_packet () } if (frame_finished) { - ContentTime const ct = ( + ContentTime const ct = ContentTime::from_seconds ( av_frame_get_best_effort_timestamp (_frame) * av_q2d (_ffmpeg_content->audio_stream()->stream (_format_context)->time_base) + _pts_offset - ) * TIME_HZ; + ); int const data_size = av_samples_get_buffer_size ( 0, audio_codec_context()->channels, _frame->nb_samples, audio_sample_format (), 1 @@ -508,9 +498,7 @@ FFmpegDecoder::decode_video_packet () } if (i->second != AV_NOPTS_VALUE) { - double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset; - VideoFrame const f = rint (pts * _ffmpeg_content->video_frame_rate ()); - video (image, false, f); + video (image, false, ContentTime::from_seconds (i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset)); } else { shared_ptr film = _film.lock (); assert (film); @@ -560,7 +548,7 @@ FFmpegDecoder::decode_subtitle_packet () indicate that the previous subtitle should stop. */ if (sub.num_rects <= 0) { - image_subtitle (shared_ptr (), dcpomatic::Rect (), 0, 0); + image_subtitle (shared_ptr (), dcpomatic::Rect (), ContentTime (), ContentTime ()); return; } else if (sub.num_rects > 1) { throw DecodeError (_("multi-part subtitles not yet supported")); @@ -572,8 +560,8 @@ FFmpegDecoder::decode_subtitle_packet () double const packet_time = (static_cast (sub.pts) / AV_TIME_BASE) + _pts_offset; /* hence start time for this sub */ - ContentTime const from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ; - ContentTime const to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ; + ContentTime const from = ContentTime::from_seconds (packet_time + (double (sub.start_display_time) / 1e3)); + ContentTime const to = ContentTime::from_seconds (packet_time + (double (sub.end_display_time) / 1e3)); AVSubtitleRect const * rect = sub.rects[0]; diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h index ee725b20c..ae9eb5084 100644 --- a/src/lib/ffmpeg_decoder.h +++ b/src/lib/ffmpeg_decoder.h @@ -74,7 +74,7 @@ private: bool seek_overrun_finished (ContentTime, boost::optional, boost::optional) const; bool seek_final_finished (int, int) const; int minimal_run (boost::function, boost::optional, int)>); - void seek_and_flush (int64_t); + void seek_and_flush (ContentTime); 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/ffmpeg_examiner.cc b/src/lib/ffmpeg_examiner.cc index e439566a1..3de62ad07 100644 --- a/src/lib/ffmpeg_examiner.cc +++ b/src/lib/ffmpeg_examiner.cc @@ -102,14 +102,14 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr c) } } -optional +optional FFmpegExaminer::frame_time (AVStream* s) const { - optional t; + optional t; int64_t const bet = av_frame_get_best_effort_timestamp (_frame); if (bet != AV_NOPTS_VALUE) { - t = bet * av_q2d (s->time_base); + t = ContentTime (bet * av_q2d (s->time_base)); } return t; @@ -133,12 +133,12 @@ FFmpegExaminer::video_size () const return dcp::Size (video_codec_context()->width, video_codec_context()->height); } -/** @return Length (in video frames) according to our content's header */ -VideoFrame +/** @return Length according to our content's header */ +ContentTime FFmpegExaminer::video_length () const { - VideoFrame const length = (double (_format_context->duration) / AV_TIME_BASE) * video_frame_rate(); - return max (1, length); + ContentTime const length = ContentTime::from_seconds (double (_format_context->duration) / AV_TIME_BASE); + return ContentTime (1, length.get ()); } string diff --git a/src/lib/ffmpeg_examiner.h b/src/lib/ffmpeg_examiner.h index 81275a9e1..381c5cea9 100644 --- a/src/lib/ffmpeg_examiner.h +++ b/src/lib/ffmpeg_examiner.h @@ -31,7 +31,7 @@ public: float video_frame_rate () const; dcp::Size video_size () const; - VideoFrame video_length () const; + ContentTime video_length () const; std::vector > subtitle_streams () const { return _subtitle_streams; @@ -41,7 +41,7 @@ public: return _audio_streams; } - boost::optional first_video () const { + boost::optional first_video () const { return _first_video; } @@ -49,9 +49,9 @@ private: std::string stream_name (AVStream* s) const; std::string audio_stream_name (AVStream* s) const; std::string subtitle_stream_name (AVStream* s) const; - boost::optional frame_time (AVStream* s) const; + boost::optional frame_time (AVStream* s) const; std::vector > _subtitle_streams; std::vector > _audio_streams; - boost::optional _first_video; + boost::optional _first_video; }; diff --git a/src/lib/film.cc b/src/lib/film.cc index 4b3f6a8dd..11fb9e4b0 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -873,7 +873,7 @@ Film::has_subtitles () const return _playlist->has_subtitles (); } -VideoFrame +int Film::best_video_frame_rate () const { return _playlist->best_dcp_frame_rate (); @@ -903,31 +903,7 @@ Film::playlist_changed () signal_changed (CONTENT); } -AudioFrame -Film::time_to_audio_frames (DCPTime t) const -{ - return divide_with_round (t * audio_frame_rate (), TIME_HZ); -} - -VideoFrame -Film::time_to_video_frames (DCPTime t) const -{ - return divide_with_round (t * video_frame_rate (), TIME_HZ); -} - -DCPTime -Film::audio_frames_to_time (AudioFrame f) const -{ - return divide_with_round (f * TIME_HZ, audio_frame_rate ()); -} - -DCPTime -Film::video_frames_to_time (VideoFrame f) const -{ - return divide_with_round (f * TIME_HZ, video_frame_rate ()); -} - -AudioFrame +int Film::audio_frame_rate () const { /* XXX */ @@ -1006,7 +982,7 @@ Film::make_kdms ( uint64_t Film::required_disk_space () const { - return uint64_t (j2k_bandwidth() / 8) * length() / TIME_HZ; + return uint64_t (j2k_bandwidth() / 8) * length().seconds(); } /** This method checks the disk that the Film is on and tries to decide whether or not diff --git a/src/lib/film.h b/src/lib/film.h index 51e272faa..f776a3f72 100644 --- a/src/lib/film.h +++ b/src/lib/film.h @@ -102,12 +102,7 @@ public: boost::shared_ptr make_player () const; boost::shared_ptr playlist () const; - AudioFrame audio_frame_rate () const; - - AudioFrame time_to_audio_frames (DCPTime) const; - VideoFrame time_to_video_frames (DCPTime) const; - DCPTime video_frames_to_time (VideoFrame) const; - DCPTime audio_frames_to_time (AudioFrame) const; + int audio_frame_rate () const; uint64_t required_disk_space () const; bool should_be_enough_disk_space (double &, double &) const; @@ -117,7 +112,7 @@ public: ContentList content () const; DCPTime length () const; bool has_subtitles () const; - VideoFrame best_video_frame_rate () const; + int best_video_frame_rate () const; FrameRateChange active_frame_rate_change (DCPTime) const; dcp::KDM diff --git a/src/lib/frame_rate_change.cc b/src/lib/frame_rate_change.cc new file mode 100644 index 000000000..3e9c4b505 --- /dev/null +++ b/src/lib/frame_rate_change.cc @@ -0,0 +1,91 @@ +/* + Copyright (C) 2012-2014 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + 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 +#include "frame_rate_change.h" +#include "compose.hpp" + +#include "i18n.h" + +static bool +about_equal (float a, float b) +{ + /* A film of F seconds at f FPS will be Ff frames; + Consider some delta FPS d, so if we run the same + film at (f + d) FPS it will last F(f + d) seconds. + + Hence the difference in length over the length of the film will + be F(f + d) - Ff frames + = Ff + Fd - Ff frames + = Fd frames + = Fd/f seconds + + So if we accept a difference of 1 frame, ie 1/f seconds, we can + say that + + 1/f = Fd/f + ie 1 = Fd + ie d = 1/F + + So for a 3hr film, ie F = 3 * 60 * 60 = 10800, the acceptable + FPS error is 1/F ~= 0.0001 ~= 10-e4 + */ + + return (fabs (a - b) < 1e-4); +} + + +FrameRateChange::FrameRateChange (float source, int dcp) + : skip (false) + , repeat (1) + , change_speed (false) +{ + if (fabs (source / 2.0 - dcp) < fabs (source - dcp)) { + /* The difference between source and DCP frame rate will be lower + (i.e. better) if we skip. + */ + skip = true; + } else if (fabs (source * 2 - dcp) < fabs (source - dcp)) { + /* The difference between source and DCP frame rate would be better + if we repeated each frame once; it may be better still if we + repeated more than once. Work out the required repeat. + */ + repeat = round (dcp / source); + } + + 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"); + } else { + if (skip) { + description = _("DCP will use every other frame of the content.\n"); + } else if (repeat == 2) { + description = _("Each content frame will be doubled in the DCP.\n"); + } else if (repeat > 2) { + description = String::compose (_("Each content frame will be repeated %1 more times in the DCP.\n"), repeat - 1); + } + + if (change_speed) { + float const pc = dcp * 100 / (source * factor()); + description += String::compose (_("DCP will run at %1%% of the content speed.\n"), pc); + } + } +} diff --git a/src/lib/frame_rate_change.h b/src/lib/frame_rate_change.h new file mode 100644 index 000000000..6165f6840 --- /dev/null +++ b/src/lib/frame_rate_change.h @@ -0,0 +1,58 @@ +/* + Copyright (C) 2012-2014 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + 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 + +struct FrameRateChange +{ + 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. + */ + float factor () const { + if (skip) { + return 0.5; + } + + return repeat; + } + + /** true to skip every other frame */ + bool skip; + /** number of times to use each frame (e.g. 1 is normal, 2 means repeat each frame once, and so on) */ + int repeat; + /** true if this DCP will run its video faster or slower than the source + * without taking into account `repeat' nor `skip'. + * (e.g. change_speed will be true if + * source is 29.97fps, DCP is 30fps + * source is 14.50fps, DCP is 30fps + * but not if + * source is 15.00fps, DCP is 30fps + * source is 12.50fps, DCP is 25fps) + */ + 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/image_content.cc b/src/lib/image_content.cc index a7f951bea..2713280b4 100644 --- a/src/lib/image_content.cc +++ b/src/lib/image_content.cc @@ -110,7 +110,7 @@ ImageContent::examine (shared_ptr job) } void -ImageContent::set_video_length (VideoFrame len) +ImageContent::set_video_length (ContentTime len) { { boost::mutex::scoped_lock lm (_mutex); @@ -125,9 +125,7 @@ ImageContent::full_length () const { shared_ptr film = _film.lock (); assert (film); - - FrameRateChange frc (video_frame_rate(), film->video_frame_rate ()); - return video_length() * frc.factor() * TIME_HZ / video_frame_rate(); + return DCPTime (video_length(), FrameRateChange (video_frame_rate(), film->video_frame_rate())); } string diff --git a/src/lib/image_content.h b/src/lib/image_content.h index f929e2b6f..1aff043d2 100644 --- a/src/lib/image_content.h +++ b/src/lib/image_content.h @@ -45,7 +45,7 @@ public: std::string identifier () const; - void set_video_length (VideoFrame); + void set_video_length (ContentTime); bool still () const; void set_video_frame_rate (float); }; diff --git a/src/lib/image_decoder.cc b/src/lib/image_decoder.cc index d8d551ef7..c9b17add8 100644 --- a/src/lib/image_decoder.cc +++ b/src/lib/image_decoder.cc @@ -36,7 +36,6 @@ ImageDecoder::ImageDecoder (shared_ptr f, shared_ptrstill ()) { video (_image, true, _video_position); - ++_video_position; + _video_position += ContentTime::from_frames (1, _image_content->video_frame_rate ()); return false; } Magick::Image* magick_image = 0; - boost::filesystem::path const path = _image_content->path (_image_content->still() ? 0 : _video_position); + + boost::filesystem::path const path = _image_content->path ( + _image_content->still() ? 0 : _video_position.frames (_image_content->video_frame_rate ()) + ); + try { magick_image = new Magick::Image (path.string ()); } catch (...) { @@ -83,7 +86,7 @@ ImageDecoder::pass () delete magick_image; video (_image, false, _video_position); - ++_video_position; + _video_position += ContentTime::from_frames (1, _image_content->video_frame_rate ()); return false; } @@ -92,6 +95,5 @@ void ImageDecoder::seek (ContentTime time, bool accurate) { Decoder::seek (time, accurate); - - _video_position = rint (time * _video_content->video_frame_rate() / TIME_HZ); + _video_position = time; } diff --git a/src/lib/image_decoder.h b/src/lib/image_decoder.h index 63b4c58e3..f4d81dfb5 100644 --- a/src/lib/image_decoder.h +++ b/src/lib/image_decoder.h @@ -41,6 +41,6 @@ private: boost::shared_ptr _image_content; boost::shared_ptr _image; - VideoFrame _video_position; + ContentTime _video_position; }; diff --git a/src/lib/image_examiner.cc b/src/lib/image_examiner.cc index 47b220da7..4cba788bd 100644 --- a/src/lib/image_examiner.cc +++ b/src/lib/image_examiner.cc @@ -39,7 +39,6 @@ using boost::bad_lexical_cast; ImageExaminer::ImageExaminer (shared_ptr film, shared_ptr content, shared_ptr) : _film (film) , _image_content (content) - , _video_length (0) { using namespace MagickCore; Magick::Image* image = new Magick::Image (content->path(0).string()); @@ -47,9 +46,9 @@ ImageExaminer::ImageExaminer (shared_ptr film, shared_ptrstill ()) { - _video_length = Config::instance()->default_still_length() * video_frame_rate(); + _video_length = ContentTime (Config::instance()->default_still_length()); } else { - _video_length = _image_content->number_of_paths (); + _video_length = ContentTime (double (_image_content->number_of_paths ()) / video_frame_rate ()); } } diff --git a/src/lib/image_examiner.h b/src/lib/image_examiner.h index 1800f1ea3..6ae0422cb 100644 --- a/src/lib/image_examiner.h +++ b/src/lib/image_examiner.h @@ -32,7 +32,7 @@ public: float video_frame_rate () const; dcp::Size video_size () const; - VideoFrame video_length () const { + ContentTime video_length () const { return _video_length; } @@ -40,5 +40,5 @@ private: boost::weak_ptr _film; boost::shared_ptr _image_content; boost::optional _video_size; - VideoFrame _video_length; + ContentTime _video_length; }; diff --git a/src/lib/player.cc b/src/lib/player.cc index 5fd3b8ffa..fc2f32d40 100644 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@ -22,6 +22,7 @@ #include "player.h" #include "film.h" #include "ffmpeg_decoder.h" +#include "audio_buffers.h" #include "ffmpeg_content.h" #include "image_decoder.h" #include "image_content.h" @@ -72,7 +73,7 @@ Player::Player (shared_ptr f, shared_ptr p) , _have_valid_pieces (false) , _video_position (0) , _audio_position (0) - , _audio_merger (f->audio_channels(), bind (&Film::time_to_audio_frames, f.get(), _1), bind (&Film::audio_frames_to_time, f.get(), _1)) + , _audio_merger (f->audio_channels(), f->audio_frame_rate ()) , _last_emit_was_black (false) , _just_did_inaccurate_seek (false) , _approximate_size (false) @@ -106,8 +107,8 @@ Player::pass () shared_ptr earliest_piece; shared_ptr earliest_decoded; - DCPTime earliest_time = TIME_MAX; - DCPTime earliest_audio = TIME_MAX; + DCPTime earliest_time = DCPTime::max (); + DCPTime earliest_audio = DCPTime::max (); for (list >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) { @@ -123,7 +124,7 @@ Player::pass () } - dec->set_dcp_times (_film->video_frame_rate(), _film->audio_frame_rate(), (*i)->frc, offset); + dec->set_dcp_times ((*i)->frc, offset); DCPTime const t = dec->dcp_time - offset; cout << "Peeked " << (*i)->content->paths()[0] << " for " << t << " cf " << ((*i)->content->full_length() - (*i)->content->trim_end ()) << "\n"; if (t >= ((*i)->content->full_length() - (*i)->content->trim_end ())) { @@ -159,13 +160,16 @@ Player::pass () return true; } - if (earliest_audio != TIME_MAX) { - TimedAudioBuffers tb = _audio_merger.pull (max (int64_t (0), earliest_audio)); + if (earliest_audio != DCPTime::max ()) { + if (earliest_audio.get() < 0) { + earliest_audio = DCPTime (); + } + TimedAudioBuffers tb = _audio_merger.pull (earliest_audio); Audio (tb.audio, tb.time); - /* This assumes that the audio_frames_to_time conversion is exact + /* This assumes that the audio-frames-to-time conversion is exact so that there are no accumulated errors caused by rounding. */ - _audio_position += _film->audio_frames_to_time (tb.audio->frames ()); + _audio_position += DCPTime::from_frames (tb.audio->frames(), _film->audio_frame_rate ()); } /* Emit the earliest thing */ @@ -175,16 +179,6 @@ Player::pass () shared_ptr dis = dynamic_pointer_cast (earliest_decoded); shared_ptr dts = dynamic_pointer_cast (earliest_decoded); - if (dv) { - cout << "Video @ " << dv->dcp_time << " " << (double(dv->dcp_time) / TIME_HZ) << ".\n"; - } else if (da) { - cout << "Audio.\n"; - } else if (dis) { - cout << "Image sub.\n"; - } else if (dts) { - cout << "Text sub.\n"; - } - /* Will be set to false if we shouldn't consume the peeked DecodedThing */ bool consume = true; @@ -327,10 +321,10 @@ Player::step_video_position (shared_ptr video) { /* This is a bit of a hack; don't update _video_position if EYES_RIGHT is on its way */ if (video->eyes != EYES_LEFT) { - /* This assumes that the video_frames_to_time conversion is exact + /* This assumes that the video-frames-to-time conversion is exact so that there are no accumulated errors caused by rounding. */ - _video_position += _film->video_frames_to_time (1); + _video_position += DCPTime::from_frames (1, _film->video_frame_rate ()); } } @@ -372,9 +366,9 @@ Player::emit_audio (weak_ptr weak_piece, shared_ptr audio) audio->data = dcp_mapped; /* Delay */ - audio->dcp_time += content->audio_delay() * TIME_HZ / 1000; - if (audio->dcp_time < 0) { - int const frames = - audio->dcp_time * _film->audio_frame_rate() / TIME_HZ; + audio->dcp_time += DCPTime::from_seconds (content->audio_delay() / 1000.0); + if (audio->dcp_time < DCPTime (0)) { + int const frames = - audio->dcp_time.frames (_film->audio_frame_rate()); if (frames >= audio->data->frames ()) { return; } @@ -383,7 +377,7 @@ Player::emit_audio (weak_ptr weak_piece, shared_ptr audio) trimmed->copy_from (audio->data.get(), audio->data->frames() - frames, frames, 0); audio->data = trimmed; - audio->dcp_time = 0; + audio->dcp_time = DCPTime (); } _audio_merger.push (audio->data, audio->dcp_time); @@ -395,7 +389,7 @@ Player::flush () TimedAudioBuffers tb = _audio_merger.flush (); if (_audio && tb.audio) { Audio (tb.audio, tb.time); - _audio_position += _film->audio_frames_to_time (tb.audio->frames ()); + _audio_position += DCPTime::from_frames (tb.audio->frames (), _film->audio_frame_rate ()); } while (_video && _video_position < _audio_position) { @@ -429,15 +423,14 @@ Player::seek (DCPTime t, bool accurate) s = min ((*i)->content->length_after_trim(), s); /* Convert this to the content time */ - ContentTime ct = (s + (*i)->content->trim_start()) * (*i)->frc.speed_up; + ContentTime ct (s + (*i)->content->trim_start(), (*i)->frc); /* And seek the decoder */ - cout << "seek " << (*i)->content->paths()[0] << " to " << ct << "\n"; (*i)->decoder->seek (ct, accurate); } - _video_position = time_round_up (t, TIME_HZ / _film->video_frame_rate()); - _audio_position = time_round_up (t, TIME_HZ / _film->audio_frame_rate()); + _video_position = t.round_up (_film->video_frame_rate()); + _audio_position = t.round_up (_film->audio_frame_rate()); _audio_merger.clear (_audio_position); @@ -470,7 +463,7 @@ Player::setup_pieces () optional frc; /* Work out a FrameRateChange for the best overlap video for this content, in case we need it below */ - DCPTime best_overlap_t = 0; + DCPTime best_overlap_t; shared_ptr best_overlap; for (ContentList::iterator j = content.begin(); j != content.end(); ++j) { shared_ptr vc = dynamic_pointer_cast (*j); @@ -532,7 +525,7 @@ Player::setup_pieces () frc = best_overlap_frc; } - ContentTime st = (*i)->trim_start() * frc->speed_up; + ContentTime st ((*i)->trim_start(), frc.get ()); decoder->seek (st, true); _pieces.push_back (shared_ptr (new Piece (*i, decoder, frc.get ()))); @@ -543,7 +536,8 @@ Player::setup_pieces () /* The Piece for the _last_incoming_video will no longer be valid */ _last_incoming_video.video.reset (); - _video_position = _audio_position = 0; + _video_position = DCPTime (); + _audio_position = DCPTime (); } void @@ -621,7 +615,7 @@ Player::emit_black () #endif Video (_black_frame, EYES_BOTH, ColourConversion(), _last_emit_was_black, _video_position); - _video_position += _film->video_frames_to_time (1); + _video_position += DCPTime::from_frames (1, _film->video_frame_rate ()); _last_emit_was_black = true; } @@ -632,8 +626,8 @@ Player::emit_silence (DCPTime most) return; } - DCPTime t = min (most, TIME_HZ / 2); - shared_ptr silence (new AudioBuffers (_film->audio_channels(), t * _film->audio_frame_rate() / TIME_HZ)); + DCPTime t = min (most, DCPTime::from_seconds (0.5)); + shared_ptr silence (new AudioBuffers (_film->audio_channels(), t.frames (_film->audio_frame_rate()))); silence->make_silent (); Audio (silence, _audio_position); diff --git a/src/lib/player.h b/src/lib/player.h index 1ef1f58b7..33ffa80f5 100644 --- a/src/lib/player.h +++ b/src/lib/player.h @@ -149,7 +149,7 @@ private: void do_seek (DCPTime, bool); void flush (); void emit_black (); - void emit_silence (AudioFrame); + void emit_silence (DCPTime); void film_changed (Film::Property); void update_subtitle_from_image (); void update_subtitle_from_text (); @@ -172,7 +172,7 @@ private: /** The time after the last audio that we emitted */ DCPTime _audio_position; - AudioMerger _audio_merger; + AudioMerger _audio_merger; dcp::Size _video_container_size; boost::shared_ptr _black_frame; diff --git a/src/lib/playlist.cc b/src/lib/playlist.cc index 4175de4c9..b7b4c20f7 100644 --- a/src/lib/playlist.cc +++ b/src/lib/playlist.cc @@ -81,14 +81,14 @@ Playlist::maybe_sequence_video () _sequencing_video = true; ContentList cl = _content; - DCPTime next = 0; + DCPTime next; for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) { if (!dynamic_pointer_cast (*i)) { continue; } (*i)->set_position (next); - next = (*i)->end() + 1; + next = (*i)->end() + DCPTime::delta (); } /* This won't change order, so it does not need a sort */ @@ -257,9 +257,9 @@ Playlist::best_dcp_frame_rate () const DCPTime Playlist::length () const { - DCPTime len = 0; + DCPTime len; for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) { - len = max (len, (*i)->end() + 1); + len = max (len, (*i)->end() + DCPTime::delta ()); } return len; @@ -282,7 +282,7 @@ Playlist::reconnect () DCPTime Playlist::video_end () const { - DCPTime end = 0; + DCPTime end; for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) { if (dynamic_pointer_cast (*i)) { end = max (end, (*i)->end ()); @@ -331,7 +331,7 @@ Playlist::content () const void Playlist::repeat (ContentList c, int n) { - pair range (TIME_MAX, 0); + pair range (DCPTime::max (), DCPTime ()); for (ContentList::iterator i = c.begin(); i != c.end(); ++i) { range.first = min (range.first, (*i)->position ()); range.second = max (range.second, (*i)->position ()); diff --git a/src/lib/sndfile_content.cc b/src/lib/sndfile_content.cc index 844f3dd47..f8648107a 100644 --- a/src/lib/sndfile_content.cc +++ b/src/lib/sndfile_content.cc @@ -49,7 +49,7 @@ SndfileContent::SndfileContent (shared_ptr f, shared_ptrnode_child ("AudioMapping"), version) { _audio_channels = node->number_child ("AudioChannels"); - _audio_length = node->number_child ("AudioLength"); + _audio_length = ContentTime (node->number_child ("AudioLength")); _audio_frame_rate = node->number_child ("AudioFrameRate"); } @@ -146,16 +146,7 @@ SndfileContent::full_length () const { shared_ptr film = _film.lock (); assert (film); - - AudioFrame const len = divide_with_round (audio_length() * output_audio_frame_rate(), content_audio_frame_rate ()); - - /* XXX: this depends on whether, alongside this audio, we are running video slower or faster than - it should be. The calculation above works out the output audio frames assuming that we are just - resampling the audio: it would be incomplete if, for example, we were running this audio alongside - 25fps video that was being run at 24fps. - */ - - return film->audio_frames_to_time (len); + return DCPTime (audio_length(), film->active_frame_rate_change (position ())); } int diff --git a/src/lib/sndfile_content.h b/src/lib/sndfile_content.h index 94f46fea3..04d157131 100644 --- a/src/lib/sndfile_content.h +++ b/src/lib/sndfile_content.h @@ -52,7 +52,7 @@ public: return _audio_channels; } - AudioFrame audio_length () const { + ContentTime audio_length () const { boost::mutex::scoped_lock lm (_mutex); return _audio_length; } @@ -75,7 +75,7 @@ public: private: int _audio_channels; - AudioFrame _audio_length; + ContentTime _audio_length; int _audio_frame_rate; AudioMapping _audio_mapping; }; diff --git a/src/lib/sndfile_decoder.cc b/src/lib/sndfile_decoder.cc index f98d4d8be..3a71fab52 100644 --- a/src/lib/sndfile_decoder.cc +++ b/src/lib/sndfile_decoder.cc @@ -105,7 +105,7 @@ SndfileDecoder::pass () } data->set_frames (this_time); - audio (data, _done * TIME_HZ / audio_frame_rate ()); + audio (data, ContentTime::from_frames (_done, audio_frame_rate ())); _done += this_time; _remaining -= this_time; @@ -118,10 +118,10 @@ SndfileDecoder::audio_channels () const return _info.channels; } -AudioFrame +ContentTime SndfileDecoder::audio_length () const { - return _info.frames; + return ContentTime::from_frames (_info.frames, audio_frame_rate ()); } int @@ -136,6 +136,6 @@ SndfileDecoder::seek (ContentTime t, bool accurate) Decoder::seek (t, accurate); AudioDecoder::seek (t, accurate); - _done = t * audio_frame_rate() / TIME_HZ; + _done = t.frames (audio_frame_rate ()); _remaining = _info.frames - _done; } diff --git a/src/lib/sndfile_decoder.h b/src/lib/sndfile_decoder.h index 46d9c5e5c..aaa4c0da8 100644 --- a/src/lib/sndfile_decoder.h +++ b/src/lib/sndfile_decoder.h @@ -32,7 +32,7 @@ public: void seek (ContentTime, bool); int audio_channels () const; - AudioFrame audio_length () const; + ContentTime audio_length () const; int audio_frame_rate () const; private: @@ -41,7 +41,7 @@ private: boost::shared_ptr _sndfile_content; SNDFILE* _sndfile; SF_INFO _info; - AudioFrame _done; - AudioFrame _remaining; + int64_t _done; + int64_t _remaining; float* _deinterleave_buffer; }; diff --git a/src/lib/subrip.cc b/src/lib/subrip.cc index 380a2ce2c..3eac98e63 100644 --- a/src/lib/subrip.cc +++ b/src/lib/subrip.cc @@ -126,18 +126,18 @@ SubRip::SubRip (shared_ptr content) ContentTime SubRip::convert_time (string t) { - ContentTime r = 0; + ContentTime r; vector a; boost::algorithm::split (a, t, boost::is_any_of (":")); assert (a.size() == 3); - r += lexical_cast (a[0]) * 60 * 60 * TIME_HZ; - r += lexical_cast (a[1]) * 60 * TIME_HZ; + r += ContentTime::from_seconds (lexical_cast (a[0]) * 60 * 60); + r += ContentTime::from_seconds (lexical_cast (a[1]) * 60); vector b; boost::algorithm::split (b, a[2], boost::is_any_of (",")); - r += lexical_cast (b[0]) * TIME_HZ; - r += lexical_cast (b[1]) * TIME_HZ / 1000; + r += ContentTime::from_seconds (lexical_cast (b[0])); + r += ContentTime::from_seconds (lexical_cast (b[1]) / 1000); return r; } @@ -229,7 +229,7 @@ ContentTime SubRip::length () const { if (_subtitles.empty ()) { - return 0; + return ContentTime (); } return _subtitles.back().to; diff --git a/src/lib/subrip_content.cc b/src/lib/subrip_content.cc index 9212add68..11cba0f66 100644 --- a/src/lib/subrip_content.cc +++ b/src/lib/subrip_content.cc @@ -20,6 +20,7 @@ #include "subrip_content.h" #include "util.h" #include "subrip.h" +#include "film.h" #include "i18n.h" @@ -39,7 +40,7 @@ SubRipContent::SubRipContent (shared_ptr film, boost::filesystem::pa SubRipContent::SubRipContent (shared_ptr film, shared_ptr node, int version) : Content (film, node) , SubtitleContent (film, node, version) - , _length (node->number_child ("Length")) + , _length (node->number_child ("Length")) { } @@ -50,7 +51,8 @@ SubRipContent::examine (boost::shared_ptr job) Content::examine (job); SubRip s (shared_from_this ()); boost::mutex::scoped_lock lm (_mutex); - _length = s.length (); + shared_ptr film = _film.lock (); + _length = DCPTime (s.length (), film->active_frame_rate_change (position ())); } string diff --git a/src/lib/subrip_decoder.cc b/src/lib/subrip_decoder.cc index f89f40da4..6ebb88323 100644 --- a/src/lib/subrip_decoder.cc +++ b/src/lib/subrip_decoder.cc @@ -47,8 +47,8 @@ SubRipDecoder::pass () i->italic, dcp::Color (255, 255, 255), 72, - _subtitles[_next].from, - _subtitles[_next].to, + dcp::Time (rint (_subtitles[_next].from.seconds() * 250)), + dcp::Time (rint (_subtitles[_next].to.seconds() * 250)), 0.9, dcp::BOTTOM, i->text, diff --git a/src/lib/subrip_subtitle.h b/src/lib/subrip_subtitle.h index 25ba7592c..dd46b6c64 100644 --- a/src/lib/subrip_subtitle.h +++ b/src/lib/subrip_subtitle.h @@ -23,6 +23,7 @@ #include #include #include "types.h" +#include "dcpomatic_time.h" struct SubRipSubtitlePiece { diff --git a/src/lib/subtitle_decoder.cc b/src/lib/subtitle_decoder.cc index ce48e6619..cf5550e42 100644 --- a/src/lib/subtitle_decoder.cc +++ b/src/lib/subtitle_decoder.cc @@ -37,7 +37,7 @@ SubtitleDecoder::SubtitleDecoder (shared_ptr f) void SubtitleDecoder::image_subtitle (shared_ptr image, dcpomatic::Rect rect, ContentTime from, ContentTime to) { - _pending.push_back (shared_ptr (new DecodedImageSubtitle (image, rect, from, to))); + _pending.push_back (shared_ptr (new DecodedImageSubtitle (from, to, image, rect))); } void diff --git a/src/lib/transcode_job.cc b/src/lib/transcode_job.cc index a0537cd42..fd4dfcef6 100644 --- a/src/lib/transcode_job.cc +++ b/src/lib/transcode_job.cc @@ -103,6 +103,7 @@ TranscodeJob::status () const return s.str (); } +/** @return Approximate remaining time in seconds */ int TranscodeJob::remaining_time () const { @@ -120,6 +121,5 @@ TranscodeJob::remaining_time () const } /* Compute approximate proposed length here, as it's only here that we need it */ - VideoFrame const left = _film->time_to_video_frames (_film->length ()) - t->video_frames_out(); - return left / fps; + return (_film->length().frames (_film->video_frame_rate ()) - t->video_frames_out()) / fps; } diff --git a/src/lib/types.h b/src/lib/types.h index cc4cd9296..42344cae5 100644 --- a/src/lib/types.h +++ b/src/lib/types.h @@ -38,12 +38,6 @@ class AudioBuffers; */ #define SERVER_LINK_VERSION 1 -typedef int64_t DCPTime; -#define TIME_MAX INT64_MAX -#define TIME_HZ ((DCPTime) 96000) -typedef int64_t ContentTime; -typedef int64_t AudioFrame; -typedef int VideoFrame; typedef std::vector > ContentList; typedef std::vector > VideoContentList; typedef std::vector > AudioContentList; diff --git a/src/lib/util.cc b/src/lib/util.cc index 7089ef2a5..78e7b480c 100644 --- a/src/lib/util.cc +++ b/src/lib/util.cc @@ -492,33 +492,6 @@ md5_digest (vector files, shared_ptr job) return s.str (); } -static bool -about_equal (float a, float b) -{ - /* A film of F seconds at f FPS will be Ff frames; - Consider some delta FPS d, so if we run the same - film at (f + d) FPS it will last F(f + d) seconds. - - Hence the difference in length over the length of the film will - be F(f + d) - Ff frames - = Ff + Fd - Ff frames - = Fd frames - = Fd/f seconds - - So if we accept a difference of 1 frame, ie 1/f seconds, we can - say that - - 1/f = Fd/f - ie 1 = Fd - ie d = 1/F - - So for a 3hr film, ie F = 3 * 60 * 60 = 10800, the acceptable - FPS error is 1/F ~= 0.0001 ~= 10-e4 - */ - - return (fabs (a - b) < 1e-4); -} - /** @param An arbitrary audio frame rate. * @return The appropriate DCP-approved frame rate (48kHz or 96kHz). */ @@ -778,17 +751,6 @@ ensure_ui_thread () assert (boost::this_thread::get_id() == ui_thread); } -/** @param v Content video frame. - * @param audio_sample_rate Source audio sample rate. - * @param frames_per_second Number of video frames per second. - * @return Equivalent number of audio frames for `v'. - */ -int64_t -video_frames_to_audio_frames (VideoFrame v, float audio_sample_rate, float frames_per_second) -{ - return ((int64_t) v * audio_sample_rate / frames_per_second); -} - string audio_channel_name (int c) { @@ -812,45 +774,6 @@ audio_channel_name (int c) return channels[c]; } -FrameRateChange::FrameRateChange (float source, int dcp) - : skip (false) - , repeat (1) - , change_speed (false) -{ - if (fabs (source / 2.0 - dcp) < fabs (source - dcp)) { - /* The difference between source and DCP frame rate will be lower - (i.e. better) if we skip. - */ - skip = true; - } else if (fabs (source * 2 - dcp) < fabs (source - dcp)) { - /* The difference between source and DCP frame rate would be better - if we repeated each frame once; it may be better still if we - repeated more than once. Work out the required repeat. - */ - repeat = round (dcp / source); - } - - 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"); - } else { - if (skip) { - description = _("DCP will use every other frame of the content.\n"); - } else if (repeat == 2) { - description = _("Each content frame will be doubled in the DCP.\n"); - } else if (repeat > 2) { - description = String::compose (_("Each content frame will be repeated %1 more times in the DCP.\n"), repeat - 1); - } - - if (change_speed) { - float const pc = dcp * 100 / (source * factor()); - description += String::compose (_("DCP will run at %1%% of the content speed.\n"), pc); - } - } -} - LocaleGuard::LocaleGuard () : _old (0) { @@ -1002,13 +925,6 @@ fit_ratio_within (float ratio, dcp::Size full_frame) return dcp::Size (full_frame.width, rint (full_frame.width / ratio)); } -DCPTime -time_round_up (DCPTime t, DCPTime nearest) -{ - DCPTime const a = t + nearest - 1; - return a - (a % nearest); -} - void * wrapped_av_malloc (size_t s) { diff --git a/src/lib/util.h b/src/lib/util.h index 13bf0f42d..d91a6b435 100644 --- a/src/lib/util.h +++ b/src/lib/util.h @@ -81,48 +81,8 @@ extern boost::shared_ptr make_signer (); extern dcp::Size fit_ratio_within (float ratio, dcp::Size); extern std::string entities_to_text (std::string e); extern std::map split_get_request (std::string url); - -struct FrameRateChange -{ - 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. - */ - float factor () const { - if (skip) { - return 0.5; - } - - return repeat; - } - - /** true to skip every other frame */ - bool skip; - /** number of times to use each frame (e.g. 1 is normal, 2 means repeat each frame once, and so on) */ - int repeat; - /** true if this DCP will run its video faster or slower than the source - * without taking into account `repeat' nor `skip'. - * (e.g. change_speed will be true if - * source is 29.97fps, DCP is 30fps - * source is 14.50fps, DCP is 30fps - * but not if - * source is 15.00fps, DCP is 30fps - * source is 12.50fps, DCP is 25fps) - */ - 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; -}; - extern int dcp_audio_frame_rate (int); extern int stride_round_up (int, int const *, int); -extern DCPTime time_round_up (DCPTime, DCPTime); extern std::multimap read_key_value (std::istream& s); extern int get_required_int (std::multimap const & kv, std::string k); extern float get_required_float (std::multimap const & kv, std::string k); @@ -171,8 +131,6 @@ private: int _timeout; }; -extern int64_t video_frames_to_audio_frames (VideoFrame v, float audio_sample_rate, float frames_per_second); - class LocaleGuard { public: diff --git a/src/lib/video_content.cc b/src/lib/video_content.cc index 9d9b58328..b30aa3df4 100644 --- a/src/lib/video_content.cc +++ b/src/lib/video_content.cc @@ -59,7 +59,7 @@ VideoContent::VideoContent (shared_ptr f) setup_default_colour_conversion (); } -VideoContent::VideoContent (shared_ptr f, DCPTime s, VideoFrame len) +VideoContent::VideoContent (shared_ptr f, DCPTime s, ContentTime len) : Content (f, s) , _video_length (len) , _video_frame_rate (0) @@ -83,7 +83,7 @@ VideoContent::VideoContent (shared_ptr f, shared_ptrnumber_child ("VideoLength"); + _video_length = ContentTime (node->number_child ("VideoLength")); _video_size.width = node->number_child ("VideoWidth"); _video_size.height = node->number_child ("VideoHeight"); _video_frame_rate = node->number_child ("VideoFrameRate"); @@ -353,21 +353,12 @@ VideoContent::video_size_after_crop () const } /** @param t A time offset from the start of this piece of content. - * @return Corresponding frame index, rounded up so that the frame index - * is that of the next complete frame which starts after `t'. + * @return Corresponding time with respect to the content. */ -VideoFrame -VideoContent::time_to_content_video_frames (DCPTime t) const +ContentTime +VideoContent::dcp_time_to_content_time (DCPTime t) const { shared_ptr film = _film.lock (); assert (film); - - /* 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; 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). - */ - FrameRateChange frc (video_frame_rate(), film->video_frame_rate()); - return ceil (t * film->video_frame_rate() / (frc.factor() * TIME_HZ)); + return ContentTime (t, FrameRateChange (video_frame_rate(), film->video_frame_rate())); } diff --git a/src/lib/video_content.h b/src/lib/video_content.h index 96572bbd9..d673d5372 100644 --- a/src/lib/video_content.h +++ b/src/lib/video_content.h @@ -43,7 +43,7 @@ public: typedef int Frame; VideoContent (boost::shared_ptr); - VideoContent (boost::shared_ptr, DCPTime, VideoFrame); + VideoContent (boost::shared_ptr, DCPTime, ContentTime); VideoContent (boost::shared_ptr, boost::filesystem::path); VideoContent (boost::shared_ptr, boost::shared_ptr); VideoContent (boost::shared_ptr, std::vector >); @@ -53,7 +53,7 @@ public: virtual std::string information () const; virtual std::string identifier () const; - VideoFrame video_length () const { + ContentTime video_length () const { boost::mutex::scoped_lock lm (_mutex); return _video_length; } @@ -123,12 +123,12 @@ public: dcp::Size video_size_after_3d_split () const; dcp::Size video_size_after_crop () const; - VideoFrame time_to_content_video_frames (DCPTime) const; + ContentTime dcp_time_to_content_time (DCPTime) const; protected: void take_from_video_examiner (boost::shared_ptr); - VideoFrame _video_length; + ContentTime _video_length; float _video_frame_rate; private: diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc index a3b45716c..e4d5516a1 100644 --- a/src/lib/video_decoder.cc +++ b/src/lib/video_decoder.cc @@ -35,24 +35,24 @@ VideoDecoder::VideoDecoder (shared_ptr f, shared_ptr image, bool same, VideoFrame frame) +VideoDecoder::video (shared_ptr image, bool same, ContentTime time) { switch (_video_content->video_frame_type ()) { case VIDEO_FRAME_TYPE_2D: - _pending.push_back (shared_ptr (new DecodedVideo (image, EYES_BOTH, same, frame))); + _pending.push_back (shared_ptr (new DecodedVideo (time, image, EYES_BOTH, same))); break; case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT: { int const half = image->size().width / 2; - _pending.push_back (shared_ptr (new DecodedVideo (image->crop (Crop (0, half, 0, 0), true), EYES_LEFT, same, frame))); - _pending.push_back (shared_ptr (new DecodedVideo (image->crop (Crop (half, 0, 0, 0), true), EYES_RIGHT, same, frame))); + _pending.push_back (shared_ptr (new DecodedVideo (time, image->crop (Crop (0, half, 0, 0), true), EYES_LEFT, same))); + _pending.push_back (shared_ptr (new DecodedVideo (time, image->crop (Crop (half, 0, 0, 0), true), EYES_RIGHT, same))); break; } case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM: { int const half = image->size().height / 2; - _pending.push_back (shared_ptr (new DecodedVideo (image->crop (Crop (0, 0, 0, half), true), EYES_LEFT, same, frame))); - _pending.push_back (shared_ptr (new DecodedVideo (image->crop (Crop (0, 0, half, 0), true), EYES_RIGHT, same, frame))); + _pending.push_back (shared_ptr (new DecodedVideo (time, image->crop (Crop (0, 0, 0, half), true), EYES_LEFT, same))); + _pending.push_back (shared_ptr (new DecodedVideo (time, image->crop (Crop (0, 0, half, 0), true), EYES_RIGHT, same))); break; } default: diff --git a/src/lib/video_decoder.h b/src/lib/video_decoder.h index 8947c2708..d8c362354 100644 --- a/src/lib/video_decoder.h +++ b/src/lib/video_decoder.h @@ -41,7 +41,7 @@ public: protected: - void video (boost::shared_ptr, bool, VideoFrame); + void video (boost::shared_ptr, bool, ContentTime); boost::shared_ptr _video_content; }; diff --git a/src/lib/video_examiner.h b/src/lib/video_examiner.h index 68469380c..d4897213b 100644 --- a/src/lib/video_examiner.h +++ b/src/lib/video_examiner.h @@ -27,5 +27,5 @@ public: virtual ~VideoExaminer () {} virtual float video_frame_rate () const = 0; virtual dcp::Size video_size () const = 0; - virtual VideoFrame video_length () const = 0; + virtual ContentTime video_length () const = 0; }; diff --git a/src/lib/writer.cc b/src/lib/writer.cc index adcaa0c68..6c6331486 100644 --- a/src/lib/writer.cc +++ b/src/lib/writer.cc @@ -41,6 +41,7 @@ #include "config.h" #include "job.h" #include "cross.h" +#include "audio_buffers.h" #include "i18n.h" @@ -294,7 +295,7 @@ try if (_film->length()) { shared_ptr job = _job.lock (); assert (job); - int total = _film->time_to_video_frames (_film->length ()); + int64_t total = _film->length().frames (_film->video_frame_rate ()); if (_film->three_d ()) { /* _full_written and so on are incremented for each eye, so we need to double the total frames to get the correct progress. diff --git a/src/lib/wscript b/src/lib/wscript index 688f4047d..16b5aafe4 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -8,6 +8,7 @@ sources = """ audio_content.cc audio_decoder.cc audio_mapping.cc + audio_merger.cc cinema.cc colour_conversion.cc config.cc @@ -17,6 +18,7 @@ sources = """ dci_metadata.cc dcp_content_type.cc dcp_video_frame.cc + dcpomatic_time.cc decoder.cc dolby_cp750.cc encoder.cc @@ -30,6 +32,7 @@ sources = """ ffmpeg_examiner.cc film.cc filter.cc + frame_rate_change.cc image.cc image_content.cc image_decoder.cc diff --git a/src/wx/film_editor.cc b/src/wx/film_editor.cc index 831a57a02..78a5c440c 100644 --- a/src/wx/film_editor.cc +++ b/src/wx/film_editor.cc @@ -936,7 +936,7 @@ FilmEditor::content_timeline_clicked () _timeline_dialog = 0; } - _timeline_dialog = new DCPTimelineDialog (this, _film); + _timeline_dialog = new TimelineDialog (this, _film); _timeline_dialog->Show (); } diff --git a/src/wx/film_editor.h b/src/wx/film_editor.h index dadb583ae..23c87e678 100644 --- a/src/wx/film_editor.h +++ b/src/wx/film_editor.h @@ -33,9 +33,9 @@ class wxNotebook; class wxListCtrl; class wxListEvent; class Film; -class DCPTimelineDialog; +class TimelineDialog; class Ratio; -class DCPTimecode; +class Timecode; class FilmEditorPanel; class SubtitleContent; @@ -156,5 +156,5 @@ private: std::vector _ratios; bool _generally_sensitive; - DCPTimelineDialog* _timeline_dialog; + TimelineDialog* _timeline_dialog; }; diff --git a/src/wx/film_viewer.cc b/src/wx/film_viewer.cc index d88f88f5e..a2c489838 100644 --- a/src/wx/film_viewer.cc +++ b/src/wx/film_viewer.cc @@ -122,7 +122,7 @@ FilmViewer::set_film (shared_ptr f) _frame.reset (); _slider->SetValue (0); - set_position_text (0); + set_position_text (DCPTime ()); if (!_film) { return; @@ -222,7 +222,7 @@ FilmViewer::slider_moved () { if (_film && _player) { try { - _player->seek (_slider->GetValue() * _film->length() / 4096, false); + _player->seek (DCPTime (_film->length().get() * _slider->GetValue() / 4096), false); fetch_next_frame (); } catch (OpenFileError& e) { /* There was a problem opening a content file; we'll let this slide as it @@ -325,9 +325,9 @@ FilmViewer::set_position_text (DCPTime t) double const fps = _film->video_frame_rate (); /* Count frame number from 1 ... not sure if this is the best idea */ - _frame_number->SetLabel (wxString::Format (wxT("%d"), int (rint (t * fps / TIME_HZ)) + 1)); + _frame_number->SetLabel (wxString::Format (wxT("%d"), int (rint (t.seconds() * fps)) + 1)); - double w = static_cast(t) / TIME_HZ; + double w = t.seconds (); int const h = (w / 3600); w -= h * 3600; int const m = (w / 60); @@ -398,9 +398,9 @@ FilmViewer::back_clicked () We want to see the one before it, so we need to go back 2. */ - DCPTime p = _player->video_position() - _film->video_frames_to_time (2); + DCPTime p = _player->video_position() - DCPTime::from_frames (2, _film->video_frame_rate ()); if (p < 0) { - p = 0; + p = DCPTime (); } try { diff --git a/src/wx/properties_dialog.cc b/src/wx/properties_dialog.cc index 11510cd0f..bdc5742d8 100644 --- a/src/wx/properties_dialog.cc +++ b/src/wx/properties_dialog.cc @@ -51,7 +51,7 @@ PropertiesDialog::PropertiesDialog (wxWindow* parent, shared_ptr film) _encoded->Finished.connect (boost::bind (&PropertiesDialog::layout, this)); _table->Add (_encoded, 1, wxALIGN_CENTER_VERTICAL); - _frames->SetLabel (std_to_wx (lexical_cast (_film->time_to_video_frames (_film->length())))); + _frames->SetLabel (std_to_wx (lexical_cast (_film->length().frames (_film->video_frame_rate ())))); double const disk = double (_film->required_disk_space()) / 1073741824.0f; stringstream s; s << fixed << setprecision (1) << disk << wx_to_std (_("Gb")); @@ -88,7 +88,7 @@ PropertiesDialog::frames_already_encoded () const if (_film->length()) { /* XXX: encoded_frames() should check which frames have been encoded */ - u << " (" << (_film->encoded_frames() * 100 / _film->time_to_video_frames (_film->length())) << "%)"; + u << " (" << (_film->encoded_frames() * 100 / _film->length().frames (_film->video_frame_rate ())) << "%)"; } return u.str (); } diff --git a/src/wx/timecode.cc b/src/wx/timecode.cc index 7208bd4c6..045327254 100644 --- a/src/wx/timecode.cc +++ b/src/wx/timecode.cc @@ -85,13 +85,13 @@ Timecode::Timecode (wxWindow* parent) void Timecode::set (DCPTime t, int fps) { - int const h = t / (3600 * TIME_HZ); - t -= h * 3600 * TIME_HZ; - int const m = t / (60 * TIME_HZ); - t -= m * 60 * TIME_HZ; - int const s = t / TIME_HZ; - t -= s * TIME_HZ; - int const f = divide_with_round (t * fps, TIME_HZ); + int const h = t.seconds() / 3600; + t -= DCPTime::from_seconds (h * 3600); + int const m = t.seconds() / 60; + t -= DCPTime::from_seconds (m * 60); + int const s = t.seconds(); + t -= DCPTime::from_seconds (s); + int const f = rint (t.seconds() * fps); checked_set (_hours, lexical_cast (h)); checked_set (_minutes, lexical_cast (m)); @@ -104,15 +104,15 @@ Timecode::set (DCPTime t, int fps) DCPTime Timecode::get (int fps) const { - DCPTime t = 0; + DCPTime t; string const h = wx_to_std (_hours->GetValue ()); - t += lexical_cast (h.empty() ? "0" : h) * 3600 * TIME_HZ; + t += DCPTime::from_seconds (lexical_cast (h.empty() ? "0" : h) * 3600); string const m = wx_to_std (_minutes->GetValue()); - t += lexical_cast (m.empty() ? "0" : m) * 60 * TIME_HZ; + t += DCPTime::from_seconds (lexical_cast (m.empty() ? "0" : m) * 60); string const s = wx_to_std (_seconds->GetValue()); - t += lexical_cast (s.empty() ? "0" : s) * TIME_HZ; + t += DCPTime::from_seconds (lexical_cast (s.empty() ? "0" : s)); string const f = wx_to_std (_frames->GetValue()); - t += lexical_cast (f.empty() ? "0" : f) * TIME_HZ / fps; + t += DCPTime::from_seconds (lexical_cast (f.empty() ? "0" : f) / fps); return t; } diff --git a/src/wx/timeline.cc b/src/wx/timeline.cc index ac26c77a9..0e713d1de 100644 --- a/src/wx/timeline.cc +++ b/src/wx/timeline.cc @@ -39,7 +39,7 @@ using boost::optional; class View : public boost::noncopyable { public: - View (DCPTimeline& t) + View (Timeline& t) : _timeline (t) { @@ -64,12 +64,12 @@ public: protected: virtual void do_paint (wxGraphicsContext *) = 0; - int time_x (DCPTime t) const + int time_x (double t) const { - return _timeline.tracks_position().x + t * _timeline.pixels_per_time_unit(); + return _timeline.tracks_position().x + t * _timeline.pixels_per_second (); } - DCPTimeline& _timeline; + Timeline& _timeline; private: dcpomatic::Rect _last_paint_bbox; @@ -80,7 +80,7 @@ private: class ContentView : public View { public: - ContentView (DCPTimeline& tl, shared_ptr c) + ContentView (Timeline& tl, shared_ptr c) : View (tl) , _content (c) , _track (0) @@ -100,7 +100,7 @@ public: return dcpomatic::Rect ( time_x (content->position ()) - 8, y_pos (_track) - 8, - content->length_after_trim () * _timeline.pixels_per_time_unit() + 16, + content->length_after_trim().seconds() * _timeline.pixels_per_second() + 16, _timeline.track_height() + 16 ); } @@ -169,7 +169,7 @@ private: wxDouble name_leading; gc->GetTextExtent (name, &name_width, &name_height, &name_descent, &name_leading); - gc->Clip (wxRegion (time_x (position), y_pos (_track), len * _timeline.pixels_per_time_unit(), _timeline.track_height())); + gc->Clip (wxRegion (time_x (position), y_pos (_track), len.seconds() * _timeline.pixels_per_second(), _timeline.track_height())); gc->DrawText (name, time_x (position) + 12, y_pos (_track + 1) - name_height - 4); gc->ResetClip (); } @@ -188,7 +188,7 @@ private: } if (!frequent) { - _timeline.setup_pixels_per_time_unit (); + _timeline.setup_pixels_per_second (); _timeline.Refresh (); } } @@ -203,7 +203,7 @@ private: class AudioContentView : public ContentView { public: - AudioContentView (DCPTimeline& tl, shared_ptr c) + AudioContentView (Timeline& tl, shared_ptr c) : ContentView (tl, c) {} @@ -222,7 +222,7 @@ private: class VideoContentView : public ContentView { public: - VideoContentView (DCPTimeline& tl, shared_ptr c) + VideoContentView (Timeline& tl, shared_ptr c) : ContentView (tl, c) {} @@ -243,10 +243,10 @@ private: } }; -class DCPTimeAxisView : public View +class TimeAxisView : public View { public: - DCPTimeAxisView (DCPTimeline& tl, int y) + TimeAxisView (Timeline& tl, int y) : View (tl) , _y (y) {} @@ -268,18 +268,18 @@ private: { gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 1, wxPENSTYLE_SOLID)); - int mark_interval = rint (128 / (TIME_HZ * _timeline.pixels_per_time_unit ())); + double mark_interval = rint (128 / _timeline.pixels_per_second ()); if (mark_interval > 5) { - mark_interval -= mark_interval % 5; + mark_interval -= int (rint (mark_interval)) % 5; } if (mark_interval > 10) { - mark_interval -= mark_interval % 10; + mark_interval -= int (rint (mark_interval)) % 10; } if (mark_interval > 60) { - mark_interval -= mark_interval % 60; + mark_interval -= int (rint (mark_interval)) % 60; } if (mark_interval > 3600) { - mark_interval -= mark_interval % 3600; + mark_interval -= int (rint (mark_interval)) % 3600; } if (mark_interval < 1) { @@ -291,14 +291,15 @@ private: path.AddLineToPoint (_timeline.width(), _y); gc->StrokePath (path); - DCPTime t = 0; - while ((t * _timeline.pixels_per_time_unit()) < _timeline.width()) { + /* Time in seconds */ + double t; + while ((t * _timeline.pixels_per_second()) < _timeline.width()) { wxGraphicsPath path = gc->CreatePath (); path.MoveToPoint (time_x (t), _y - 4); path.AddLineToPoint (time_x (t), _y + 4); gc->StrokePath (path); - int tc = t / TIME_HZ; + double tc = t; int const h = tc / 3600; tc -= h * 3600; int const m = tc / 60; @@ -312,12 +313,12 @@ private: wxDouble str_leading; gc->GetTextExtent (str, &str_width, &str_height, &str_descent, &str_leading); - int const tx = _timeline.x_offset() + t * _timeline.pixels_per_time_unit(); + int const tx = _timeline.x_offset() + t * _timeline.pixels_per_second(); if ((tx + str_width) < _timeline.width()) { gc->DrawText (str, time_x (t), _y + 16); } - t += mark_interval * TIME_HZ; + t += mark_interval; } } @@ -326,13 +327,13 @@ private: }; -DCPTimeline::DCPTimeline (wxWindow* parent, FilmEditor* ed, shared_ptr film) +Timeline::Timeline (wxWindow* parent, FilmEditor* ed, shared_ptr film) : wxPanel (parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE) , _film_editor (ed) , _film (film) - , _time_axis_view (new DCPTimeAxisView (*this, 32)) + , _time_axis_view (new TimeAxisView (*this, 32)) , _tracks (0) - , _pixels_per_time_unit (0) + , _pixels_per_second (0) , _left_down (false) , _down_view_position (0) , _first_move (false) @@ -343,22 +344,22 @@ DCPTimeline::DCPTimeline (wxWindow* parent, FilmEditor* ed, shared_ptr fil SetDoubleBuffered (true); #endif - Bind (wxEVT_PAINT, boost::bind (&DCPTimeline::paint, this)); - Bind (wxEVT_LEFT_DOWN, boost::bind (&DCPTimeline::left_down, this, _1)); - Bind (wxEVT_LEFT_UP, boost::bind (&DCPTimeline::left_up, this, _1)); - Bind (wxEVT_RIGHT_DOWN, boost::bind (&DCPTimeline::right_down, this, _1)); - Bind (wxEVT_MOTION, boost::bind (&DCPTimeline::mouse_moved, this, _1)); - Bind (wxEVT_SIZE, boost::bind (&DCPTimeline::resized, this)); + Bind (wxEVT_PAINT, boost::bind (&Timeline::paint, this)); + Bind (wxEVT_LEFT_DOWN, boost::bind (&Timeline::left_down, this, _1)); + Bind (wxEVT_LEFT_UP, boost::bind (&Timeline::left_up, this, _1)); + Bind (wxEVT_RIGHT_DOWN, boost::bind (&Timeline::right_down, this, _1)); + Bind (wxEVT_MOTION, boost::bind (&Timeline::mouse_moved, this, _1)); + Bind (wxEVT_SIZE, boost::bind (&Timeline::resized, this)); playlist_changed (); SetMinSize (wxSize (640, tracks() * track_height() + 96)); - _playlist_connection = film->playlist()->Changed.connect (bind (&DCPTimeline::playlist_changed, this)); + _playlist_connection = film->playlist()->Changed.connect (bind (&Timeline::playlist_changed, this)); } void -DCPTimeline::paint () +Timeline::paint () { wxPaintDC dc (this); @@ -377,7 +378,7 @@ DCPTimeline::paint () } void -DCPTimeline::playlist_changed () +Timeline::playlist_changed () { ensure_ui_thread (); @@ -401,12 +402,12 @@ DCPTimeline::playlist_changed () } assign_tracks (); - setup_pixels_per_time_unit (); + setup_pixels_per_second (); Refresh (); } void -DCPTimeline::assign_tracks () +Timeline::assign_tracks () { for (ViewList::iterator i = _views.begin(); i != _views.end(); ++i) { shared_ptr cv = dynamic_pointer_cast (*i); @@ -465,24 +466,24 @@ DCPTimeline::assign_tracks () } int -DCPTimeline::tracks () const +Timeline::tracks () const { return _tracks; } void -DCPTimeline::setup_pixels_per_time_unit () +Timeline::setup_pixels_per_second () { shared_ptr film = _film.lock (); if (!film || film->length() == 0) { return; } - _pixels_per_time_unit = static_cast(width() - x_offset() * 2) / film->length (); + _pixels_per_second = static_cast(width() - x_offset() * 2) / film->length().seconds (); } shared_ptr -DCPTimeline::event_to_view (wxMouseEvent& ev) +Timeline::event_to_view (wxMouseEvent& ev) { ViewList::iterator i = _views.begin(); Position const p (ev.GetX(), ev.GetY()); @@ -498,7 +499,7 @@ DCPTimeline::event_to_view (wxMouseEvent& ev) } void -DCPTimeline::left_down (wxMouseEvent& ev) +Timeline::left_down (wxMouseEvent& ev) { shared_ptr view = event_to_view (ev); shared_ptr content_view = dynamic_pointer_cast (view); @@ -539,7 +540,7 @@ DCPTimeline::left_down (wxMouseEvent& ev) } void -DCPTimeline::left_up (wxMouseEvent& ev) +Timeline::left_up (wxMouseEvent& ev) { _left_down = false; @@ -551,7 +552,7 @@ DCPTimeline::left_up (wxMouseEvent& ev) } void -DCPTimeline::mouse_moved (wxMouseEvent& ev) +Timeline::mouse_moved (wxMouseEvent& ev) { if (!_left_down) { return; @@ -561,7 +562,7 @@ DCPTimeline::mouse_moved (wxMouseEvent& ev) } void -DCPTimeline::right_down (wxMouseEvent& ev) +Timeline::right_down (wxMouseEvent& ev) { shared_ptr view = event_to_view (ev); shared_ptr cv = dynamic_pointer_cast (view); @@ -578,7 +579,7 @@ DCPTimeline::right_down (wxMouseEvent& ev) } void -DCPTimeline::set_position_from_event (wxMouseEvent& ev) +Timeline::set_position_from_event (wxMouseEvent& ev) { wxPoint const p = ev.GetPosition(); @@ -597,13 +598,13 @@ DCPTimeline::set_position_from_event (wxMouseEvent& ev) return; } - DCPTime new_position = _down_view_position + (p.x - _down_point.x) / _pixels_per_time_unit; + DCPTime new_position = _down_view_position + DCPTime::from_seconds ((p.x - _down_point.x) / _pixels_per_second); if (_snap) { bool first = true; - DCPTime nearest_distance = TIME_MAX; - DCPTime nearest_new_position = TIME_MAX; + DCPTime nearest_distance = DCPTime::max (); + DCPTime nearest_new_position = DCPTime::max (); /* Find the nearest content edge; this is inefficient */ for (ViewList::iterator i = _views.begin(); i != _views.end(); ++i) { @@ -614,7 +615,7 @@ DCPTimeline::set_position_from_event (wxMouseEvent& ev) { /* Snap starts to ends */ - DCPTime const d = abs (cv->content()->end() - new_position); + DCPTime const d = DCPTime (cv->content()->end() - new_position).abs (); if (first || d < nearest_distance) { nearest_distance = d; nearest_new_position = cv->content()->end(); @@ -623,7 +624,10 @@ DCPTimeline::set_position_from_event (wxMouseEvent& ev) { /* Snap ends to starts */ - DCPTime const d = abs (cv->content()->position() - (new_position + _down_view->content()->length_after_trim())); + DCPTime const d = DCPTime ( + cv->content()->position() - (new_position + _down_view->content()->length_after_trim()) + ).abs (); + if (d < nearest_distance) { nearest_distance = d; nearest_new_position = cv->content()->position() - _down_view->content()->length_after_trim (); @@ -635,14 +639,14 @@ DCPTimeline::set_position_from_event (wxMouseEvent& ev) if (!first) { /* Snap if it's close; `close' means within a proportion of the time on the timeline */ - if (nearest_distance < (width() / pixels_per_time_unit()) / 32) { + if (nearest_distance < (width() / pixels_per_second()) / 32) { new_position = nearest_new_position; } } } if (new_position < 0) { - new_position = 0; + new_position = DCPTime (); } _down_view->content()->set_position (new_position); @@ -653,25 +657,25 @@ DCPTimeline::set_position_from_event (wxMouseEvent& ev) } void -DCPTimeline::force_redraw (dcpomatic::Rect const & r) +Timeline::force_redraw (dcpomatic::Rect const & r) { RefreshRect (wxRect (r.x, r.y, r.width, r.height), false); } shared_ptr -DCPTimeline::film () const +Timeline::film () const { return _film.lock (); } void -DCPTimeline::resized () +Timeline::resized () { - setup_pixels_per_time_unit (); + setup_pixels_per_second (); } void -DCPTimeline::clear_selection () +Timeline::clear_selection () { for (ViewList::iterator i = _views.begin(); i != _views.end(); ++i) { shared_ptr cv = dynamic_pointer_cast (*i); @@ -681,8 +685,8 @@ DCPTimeline::clear_selection () } } -DCPTimeline::ContentViewList -DCPTimeline::selected_views () const +Timeline::ContentViewList +Timeline::selected_views () const { ContentViewList sel; @@ -697,7 +701,7 @@ DCPTimeline::selected_views () const } ContentList -DCPTimeline::selected_content () const +Timeline::selected_content () const { ContentList sel; ContentViewList views = selected_views (); diff --git a/src/wx/timeline.h b/src/wx/timeline.h index b3ee77fff..35153dd17 100644 --- a/src/wx/timeline.h +++ b/src/wx/timeline.h @@ -29,12 +29,12 @@ class Film; class View; class ContentView; class FilmEditor; -class DCPTimeAxisView; +class TimeAxisView; -class DCPTimeline : public wxPanel +class Timeline : public wxPanel { public: - DCPTimeline (wxWindow *, FilmEditor *, boost::shared_ptr); + Timeline (wxWindow *, FilmEditor *, boost::shared_ptr); boost::shared_ptr film () const; @@ -52,8 +52,8 @@ public: return 48; } - double pixels_per_time_unit () const { - return _pixels_per_time_unit; + double pixels_per_second () const { + return _pixels_per_second; } Position tracks_position () const { @@ -62,7 +62,7 @@ public: int tracks () const; - void setup_pixels_per_time_unit (); + void setup_pixels_per_second (); void set_snap (bool s) { _snap = s; @@ -94,9 +94,9 @@ private: FilmEditor* _film_editor; boost::weak_ptr _film; ViewList _views; - boost::shared_ptr _time_axis_view; + boost::shared_ptr _time_axis_view; int _tracks; - double _pixels_per_time_unit; + double _pixels_per_second; bool _left_down; wxPoint _down_point; boost::shared_ptr _down_view; diff --git a/src/wx/timeline_dialog.cc b/src/wx/timeline_dialog.cc index a63c219db..dbf7ae232 100644 --- a/src/wx/timeline_dialog.cc +++ b/src/wx/timeline_dialog.cc @@ -28,8 +28,8 @@ using std::list; using std::cout; using boost::shared_ptr; -DCPTimelineDialog::DCPTimelineDialog (FilmEditor* ed, shared_ptr film) - : wxDialog (ed, wxID_ANY, _("DCPTimeline"), wxDefaultPosition, wxSize (640, 512), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE) +TimelineDialog::TimelineDialog (FilmEditor* ed, shared_ptr film) + : wxDialog (ed, wxID_ANY, _("Timeline"), wxDefaultPosition, wxSize (640, 512), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE) , _timeline (this, ed, film) { wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL); @@ -46,11 +46,11 @@ DCPTimelineDialog::DCPTimelineDialog (FilmEditor* ed, shared_ptr film) sizer->SetSizeHints (this); _snap->SetValue (_timeline.snap ()); - _snap->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&DCPTimelineDialog::snap_toggled, this)); + _snap->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&TimelineDialog::snap_toggled, this)); } void -DCPTimelineDialog::snap_toggled () +TimelineDialog::snap_toggled () { _timeline.set_snap (_snap->GetValue ()); } diff --git a/src/wx/timeline_dialog.h b/src/wx/timeline_dialog.h index b64445d9c..1e5955003 100644 --- a/src/wx/timeline_dialog.h +++ b/src/wx/timeline_dialog.h @@ -24,14 +24,14 @@ class Playlist; -class DCPTimelineDialog : public wxDialog +class TimelineDialog : public wxDialog { public: - DCPTimelineDialog (FilmEditor *, boost::shared_ptr); + TimelineDialog (FilmEditor *, boost::shared_ptr); private: void snap_toggled (); - DCPTimeline _timeline; + Timeline _timeline; wxCheckBox* _snap; }; diff --git a/src/wx/timing_panel.cc b/src/wx/timing_panel.cc index 34702463c..b8b923a6b 100644 --- a/src/wx/timing_panel.cc +++ b/src/wx/timing_panel.cc @@ -85,31 +85,31 @@ TimingPanel::film_content_changed (int property) if (content) { _position->set (content->position (), _editor->film()->video_frame_rate ()); } else { - _position->set (0, 24); + _position->set (DCPTime () , 24); } } else if (property == ContentProperty::LENGTH || property == VideoContentProperty::VIDEO_FRAME_RATE) { if (content) { _full_length->set (content->full_length (), _editor->film()->video_frame_rate ()); _play_length->set (content->length_after_trim (), _editor->film()->video_frame_rate ()); } else { - _full_length->set (0, 24); - _play_length->set (0, 24); + _full_length->set (DCPTime (), 24); + _play_length->set (DCPTime (), 24); } } else if (property == ContentProperty::TRIM_START) { if (content) { _trim_start->set (content->trim_start (), _editor->film()->video_frame_rate ()); _play_length->set (content->length_after_trim (), _editor->film()->video_frame_rate ()); } else { - _trim_start->set (0, 24); - _play_length->set (0, 24); + _trim_start->set (DCPTime (), 24); + _play_length->set (DCPTime (), 24); } } else if (property == ContentProperty::TRIM_END) { if (content) { _trim_end->set (content->trim_end (), _editor->film()->video_frame_rate ()); _play_length->set (content->length_after_trim (), _editor->film()->video_frame_rate ()); } else { - _trim_end->set (0, 24); - _play_length->set (0, 24); + _trim_end->set (DCPTime (), 24); + _play_length->set (DCPTime (), 24); } } @@ -149,7 +149,8 @@ TimingPanel::full_length_changed () if (c.size() == 1) { shared_ptr ic = dynamic_pointer_cast (c.front ()); if (ic && ic->still ()) { - ic->set_video_length (rint (_full_length->get (_editor->film()->video_frame_rate()) * ic->video_frame_rate() / TIME_HZ)); + /* XXX: No effective FRC here... is this right? */ + ic->set_video_length (ContentTime (_full_length->get (_editor->film()->video_frame_rate()), FrameRateChange (1, 1))); } } } diff --git a/test/audio_merger_test.cc b/test/audio_merger_test.cc index 31d055ab7..e8af22d2a 100644 --- a/test/audio_merger_test.cc +++ b/test/audio_merger_test.cc @@ -29,27 +29,22 @@ using boost::bind; static shared_ptr last_audio; -static int -pass_through (int x) -{ - return x; -} - BOOST_AUTO_TEST_CASE (audio_merger_test1) { - AudioMerger merger (1, bind (&pass_through, _1), boost::bind (&pass_through, _1)); + int const frame_rate = 48000; + AudioMerger merger (1, frame_rate); /* Push 64 samples, 0 -> 63 at time 0 */ shared_ptr buffers (new AudioBuffers (1, 64)); for (int i = 0; i < 64; ++i) { buffers->data()[0][i] = i; } - merger.push (buffers, 0); + merger.push (buffers, DCPTime ()); /* Push 64 samples, 0 -> 63 at time 22 */ - merger.push (buffers, 22); + merger.push (buffers, DCPTime::from_frames (22, frame_rate)); - TimedAudioBuffers tb = merger.pull (22); + TimedAudioBuffers tb = merger.pull (DCPTime::from_frames (22, frame_rate)); BOOST_CHECK (tb.audio != shared_ptr ()); BOOST_CHECK_EQUAL (tb.audio->frames(), 22); BOOST_CHECK_EQUAL (tb.time, 0); @@ -63,7 +58,7 @@ BOOST_AUTO_TEST_CASE (audio_merger_test1) /* That flush should give us 64 samples at 22 */ BOOST_CHECK_EQUAL (tb.audio->frames(), 64); - BOOST_CHECK_EQUAL (tb.time, 22); + BOOST_CHECK_EQUAL (tb.time, DCPTime::from_frames (22, frame_rate)); /* Check the sample values */ for (int i = 0; i < 64; ++i) { @@ -77,16 +72,17 @@ BOOST_AUTO_TEST_CASE (audio_merger_test1) BOOST_AUTO_TEST_CASE (audio_merger_test2) { - AudioMerger merger (1, bind (&pass_through, _1), boost::bind (&pass_through, _1)); + int const frame_rate = 48000; + AudioMerger merger (1, frame_rate); /* Push 64 samples, 0 -> 63 at time 9 */ shared_ptr buffers (new AudioBuffers (1, 64)); for (int i = 0; i < 64; ++i) { buffers->data()[0][i] = i; } - merger.push (buffers, 9); + merger.push (buffers, DCPTime::from_frames (9, frame_rate)); - TimedAudioBuffers tb = merger.pull (9); + TimedAudioBuffers tb = merger.pull (DCPTime::from_frames (9, frame_rate)); BOOST_CHECK_EQUAL (tb.audio->frames(), 9); BOOST_CHECK_EQUAL (tb.time, 0); diff --git a/test/black_fill_test.cc b/test/black_fill_test.cc index 9e8aa381b..101fe0523 100644 --- a/test/black_fill_test.cc +++ b/test/black_fill_test.cc @@ -46,10 +46,10 @@ BOOST_AUTO_TEST_CASE (black_fill_test) film->examine_and_add_content (contentB); wait_for_jobs (); - contentA->set_video_length (3); - contentA->set_position (film->video_frames_to_time (2)); - contentB->set_video_length (1); - contentB->set_position (film->video_frames_to_time (7)); + contentA->set_video_length (ContentTime::from_frames (3, 24)); + contentA->set_position (DCPTime::from_frames (2, film->video_frame_rate ())); + contentB->set_video_length (ContentTime::from_frames (1, 24)); + contentB->set_position (DCPTime::from_frames (7, film->video_frame_rate ())); film->make_dcp (); diff --git a/test/ffmpeg_seek_test.cc b/test/ffmpeg_seek_test.cc index 3c175f08c..235a1a1d6 100644 --- a/test/ffmpeg_seek_test.cc +++ b/test/ffmpeg_seek_test.cc @@ -62,7 +62,7 @@ static string print_time (DCPTime t, float fps) { stringstream s; - s << t << " " << (float(t) / TIME_HZ) << "s " << (float(t) * fps / TIME_HZ) << "f"; + s << t << " " << t.seconds() << "s " << t.frames (fps) << "f"; return s.str (); } @@ -90,8 +90,8 @@ check (shared_ptr p, DCPTime t) BOOST_CHECK (first_video.get() >= t); BOOST_CHECK (first_audio.get() >= t); /* And should be rounded to frame boundaries */ - BOOST_CHECK ((first_video.get() % (TIME_HZ / film->video_frame_rate())) == 0); - BOOST_CHECK ((first_audio.get() % (TIME_HZ / film->audio_frame_rate())) == 0); + BOOST_CHECK (first_video.get() == first_video.get().round_up (film->video_frame_rate())); + BOOST_CHECK (first_audio.get() == first_audio.get().round_up (film->audio_frame_rate())); } /* Test basic seeking */ @@ -110,8 +110,8 @@ BOOST_AUTO_TEST_CASE (ffmpeg_seek_test) 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); + check (player, DCPTime::from_seconds (0)); + check (player, DCPTime::from_seconds (0.1)); + check (player, DCPTime::from_seconds (0.2)); + check (player, DCPTime::from_seconds (0.3)); } diff --git a/test/long_ffmpeg_seek_test.cc b/test/long_ffmpeg_seek_test.cc index 5285d4481..fedd8749b 100644 --- a/test/long_ffmpeg_seek_test.cc +++ b/test/long_ffmpeg_seek_test.cc @@ -54,7 +54,7 @@ static string print_time (DCPTime t, float fps) { stringstream s; - s << t << " " << (float(t) / TIME_HZ) << "s " << (float(t) * fps / TIME_HZ) << "f"; + s << t << " " << t.seconds() << "s " << t.frames(fps) << "f"; return s.str (); } @@ -98,7 +98,7 @@ BOOST_AUTO_TEST_CASE (long_ffmpeg_seek_test) player->Audio.connect (boost::bind (&process_audio, _1, _2)); for (float i = 0; i < 10; i += 0.1) { - check (player, i * TIME_HZ); + check (player, DCPTime::from_seconds (i)); } } diff --git a/test/play_test.cc b/test/play_test.cc index 54fe2699f..5ffc55a8e 100644 --- a/test/play_test.cc +++ b/test/play_test.cc @@ -102,7 +102,7 @@ BOOST_AUTO_TEST_CASE (play_test) BOOST_CHECK_EQUAL (A->position(), 0); /* A is 16 frames long at 25 fps */ - BOOST_CHECK_EQUAL (B->position(), 16 * TIME_HZ / 25); + BOOST_CHECK_EQUAL (B->position(), DCPTime::from_frames (16, 25)); shared_ptr player = film->make_player (); PlayerWrapper wrap (player); @@ -117,10 +117,10 @@ BOOST_AUTO_TEST_CASE (play_test) } } - player->seek (6 * TIME_HZ / 25, true); + player->seek (DCPTime::from_frames (6, 25), true); optional