diff --git a/gst-libs/gst/video/Makefile.am b/gst-libs/gst/video/Makefile.am index 93bcbd4702..979bf17607 100644 --- a/gst-libs/gst/video/Makefile.am +++ b/gst-libs/gst/video/Makefile.am @@ -47,7 +47,8 @@ libgstvideo_@GST_API_VERSION@_la_SOURCES = \ video-resampler.c \ video-blend.c \ video-overlay-composition.c \ - video-multiview.c + video-multiview.c \ + gstvideotimecode.c nodist_libgstvideo_@GST_API_VERSION@_la_SOURCES = $(BUILT_SOURCES) @@ -80,7 +81,8 @@ libgstvideo_@GST_API_VERSION@include_HEADERS = \ video-resampler.h \ video-blend.h \ video-overlay-composition.h \ - video-multiview.h + video-multiview.h \ + gstvideotimecode.h nodist_libgstvideo_@GST_API_VERSION@include_HEADERS = $(built_headers) noinst_HEADERS = gstvideoutilsprivate.h diff --git a/gst-libs/gst/video/gstvideotimecode.c b/gst-libs/gst/video/gstvideotimecode.c new file mode 100644 index 0000000000..a9f62566d1 --- /dev/null +++ b/gst-libs/gst/video/gstvideotimecode.c @@ -0,0 +1,611 @@ +/* GStreamer + * Copyright (C) <2016> Vivia Nikolaidou + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "gstvideotimecode.h" + +G_DEFINE_BOXED_TYPE (GstVideoTimeCode, gst_video_time_code, + (GBoxedCopyFunc) gst_video_time_code_copy, + (GBoxedFreeFunc) gst_video_time_code_free); + +/** + * gst_video_time_code_is_valid: + * @tc: #GstVideoTimeCode to check + * + * Returns: whether @tc is a valid timecode (supported frame rate, + * hours/minutes/seconds/frames not overflowing) + * + * Since: 1.10 + */ +gboolean +gst_video_time_code_is_valid (const GstVideoTimeCode * tc) +{ + g_return_val_if_fail (tc != NULL, FALSE); + + if (tc->hours > 24) + return FALSE; + if (tc->minutes >= 60) + return FALSE; + if (tc->seconds >= 60) + return FALSE; + if (tc->config.fps_d == 0) + return FALSE; + if ((tc->frames > tc->config.fps_n / tc->config.fps_d) + && (tc->config.fps_n != 0 || tc->config.fps_d != 1)) + return FALSE; + if (tc->config.fps_d == 1001) { + if (tc->config.fps_n != 30000 && tc->config.fps_n != 60000) + return FALSE; + } else if (tc->config.fps_n % tc->config.fps_d != 0) { + return FALSE; + } + + return TRUE; +} + +/** + * gst_video_time_code_to_string: + * @tc: #GstVideoTimeCode to convert + * + * Returns: the SMPTE ST 2059-1:2015 string representation of @tc. That will + * take the form hh:mm:ss:ff . The last separator (between seconds and frames) + * may vary: + * + * ';' for drop-frame, non-interlaced content and for drop-frame interlaced + * field 2 + * ',' for drop-frame interlaced field 1 + * ':' for non-drop-frame, non-interlaced content and for non-drop-frame + * interlaced field 2 + * '.' for non-drop-frame interlaced field 1 + * + * Since: 1.10 + */ +gchar * +gst_video_time_code_to_string (const GstVideoTimeCode * tc) +{ + gchar *ret; + gboolean top_dot_present; + gchar sep; + + g_return_val_if_fail (gst_video_time_code_is_valid (tc), NULL); + + /* Top dot is present for non-interlaced content, and for field 2 in + * interlaced content */ + top_dot_present = + !((tc->config.flags & GST_VIDEO_TIME_CODE_FLAGS_INTERLACED) == 1 + && tc->field_count == 1); + + if (tc->config.flags & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME) + sep = top_dot_present ? ';' : ','; + else + sep = top_dot_present ? ':' : '.'; + + ret = + g_strdup_printf ("%02d:%02d:%02d%c%02d", tc->hours, tc->minutes, + tc->seconds, sep, tc->frames); + + return ret; +} + +/** + * gst_video_time_code_to_date_time: + * @tc: #GstVideoTimeCode to convert + * + * The @tc.config->latest_daily_jam is required to be non-NULL. + * + * Returns: the #GDateTime representation of @tc. + * + * Since: 1.10 + */ +GDateTime * +gst_video_time_code_to_date_time (const GstVideoTimeCode * tc) +{ + GDateTime *ret; + GDateTime *ret2; + gdouble add_us; + + g_return_val_if_fail (gst_video_time_code_is_valid (tc), NULL); + g_return_val_if_fail (tc->config.latest_daily_jam != NULL, NULL); + + ret = g_date_time_ref (tc->config.latest_daily_jam); + + if (ret == NULL) { + gchar *tc_str = gst_video_time_code_to_string (tc); + GST_WARNING + ("Asked to convert time code %s to GDateTime, but its latest daily jam is NULL", + tc_str); + g_free (tc_str); + return NULL; + } + + if (tc->config.fps_n == 0 && tc->config.fps_d == 1) { + gchar *tc_str = gst_video_time_code_to_string (tc); + GST_WARNING + ("Asked to convert time code %s to GDateTime, but its framerate is unknown", + tc_str); + g_free (tc_str); + return NULL; + } + + gst_util_fraction_to_double (tc->frames * tc->config.fps_d, tc->config.fps_n, + &add_us); + if ((tc->config.flags & GST_VIDEO_TIME_CODE_FLAGS_INTERLACED) + && tc->field_count == 1) { + gdouble sub_us; + + gst_util_fraction_to_double (tc->config.fps_d, 2 * tc->config.fps_n, + &sub_us); + add_us -= sub_us; + } + + ret2 = g_date_time_add_seconds (ret, add_us + tc->seconds); + g_date_time_unref (ret); + ret = g_date_time_add_minutes (ret2, tc->minutes); + g_date_time_unref (ret2); + ret2 = g_date_time_add_hours (ret, tc->hours); + g_date_time_unref (ret); + + return ret2; +} + +/** + * gst_video_time_code_nsec_since_daily_jam: + * @tc: a #GstVideoTimeCode + * + * Returns: how many nsec have passed since the daily jam of @tc . + * + * Since: 1.10 + */ +guint64 +gst_video_time_code_nsec_since_daily_jam (const GstVideoTimeCode * tc) +{ + gdouble nsec; + + g_return_val_if_fail (gst_video_time_code_is_valid (tc), -1); + + if (tc->config.fps_n == 0 && tc->config.fps_d == 1) { + gchar *tc_str = gst_video_time_code_to_string (tc); + GST_WARNING + ("Asked to calculate nsec since daily jam of time code %s, but its framerate is unknown", + tc_str); + g_free (tc_str); + return -1; + } + + if ((tc->config.flags & GST_VIDEO_TIME_CODE_FLAGS_INTERLACED) + && tc->field_count == 1) + nsec = + gst_util_uint64_scale (GST_SECOND * tc->frames - 500 * GST_MSECOND, + tc->config.fps_d, tc->config.fps_n); + else + nsec = + gst_util_uint64_scale (GST_SECOND * tc->frames, tc->config.fps_d, + tc->config.fps_n); + + /* hours <= 24 (daily jam required) + * minutes < 60 + * seconds < 60 + * this can't overflow */ + nsec += GST_SECOND * (tc->seconds + (60 * (tc->minutes + 60 * tc->hours))); + + return nsec; +} + +/** + * gst_video_time_code_frames_since_daily_jam: + * @tc: a #GstVideoTimeCode + * + * Returns: how many frames have passed since the daily jam of @tc . + * + * Since: 1.10 + */ +guint64 +gst_video_time_code_frames_since_daily_jam (const GstVideoTimeCode * tc) +{ + guint ff_nom; + gdouble ff; + + g_return_val_if_fail (gst_video_time_code_is_valid (tc), -1); + + gst_util_fraction_to_double (tc->config.fps_n, tc->config.fps_d, &ff); + if (tc->config.fps_d == 1001) { + ff_nom = tc->config.fps_n / 1000; + } else { + ff_nom = ff; + } + if (tc->config.flags & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME) { + /* these need to be truncated to integer: side effect, code looks cleaner + * */ + guint ff_minutes = 60 * ff; + guint ff_hours = 3600 * ff; + /* for 30000/1001 we drop the first 2 frames per minute, for 60000/1001 we + * drop the first 4 : so we use this number */ + guint dropframe_multiplier; + + if (tc->config.fps_n == 30000) { + dropframe_multiplier = 2; + } else if (tc->config.fps_n == 60000) { + dropframe_multiplier = 4; + } else { + GST_ERROR ("Unsupported drop frame rate %u/%u", tc->config.fps_n, + tc->config.fps_d); + return -1; + } + + return tc->frames + (ff_nom * tc->seconds) + + (ff_minutes * tc->minutes) + + dropframe_multiplier * ((gint) (tc->minutes / 10)) + + (ff_hours * tc->hours); + } else { + return tc->frames + (ff_nom * (tc->seconds + (60 * (tc->minutes + + (60 * tc->hours))))); + } + +} + +/** + * gst_video_time_code_increment_frame: + * @tc: a #GstVideoTimeCode + * + * Adds one frame to @tc . + * + * Since: 1.10 + */ +void +gst_video_time_code_increment_frame (GstVideoTimeCode * tc) +{ + return gst_video_time_code_add_frames (tc, 1); +} + +/** + * gst_video_time_code_add_frames: + * @tc: a #GstVideoTimeCode + * @frames: How many frames to add or subtract + * + * Adds or subtracts @frames amount of frames to @tc . + * + * Since: 1.10 + */ +void +gst_video_time_code_add_frames (GstVideoTimeCode * tc, gint64 frames) +{ + guint64 framecount; + guint64 h_notmod24; + guint64 h_new, min_new, sec_new, frames_new; + gdouble ff; + guint ff_nom; + /* formulas found in SMPTE ST 2059-1:2015 section 9.4.3 + * and adapted for 60/1.001 as well as 30/1.001 */ + + g_return_if_fail (gst_video_time_code_is_valid (tc)); + + gst_util_fraction_to_double (tc->config.fps_n, tc->config.fps_d, &ff); + if (tc->config.fps_d == 1001) { + ff_nom = tc->config.fps_n / 1000; + } else { + ff_nom = ff; + if (tc->config.fps_d != 1) + GST_WARNING ("Unsupported frame rate %u/%u, results may be wrong", + tc->config.fps_n, tc->config.fps_d); + } + if (tc->config.flags & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME) { + /* these need to be truncated to integer: side effect, code looks cleaner + * */ + guint ff_minutes = 60 * ff; + guint ff_hours = 3600 * ff; + /* a bunch of intermediate variables, to avoid monster code with possible + * integer overflows */ + guint64 min_new_tmp1, min_new_tmp2, min_new_tmp3, min_new_denom; + /* for 30000/1001 we drop the first 2 frames per minute, for 60000/1001 we + * drop the first 4 : so we use this number */ + guint dropframe_multiplier; + + if (tc->config.fps_n == 30000) + dropframe_multiplier = 2; + else if (tc->config.fps_n == 60000) + dropframe_multiplier = 4; + else { + GST_ERROR ("Unsupported drop frame rate %u/%u", tc->config.fps_n, + tc->config.fps_d); + return; + } + + framecount = + frames + tc->frames + (ff_nom * tc->seconds) + + (ff_minutes * tc->minutes) + + dropframe_multiplier * ((gint) (tc->minutes / 10)) + + (ff_hours * tc->hours); + h_notmod24 = gst_util_uint64_scale_int (framecount, 1, ff_hours); + + min_new_denom = 60 * ff_nom; + min_new_tmp1 = (framecount - (h_notmod24 * ff_hours)) / min_new_denom; + min_new_tmp2 = framecount + dropframe_multiplier * min_new_tmp1; + min_new_tmp1 = (framecount - (h_notmod24 * ff_hours)) / (60 * 10 * ff_nom); + min_new_tmp3 = + dropframe_multiplier * min_new_tmp1 + (h_notmod24 * ff_hours); + min_new = + gst_util_uint64_scale_int (min_new_tmp2 - min_new_tmp3, 1, + min_new_denom); + + sec_new = + (guint64) ((framecount - (ff_minutes * min_new) - + dropframe_multiplier * ((gint) (min_new / 10)) - + (ff_hours * h_notmod24)) / ff_nom); + + frames_new = + framecount - (ff_nom * sec_new) - (ff_minutes * min_new) - + (dropframe_multiplier * ((gint) (min_new / 10))) - + (ff_hours * h_notmod24); + } else { + framecount = + frames + tc->frames + (ff_nom * (tc->seconds + (60 * (tc->minutes + + (60 * tc->hours))))); + h_notmod24 = gst_util_uint64_scale_int (framecount, 1, ff_nom * 3600); + min_new = + gst_util_uint64_scale_int ((framecount - (ff_nom * 3600 * h_notmod24)), + 1, (ff_nom * 60)); + sec_new = + gst_util_uint64_scale_int ((framecount - (ff_nom * 60 * (min_new + + (60 * h_notmod24)))), 1, ff_nom); + frames_new = + framecount - (ff_nom * (sec_new + 60 * (min_new + (60 * h_notmod24)))); + if (frames_new > ff_nom) + frames_new = 0; + } + h_new = h_notmod24 % 24; + + g_assert (min_new < 60); + g_assert (sec_new < 60); + g_assert (frames_new < ff_nom); + tc->hours = h_new; + tc->minutes = min_new; + tc->seconds = sec_new; + tc->frames = frames_new; +} + +/** + * gst_video_time_code_compare: + * @tc1: a #GstVideoTimeCode + * @tc2: another #GstVideoTimeCode + * + * Compares @tc1 and @tc2 . If both have latest daily jam information, it is + * taken into account. Otherwise, it is assumed that the daily jam of both + * @tc1 and @tc2 was at the same time. + * + * Returns: 1 if @tc1 is after @tc2, -1 if @tc1 is before @tc2, 0 otherwise. + * + * Since: 1.10 + */ +gint +gst_video_time_code_compare (const GstVideoTimeCode * tc1, + const GstVideoTimeCode * tc2) +{ + g_return_val_if_fail (gst_video_time_code_is_valid (tc1), -1); + g_return_val_if_fail (gst_video_time_code_is_valid (tc2), -1); + + if (tc1->config.latest_daily_jam == NULL + || tc2->config.latest_daily_jam == NULL) { + guint64 nsec1, nsec2; +#ifndef GST_DISABLE_GST_DEBUG + gchar *str1, *str2; + + str1 = gst_video_time_code_to_string (tc1); + str2 = gst_video_time_code_to_string (tc2); + GST_INFO + ("Comparing time codes %s and %s, but at least one of them has no " + "latest daily jam information. Assuming they started together", + str1, str2); + g_free (str1); + g_free (str2); +#endif + if (tc1->hours > tc2->hours) { + return 1; + } else if (tc1->hours < tc2->hours) { + return -1; + } + if (tc1->minutes > tc2->minutes) { + return 1; + } else if (tc1->minutes < tc2->minutes) { + return -1; + } + if (tc1->seconds > tc2->seconds) { + return 1; + } else if (tc1->seconds < tc2->seconds) { + return -1; + } + + nsec1 = + gst_util_uint64_scale (GST_SECOND, + tc1->frames * tc1->config.fps_n, tc1->config.fps_d); + nsec2 = + gst_util_uint64_scale (GST_SECOND, + tc2->frames * tc2->config.fps_n, tc2->config.fps_d); + if (nsec1 > nsec2) { + return 1; + } else if (nsec1 < nsec2) { + return -1; + } + if (tc1->config.flags & GST_VIDEO_TIME_CODE_FLAGS_INTERLACED) { + if (tc1->field_count > tc2->field_count) + return 1; + else if (tc1->field_count < tc2->field_count) + return -1; + } + return 0; + } else { + GDateTime *dt1, *dt2; + gint ret; + + dt1 = gst_video_time_code_to_date_time (tc1); + dt2 = gst_video_time_code_to_date_time (tc2); + + ret = g_date_time_compare (dt1, dt2); + + g_date_time_unref (dt1); + g_date_time_unref (dt2); + + return ret; + } +} + +/** + * gst_video_time_code_new: + * @fps_n: Numerator of the frame rate + * @fps_d: Denominator of the frame rate + * @latest_daily_jam: The latest daily jam of the #GstVideoTimeCode + * @flags: #GstVideoTimeCodeFlags + * @hours: the hours field of #GstVideoTimeCode + * @minutes: the minutes field of #GstVideoTimeCode + * @seconds: the seconds field of #GstVideoTimeCode + * @frames: the frames field of #GstVideoTimeCode + * @field_count: Interlaced video field count + * + * @field_count is 0 for progressive, 1 or 2 for interlaced. + * @latest_daiy_jam reference is stolen from caller. + * + * Returns: a new #GstVideoTimeCode with the given values. + * + * Since: 1.10 + */ +GstVideoTimeCode * +gst_video_time_code_new (guint fps_n, guint fps_d, GDateTime * latest_daily_jam, + GstVideoTimeCodeFlags flags, guint hours, guint minutes, guint seconds, + guint frames, guint field_count) +{ + GstVideoTimeCode *tc; + + tc = g_new0 (GstVideoTimeCode, 1); + gst_video_time_code_init (tc, fps_n, fps_d, latest_daily_jam, flags, hours, + minutes, seconds, frames, field_count); + return tc; +} + +/** + * gst_video_time_code_new_empty: + * + * Returns: a new empty #GstVideoTimeCode + * + * Since: 1.10 + */ +GstVideoTimeCode * +gst_video_time_code_new_empty (void) +{ + GstVideoTimeCode *tc; + + tc = g_new0 (GstVideoTimeCode, 1); + gst_video_time_code_clear (tc); + return tc; +} + +/** + * gst_video_time_code_init: + * @tc: a #GstVideoTimeCode + * @fps_n: Numerator of the frame rate + * @fps_d: Denominator of the frame rate + * @latest_daily_jam: The latest daily jam of the #GstVideoTimeCode + * @flags: #GstVideoTimeCodeFlags + * @hours: the hours field of #GstVideoTimeCode + * @minutes: the minutes field of #GstVideoTimeCode + * @seconds: the seconds field of #GstVideoTimeCode + * @frames: the frames field of #GstVideoTimeCode + * @field_count: Interlaced video field count + * + * @field_count is 0 for progressive, 1 or 2 for interlaced. + * @latest_daiy_jam reference is stolen from caller. + * + * Initializes @tc with the given values. + * + * Since: 1.10 + */ +void +gst_video_time_code_init (GstVideoTimeCode * tc, guint fps_n, guint fps_d, + GDateTime * latest_daily_jam, GstVideoTimeCodeFlags flags, guint hours, + guint minutes, guint seconds, guint frames, guint field_count) +{ + tc->hours = hours; + tc->minutes = minutes; + tc->seconds = seconds; + tc->frames = frames; + tc->field_count = field_count; + tc->config.fps_n = fps_n; + tc->config.fps_d = fps_d; + if (latest_daily_jam != NULL) + tc->config.latest_daily_jam = g_date_time_ref (latest_daily_jam); + else + tc->config.latest_daily_jam = NULL; + tc->config.flags = flags; + + g_return_if_fail (gst_video_time_code_is_valid (tc)); +} + +/** + * gst_video_time_code_clear: + * @tc: a #GstVideoTimeCode + * + * Initializes @tc with empty/zero/NULL values. + * + * Since: 1.10 + */ +void +gst_video_time_code_clear (GstVideoTimeCode * tc) +{ + tc->hours = 0; + tc->minutes = 0; + tc->seconds = 0; + tc->frames = 0; + tc->field_count = 0; + tc->config.fps_n = 0; + tc->config.fps_d = 1; + if (tc->config.latest_daily_jam != NULL) + g_date_time_unref (tc->config.latest_daily_jam); + tc->config.latest_daily_jam = NULL; + tc->config.flags = 0; +} + +/** + * gst_video_time_code_copy: + * @tc: a #GstVideoTimeCode + * + * Returns: a new #GstVideoTimeCode with the same values as @tc . + * + * Since: 1.10 + */ +GstVideoTimeCode * +gst_video_time_code_copy (const GstVideoTimeCode * tc) +{ + return gst_video_time_code_new (tc->config.fps_n, tc->config.fps_d, + tc->config.latest_daily_jam, tc->config.flags, tc->hours, tc->minutes, + tc->seconds, tc->frames, tc->field_count); +} + +/** + * gst_video_time_code_free: + * @tc: a #GstVideoTimeCode + * + * Frees @tc . + * + * Since: 1.10 + */ +void +gst_video_time_code_free (GstVideoTimeCode * tc) +{ + if (tc->config.latest_daily_jam != NULL) + g_date_time_unref (tc->config.latest_daily_jam); + + g_free (tc); +} diff --git a/gst-libs/gst/video/gstvideotimecode.h b/gst-libs/gst/video/gstvideotimecode.h new file mode 100644 index 0000000000..89c28c0199 --- /dev/null +++ b/gst-libs/gst/video/gstvideotimecode.h @@ -0,0 +1,151 @@ +/* GStreamer + * Copyright (C) <2016> Vivia Nikolaidou + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_VIDEO_TIME_CODE_H__ +#define __GST_VIDEO_TIME_CODE_H__ + +#include + +G_BEGIN_DECLS + +typedef struct _GstVideoTimeCodeConfig GstVideoTimeCodeConfig; +typedef struct _GstVideoTimeCode GstVideoTimeCode; + +/** + * GstVideoTimeCodeFlags: + * @GST_VIDEO_TIME_CODE_FLAGS_NONE: No flags + * @GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME: Whether we have drop frame rate + * @GST_VIDEO_TIME_CODE_FLAGS_INTERLACED: Whether we have interlaced video + * + * Flags related to the time code information. + * For drop frame, only 30000/1001 and 60000/1001 frame rates are supported. + * + * Since: 1.10 + */ +typedef enum +{ + GST_VIDEO_TIME_CODE_FLAGS_NONE = 0, + GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME = (1<<0), + GST_VIDEO_TIME_CODE_FLAGS_INTERLACED = (1<<1) + /* Not supported yet: + * GST_VIDEO_TIME_CODE_ALLOW_MORE_THAN_24H = (1<<2) + * GST_VIDEO_TIME_CODE_ALLOW_NEGATIVE = (1<<3) + */ +} GstVideoTimeCodeFlags; + +/** + * GstVideoTimeCodeConfig: + * @fps_n: Numerator of the frame rate + * @fps_d: Denominator of the frame rate + * @flags: the corresponding #GstVideoTimeCodeFlags + * @latest_daily_jam: The latest daily jam information, if present, or NULL + * + * Supported frame rates: 30000/1001, 60000/1001 (both with and without drop + * frame), and integer frame rates e.g. 25/1, 30/1, 50/1, 60/1. + * + * The configuration of the time code. + * + * Since: 1.10 + */ +struct _GstVideoTimeCodeConfig { + guint fps_n; + guint fps_d; + GstVideoTimeCodeFlags flags; + GDateTime *latest_daily_jam; +}; + +/** + * GstVideoTimeCode: + * @hours: the hours field of #GstVideoTimeCode + * @minutes: the minutes field of #GstVideoTimeCode + * @seconds: the seconds field of #GstVideoTimeCode + * @frames: the frames field of #GstVideoTimeCode + * @field_count: Interlaced video field count + * @config: the corresponding #GstVideoTimeCodeConfig + * + * @field_count must be 0 for progressive video and 1 or 2 for interlaced. + * + * A representation of a SMPTE time code. + * + * @hours must be positive and less than 24. Will wrap around otherwise. + * @minutes and @seconds must be positive and less than 60. + * @frames must be less than or equal to @config.fps_n / @config.fps_d + * These values are *NOT* automatically normalized. + * + * Since: 1.10 + */ +struct _GstVideoTimeCode { + GstVideoTimeCodeConfig config; + + guint hours; + guint minutes; + guint seconds; + guint frames; + guint field_count; +}; + +#define GST_TYPE_VIDEO_TIME_CODE (gst_video_time_code_get_type()) +GType gst_video_time_code_get_type (void); + +GstVideoTimeCode * gst_video_time_code_new (guint fps_n, + guint fps_d, + GDateTime * latest_daily_jam, + GstVideoTimeCodeFlags flags, + guint hours, + guint minutes, + guint seconds, + guint frames, + guint field_count); +GstVideoTimeCode * gst_video_time_code_new_empty (void); +void gst_video_time_code_free (GstVideoTimeCode * tc); + +GstVideoTimeCode * gst_video_time_code_copy (const GstVideoTimeCode * tc); + +void gst_video_time_code_init (GstVideoTimeCode * tc, + guint fps_n, + guint fps_d, + GDateTime * latest_daily_jam, + GstVideoTimeCodeFlags flags, + guint hours, + guint minutes, + guint seconds, + guint frames, + guint field_count); +void gst_video_time_code_clear (GstVideoTimeCode * tc); + +gboolean gst_video_time_code_is_valid (const GstVideoTimeCode * tc); + +gint gst_video_time_code_compare (const GstVideoTimeCode * tc1, + const GstVideoTimeCode * tc2); + +void gst_video_time_code_increment_frame (GstVideoTimeCode * tc); +void gst_video_time_code_add_frames (GstVideoTimeCode * tc, + gint64 frames); + +gchar *gst_video_time_code_to_string (const GstVideoTimeCode * tc); + +GDateTime *gst_video_time_code_to_date_time (const GstVideoTimeCode * tc); + +guint64 gst_video_time_code_nsec_since_daily_jam (const GstVideoTimeCode * tc); + +guint64 gst_video_time_code_frames_since_daily_jam (const GstVideoTimeCode * tc); + +G_END_DECLS + +#endif /* __GST_VIDEO_TIME_CODE_H__ */ diff --git a/gst-libs/gst/video/video.h b/gst-libs/gst/video/video.h index 5399182be5..3f23502044 100644 --- a/gst-libs/gst/video/video.h +++ b/gst-libs/gst/video/video.h @@ -140,5 +140,6 @@ G_END_DECLS #include #include #include +#include #endif /* __GST_VIDEO_H__ */ diff --git a/tests/check/Makefile.am b/tests/check/Makefile.am index c81c664960..eec5b88ed4 100644 --- a/tests/check/Makefile.am +++ b/tests/check/Makefile.am @@ -217,6 +217,7 @@ check_PROGRAMS = \ libs/video \ libs/videodecoder \ libs/videoencoder \ + libs/videotimecode \ libs/xmpwriter \ pipelines/simple-launch-lines \ pipelines/basetime \ @@ -621,6 +622,16 @@ libs_videoencoder_LDADD = \ $(GST_BASE_LIBS) \ $(LDADD) +libs_videotimecode_CFLAGS = \ + $(GST_PLUGINS_BASE_CFLAGS) \ + $(GST_BASE_CFLAGS) \ + $(AM_CFLAGS) + +libs_videotimecode_LDADD = \ + $(top_builddir)/gst-libs/gst/video/libgstvideo-@GST_API_VERSION@.la \ + $(GST_BASE_LIBS) \ + $(LDADD) + elements_multisocketsink_CFLAGS = $(GIO_CFLAGS) $(AM_CFLAGS) elements_multisocketsink_LDADD = $(GIO_LIBS) $(LDADD) diff --git a/tests/check/libs/videotimecode.c b/tests/check/libs/videotimecode.c new file mode 100644 index 0000000000..c403db8df7 --- /dev/null +++ b/tests/check/libs/videotimecode.c @@ -0,0 +1,401 @@ +/* GStreamer + * + * Copyright (C) 2016 Vivia Nikolaidou + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include +#include +#include + + +GST_START_TEST (videotimecode_compare_equal) +{ + GstVideoTimeCode *tc1, *tc2; + + tc2 = gst_video_time_code_new (50, 1, NULL, 0, 10, 10, 10, 10, 0); + tc1 = gst_video_time_code_new (50, 1, NULL, 0, 10, 10, 10, 10, 0); + fail_unless (gst_video_time_code_compare (tc1, tc2) == 0); + gst_video_time_code_free (tc1); + gst_video_time_code_free (tc2); +} + +GST_END_TEST; + +GST_START_TEST (videotimecode_compare_fps_n) +{ + GstVideoTimeCode *tc1, *tc2; + + tc1 = gst_video_time_code_new (50, 1, NULL, 0, 10, 10, 10, 10, 0); + tc2 = gst_video_time_code_new (25, 1, NULL, 0, 10, 10, 10, 10, 0); + fail_unless (gst_video_time_code_compare (tc1, tc2) == 1); + fail_unless (gst_video_time_code_compare (tc2, tc1) == -1); + gst_video_time_code_free (tc1); + gst_video_time_code_free (tc2); +} + +GST_END_TEST; + +GST_START_TEST (videotimecode_compare_fps_d) +{ + GstVideoTimeCode *tc1, *tc2; + + tc1 = gst_video_time_code_new (50, 1, NULL, 0, 10, 10, 10, 10, 0); + tc2 = gst_video_time_code_new (50, 2, NULL, 0, 10, 10, 10, 10, 0); + fail_unless (gst_video_time_code_compare (tc1, tc2) == 1); + fail_unless (gst_video_time_code_compare (tc2, tc1) == -1); + gst_video_time_code_free (tc1); + gst_video_time_code_free (tc2); +} + +GST_END_TEST; + +GST_START_TEST (videotimecode_compare_frames) +{ + GstVideoTimeCode *tc1, *tc2; + + tc1 = gst_video_time_code_new (50, 1, NULL, 0, 10, 10, 10, 10, 0); + tc2 = gst_video_time_code_new (50, 1, NULL, 0, 10, 10, 10, 9, 0); + fail_unless (gst_video_time_code_compare (tc1, tc2) == 1); + fail_unless (gst_video_time_code_compare (tc2, tc1) == -1); + gst_video_time_code_free (tc1); + gst_video_time_code_free (tc2); +} + +GST_END_TEST; + +GST_START_TEST (videotimecode_compare_seconds) +{ + GstVideoTimeCode *tc1, *tc2; + + tc1 = gst_video_time_code_new (50, 1, NULL, 0, 10, 10, 10, 10, 0); + tc2 = gst_video_time_code_new (50, 1, NULL, 0, 10, 10, 9, 10, 0); + fail_unless (gst_video_time_code_compare (tc1, tc2) == 1); + fail_unless (gst_video_time_code_compare (tc2, tc1) == -1); + gst_video_time_code_free (tc1); + gst_video_time_code_free (tc2); +} + +GST_END_TEST; + +GST_START_TEST (videotimecode_compare_minutes) +{ + GstVideoTimeCode *tc1, *tc2; + + tc1 = gst_video_time_code_new (50, 1, NULL, 0, 10, 10, 10, 10, 0); + tc2 = gst_video_time_code_new (50, 1, NULL, 0, 10, 9, 10, 10, 0); + fail_unless (gst_video_time_code_compare (tc1, tc2) == 1); + fail_unless (gst_video_time_code_compare (tc2, tc1) == -1); + gst_video_time_code_free (tc1); + gst_video_time_code_free (tc2); +} + +GST_END_TEST; + +GST_START_TEST (videotimecode_compare_hours) +{ + GstVideoTimeCode *tc1, *tc2; + + tc1 = gst_video_time_code_new (50, 1, NULL, 0, 10, 10, 10, 10, 0); + tc2 = gst_video_time_code_new (50, 1, NULL, 0, 9, 10, 10, 10, 0); + fail_unless (gst_video_time_code_compare (tc1, tc2) == 1); + fail_unless (gst_video_time_code_compare (tc2, tc1) == -1); + gst_video_time_code_free (tc1); + gst_video_time_code_free (tc2); +} + +GST_END_TEST; + +GST_START_TEST (videotimecode_compare_fieldcounts) +{ + GstVideoTimeCode *tc1, *tc2; + + tc1 = + gst_video_time_code_new (50, 1, NULL, + GST_VIDEO_TIME_CODE_FLAGS_INTERLACED, 10, 10, 10, 10, 2); + tc2 = + gst_video_time_code_new (50, 1, NULL, + GST_VIDEO_TIME_CODE_FLAGS_INTERLACED, 10, 10, 10, 10, 1); + fail_unless (gst_video_time_code_compare (tc1, tc2) == 1); + fail_unless (gst_video_time_code_compare (tc2, tc1) == -1); + gst_video_time_code_free (tc1); + gst_video_time_code_free (tc2); +} + +GST_END_TEST; + +GST_START_TEST (videotimecode_addframe_10) +{ + GstVideoTimeCode *tc1; + + tc1 = gst_video_time_code_new (50, 1, NULL, 0, 10, 10, 10, 10, 0); + gst_video_time_code_increment_frame (tc1); + fail_unless (tc1->hours == 10); + fail_unless (tc1->minutes == 10); + fail_unless (tc1->seconds == 10); + fail_unless (tc1->frames == 11); + gst_video_time_code_free (tc1); +} + +GST_END_TEST; + +GST_START_TEST (videotimecode_addframe_0) +{ + GstVideoTimeCode *tc1; + + tc1 = gst_video_time_code_new (50, 1, NULL, 0, 0, 0, 0, 0, 0); + gst_video_time_code_increment_frame (tc1); + fail_unless (tc1->hours == 0); + fail_unless (tc1->minutes == 0); + fail_unless (tc1->seconds == 0); + fail_unless (tc1->frames == 1); + gst_video_time_code_free (tc1); +} + +GST_END_TEST; + +GST_START_TEST (videotimecode_addframe_high) +{ + GstVideoTimeCode *tc1; + /* Make sure nothing overflows */ + + tc1 = gst_video_time_code_new (60, 1, NULL, 0, 23, 59, 59, 58, 0); + gst_video_time_code_increment_frame (tc1); + fail_unless (tc1->hours == 23); + fail_unless (tc1->minutes == 59); + fail_unless (tc1->seconds == 59); + fail_unless (tc1->frames == 59); + gst_video_time_code_free (tc1); +} + +GST_END_TEST; + +GST_START_TEST (videotimecode_addframe_dropframe) +{ + GstVideoTimeCode *tc1; + + tc1 = + gst_video_time_code_new (30000, 1001, NULL, + GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME, 10, 10, 10, 10, 0); + gst_video_time_code_increment_frame (tc1); + fail_unless (tc1->hours == 10); + fail_unless (tc1->minutes == 10); + fail_unless (tc1->seconds == 10); + fail_unless (tc1->frames == 11); + gst_video_time_code_free (tc1); +} + +GST_END_TEST; + +GST_START_TEST (videotimecode_addframe_framedropped) +{ + GstVideoTimeCode *tc1; + + tc1 = + gst_video_time_code_new (30000, 1001, NULL, + GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME, 10, 10, 59, 29, 0); + gst_video_time_code_increment_frame (tc1); + fail_unless (tc1->hours == 10); + fail_unless (tc1->minutes == 11); + fail_unless (tc1->seconds == 0); + fail_unless (tc1->frames == 2); + gst_video_time_code_free (tc1); +} + +GST_END_TEST; + +GST_START_TEST (videotimecode_addframe_wrapover) +{ + GstVideoTimeCode *tc1; + + tc1 = + gst_video_time_code_new (30000, 1001, NULL, + GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME, 23, 59, 59, 29, 0); + gst_video_time_code_increment_frame (tc1); + fail_unless (tc1->hours == 0); + fail_unless (tc1->minutes == 0); + fail_unless (tc1->seconds == 0); + fail_unless (tc1->frames == 0); + gst_video_time_code_free (tc1); +} + +GST_END_TEST; + +GST_START_TEST (videotimecode_addframe_60drop_dropframe) +{ + GstVideoTimeCode *tc1; + + tc1 = + gst_video_time_code_new (60000, 1001, NULL, + GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME, 10, 10, 10, 10, 0); + gst_video_time_code_increment_frame (tc1); + fail_unless (tc1->hours == 10); + fail_unless (tc1->minutes == 10); + fail_unless (tc1->seconds == 10); + fail_unless (tc1->frames == 11); + gst_video_time_code_free (tc1); +} + +GST_END_TEST; + +GST_START_TEST (videotimecode_addframe_60drop_framedropped) +{ + GstVideoTimeCode *tc1; + + tc1 = + gst_video_time_code_new (60000, 1001, NULL, + GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME, 10, 10, 59, 59, 0); + gst_video_time_code_increment_frame (tc1); + fail_unless (tc1->hours == 10); + fail_unless (tc1->minutes == 11); + fail_unless (tc1->seconds == 0); + fail_unless (tc1->frames == 4); + gst_video_time_code_free (tc1); +} + +GST_END_TEST; + +GST_START_TEST (videotimecode_addframe_60drop_wrapover) +{ + GstVideoTimeCode *tc1; + /* Make sure nothing overflows here either */ + + tc1 = + gst_video_time_code_new (60000, 1001, NULL, + GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME, 23, 59, 59, 59, 0); + gst_video_time_code_increment_frame (tc1); + fail_unless (tc1->hours == 0); + fail_unless (tc1->minutes == 0); + fail_unless (tc1->seconds == 0); + fail_unless (tc1->frames == 0); + gst_video_time_code_free (tc1); +} + +GST_END_TEST; + +GST_START_TEST (videotimecode_addframe_loop) +{ + GstVideoTimeCode *tc1; + guint64 i; + /* Loop for an hour and a bit, make sure no assertions explode */ + + tc1 = + gst_video_time_code_new (60000, 1001, NULL, + GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME, 12, 12, 12, 12, 0); + for (i = 0; i < 220000; i++) + gst_video_time_code_increment_frame (tc1); + gst_video_time_code_init (tc1, 60, 1, NULL, 0, 12, 12, 12, 12, 0); + for (i = 0; i < 220000; i++) + gst_video_time_code_increment_frame (tc1); + gst_video_time_code_free (tc1); +} + +GST_END_TEST; + +GST_START_TEST (videotimecode_dailyjam_todatetime) +{ + GstVideoTimeCode *tc1; + GDateTime *dt1, *dt2; + + dt1 = g_date_time_new_utc (2016, 7, 29, 10, 32, 50); + + tc1 = + gst_video_time_code_new (50, 1, dt1, + GST_VIDEO_TIME_CODE_FLAGS_NONE, 0, 0, 0, 0, 0); + /* 1 hour, 4 minutes, 3 seconds, and 2 frames */ + gst_video_time_code_add_frames (tc1, 192152); + fail_unless (tc1->hours == 1); + fail_unless (tc1->minutes == 4); + fail_unless (tc1->seconds == 3); + fail_unless (tc1->frames == 2); + + dt2 = gst_video_time_code_to_date_time (tc1); + fail_unless (g_date_time_get_year (dt2) == 2016); + fail_unless (g_date_time_get_month (dt2) == 7); + fail_unless (g_date_time_get_day_of_month (dt2) == 29); + fail_unless (g_date_time_get_hour (dt2) == 11); + fail_unless (g_date_time_get_minute (dt2) == 36); + fail_unless (g_date_time_get_seconds (dt2) == 53.04); + + gst_video_time_code_free (tc1); + g_date_time_unref (dt2); + g_date_time_unref (dt1); +} + +GST_END_TEST; + +GST_START_TEST (videotimecode_dailyjam_compare) +{ + GstVideoTimeCode *tc1, *tc2; + GDateTime *dt1; + + dt1 = g_date_time_new_utc (2016, 7, 29, 10, 32, 50); + + tc1 = + gst_video_time_code_new (50, 1, dt1, + GST_VIDEO_TIME_CODE_FLAGS_NONE, 0, 0, 0, 0, 0); + tc2 = gst_video_time_code_copy (tc1); + fail_unless (gst_video_time_code_compare (tc1, tc2) == 0); + gst_video_time_code_increment_frame (tc1); + fail_unless (gst_video_time_code_compare (tc1, tc2) == 1); + gst_video_time_code_add_frames (tc2, 2); + fail_unless (gst_video_time_code_compare (tc1, tc2) == -1); + + gst_video_time_code_free (tc1); + gst_video_time_code_free (tc2); + g_date_time_unref (dt1); +} + +GST_END_TEST; + +static Suite * +gst_videotimecode_suite (void) +{ + Suite *s = suite_create ("GstVideoTimeCode"); + TCase *tc = tcase_create ("general"); + + suite_add_tcase (s, tc); + + tcase_add_test (tc, videotimecode_compare_equal); + tcase_add_test (tc, videotimecode_compare_fps_n); + tcase_add_test (tc, videotimecode_compare_fps_d); + tcase_add_test (tc, videotimecode_compare_frames); + tcase_add_test (tc, videotimecode_compare_seconds); + tcase_add_test (tc, videotimecode_compare_minutes); + tcase_add_test (tc, videotimecode_compare_hours); + tcase_add_test (tc, videotimecode_compare_fieldcounts); + + tcase_add_test (tc, videotimecode_addframe_10); + tcase_add_test (tc, videotimecode_addframe_0); + tcase_add_test (tc, videotimecode_addframe_high); + tcase_add_test (tc, videotimecode_addframe_dropframe); + tcase_add_test (tc, videotimecode_addframe_framedropped); + tcase_add_test (tc, videotimecode_addframe_wrapover); + tcase_add_test (tc, videotimecode_addframe_60drop_dropframe); + tcase_add_test (tc, videotimecode_addframe_60drop_framedropped); + tcase_add_test (tc, videotimecode_addframe_60drop_wrapover); + tcase_add_test (tc, videotimecode_addframe_loop); + + tcase_add_test (tc, videotimecode_dailyjam_todatetime); + tcase_add_test (tc, videotimecode_dailyjam_compare); + return s; +} + +GST_CHECK_MAIN (gst_videotimecode);