diff --git a/configure.ac b/configure.ac index ff75aea59c..fd1ad03217 100644 --- a/configure.ac +++ b/configure.ac @@ -471,6 +471,7 @@ AG_GST_CHECK_PLUGIN(mpegpsmux) AG_GST_CHECK_PLUGIN(mve) AG_GST_CHECK_PLUGIN(mxf) AG_GST_CHECK_PLUGIN(nuvdemux) +AG_GST_CHECK_PLUGIN(onvif) AG_GST_CHECK_PLUGIN(patchdetect) AG_GST_CHECK_PLUGIN(pcapparse) AG_GST_CHECK_PLUGIN(pnm) @@ -3220,6 +3221,7 @@ gst/mpegpsmux/Makefile gst/mve/Makefile gst/mxf/Makefile gst/nuvdemux/Makefile +gst/onvif/Makefile gst/patchdetect/Makefile gst/pcapparse/Makefile gst/pnm/Makefile diff --git a/gst/onvif/Makefile.am b/gst/onvif/Makefile.am new file mode 100644 index 0000000000..9d0223bd42 --- /dev/null +++ b/gst/onvif/Makefile.am @@ -0,0 +1,28 @@ +plugin_LTLIBRARIES = libgstrtponvif.la + +libgstrtponvif_la_SOURCES = \ + gstrtponvif.c \ + gstrtponviftimestamp.c gstrtponviftimestamp.h +libgstrtponvif_la_CFLAGS = $(GST_PLUGINS_BAD_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) \ + $(GST_BASE_CFLAGS) $(GST_CFLAGS) +libgstrtponvif_la_LIBADD = $(GST_PLUGINS_BASE_LIBS) \ + $(GST_BASE_LIBS) $(GST_LIBS) -lgstrtp-$(GST_API_VERSION) +libgstrtponvif_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstrtponvif_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS) + + +EXTRA_DIST = + +Android.mk: Makefile.am $(BUILT_SOURCES) + androgenizer \ + -:PROJECT libgstrtponvif -:SHARED libgstrtponvif \ + -:TAGS eng debug \ + -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ + -:SOURCES $(libgstrtponvif_la_SOURCES) \ + -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(libgstrtponvif_la_CFLAGS) \ + -:LDFLAGS $(libgstrtponvif_la_LDFLAGS) \ + $(libgstrtponvif_la_LIBADD) \ + -ldl \ + -:PASSTHROUGH LOCAL_ARM_MODE:=arm \ + LOCAL_MODULE_PATH:='$$(TARGET_OUT)/lib/gstreamer-0.10' \ + > $@ diff --git a/gst/onvif/gstrtponvif.c b/gst/onvif/gstrtponvif.c new file mode 100644 index 0000000000..8f24d94fb7 --- /dev/null +++ b/gst/onvif/gstrtponvif.c @@ -0,0 +1,44 @@ +/* + * gstrtponvif.c + * + * Copyright (C) 2014 Axis Communications AB + * Author: Guillaume Desmottes + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "gstrtponviftimestamp.h" + +static gboolean +plugin_init (GstPlugin * plugin) +{ + if (!gst_element_register (plugin, "rtponviftimestamp", GST_RANK_NONE, + GST_TYPE_RTP_ONVIF_TIMESTAMP)) + return FALSE; + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + rtponvif, + "ONVIF Streaming features", + plugin_init, VERSION, GST_LICENSE_UNKNOWN, GST_PACKAGE_NAME, + GST_PACKAGE_ORIGIN) diff --git a/gst/onvif/gstrtponviftimestamp.c b/gst/onvif/gstrtponviftimestamp.c new file mode 100644 index 0000000000..8493da6700 --- /dev/null +++ b/gst/onvif/gstrtponviftimestamp.c @@ -0,0 +1,449 @@ +/* + * gstrtponviftimestamp.h + * + * Copyright (C) 2014 Axis Communications AB + * Author: Guillaume Desmottes + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include + +#include "gstrtponviftimestamp.h" + +#define DEFAULT_NTP_OFFSET 0 +#define DEFAULT_CSEQ 0 +#define DEFAULT_SET_E_BIT FALSE + +GST_DEBUG_CATEGORY_STATIC (rtponviftimestamp_debug); +#define GST_CAT_DEFAULT (rtponviftimestamp_debug) + +static GstFlowReturn gst_rtp_onvif_timestamp_chain (GstPad * pad, + GstObject * parent, GstBuffer * buf); +static GstFlowReturn gst_rtp_onvif_timestamp_chain_list (GstPad * pad, + GstObject * parent, GstBufferList * list); + +static GstStaticPadTemplate sink_template_factory = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-rtp") + ); + +static GstStaticPadTemplate src_template_factory = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-rtp") + ); + +enum +{ + ARG_0, + PROP_NTP_OFFSET, + PROP_CSEQ, + PROP_SET_E_BIT, +}; + +/*static guint gst_rtp_onvif_timestamp_signals[LAST_SIGNAL] = { 0 }; */ + +G_DEFINE_TYPE (GstRtpOnvifTimestamp, gst_rtp_onvif_timestamp, GST_TYPE_ELEMENT); + +static void +gst_rtp_onvif_timestamp_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + GstRtpOnvifTimestamp *self = GST_RTP_ONVIF_TIMESTAMP (object); + + switch (prop_id) { + case PROP_NTP_OFFSET: + g_value_set_uint64 (value, self->prop_ntp_offset); + break; + case PROP_CSEQ: + g_value_set_uint (value, self->prop_cseq); + break; + case PROP_SET_E_BIT: + g_value_set_boolean (value, self->prop_set_e_bit); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_rtp_onvif_timestamp_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + GstRtpOnvifTimestamp *self = GST_RTP_ONVIF_TIMESTAMP (object); + + switch (prop_id) { + case PROP_NTP_OFFSET: + self->prop_ntp_offset = g_value_get_uint64 (value); + break; + case PROP_CSEQ: + self->prop_cseq = g_value_get_uint (value); + break; + case PROP_SET_E_BIT: + self->prop_set_e_bit = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstStateChangeReturn +gst_rtp_onvif_timestamp_change_state (GstElement * element, + GstStateChange transition) +{ + GstRtpOnvifTimestamp *self = GST_RTP_ONVIF_TIMESTAMP (element); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED); + break; + default: + break; + } + + return + GST_ELEMENT_CLASS (gst_rtp_onvif_timestamp_parent_class)->change_state + (element, transition); +} + +static void +gst_rtp_onvif_timestamp_finalize (GObject * object) +{ + GstRtpOnvifTimestamp *self = GST_RTP_ONVIF_TIMESTAMP (object); + + if (self->buffer) + gst_buffer_unref (self->buffer); + if (self->list) + gst_buffer_list_unref (self->list); + + G_OBJECT_CLASS (gst_rtp_onvif_timestamp_parent_class)->finalize (object); +} + +static void +gst_rtp_onvif_timestamp_class_init (GstRtpOnvifTimestampClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = G_OBJECT_CLASS (klass); + gstelement_class = GST_ELEMENT_CLASS (klass); + + gobject_class->get_property = gst_rtp_onvif_timestamp_get_property; + gobject_class->set_property = gst_rtp_onvif_timestamp_set_property; + gobject_class->finalize = gst_rtp_onvif_timestamp_finalize; + + g_object_class_install_property (gobject_class, PROP_NTP_OFFSET, + g_param_spec_uint64 ("ntp-offset", "NTP offset", + "Offset between the pipeline running time and the absolute UTC time, " + "in seconds since 1900", + 0, G_MAXUINT64, + DEFAULT_NTP_OFFSET, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_CSEQ, + g_param_spec_uint ("cseq", "CSeq", + "The RTSP CSeq which initiated the playback", + 0, G_MAXUINT32, + DEFAULT_CSEQ, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_SET_E_BIT, + g_param_spec_boolean ("set-e-bit", "Set 'E' bit", + "If the element should set the 'E' bit as defined in the ONVIF RTP " + "extension. This increases latency by one packet", + DEFAULT_SET_E_BIT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /* register pads */ + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&sink_template_factory)); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&src_template_factory)); + + gst_element_class_set_static_metadata (gstelement_class, + "ONVIF NTP timestamps RTP extension", "Effect/RTP", + "Add absolute timestamps and flags of recorded data in a playback " + "session", "Guillaume Desmottes "); + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_rtp_onvif_timestamp_change_state); + + GST_DEBUG_CATEGORY_INIT (rtponviftimestamp_debug, "rtponviftimestamp", + 0, "ONVIF NTP timestamps RTP extension"); +} + +static GstFlowReturn handle_and_push_buffer (GstRtpOnvifTimestamp * self, + GstBuffer * buf, gboolean end_contiguous); +static GstFlowReturn handle_and_push_buffer_list (GstRtpOnvifTimestamp * self, + GstBufferList * list, gboolean end_contiguous); + +static gboolean +gst_rtp_onvif_timestamp_sink_event (GstPad * pad, GstObject * parent, + GstEvent * event) +{ + GstRtpOnvifTimestamp *self = GST_RTP_ONVIF_TIMESTAMP (parent); + + GST_DEBUG_OBJECT (pad, "handling event %s", GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEGMENT: + gst_event_copy_segment (event, &self->segment); + break; + case GST_EVENT_FLUSH_STOP: + gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED); + break; + case GST_EVENT_EOS: + /* Push pending buffers, if any */ + if (self->buffer) { + handle_and_push_buffer (self, self->buffer, TRUE); + self->buffer = NULL; + } + if (self->list) { + handle_and_push_buffer_list (self, self->list, TRUE); + self->list = NULL; + } + break; + default: + break; + } + + return gst_pad_event_default (pad, parent, event); +} + +static void +gst_rtp_onvif_timestamp_init (GstRtpOnvifTimestamp * self) +{ + self->sinkpad = + gst_pad_new_from_static_template (&sink_template_factory, "sink"); + gst_pad_set_chain_function (self->sinkpad, gst_rtp_onvif_timestamp_chain); + gst_pad_set_chain_list_function (self->sinkpad, + gst_rtp_onvif_timestamp_chain_list); + gst_pad_set_event_function (self->sinkpad, + gst_rtp_onvif_timestamp_sink_event); + gst_element_add_pad (GST_ELEMENT (self), self->sinkpad); + GST_PAD_SET_PROXY_CAPS (self->sinkpad); + GST_PAD_SET_PROXY_ALLOCATION (self->sinkpad); + + self->srcpad = + gst_pad_new_from_static_template (&src_template_factory, "src"); + gst_element_add_pad (GST_ELEMENT (self), self->srcpad); + + self->prop_ntp_offset = DEFAULT_NTP_OFFSET; + self->prop_set_e_bit = DEFAULT_SET_E_BIT; + + self->buffer = NULL; + self->list = NULL; + + gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED); +} + +#define EXTENSION_ID 0xABAC +#define EXTENSION_SIZE 3 + +static gboolean +handle_buffer (GstRtpOnvifTimestamp * self, GstBuffer * buf, + gboolean end_contiguous) +{ + GstRTPBuffer rtp = GST_RTP_BUFFER_INIT; + guint8 *data; + guint16 bits; + guint wordlen; + guint64 time; + guint8 field = 0; + + if (self->segment.format != GST_FORMAT_TIME) { + GST_ELEMENT_ERROR (self, STREAM, FAILED, + ("did not receive a time segment yet"), (NULL)); + return FALSE; + } + + if (!gst_rtp_buffer_map (buf, GST_MAP_READWRITE, &rtp)) { + GST_ELEMENT_ERROR (self, STREAM, FAILED, + ("Failed to map RTP buffer"), (NULL)); + return FALSE; + } + + if (!gst_rtp_buffer_set_extension_data (&rtp, EXTENSION_ID, EXTENSION_SIZE)) { + GST_ELEMENT_ERROR (self, STREAM, FAILED, ("Failed to set extension data"), + (NULL)); + gst_rtp_buffer_unmap (&rtp); + return FALSE; + } + + if (!gst_rtp_buffer_get_extension_data (&rtp, &bits, (gpointer) & data, + &wordlen)) { + GST_ELEMENT_ERROR (self, STREAM, FAILED, ("Failed to get extension data"), + (NULL)); + gst_rtp_buffer_unmap (&rtp); + return FALSE; + } + + /* NTP timestamp */ + if (GST_BUFFER_DTS_IS_VALID (buf)) { + time = gst_segment_to_running_time (&self->segment, GST_FORMAT_TIME, + GST_BUFFER_DTS (buf)); + } else if (GST_BUFFER_PTS_IS_VALID (buf)) { + time = gst_segment_to_running_time (&self->segment, GST_FORMAT_TIME, + GST_BUFFER_PTS (buf)); + } else { + GST_ERROR_OBJECT (self, + "Buffer doesn't contain any valid DTS or PTS timestamp"); + goto done; + } + + if (time == GST_CLOCK_TIME_NONE) { + GST_ERROR_OBJECT (self, "Failed to get running time"); + goto done; + } + + /* add the offset (in seconds) */ + time += (self->prop_ntp_offset * 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); + + GST_WRITE_UINT64_BE (data, time); + + /* The next byte is composed of: C E D mbz (5 bits) */ + + /* Set C if the buffer does *not* have the DELTA_UNIT flag as it means + * that's a key frame (or 'clean point'). */ + if (!GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT)) { + GST_DEBUG_OBJECT (self, "set C flag"); + field |= (1 << 7); + } + + /* Set E if the next buffer has DISCONT */ + if (end_contiguous) { + GST_DEBUG_OBJECT (self, "set E flag"); + field |= (1 << 6); + } + + /* Set D if the buffer has the DISCONT flag */ + if (GST_BUFFER_IS_DISCONT (buf)) { + GST_DEBUG_OBJECT (self, "set D flag"); + field |= (1 << 5); + } + + GST_WRITE_UINT8 (data + 8, field); + + /* CSeq (low-order byte) */ + GST_WRITE_UINT8 (data + 9, (guchar) self->prop_cseq); + + memset (data + 10, 0, 3); + +done: + gst_rtp_buffer_unmap (&rtp); + return TRUE; +} + +/* @buf: (transfer all) */ +static GstFlowReturn +handle_and_push_buffer (GstRtpOnvifTimestamp * self, GstBuffer * buf, + gboolean end_contiguous) +{ + if (!handle_buffer (self, buf, end_contiguous)) { + gst_buffer_unref (buf); + return GST_FLOW_ERROR; + } + + return gst_pad_push (self->srcpad, buf); +} + +static GstFlowReturn +gst_rtp_onvif_timestamp_chain (GstPad * pad, GstObject * parent, + GstBuffer * buf) +{ + GstRtpOnvifTimestamp *self = GST_RTP_ONVIF_TIMESTAMP (parent); + GstFlowReturn result = GST_FLOW_OK; + + if (!self->prop_set_e_bit) { + /* Modify and push this buffer right away */ + return handle_and_push_buffer (self, buf, FALSE); + } + + /* We have to wait for the *next* buffer before pushing this one */ + + if (self->buffer) { + /* push the *previous* buffer received */ + result = handle_and_push_buffer (self, self->buffer, + GST_BUFFER_IS_DISCONT (buf)); + } + + /* Transfer ownership */ + self->buffer = buf; + return result; +} + +/* @buf: (transfer all) */ +static GstFlowReturn +handle_and_push_buffer_list (GstRtpOnvifTimestamp * self, + GstBufferList * list, gboolean end_contiguous) +{ + GstBuffer *buf; + + /* Set the extension on the *first* buffer */ + buf = gst_buffer_list_get (list, 0); + if (!handle_buffer (self, buf, end_contiguous)) { + gst_buffer_list_unref (list); + return GST_FLOW_ERROR; + } + + return gst_pad_push_list (self->srcpad, list); +} + +/* gst_pad_chain_list_default() refs the buffer when passing it to the chain + * function, making it not writable. We implement our own chain_list function + * to avoid having to copy each buffer. */ +static GstFlowReturn +gst_rtp_onvif_timestamp_chain_list (GstPad * pad, GstObject * parent, + GstBufferList * list) +{ + GstRtpOnvifTimestamp *self = GST_RTP_ONVIF_TIMESTAMP (parent); + GstFlowReturn result = GST_FLOW_OK; + GstBuffer *buf; + + if (!self->prop_set_e_bit) { + return handle_and_push_buffer_list (self, list, FALSE); + } + + /* We have to wait for the *next* list before pushing this one */ + + if (self->list) { + /* push the *previous* list received */ + buf = gst_buffer_list_get (list, 0); + + result = handle_and_push_buffer_list (self, self->list, + GST_BUFFER_IS_DISCONT (buf)); + } + + /* Transfer ownership */ + self->list = list; + return result; +} diff --git a/gst/onvif/gstrtponviftimestamp.h b/gst/onvif/gstrtponviftimestamp.h new file mode 100644 index 0000000000..d068846f15 --- /dev/null +++ b/gst/onvif/gstrtponviftimestamp.h @@ -0,0 +1,72 @@ +/* + * gstrtponviftimestamp.h + * + * Copyright (C) 2014 Axis Communications AB + * Author: Guillaume Desmottes + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#ifndef __GST_RTP_ONVIF_TIMESTAMP_H__ +#define __GST_RTP_ONVIF_TIMESTAMP_H__ + + +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#define GST_TYPE_RTP_ONVIF_TIMESTAMP \ + (gst_rtp_onvif_timestamp_get_type()) +#define GST_RTP_ONVIF_TIMESTAMP(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTP_ONVIF_TIMESTAMP,GstRtpOnvifTimestamp)) +#define GST_RTP_ONVIF_TIMESTAMP_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RTP_ONVIF_TIMESTAMP,GstRtpOnvifTimestampClass)) +#define GST_IS_RTP_ONVIF_TIMESTAMP(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTP_ONVIF_TIMESTAMP)) +#define GST_IS_RTP_ONVIF_TIMESTAMP_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RTP_ONVIF_TIMESTAMP)) + +typedef struct _GstRtpOnvifTimestamp GstRtpOnvifTimestamp; +typedef struct _GstRtpOnvifTimestampClass GstRtpOnvifTimestampClass; + +struct _GstRtpOnvifTimestamp { + GstElement element; + + /* pads */ + GstPad *sinkpad,*srcpad; + + guint64 prop_ntp_offset; + guint prop_cseq; + gboolean prop_set_e_bit; + + GstSegment segment; + gboolean received_segment; + /* Buffer waiting to be handled, only used if prop_set_e_bit is TRUE */ + GstBuffer *buffer; + GstBufferList *list; +}; + +struct _GstRtpOnvifTimestampClass { + GstElementClass parent_class; +}; + +GType gst_rtp_onvif_timestamp_get_type(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __GST_RTP_ONVIF_TIMESTAMP_H__ */ diff --git a/tests/check/Makefile.am b/tests/check/Makefile.am index 311fc1a739..ccecaf8e7a 100644 --- a/tests/check/Makefile.am +++ b/tests/check/Makefile.am @@ -256,6 +256,7 @@ check_PROGRAMS = \ $(check_mpg123) \ elements/mxfdemux \ elements/mxfmux \ + elements/rtponvif \ elements/id3mux \ pipelines/mxf \ $(check_mimic) \ @@ -431,6 +432,8 @@ libs_insertbin_LDADD = \ libs_insertbin_CFLAGS = \ $(GST_PLUGINS_BAD_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) $(AM_CFLAGS) +elements_rtponvif_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(AM_CFLAGS) +elements_rtponvif_LDADD = $(GST_PLUGINS_BASE_LIBS) $(GST_BASE_LIBS) $(GST_LIBS) -lgstrtp-$(GST_API_VERSION) $(LDADD) EXTRA_DIST = gst-plugins-bad.supp $(uvch264_dist_data) diff --git a/tests/check/elements/.gitignore b/tests/check/elements/.gitignore index 6cdc463a5e..d38dcdbb66 100644 --- a/tests/check/elements/.gitignore +++ b/tests/check/elements/.gitignore @@ -42,6 +42,7 @@ mxfdemux mxfmux neonhttpsrc ofa +rtponvif opus rganalysis rglimiter diff --git a/tests/check/elements/rtponvif.c b/tests/check/elements/rtponvif.c new file mode 100644 index 0000000000..99a98e8c8e --- /dev/null +++ b/tests/check/elements/rtponvif.c @@ -0,0 +1,359 @@ +/* + * onviftimestamp.c + * + * Copyright (C) 2014 Axis Communications AB + * Author: Guillaume Desmottes + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include +#include + +/* For ease of programming we use globals to keep refs for our floating + * src and sink pads we create; otherwise we always have to do get_pad, + * get_peer, and then remove references in every test function */ +static GstPad *mysrcpad, *mysinkpad; + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-rtp") + ); +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-rtp") + ); + +#define NTP_OFFSET (guint64) 1245 +#define TIMESTAMP 42 + +static void +setup_element (GstElement * element) +{ + mysrcpad = gst_check_setup_src_pad (element, &srctemplate); + mysinkpad = gst_check_setup_sink_pad (element, &sinktemplate); + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + fail_unless (gst_element_set_state (element, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); +} + +static GstElement * +setup_rtponviftimestamp (gboolean set_e_bit) +{ + GstElement *timestamp; + + GST_DEBUG ("setup_rtponviftimestamp"); + timestamp = gst_check_setup_element ("rtponviftimestamp"); + + g_object_set (timestamp, "ntp-offset", NTP_OFFSET, "cseq", 0x12345678, + "set-e-bit", set_e_bit, NULL); + + setup_element (timestamp); + + return timestamp; +} + +static void +cleanup_element (GstElement * element) +{ + fail_unless (gst_element_set_state (element, + GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS, "could not set to null"); + + gst_pad_set_active (mysrcpad, FALSE); + if (mysinkpad) + gst_pad_set_active (mysinkpad, FALSE); + gst_check_teardown_src_pad (element); + gst_check_teardown_sink_pad (element); + gst_check_teardown_element (element); + mysrcpad = NULL; + mysinkpad = NULL; +} + +static void +cleanup_rtponviftimestamp (GstElement * timestamp) +{ + GST_DEBUG ("cleanup_rtponviftimestamp"); + + cleanup_element (timestamp); +} + +static void +check_buffer_equal (GstBuffer * buf, GstBuffer * expected) +{ + GstMapInfo info_buf, info_expected; + + fail_if (buf == NULL); + fail_if (expected == NULL); + + gst_buffer_map (buf, &info_buf, GST_MAP_READ); + gst_buffer_map (expected, &info_expected, GST_MAP_READ); + + GST_LOG ("buffer: size %" G_GSIZE_FORMAT, info_buf.size); + GST_LOG ("expected: size %" G_GSIZE_FORMAT, info_expected.size); + GST_MEMDUMP ("buffer", info_buf.data, info_buf.size); + GST_MEMDUMP ("expected", info_expected.data, info_expected.size); + + fail_unless (info_buf.size == info_expected.size, + "size of the buffers are not the same"); + fail_unless (memcmp (info_buf.data, info_expected.data, info_buf.size) == 0, + "data is not the same"); + + gst_buffer_unmap (buf, &info_buf); + gst_buffer_unmap (expected, &info_expected); +} + +/* Create a RTP buffer without the extension */ +static GstBuffer * +create_rtp_buffer (guint64 timestamp, gboolean clean_point, gboolean discont) +{ + GstBuffer *buffer_in; + GstRTPBuffer rtpbuffer_in = GST_RTP_BUFFER_INIT; + + buffer_in = gst_rtp_buffer_new_allocate (4, 0, 0); + buffer_in->pts = timestamp; + + if (!clean_point) + GST_BUFFER_FLAG_SET (buffer_in, GST_BUFFER_FLAG_DELTA_UNIT); + if (discont) + GST_BUFFER_FLAG_SET (buffer_in, GST_BUFFER_FLAG_DISCONT); + + fail_unless (gst_rtp_buffer_map (buffer_in, GST_MAP_READ, &rtpbuffer_in)); + fail_if (gst_rtp_buffer_get_extension (&rtpbuffer_in)); + gst_rtp_buffer_unmap (&rtpbuffer_in); + + return buffer_in; +} + +static guint64 +convert_to_ntp (guint64 t) +{ + guint64 ntptime; + + /* convert to NTP time. upper 32 bits should contain the seconds + * and the lower 32 bits, the fractions of a second. */ + ntptime = gst_util_uint64_scale (t, (G_GINT64_CONSTANT (1) << 32), + GST_SECOND); + + return ntptime; +} + +/* Create a copy of @buffer_in having the RTP extension */ +static GstBuffer * +create_extension_buffer (GstBuffer * buffer_in, gboolean clean_point, + gboolean end_contiguous, gboolean discont) +{ + GstBuffer *buffer_out; + GstRTPBuffer rtpbuffer_out = GST_RTP_BUFFER_INIT; + guint8 *data; + guint8 flags = 0; + + buffer_out = gst_buffer_copy (buffer_in); + + fail_unless (gst_rtp_buffer_map (buffer_out, GST_MAP_READWRITE, + &rtpbuffer_out)); + + /* extension */ + gst_rtp_buffer_set_extension_data (&rtpbuffer_out, 0xABAC, 3); + fail_unless (gst_rtp_buffer_get_extension (&rtpbuffer_out)); + gst_rtp_buffer_get_extension_data (&rtpbuffer_out, NULL, (gpointer) & data, + NULL); + + /* NTP timestamp */ + GST_WRITE_UINT64_BE (data, convert_to_ntp (buffer_in->pts + NTP_OFFSET * + GST_SECOND)); + + /* C E D mbz */ + if (clean_point) + flags |= (1 << 7); + if (end_contiguous) + flags |= (1 << 6); + if (discont) + flags |= (1 << 5); + + GST_WRITE_UINT8 (data + 8, flags); + + /* CSeq */ + GST_WRITE_UINT8 (data + 9, 0x78); + + memset (data + 10, 0, 4); + + gst_rtp_buffer_unmap (&rtpbuffer_out); + + return buffer_out; +} + +static void +do_one_buffer_test_apply (gboolean clean_point, gboolean discont) +{ + GstElement *apply; + GstBuffer *buffer_in, *buffer_out; + GstSegment segment; + + apply = setup_rtponviftimestamp (FALSE); + + buffer_in = create_rtp_buffer (TIMESTAMP, clean_point, discont); + buffer_out = create_extension_buffer (buffer_in, clean_point, FALSE, discont); + + /* stream start */ + fail_unless (gst_pad_push_event (mysrcpad, + gst_event_new_stream_start ("test"))); + + /* Push a segment */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + /* Push buffer */ + fail_unless (gst_pad_push (mysrcpad, buffer_in) == GST_FLOW_OK, + "failed pushing buffer"); + + check_buffer_equal (buffers->data, buffer_out); + + g_list_foreach (buffers, (GFunc) gst_mini_object_unref, NULL); + g_list_free (buffers); + buffers = NULL; + + ASSERT_OBJECT_REFCOUNT (apply, "rtponviftimestamp", 1); + cleanup_rtponviftimestamp (apply); +} + +static void +do_two_buffers_test_apply (gboolean end_contiguous) +{ + GstElement *apply; + GstBuffer *buffer_in, *buffer_out; + GstSegment segment; + + apply = setup_rtponviftimestamp (TRUE); + + buffer_in = create_rtp_buffer (TIMESTAMP, FALSE, FALSE); + buffer_out = create_extension_buffer (buffer_in, FALSE, end_contiguous, + FALSE); + + /* stream start */ + fail_unless (gst_pad_push_event (mysrcpad, + gst_event_new_stream_start ("test"))); + + /* Push a segment */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + /* Push buffer */ + fail_unless (gst_pad_push (mysrcpad, buffer_in) == GST_FLOW_OK, + "failed pushing buffer"); + + /* The buffer hasn't been pushed it as the element is waiting for the next + * buffer. */ + g_assert_cmpuint (g_list_length (buffers), ==, 0); + + /* A second buffer is pushed, it has the DISCONT flag if we want that the + * first one has the 'E' bit set. */ + buffer_in = create_rtp_buffer (TIMESTAMP + 1, FALSE, end_contiguous); + + fail_unless (gst_pad_push (mysrcpad, buffer_in) == GST_FLOW_OK, + "failed pushing buffer"); + + /* The first buffer has now been pushed out */ + g_assert_cmpuint (g_list_length (buffers), ==, 1); + + check_buffer_equal (buffers->data, buffer_out); + + g_list_foreach (buffers, (GFunc) gst_mini_object_unref, NULL); + g_list_free (buffers); + buffers = NULL; + + /* Push EOS */ + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + + /* The second buffer has been pushed out */ + g_assert_cmpuint (g_list_length (buffers), ==, 1); + + /* Latest buffer always has the 'E' flag */ + buffer_out = create_extension_buffer (buffer_in, FALSE, TRUE, end_contiguous); + check_buffer_equal (buffers->data, buffer_out); + + ASSERT_OBJECT_REFCOUNT (apply, "rtponviftimestamp", 1); + cleanup_rtponviftimestamp (apply); +} + +GST_START_TEST (test_apply_discont) +{ + do_one_buffer_test_apply (FALSE, TRUE); +} + +GST_END_TEST; + +GST_START_TEST (test_apply_not_discont) +{ + do_one_buffer_test_apply (FALSE, FALSE); +} + +GST_END_TEST; + +GST_START_TEST (test_apply_clean_point) +{ + do_one_buffer_test_apply (TRUE, FALSE); +} + +GST_END_TEST; + +GST_START_TEST (test_apply_no_e_bit) +{ + do_two_buffers_test_apply (FALSE); +} + +GST_END_TEST; + +GST_START_TEST (test_apply_e_bit) +{ + do_two_buffers_test_apply (TRUE); +} + +GST_END_TEST; + +static Suite * +onviftimestamp_suite (void) +{ + Suite *s = suite_create ("onviftimestamp"); + TCase *tc_chain = tcase_create ("apply"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_apply_discont); + tcase_add_test (tc_chain, test_apply_not_discont); + tcase_add_test (tc_chain, test_apply_clean_point); + tcase_add_test (tc_chain, test_apply_no_e_bit); + tcase_add_test (tc_chain, test_apply_e_bit); + + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + Suite *s = onviftimestamp_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +}