diff --git a/subprojects/gst-plugins-good/docs/gst_plugins_cache.json b/subprojects/gst-plugins-good/docs/gst_plugins_cache.json index dc3097f1aa..3f2b8eb577 100644 --- a/subprojects/gst-plugins-good/docs/gst_plugins_cache.json +++ b/subprojects/gst-plugins-good/docs/gst_plugins_cache.json @@ -15863,6 +15863,129 @@ }, "rank": "primary" }, + "rtppassthroughpay": { + "author": "Sebastian Dröge , Jonas Danielsson ", + "description": "Passes through RTP packets", + "hierarchy": [ + "GstRtpPassthroughPay", + "GstElement", + "GstObject", + "GInitiallyUnowned", + "GObject" + ], + "klass": "Codec/Payloader/Network/RTP", + "pad-templates": { + "sink": { + "caps": "application/x-rtp:\n payload: [ 0, 127 ]\n clock-rate: [ 1, 2147483647 ]\n", + "direction": "sink", + "presence": "always" + }, + "src": { + "caps": "application/x-rtp:\n payload: [ 0, 127 ]\n clock-rate: [ 1, 2147483647 ]\n", + "direction": "src", + "presence": "always" + } + }, + "properties": { + "mtu": { + "blurb": "Maximum size of one packet", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "-1", + "min": "28", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": true + }, + "pt": { + "blurb": "The payload type of the packets", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "128", + "max": "128", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": true + }, + "seqnum": { + "blurb": "The RTP sequence number of the last processed packet", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "65535", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": false + }, + "seqnum-offset": { + "blurb": "Offset to add to all outgoing seqnum (-1 = random)", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "65535", + "min": "-1", + "mutable": "null", + "readable": true, + "type": "gint", + "writable": true + }, + "stats": { + "blurb": "Various statistics", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "application/x-rtp-payload-stats, clock-rate=(uint)0, running-time=(guint64)18446744073709551615, seqnum=(uint)0, timestamp=(uint)0, ssrc=(uint)0, pt=(uint)128, seqnum-offset=(uint)0, timestamp-offset=(uint)0;", + "mutable": "null", + "readable": true, + "type": "GstStructure", + "writable": false + }, + "timestamp": { + "blurb": "The RTP timestamp of the last processed packet", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "-1", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": false + }, + "timestamp-offset": { + "blurb": "Offset to add to all outgoing timestamps (default = random)", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "-1", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": true + } + }, + "rank": "none" + }, "rtppcmadepay": { "author": "Edgard Lima , Zeeshan Ali ", "description": "Extracts PCMA audio from RTP packets", diff --git a/subprojects/gst-plugins-good/gst/rtp/gstrtp.c b/subprojects/gst-plugins-good/gst/rtp/gstrtp.c index 9528ffb10c..180a3f634d 100644 --- a/subprojects/gst-plugins-good/gst/rtp/gstrtp.c +++ b/subprojects/gst-plugins-good/gst/rtp/gstrtp.c @@ -66,6 +66,7 @@ plugin_init (GstPlugin * plugin) ret |= GST_ELEMENT_REGISTER (rtpmpvpay, plugin); ret |= GST_ELEMENT_REGISTER (rtpopusdepay, plugin); ret |= GST_ELEMENT_REGISTER (rtpopuspay, plugin); + ret |= GST_ELEMENT_REGISTER (rtppassthroughpay, plugin); ret |= GST_ELEMENT_REGISTER (rtph261pay, plugin); ret |= GST_ELEMENT_REGISTER (rtph261depay, plugin); ret |= GST_ELEMENT_REGISTER (rtph263ppay, plugin); diff --git a/subprojects/gst-plugins-good/gst/rtp/gstrtpelements.h b/subprojects/gst-plugins-good/gst/rtp/gstrtpelements.h index 9321d3530a..ec9663730e 100644 --- a/subprojects/gst-plugins-good/gst/rtp/gstrtpelements.h +++ b/subprojects/gst-plugins-good/gst/rtp/gstrtpelements.h @@ -66,6 +66,7 @@ GST_ELEMENT_REGISTER_DECLARE (rtpmpvdepay); GST_ELEMENT_REGISTER_DECLARE (rtpmpvpay); GST_ELEMENT_REGISTER_DECLARE (rtpopusdepay); GST_ELEMENT_REGISTER_DECLARE (rtpopuspay); +GST_ELEMENT_REGISTER_DECLARE (rtppassthroughpay); GST_ELEMENT_REGISTER_DECLARE (rtph261pay); GST_ELEMENT_REGISTER_DECLARE (rtph261depay); GST_ELEMENT_REGISTER_DECLARE (rtph263ppay); diff --git a/subprojects/gst-plugins-good/gst/rtp/gstrtppassthroughpay.c b/subprojects/gst-plugins-good/gst/rtp/gstrtppassthroughpay.c new file mode 100644 index 0000000000..436f8c003b --- /dev/null +++ b/subprojects/gst-plugins-good/gst/rtp/gstrtppassthroughpay.c @@ -0,0 +1,467 @@ +/* + * GStreamer + * + * Copyright (C) 2023 Sebastian Dröge + * Copyright (C) 2023 Jonas Danielsson + * + * gstrtppassthroughpay.c: + * + * 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. + */ + +/** + * SECTION:element-rtppassthroughpay + * @title: rtppassthroughpay + * + * This elements pass RTP packets along unchanged and appear as a RTP + * payloader element to the outside world. + * + * This is useful, for example, in the case where you are receiving RTP + * packets from a different source and want to serve them over RTSP. Since the + * gst-rtsp-server library expect the element marked as `payX` to be a RTP + * payloader element and assumes certain properties are available. + * + * ## Example pipelines + * + * |[ + * gst-launch-1.0 rtpbin name=rtpbin \ + * videotestsrc ! videoconvert ! x264enc ! rtph264pay ! rtpbin.send_rtp_sink_0 \ + * rtpbin.send_rtp_src_0 ! udpsink port=5000 + * ]| Encode and payload H264 video from videotestsrc. The H264 RTP packets are + * sent on UDP port 5000. + * |[ + * test-launch "( udpsrc port=5000 caps=application/x-rtp, ..." ! .recv_rtp_sink_0 \ + * rtpbin ! rtppassthroughpay name=pay0 )" + * ]| Setup a gstreamer-rtsp-server using the example tool, it will listen for + * H264 RTP packets on port 5000 and present them using the rtppassthroughpay + * element as the well-known payloader pay0. + * + * Since: 1.24 + */ + +#include + +#include "gstrtpelements.h" +#include "gstrtppassthroughpay.h" + +GST_DEBUG_CATEGORY_STATIC (gst_rtp_passthrough_pay_debug); +#define GST_CAT_DEFAULT gst_rtp_passthrough_pay_debug + +#define PAYLOAD_TYPE_INVALID 128 /* valid range is 0 - 127 (seven bits) */ + +G_DEFINE_TYPE (GstRtpPassthroughPay, gst_rtp_passthrough_pay, GST_TYPE_ELEMENT); +GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (rtppassthroughpay, + "rtppassthroughpay", GST_RANK_NONE, GST_TYPE_RTP_PASSTHROUGH_PAY, + rtp_element_init (plugin)); + +static GstStaticPadTemplate src_template = +GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-rtp, " "payload = (int) [ 0, 127 ]," + "clock-rate = (int) [ 1, 2147483647 ]")); + +static GstStaticPadTemplate sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-rtp, " "payload = (int) [ 0, 127 ]," + "clock-rate = (int) [ 1, 2147483647 ]")); + +enum +{ + PROP_0, + PROP_PT, + PROP_MTU, + PROP_STATS, + PROP_SEQNUM, + PROP_SEQNUM_OFFSET, + PROP_TIMESTAMP, + PROP_TIMESTAMP_OFFSET, +}; + +static void gst_rtp_passthrough_pay_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec); + +static void gst_rtp_passthrough_pay_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); +static void gst_rtp_passthrough_pay_finalize (GObject * object); + +static GstStateChangeReturn +gst_rtp_passthrough_pay_change_state (GstElement * element, + GstStateChange transition); + +static GstFlowReturn gst_rtp_passthrough_pay_chain (GstPad * pad, + GstObject * parent, GstBuffer * buffer); + +static gboolean gst_rtp_passthrough_pay_sink_event (GstPad * pad, + GstObject * parent, GstEvent * event); + +static void gst_rtp_passthrough_set_payload_type (GstRtpPassthroughPay * self, + guint pt); + +static GstStructure + * gst_rtp_passthrough_pay_create_stats (GstRtpPassthroughPay * self); + +static void +gst_rtp_passthrough_pay_init (GstRtpPassthroughPay * self) +{ + self->sinkpad = gst_pad_new_from_static_template (&sink_template, "sink"); + gst_pad_set_chain_function (self->sinkpad, gst_rtp_passthrough_pay_chain); + gst_pad_set_event_function (self->sinkpad, + gst_rtp_passthrough_pay_sink_event); + GST_OBJECT_FLAG_SET (self->sinkpad, GST_PAD_FLAG_PROXY_ALLOCATION); + GST_OBJECT_FLAG_SET (self->sinkpad, GST_PAD_FLAG_PROXY_CAPS); + GST_OBJECT_FLAG_SET (self->sinkpad, GST_PAD_FLAG_PROXY_SCHEDULING); + gst_element_add_pad (GST_ELEMENT (self), self->sinkpad); + + self->srcpad = gst_pad_new_from_static_template (&src_template, "src"); + GST_OBJECT_FLAG_SET (self->srcpad, GST_PAD_FLAG_PROXY_CAPS); + gst_element_add_pad (GST_ELEMENT (self), self->srcpad); + + self->pt = PAYLOAD_TYPE_INVALID; +} + +static void +gst_rtp_passthrough_pay_class_init (GstRtpPassthroughPayClass + * gst_rtp_passthrough_pay_class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (gst_rtp_passthrough_pay_class); + GstElementClass *element_class = + GST_ELEMENT_CLASS (gst_rtp_passthrough_pay_class); + + gobject_class->set_property = gst_rtp_passthrough_pay_set_property; + gobject_class->get_property = gst_rtp_passthrough_pay_get_property; + gobject_class->finalize = gst_rtp_passthrough_pay_finalize; + + /** + * GstRtpPassthroughPay:pt: + * + * If set this will override the payload of the incoming RTP packets. + * If not set the payload type will be same as incoming RTP packets. + * + * Since: 1.24 + */ + g_object_class_install_property (G_OBJECT_CLASS (gobject_class), PROP_PT, + g_param_spec_uint ("pt", "payload type", + "The payload type of the packets", 0, 0x80, PAYLOAD_TYPE_INVALID, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstRtpPassthroughPay:mtu: + * + * Setting this property has no effect on this element, it is here and it + * is writable only to emulate a proper RTP payloader. + * + * Since: 1.24 + */ + g_object_class_install_property (G_OBJECT_CLASS (gobject_class), PROP_MTU, + g_param_spec_uint ("mtu", "MTU", "Maximum size of one packet", 28, + G_MAXUINT, 1492, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstRtpPassthroughPay:timestamp: + * + * Since: 1.24 + */ + g_object_class_install_property (G_OBJECT_CLASS (gobject_class), + PROP_TIMESTAMP, g_param_spec_uint ("timestamp", "Timestamp", + "The RTP timestamp of the last processed packet", 0, G_MAXUINT32, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + /** + * GstRtpPassthroughPay:seqnum: + * + * Since: 1.24 + */ + g_object_class_install_property (G_OBJECT_CLASS (gobject_class), PROP_SEQNUM, + g_param_spec_uint ("seqnum", "Sequence number", + "The RTP sequence number of the last processed packet", 0, + G_MAXUINT16, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + /** + * GstRtpPassthroughPay:timestamp-offset: + * + * Since: 1.24 + */ + g_object_class_install_property (G_OBJECT_CLASS (gobject_class), + PROP_TIMESTAMP_OFFSET, g_param_spec_uint ("timestamp-offset", + "Timestamp Offset", + "Offset to add to all outgoing timestamps (default = random)", 0, + G_MAXUINT32, -1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstRtpPassthroughPay:seqnum-offset: + * + * Since: 1.24 + */ + g_object_class_install_property (G_OBJECT_CLASS (gobject_class), + PROP_SEQNUM_OFFSET, g_param_spec_int ("seqnum-offset", + "Sequence number Offset", + "Offset to add to all outgoing seqnum (-1 = random)", -1, G_MAXUINT16, + -1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstRtpPassthroughPay:stats: + * + * Since: 1.24 + */ + g_object_class_install_property (G_OBJECT_CLASS (gobject_class), PROP_STATS, + g_param_spec_boxed ("stats", "Statistics", "Various statistics", + GST_TYPE_STRUCTURE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + element_class->change_state = gst_rtp_passthrough_pay_change_state; + + gst_element_class_add_static_pad_template (element_class, &sink_template); + gst_element_class_add_static_pad_template (element_class, &src_template); + + gst_element_class_set_static_metadata (element_class, + "RTP Passthrough payloader", "Codec/Payloader/Network/RTP", + "Passes through RTP packets", + "Sebastian Dröge , " + "Jonas Danielsson "); + + GST_DEBUG_CATEGORY_INIT (gst_rtp_passthrough_pay_debug, "rtppassthroughpay", + 0, "RTP Passthrough Payloader"); +} + +static void +gst_rtp_passthrough_pay_finalize (GObject * object) +{ + GstRtpPassthroughPay *self = GST_RTP_PASSTHROUGH_PAY (object); + + gst_clear_caps (&self->caps); + G_OBJECT_CLASS (gst_rtp_passthrough_pay_parent_class)->finalize (object); +} + +static void +gst_rtp_passthrough_pay_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstRtpPassthroughPay *self = GST_RTP_PASSTHROUGH_PAY (object); + + switch (prop_id) { + case PROP_PT: + g_value_set_uint (value, self->pt); + break; + case PROP_MTU: + g_value_set_uint (value, 0U); + break; + case PROP_TIMESTAMP: + g_value_set_uint (value, self->timestamp); + break; + case PROP_TIMESTAMP_OFFSET: + g_value_set_uint (value, self->timestamp_offset); + break; + case PROP_SEQNUM: + g_value_set_uint (value, self->seqnum); + break; + case PROP_SEQNUM_OFFSET: + g_value_set_int (value, (guint16) self->seqnum_offset); + break; + case PROP_STATS: + g_value_take_boxed (value, gst_rtp_passthrough_pay_create_stats (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_rtp_passthrough_pay_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstRtpPassthroughPay *self = GST_RTP_PASSTHROUGH_PAY (object); + + switch (prop_id) { + case PROP_PT: + gst_rtp_passthrough_set_payload_type (self, g_value_get_uint (value)); + break; + case PROP_MTU: + GST_WARNING_OBJECT (self, "Setting the mtu property has no effect"); + break; + case PROP_TIMESTAMP_OFFSET: + GST_FIXME_OBJECT (self, + "Setting the timestamp-offset property has no effect"); + break; + case PROP_SEQNUM_OFFSET: + GST_FIXME_OBJECT (self, + "Setting the seqnum-offset property has no effect"); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstStateChangeReturn +gst_rtp_passthrough_pay_change_state (GstElement * element, + GstStateChange transition) +{ + GstRtpPassthroughPay *self = GST_RTP_PASSTHROUGH_PAY (element); + GstStateChangeReturn ret; + + ret = GST_ELEMENT_CLASS (gst_rtp_passthrough_pay_parent_class) + ->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_clear_caps (&self->caps); + gst_segment_init (&self->segment, GST_FORMAT_TIME); + self->clock_rate = -1; + self->pt = PAYLOAD_TYPE_INVALID; + self->pt_override = FALSE; + self->ssrc = -1; + self->ssrc_set = FALSE; + self->timestamp = -1; + self->timestamp_offset = -1; + self->timestamp_offset_set = FALSE; + self->seqnum = -1; + self->pts_or_dts = GST_CLOCK_TIME_NONE; + break; + default: + break; + } + + return ret; +} + +static GstFlowReturn +gst_rtp_passthrough_pay_chain (GstPad * pad, + GstObject * parent, GstBuffer * buffer) +{ + GstRtpPassthroughPay *self = GST_RTP_PASSTHROUGH_PAY (parent); + GstRTPBuffer rtp_buffer = GST_RTP_BUFFER_INIT; + guint pt, ssrc, seqnum, timestamp; + + if (!gst_rtp_buffer_map (buffer, GST_MAP_READWRITE, &rtp_buffer)) { + GST_ERROR_OBJECT (self, "Invalid RTP buffer"); + return gst_pad_push (self->srcpad, buffer); + } + + /* If the PROP_PT property is set we override the incoming packets payload + * type. If it is not, we will mirror the payload type. + * + */ + pt = gst_rtp_buffer_get_payload_type (&rtp_buffer); + if (self->pt_override && self->pt != PAYLOAD_TYPE_INVALID) { + gst_rtp_buffer_set_payload_type (&rtp_buffer, self->pt); + } else { + if (pt != self->pt) { + if (self->pt != PAYLOAD_TYPE_INVALID) { + GST_WARNING_OBJECT (self, "Payload type changed from %u to %u", + self->pt, pt); + } + self->pt = pt; + g_object_notify (G_OBJECT (self), "pt"); + } + } + + ssrc = gst_rtp_buffer_get_ssrc (&rtp_buffer); + if (self->ssrc_set && self->ssrc != ssrc) { + GST_WARNING_OBJECT (self, "SSRC changed from %u to %u", self->ssrc, ssrc); + } + self->ssrc = ssrc; + self->ssrc_set = TRUE; + + seqnum = gst_rtp_buffer_get_seq (&rtp_buffer); + self->seqnum = seqnum; + if (self->seqnum_offset == (guint) (-1)) { + self->seqnum_offset = seqnum; + g_object_notify (G_OBJECT (self), "seqnum-offset"); + } + + timestamp = gst_rtp_buffer_get_timestamp (&rtp_buffer); + self->timestamp = timestamp; + if (!self->timestamp_offset_set) { + self->timestamp_offset = timestamp; + self->timestamp_offset_set = TRUE; + g_object_notify (G_OBJECT (self), "timestamp-offset"); + } + + gst_rtp_buffer_unmap (&rtp_buffer); + + if (GST_BUFFER_PTS_IS_VALID (buffer)) + self->pts_or_dts = GST_BUFFER_PTS (buffer); + else if (GST_BUFFER_DTS_IS_VALID (buffer)) + self->pts_or_dts = GST_BUFFER_DTS (buffer); + + return gst_pad_push (self->srcpad, buffer); +} + +static gboolean +gst_rtp_passthrough_pay_sink_event (GstPad * pad, + GstObject * parent, GstEvent * event) +{ + GstRtpPassthroughPay *self = GST_RTP_PASSTHROUGH_PAY (parent); + gboolean ret = FALSE; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEGMENT:{ + gst_event_copy_segment (event, &self->segment); + + ret = gst_pad_event_default (pad, parent, event); + break; + } + case GST_EVENT_CAPS:{ + GstCaps *caps; + const GstStructure *s; + + gst_event_parse_caps (event, &caps); + gst_caps_replace (&self->caps, caps); + + s = gst_caps_get_structure (caps, 0); + + gst_structure_get_uint (s, "payload", &self->pt); + gst_structure_get_uint (s, "clock-rate", &self->clock_rate); + if (gst_structure_get_uint (s, "ssrc", &self->ssrc)) + self->ssrc_set = TRUE; + if (gst_structure_get_uint (s, "clock-base", &self->timestamp_offset)) + self->timestamp_offset_set = TRUE; + gst_structure_get_uint (s, "seqnum-base", &self->seqnum_offset); + + ret = gst_pad_event_default (pad, parent, event); + break; + } + default: + ret = gst_pad_event_default (pad, parent, event); + break; + } + + return ret; +} + +static void +gst_rtp_passthrough_set_payload_type (GstRtpPassthroughPay * self, guint pt) +{ + if (self->pt == pt) { + return; + } + + if (pt != PAYLOAD_TYPE_INVALID) { + GST_INFO_OBJECT (self, "Overriding payload type (%u)", pt); + self->pt_override = TRUE; + } else { + self->pt_override = FALSE; + } + + self->pt = pt; +} + +static GstStructure * +gst_rtp_passthrough_pay_create_stats (GstRtpPassthroughPay * self) +{ + GstClockTime running_time; + + running_time = gst_segment_to_running_time (&self->segment, GST_FORMAT_TIME, + self->pts_or_dts); + + return gst_structure_new ("application/x-rtp-payload-stats", "clock-rate", + G_TYPE_UINT, (guint) self->clock_rate, "running-time", G_TYPE_UINT64, + running_time, "seqnum", G_TYPE_UINT, (guint) self->seqnum, "timestamp", + G_TYPE_UINT, (guint) self->timestamp, "ssrc", G_TYPE_UINT, self->ssrc, + "pt", G_TYPE_UINT, self->pt, "seqnum-offset", G_TYPE_UINT, + (guint) self->seqnum_offset, "timestamp-offset", G_TYPE_UINT, + (guint) self->timestamp_offset, NULL); + + return NULL; +} diff --git a/subprojects/gst-plugins-good/gst/rtp/gstrtppassthroughpay.h b/subprojects/gst-plugins-good/gst/rtp/gstrtppassthroughpay.h new file mode 100644 index 0000000000..9191e35b89 --- /dev/null +++ b/subprojects/gst-plugins-good/gst/rtp/gstrtppassthroughpay.h @@ -0,0 +1,71 @@ +/* + * GStreamer + * + * Copyright (C) 2023 Sebastian Dröge + * Copyright (C) 2023 Jonas Danielsson + * + * gstrtppassthroughpay.h: + * + * 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. + */ + +#pragma once + +#include + +G_BEGIN_DECLS +#define GST_TYPE_RTP_PASSTHROUGH_PAY (gst_rtp_passthrough_pay_get_type()) +#define GST_RTP_PASSTHROUGH_PAY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_RTP_PASSTHROUGH_PAY, \ + GstRtpPassthroughPay)) +#define GST_RTP_PASSTHROUGH_PAY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_RTP_PASSTHROUGH_PAY, \ + GstRtpPassthroughPayClass)) +#define GST_IS_RTP_PASSTHROUGH_PAY(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_RTP_PASSTHROUGH_PAY)) +#define GST_IS_RTP_PASSTHROUGH_PAY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_RTP_PASSTHROUGH_PAY)) +#define GST_RTP_PASSTHROUGH_PAY_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), GST_TYPE_RTP_PASSTHROUGH_PAY, \ + GstRtpPassthroughPayClass)) +typedef struct _GstRtpPassthroughPay GstRtpPassthroughPay; +typedef struct _GstRtpPassthroughPayClass GstRtpPassthroughPayClass; + +struct _GstRtpPassthroughPayClass +{ + GstElementClass parent_class; +}; + +struct _GstRtpPassthroughPay +{ + GstElement parent; + + GstPad *sinkpad, *srcpad; + + GstCaps *caps; + GstSegment segment; + + guint clock_rate; + guint pt; + gboolean pt_override; + guint ssrc; + gboolean ssrc_set; + guint timestamp; + guint timestamp_offset; + gboolean timestamp_offset_set; + guint seqnum; + guint seqnum_offset; + GstClockTime pts_or_dts; +}; + +GType gst_rtp_passthrough_pay_get_type (void); + +G_END_DECLS diff --git a/subprojects/gst-plugins-good/gst/rtp/meson.build b/subprojects/gst-plugins-good/gst/rtp/meson.build index aa76523ec3..000b91d7ef 100644 --- a/subprojects/gst-plugins-good/gst/rtp/meson.build +++ b/subprojects/gst-plugins-good/gst/rtp/meson.build @@ -26,6 +26,7 @@ rtp_sources = [ 'gstrtpmpvpay.c', 'gstrtpopuspay.c', 'gstrtpopusdepay.c', + 'gstrtppassthroughpay.c', 'gstrtppcmadepay.c', 'gstrtppcmudepay.c', 'gstrtppcmupay.c', diff --git a/subprojects/gst-plugins-good/tests/check/elements/rtppassthrough.c b/subprojects/gst-plugins-good/tests/check/elements/rtppassthrough.c new file mode 100644 index 0000000000..ad9dcd52d8 --- /dev/null +++ b/subprojects/gst-plugins-good/tests/check/elements/rtppassthrough.c @@ -0,0 +1,122 @@ +/* GStreamer + * + * Copyright (C) 2023 Jonas Danielsson + * + * 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. + * + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#define buffer_from_array(a) gst_buffer_new_memdup (a, G_N_ELEMENTS (a)) + +static guint8 klv_data[] = { + 0x06, 0x0e, 0x2b, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x02, 0x00, 0x03, +}; + +GST_START_TEST (test_pay_depay_passthrough) +{ + GstHarness *h = + gst_harness_new_parse ("rtpklvpay ! rtppassthroughpay ! rtpklvdepay"); + GstBuffer *buf = buffer_from_array (klv_data); + + gst_harness_set_src_caps_str (h, "meta/x-klv,parsed=true"); + fail_unless_equals_int (GST_FLOW_OK, gst_harness_push (h, buf)); + + gst_buffer_unref (gst_harness_pull (h)); + gst_harness_teardown (h); +} + +GST_END_TEST +GST_START_TEST (test_read_properties) +{ + GstElement *passthrough_pay; + GstElement *klv_pay; + GstStructure *stats; + guint pt; + guint ssrc_klv; + guint ssrc_passthrough; + GstHarness *h = + gst_harness_new_parse ("rtpklvpay pt=97 ssrc=424242 ! rtppassthroughpay"); + GstBuffer *buf = buffer_from_array (klv_data); + + gst_harness_set_src_caps_str (h, "meta/x-klv,parsed=true"); + fail_unless_equals_int (GST_FLOW_OK, gst_harness_push (h, buf)); + + passthrough_pay = gst_harness_find_element (h, "rtppassthroughpay"); + g_object_get (G_OBJECT (passthrough_pay), "pt", &pt, NULL); + fail_unless_equals_uint64 (pt, 97U); + + klv_pay = gst_harness_find_element (h, "rtpklvpay"); + g_object_get (G_OBJECT (klv_pay), "ssrc", &ssrc_klv, NULL); + g_object_get (G_OBJECT (passthrough_pay), "stats", &stats, NULL); + gst_structure_get_uint (stats, "ssrc", &ssrc_passthrough); + fail_unless_equals_uint64 (424242U, ssrc_passthrough); + + gst_structure_free (stats); + gst_object_unref (klv_pay); + gst_object_unref (passthrough_pay); + gst_buffer_unref (gst_harness_pull (h)); + gst_harness_teardown (h); +} + +GST_END_TEST; + +GST_START_TEST (test_override_payload_type) +{ + GstElement *passthrough_pay; + guint pt; + GstHarness *h = + gst_harness_new_parse ("rtpklvpay pt=97 ! rtppassthroughpay pt=98"); + GstBuffer *buf = buffer_from_array (klv_data); + GstRTPBuffer rtp_buf = GST_RTP_BUFFER_INIT; + + gst_harness_set_src_caps_str (h, "meta/x-klv,parsed=true"); + fail_unless_equals_int (GST_FLOW_OK, gst_harness_push (h, buf)); + + passthrough_pay = gst_harness_find_element (h, "rtppassthroughpay"); + g_object_get (G_OBJECT (passthrough_pay), "pt", &pt, NULL); + fail_unless_equals_uint64 (pt, 98U); + + buf = gst_harness_pull (h); + gst_rtp_buffer_map (buf, GST_MAP_READ, &rtp_buf); + fail_unless_equals_uint64 (gst_rtp_buffer_get_payload_type (&rtp_buf), 98U); + gst_rtp_buffer_unmap (&rtp_buf); + + gst_object_unref (passthrough_pay); + gst_buffer_unref (buf); + gst_harness_teardown (h); +} + +GST_END_TEST; + +static Suite * +rtppassthrough_suite (void) +{ + Suite *s = suite_create ("rtppassthrough"); + TCase *tc_chain; + + suite_add_tcase (s, (tc_chain = tcase_create ("rtppassthrough"))); + tcase_add_test (tc_chain, test_pay_depay_passthrough); + tcase_add_test (tc_chain, test_read_properties); + tcase_add_test (tc_chain, test_override_payload_type); + + return s; +} + +GST_CHECK_MAIN (rtppassthrough); diff --git a/subprojects/gst-plugins-good/tests/check/meson.build b/subprojects/gst-plugins-good/tests/check/meson.build index 02a5cee874..3a4b5c99a6 100644 --- a/subprojects/gst-plugins-good/tests/check/meson.build +++ b/subprojects/gst-plugins-good/tests/check/meson.build @@ -92,6 +92,7 @@ if not get_option('rtp').disabled() and not get_option('rtpmanager').disabled() [ 'elements/rtph264' ], [ 'elements/rtph265' ], [ 'elements/rtpopus' ], + [ 'elements/rtppassthrough' ], [ 'elements/rtpvp8' ], [ 'elements/rtpvp9' ], [ 'elements/rtpbin' ],