summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian Dröge <sebastian@centricular.com>2023-04-10 12:54:51 +0300
committerTim-Philipp Müller <tim@centricular.com>2023-07-19 15:59:19 +0100
commit6910a24c1437a0e49c21a51d147d66688fa5336c (patch)
tree994fcc04111030e3020ab4bfd5ef1159cc4fdefb
parenteb89e0a13eeb59fc5bab787ded50faf6a50087e3 (diff)
video: timecode: Add support for framerates lower than 1fps
These are not explicitly defined but the existing calculations can be extended to also cover that case by inverting them to avoid floating point calculations. Fixes https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/2465 Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/5074>
-rw-r--r--subprojects/gst-plugins-base/gst-libs/gst/video/gstvideotimecode.c102
-rw-r--r--subprojects/gst-plugins-base/tests/check/libs/videotimecode.c52
2 files changed, 125 insertions, 29 deletions
diff --git a/subprojects/gst-plugins-base/gst-libs/gst/video/gstvideotimecode.c b/subprojects/gst-plugins-base/gst-libs/gst/video/gstvideotimecode.c
index 15f06c6e05..e92aeb4779 100644
--- a/subprojects/gst-plugins-base/gst-libs/gst/video/gstvideotimecode.c
+++ b/subprojects/gst-plugins-base/gst-libs/gst/video/gstvideotimecode.c
@@ -81,16 +81,28 @@ gst_video_time_code_is_valid (const GstVideoTimeCode * tc)
/* We can't have more frames than rounded up frames per second */
fr = (tc->config.fps_n + (tc->config.fps_d >> 1)) / tc->config.fps_d;
- if (tc->frames >= fr && (tc->config.fps_n != 0 || tc->config.fps_d != 1))
- return FALSE;
+ if (tc->config.fps_d > tc->config.fps_n) {
+ guint64 s;
+
+ if (tc->frames > 0)
+ return FALSE;
+ /* For less than 1 fps only certain second values are allowed */
+ s = tc->seconds + (60 * (tc->minutes + (60 * tc->hours)));
+ if ((s * tc->config.fps_n) % tc->config.fps_d != 0)
+ return FALSE;
+ } else {
+ if (tc->frames >= fr && (tc->config.fps_n != 0 || tc->config.fps_d != 1))
+ return FALSE;
+ }
- /* We either need a specific X/1001 framerate or otherwise an integer
- * framerate */
+ /* We either need a specific X/1001 framerate, otherwise an integer
+ * framerate or less than 1 frame per second */
if (tc->config.fps_d == 1001) {
if (tc->config.fps_n != 30000 && tc->config.fps_n != 60000 &&
tc->config.fps_n != 24000)
return FALSE;
- } else if (tc->config.fps_n % tc->config.fps_d != 0) {
+ } else if (tc->config.fps_n >= tc->config.fps_d
+ && tc->config.fps_n % tc->config.fps_d != 0) {
return FALSE;
}
@@ -256,8 +268,6 @@ gst_video_time_code_init_from_date_time_full (GstVideoTimeCode * tc,
GDateTime * dt, GstVideoTimeCodeFlags flags, guint field_count)
{
GDateTime *jam;
- guint64 frames;
- gboolean add_a_frame = FALSE;
g_return_val_if_fail (tc != NULL, FALSE);
g_return_val_if_fail (dt != NULL, FALSE);
@@ -268,31 +278,51 @@ gst_video_time_code_init_from_date_time_full (GstVideoTimeCode * tc,
jam = g_date_time_new_local (g_date_time_get_year (dt),
g_date_time_get_month (dt), g_date_time_get_day_of_month (dt), 0, 0, 0.0);
- /* Note: This might be inaccurate for 1 frame
- * in case we have a drop frame timecode */
- frames =
- gst_util_uint64_scale_round (g_date_time_get_microsecond (dt) *
- G_GINT64_CONSTANT (1000), fps_n, fps_d * GST_SECOND);
- if (G_UNLIKELY (((frames == fps_n) && (fps_d == 1)) ||
- ((frames == fps_n / 1000) && (fps_d == 1001)))) {
- /* Avoid invalid timecodes */
- frames--;
- add_a_frame = TRUE;
- }
+ if (fps_d > fps_n) {
+ guint64 hour, min, sec;
- gst_video_time_code_init (tc, fps_n, fps_d, jam, flags,
- g_date_time_get_hour (dt), g_date_time_get_minute (dt),
- g_date_time_get_second (dt), frames, field_count);
+ sec =
+ g_date_time_get_second (dt) + (60 * (g_date_time_get_minute (dt) +
+ (60 * g_date_time_get_hour (dt))));
+ sec -= (sec * fps_n) % fps_d;
- if (tc->config.flags & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME) {
- guint df = (tc->config.fps_n + (tc->config.fps_d >> 1)) /
- (15 * tc->config.fps_d);
- if (tc->minutes % 10 && tc->seconds == 0 && tc->frames < df) {
- tc->frames = df;
+ min = sec / 60;
+ sec = sec % 60;
+ hour = min / 60;
+ min = min % 60;
+
+ gst_video_time_code_init (tc, fps_n, fps_d, jam, flags,
+ hour, min, sec, 0, field_count);
+ } else {
+ guint64 frames;
+ gboolean add_a_frame = FALSE;
+
+ /* Note: This might be inaccurate for 1 frame
+ * in case we have a drop frame timecode */
+ frames =
+ gst_util_uint64_scale_round (g_date_time_get_microsecond (dt) *
+ G_GINT64_CONSTANT (1000), fps_n, fps_d * GST_SECOND);
+ if (G_UNLIKELY (((frames == fps_n) && (fps_d == 1)) ||
+ ((frames == fps_n / 1000) && (fps_d == 1001)))) {
+ /* Avoid invalid timecodes */
+ frames--;
+ add_a_frame = TRUE;
+ }
+
+ gst_video_time_code_init (tc, fps_n, fps_d, jam, flags,
+ g_date_time_get_hour (dt), g_date_time_get_minute (dt),
+ g_date_time_get_second (dt), frames, field_count);
+
+ if (tc->config.flags & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME) {
+ guint df = (tc->config.fps_n + (tc->config.fps_d >> 1)) /
+ (15 * tc->config.fps_d);
+ if (tc->minutes % 10 && tc->seconds == 0 && tc->frames < df) {
+ tc->frames = df;
+ }
}
+ if (add_a_frame)
+ gst_video_time_code_increment_frame (tc);
}
- if (add_a_frame)
- gst_video_time_code_increment_frame (tc);
g_date_time_unref (jam);
@@ -366,6 +396,9 @@ gst_video_time_code_frames_since_daily_jam (const GstVideoTimeCode * tc)
(ff_minutes * tc->minutes) +
dropframe_multiplier * ((gint) (tc->minutes / 10)) +
(ff_hours * tc->hours);
+ } else if (tc->config.fps_d > tc->config.fps_n) {
+ return gst_util_uint64_scale (tc->seconds + (60 * (tc->minutes +
+ (60 * tc->hours))), tc->config.fps_n, tc->config.fps_d);
} else {
return tc->frames + (ff_nom * (tc->seconds + (60 * (tc->minutes +
(60 * tc->hours)))));
@@ -468,6 +501,17 @@ gst_video_time_code_add_frames (GstVideoTimeCode * tc, gint64 frames)
framecount - (ff_nom * sec_new) - (ff_minutes * min_new) -
(dropframe_multiplier * ((gint) (min_new / 10))) -
(ff_hours * h_notmod24);
+ } else if (tc->config.fps_d > tc->config.fps_n) {
+ frames_new =
+ frames + gst_util_uint64_scale (tc->seconds + (60 * (tc->minutes +
+ (60 * tc->hours))), tc->config.fps_n, tc->config.fps_d);
+ sec_new =
+ gst_util_uint64_scale (frames_new, tc->config.fps_d, tc->config.fps_n);
+ frames_new = 0;
+ min_new = sec_new / 60;
+ sec_new = sec_new % 60;
+ h_notmod24 = min_new / 60;
+ min_new = min_new % 60;
} else {
framecount =
frames + tc->frames + (ff_nom * (tc->seconds + (sixty * (tc->minutes +
@@ -492,7 +536,7 @@ gst_video_time_code_add_frames (GstVideoTimeCode * tc, gint64 frames)
/* The calculations above should always give correct results */
g_assert (min_new < 60);
g_assert (sec_new < 60);
- g_assert (frames_new < ff_nom);
+ g_assert (frames_new < ff_nom || (ff_nom == 0 && frames_new == 0));
tc->hours = h_new;
tc->minutes = min_new;
diff --git a/subprojects/gst-plugins-base/tests/check/libs/videotimecode.c b/subprojects/gst-plugins-base/tests/check/libs/videotimecode.c
index 6e01548dd6..b14664957b 100644
--- a/subprojects/gst-plugins-base/tests/check/libs/videotimecode.c
+++ b/subprojects/gst-plugins-base/tests/check/libs/videotimecode.c
@@ -714,6 +714,56 @@ GST_START_TEST (videotimecode_from_to_string)
GST_END_TEST;
+GST_START_TEST (videotimecode_half_fps)
+{
+ GstVideoTimeCode *tc;
+ GDateTime *dt;
+
+ dt = g_date_time_new_utc (2016, 7, 29, 10, 32, 50);
+
+ tc = gst_video_time_code_new (1, 2, dt,
+ GST_VIDEO_TIME_CODE_FLAGS_NONE, 0, 0, 0, 0, 0);
+
+ fail_unless (gst_video_time_code_is_valid (tc));
+ fail_unless_equals_uint64 (gst_video_time_code_nsec_since_daily_jam (tc), 0);
+ fail_unless_equals_uint64 (gst_video_time_code_frames_since_daily_jam (tc),
+ 0);
+ fail_unless_equals_int (tc->frames, 0);
+ fail_unless_equals_int (tc->seconds, 0);
+ fail_unless_equals_int (tc->minutes, 0);
+ fail_unless_equals_int (tc->hours, 0);
+
+ gst_video_time_code_add_frames (tc, 10);
+ fail_unless (gst_video_time_code_is_valid (tc));
+ fail_unless_equals_uint64 (gst_video_time_code_nsec_since_daily_jam (tc),
+ 20 * GST_SECOND);
+ fail_unless_equals_uint64 (gst_video_time_code_frames_since_daily_jam (tc),
+ 10);
+ fail_unless_equals_int (tc->frames, 0);
+ fail_unless_equals_int (tc->seconds, 20);
+ fail_unless_equals_int (tc->minutes, 0);
+ fail_unless_equals_int (tc->hours, 0);
+
+ gst_video_time_code_add_frames (tc, 40);
+ fail_unless (gst_video_time_code_is_valid (tc));
+ fail_unless_equals_uint64 (gst_video_time_code_nsec_since_daily_jam (tc),
+ 100 * GST_SECOND);
+ fail_unless_equals_uint64 (gst_video_time_code_frames_since_daily_jam (tc),
+ 50);
+ fail_unless_equals_int (tc->frames, 0);
+ fail_unless_equals_int (tc->seconds, 40);
+ fail_unless_equals_int (tc->minutes, 1);
+ fail_unless_equals_int (tc->hours, 0);
+
+ tc->seconds += 1;
+ fail_if (gst_video_time_code_is_valid (tc));
+
+ gst_video_time_code_free (tc);
+ g_date_time_unref (dt);
+}
+
+GST_END_TEST;
+
static Suite *
gst_videotimecode_suite (void)
{
@@ -755,6 +805,8 @@ gst_videotimecode_suite (void)
tcase_add_test (tc, videotimecode_from_to_string);
+ tcase_add_test (tc, videotimecode_half_fps);
+
return s;
}