From 49d5a9592a8cab6c6c1b2a3b56ceadb804c66611 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 21 May 2011 06:58:15 +0200 Subject: [PATCH] Fix drop-frame: remove float framerate --- src/lib/time.cpp | 135 +++++++++++++++++++++++------------ src/lib/time.h | 37 ++++------ tests/library/test-time.c | 143 ++++++++++++++++++++------------------ 3 files changed, 177 insertions(+), 138 deletions(-) diff --git a/src/lib/time.cpp b/src/lib/time.cpp index 9a760ac4d..ff9815f95 100644 --- a/src/lib/time.cpp +++ b/src/lib/time.cpp @@ -28,7 +28,7 @@ extern "C" { #include "lib/tmpbuf.h" } -#include +#include #include using util::floordiv; @@ -102,25 +102,55 @@ lumiera_frame_duration (FrameRate const& fps) } -namespace { // implementation helper +namespace { // implementation: basic frame quantisation.... - inline long long + inline int64_t calculate_quantisation (gavl_time_t time, gavl_time_t origin, gavl_time_t grid) { time -= origin; return floordiv (time,grid); } + + inline int64_t + calculate_quantisation (gavl_time_t time, gavl_time_t origin, uint framerate, uint framerate_divisor=1) + { + REQUIRE (framerate); + REQUIRE (framerate_divisor); + + const int64_t limit_num = std::numeric_limits::max() / framerate; + const int64_t limit_den = std::numeric_limits::max() / framerate_divisor; + const int64_t microScale(GAVL_TIME_SCALE); + + // protect against numeric overflow + if (abs(time) < limit_num && microScale < limit_den) + { + // safe to calculate "time * framerate" + time -= origin; + return floordiv (time*framerate, microScale*framerate_divisor); + } + else + { + // direct calculation will overflow. + // use the less precise method instead... + gavl_time_t frameDuration = microScale / framerate; // truncated to µs + return calculate_quantisation (time,origin, frameDuration); + } + } } int64_t lumiera_quantise_frames (gavl_time_t time, gavl_time_t origin, gavl_time_t grid) { - ENSURE (sizeof(int64_t) <= sizeof(long long)); - return calculate_quantisation (time, origin, grid); } +int64_t +lumiera_quantise_frames_fps (gavl_time_t time, gavl_time_t origin, uint framerate) +{ + return calculate_quantisation (time,origin,framerate); +} + gavl_time_t lumiera_quantise_time (gavl_time_t time, gavl_time_t origin, gavl_time_t grid) { @@ -149,9 +179,9 @@ lumiera_build_time(long millis, uint secs, uint mins, uint hours) } gavl_time_t -lumiera_build_time_fps (float fps, uint frames, uint secs, uint mins, uint hours) +lumiera_build_time_fps (uint fps, uint frames, uint secs, uint mins, uint hours) { - gavl_time_t time = frames * (1000.0 / fps) + gavl_time_t time = 1000LL * frames/fps + 1000 * secs + 1000 * 60 * mins + 1000 * 60 * 60 * hours; @@ -159,19 +189,6 @@ lumiera_build_time_fps (float fps, uint frames, uint secs, uint mins, uint hours return time; } -gavl_time_t -lumiera_build_time_ntsc_drop (uint frames, uint secs, uint mins, uint hours) -{ - uint64_t total_mins = 60 * hours + mins; - uint64_t total_frames = 108000 * hours - + 1800 * mins - + 30 * secs - + frames - - 2 * (total_mins - total_mins / 10); - - return lumiera_framecount_to_time (total_frames, FrameRate::NTSC); -} - int lumiera_time_hours (gavl_time_t time) { @@ -197,56 +214,84 @@ lumiera_time_millis (gavl_time_t time) } int -lumiera_time_frames (gavl_time_t time, float fps) +lumiera_time_frames (gavl_time_t time, uint fps) { - return (fps * (lumiera_time_millis(time))) / 1000; + REQUIRE (fps < uint(std::numeric_limits::max())); + return floordiv (lumiera_time_millis(time) * int(fps), GAVL_TIME_SCALE_MS); } -int -lumiera_time_frame_count (gavl_time_t time, float fps) -{ - REQUIRE (fps > 0); - - return roundf((time / GAVL_TIME_SCALE_MS / 1000.0f) * fps); -} -/** - * This function is used in the NTSC drop-frame functions to avoid code - * repetition. - * @return the frame number for given time - */ -static int -ntsc_drop_get_frame_number (gavl_time_t time) -{ - int frame = lumiera_time_frame_count (time, NTSC_DROP_FRAME_FPS); + + +/* ===== NTSC drop-frame conversions ===== */ + + +namespace { // implementation helper - int d = frame / 17982; // 17982 = 600 * 29.97 - int m = frame % 17982; // 17982 = 600 * 29.97 + const uint FRAMES_PER_10min = 10*60 * 30000/1001; + const uint FRAMES_PER_1min = 1*60 * 30000/1001; + const uint DISCREPANCY = (1*60 * 30) - FRAMES_PER_1min; + - return frame + 18*d + 2*((m - 2) / 1798); // 1798 = 60 * 29.97 + /** reverse the drop-frame calculation + * @param time absolute time value in micro ticks + * @return the absolute frame number using NTSC drop-frame encoding + * @todo I doubt this works correct for negative times!! + */ + inline int64_t + calculate_drop_frame_number (gavl_time_t time) + { + int64_t frameNr = calculate_quantisation (time, 0, 30000, 1001); + + // partition into 10 minute segments + lldiv_t tenMinFrames = lldiv (frameNr, FRAMES_PER_10min); + + // ensure the drop-frame incidents happen at full minutes; + // at start of each 10-minute segment *no* drop incident happens, + // thus we need to correct discrepancy between nominal/real framerate once: + int64_t remainingMinutes = (tenMinFrames.rem - DISCREPANCY) / FRAMES_PER_1min; + + int64_t dropIncidents = (10-1) * tenMinFrames.quot + remainingMinutes; + return frameNr + 2*dropIncidents; + } } int lumiera_time_ntsc_drop_frames (gavl_time_t time) { - return ntsc_drop_get_frame_number(time) % 30; + return calculate_drop_frame_number(time) % 30; } int lumiera_time_ntsc_drop_seconds (gavl_time_t time) { - return ntsc_drop_get_frame_number(time) / 30 % 60; + return calculate_drop_frame_number(time) / 30 % 60; } int lumiera_time_ntsc_drop_minutes (gavl_time_t time) { - return ntsc_drop_get_frame_number(time) / 30 / 60 % 60; + return calculate_drop_frame_number(time) / 30 / 60 % 60; } int lumiera_time_ntsc_drop_hours (gavl_time_t time) { - return ntsc_drop_get_frame_number(time) / 30 / 60 / 60 % 24; + return calculate_drop_frame_number(time) / 30 / 60 / 60 % 24; } +gavl_time_t +lumiera_build_time_ntsc_drop (uint frames, uint secs, uint mins, uint hours) +{ + uint64_t total_mins = 60 * hours + mins; + uint64_t total_frames = 30*60*60 * hours + + 30*60 * mins + + 30 * secs + + frames + - 2 * (total_mins - total_mins / 10); + gavl_time_t result = lumiera_framecount_to_time (total_frames, FrameRate::NTSC); + + if (0 != result) // compensate for truncating down on conversion + result += 1; // without this adjustment the frame number + return result; // would turn out off by -1 on back conversion +} diff --git a/src/lib/time.h b/src/lib/time.h index c017f6713..f321e176a 100644 --- a/src/lib/time.h +++ b/src/lib/time.h @@ -94,8 +94,6 @@ lumiera_frame_duration (lib::time::FrameRate const& fps); extern "C" { /* ===================== C interface ======================== */ #endif -#define NTSC_DROP_FRAME_FPS 29.97 -/* TODO: replace this by lib::time::FrameRate::NTSC */ /** * Formats a time value in H:MM:SS.mmm format into a temporary buffer. @@ -116,6 +114,9 @@ lumiera_tmpbuf_print_time (gavl_time_t time); int64_t lumiera_quantise_frames (gavl_time_t time, gavl_time_t origin, gavl_time_t grid); +int64_t +lumiera_quantise_frames_fps (gavl_time_t time, gavl_time_t origin, uint framerate); + /** * Similar to #lumiera_quantise_frames, but returns a grid aligned \em time value * @return time of start of the grid interval containing the given time, @@ -138,7 +139,7 @@ gavl_time_t lumiera_time_of_gridpoint (int64_t nr, gavl_time_t origin, gavl_time_t grid); /** - * Builds a time value by summing up the given components. + * Build a time value by summing up the given components. * @param millis number of milliseconds * @param secs number of seconds * @param mins number of minutes @@ -149,15 +150,14 @@ lumiera_build_time (long millis, uint secs, uint mins, uint hours); /** * Builds a time value by summing up the given components. - * @todo replace float framerates by lib::time::FrameRate - * @deprecated this function doesn't fit in the general time handling concept. - * SMPTE is a timecode format, and should be distinguished from a - * raw time value. For any timecode format, we always require an explicit - * 'time grid' (coordinate system), which also defines a zero point. - * Usually, this time grid is managed and provided by the session + * @param fps framerate (frames per second) + * @param frames number of additional frames + * @param secs number of seconds + * @param mins number of minutes + * @param hours number of hours */ gavl_time_t -lumiera_build_time_fps (float fps, uint frames, uint secs, uint mins, uint hours); +lumiera_build_time_fps (uint fps, uint frames, uint secs, uint mins, uint hours); /** * Builds a time value by summing up the given components. @@ -188,22 +188,11 @@ int lumiera_time_millis (gavl_time_t time); /** - * Extract the frame part of given time, using the given fps. - * @param fps frame rate - * @todo use the rational lib::time::FrameRate instead of a float + * Extract the remaining frame part of given time. + * @param fps frame rate (frames per second) */ int -lumiera_time_frames (gavl_time_t time, float fps); - -/** - * Extract the frame count for the given time, using the given fps. - * @todo use the rational lib::time::FrameRate instead of a float - * @deprecated should be moved down into the implementation; - * this function doesn't fit into the general time handling, - * because it doesn't link to a time grid (coordinate system) - */ -int -lumiera_time_frame_count (gavl_time_t time, float fps); +lumiera_time_frames (gavl_time_t time, uint fps); /** * Extract the frame part of given time, using NTSC drop-frame timecode. diff --git a/tests/library/test-time.c b/tests/library/test-time.c index 3c1957645..b7922e778 100644 --- a/tests/library/test-time.c +++ b/tests/library/test-time.c @@ -26,6 +26,13 @@ typedef unsigned int uint; #include +static int +calculate_framecount (gavl_time_t t, uint fps) +{ + return lumiera_quantise_frames_fps (t,0,fps); +} + + TESTS_BEGIN const int FRAMES = 15; @@ -33,7 +40,7 @@ const int MILLIS = 700; const int SECONDS = 20; const int MINUTES = 55; const int HOURS = 3; -const float FPS = 24.0; +const int FPS = 24; /* * 1. Basic functionality @@ -44,29 +51,29 @@ TEST (basic) // Zero gavl_time_t t = lumiera_build_time (0,0,0,0); - CHECK ((gavl_time_t) t == 0); - CHECK (lumiera_time_millis (t) == 0); - CHECK (lumiera_time_seconds (t) == 0); - CHECK (lumiera_time_minutes (t) == 0); - CHECK (lumiera_time_hours (t) == 0); - CHECK (lumiera_time_frames (t, FPS) == 0); - CHECK (lumiera_time_frames (t, FPS+5) == 0); - CHECK (lumiera_time_frame_count (t, FPS) == 0); - CHECK (lumiera_time_frame_count (t, FPS+5) == 0); + CHECK ((gavl_time_t) t == 0); + CHECK (lumiera_time_millis (t) == 0); + CHECK (lumiera_time_seconds (t) == 0); + CHECK (lumiera_time_minutes (t) == 0); + CHECK (lumiera_time_hours (t) == 0); + CHECK (lumiera_time_frames (t, FPS) == 0); + CHECK (lumiera_time_frames (t, FPS+5) == 0); + CHECK (calculate_framecount (t,FPS) == 0); + CHECK (calculate_framecount (t, FPS+5) == 0); ECHO ("%s", lumiera_tmpbuf_print_time (t)); // Non-zero t = lumiera_build_time (MILLIS, SECONDS, MINUTES, HOURS); - CHECK (lumiera_time_millis (t) == MILLIS); - CHECK (lumiera_time_seconds (t) == SECONDS); - CHECK (lumiera_time_minutes (t) == MINUTES); - CHECK (lumiera_time_hours (t) == HOURS); - CHECK (lumiera_time_frames (t, FPS) == (int)((FPS * MILLIS) / 1000)); - CHECK (lumiera_time_frames (t, FPS+5) == (int)(((FPS+5) * MILLIS) / 1000)); - CHECK (lumiera_time_frame_count (t, FPS) == 338897); - CHECK (lumiera_time_frame_count (t, FPS+5) == 409500); + CHECK (lumiera_time_millis (t) == MILLIS); + CHECK (lumiera_time_seconds (t) == SECONDS); + CHECK (lumiera_time_minutes (t) == MINUTES); + CHECK (lumiera_time_hours (t) == HOURS); + CHECK (lumiera_time_frames (t, FPS) == FPS * MILLIS / 1000); + CHECK (lumiera_time_frames (t, FPS+5) == (FPS+5) * MILLIS / 1000); + CHECK (calculate_framecount (t, FPS) == 338896); + CHECK (calculate_framecount (t, FPS+5) == 409500); ECHO ("%s", lumiera_tmpbuf_print_time (t)); } @@ -79,14 +86,14 @@ TEST (fps) { gavl_time_t t = lumiera_build_time_fps (FPS, FRAMES, SECONDS, MINUTES, HOURS); - CHECK (lumiera_time_millis (t) == (int)(FRAMES * (1000.0 / FPS))); - CHECK (lumiera_time_seconds (t) == SECONDS); - CHECK (lumiera_time_minutes (t) == MINUTES); - CHECK (lumiera_time_hours (t) == HOURS); - CHECK (lumiera_time_frames (t, FPS) == FRAMES); - CHECK (lumiera_time_frames (t, FPS+5) == (int)(((FPS+5) * 625) / 1000)); - CHECK (lumiera_time_frame_count (t, FPS) == 338895); - CHECK (lumiera_time_frame_count (t, FPS+5) == 409498); + CHECK (lumiera_time_millis (t) == FRAMES * 1000 / FPS); + CHECK (lumiera_time_seconds (t) == SECONDS); + CHECK (lumiera_time_minutes (t) == MINUTES); + CHECK (lumiera_time_hours (t) == HOURS); + CHECK (lumiera_time_frames (t, FPS) == FRAMES); + CHECK (lumiera_time_frames (t, FPS+5) == FRAMES * (FPS+5)/FPS); + CHECK (calculate_framecount (t, FPS) == 338895); + CHECK (calculate_framecount (t, FPS+5) == 409498); } /* @@ -98,40 +105,37 @@ TEST (ntsc_drop_frame) // Make sure frame 0 begins at 0 gavl_time_t t = lumiera_build_time_ntsc_drop (0, 0, 0, 0); - CHECK ((gavl_time_t) t == 0); - CHECK (lumiera_time_millis (t) == 0); - CHECK (lumiera_time_seconds (t) == 0); - CHECK (lumiera_time_minutes (t) == 0); - CHECK (lumiera_time_hours (t) == 0); - CHECK (lumiera_time_frames (t, FPS) == 0); - CHECK (lumiera_time_frames (t, FPS+5) == 0); - CHECK (lumiera_time_frame_count (t, FPS) == 0); - CHECK (lumiera_time_frame_count (t, FPS+5) == 0); - - // Use some arbitrary value to test with - const int FRAMES = 15; - const int SECONDS = 20; - const int MINUTES = 55; - const int HOURS = 3; - const float FPS = 24.0; - + CHECK ((gavl_time_t) t == 0); + CHECK (lumiera_time_millis (t) == 0); + CHECK (lumiera_time_seconds (t) == 0); + CHECK (lumiera_time_minutes (t) == 0); + CHECK (lumiera_time_hours (t) == 0); + CHECK (lumiera_time_frames (t, FPS) == 0); + CHECK (lumiera_time_frames (t, FPS+5) == 0); + CHECK (calculate_framecount (t, FPS) == 0); + CHECK (calculate_framecount (t, FPS+5) == 0); + + + t = lumiera_build_time_ntsc_drop (FRAMES, SECONDS, MINUTES, HOURS); - - CHECK (lumiera_time_millis (t) == 486); - CHECK (lumiera_time_seconds (t) == SECONDS); - CHECK (lumiera_time_minutes (t) == MINUTES); - CHECK (lumiera_time_hours (t) == HOURS); - - // Check standard frame calculations to verify build_time_ntsc_drop - CHECK (lumiera_time_frames (t, FPS) == 11); - CHECK (lumiera_time_frames (t, FPS+5) == 14); - CHECK (lumiera_time_frame_count (t, FPS) == 338892); - CHECK (lumiera_time_frame_count (t, FPS+5) == 409494); - - // Frames for NTSC drop - CHECK (lumiera_time_ntsc_drop_frames (t) == 15); - CHECK (lumiera_time_frame_count (t, NTSC_DROP_FRAME_FPS) == 423191); - + + // Calculate manually what result to expect.... + int frames = FRAMES + 30*SECONDS + 30*60*MINUTES + 30*60*60*HOURS; // sum up using nominal 30fps + int minutes_to_drop_frames = (MINUTES - MINUTES/10) + (HOURS * 54); // but every minute, with the exception of every 10 minutes... + frames -= 2*minutes_to_drop_frames; // ...drop 2 frames + int64_t expectedMillis = 1000LL * frames * 1001/30000; // now convert frames to time, using the real framerate + + expectedMillis %= 1000; // look at the remainder.. + CHECK (lumiera_time_millis (t) == expectedMillis); + + CHECK (lumiera_time_seconds (t) == SECONDS); // while all other components should come out equal as set + CHECK (lumiera_time_minutes (t) == MINUTES); + CHECK (lumiera_time_hours (t) == HOURS); + + // Reverse calculate frames for NTSC drop +//CHECK (lumiera_quantise_frames (t, 0, dropFrameDuration) == frames); // the total nominal frames + CHECK (lumiera_time_ntsc_drop_frames (t) == FRAMES); // maximum one frame off due to rounding + // Cover the whole value range; // Manually construct a drop-frame timecode // Make sure our library function returns the same times. @@ -140,8 +144,8 @@ TEST (ntsc_drop_frame) int frame; int hrs; for (hrs = 0; hrs <= 24; hrs += 6) - for (min = 0; min <= 59; min += 8) - for (sec = 0; sec <= 59; sec += 8) + for (min = 0; min <= 59; min += 1) + for (sec = 0; sec <= 59; sec += 10) for (frame = 0; frame <= 29; frame++) { // Skip dropped frames @@ -149,14 +153,15 @@ TEST (ntsc_drop_frame) continue; t = lumiera_build_time_ntsc_drop(frame, sec, min, hrs); - - /* ECHO ("%02d:%02d:%02d.%02d", */ - /* lumiera_time_ntsc_drop_hours (t), */ - /* lumiera_time_ntsc_drop_minutes (t), */ - /* lumiera_time_ntsc_drop_seconds (t), */ - /* lumiera_time_ntsc_drop_frames (t)); */ - - CHECK (lumiera_time_ntsc_drop_frames (t) == frame); + /* + ECHO ("%02d:%02d:%02d;%02d" + , lumiera_time_ntsc_drop_hours (t) + , lumiera_time_ntsc_drop_minutes (t) + , lumiera_time_ntsc_drop_seconds (t) + , lumiera_time_ntsc_drop_frames (t) + ); + */ + CHECK (lumiera_time_ntsc_drop_frames (t) == frame); CHECK (lumiera_time_ntsc_drop_seconds (t) == sec); CHECK (lumiera_time_ntsc_drop_minutes (t) == min); CHECK (lumiera_time_ntsc_drop_hours (t) == hrs % 24);