gstreamer/gst/rtsp-server/rtsp-sdp.c
Sebastian Dröge 9fab555cc5 rtsp-server: Implement clock signalling according to RFC7273
For NTP and PTP clocks we signal the actual clock that is used and signal
the direct media clock offset.

For all other clocks we at least signal that it's the local sender clock.

This allows receivers to know which clock was used to generate the media and
its RTP timestamps. Receivers can then implement network synchronization,
either absolute or at least relative by getting the sender clock rate directly
via NTP/PTP instead of estimating it from RTP timestamps and packet receive
times.

https://bugzilla.gnome.org/show_bug.cgi?id=760005
2016-04-03 11:22:31 +03:00

431 lines
12 KiB
C

/* GStreamer
* Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
*
* 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.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* SECTION:rtsp-sdp
* @short_description: Make SDP messages
* @see_also: #GstRTSPMedia
*
* Last reviewed on 2013-07-11 (1.0.0)
*/
#include <string.h>
#include <gst/net/net.h>
#include <gst/sdp/gstmikey.h>
#include "rtsp-sdp.h"
static gboolean
get_info_from_tags (GstPad * pad, GstEvent ** event, gpointer user_data)
{
GstSDPMedia *media = (GstSDPMedia *) user_data;
if (GST_EVENT_TYPE (*event) == GST_EVENT_TAG) {
GstTagList *tags;
guint bitrate = 0;
gst_event_parse_tag (*event, &tags);
if (gst_tag_list_get_scope (tags) != GST_TAG_SCOPE_STREAM)
return TRUE;
if (!gst_tag_list_get_uint (tags, GST_TAG_MAXIMUM_BITRATE,
&bitrate) || bitrate == 0)
if (!gst_tag_list_get_uint (tags, GST_TAG_BITRATE, &bitrate) ||
bitrate == 0)
return TRUE;
/* set bandwidth (kbits/s) */
gst_sdp_media_add_bandwidth (media, GST_SDP_BWTYPE_AS, bitrate / 1000);
return FALSE;
}
return TRUE;
}
static void
update_sdp_from_tags (GstRTSPStream * stream, GstSDPMedia * stream_media)
{
GstPad *src_pad;
src_pad = gst_rtsp_stream_get_srcpad (stream);
gst_pad_sticky_events_foreach (src_pad, get_info_from_tags, stream_media);
gst_object_unref (src_pad);
}
static void
make_media (GstSDPMessage * sdp, GstSDPInfo * info,
GstRTSPStream * stream, GstCaps * caps, GstRTSPProfile profile)
{
GstSDPMedia *smedia;
gchar *tmp;
GstRTSPLowerTrans ltrans;
GSocketFamily family;
const gchar *addrtype, *proto;
gchar *address;
guint ttl;
GstClockTime rtx_time;
gchar *base64;
guint32 ssrc;
GstMIKEYMessage *mikey_msg;
gst_sdp_media_new (&smedia);
if (gst_sdp_media_set_media_from_caps (caps, smedia) != GST_SDP_OK) {
goto error;
}
gst_sdp_media_set_port_info (smedia, 0, 1);
switch (profile) {
case GST_RTSP_PROFILE_AVP:
proto = "RTP/AVP";
break;
case GST_RTSP_PROFILE_AVPF:
proto = "RTP/AVPF";
break;
case GST_RTSP_PROFILE_SAVP:
proto = "RTP/SAVP";
break;
case GST_RTSP_PROFILE_SAVPF:
proto = "RTP/SAVPF";
break;
default:
proto = "udp";
break;
}
gst_sdp_media_set_proto (smedia, proto);
if (info->is_ipv6) {
addrtype = "IP6";
family = G_SOCKET_FAMILY_IPV6;
} else {
addrtype = "IP4";
family = G_SOCKET_FAMILY_IPV4;
}
ltrans = gst_rtsp_stream_get_protocols (stream);
if (ltrans == GST_RTSP_LOWER_TRANS_UDP_MCAST) {
GstRTSPAddress *addr;
addr = gst_rtsp_stream_get_multicast_address (stream, family);
if (addr == NULL)
goto no_multicast;
address = g_strdup (addr->address);
ttl = addr->ttl;
gst_rtsp_address_free (addr);
} else {
ttl = 16;
if (info->is_ipv6)
address = g_strdup ("::");
else
address = g_strdup ("0.0.0.0");
}
/* for the c= line */
gst_sdp_media_add_connection (smedia, "IN", addrtype, address, ttl, 1);
g_free (address);
/* the config uri */
tmp = gst_rtsp_stream_get_control (stream);
gst_sdp_media_add_attribute (smedia, "control", tmp);
g_free (tmp);
/* check for srtp */
mikey_msg = gst_mikey_message_new_from_caps (caps);
if (mikey_msg) {
gst_rtsp_stream_get_ssrc (stream, &ssrc);
/* add policy '0' for our SSRC */
gst_mikey_message_add_cs_srtp (mikey_msg, 0, ssrc, 0);
base64 = gst_mikey_message_base64_encode (mikey_msg);
if (base64) {
tmp = g_strdup_printf ("mikey %s", base64);
g_free (base64);
gst_sdp_media_add_attribute (smedia, "key-mgmt", tmp);
g_free (tmp);
}
gst_mikey_message_unref (mikey_msg);
}
/* RFC 7273 clock signalling */
{
GstBin *joined_bin = gst_rtsp_stream_get_joined_bin (stream);
GstClock *clock = gst_element_get_clock (GST_ELEMENT_CAST (joined_bin));
gchar *ts_refclk = NULL;
gchar *mediaclk = NULL;
guint rtptime, clock_rate;
GstClockTime running_time, base_time, clock_time;
GstRTSPPublishClockMode publish_clock_mode =
gst_rtsp_stream_get_publish_clock_mode (stream);
gst_rtsp_stream_get_rtpinfo (stream, &rtptime, NULL, &clock_rate,
&running_time);
base_time = gst_element_get_base_time (GST_ELEMENT_CAST (joined_bin));
g_assert (base_time != GST_CLOCK_TIME_NONE);
clock_time = running_time + base_time;
if (publish_clock_mode != GST_RTSP_PUBLISH_CLOCK_MODE_NONE && clock) {
if (GST_IS_NTP_CLOCK (clock) || GST_IS_PTP_CLOCK (clock)) {
if (publish_clock_mode == GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET) {
guint32 mediaclk_offset;
/* Calculate RTP time at the clock's epoch. That's the direct offset */
clock_time =
gst_util_uint64_scale (clock_time, clock_rate, GST_SECOND);
clock_time &= 0xffffffff;
mediaclk_offset =
G_GUINT64_CONSTANT (0xffffffff) + rtptime - clock_time;
mediaclk = g_strdup_printf ("direct=%u", (guint32) mediaclk_offset);
}
if (GST_IS_NTP_CLOCK (clock)) {
gchar *ntp_address;
guint ntp_port;
g_object_get (clock, "address", &ntp_address, "port", &ntp_port,
NULL);
if (ntp_port == 123)
ts_refclk = g_strdup_printf ("ntp=%s", ntp_address);
else
ts_refclk = g_strdup_printf ("ntp=%s:%u", ntp_address, ntp_port);
g_free (ntp_address);
} else {
guint64 ptp_clock_id;
guint ptp_domain;
g_object_get (clock, "grandmaster-clock-id", &ptp_clock_id, "domain",
&ptp_domain, NULL);
if (ptp_domain != 0)
ts_refclk =
g_strdup_printf
("ptp=IEEE1588-2008:%02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X:%u",
(guint) (ptp_clock_id >> 56) & 0xff,
(guint) (ptp_clock_id >> 48) & 0xff,
(guint) (ptp_clock_id >> 40) & 0xff,
(guint) (ptp_clock_id >> 32) & 0xff,
(guint) (ptp_clock_id >> 24) & 0xff,
(guint) (ptp_clock_id >> 16) & 0xff,
(guint) (ptp_clock_id >> 8) & 0xff,
(guint) (ptp_clock_id >> 0) & 0xff, ptp_domain);
else
ts_refclk =
g_strdup_printf
("ptp=IEEE1588-2008:%02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X",
(guint) (ptp_clock_id >> 56) & 0xff,
(guint) (ptp_clock_id >> 48) & 0xff,
(guint) (ptp_clock_id >> 40) & 0xff,
(guint) (ptp_clock_id >> 32) & 0xff,
(guint) (ptp_clock_id >> 24) & 0xff,
(guint) (ptp_clock_id >> 16) & 0xff,
(guint) (ptp_clock_id >> 8) & 0xff,
(guint) (ptp_clock_id >> 0) & 0xff);
}
}
}
if (clock)
gst_object_unref (clock);
if (!ts_refclk)
ts_refclk = g_strdup ("local");
if (!mediaclk)
mediaclk = g_strdup ("sender");
gst_sdp_media_add_attribute (smedia, "ts-refclk", ts_refclk);
gst_sdp_media_add_attribute (smedia, "mediaclk", mediaclk);
g_free (ts_refclk);
g_free (mediaclk);
gst_object_unref (joined_bin);
}
update_sdp_from_tags (stream, smedia);
if ((profile == GST_RTSP_PROFILE_AVPF || profile == GST_RTSP_PROFILE_SAVPF)
&& (rtx_time = gst_rtsp_stream_get_retransmission_time (stream))) {
/* ssrc multiplexed retransmit functionality */
guint rtx_pt = gst_rtsp_stream_get_retransmission_pt (stream);
if (rtx_pt == 0) {
g_warning ("failed to find an available dynamic payload type. "
"Not adding retransmission");
} else {
gchar *tmp;
GstStructure *s;
gint caps_pt, caps_rate;
s = gst_caps_get_structure (caps, 0);
if (s == NULL)
goto error;
/* get payload type and clock rate */
gst_structure_get_int (s, "payload", &caps_pt);
gst_structure_get_int (s, "clock-rate", &caps_rate);
tmp = g_strdup_printf ("%d", rtx_pt);
gst_sdp_media_add_format (smedia, tmp);
g_free (tmp);
tmp = g_strdup_printf ("%d rtx/%d", rtx_pt, caps_rate);
gst_sdp_media_add_attribute (smedia, "rtpmap", tmp);
g_free (tmp);
tmp =
g_strdup_printf ("%d apt=%d;rtx-time=%" G_GINT64_FORMAT, rtx_pt,
caps_pt, GST_TIME_AS_MSECONDS (rtx_time));
gst_sdp_media_add_attribute (smedia, "fmtp", tmp);
g_free (tmp);
}
}
gst_sdp_message_add_media (sdp, smedia);
gst_sdp_media_free (smedia);
return;
/* ERRORS */
no_multicast:
{
gst_sdp_media_free (smedia);
g_warning ("ignoring stream %d without multicast address",
gst_rtsp_stream_get_index (stream));
return;
}
error:
{
gst_sdp_media_free (smedia);
g_warning ("ignoring stream %d", gst_rtsp_stream_get_index (stream));
return;
}
}
/**
* gst_rtsp_sdp_from_media:
* @sdp: a #GstSDPMessage
* @info: (transfer none): a #GstSDPInfo
* @media: (transfer none): a #GstRTSPMedia
*
* Add @media specific info to @sdp. @info is used to configure the connection
* information in the SDP.
*
* Returns: TRUE on success.
*/
gboolean
gst_rtsp_sdp_from_media (GstSDPMessage * sdp, GstSDPInfo * info,
GstRTSPMedia * media)
{
guint i, n_streams;
gchar *rangestr;
n_streams = gst_rtsp_media_n_streams (media);
rangestr = gst_rtsp_media_get_range_string (media, FALSE, GST_RTSP_RANGE_NPT);
if (rangestr == NULL)
goto not_prepared;
gst_sdp_message_add_attribute (sdp, "range", rangestr);
g_free (rangestr);
for (i = 0; i < n_streams; i++) {
GstRTSPStream *stream;
stream = gst_rtsp_media_get_stream (media, i);
gst_rtsp_sdp_from_stream (sdp, info, stream);
}
{
GstNetTimeProvider *provider;
if ((provider =
gst_rtsp_media_get_time_provider (media, info->server_ip, 0))) {
GstClock *clock;
gchar *address, *str;
gint port;
g_object_get (provider, "clock", &clock, "address", &address, "port",
&port, NULL);
str = g_strdup_printf ("GstNetTimeProvider %s %s:%d %" G_GUINT64_FORMAT,
g_type_name (G_TYPE_FROM_INSTANCE (clock)), address, port,
gst_clock_get_time (clock));
gst_sdp_message_add_attribute (sdp, "x-gst-clock", str);
g_free (str);
gst_object_unref (clock);
g_free (address);
gst_object_unref (provider);
}
}
return TRUE;
/* ERRORS */
not_prepared:
{
GST_ERROR ("media %p is not prepared", media);
return FALSE;
}
}
/**
* gst_rtsp_sdp_from_stream:
* @sdp: a #GstSDPMessage
* @info: (transfer none): a #GstSDPInfo
* @stream: (transfer none): a #GstRTSPStream
*
* Add info from @stream to @sdp.
*
*/
void
gst_rtsp_sdp_from_stream (GstSDPMessage * sdp, GstSDPInfo * info,
GstRTSPStream * stream)
{
GstCaps *caps;
GstRTSPProfile profiles;
guint mask;
caps = gst_rtsp_stream_get_caps (stream);
if (caps == NULL) {
g_warning ("ignoring stream without caps");
return;
}
/* make a new media for each profile */
profiles = gst_rtsp_stream_get_profiles (stream);
mask = 1;
while (profiles >= mask) {
GstRTSPProfile prof = profiles & mask;
if (prof)
make_media (sdp, info, stream, caps, prof);
mask <<= 1;
}
gst_caps_unref (caps);
}