Add support for NTSC drop-frame timecode.

This commit is contained in:
Stefan Kangas 2010-12-13 18:22:12 +01:00 committed by Ichthyostega
parent 94f8379aa2
commit 6a44134833
4 changed files with 189 additions and 4 deletions

View file

@ -34,14 +34,14 @@ lumiera_tmpbuf_print_time (gavl_time_t time)
{ {
int milliseconds, seconds, minutes, hours; int milliseconds, seconds, minutes, hours;
int negative; int negative;
if(time < 0) if(time < 0)
{ {
negative = 1; negative = 1;
time = -time; time = -time;
} }
else negative = 0; else negative = 0;
time /= GAVL_TIME_SCALE_MS; time /= GAVL_TIME_SCALE_MS;
milliseconds = time % 1000; milliseconds = time % 1000;
time /= 1000; time /= 1000;
@ -50,10 +50,10 @@ lumiera_tmpbuf_print_time (gavl_time_t time)
minutes = time % 60; minutes = time % 60;
time /= 60; time /= 60;
hours = time; hours = time;
char *buffer = lumiera_tmpbuf_snprintf(64, "%s%02d:%02d:%02d.%03d", char *buffer = lumiera_tmpbuf_snprintf(64, "%s%02d:%02d:%02d.%03d",
negative ? "-" : "", hours, minutes, seconds, milliseconds); negative ? "-" : "", hours, minutes, seconds, milliseconds);
ENSURE(buffer != NULL); ENSURE(buffer != NULL);
return buffer; return buffer;
} }
@ -88,6 +88,24 @@ lumiera_build_time_fps (float fps, uint frames, uint secs, uint mins, uint hours
return time; return time;
} }
gavl_time_t
lumiera_build_time_ntsc_drop (uint frames, uint secs, uint mins, uint hours)
{
REQUIRE (mins < 60);
REQUIRE (secs < 60);
REQUIRE (frames < 30);
REQUIRE_IF (secs == 0 && mins % 10, frames >= 2, "non-existent frame in NTSC drop-frame");
int total_mins = 60 * hours + mins;
int total_frames = 108000 * hours
+ 1800 * mins
+ 30 * secs
+ frames
- 2 * (total_mins - total_mins / 10);
return (total_frames / 29.97f) * 1000 * GAVL_TIME_SCALE_MS;
}
int int
lumiera_time_hours (gavl_time_t time) lumiera_time_hours (gavl_time_t time)
{ {
@ -125,3 +143,44 @@ lumiera_time_frame_count (gavl_time_t time, float fps)
return roundf((time / GAVL_TIME_SCALE_MS / 1000.0f) * fps); 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);
int d = frame / 17982; // 17982 = 600 * 29.97
int m = frame % 17982; // 17982 = 600 * 29.97
return frame + 18*d + 2*((m - 2) / 1798); // 1798 = 60 * 29.97
}
int
lumiera_time_ntsc_drop_frames (gavl_time_t time)
{
return ntsc_drop_get_frame_number(time) % 30;
}
int
lumiera_time_ntsc_drop_seconds (gavl_time_t time)
{
return ntsc_drop_get_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;
}
int
lumiera_time_ntsc_drop_hours (gavl_time_t time)
{
return ntsc_drop_get_frame_number(time) / 30 / 60 / 60 % 24;
}

View file

@ -25,6 +25,8 @@
#include <inttypes.h> #include <inttypes.h>
#include <gavl/gavl.h> #include <gavl/gavl.h>
#define NTSC_DROP_FRAME_FPS 29.97
/** /**
* Formats a time in a safeclib tmpbuf in HH:MM:SS:mmm format. * Formats a time in a safeclib tmpbuf in HH:MM:SS:mmm format.
* @param size maximal length for the string * @param size maximal length for the string
@ -55,6 +57,18 @@ lumiera_build_time (long millis, uint secs, uint mins, uint hours);
gavl_time_t gavl_time_t
lumiera_build_time_fps (float fps, uint frames, uint secs, uint mins, uint hours); lumiera_build_time_fps (float fps, uint frames, uint secs, uint mins, uint hours);
/**
* Builds a time value by summing up the given components.
* The components are interpreted as a NTSC drop-frame timecode.
* @param frames number of frames
* @param secs number of seconds
* @param mins number of minutes
* @param hours number of hours
* You must take care not to specify time codes that are illegal NTSC drop-frame times.
*/
gavl_time_t
lumiera_build_time_ntsc_drop (uint frames, uint secs, uint mins, uint hours);
/** /**
* Get the hour part of given time. * Get the hour part of given time.
* @param time the time value to use * @param time the time value to use
@ -99,4 +113,32 @@ lumiera_time_frames (gavl_time_t time, float fps);
int int
lumiera_time_frame_count (gavl_time_t time, float fps); lumiera_time_frame_count (gavl_time_t time, float fps);
/**
* Get the frame part of given time, using NTSC drop-frame timecode.
* @param time the time value to use
*/
int
lumiera_time_ntsc_drop_frames (gavl_time_t time);
/**
* Get the second part of given time, using NTSC drop-frame timecode.
* @param time the time value to use
*/
int
lumiera_time_ntsc_drop_seconds (gavl_time_t time);
/**
* Get the minute part of given time, using NTSC drop-frame timecode.
* @param time the time value to use
*/
int
lumiera_time_ntsc_drop_minutes (gavl_time_t time);
/**
* Get the hour part of given time, using NTSC drop-frame timecode.
* @param time the time value to use
*/
int
lumiera_time_ntsc_drop_hours (gavl_time_t time);
#endif #endif

View file

@ -7,3 +7,6 @@ END
TEST "frame rate dependent calculations" fps <<END TEST "frame rate dependent calculations" fps <<END
END END
TEST "ntsc drop-frame calculations" ntsc_drop_frame << END
END

View file

@ -89,4 +89,85 @@ TEST (fps)
CHECK (lumiera_time_frame_count (t, FPS+5) == 409498); CHECK (lumiera_time_frame_count (t, FPS+5) == 409498);
} }
/*
* 3. NTSC drop-frame calculations.
*/
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;
t = lumiera_build_time_ntsc_drop (FRAMES, SECONDS, MINUTES, HOURS);
CHECK (lumiera_time_millis (t) == 487);
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);
// Make sure we get back the same times. These tests are perhaps overly exhaustive.
int min;
int sec;
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 (frame = 0; frame <= 29; frame++)
{
// Skip dropped frames
if (min % 10 && sec == 0 && frame < 2)
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);
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);
}
// Make sure we do not get non-existent frames
int i;
for (i = 0; i < 59; i++)
{
int frame = (i % 10 == 0) ? 0 : 2;
t = lumiera_build_time_ntsc_drop (frame, 0, i, 0);
CHECK (lumiera_time_ntsc_drop_frames (t) == frame);
}
}
TESTS_END TESTS_END