1 /*
2 * GStreamer
3 * Copyright (C) 2016 Vivia Nikolaidou <vivia@toolsonair.com>
4 *
5 * gsttimecodestamper.c
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library; if not, write to the
19 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
21 */
22
23 /**
24 * SECTION:element-timecodestamper
25 * @title: timecodestamper
26 * @short_description: Attach a timecode into incoming video frames
27 *
28 * This element attaches a timecode into every incoming video frame. It starts
29 * counting from the stream time of each segment start, which it converts into
30 * a timecode.
31 *
32 * ## Example launch line
33 * |[
34 * gst-launch-1.0 videotestsrc ! timecodestamper ! autovideosink
35 * ]|
36 *
37 */
38
39 #ifdef HAVE_CONFIG_H
40 #include "config.h"
41 #endif
42
43 #include "gsttimecodestamper.h"
44
45 #include <gst/gst.h>
46 #include <gst/video/video.h>
47 #include <stdlib.h>
48 #include <string.h>
49
50 GST_DEBUG_CATEGORY_STATIC (timecodestamper_debug);
51 #define GST_CAT_DEFAULT timecodestamper_debug
52
53 /* GstTimeCodeStamper properties */
54 enum
55 {
56 PROP_0,
57 PROP_OVERRIDE_EXISTING,
58 PROP_DROP_FRAME,
59 PROP_DAILY_JAM,
60 PROP_POST_MESSAGES,
61 PROP_FIRST_TIMECODE,
62 PROP_FIRST_NOW
63 };
64
65 #define DEFAULT_OVERRIDE_EXISTING FALSE
66 #define DEFAULT_DROP_FRAME FALSE
67 #define DEFAULT_DAILY_JAM NULL
68 #define DEFAULT_POST_MESSAGES FALSE
69 #define DEFAULT_FIRST_NOW FALSE
70
71 static GstStaticPadTemplate gst_timecodestamper_src_template =
72 GST_STATIC_PAD_TEMPLATE ("src",
73 GST_PAD_SRC,
74 GST_PAD_ALWAYS,
75 GST_STATIC_CAPS ("video/x-raw")
76 );
77
78 static GstStaticPadTemplate gst_timecodestamper_sink_template =
79 GST_STATIC_PAD_TEMPLATE ("sink",
80 GST_PAD_SINK,
81 GST_PAD_ALWAYS,
82 GST_STATIC_CAPS ("video/x-raw")
83 );
84
85 static void gst_timecodestamper_set_property (GObject * object, guint prop_id,
86 const GValue * value, GParamSpec * pspec);
87 static void gst_timecodestamper_get_property (GObject * object, guint prop_id,
88 GValue * value, GParamSpec * pspec);
89 static void gst_timecodestamper_dispose (GObject * object);
90 static gboolean gst_timecodestamper_sink_event (GstBaseTransform * trans,
91 GstEvent * event);
92 static GstFlowReturn gst_timecodestamper_transform_ip (GstBaseTransform *
93 vfilter, GstBuffer * buffer);
94 static gboolean gst_timecodestamper_stop (GstBaseTransform * trans);
95
96 G_DEFINE_TYPE (GstTimeCodeStamper, gst_timecodestamper,
97 GST_TYPE_BASE_TRANSFORM);
98
99 static void
gst_timecodestamper_class_init(GstTimeCodeStamperClass * klass)100 gst_timecodestamper_class_init (GstTimeCodeStamperClass * klass)
101 {
102 GObjectClass *gobject_class = (GObjectClass *) klass;
103 GstElementClass *element_class = (GstElementClass *) klass;
104 GstBaseTransformClass *trans_class = (GstBaseTransformClass *) klass;
105
106 GST_DEBUG_CATEGORY_INIT (timecodestamper_debug, "timecodestamper", 0,
107 "timecodestamper");
108 gst_element_class_set_static_metadata (element_class, "Timecode stamper",
109 "Filter/Video", "Attaches a timecode meta into each video frame",
110 "Vivia Nikolaidou <vivia@toolsonair.com");
111
112 gobject_class->set_property = gst_timecodestamper_set_property;
113 gobject_class->get_property = gst_timecodestamper_get_property;
114 gobject_class->dispose = gst_timecodestamper_dispose;
115
116 g_object_class_install_property (gobject_class, PROP_OVERRIDE_EXISTING,
117 g_param_spec_boolean ("override-existing", "Override existing timecode",
118 "If set to true, any existing timecode will be overridden",
119 DEFAULT_OVERRIDE_EXISTING,
120 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
121 g_object_class_install_property (gobject_class, PROP_DROP_FRAME,
122 g_param_spec_boolean ("drop-frame", "Override existing timecode",
123 "Use drop-frame timecodes for 29.97 and 59.94 FPS",
124 DEFAULT_DROP_FRAME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
125 g_object_class_install_property (gobject_class, PROP_DAILY_JAM,
126 g_param_spec_boxed ("daily-jam",
127 "Daily jam",
128 "The daily jam of the timecode",
129 G_TYPE_DATE_TIME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
130 g_object_class_install_property (gobject_class, PROP_POST_MESSAGES,
131 g_param_spec_boolean ("post-messages", "Post element message",
132 "Post element message containing the current timecode",
133 DEFAULT_POST_MESSAGES, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
134 g_object_class_install_property (gobject_class, PROP_FIRST_TIMECODE,
135 g_param_spec_boxed ("first-timecode",
136 "Timecode at the first frame",
137 "If set, take this timecode for the first frame and increment from "
138 "it. Only the values itself are taken, flags and frame rate are "
139 "always determined by timecodestamper itself. "
140 "If unset (and to-now is also not set), the timecode will start at 0",
141 GST_TYPE_VIDEO_TIME_CODE,
142 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
143 g_object_class_install_property (gobject_class, PROP_FIRST_NOW,
144 g_param_spec_boolean ("first-timecode-to-now",
145 "Sets first timecode to system time",
146 "If true and first-timecode is unset, set it to system time "
147 "automatically when the first media segment is received.",
148 DEFAULT_FIRST_NOW, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
149
150 gst_element_class_add_pad_template (element_class,
151 gst_static_pad_template_get (&gst_timecodestamper_sink_template));
152 gst_element_class_add_pad_template (element_class,
153 gst_static_pad_template_get (&gst_timecodestamper_src_template));
154
155 trans_class->sink_event = GST_DEBUG_FUNCPTR (gst_timecodestamper_sink_event);
156 trans_class->stop = GST_DEBUG_FUNCPTR (gst_timecodestamper_stop);
157
158 trans_class->transform_ip =
159 GST_DEBUG_FUNCPTR (gst_timecodestamper_transform_ip);
160 }
161
162 static void
gst_timecodestamper_init(GstTimeCodeStamper * timecodestamper)163 gst_timecodestamper_init (GstTimeCodeStamper * timecodestamper)
164 {
165 timecodestamper->override_existing = DEFAULT_OVERRIDE_EXISTING;
166 timecodestamper->drop_frame = DEFAULT_DROP_FRAME;
167 timecodestamper->current_tc = gst_video_time_code_new_empty ();
168 timecodestamper->first_tc = NULL;
169 timecodestamper->current_tc->config.latest_daily_jam = DEFAULT_DAILY_JAM;
170 timecodestamper->post_messages = DEFAULT_POST_MESSAGES;
171 timecodestamper->first_tc_now = DEFAULT_FIRST_NOW;
172 }
173
174 static void
gst_timecodestamper_dispose(GObject * object)175 gst_timecodestamper_dispose (GObject * object)
176 {
177 GstTimeCodeStamper *timecodestamper = GST_TIME_CODE_STAMPER (object);
178
179 if (timecodestamper->current_tc != NULL) {
180 gst_video_time_code_free (timecodestamper->current_tc);
181 timecodestamper->current_tc = NULL;
182 }
183
184 if (timecodestamper->first_tc != NULL) {
185 gst_video_time_code_free (timecodestamper->first_tc);
186 timecodestamper->first_tc = NULL;
187 }
188
189 G_OBJECT_CLASS (gst_timecodestamper_parent_class)->dispose (object);
190 }
191
192 static void
gst_timecodestamper_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)193 gst_timecodestamper_set_property (GObject * object, guint prop_id,
194 const GValue * value, GParamSpec * pspec)
195 {
196 GstTimeCodeStamper *timecodestamper = GST_TIME_CODE_STAMPER (object);
197
198 switch (prop_id) {
199 case PROP_OVERRIDE_EXISTING:
200 timecodestamper->override_existing = g_value_get_boolean (value);
201 break;
202 case PROP_DROP_FRAME:
203 timecodestamper->drop_frame = g_value_get_boolean (value);
204 break;
205 case PROP_DAILY_JAM:
206 if (timecodestamper->current_tc->config.latest_daily_jam)
207 g_date_time_unref (timecodestamper->current_tc->
208 config.latest_daily_jam);
209 timecodestamper->current_tc->config.latest_daily_jam =
210 g_value_dup_boxed (value);
211 break;
212 case PROP_POST_MESSAGES:
213 timecodestamper->post_messages = g_value_get_boolean (value);
214 break;
215 case PROP_FIRST_TIMECODE:
216 if (timecodestamper->first_tc)
217 gst_video_time_code_free (timecodestamper->first_tc);
218 timecodestamper->first_tc = g_value_dup_boxed (value);
219 break;
220 case PROP_FIRST_NOW:
221 timecodestamper->first_tc_now = g_value_get_boolean (value);
222 break;
223 default:
224 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
225 break;
226 }
227 }
228
229 static void
gst_timecodestamper_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)230 gst_timecodestamper_get_property (GObject * object, guint prop_id,
231 GValue * value, GParamSpec * pspec)
232 {
233 GstTimeCodeStamper *timecodestamper = GST_TIME_CODE_STAMPER (object);
234
235 switch (prop_id) {
236 case PROP_OVERRIDE_EXISTING:
237 g_value_set_boolean (value, timecodestamper->override_existing);
238 break;
239 case PROP_DROP_FRAME:
240 g_value_set_boolean (value, timecodestamper->drop_frame);
241 break;
242 case PROP_DAILY_JAM:
243 g_value_set_boxed (value,
244 timecodestamper->current_tc->config.latest_daily_jam);
245 break;
246 case PROP_POST_MESSAGES:
247 g_value_set_boolean (value, timecodestamper->post_messages);
248 break;
249 case PROP_FIRST_TIMECODE:
250 g_value_set_boxed (value, timecodestamper->first_tc);
251 break;
252 case PROP_FIRST_NOW:
253 g_value_set_boolean (value, timecodestamper->first_tc_now);
254 break;
255 default:
256 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
257 break;
258 }
259 }
260
261 static void
gst_timecodestamper_set_drop_frame(GstTimeCodeStamper * timecodestamper)262 gst_timecodestamper_set_drop_frame (GstTimeCodeStamper * timecodestamper)
263 {
264 if (timecodestamper->drop_frame && timecodestamper->vinfo.fps_d == 1001 &&
265 (timecodestamper->vinfo.fps_n == 30000 ||
266 timecodestamper->vinfo.fps_n == 60000))
267 timecodestamper->current_tc->config.flags |=
268 GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME;
269 else
270 timecodestamper->current_tc->config.flags &=
271 ~GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME;
272 }
273
274 static gboolean
gst_timecodestamper_stop(GstBaseTransform * trans)275 gst_timecodestamper_stop (GstBaseTransform * trans)
276 {
277 GstTimeCodeStamper *timecodestamper = GST_TIME_CODE_STAMPER (trans);
278
279 gst_video_info_init (&timecodestamper->vinfo);
280
281 return TRUE;
282 }
283
284 /* Must be called with object lock */
285 static void
gst_timecodestamper_reset_timecode(GstTimeCodeStamper * timecodestamper)286 gst_timecodestamper_reset_timecode (GstTimeCodeStamper * timecodestamper)
287 {
288 GDateTime *jam = NULL;
289
290 if (timecodestamper->first_tc &&
291 timecodestamper->first_tc->config.latest_daily_jam)
292 jam = g_date_time_ref (timecodestamper->first_tc->config.latest_daily_jam);
293 else if (timecodestamper->current_tc->config.latest_daily_jam)
294 jam =
295 g_date_time_ref (timecodestamper->current_tc->config.latest_daily_jam);
296 gst_video_time_code_clear (timecodestamper->current_tc);
297 /* FIXME: What if the buffer doesn't contain both top and bottom fields? */
298 gst_video_time_code_init (timecodestamper->current_tc,
299 timecodestamper->vinfo.fps_n,
300 timecodestamper->vinfo.fps_d,
301 jam,
302 timecodestamper->vinfo.interlace_mode ==
303 GST_VIDEO_INTERLACE_MODE_PROGRESSIVE ? 0 :
304 GST_VIDEO_TIME_CODE_FLAGS_INTERLACED, 0, 0, 0, 0, 0);
305 if (jam)
306 g_date_time_unref (jam);
307 if (timecodestamper->first_tc) {
308 timecodestamper->current_tc->hours = timecodestamper->first_tc->hours;
309 timecodestamper->current_tc->minutes = timecodestamper->first_tc->minutes;
310 timecodestamper->current_tc->seconds = timecodestamper->first_tc->seconds;
311 timecodestamper->current_tc->frames = timecodestamper->first_tc->frames;
312 timecodestamper->current_tc->field_count =
313 timecodestamper->first_tc->field_count;
314 }
315 gst_timecodestamper_set_drop_frame (timecodestamper);
316 }
317
318 static gboolean
gst_timecodestamper_sink_event(GstBaseTransform * trans,GstEvent * event)319 gst_timecodestamper_sink_event (GstBaseTransform * trans, GstEvent * event)
320 {
321 gboolean ret = FALSE;
322 GstTimeCodeStamper *timecodestamper = GST_TIME_CODE_STAMPER (trans);
323
324 GST_DEBUG_OBJECT (trans, "received event %" GST_PTR_FORMAT, event);
325 switch (GST_EVENT_TYPE (event)) {
326 case GST_EVENT_SEGMENT:
327 {
328 GstSegment segment;
329 guint64 frames;
330 gchar *tc_str;
331 gboolean notify = FALSE;
332
333 GST_OBJECT_LOCK (timecodestamper);
334
335 gst_event_copy_segment (event, &segment);
336 if (segment.format != GST_FORMAT_TIME) {
337 GST_OBJECT_UNLOCK (timecodestamper);
338 GST_ERROR_OBJECT (timecodestamper, "Invalid segment format");
339 return FALSE;
340 }
341 if (GST_VIDEO_INFO_FORMAT (&timecodestamper->vinfo) ==
342 GST_VIDEO_FORMAT_UNKNOWN) {
343 GST_ERROR_OBJECT (timecodestamper,
344 "Received segment event without caps");
345 GST_OBJECT_UNLOCK (timecodestamper);
346 return FALSE;
347 }
348
349 if (timecodestamper->first_tc_now && !timecodestamper->first_tc) {
350 GDateTime *dt = g_date_time_new_now_local ();
351 GstVideoTimeCode *tc;
352
353 gst_timecodestamper_set_drop_frame (timecodestamper);
354
355 tc = gst_video_time_code_new_from_date_time_full
356 (timecodestamper->vinfo.fps_n, timecodestamper->vinfo.fps_d, dt,
357 timecodestamper->current_tc->config.flags, 0);
358
359 g_date_time_unref (dt);
360
361 if (!tc) {
362 GST_ERROR_OBJECT (timecodestamper,
363 "Can't convert current time to a timecode");
364 GST_OBJECT_UNLOCK (timecodestamper);
365 return FALSE;
366 }
367
368 timecodestamper->first_tc = tc;
369 notify = TRUE;
370 }
371
372 frames =
373 gst_util_uint64_scale (segment.time, timecodestamper->vinfo.fps_n,
374 timecodestamper->vinfo.fps_d * GST_SECOND);
375 gst_timecodestamper_reset_timecode (timecodestamper);
376 gst_video_time_code_add_frames (timecodestamper->current_tc, frames);
377 GST_DEBUG_OBJECT (timecodestamper,
378 "Got %" G_GUINT64_FORMAT " frames when segment time is %"
379 GST_TIME_FORMAT, frames, GST_TIME_ARGS (segment.time));
380 tc_str = gst_video_time_code_to_string (timecodestamper->current_tc);
381 GST_DEBUG_OBJECT (timecodestamper, "New timecode is %s", tc_str);
382 g_free (tc_str);
383 GST_OBJECT_UNLOCK (timecodestamper);
384 if (notify)
385 g_object_notify (G_OBJECT (timecodestamper), "first-timecode");
386 break;
387 }
388 case GST_EVENT_CAPS:
389 {
390 GstCaps *caps;
391
392 GST_OBJECT_LOCK (timecodestamper);
393 gst_event_parse_caps (event, &caps);
394 if (!gst_video_info_from_caps (&timecodestamper->vinfo, caps)) {
395 GST_OBJECT_UNLOCK (timecodestamper);
396 return FALSE;
397 }
398 gst_timecodestamper_reset_timecode (timecodestamper);
399 GST_OBJECT_UNLOCK (timecodestamper);
400 break;
401 }
402 default:
403 break;
404 }
405 ret =
406 GST_BASE_TRANSFORM_CLASS (gst_timecodestamper_parent_class)->sink_event
407 (trans, event);
408 return ret;
409 }
410
411 static gboolean
remove_timecode_meta(GstBuffer * buffer,GstMeta ** meta,gpointer user_data)412 remove_timecode_meta (GstBuffer * buffer, GstMeta ** meta, gpointer user_data)
413 {
414 if (meta && *meta && (*meta)->info->api == GST_VIDEO_TIME_CODE_META_API_TYPE) {
415 *meta = NULL;
416 }
417
418 return TRUE;
419 }
420
421 static GstFlowReturn
gst_timecodestamper_transform_ip(GstBaseTransform * vfilter,GstBuffer * buffer)422 gst_timecodestamper_transform_ip (GstBaseTransform * vfilter,
423 GstBuffer * buffer)
424 {
425 GstTimeCodeStamper *timecodestamper = GST_TIME_CODE_STAMPER (vfilter);
426 GstVideoTimeCodeMeta *tc_meta;
427 GstVideoTimeCode *tc;
428
429 GST_OBJECT_LOCK (timecodestamper);
430 tc_meta = gst_buffer_get_video_time_code_meta (buffer);
431 if (tc_meta && !timecodestamper->override_existing) {
432 GST_OBJECT_UNLOCK (timecodestamper);
433 tc = gst_video_time_code_copy (&tc_meta->tc);
434 goto beach;
435 } else if (timecodestamper->override_existing) {
436 gst_buffer_foreach_meta (buffer, remove_timecode_meta, NULL);
437 }
438
439 gst_buffer_add_video_time_code_meta (buffer, timecodestamper->current_tc);
440 tc = gst_video_time_code_copy (timecodestamper->current_tc);
441 gst_video_time_code_increment_frame (timecodestamper->current_tc);
442 GST_OBJECT_UNLOCK (timecodestamper);
443
444 beach:
445 if (timecodestamper->post_messages) {
446 GstClockTime stream_time, running_time, duration;
447 GstStructure *s;
448 GstMessage *msg;
449
450 running_time =
451 gst_segment_to_running_time (&vfilter->segment, GST_FORMAT_TIME,
452 GST_BUFFER_PTS (buffer));
453 stream_time =
454 gst_segment_to_stream_time (&vfilter->segment, GST_FORMAT_TIME,
455 GST_BUFFER_PTS (buffer));
456 duration =
457 gst_util_uint64_scale_int (GST_SECOND, timecodestamper->vinfo.fps_d,
458 timecodestamper->vinfo.fps_n);
459 s = gst_structure_new ("timecodestamper", "timestamp", G_TYPE_UINT64,
460 GST_BUFFER_PTS (buffer), "stream-time", G_TYPE_UINT64, stream_time,
461 "running-time", G_TYPE_UINT64, running_time, "duration", G_TYPE_UINT64,
462 duration, "timecode", GST_TYPE_VIDEO_TIME_CODE, tc, NULL);
463 msg = gst_message_new_element (GST_OBJECT (timecodestamper), s);
464 gst_element_post_message (GST_ELEMENT (timecodestamper), msg);
465 }
466 gst_video_time_code_free (tc);
467 return GST_FLOW_OK;
468 }
469