diff --git a/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json b/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json index ebb42c118f..1e5bcb0ae2 100644 --- a/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json +++ b/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json @@ -222573,6 +222573,18 @@ "readable": true, "type": "gboolean", "writable": true + }, + "use-reference-timestamps": { + "blurb": "Whether the element should use reference UTC timestamps from the buffers instead of using the ntp-offset mechanism.", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "false", + "mutable": "ready", + "readable": true, + "type": "gboolean", + "writable": true } }, "rank": "none" diff --git a/subprojects/gst-plugins-bad/gst/onvif/gstrtponviftimestamp.c b/subprojects/gst-plugins-bad/gst/onvif/gstrtponviftimestamp.c index c5fb1d4510..f0d5533ab6 100644 --- a/subprojects/gst-plugins-bad/gst/onvif/gstrtponviftimestamp.c +++ b/subprojects/gst-plugins-bad/gst/onvif/gstrtponviftimestamp.c @@ -30,13 +30,14 @@ #include "gstrtponviftimestamp.h" -#define GST_NTP_OFFSET_EVENT_NAME "GstNtpOffset" +#define GST_ONVIF_TIMESTAMP_EVENT_NAME "GstOnvifTimestamp" #define DEFAULT_NTP_OFFSET GST_CLOCK_TIME_NONE #define DEFAULT_CSEQ 0 #define DEFAULT_SET_E_BIT FALSE #define DEFAULT_SET_T_BIT FALSE #define DEFAULT_DROP_OUT_OF_SEGMENT TRUE +#define DEFAULT_USE_REFERENCE_TIMESTAMPS FALSE GST_DEBUG_CATEGORY_STATIC (rtponviftimestamp_debug); #define GST_CAT_DEFAULT (rtponviftimestamp_debug) @@ -72,7 +73,8 @@ enum PROP_CSEQ, PROP_SET_E_BIT, PROP_SET_T_BIT, - PROP_DROP_OUT_OF_SEGMENT + PROP_DROP_OUT_OF_SEGMENT, + PROP_USE_REFERENCE_TIMESTAMPS }; /*static guint gst_rtp_onvif_timestamp_signals[LAST_SIGNAL] = { 0 }; */ @@ -103,6 +105,9 @@ gst_rtp_onvif_timestamp_get_property (GObject * object, case PROP_DROP_OUT_OF_SEGMENT: g_value_set_boolean (value, self->prop_drop_out_of_segment); break; + case PROP_USE_REFERENCE_TIMESTAMPS: + g_value_set_boolean (value, self->prop_use_reference_timestamps); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -131,6 +136,9 @@ gst_rtp_onvif_timestamp_set_property (GObject * object, case PROP_DROP_OUT_OF_SEGMENT: self->prop_drop_out_of_segment = g_value_get_boolean (value); break; + case PROP_USE_REFERENCE_TIMESTAMPS: + self->prop_use_reference_timestamps = g_value_get_boolean (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -204,9 +212,18 @@ gst_rtp_onvif_timestamp_change_state (GstElement * element, switch (transition) { case GST_STATE_CHANGE_READY_TO_PAUSED: - self->ntp_offset = self->prop_ntp_offset; - GST_DEBUG_OBJECT (self, "ntp-offset: %" GST_TIME_FORMAT, - GST_TIME_ARGS (self->ntp_offset)); + if (self->prop_use_reference_timestamps && + self->prop_ntp_offset != DEFAULT_NTP_OFFSET) { + GST_WARNING_OBJECT (self, "ntp-offset should not be set if reference " + "timestamps are used"); + self->ntp_offset = DEFAULT_NTP_OFFSET; + } else if (self->prop_use_reference_timestamps) { + GST_DEBUG_OBJECT (self, "using reference timestamp meta"); + } else { + self->ntp_offset = self->prop_ntp_offset; + GST_DEBUG_OBJECT (self, "ntp-offset: %" GST_TIME_FORMAT, + GST_TIME_ARGS (self->ntp_offset)); + } self->set_d_bit = TRUE; self->set_e_bit = FALSE; self->set_t_bit = FALSE; @@ -239,6 +256,7 @@ gst_rtp_onvif_timestamp_finalize (GObject * object) GstRtpOnvifTimestamp *self = GST_RTP_ONVIF_TIMESTAMP (object); g_queue_free (self->event_queue); + gst_caps_replace (&self->reference_timestamp_id, NULL); G_OBJECT_CLASS (gst_rtp_onvif_timestamp_parent_class)->finalize (object); } @@ -288,6 +306,28 @@ gst_rtp_onvif_timestamp_class_init (GstRtpOnvifTimestampClass * klass) DEFAULT_DROP_OUT_OF_SEGMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstRtpOnvifTimestamp:use-reference-timestamps: + * + * Whether to obtain timestamps from reference timestamp meta instead of using + * the ntp-offset method. If enabled then timestamps are expected to be + * attached to the buffers, and in that case ntp-offset should not be + * configured. + * + * Default value is FALSE, meaning that the ntp-offset property is used. + * If neither is set then the element calculates an ntp-offset. + * + * Since: 1.22 + */ + g_object_class_install_property (gobject_class, PROP_USE_REFERENCE_TIMESTAMPS, + g_param_spec_boolean ("use-reference-timestamps", + "Use reference timestamps", + "Whether the element should use reference UTC timestamps from the " + "buffers instead of using the ntp-offset mechanism.", + DEFAULT_USE_REFERENCE_TIMESTAMPS, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | + GST_PARAM_MUTABLE_READY)); + /* register pads */ gst_element_class_add_static_pad_template (gstelement_class, &sink_template_factory); @@ -347,9 +387,9 @@ gst_rtp_onvif_timestamp_sink_event (GstPad * pad, GstObject * parent, case GST_EVENT_CUSTOM_DOWNSTREAM: /* if the "set-e-bit" property is set, an offset event might mark the * stream as discontinued. We need to check if the currently cached buffer - * needs the e-bit before it's pushed */ - if (self->buffer != NULL && self->prop_set_e_bit && - gst_event_has_name (event, GST_NTP_OFFSET_EVENT_NAME)) { + * or buffer list needs the e-bit before it's pushed */ + if ((self->buffer != NULL || self->list != NULL) && self->prop_set_e_bit + && gst_event_has_name (event, GST_ONVIF_TIMESTAMP_EVENT_NAME)) { gboolean discont; if (parse_event_ntp_offset (self, event, NULL, &discont)) { GST_DEBUG_OBJECT (self, "stream %s discontinued", @@ -391,7 +431,7 @@ gst_rtp_onvif_timestamp_sink_event (GstPad * pad, GstObject * parent, /* enqueue serialized events if there is a cached buffer */ if (GST_EVENT_IS_SERIALIZED (event) && (self->buffer || self->list)) { - GST_DEBUG ("enqueueing serialized event"); + GST_WARNING ("enqueueing serialized event"); g_queue_push_tail (self->event_queue, event); event = NULL; goto out; @@ -403,7 +443,7 @@ gst_rtp_onvif_timestamp_sink_event (GstPad * pad, GstObject * parent, /* update the ntp-offset after any cached buffer/buffer list has been * pushed. the d-bit of the next buffer/buffer list should be set if * the stream is discontinued */ - if (gst_event_has_name (event, GST_NTP_OFFSET_EVENT_NAME)) { + if (gst_event_has_name (event, GST_ONVIF_TIMESTAMP_EVENT_NAME)) { GstClockTime offset; gboolean discont; if (parse_event_ntp_offset (self, event, &offset, &discont)) { @@ -452,6 +492,9 @@ gst_rtp_onvif_timestamp_init (GstRtpOnvifTimestamp * self) gst_pad_new_from_static_template (&src_template_factory, "src"); gst_element_add_pad (GST_ELEMENT (self), self->srcpad); + self->prop_use_reference_timestamps = DEFAULT_USE_REFERENCE_TIMESTAMPS; + self->reference_timestamp_id = gst_caps_new_empty_simple ("timestamp/x-unix"); + self->prop_ntp_offset = DEFAULT_NTP_OFFSET; self->prop_set_e_bit = DEFAULT_SET_E_BIT; self->prop_set_t_bit = DEFAULT_SET_T_BIT; @@ -467,6 +510,51 @@ gst_rtp_onvif_timestamp_init (GstRtpOnvifTimestamp * self) #define EXTENSION_ID 0xABAC #define EXTENSION_SIZE 3 +static guint64 +get_utc_from_reference_timestamp (GstRtpOnvifTimestamp * self, GstBuffer * buf) +{ + GstReferenceTimestampMeta *meta; + GstClockTime time; + + meta = gst_buffer_get_reference_timestamp_meta (buf, + self->reference_timestamp_id); + if (meta != NULL) { + /* the reference timestamp is expressed in unix times so add the difference + * between unix and ntp epochs */ + time = meta->timestamp + G_GUINT64_CONSTANT (2208988800) * GST_SECOND; + GST_TRACE_OBJECT (self, "UTC reference timestamp found: %" GST_TIME_FORMAT, + GST_TIME_ARGS (time)); + } else { + GST_ERROR_OBJECT (self, "UTC reference timestamp not found"); + time = GST_CLOCK_TIME_NONE; + } + + return time; +} + +static guint64 +get_utc_from_offset (GstRtpOnvifTimestamp * self, GstBuffer * buf) +{ + guint64 time = GST_CLOCK_TIME_NONE; + + if (GST_BUFFER_PTS_IS_VALID (buf)) { + time = gst_segment_to_stream_time (&self->segment, GST_FORMAT_TIME, + GST_BUFFER_PTS (buf)); + } else if (GST_BUFFER_DTS_IS_VALID (buf)) { + time = gst_segment_to_stream_time (&self->segment, GST_FORMAT_TIME, + GST_BUFFER_DTS (buf)); + } else { + g_assert_not_reached (); + } + + /* add the offset (in seconds) */ + if (time != GST_CLOCK_TIME_NONE) { + time += self->ntp_offset; + } + + return time; +} + static gboolean handle_buffer (GstRtpOnvifTimestamp * self, GstBuffer * buf) { @@ -477,7 +565,8 @@ handle_buffer (GstRtpOnvifTimestamp * self, GstBuffer * buf) guint64 time; guint8 field = 0; - if (!GST_CLOCK_TIME_IS_VALID (self->ntp_offset)) { + if (!self->prop_use_reference_timestamps && + !GST_CLOCK_TIME_IS_VALID (self->ntp_offset)) { GstClock *clock = gst_element_get_clock (GST_ELEMENT (self)); if (clock) { @@ -535,33 +624,35 @@ handle_buffer (GstRtpOnvifTimestamp * self, GstBuffer * buf) return FALSE; } - /* NTP timestamp */ - if (GST_BUFFER_PTS_IS_VALID (buf)) { - time = gst_segment_to_stream_time (&self->segment, GST_FORMAT_TIME, - GST_BUFFER_PTS (buf)); - } else if (GST_BUFFER_DTS_IS_VALID (buf)) { - time = gst_segment_to_stream_time (&self->segment, GST_FORMAT_TIME, - GST_BUFFER_DTS (buf)); + if (self->prop_use_reference_timestamps) { + time = get_utc_from_reference_timestamp (self, buf); + if (time == GST_CLOCK_TIME_NONE) { + gst_rtp_buffer_unmap (&rtp); + return FALSE; + } + } else if (GST_BUFFER_PTS_IS_VALID (buf) || GST_BUFFER_DTS_IS_VALID (buf)) { + time = get_utc_from_offset (self, buf); + if (self->prop_drop_out_of_segment && time == GST_CLOCK_TIME_NONE) { + GST_ERROR_OBJECT (self, "Failed to get stream time"); + gst_rtp_buffer_unmap (&rtp); + return FALSE; + } } else { GST_INFO_OBJECT (self, "Buffer doesn't contain any valid DTS or PTS timestamp"); goto done; } - if (self->prop_drop_out_of_segment && time == GST_CLOCK_TIME_NONE) { - GST_ERROR_OBJECT (self, "Failed to get stream time"); + if (time == GST_CLOCK_TIME_NONE) { + GST_ERROR_OBJECT (self, "failed calculating timestamp"); gst_rtp_buffer_unmap (&rtp); return FALSE; } - /* add the offset (in seconds) */ - if (time != GST_CLOCK_TIME_NONE) { - time += self->ntp_offset; - /* convert to NTP time. upper 32 bits should contain the seconds - * and the lower 32 bits, the fractions of a second. */ - time = gst_util_uint64_scale (time, (G_GINT64_CONSTANT (1) << 32), - GST_SECOND); - } + /* convert to NTP time. upper 32 bits should contain the seconds + * and the lower 32 bits, the fractions of a second. */ + time = gst_util_uint64_scale (time, (G_GINT64_CONSTANT (1) << 32), + GST_SECOND); GST_DEBUG_OBJECT (self, "timestamp: %" G_GUINT64_FORMAT, time); diff --git a/subprojects/gst-plugins-bad/gst/onvif/gstrtponviftimestamp.h b/subprojects/gst-plugins-bad/gst/onvif/gstrtponviftimestamp.h index 15a47f828a..5cbc5f5846 100644 --- a/subprojects/gst-plugins-bad/gst/onvif/gstrtponviftimestamp.h +++ b/subprojects/gst-plugins-bad/gst/onvif/gstrtponviftimestamp.h @@ -54,11 +54,16 @@ struct _GstRtpOnvifTimestamp { gboolean prop_set_t_bit; gboolean prop_drop_out_of_segment; + /* whether reference timestamps from the buffers should be used instead + * of the ntp offset mechanism */ + gboolean prop_use_reference_timestamps; + GstCaps *reference_timestamp_id; + /* currently used ntp-offset - *(can be changed runtime with a GstNtpOffset event) + *(can be changed runtime with a GstOnvifTimestamp event) */ GstClockTime ntp_offset; - /* a GstNtpOffset event might mark the stream as discontinued */ + /* a GstOnvifTimestamp event might mark the stream as discontinued */ gboolean set_d_bit; gboolean set_e_bit; gboolean set_t_bit; diff --git a/subprojects/gst-plugins-bad/tests/check/elements/rtponviftimestamp.c b/subprojects/gst-plugins-bad/tests/check/elements/rtponviftimestamp.c index f5c6c7cc56..bb3aabbc38 100644 --- a/subprojects/gst-plugins-bad/tests/check/elements/rtponviftimestamp.c +++ b/subprojects/gst-plugins-bad/tests/check/elements/rtponviftimestamp.c @@ -66,7 +66,8 @@ create_ntp_offset_event (GstClockTime ntp_offset, gboolean discont) { GstStructure *structure; - structure = gst_structure_new ("GstNtpOffset", "ntp-offset", G_TYPE_UINT64, + structure = + gst_structure_new ("GstOnvifTimestamp", "ntp-offset", G_TYPE_UINT64, ntp_offset, "discont", G_TYPE_BOOLEAN, discont, NULL); return gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, structure); @@ -757,6 +758,90 @@ GST_START_TEST (test_ntp_time) GST_END_TEST; +GST_START_TEST (test_reference_ts) +{ + GstCaps *ref_ts_id = gst_caps_new_empty_simple ("timestamp/x-unix"); + GstSegment segment; + GstBuffer *buffer; + guint64 timestamp; + GstRTPBuffer rtpbuffer = GST_RTP_BUFFER_INIT; + guint8 *data; + guint64 expected_ntp_time; + + /* configure element to use references timestamps */ + g_object_set (element, "use-reference-timestamps", TRUE, NULL); + + ASSERT_SET_STATE (element, GST_STATE_PLAYING, GST_STATE_CHANGE_SUCCESS); + + /* push initial events */ + gst_check_setup_events (mysrcpad, element, NULL, GST_FORMAT_TIME); + + /* a suitable segment */ + gst_segment_init (&segment, GST_FORMAT_TIME); + segment.start = 0; + segment.base = 0; + gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment)); + + /* create a buffer with PTS 0, which should result in both stream time and + * running time becoming 0 with the segment pushed above */ + buffer = create_rtp_buffer (0, FALSE); + + /* add a reference timestamp to the buffer and push it to the element */ + timestamp = 42; + ck_assert_ptr_ne (gst_buffer_add_reference_timestamp_meta (buffer, + ref_ts_id, timestamp, GST_CLOCK_TIME_NONE), NULL); + + /* the timestamp in the extension header is relative to the NTP epoch, so + * adjust the expected timestamp for the difference between unix and ntp + * epochs */ + expected_ntp_time = + gst_util_uint64_scale (timestamp + + G_GUINT64_CONSTANT (2208988800) * GST_SECOND, + (G_GINT64_CONSTANT (1) << 32), GST_SECOND); + + fail_unless_equals_int (gst_pad_push (mysrcpad, buffer), GST_FLOW_OK); + fail_unless_equals_int (g_list_length (buffers), 1); + buffer = g_list_last (buffers)->data; + + /* get the extension header */ + fail_unless (gst_rtp_buffer_map (buffer, GST_MAP_READWRITE, &rtpbuffer)); + fail_unless (gst_rtp_buffer_get_extension_data (&rtpbuffer, NULL, + (gpointer) & data, NULL)); + + /* read the NTP timestamp and verify that it's the expected one, i.e. derived + * from the reference timestamp */ + timestamp = GST_READ_UINT64_BE (data); + fail_unless_equals_uint64 (timestamp, expected_ntp_time); + + gst_rtp_buffer_unmap (&rtpbuffer); + gst_check_drop_buffers (); + gst_caps_unref (ref_ts_id); +} + +GST_END_TEST; + +GST_START_TEST (test_reference_ts_not_present) +{ + GstBuffer *buffer; + + /* configure element to use references timestamps */ + g_object_set (element, "use-reference-timestamps", TRUE, NULL); + + ASSERT_SET_STATE (element, GST_STATE_PLAYING, GST_STATE_CHANGE_SUCCESS); + + /* push initial events */ + gst_check_setup_events (mysrcpad, element, NULL, GST_FORMAT_TIME); + + /* create a buffer without reference timestamp, push it and verify that + * GST_FLOW_ERROR is returned */ + buffer = create_rtp_buffer (0, FALSE); + fail_unless_equals_int (gst_pad_push (mysrcpad, buffer), GST_FLOW_ERROR); + + gst_check_drop_buffers (); +} + +GST_END_TEST; + static Suite * onviftimestamp_suite (void) { @@ -776,6 +861,9 @@ onviftimestamp_suite (void) tcase_add_test (tc_general, test_ntp_offset_event); tcase_add_test (tc_general, test_ntp_time); + tcase_add_test (tc_general, test_reference_ts); + tcase_add_test (tc_general, test_reference_ts_not_present); + tc_events = tcase_create ("events"); suite_add_tcase (s, tc_events); tcase_add_checked_fixture (tc_events, setup_with_event, cleanup_with_event); diff --git a/subprojects/gst-rtsp-server/examples/test-onvif-server.c b/subprojects/gst-rtsp-server/examples/test-onvif-server.c index bcd48afdfb..324c63be96 100644 --- a/subprojects/gst-rtsp-server/examples/test-onvif-server.c +++ b/subprojects/gst-rtsp-server/examples/test-onvif-server.c @@ -381,7 +381,7 @@ handle_segment_done (ReplayBin * self, GstPad * pad) GstStructure *s; /* Signify the end of a contiguous section of recording */ - s = gst_structure_new ("GstNtpOffset", + s = gst_structure_new ("GstOnvifTimestamp", "ntp-offset", G_TYPE_UINT64, 0, "discont", G_TYPE_BOOLEAN, TRUE, NULL); event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s); diff --git a/subprojects/gst-rtsp-server/tests/check/gst/onvif.c b/subprojects/gst-rtsp-server/tests/check/gst/onvif.c index 4014f421af..f33e50dc39 100644 --- a/subprojects/gst-rtsp-server/tests/check/gst/onvif.c +++ b/subprojects/gst-rtsp-server/tests/check/gst/onvif.c @@ -197,7 +197,7 @@ test_src_create (GstPushSrc * psrc, GstBuffer ** buffer) real_time += (G_GUINT64_CONSTANT (2208988800) * GST_SECOND); src->ntp_offset = real_time - clock_time; - s = gst_structure_new ("GstNtpOffset", + s = gst_structure_new ("GstOnvifTimestamp", "ntp-offset", G_TYPE_UINT64, src->ntp_offset, "discont", G_TYPE_BOOLEAN, FALSE, NULL); @@ -221,7 +221,7 @@ test_src_create (GstPushSrc * psrc, GstBuffer ** buffer) next_n_frames = (n_frames / 10 - n_gops) * 10; src->segment->position = next_n_frames * GST_MSECOND; - s = gst_structure_new ("GstNtpOffset", + s = gst_structure_new ("GstOnvifTimestamp", "ntp-offset", G_TYPE_UINT64, src->ntp_offset, "discont", G_TYPE_BOOLEAN, TRUE, NULL); @@ -609,7 +609,7 @@ test_play_response_200_and_check_data (GstRTSPClient * client, buf = gst_rtp_buffer_new_copy_data (body, body_size); switch (body_size) { - case 115: /* Ignore our serialized custom events */ + case 120: /* Ignore our serialized custom events */ is_custom_event = TRUE; break; case 56: