blob: 818c8afc6c7f210562125ca1f83102aee15cba92 [file] [log] [blame]
Sebastian Dröge4139fce2015-03-17 09:38:41 +01001/*
2 * gstrtponviftimestamp.h
3 *
4 * Copyright (C) 2014 Axis Communications AB
5 * Author: Guillaume Desmottes <guillaume.desmottes@collabora.com>
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 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 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
19 */
20
21#ifdef HAVE_CONFIG_H
22#include "config.h"
23#endif
24
25#include <stdio.h>
26#include <stdlib.h>
27#include <string.h>
28
29#include <gst/rtp/gstrtpbuffer.h>
30
31#include "gstrtponviftimestamp.h"
32
33#define DEFAULT_NTP_OFFSET GST_CLOCK_TIME_NONE
34#define DEFAULT_CSEQ 0
35#define DEFAULT_SET_E_BIT FALSE
36
37GST_DEBUG_CATEGORY_STATIC (rtponviftimestamp_debug);
38#define GST_CAT_DEFAULT (rtponviftimestamp_debug)
39
40static GstFlowReturn gst_rtp_onvif_timestamp_chain (GstPad * pad,
41 GstObject * parent, GstBuffer * buf);
42static GstFlowReturn gst_rtp_onvif_timestamp_chain_list (GstPad * pad,
43 GstObject * parent, GstBufferList * list);
44
45static GstStaticPadTemplate sink_template_factory =
46GST_STATIC_PAD_TEMPLATE ("sink",
47 GST_PAD_SINK,
48 GST_PAD_ALWAYS,
49 GST_STATIC_CAPS ("application/x-rtp")
50 );
51
52static GstStaticPadTemplate src_template_factory =
53GST_STATIC_PAD_TEMPLATE ("src",
54 GST_PAD_SRC,
55 GST_PAD_ALWAYS,
56 GST_STATIC_CAPS ("application/x-rtp")
57 );
58
59enum
60{
Sebastian Dröge441328f2015-05-13 15:47:04 +030061 PROP_0,
Sebastian Dröge4139fce2015-03-17 09:38:41 +010062 PROP_NTP_OFFSET,
63 PROP_CSEQ,
64 PROP_SET_E_BIT,
65};
66
67/*static guint gst_rtp_onvif_timestamp_signals[LAST_SIGNAL] = { 0 }; */
68
69G_DEFINE_TYPE (GstRtpOnvifTimestamp, gst_rtp_onvif_timestamp, GST_TYPE_ELEMENT);
70
71static void
72gst_rtp_onvif_timestamp_get_property (GObject * object,
73 guint prop_id, GValue * value, GParamSpec * pspec)
74{
75 GstRtpOnvifTimestamp *self = GST_RTP_ONVIF_TIMESTAMP (object);
76
77 switch (prop_id) {
78 case PROP_NTP_OFFSET:
79 g_value_set_uint64 (value, self->prop_ntp_offset);
80 break;
81 case PROP_CSEQ:
82 g_value_set_uint (value, self->prop_cseq);
83 break;
84 case PROP_SET_E_BIT:
85 g_value_set_boolean (value, self->prop_set_e_bit);
86 break;
87 default:
88 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
89 break;
90 }
91}
92
93static void
94gst_rtp_onvif_timestamp_set_property (GObject * object,
95 guint prop_id, const GValue * value, GParamSpec * pspec)
96{
97 GstRtpOnvifTimestamp *self = GST_RTP_ONVIF_TIMESTAMP (object);
98
99 switch (prop_id) {
100 case PROP_NTP_OFFSET:
101 self->prop_ntp_offset = g_value_get_uint64 (value);
102 break;
103 case PROP_CSEQ:
104 self->prop_cseq = g_value_get_uint (value);
105 break;
106 case PROP_SET_E_BIT:
107 self->prop_set_e_bit = g_value_get_boolean (value);
108 break;
109 default:
110 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
111 break;
112 }
113}
114
115static GstStateChangeReturn
116gst_rtp_onvif_timestamp_change_state (GstElement * element,
117 GstStateChange transition)
118{
119 GstRtpOnvifTimestamp *self = GST_RTP_ONVIF_TIMESTAMP (element);
120 GstStateChangeReturn ret;
121
122 switch (transition) {
123 case GST_STATE_CHANGE_PAUSED_TO_READY:
124 gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED);
125 break;
126 default:
127 break;
128 }
129
130 ret = GST_ELEMENT_CLASS (gst_rtp_onvif_timestamp_parent_class)->change_state
131 (element, transition);
132
133 if (ret == GST_STATE_CHANGE_FAILURE)
134 return ret;
135
136 switch (transition) {
137 case GST_STATE_CHANGE_READY_TO_PAUSED:
138 if (GST_CLOCK_TIME_IS_VALID (self->prop_ntp_offset))
139 self->ntp_offset = self->prop_ntp_offset;
140 else
141 self->ntp_offset = GST_CLOCK_TIME_NONE;
142 break;
143 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
144 if (!GST_CLOCK_TIME_IS_VALID (self->prop_ntp_offset) &&
145 GST_ELEMENT_CLOCK (element) == NULL) {
146 GST_ELEMENT_ERROR (element, CORE, CLOCK, ("Missing NTP offset"),
147 ("Set the \"ntp-offset\" property to,"
148 " can't guess it without a clock on the pipeline."));
149 return GST_STATE_CHANGE_FAILURE;
150 }
151 break;
152 default:
153 break;
154 }
155
156 return ret;
157}
158
159static void
160gst_rtp_onvif_timestamp_finalize (GObject * object)
161{
162 GstRtpOnvifTimestamp *self = GST_RTP_ONVIF_TIMESTAMP (object);
163
164 if (self->buffer)
165 gst_buffer_unref (self->buffer);
166 if (self->list)
167 gst_buffer_list_unref (self->list);
168
169 G_OBJECT_CLASS (gst_rtp_onvif_timestamp_parent_class)->finalize (object);
170}
171
172static void
173gst_rtp_onvif_timestamp_class_init (GstRtpOnvifTimestampClass * klass)
174{
175 GObjectClass *gobject_class;
176 GstElementClass *gstelement_class;
177
178 gobject_class = G_OBJECT_CLASS (klass);
179 gstelement_class = GST_ELEMENT_CLASS (klass);
180
181 gobject_class->get_property = gst_rtp_onvif_timestamp_get_property;
182 gobject_class->set_property = gst_rtp_onvif_timestamp_set_property;
183 gobject_class->finalize = gst_rtp_onvif_timestamp_finalize;
184
185 g_object_class_install_property (gobject_class, PROP_NTP_OFFSET,
186 g_param_spec_uint64 ("ntp-offset", "NTP offset",
187 "Offset between the pipeline running time and the absolute UTC time, "
188 "in nano-seconds since 1900 (-1 for automatic computation)",
189 0, G_MAXUINT64,
190 DEFAULT_NTP_OFFSET, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
191
192 g_object_class_install_property (gobject_class, PROP_CSEQ,
193 g_param_spec_uint ("cseq", "CSeq",
194 "The RTSP CSeq which initiated the playback",
195 0, G_MAXUINT32,
196 DEFAULT_CSEQ, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
197
198 g_object_class_install_property (gobject_class, PROP_SET_E_BIT,
199 g_param_spec_boolean ("set-e-bit", "Set 'E' bit",
200 "If the element should set the 'E' bit as defined in the ONVIF RTP "
201 "extension. This increases latency by one packet",
202 DEFAULT_SET_E_BIT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
203
204 /* register pads */
205 gst_element_class_add_pad_template (gstelement_class,
206 gst_static_pad_template_get (&sink_template_factory));
207 gst_element_class_add_pad_template (gstelement_class,
208 gst_static_pad_template_get (&src_template_factory));
209
210 gst_element_class_set_static_metadata (gstelement_class,
211 "ONVIF NTP timestamps RTP extension", "Effect/RTP",
212 "Add absolute timestamps and flags of recorded data in a playback "
213 "session", "Guillaume Desmottes <guillaume.desmottes@collabora.com>");
214
215 gstelement_class->change_state =
216 GST_DEBUG_FUNCPTR (gst_rtp_onvif_timestamp_change_state);
217
218 GST_DEBUG_CATEGORY_INIT (rtponviftimestamp_debug, "rtponviftimestamp",
219 0, "ONVIF NTP timestamps RTP extension");
220}
221
222static GstFlowReturn handle_and_push_buffer (GstRtpOnvifTimestamp * self,
223 GstBuffer * buf, gboolean end_contiguous);
224static GstFlowReturn handle_and_push_buffer_list (GstRtpOnvifTimestamp * self,
225 GstBufferList * list, gboolean end_contiguous);
226
227static gboolean
228gst_rtp_onvif_timestamp_sink_event (GstPad * pad, GstObject * parent,
229 GstEvent * event)
230{
231 GstRtpOnvifTimestamp *self = GST_RTP_ONVIF_TIMESTAMP (parent);
232
233 GST_DEBUG_OBJECT (pad, "handling event %s", GST_EVENT_TYPE_NAME (event));
234
235 switch (GST_EVENT_TYPE (event)) {
236 case GST_EVENT_SEGMENT:
237 gst_event_copy_segment (event, &self->segment);
238 break;
239 case GST_EVENT_FLUSH_STOP:
240 gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED);
241 break;
242 case GST_EVENT_EOS:
243 /* Push pending buffers, if any */
244 if (self->buffer) {
245 handle_and_push_buffer (self, self->buffer, TRUE);
246 self->buffer = NULL;
247 }
248 if (self->list) {
249 handle_and_push_buffer_list (self, self->list, TRUE);
250 self->list = NULL;
251 }
252 break;
253 default:
254 break;
255 }
256
257 return gst_pad_event_default (pad, parent, event);
258}
259
260static void
261gst_rtp_onvif_timestamp_init (GstRtpOnvifTimestamp * self)
262{
263 self->sinkpad =
264 gst_pad_new_from_static_template (&sink_template_factory, "sink");
265 gst_pad_set_chain_function (self->sinkpad, gst_rtp_onvif_timestamp_chain);
266 gst_pad_set_chain_list_function (self->sinkpad,
267 gst_rtp_onvif_timestamp_chain_list);
268 gst_pad_set_event_function (self->sinkpad,
269 gst_rtp_onvif_timestamp_sink_event);
270 gst_element_add_pad (GST_ELEMENT (self), self->sinkpad);
271 GST_PAD_SET_PROXY_CAPS (self->sinkpad);
272 GST_PAD_SET_PROXY_ALLOCATION (self->sinkpad);
273
274 self->srcpad =
275 gst_pad_new_from_static_template (&src_template_factory, "src");
276 gst_element_add_pad (GST_ELEMENT (self), self->srcpad);
277
278 self->prop_ntp_offset = DEFAULT_NTP_OFFSET;
279 self->prop_set_e_bit = DEFAULT_SET_E_BIT;
280
281 self->buffer = NULL;
282 self->list = NULL;
283
284 gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED);
285}
286
287#define EXTENSION_ID 0xABAC
288#define EXTENSION_SIZE 3
289
290static gboolean
291handle_buffer (GstRtpOnvifTimestamp * self, GstBuffer * buf,
292 gboolean end_contiguous)
293{
294 GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
295 guint8 *data;
296 guint16 bits;
297 guint wordlen;
298 guint64 time;
299 guint8 field = 0;
300
301 if (!GST_CLOCK_TIME_IS_VALID (self->ntp_offset)) {
302 GstClock *clock = gst_element_get_clock (GST_ELEMENT (self));
303
304 if (clock) {
305 GstClockTime clock_time = gst_clock_get_time (clock);
306 guint64 real_time = g_get_real_time ();
307 GstClockTime running_time = clock_time -
308 gst_element_get_base_time (GST_ELEMENT (self));
309
310 /* convert microseconds to nanoseconds */
311 real_time *= 1000;
312
313 /* add constant to convert from 1970 based time to 1900 based time */
314 real_time += (G_GUINT64_CONSTANT (2208988800) * GST_SECOND);
315
316 self->ntp_offset = real_time - running_time;
317
318 gst_object_unref (clock);
319 } else {
320 /* Received a buffer in PAUSED, so we can't guess the match
321 * between the running time and the NTP clock yet.
322 */
323 return TRUE;
324 }
325 }
326
327 if (self->segment.format != GST_FORMAT_TIME) {
328 GST_ELEMENT_ERROR (self, STREAM, FAILED,
329 ("did not receive a time segment yet"), (NULL));
330 return FALSE;
331 }
332
333 if (!gst_rtp_buffer_map (buf, GST_MAP_READWRITE, &rtp)) {
334 GST_ELEMENT_ERROR (self, STREAM, FAILED,
335 ("Failed to map RTP buffer"), (NULL));
336 return FALSE;
337 }
338
339 if (!gst_rtp_buffer_set_extension_data (&rtp, EXTENSION_ID, EXTENSION_SIZE)) {
340 GST_ELEMENT_ERROR (self, STREAM, FAILED, ("Failed to set extension data"),
341 (NULL));
342 gst_rtp_buffer_unmap (&rtp);
343 return FALSE;
344 }
345
346 if (!gst_rtp_buffer_get_extension_data (&rtp, &bits, (gpointer) & data,
347 &wordlen)) {
348 GST_ELEMENT_ERROR (self, STREAM, FAILED, ("Failed to get extension data"),
349 (NULL));
350 gst_rtp_buffer_unmap (&rtp);
351 return FALSE;
352 }
353
354 /* NTP timestamp */
355 if (GST_BUFFER_DTS_IS_VALID (buf)) {
356 time = gst_segment_to_running_time (&self->segment, GST_FORMAT_TIME,
357 GST_BUFFER_DTS (buf));
358 } else if (GST_BUFFER_PTS_IS_VALID (buf)) {
359 time = gst_segment_to_running_time (&self->segment, GST_FORMAT_TIME,
360 GST_BUFFER_PTS (buf));
361 } else {
362 GST_ERROR_OBJECT (self,
363 "Buffer doesn't contain any valid DTS or PTS timestamp");
364 goto done;
365 }
366
367 if (time == GST_CLOCK_TIME_NONE) {
368 GST_ERROR_OBJECT (self, "Failed to get running time");
369 goto done;
370 }
371
372 /* add the offset (in seconds) */
373 time += self->ntp_offset;
374
375 /* convert to NTP time. upper 32 bits should contain the seconds
376 * and the lower 32 bits, the fractions of a second. */
377 time = gst_util_uint64_scale (time, (G_GINT64_CONSTANT (1) << 32),
378 GST_SECOND);
379
380 GST_DEBUG_OBJECT (self, "timestamp: %" G_GUINT64_FORMAT, time);
381
382 GST_WRITE_UINT64_BE (data, time);
383
384 /* The next byte is composed of: C E D mbz (5 bits) */
385
386 /* Set C if the buffer does *not* have the DELTA_UNIT flag as it means
387 * that's a key frame (or 'clean point'). */
388 if (!GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT)) {
389 GST_DEBUG_OBJECT (self, "set C flag");
390 field |= (1 << 7);
391 }
392
393 /* Set E if the next buffer has DISCONT */
394 if (end_contiguous) {
395 GST_DEBUG_OBJECT (self, "set E flag");
396 field |= (1 << 6);
397 }
398
399 /* Set D if the buffer has the DISCONT flag */
400 if (GST_BUFFER_IS_DISCONT (buf)) {
401 GST_DEBUG_OBJECT (self, "set D flag");
402 field |= (1 << 5);
403 }
404
405 GST_WRITE_UINT8 (data + 8, field);
406
407 /* CSeq (low-order byte) */
408 GST_WRITE_UINT8 (data + 9, (guchar) self->prop_cseq);
409
410 memset (data + 10, 0, 3);
411
412done:
413 gst_rtp_buffer_unmap (&rtp);
414 return TRUE;
415}
416
417/* @buf: (transfer all) */
418static GstFlowReturn
419handle_and_push_buffer (GstRtpOnvifTimestamp * self, GstBuffer * buf,
420 gboolean end_contiguous)
421{
422 if (!handle_buffer (self, buf, end_contiguous)) {
423 gst_buffer_unref (buf);
424 return GST_FLOW_ERROR;
425 }
426
427 return gst_pad_push (self->srcpad, buf);
428}
429
430static GstFlowReturn
431gst_rtp_onvif_timestamp_chain (GstPad * pad, GstObject * parent,
432 GstBuffer * buf)
433{
434 GstRtpOnvifTimestamp *self = GST_RTP_ONVIF_TIMESTAMP (parent);
435 GstFlowReturn result = GST_FLOW_OK;
436
437 if (!self->prop_set_e_bit) {
438 /* Modify and push this buffer right away */
439 return handle_and_push_buffer (self, buf, FALSE);
440 }
441
442 /* We have to wait for the *next* buffer before pushing this one */
443
444 if (self->buffer) {
445 /* push the *previous* buffer received */
446 result = handle_and_push_buffer (self, self->buffer,
447 GST_BUFFER_IS_DISCONT (buf));
448 }
449
450 /* Transfer ownership */
451 self->buffer = buf;
452 return result;
453}
454
455/* @buf: (transfer all) */
456static GstFlowReturn
457handle_and_push_buffer_list (GstRtpOnvifTimestamp * self,
458 GstBufferList * list, gboolean end_contiguous)
459{
460 GstBuffer *buf;
461
462 /* Set the extension on the *first* buffer */
463 buf = gst_buffer_list_get (list, 0);
464 if (!handle_buffer (self, buf, end_contiguous)) {
465 gst_buffer_list_unref (list);
466 return GST_FLOW_ERROR;
467 }
468
469 return gst_pad_push_list (self->srcpad, list);
470}
471
472/* gst_pad_chain_list_default() refs the buffer when passing it to the chain
473 * function, making it not writable. We implement our own chain_list function
474 * to avoid having to copy each buffer. */
475static GstFlowReturn
476gst_rtp_onvif_timestamp_chain_list (GstPad * pad, GstObject * parent,
477 GstBufferList * list)
478{
479 GstRtpOnvifTimestamp *self = GST_RTP_ONVIF_TIMESTAMP (parent);
480 GstFlowReturn result = GST_FLOW_OK;
481 GstBuffer *buf;
482
483 if (!self->prop_set_e_bit) {
484 return handle_and_push_buffer_list (self, list, FALSE);
485 }
486
487 /* We have to wait for the *next* list before pushing this one */
488
489 if (self->list) {
490 /* push the *previous* list received */
491 buf = gst_buffer_list_get (list, 0);
492
493 result = handle_and_push_buffer_list (self, self->list,
494 GST_BUFFER_IS_DISCONT (buf));
495 }
496
497 /* Transfer ownership */
498 self->list = list;
499 return result;
500}