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; - /* We either need a specific X/1001 framerate or otherwise an integer - * framerate */ + 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, 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; }