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/4374>
This commit is contained in:
Sebastian Dröge 2023-04-10 12:54:51 +03:00 committed by GStreamer Marge Bot
parent 4c6f41a00a
commit 83106de7e7
2 changed files with 125 additions and 29 deletions

View file

@ -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 */ /* 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; 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)) if (tc->config.fps_d > tc->config.fps_n) {
return FALSE; guint64 s;
/* We either need a specific X/1001 framerate or otherwise an integer if (tc->frames > 0)
* framerate */ 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, otherwise an integer
* framerate or less than 1 frame per second */
if (tc->config.fps_d == 1001) { if (tc->config.fps_d == 1001) {
if (tc->config.fps_n != 30000 && tc->config.fps_n != 60000 && if (tc->config.fps_n != 30000 && tc->config.fps_n != 60000 &&
tc->config.fps_n != 24000) tc->config.fps_n != 24000)
return FALSE; 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; 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 * dt, GstVideoTimeCodeFlags flags, guint field_count)
{ {
GDateTime *jam; GDateTime *jam;
guint64 frames;
gboolean add_a_frame = FALSE;
g_return_val_if_fail (tc != NULL, FALSE); g_return_val_if_fail (tc != NULL, FALSE);
g_return_val_if_fail (dt != 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), 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); 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 if (fps_d > fps_n) {
* in case we have a drop frame timecode */ guint64 hour, min, sec;
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, sec =
g_date_time_get_hour (dt), g_date_time_get_minute (dt), g_date_time_get_second (dt) + (60 * (g_date_time_get_minute (dt) +
g_date_time_get_second (dt), frames, field_count); (60 * g_date_time_get_hour (dt))));
sec -= (sec * fps_n) % fps_d;
if (tc->config.flags & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME) { min = sec / 60;
guint df = (tc->config.fps_n + (tc->config.fps_d >> 1)) / sec = sec % 60;
(15 * tc->config.fps_d); hour = min / 60;
if (tc->minutes % 10 && tc->seconds == 0 && tc->frames < df) { min = min % 60;
tc->frames = df;
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); g_date_time_unref (jam);
@ -366,6 +396,9 @@ gst_video_time_code_frames_since_daily_jam (const GstVideoTimeCode * tc)
(ff_minutes * tc->minutes) + (ff_minutes * tc->minutes) +
dropframe_multiplier * ((gint) (tc->minutes / 10)) + dropframe_multiplier * ((gint) (tc->minutes / 10)) +
(ff_hours * tc->hours); (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 { } else {
return tc->frames + (ff_nom * (tc->seconds + (60 * (tc->minutes + return tc->frames + (ff_nom * (tc->seconds + (60 * (tc->minutes +
(60 * tc->hours))))); (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) - framecount - (ff_nom * sec_new) - (ff_minutes * min_new) -
(dropframe_multiplier * ((gint) (min_new / 10))) - (dropframe_multiplier * ((gint) (min_new / 10))) -
(ff_hours * h_notmod24); (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 { } else {
framecount = framecount =
frames + tc->frames + (ff_nom * (tc->seconds + (sixty * (tc->minutes + 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 */ /* The calculations above should always give correct results */
g_assert (min_new < 60); g_assert (min_new < 60);
g_assert (sec_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->hours = h_new;
tc->minutes = min_new; tc->minutes = min_new;

View file

@ -714,6 +714,56 @@ GST_START_TEST (videotimecode_from_to_string)
GST_END_TEST; 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 * static Suite *
gst_videotimecode_suite (void) 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_from_to_string);
tcase_add_test (tc, videotimecode_half_fps);
return s; return s;
} }