From 4a74ca2cb973585122e84c21ff48ff4ff1ebd488 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Wed, 4 Jun 2014 13:10:28 +0100 Subject: [PATCH] Back-port v2's rename and slight extension of FrameRateConversion. --- src/lib/audio_content.cc | 5 +- src/lib/ffmpeg_content.cc | 3 +- src/lib/frame_rate_change.cc | 91 ++++++++++++++++++++++++++++++++++++ src/lib/frame_rate_change.h | 58 +++++++++++++++++++++++ src/lib/image_content.cc | 3 +- src/lib/player.cc | 3 +- src/lib/util.cc | 38 --------------- src/lib/util.h | 34 -------------- src/lib/video_content.cc | 3 +- src/lib/wscript | 1 + src/wx/video_panel.cc | 3 +- test/frame_rate_test.cc | 39 ++++++++-------- 12 files changed, 183 insertions(+), 98 deletions(-) create mode 100644 src/lib/frame_rate_change.cc create mode 100644 src/lib/frame_rate_change.h diff --git a/src/lib/audio_content.cc b/src/lib/audio_content.cc index 79912f1ae..7d77154e1 100644 --- a/src/lib/audio_content.cc +++ b/src/lib/audio_content.cc @@ -25,6 +25,7 @@ #include "film.h" #include "exceptions.h" #include "config.h" +#include "frame_rate_change.h" #include "i18n.h" @@ -159,7 +160,7 @@ AudioContent::output_audio_frame_rate () const /* Resample to a DCI-approved sample rate */ double t = dcp_audio_frame_rate (content_audio_frame_rate ()); - FrameRateConversion frc (video_frame_rate(), film->video_frame_rate()); + FrameRateChange frc (video_frame_rate(), film->video_frame_rate()); /* Compensate if the DCP is being run at a different frame rate to the source; that is, if the video is run such that it will @@ -168,7 +169,7 @@ AudioContent::output_audio_frame_rate () const */ if (frc.change_speed) { - t *= video_frame_rate() * frc.factor() / film->video_frame_rate(); + t /= frc.speed_up; } return rint (t); diff --git a/src/lib/ffmpeg_content.cc b/src/lib/ffmpeg_content.cc index 4ea6dbc6a..4d886a6dd 100644 --- a/src/lib/ffmpeg_content.cc +++ b/src/lib/ffmpeg_content.cc @@ -31,6 +31,7 @@ extern "C" { #include "film.h" #include "log.h" #include "exceptions.h" +#include "frame_rate_change.h" #include "i18n.h" @@ -407,7 +408,7 @@ FFmpegContent::full_length () const shared_ptr film = _film.lock (); assert (film); - FrameRateConversion frc (video_frame_rate (), film->video_frame_rate ()); + FrameRateChange frc (video_frame_rate (), film->video_frame_rate ()); return video_length_after_3d_combine() * frc.factor() * TIME_HZ / film->video_frame_rate (); } 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 3b87fcf00..6acf0bab9 100644 --- a/src/lib/image_content.cc +++ b/src/lib/image_content.cc @@ -24,6 +24,7 @@ #include "compose.hpp" #include "film.h" #include "job.h" +#include "frame_rate_change.h" #include "i18n.h" @@ -130,7 +131,7 @@ ImageContent::full_length () const shared_ptr film = _film.lock (); assert (film); - FrameRateConversion frc (video_frame_rate(), film->video_frame_rate ()); + FrameRateChange frc (video_frame_rate(), film->video_frame_rate ()); return video_length_after_3d_combine() * frc.factor() * TIME_HZ / video_frame_rate(); } diff --git a/src/lib/player.cc b/src/lib/player.cc index 68df8ea70..20cea7e4a 100644 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@ -36,6 +36,7 @@ #include "log.h" #include "scaler.h" #include "player_video_frame.h" +#include "frame_rate_change.h" #define LOG_GENERAL(...) _film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL); @@ -201,7 +202,7 @@ Player::process_video (weak_ptr weak_piece, shared_ptr shared_ptr content = dynamic_pointer_cast (piece->content); assert (content); - FrameRateConversion frc (content->video_frame_rate(), _film->video_frame_rate()); + FrameRateChange frc (content->video_frame_rate(), _film->video_frame_rate()); if (frc.skip && (frame % 2) == 1) { return; } diff --git a/src/lib/util.cc b/src/lib/util.cc index bbe6f77e1..fa7be179a 100644 --- a/src/lib/util.cc +++ b/src/lib/util.cc @@ -789,44 +789,6 @@ audio_channel_name (int c) return channels[c]; } -FrameRateConversion::FrameRateConversion (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); - } - - change_speed = !about_equal (source * factor(), dcp); - - 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); - } - } -} - bool valid_image_file (boost::filesystem::path f) { diff --git a/src/lib/util.h b/src/lib/util.h index 5d93456df..70bf495c6 100644 --- a/src/lib/util.h +++ b/src/lib/util.h @@ -73,40 +73,6 @@ extern boost::shared_ptr make_signer (); extern libdcp::Size fit_ratio_within (float ratio, libdcp::Size); extern std::string entities_to_text (std::string e); extern std::map split_get_request (std::string url); - -struct FrameRateConversion -{ - FrameRateConversion (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; - - std::string description; -}; - extern int dcp_audio_frame_rate (int); extern int stride_round_up (int, int const *, int); extern std::multimap read_key_value (std::istream& s); diff --git a/src/lib/video_content.cc b/src/lib/video_content.cc index 40772980f..ec5890b45 100644 --- a/src/lib/video_content.cc +++ b/src/lib/video_content.cc @@ -30,6 +30,7 @@ #include "util.h" #include "film.h" #include "exceptions.h" +#include "frame_rate_change.h" #include "i18n.h" @@ -367,7 +368,7 @@ VideoContent::time_to_content_video_frames (Time t) const shared_ptr film = _film.lock (); assert (film); - FrameRateConversion frc (video_frame_rate(), film->video_frame_rate()); + FrameRateChange frc (video_frame_rate(), film->video_frame_rate()); /* Here we are converting from time (in the DCP) to a frame number in the content. Hence we need to use the DCP's frame rate and the double/skip correction, not diff --git a/src/lib/wscript b/src/lib/wscript index f932a142d..517ad7787 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -30,6 +30,7 @@ sources = """ ffmpeg_examiner.cc film.cc filter.cc + frame_rate_change.cc internet.cc image.cc image_content.cc diff --git a/src/wx/video_panel.cc b/src/wx/video_panel.cc index ac4c29222..2d874b959 100644 --- a/src/wx/video_panel.cc +++ b/src/wx/video_panel.cc @@ -24,6 +24,7 @@ #include "lib/config.h" #include "lib/util.h" #include "lib/ratio.h" +#include "lib/frame_rate_change.h" #include "filter_dialog.h" #include "video_panel.h" #include "wx_util.h" @@ -330,7 +331,7 @@ VideoPanel::setup_description () d << wxString::Format (_("Content frame rate %.4f\n"), vcs->video_frame_rate ()); ++lines; - FrameRateConversion frc (vcs->video_frame_rate(), _editor->film()->video_frame_rate ()); + FrameRateChange frc (vcs->video_frame_rate(), _editor->film()->video_frame_rate ()); d << std_to_wx (frc.description) << "\n"; ++lines; diff --git a/test/frame_rate_test.cc b/test/frame_rate_test.cc index fdfdcf452..8981a9d7e 100644 --- a/test/frame_rate_test.cc +++ b/test/frame_rate_test.cc @@ -22,11 +22,12 @@ #include "lib/config.h" #include "lib/ffmpeg_content.h" #include "lib/playlist.h" +#include "lib/frame_rate_change.h" #include "test.h" using boost::shared_ptr; -/* Test Playlist::best_dcp_frame_rate and FrameRateConversion +/* Test Playlist::best_dcp_frame_rate and FrameRateChange with a single piece of content. */ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) @@ -47,7 +48,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) content->_video_frame_rate = 60; int best = film->playlist()->best_dcp_frame_rate (); - FrameRateConversion frc = FrameRateConversion (60, best); + FrameRateChange frc = FrameRateChange (60, best); BOOST_CHECK_EQUAL (best, 30); BOOST_CHECK_EQUAL (frc.skip, true); BOOST_CHECK_EQUAL (frc.repeat, 1); @@ -55,7 +56,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) content->_video_frame_rate = 50; best = film->playlist()->best_dcp_frame_rate (); - frc = FrameRateConversion (50, best); + frc = FrameRateChange (50, best); BOOST_CHECK_EQUAL (best, 25); BOOST_CHECK_EQUAL (frc.skip, true); BOOST_CHECK_EQUAL (frc.repeat, 1); @@ -63,7 +64,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) content->_video_frame_rate = 48; best = film->playlist()->best_dcp_frame_rate (); - frc = FrameRateConversion (48, best); + frc = FrameRateChange (48, best); BOOST_CHECK_EQUAL (best, 24); BOOST_CHECK_EQUAL (frc.skip, true); BOOST_CHECK_EQUAL (frc.repeat, 1); @@ -71,7 +72,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) content->_video_frame_rate = 30; best = film->playlist()->best_dcp_frame_rate (); - frc = FrameRateConversion (30, best); + frc = FrameRateChange (30, best); BOOST_CHECK_EQUAL (best, 30); BOOST_CHECK_EQUAL (frc.skip, false); BOOST_CHECK_EQUAL (frc.repeat, 1); @@ -79,7 +80,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) content->_video_frame_rate = 29.97; best = film->playlist()->best_dcp_frame_rate (); - frc = FrameRateConversion (29.97, best); + frc = FrameRateChange (29.97, best); BOOST_CHECK_EQUAL (best, 30); BOOST_CHECK_EQUAL (frc.skip, false); BOOST_CHECK_EQUAL (frc.repeat, 1); @@ -87,7 +88,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) content->_video_frame_rate = 25; best = film->playlist()->best_dcp_frame_rate (); - frc = FrameRateConversion (25, best); + frc = FrameRateChange (25, best); BOOST_CHECK_EQUAL (best, 25); BOOST_CHECK_EQUAL (frc.skip, false); BOOST_CHECK_EQUAL (frc.repeat, 1); @@ -95,7 +96,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) content->_video_frame_rate = 24; best = film->playlist()->best_dcp_frame_rate (); - frc = FrameRateConversion (24, best); + frc = FrameRateChange (24, best); BOOST_CHECK_EQUAL (best, 24); BOOST_CHECK_EQUAL (frc.skip, false); BOOST_CHECK_EQUAL (frc.repeat, 1); @@ -103,7 +104,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) content->_video_frame_rate = 14.5; best = film->playlist()->best_dcp_frame_rate (); - frc = FrameRateConversion (14.5, best); + frc = FrameRateChange (14.5, best); BOOST_CHECK_EQUAL (best, 30); BOOST_CHECK_EQUAL (frc.skip, false); BOOST_CHECK_EQUAL (frc.repeat, 2); @@ -111,7 +112,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) content->_video_frame_rate = 12.6; best = film->playlist()->best_dcp_frame_rate (); - frc = FrameRateConversion (12.6, best); + frc = FrameRateChange (12.6, best); BOOST_CHECK_EQUAL (best, 25); BOOST_CHECK_EQUAL (frc.skip, false); BOOST_CHECK_EQUAL (frc.repeat, 2); @@ -119,7 +120,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) content->_video_frame_rate = 12.4; best = film->playlist()->best_dcp_frame_rate (); - frc = FrameRateConversion (12.4, best); + frc = FrameRateChange (12.4, best); BOOST_CHECK_EQUAL (best, 25); BOOST_CHECK_EQUAL (frc.skip, false); BOOST_CHECK_EQUAL (frc.repeat, 2); @@ -127,7 +128,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) content->_video_frame_rate = 12; best = film->playlist()->best_dcp_frame_rate (); - frc = FrameRateConversion (12, best); + frc = FrameRateChange (12, best); BOOST_CHECK_EQUAL (best, 24); BOOST_CHECK_EQUAL (frc.skip, false); BOOST_CHECK_EQUAL (frc.repeat, 2); @@ -144,7 +145,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) content->_video_frame_rate = 60; best = film->playlist()->best_dcp_frame_rate (); - frc = FrameRateConversion (60, best); + frc = FrameRateChange (60, best); BOOST_CHECK_EQUAL (best, 60); BOOST_CHECK_EQUAL (frc.skip, false); BOOST_CHECK_EQUAL (frc.repeat, 1); @@ -152,7 +153,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) content->_video_frame_rate = 50; best = film->playlist()->best_dcp_frame_rate (); - frc = FrameRateConversion (50, best); + frc = FrameRateChange (50, best); BOOST_CHECK_EQUAL (best, 50); BOOST_CHECK_EQUAL (frc.skip, false); BOOST_CHECK_EQUAL (frc.repeat, 1); @@ -160,7 +161,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) content->_video_frame_rate = 48; best = film->playlist()->best_dcp_frame_rate (); - frc = FrameRateConversion (48, best); + frc = FrameRateChange (48, best); BOOST_CHECK_EQUAL (best, 48); BOOST_CHECK_EQUAL (frc.skip, false); BOOST_CHECK_EQUAL (frc.repeat, 1); @@ -168,7 +169,7 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) /* Check some out-there conversions (not the best) */ - frc = FrameRateConversion (14.99, 24); + frc = FrameRateChange (14.99, 24); BOOST_CHECK_EQUAL (frc.skip, false); BOOST_CHECK_EQUAL (frc.repeat, 2); BOOST_CHECK_EQUAL (frc.change_speed, true); @@ -181,14 +182,14 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single) content->_video_frame_rate = 25; best = film->playlist()->best_dcp_frame_rate (); - frc = FrameRateConversion (25, best); + frc = FrameRateChange (25, best); BOOST_CHECK_EQUAL (best, 24); BOOST_CHECK_EQUAL (frc.skip, false); BOOST_CHECK_EQUAL (frc.repeat, 1); BOOST_CHECK_EQUAL (frc.change_speed, true); } -/* Test Playlist::best_dcp_frame_rate and FrameRateConversion +/* Test Playlist::best_dcp_frame_rate and FrameRateChange with two pieces of content. */ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_double) @@ -266,7 +267,7 @@ BOOST_AUTO_TEST_CASE (audio_sampling_rate_test) content->_video_frame_rate = 14.99; film->set_video_frame_rate (25); content->set_audio_stream (shared_ptr (new FFmpegAudioStream ("a", 42, 16000, 0))); - /* The FrameRateConversion within output_audio_frame_rate should choose to double-up + /* The FrameRateChange within output_audio_frame_rate should choose to double-up the 14.99 fps video to 30 and then run it slow at 25. */ BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), rint (48000 * 2 * 14.99 / 25)); -- 2.30.2