From e9ee53e42d7f8bbcd2a454ee9de5f3e66ec06b03 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Wed, 16 Jan 2013 23:36:16 +0000 Subject: [PATCH] Add some tests and hopefully clarify the DCPFrameRate class. --- src/lib/config.cc | 4 ++ src/lib/config.h | 11 ++++- src/lib/film.cc | 15 ++++--- src/lib/util.cc | 89 +++++++++++++++++++++++++++++---------- src/lib/util.h | 21 ++++++++-- test/test.cc | 105 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 213 insertions(+), 32 deletions(-) diff --git a/src/lib/config.cc b/src/lib/config.cc index 65d01bf00..307b96844 100644 --- a/src/lib/config.cc +++ b/src/lib/config.cc @@ -44,6 +44,10 @@ Config::Config () , _tms_path (".") , _sound_processor (SoundProcessor::from_id ("dolby_cp750")) { + _allowed_dcp_frame_rates.push_back (24); + _allowed_dcp_frame_rates.push_back (25); + _allowed_dcp_frame_rates.push_back (30); + ifstream f (file().c_str ()); string line; while (getline (f, line)) { diff --git a/src/lib/config.h b/src/lib/config.h index c84ce76b5..c41437efb 100644 --- a/src/lib/config.h +++ b/src/lib/config.h @@ -94,6 +94,10 @@ public: return _sound_processor; } + std::list allowed_dcp_frame_rates () const { + return _allowed_dcp_frame_rates; + } + /** @param n New number of local encoding threads */ void set_num_local_encoding_threads (int n) { _num_local_encoding_threads = n; @@ -140,7 +144,11 @@ public: void set_tms_password (std::string p) { _tms_password = p; } - + + void set_allowed_dcp_frame_rates (std::list const & r) { + _allowed_dcp_frame_rates = r; + } + void write () const; static Config* instance (); @@ -172,6 +180,7 @@ private: std::string _tms_password; /** Our sound processor */ SoundProcessor const * _sound_processor; + std::list _allowed_dcp_frame_rates; /** Singleton instance, or 0 */ static Config* _instance; diff --git a/src/lib/film.cc b/src/lib/film.cc index 17e14e544..83c60a5f5 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -255,7 +255,9 @@ Film::make_dcp (bool transcode) } log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? "still" : "video"))); - log()->log (String::compose ("Content length %1", length().get())); + if (length()) { + log()->log (String::compose ("Content length %1", length().get())); + } log()->log (String::compose ("Content digest %1", content_digest())); log()->log (String::compose ("%1 threads", Config::instance()->num_local_encoding_threads())); log()->log (String::compose ("J2K bandwidth %1", j2k_bandwidth())); @@ -707,13 +709,14 @@ Film::target_audio_sample_rate () const DCPFrameRate dfr (frames_per_second ()); - /* Compensate for the fact that video will be rounded to the - nearest integer number of frames per second. + /* Compensate if the DCP is being run at a different frame rate + to the source; that is, if the video is run such that it will + look different in the DCP compared to the source (slower or faster). + skip/repeat doesn't come into effect here. */ - int const mult = dfr.skip ? 2 : 1; - if (dfr.run_fast) { - t *= _frames_per_second * mult / dfr.frames_per_second; + if (dfr.change_speed) { + t *= _frames_per_second * dfr.factor() / dfr.frames_per_second; } return rint (t); diff --git a/src/lib/util.cc b/src/lib/util.cc index 4228ce6cf..b500ddc2f 100644 --- a/src/lib/util.cc +++ b/src/lib/util.cc @@ -26,6 +26,7 @@ #include #include #include +#include #ifdef DVDOMATIC_POSIX #include #include @@ -58,6 +59,7 @@ extern "C" { #include "dcp_content_type.h" #include "filter.h" #include "sound_processor.h" +#include "config.h" using namespace std; using namespace boost; @@ -356,29 +358,74 @@ static bool about_equal (float a, float b) return (fabs (a - b) < 1e-4); } +class FrameRateCandidate +{ +public: + FrameRateCandidate (float source_, int dcp_) + : source (source_) + , dcp (dcp_) + {} + + bool skip () const { + return !about_equal (source, dcp) && source > dcp; + } + + bool repeat () const { + return !about_equal (source, dcp) && source < dcp; + } + + float source; + int dcp; +}; + /** @param fps Arbitrary source frames-per-second value */ -DCPFrameRate::DCPFrameRate (float fps) - : frames_per_second (rint (fps)) - , skip (false) - , repeat (false) - , run_fast (false) -{ - if (about_equal (fps, 50)) { - /* XXX: not sure about this; just run at 50? - Ring Peter Jackson. - */ - frames_per_second = 25; - skip = true; - } else if (fps >= (27.5 / 2) && fps <= (32.5 / 2)) { - frames_per_second = 30; - repeat = true; - } else if (fps >= (24.5 / 2) && fps <= (27.5 / 2)) { - frames_per_second = 25; - repeat = true; - } else if (fps >= (20 / 2) && fps <= (24.5 / 2)) { - frames_per_second = 24; - repeat = true; +/** XXX: this could be slow-ish */ +DCPFrameRate::DCPFrameRate (float source_fps) +{ + list const allowed_dcp_frame_rates = Config::instance()->allowed_dcp_frame_rates (); + + /* Work out what rates we could manage, including those achieved by using skip / repeat. */ + list candidates; + + /* Start with the ones without skip / repeat so they will get matched in preference to skipped/repeated ones */ + for (list::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) { + candidates.push_back (FrameRateCandidate (*i, *i)); } + + /* Then the skip/repeat ones */ + for (list::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) { + candidates.push_back (FrameRateCandidate (float (*i) / 2, *i)); + candidates.push_back (FrameRateCandidate (float (*i) * 2, *i)); + } + + /* Pick the best one, bailing early if we hit an exact match */ + float error = numeric_limits::max (); + boost::optional best; + list::iterator i = candidates.begin(); + while (i != candidates.end()) { + + if (about_equal (i->source, source_fps)) { + best = *i; + break; + } + + float const e = fabs (i->source - source_fps); + if (e < error) { + error = e; + best = *i; + } + + ++i; + } + + if (!best) { + throw EncodeError ("cannot find a suitable DCP frame rate for this source"); + } + + frames_per_second = best->dcp; + skip = best->skip (); + repeat = best->repeat (); + change_speed = !about_equal (source_fps * factor(), frames_per_second); } /** @param An arbitrary sampling rate. diff --git a/src/lib/util.h b/src/lib/util.h index 50e7410d6..1fd2c0150 100644 --- a/src/lib/util.h +++ b/src/lib/util.h @@ -62,6 +62,19 @@ typedef int SourceFrame; struct DCPFrameRate { DCPFrameRate (float); + + /** @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; + } else if (repeat) { + return 2; + } + + return 1; + } /** frames per second for the DCP */ int frames_per_second; @@ -69,16 +82,16 @@ struct DCPFrameRate bool skip; /** true to repeat every frame once */ bool repeat; - /** true if this DCP will run its video faster than the source - * without taking into account `skip' and `repeat'. - * (i.e. run_fast will be true if + /** true if this DCP will run its video faster or slower than the source + * without taking into account `repeat'. + * (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 run_fast; + bool change_speed; }; enum ContentType { diff --git a/test/test.cc b/test/test.cc index c393aac5e..45a80f024 100644 --- a/test/test.cc +++ b/test/test.cc @@ -429,6 +429,111 @@ BOOST_AUTO_TEST_CASE (make_dcp_with_range_test) BOOST_CHECK_EQUAL (JobManager::instance()->errors(), false); } +/* Test the constructor of DCPFrameRate */ +BOOST_AUTO_TEST_CASE (dcp_frame_rate_test) +{ + /* Run some tests with a limited range of allowed rates */ + + std::list afr; + afr.push_back (24); + afr.push_back (25); + afr.push_back (30); + Config::instance()->set_allowed_dcp_frame_rates (afr); + + DCPFrameRate dfr = DCPFrameRate (60); + BOOST_CHECK_EQUAL (dfr.frames_per_second, 30); + BOOST_CHECK_EQUAL (dfr.skip, true); + BOOST_CHECK_EQUAL (dfr.repeat, false); + BOOST_CHECK_EQUAL (dfr.change_speed, false); + + dfr = DCPFrameRate (50); + BOOST_CHECK_EQUAL (dfr.frames_per_second, 25); + BOOST_CHECK_EQUAL (dfr.skip, true); + BOOST_CHECK_EQUAL (dfr.repeat, false); + BOOST_CHECK_EQUAL (dfr.change_speed, false); + + dfr = DCPFrameRate (48); + BOOST_CHECK_EQUAL (dfr.frames_per_second, 24); + BOOST_CHECK_EQUAL (dfr.skip, true); + BOOST_CHECK_EQUAL (dfr.repeat, false); + BOOST_CHECK_EQUAL (dfr.change_speed, false); + + dfr = DCPFrameRate (30); + BOOST_CHECK_EQUAL (dfr.skip, false); + BOOST_CHECK_EQUAL (dfr.frames_per_second, 30); + BOOST_CHECK_EQUAL (dfr.repeat, false); + BOOST_CHECK_EQUAL (dfr.change_speed, false); + + dfr = DCPFrameRate (29.97); + BOOST_CHECK_EQUAL (dfr.skip, false); + BOOST_CHECK_EQUAL (dfr.frames_per_second, 30); + BOOST_CHECK_EQUAL (dfr.repeat, false); + BOOST_CHECK_EQUAL (dfr.change_speed, true); + + dfr = DCPFrameRate (25); + BOOST_CHECK_EQUAL (dfr.skip, false); + BOOST_CHECK_EQUAL (dfr.frames_per_second, 25); + BOOST_CHECK_EQUAL (dfr.repeat, false); + BOOST_CHECK_EQUAL (dfr.change_speed, false); + + dfr = DCPFrameRate (24); + BOOST_CHECK_EQUAL (dfr.skip, false); + BOOST_CHECK_EQUAL (dfr.frames_per_second, 24); + BOOST_CHECK_EQUAL (dfr.repeat, false); + BOOST_CHECK_EQUAL (dfr.change_speed, false); + + dfr = DCPFrameRate (14.5); + BOOST_CHECK_EQUAL (dfr.skip, false); + BOOST_CHECK_EQUAL (dfr.frames_per_second, 30); + BOOST_CHECK_EQUAL (dfr.repeat, true); + BOOST_CHECK_EQUAL (dfr.change_speed, true); + + dfr = DCPFrameRate (12.6); + BOOST_CHECK_EQUAL (dfr.skip, false); + BOOST_CHECK_EQUAL (dfr.frames_per_second, 25); + BOOST_CHECK_EQUAL (dfr.repeat, true); + BOOST_CHECK_EQUAL (dfr.change_speed, true); + + dfr = DCPFrameRate (12.4); + BOOST_CHECK_EQUAL (dfr.skip, false); + BOOST_CHECK_EQUAL (dfr.frames_per_second, 25); + BOOST_CHECK_EQUAL (dfr.repeat, true); + BOOST_CHECK_EQUAL (dfr.change_speed, true); + + dfr = DCPFrameRate (12); + BOOST_CHECK_EQUAL (dfr.skip, false); + BOOST_CHECK_EQUAL (dfr.frames_per_second, 24); + BOOST_CHECK_EQUAL (dfr.repeat, true); + BOOST_CHECK_EQUAL (dfr.change_speed, false); + + /* Now add some more rates and see if it will use them + in preference to skip/repeat. + */ + + afr.push_back (48); + afr.push_back (50); + afr.push_back (60); + Config::instance()->set_allowed_dcp_frame_rates (afr); + + dfr = DCPFrameRate (60); + BOOST_CHECK_EQUAL (dfr.frames_per_second, 60); + BOOST_CHECK_EQUAL (dfr.skip, false); + BOOST_CHECK_EQUAL (dfr.repeat, false); + BOOST_CHECK_EQUAL (dfr.change_speed, false); + + dfr = DCPFrameRate (50); + BOOST_CHECK_EQUAL (dfr.frames_per_second, 50); + BOOST_CHECK_EQUAL (dfr.skip, false); + BOOST_CHECK_EQUAL (dfr.repeat, false); + BOOST_CHECK_EQUAL (dfr.change_speed, false); + + dfr = DCPFrameRate (48); + BOOST_CHECK_EQUAL (dfr.frames_per_second, 48); + BOOST_CHECK_EQUAL (dfr.skip, false); + BOOST_CHECK_EQUAL (dfr.repeat, false); + BOOST_CHECK_EQUAL (dfr.change_speed, false); +} + BOOST_AUTO_TEST_CASE (audio_sampling_rate_test) { shared_ptr f = new_test_film ("audio_sampling_rate_test"); -- 2.30.2