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
This commit is contained in:
Sebastian Dröge 2015-12-30 18:39:05 +02:00
parent b63a6f029f
commit 9fab555cc5
8 changed files with 363 additions and 0 deletions

View file

@ -1042,10 +1042,12 @@ handle_pause_request (GstRTSPClient * client, GstRTSPContext * ctx)
GstRTSPSession *session;
GstRTSPClientClass *klass;
GstRTSPSessionMedia *sessmedia;
GstRTSPMedia *media;
GstRTSPStatusCode code;
GstRTSPState rtspstate;
gchar *path;
gint matched;
guint i, n;
if (!(session = ctx->session))
goto no_session;
@ -1066,6 +1068,16 @@ handle_pause_request (GstRTSPClient * client, GstRTSPContext * ctx)
g_free (path);
media = gst_rtsp_session_media_get_media (sessmedia);
n = gst_rtsp_media_n_streams (media);
for (i = 0; i < n; i++) {
GstRTSPStream *stream = gst_rtsp_media_get_stream (media, i);
if (gst_rtsp_stream_get_publish_clock_mode (stream) ==
GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET)
goto not_supported;
}
ctx->sessmedia = sessmedia;
rtspstate = gst_rtsp_session_media_get_rtsp_state (sessmedia);
@ -1126,6 +1138,12 @@ invalid_state:
ctx);
return FALSE;
}
not_supported:
{
GST_ERROR ("client %p: pausing not supported", client);
send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
return FALSE;
}
}
/* convert @url and @path to a URL used as a content base for the factory

View file

@ -72,6 +72,8 @@ struct _GstRTSPMediaFactoryPrivate
GType media_gtype;
GstClock *clock;
GstRTSPPublishClockMode publish_clock_mode;
};
#define DEFAULT_LAUNCH NULL
@ -261,6 +263,7 @@ gst_rtsp_media_factory_init (GstRTSPMediaFactory * factory)
priv->latency = DEFAULT_LATENCY;
priv->transport_mode = DEFAULT_TRANSPORT_MODE;
priv->stop_on_disconnect = DEFAULT_STOP_ON_DISCONNECT;
priv->publish_clock_mode = GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK;
g_mutex_init (&priv->lock);
g_mutex_init (&priv->medias_lock);
@ -1335,6 +1338,51 @@ gst_rtsp_media_factory_get_clock (GstRTSPMediaFactory * factory)
return ret;
}
/**
* gst_rtsp_media_factory_set_publish_clock_mode:
* @factory: a #GstRTSPMediaFactory
* @mode: the clock publish mode
*
* Sets if and how the media clock should be published according to RFC7273.
*
* Since: 1.8
*/
void
gst_rtsp_media_factory_set_publish_clock_mode (GstRTSPMediaFactory * factory,
GstRTSPPublishClockMode mode)
{
GstRTSPMediaFactoryPrivate *priv;
GST_RTSP_MEDIA_FACTORY_LOCK (factory);
priv = factory->priv;
priv->publish_clock_mode = mode;
GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
}
/**
* gst_rtsp_media_factory_get_publish_clock_mode:
* @factory: a #GstRTSPMediaFactory
*
* Gets if and how the media clock should be published according to RFC7273.
*
* Returns: The GstRTSPPublishClockMode
*
* Since: 1.8
*/
GstRTSPPublishClockMode
gst_rtsp_media_factory_get_publish_clock_mode (GstRTSPMediaFactory * factory)
{
GstRTSPMediaFactoryPrivate *priv;
GstRTSPPublishClockMode ret;
GST_RTSP_MEDIA_FACTORY_LOCK (factory);
priv = factory->priv;
ret = priv->publish_clock_mode;
GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
return ret;
}
static gchar *
default_gen_key (GstRTSPMediaFactory * factory, const GstRTSPUrl * url)
{
@ -1480,6 +1528,7 @@ default_configure (GstRTSPMediaFactory * factory, GstRTSPMedia * media)
GstRTSPTransportMode transport_mode;
GstClock *clock;
gchar *multicast_iface;
GstRTSPPublishClockMode publish_clock_mode;
/* configure the sharedness */
GST_RTSP_MEDIA_FACTORY_LOCK (factory);
@ -1494,6 +1543,7 @@ default_configure (GstRTSPMediaFactory * factory, GstRTSPMedia * media)
transport_mode = priv->transport_mode;
stop_on_disconnect = priv->stop_on_disconnect;
clock = priv->clock ? gst_object_ref (priv->clock) : NULL;
publish_clock_mode = priv->publish_clock_mode;
GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
gst_rtsp_media_set_suspend_mode (media, suspend_mode);
@ -1506,6 +1556,7 @@ default_configure (GstRTSPMediaFactory * factory, GstRTSPMedia * media)
gst_rtsp_media_set_latency (media, latency);
gst_rtsp_media_set_transport_mode (media, transport_mode);
gst_rtsp_media_set_stop_on_disconnect (media, stop_on_disconnect);
gst_rtsp_media_set_publish_clock_mode (media, publish_clock_mode);
if (clock) {
gst_rtsp_media_set_clock (media, clock);

View file

@ -168,6 +168,9 @@ void gst_rtsp_media_factory_set_clock (GstRTSPMediaFacto
GstClock * clock);
GstClock * gst_rtsp_media_factory_get_clock (GstRTSPMediaFactory *factory);
void gst_rtsp_media_factory_set_publish_clock_mode (GstRTSPMediaFactory * factory, GstRTSPPublishClockMode mode);
GstRTSPPublishClockMode gst_rtsp_media_factory_get_publish_clock_mode (GstRTSPMediaFactory * factory);
/* creating the media from the factory and a url */
GstRTSPMedia * gst_rtsp_media_factory_construct (GstRTSPMediaFactory *factory,
const GstRTSPUrl *url);

View file

@ -141,6 +141,7 @@ struct _GstRTSPMediaPrivate
GstClockTime rtx_time; /* protected by lock */
guint latency; /* protected by lock */
GstClock *clock; /* protected by lock */
GstRTSPPublishClockMode publish_clock_mode;
};
#define DEFAULT_SHARED FALSE
@ -264,6 +265,29 @@ gst_rtsp_transport_mode_get_type (void)
return (GType) id;
}
GType
gst_rtsp_publish_clock_mode_get_type (void)
{
static gsize id = 0;
static const GEnumValue values[] = {
{C_ENUM (GST_RTSP_PUBLISH_CLOCK_MODE_NONE),
"GST_RTSP_PUBLISH_CLOCK_MODE_NONE", "none"},
{C_ENUM (GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK),
"GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK",
"clock"},
{C_ENUM (GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET),
"GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET",
"clock-and-offset"},
{0, NULL, NULL}
};
if (g_once_init_enter (&id)) {
GType tmp = g_enum_register_static ("GstRTSPPublishClockMode", values);
g_once_init_leave (&id, tmp);
}
return (GType) id;
}
G_DEFINE_TYPE (GstRTSPMedia, gst_rtsp_media, G_TYPE_OBJECT);
static void
@ -415,6 +439,7 @@ gst_rtsp_media_init (GstRTSPMedia * media)
priv->time_provider = DEFAULT_TIME_PROVIDER;
priv->transport_mode = DEFAULT_TRANSPORT_MODE;
priv->stop_on_disconnect = DEFAULT_STOP_ON_DISCONNECT;
priv->publish_clock_mode = GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK;
}
static void
@ -1438,6 +1463,59 @@ gst_rtsp_media_set_clock (GstRTSPMedia * media, GstClock * clock)
g_mutex_unlock (&priv->lock);
}
/**
* gst_rtsp_media_set_publish_clock_mode:
* @media: a #GstRTSPMedia
* @mode: the clock publish mode
*
* Sets if and how the media clock should be published according to RFC7273.
*
* Since: 1.8
*/
void
gst_rtsp_media_set_publish_clock_mode (GstRTSPMedia * media,
GstRTSPPublishClockMode mode)
{
GstRTSPMediaPrivate *priv;
guint i, n;
priv = media->priv;
g_mutex_lock (&priv->lock);
priv->publish_clock_mode = mode;
n = priv->streams->len;
for (i = 0; i < n; i++) {
GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
gst_rtsp_stream_set_publish_clock_mode (stream, mode);
}
g_mutex_unlock (&priv->lock);
}
/**
* gst_rtsp_media_get_publish_clock_mode:
* @factory: a #GstRTSPMedia
*
* Gets if and how the media clock should be published according to RFC7273.
*
* Returns: The GstRTSPPublishClockMode
*
* Since: 1.8
*/
GstRTSPPublishClockMode
gst_rtsp_media_get_publish_clock_mode (GstRTSPMedia * media)
{
GstRTSPMediaPrivate *priv;
GstRTSPPublishClockMode ret;
priv = media->priv;
g_mutex_lock (&priv->lock);
ret = priv->publish_clock_mode;
g_mutex_unlock (&priv->lock);
return ret;
}
/**
* gst_rtsp_media_set_address_pool:
* @media: a #GstRTSPMedia
@ -1744,6 +1822,7 @@ gst_rtsp_media_create_stream (GstRTSPMedia * media, GstElement * payloader,
gst_rtsp_stream_set_protocols (stream, priv->protocols);
gst_rtsp_stream_set_retransmission_time (stream, priv->rtx_time);
gst_rtsp_stream_set_buffer_size (stream, priv->buffer_size);
gst_rtsp_stream_set_publish_clock_mode (stream, priv->publish_clock_mode);
g_ptr_array_add (priv->streams, stream);
@ -2072,6 +2151,18 @@ gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range)
/* TODO: Seeking for RECORD? */
priv->seekable = FALSE;
} else {
guint i, n = priv->streams->len;
for (i = 0; i < n; i++) {
GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
if (gst_rtsp_stream_get_publish_clock_mode (stream) ==
GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET) {
priv->seekable = FALSE;
goto not_seekable;
}
}
query = gst_query_new_seeking (GST_FORMAT_TIME);
if (gst_element_query (priv->pipeline, query)) {
GstFormat format;
@ -2081,6 +2172,7 @@ gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range)
gst_query_parse_seeking (query, &format, &seekable, &start, &end);
priv->seekable = seekable;
}
gst_query_unref (query);
}

View file

@ -88,12 +88,29 @@ typedef enum {
GST_RTSP_TRANSPORT_MODE_RECORD = 2,
} GstRTSPTransportMode;
/**
* GstRTSPPublishClockMode:
* @GST_RTSP_PUBLISH_CLOCK_MODE_NONE: Publish nothing
* @GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK: Publish the clock but not the offset
* @GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET: Publish the clock and offset
*
* Whether the clock and possibly RTP/clock offset should be published according to RFC7273.
*/
typedef enum {
GST_RTSP_PUBLISH_CLOCK_MODE_NONE,
GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK,
GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET
} GstRTSPPublishClockMode;
#define GST_TYPE_RTSP_TRANSPORT_MODE (gst_rtsp_transport_mode_get_type())
GType gst_rtsp_transport_mode_get_type (void);
#define GST_TYPE_RTSP_SUSPEND_MODE (gst_rtsp_suspend_mode_get_type())
GType gst_rtsp_suspend_mode_get_type (void);
#define GST_TYPE_RTSP_PUBLISH_CLOCK_MODE (gst_rtsp_publish_clock_mode_get_type())
GType gst_rtsp_publish_clock_mode_get_type (void);
#include "rtsp-stream.h"
#include "rtsp-thread-pool.h"
#include "rtsp-permissions.h"
@ -226,6 +243,10 @@ GstNetTimeProvider * gst_rtsp_media_get_time_provider (GstRTSPMedia *media,
void gst_rtsp_media_set_clock (GstRTSPMedia *media, GstClock * clock);
void gst_rtsp_media_set_publish_clock_mode (GstRTSPMedia * media, GstRTSPPublishClockMode mode);
GstRTSPPublishClockMode gst_rtsp_media_get_publish_clock_mode (GstRTSPMedia * media);
/* prepare the media for playback */
gboolean gst_rtsp_media_prepare (GstRTSPMedia *media, GstRTSPThread *thread);
gboolean gst_rtsp_media_unprepare (GstRTSPMedia *media);

View file

@ -26,6 +26,7 @@
#include <string.h>
#include <gst/net/net.h>
#include <gst/sdp/gstmikey.h>
#include "rtsp-sdp.h"
@ -169,6 +170,100 @@ make_media (GstSDPMessage * sdp, GstSDPInfo * info,
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)

View file

@ -71,6 +71,7 @@ struct _GstRTSPStreamPrivate
GstElement *payloader;
guint buffer_size;
gboolean is_joined;
GstBin *joined_bin;
/* TRUE if this stream is running on
* the client side of an RTSP link (for RECORD) */
@ -164,6 +165,8 @@ struct _GstRTSPStreamPrivate
/* pt->caps map for RECORD streams */
GHashTable *ptmap;
GstRTSPPublishClockMode publish_clock_mode;
};
#define DEFAULT_CONTROL NULL
@ -259,6 +262,7 @@ gst_rtsp_stream_init (GstRTSPStream * stream)
priv->control = g_strdup (DEFAULT_CONTROL);
priv->profiles = DEFAULT_PROFILES;
priv->protocols = DEFAULT_PROTOCOLS;
priv->publish_clock_mode = GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK;
g_mutex_init (&priv->lock);
@ -2281,6 +2285,51 @@ gst_rtsp_stream_set_pt_map (GstRTSPStream * stream, guint pt, GstCaps * caps)
g_mutex_unlock (&priv->lock);
}
/**
* gst_rtsp_stream_set_publish_clock_mode:
* @stream: a #GstRTSPStream
* @mode: the clock publish mode
*
* Sets if and how the stream clock should be published according to RFC7273.
*
* Since: 1.8
*/
void
gst_rtsp_stream_set_publish_clock_mode (GstRTSPStream * stream,
GstRTSPPublishClockMode mode)
{
GstRTSPStreamPrivate *priv;
priv = stream->priv;
g_mutex_lock (&priv->lock);
priv->publish_clock_mode = mode;
g_mutex_unlock (&priv->lock);
}
/**
* gst_rtsp_stream_get_publish_clock_mode:
* @factory: a #GstRTSPStream
*
* Gets if and how the stream clock should be published according to RFC7273.
*
* Returns: The GstRTSPPublishClockMode
*
* Since: 1.8
*/
GstRTSPPublishClockMode
gst_rtsp_stream_get_publish_clock_mode (GstRTSPStream * stream)
{
GstRTSPStreamPrivate *priv;
GstRTSPPublishClockMode ret;
priv = stream->priv;
g_mutex_lock (&priv->lock);
ret = priv->publish_clock_mode;
g_mutex_unlock (&priv->lock);
return ret;
}
static GstCaps *
request_pt_map (GstElement * rtpbin, guint session, guint pt,
GstRTSPStream * stream)
@ -2730,6 +2779,7 @@ gst_rtsp_stream_join_bin (GstRTSPStream * stream, GstBin * bin,
(GCallback) caps_notify, stream);
}
priv->joined_bin = bin;
priv->is_joined = TRUE;
g_mutex_unlock (&priv->lock);
@ -2839,6 +2889,8 @@ gst_rtsp_stream_leave_bin (GstRTSPStream * stream, GstBin * bin,
if (!priv->is_joined)
goto was_not_joined;
priv->joined_bin = NULL;
/* all transports must be removed by now */
if (priv->transports != NULL)
goto transports_not_removed;
@ -3013,6 +3065,31 @@ transports_not_removed:
}
}
/**
* gst_rtsp_stream_get_joined_bin:
* @stream: a #GstRTSPStream
*
* Get the previous joined bin with gst_rtsp_stream_join_bin() or NULL.
*
* Return: (transfer full): the joined bin or NULL.
*/
GstBin *
gst_rtsp_stream_get_joined_bin (GstRTSPStream * stream)
{
GstRTSPStreamPrivate *priv;
GstBin *bin = NULL;
g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
priv = stream->priv;
g_mutex_lock (&priv->lock);
bin = priv->joined_bin ? gst_object_ref (priv->joined_bin) : NULL;
g_mutex_unlock (&priv->lock);
return bin;
}
/**
* gst_rtsp_stream_get_rtpinfo:
* @stream: a #GstRTSPStream

View file

@ -43,6 +43,7 @@ typedef struct _GstRTSPStreamPrivate GstRTSPStreamPrivate;
#include "rtsp-stream-transport.h"
#include "rtsp-address-pool.h"
#include "rtsp-session.h"
#include "rtsp-media.h"
/**
* GstRTSPStream:
@ -110,6 +111,7 @@ gboolean gst_rtsp_stream_join_bin (GstRTSPStream *stream,
GstState state);
gboolean gst_rtsp_stream_leave_bin (GstRTSPStream *stream,
GstBin *bin, GstElement *rtpbin);
GstBin * gst_rtsp_stream_get_joined_bin (GstRTSPStream *stream);
gboolean gst_rtsp_stream_set_blocked (GstRTSPStream * stream,
gboolean blocked);
@ -174,6 +176,10 @@ GstElement * gst_rtsp_stream_request_aux_sender (GstRTSPStream * st
gboolean gst_rtsp_stream_allocate_udp_sockets (GstRTSPStream * stream, GSocketFamily family,
GstRTSPTransport *transport, gboolean use_client_setttings);
void gst_rtsp_stream_set_publish_clock_mode (GstRTSPStream * stream, GstRTSPPublishClockMode mode);
GstRTSPPublishClockMode gst_rtsp_stream_get_publish_clock_mode (GstRTSPStream * stream);
/**
* GstRTSPStreamTransportFilterFunc:
* @stream: a #GstRTSPStream object