Fix drop-frame: remove float framerate

This commit is contained in:
Fischlurch 2011-05-21 06:58:15 +02:00
parent b6f81d1e1e
commit 49d5a9592a
3 changed files with 177 additions and 138 deletions

View file

@ -28,7 +28,7 @@ extern "C" {
#include "lib/tmpbuf.h"
}
#include <limits.h>
#include <limits>
#include <math.h>
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<gavl_time_t>::max() / framerate;
const int64_t limit_den = std::numeric_limits<gavl_time_t>::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<int>::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
}

View file

@ -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.

View file

@ -26,6 +26,13 @@ typedef unsigned int uint;
#include <nobug.h>
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);