/* * 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 ]")); #define GST_RTPPASSTHROUGHPAY_RETIMESTAMP_MODE_TYPE (gst_rtp_passthrough_pay_retimestamp_mode_get_type()) static GType gst_rtp_passthrough_pay_retimestamp_mode_get_type (void) { static GType mode_type = 0; static const GEnumValue enum_values[] = { {GST_RTPPASSTHROUGHPAY_RETIMESTAMP_MODE_DISABLED, "No retimestamping", "disabled"}, {GST_RTPPASSTHROUGHPAY_RETIMESTAMP_MODE_ENABLED, "Retimestamp based on PTS of each buffer", "enabled"}, {0, NULL, NULL}, }; if (!mode_type) mode_type = g_enum_register_static ("GstRtpPassthroughPayRetimestampMode", enum_values); return mode_type; } enum { PROP_0, PROP_PT, PROP_MTU, PROP_STATS, PROP_SEQNUM, PROP_SEQNUM_OFFSET, PROP_TIMESTAMP, PROP_TIMESTAMP_OFFSET, PROP_RETIMESTAMP_MODE, }; 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)); /** * GstRtpPassthroughPay:retimestamp-mode: * * If enabled, this mode will cause RTP timestamps to be re-generated * based on the PTS of each processed buffer. * * Since: 1.26 */ g_object_class_install_property (gobject_class, PROP_RETIMESTAMP_MODE, g_param_spec_enum ("retimestamp-mode", "Retimestamp mode", "RTP timestamp regeneration mode", GST_RTPPASSTHROUGHPAY_RETIMESTAMP_MODE_TYPE, GST_RTPPASSTHROUGHPAY_RETIMESTAMP_MODE_DISABLED, G_PARAM_READWRITE | 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"); gst_type_mark_as_plugin_api (GST_RTPPASSTHROUGHPAY_RETIMESTAMP_MODE_TYPE, 0); } 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; case PROP_RETIMESTAMP_MODE: g_value_set_enum (value, self->retimestamp_mode); 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; case PROP_RETIMESTAMP_MODE: self->retimestamp_mode = g_value_get_enum (value); 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 void gst_rtppassthrough_pay_handle_retimestamp (GstRtpPassthroughPay * self, GstRTPBuffer * rtp_buffer) { GstClockTime buf_pts = GST_BUFFER_PTS (rtp_buffer->buffer); guint32 old_timestamp = gst_rtp_buffer_get_timestamp (rtp_buffer); guint32 new_timestamp; /* Code below is mostly borrowed from gst_rtp_base_payload_prepare_push() */ if (GST_CLOCK_TIME_IS_VALID (buf_pts)) { guint64 rtime_hz, rtime_ns; rtime_ns = gst_segment_to_running_time (&self->segment, GST_FORMAT_TIME, buf_pts); if (!GST_CLOCK_TIME_IS_VALID (rtime_ns)) { GST_LOG_OBJECT (self, "Clipped pts, using base RTP timestamp"); rtime_hz = 0; } else { GST_LOG_OBJECT (self, "Using running_time %" GST_TIME_FORMAT " for RTP timestamp", GST_TIME_ARGS (rtime_ns)); rtime_hz = gst_util_uint64_scale_int (rtime_ns, self->clock_rate, GST_SECOND); } /* add running_time in clock-rate units to the base timestamp */ new_timestamp = self->timestamp_offset + rtime_hz; } else { GST_LOG_OBJECT (self, "Using previous RTP timestamp %" G_GUINT32_FORMAT, self->timestamp); /* no timestamp to convert, take previous timestamp */ new_timestamp = self->timestamp; } GST_LOG_OBJECT (self, "Retimestamped from %u to %u", old_timestamp, new_timestamp); gst_rtp_buffer_set_timestamp (rtp_buffer, new_timestamp); } 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"); } if (self->retimestamp_mode != GST_RTPPASSTHROUGHPAY_RETIMESTAMP_MODE_DISABLED) { gst_rtppassthrough_pay_handle_retimestamp (self, &rtp_buffer); } 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); if (!self->pt_override && !gst_structure_get_int (s, "payload", &self->pt)) { GST_WARNING_OBJECT (self, "Caps are missing payload type!"); } if (!gst_structure_get_int (s, "clock-rate", &self->clock_rate)) GST_WARNING_OBJECT (self, "Caps are missing 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; if (self->segment.format == GST_FORMAT_UNDEFINED) { running_time = 0; } else { 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_INT, 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_INT, self->pt, "seqnum-offset", G_TYPE_UINT, (guint) self->seqnum_offset, "timestamp-offset", G_TYPE_UINT, (guint) self->timestamp_offset, NULL); return NULL; }