diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 7419bedf9..91327cdf5 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -19,6 +19,17 @@ libcin3_a_srcdir = $(top_srcdir)/src/lib noinst_LIBRARIES += libcin3.a libcin3_a_CFLAGS = $(CFLAGS) -std=gnu99 -Wall -Werror +libcin3_a_CPPFLAGS = -I$(top_srcdir)/src/ + +libcin3_a_SOURCES = \ + $(libcin3_a_srcdir)/plugin.c \ + $(libcin3_a_srcdir)/error.c \ + $(libcin3_a_srcdir)/time.c \ + $(libcin3_a_srcdir)/framerate.c + +noinst_HEADERS += \ + $(libcin3_a_srcdir)/plugin.h \ + $(libcin3_a_srcdir)/error.h \ + $(libcin3_a_srcdir)/time.h \ + $(libcin3_a_srcdir)/framerate.h -libcin3_a_SOURCES = $(libcin3_a_srcdir)/plugin.c $(libcin3_a_srcdir)/error.c -noinst_HEADERS += $(libcin3_a_srcdir)/plugin.h $(libcin3_a_srcdir)/error.h diff --git a/src/lib/framerate.c b/src/lib/framerate.c new file mode 100644 index 000000000..138a338a3 --- /dev/null +++ b/src/lib/framerate.c @@ -0,0 +1,25 @@ +/* + framerate.c - framerate calculations + + Copyright (C) CinelerraCV + 2007, Christian Thaeter + + 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 "lib/error.h" + +CINELERRA_ERROR_DEFINE(FRAMERATE_ILLEGAL_TIME, "invalid time given"); +CINELERRA_ERROR_DEFINE(FRAMERATE_ILLEGAL_FRAME, "invalid frame given"); diff --git a/src/lib/framerate.h b/src/lib/framerate.h new file mode 100644 index 000000000..2df5971fb --- /dev/null +++ b/src/lib/framerate.h @@ -0,0 +1,103 @@ +/* + framerate.h - framerate calculations + + Copyright (C) CinelerraCV + 2007, Christian Thaeter + + 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. +*/ + +#ifndef CINELERRA_FRAMERATE_H +#define CINELERRA_FRAMERATE_H + +#include + +#include "lib/error.h" +#include "lib/time.h" + +/** + * framerates are defined as a rational number + * for example NTSC with 30000/1001fps + */ +struct cinelerra_framerate_struct +{ + unsigned n; //numerator + unsigned d; //denominator +}; + +typedef struct cinelerra_framerate_struct cinelerra_framerate; +typedef cinelerra_framerate* CinelerraFramerate; + +typedef signed long cinelerra_framepos; + +CINELERRA_ERROR_DECLARE(FRAMERATE_ILLEGAL_TIME); +CINELERRA_ERROR_DECLARE(FRAMERATE_ILLEGAL_FRAME); + +#define CINELERRA_FRAMEPOS_ERROR LONG_MIN + +/** + * Get the frame number of a given time at a given frame rate. + * frame indexing starts with 1 + * @param framerate is a pointer to the framerate used, defined as rational number. Must be given. + * @param time is a pointer to a cinelerra_time which shall be converted. + * @return frame at the given time or CINELERRA_FRAMEPOS_ERROR on error. + */ +static inline cinelerra_framepos +cinelerra_framerate_frame_get_time (const CinelerraFramerate framerate, CinelerraTime time) +{ + REQUIRE (framerate); + if (!time || time->tv_sec == (time_t)-1) + { + cinelerra_error_set(CINELERRA_ERROR_FRAMERATE_ILLEGAL_TIME); + return CINELERRA_FRAMEPOS_ERROR; + } + + /* we add a magic microsecond for rounding, because of integer truncation frames may be calculated at most 1us earlier, + the idea is to compensate odd framerates which fall out off microsecond precision. + */ + return ((time->tv_sec * 1000000ULL + time->tv_usec + /*magic*/1) * framerate->n)/(framerate->d * 1000000ULL) + 1; +} + +/** + * Get the start time for a frame. + * frame indexing starts with 1 + * @param framerate is a pointer to the framerate used, defined as rational number. Must be given. + * @param time is a pointer to a cinelerra_time which shall take the result. + * @param frame frame number to be converted to time. This frame number must be greater than 0. + * @return the pointer given in time or NULL on error (or when it was given as time). + */ +static inline CinelerraTime +cinelerra_framerate_time_get_time_frame (const CinelerraFramerate framerate, + CinelerraTime time, + cinelerra_framepos frame) +{ + REQUIRE (framerate); + if (time) + { + if (frame < 1) + { + cinelerra_error_set(CINELERRA_ERROR_FRAMERATE_ILLEGAL_FRAME); + return NULL; + } + + unsigned long long usec = ((frame-1) * framerate->d * 1000000ULL - (/*magic*/frame>1?1:0)) / framerate->n; + + time->tv_sec = usec / 1000000; + time->tv_usec = usec % 1000000; + } + return time; +} + +#endif diff --git a/src/lib/time.c b/src/lib/time.c new file mode 100644 index 000000000..6cbdde6ee --- /dev/null +++ b/src/lib/time.c @@ -0,0 +1,26 @@ +/* + time.h - Time and frame calculations + + Copyright (C) CinelerraCV + 2007, Christian Thaeter + + 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 "lib/error.h" + +CINELERRA_ERROR_DEFINE(TIME_OVERFLOW, "Time overflow"); +CINELERRA_ERROR_DEFINE(TIME_UNDERFLOW, "Time underflow"); +CINELERRA_ERROR_DEFINE(TIME_NEGATIVE, "Time negative"); diff --git a/src/lib/time.h b/src/lib/time.h new file mode 100644 index 000000000..a2dea483b --- /dev/null +++ b/src/lib/time.h @@ -0,0 +1,247 @@ +/* + time.h - Time calculations + + Copyright (C) CinelerraCV + 2007, Christian Thaeter + + 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. +*/ + +#ifndef CINELERRA_TIME_H +#define CINELERRA_TIME_H + +#include +#include +#include + +#include "lib/error.h" + +/* + this time functions are small macro like wrapers, they are all inlined for performance reasons + time is passed around as pointers, this pointer must never be NULL + + timehandling is a delicate business, be careful of precision errors accumulating, TODO explain how to use time + +*/ + +/* over or underflow (tried to make a movie which has negative length? or more than some hundreds days?) */ +CINELERRA_ERROR_DECLARE(TIME_OVERFLOW); +CINELERRA_ERROR_DECLARE(TIME_UNDERFLOW); +CINELERRA_ERROR_DECLARE(TIME_NEGATIVE); + +/* + Note: we measure time starting from zero, + time never becomes negative + (I didnt checked if the time types are signed) +*/ +typedef struct timeval cinelerra_time; +typedef cinelerra_time* CinelerraTime; + +/** + * normalize time after operations. + * used internally + */ +static inline void +cinelerra_time_normalize (CinelerraTime time) +{ + REQUIRE (time); + if (time->tv_usec >= 1000000) + { + time->tv_sec += (time->tv_usec / 1000000); + time->tv_usec = (time->tv_usec % 1000000); + } +} + + +/** + * set a time value to zero. + */ +static inline CinelerraTime +cinelerra_time_clear (CinelerraTime time) +{ + if(time) + { + time->tv_sec = 0; + time->tv_usec = 0; + } + return time; +} + +/** + * get current time. + */ +static inline CinelerraTime +cinelerra_time_current (CinelerraTime time) +{ + if (time) + { + /* gettime should never ever fail in a correct program */ + if (gettimeofday (time, NULL)) + CINELERRA_DIE; + } + return time; +} + +/** + * init from floating point representation. + */ +static inline CinelerraTime +cinelerra_time_set_double (CinelerraTime time, double fp) +{ + if (time) + { + if (fp >= 0.0) + { + time->tv_sec = fp; + time->tv_usec = round((fp - time->tv_sec) * 1000000.0); + return time; + } + else + { + time->tv_sec = (time_t)-1; + time->tv_usec = (suseconds_t)-1; + cinelerra_error_set(CINELERRA_ERROR_TIME_NEGATIVE); + } + } + return NULL; +} + +/** + * initialize with seconds and microseconds. + */ +static inline CinelerraTime +cinelerra_time_init (CinelerraTime time, time_t sec, suseconds_t usec) +{ + if (time) + { + time->tv_sec = sec; + time->tv_usec = usec; + cinelerra_time_normalize (time); + } + return time; +} + +/** + * get the seconds part from a time. + */ +static inline time_t +cinelerra_time_sec (CinelerraTime time) +{ + if (time) + return time->tv_sec; + else + return (time_t)-1; +} + +/** + * get the microseconds part of a time. + */ +static inline suseconds_t +cinelerra_time_usec (CinelerraTime time) +{ + if (time) + return time->tv_usec; + else + return (suseconds_t)-1; +} + +/** + * convert to floating point repesentation. + */ +static inline double +cinelerra_time_double_get (CinelerraTime time) +{ + if (time) + { + double fp; + + fp = time->tv_sec; + fp += time->tv_usec / 1000000.0; + return fp; + } + return NAN; +} + + +/** + * copy time + */ +static inline CinelerraTime +cinelerra_time_copy (CinelerraTime dest, const CinelerraTime src) +{ + if (dest && src) + { + dest->tv_sec = src->tv_sec; + dest->tv_usec = src->tv_usec; + } + return dest; +} + +/** + * add time. + */ +static inline CinelerraTime +cinelerra_time_add (CinelerraTime dest, const CinelerraTime src) +{ + if (dest && src) + { + time_t t = dest->tv_sec; + + dest->tv_sec += src->tv_sec; + dest->tv_usec += src->tv_usec; + + cinelerra_time_normalize (dest); + + if (dest->tv_sec < t) + { + cinelerra_error_set (CINELERRA_ERROR_TIME_OVERFLOW); + return NULL; + } + } + return dest; +} + +/** + * substact time. + */ +static inline CinelerraTime +cinelerra_time_sub (CinelerraTime dest, const CinelerraTime src) +{ + if (dest && src) + { + time_t t = dest->tv_sec; + + dest->tv_sec -= src->tv_sec; + if (dest->tv_usec >= src->tv_usec) + dest->tv_usec -= src->tv_usec; + else + { + --dest->tv_sec; + dest->tv_usec += 1000000 - src->tv_usec; + } + + cinelerra_time_normalize (dest); + + if (dest->tv_sec > t) + { + cinelerra_error_set (CINELERRA_ERROR_TIME_UNDERFLOW); + return NULL; + } + } + return dest; +} + + +#endif diff --git a/tests/10timefunctions.tests b/tests/10timefunctions.tests index 764d91fd8..835e1a918 100644 --- a/tests/10timefunctions.tests +++ b/tests/10timefunctions.tests @@ -1,15 +1,126 @@ -TESTING "Time functions" +TESTING "Time functions" ./test-time -PLANNED "get time" -PLANNED "add time, normalized" -PLANNED "substract time, normalized" +TEST "time init" init 15 500000 <time->frame conversion 2" ntscframecheck 2 <time->frame conversion 3" ntscframecheck 3 <time->frame conversion 4" ntscframecheck 4 <time->frame conversion 5" ntscframecheck 5 <time->frame conversion 6" ntscframecheck 6 <time->frame conversion 7" ntscframecheck 7 <time->frame conversion 8" ntscframecheck 8 <time->frame conversion 9" ntscframecheck 9 <time->frame conversion 100000" ntscframecheck 100000 <time->frame conversion 999999" ntscframecheck 999999 < + + 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 + +#include "lib/time.h" +#include "lib/framerate.h" + + +CINELERRA_ERROR_DEFINE(TEST, "test error"); + +int +main (int argc, char** argv) +{ + NOBUG_INIT; + + if (argc == 1) + return 0; + + if (!strcmp(argv[1], "init")) + { + cinelerra_time time; + + cinelerra_time_init (&time, atol (argv[2]), atol(argv[3])); + + printf ("%lu %lu\n", (long)cinelerra_time_sec (&time), (long)cinelerra_time_usec (&time)); + } + + if (!strcmp(argv[1], "todouble")) + { + cinelerra_time time; + + cinelerra_time_init (&time, atol (argv[2]), atol(argv[3])); + + printf ("%g\n", cinelerra_time_double_get (&time)); + } + + if (!strcmp(argv[1], "todoublenull")) + { + printf ("%g\n", cinelerra_time_double_get (NULL)); + } + + if (!strcmp(argv[1], "fromdouble")) + { + cinelerra_time time; + + cinelerra_time_set_double (&time, atof (argv[2])); + + printf ("%lu %lu\n", (long)cinelerra_time_sec (&time), (long)cinelerra_time_usec (&time)); + } + + if (!strcmp(argv[1], "currenttime")) + { + cinelerra_time time; + + cinelerra_time_current (&time); + + printf ("%lu %lu\n", (long)cinelerra_time_sec (&time), (long)cinelerra_time_usec (&time)); + } + + if (!strcmp(argv[1], "add")) + { + cinelerra_time time1, time2; + + cinelerra_time_init (&time1, 0, atol (argv[2])); + cinelerra_time_init (&time2, 0, atol (argv[3])); + cinelerra_time_add (&time1, &time2); + + printf ("%lu %lu\n", (long)cinelerra_time_sec (&time1), (long)cinelerra_time_usec (&time1)); + } + + if (!strcmp(argv[1], "sub")) + { + cinelerra_time time1, time2; + + cinelerra_time_init (&time1, 0, atol (argv[2])); + cinelerra_time_init (&time2, 0, atol (argv[3])); + cinelerra_time_sub (&time1, &time2); + + printf ("%lu %lu\n", (long)cinelerra_time_sec (&time1), (long)cinelerra_time_usec (&time1)); + } + + if (!strcmp(argv[1], "ntscframefromtime")) + { + cinelerra_framerate ntsc = {30000, 1001}; + cinelerra_time time; + + cinelerra_time_init (&time, atol (argv[2]), atol (argv[3])); + + printf ("%lu\n", (long)cinelerra_framerate_frame_get_time (&ntsc, &time)); + } + + if (!strcmp(argv[1], "ntscframestart")) + { + cinelerra_framerate ntsc = {30000, 1001}; + cinelerra_time time; + + if(cinelerra_framerate_time_get_time_frame (&ntsc, &time, atol (argv[2]))) + printf ("%lu %lu\n", (long)cinelerra_time_sec(&time), (long)cinelerra_time_usec(&time)); + } + + if (!strcmp(argv[1], "ntscframecheck")) + { + cinelerra_framerate ntsc = {30000, 1001}; + cinelerra_time time1; + cinelerra_time time2; + cinelerra_framepos frame; + + cinelerra_framepos frame1; + cinelerra_framepos frame2; + + frame = atol (argv[2]); + + + if (cinelerra_framerate_time_get_time_frame (&ntsc, &time1, frame)) + { + printf("frame %lu ", frame1 = cinelerra_framerate_frame_get_time (&ntsc, &time1)); + + cinelerra_time_init (&time2, 0, 1); + cinelerra_time_sub (&time1, &time2); + printf("%lu\n", frame2 = cinelerra_framerate_frame_get_time (&ntsc, &time1)); + ENSURE (frame1 == frame2+1); + } + + } + + return 0; +} diff --git a/wiki/support_library.html b/wiki/support_library.html index 6e8b5a717..144de95db 100644 --- a/wiki/support_library.html +++ b/wiki/support_library.html @@ -720,6 +720,19 @@ We use a common base class for all our application specific exceptions. These ex * getting standardized error messages automatically +
+
! Framerates
+Framerates are stored as rational numbers eg. 30000/1001 for NTSC, this representation allows highly precise integer calculations for frames and times.
+
+! Frames
+All calculations in cinelerra are based on frames which is a signed integer type. Together with a Framerate and a starting point, every frame can be exactly located.
+
+! Time
+Time is used only internally in cinelerra, every external representation of time will be converted to frames. Time has a precision of 1 microsecond and is stored in POSIX struct timeval. Time is always handled as absolute time, thus frame addresses map to absolute times, there is at worst a 1us precision jitter but no drift.
+
+!! SMPTE and other Timecodes
+will be added on demand, but be frame based, not time based
+
/***
 |Name|FullScreenPlugin|
@@ -2360,7 +2373,7 @@ h1,h2,h3,h4,h5,h6 {
 /*}}}*/
 
-
+
The Support Library contains all tools we need at various places, but by themselves don't defines a subsystem on their own.
 
 These things are:
@@ -2369,7 +2382,7 @@ These things are:
 * a wrapper for POSIX Threads
 ** Thread creation joining and canceling
 ** Locking primitives like Condition variables and Mutexes
-
+* [[Frame and Time handling and calculations|FrameAndTime]]
  
 (... to be continued)