From eb0272e2103e79ef7dae6529c323786433e9c466 Mon Sep 17 00:00:00 2001 From: Jacob Johnsson Date: Mon, 26 Jun 2023 08:25:36 +0200 Subject: [PATCH] rtsp-server: Add new ensure-keyunit-on-start property While the suspend modes NONE and PAUSED provided a low startup latency for connecting clients they did not ensure that streams started on fresh data. With this property we can maintain the low startup latency of those suspend modes while also ensuring that a stream starts on a key unit. Furthermore, by modifying the value of a new property, ensure-keyunit-on-start-timeout, it is possible to accept a keyunit of a certain age but discard it if too much time has passed and instead force a new keyunit. Fixes #2443 Part-of: --- girs/GstRtspServer-1.0.gir | 168 ++++++++++ .../gst/rtsp-server/meson.build | 4 +- .../gst/rtsp-server/rtsp-media-factory.c | 181 ++++++++++ .../gst/rtsp-server/rtsp-media-factory.h | 15 + .../gst/rtsp-server/rtsp-media.c | 309 +++++++++++++++++- .../gst/rtsp-server/rtsp-media.h | 14 + .../rtsp-server/rtsp-onvif-media-factory.c | 4 + .../gst/rtsp-server/rtsp-server-internal.h | 4 + .../gst/rtsp-server/rtsp-stream.c | 143 +++++++- subprojects/gst-rtsp-server/meson.build | 2 + .../gst-rtsp-server/tests/check/meson.build | 3 +- 11 files changed, 837 insertions(+), 10 deletions(-) diff --git a/girs/GstRtspServer-1.0.gir b/girs/GstRtspServer-1.0.gir index 8dcbc918b6..549e616ff7 100644 --- a/girs/GstRtspServer-1.0.gir +++ b/girs/GstRtspServer-1.0.gir @@ -3767,6 +3767,34 @@ g_object_unref() after usage. + + Get ensure-keyunit-on-start flag. + + + The ensure-keyunit-on-start flag. + + + + + a #GstRTSPMedia + + + + + + Get ensure-keyunit-on-start-timeout time. + + + The ensure-keyunit-on-start-timeout time. + + + + + a #GstRTSPMedia + + + + Get the latency that is used for receiving media. @@ -4415,6 +4443,48 @@ INADDR_ANY. + + Set whether or not a keyunit should be ensured when a client connects. It +will also configure the streams to drop delta units to ensure that they start +on a keyunit. + +Note that this will only affect non-shared medias for now. + + + + + + + a #GstRTSPMedia + + + + the new value + + + + + + Sets the maximum allowed time before the first keyunit is considered +expired. + +Note that this will only have an effect when ensure-keyunit-on-start is +enabled. + + + + + + + a #GstRTSPMedia + + + + the new value + + + + Set or unset if an EOS event will be sent to the pipeline for @media before it is unprepared. @@ -4857,6 +4927,22 @@ when the media was not in the suspended state. + + Whether or not a keyunit should be ensured when a client connects. It +will also configure the streams to drop delta units to ensure that they start +on a keyunit. + +Note that this will only affect non-shared medias for now. + + + + The maximum allowed time before the first keyunit is considered +expired. + +Note that this will only have an effect when ensure-keyunit-on-start is +enabled. + + @@ -5568,6 +5654,34 @@ of all medias created from this factory. + + Get ensure-keyunit-on-start flag. + + + The ensure-keyunit-on-start flag. + + + + + a #GstRTSPMediaFactory + + + + + + Get ensure-keyunit-on-start-timeout time. + + + The ensure-keyunit-on-start-timeout time. + + + + + a #GstRTSPMediaFactory + + + + Get the latency that is used for receiving media @@ -5928,6 +6042,41 @@ receiving media + + If media from this factory should ensure a key unit when a client connects. + + + + + + + a #GstRTSPMediaFactory + + + + the new value + + + + + + Configures medias from this factory to consider keyunits older than timeout +to be expired. Expired keyunits will be discarded. + + + + + + + a #GstRTSPMediaFactory + + + + the new value + + + + Configure if media created from this factory will have an EOS sent to the pipeline before shutdown. @@ -6212,6 +6361,25 @@ when a client disconnects without sending TEARDOWN. Whether the created media should send and receive RTCP + + If media from this factory should ensure a key unit when a client connects. + +This property will ensure that the stream always starts on a key unit +instead of a delta unit which the client would not be able to decode. + +Note that this will only affect non-shared medias for now. + + + + Timeout in milliseconds used to determine if a keyunit should be discarded +when a client connects. + +If the timeout has been reached a new keyframe will be forced, otherwise +the currently blocking keyframe will be used. + +This options is only relevant when ensure-keyunit-on-start is enabled. + + diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/meson.build b/subprojects/gst-rtsp-server/gst/rtsp-server/meson.build index 15cb124763..5b830a720f 100644 --- a/subprojects/gst-rtsp-server/gst/rtsp-server/meson.build +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/meson.build @@ -55,7 +55,7 @@ rtsp_server_headers = files( install_headers(rtsp_server_headers, subdir : 'gstreamer-1.0/gst/rtsp-server') -gst_rtsp_server_deps = [gstrtsp_dep, gstrtp_dep, gstsdp_dep, gstnet_dep, gstapp_dep, gst_dep] +gst_rtsp_server_deps = [gstrtsp_dep, gstrtp_dep, gstsdp_dep, gstnet_dep, gstapp_dep, gst_dep, gstvideo_dep] gst_rtsp_server = library('gstrtspserver-@0@'.format(api_version), rtsp_server_sources, include_directories : rtspserver_incs, @@ -104,6 +104,6 @@ gst_libraries += [[pkg_name, library_def]] gst_rtsp_server_dep = declare_dependency(link_with : gst_rtsp_server, include_directories : rtspserver_incs, sources : rtsp_server_gen_sources, - dependencies : [gstrtsp_dep, gstrtp_dep, gstsdp_dep, gstnet_dep, gstapp_dep]) + dependencies : [gstrtsp_dep, gstrtp_dep, gstsdp_dep, gstnet_dep, gstapp_dep, gstvideo_dep]) meson.override_dependency(pkg_name, gst_rtsp_server_dep) diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media-factory.c b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media-factory.c index 9f0a121c42..ac432924d1 100644 --- a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media-factory.c +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media-factory.c @@ -59,6 +59,8 @@ struct _GstRTSPMediaFactoryPrivate GstRTSPProfile profiles; GstRTSPLowerTrans protocols; guint buffer_size; + gboolean ensure_keyunit_on_start; + guint ensure_keyunit_on_start_timeout; gint dscp_qos; GstRTSPAddressPool *pool; GstRTSPTransportMode transport_mode; @@ -90,6 +92,8 @@ struct _GstRTSPMediaFactoryPrivate #define DEFAULT_PROTOCOLS GST_RTSP_LOWER_TRANS_UDP | GST_RTSP_LOWER_TRANS_UDP_MCAST | \ GST_RTSP_LOWER_TRANS_TCP #define DEFAULT_BUFFER_SIZE 0x80000 +#define DEFAULT_ENSURE_KEYUNIT_ON_START FALSE +#define DEFAULT_ENSURE_KEYUNIT_ON_START_TIMEOUT 100 #define DEFAULT_LATENCY 200 #define DEFAULT_MAX_MCAST_TTL 255 #define DEFAULT_BIND_MCAST_ADDRESS FALSE @@ -109,6 +113,8 @@ enum PROP_PROFILES, PROP_PROTOCOLS, PROP_BUFFER_SIZE, + PROP_ENSURE_KEYUNIT_ON_START, + PROP_ENSURE_KEYUNIT_ON_START_TIMEOUT, PROP_LATENCY, PROP_TRANSPORT_MODE, PROP_STOP_ON_DISCONNECT, @@ -214,6 +220,48 @@ gst_rtsp_media_factory_class_init (GstRTSPMediaFactoryClass * klass) "The kernel UDP buffer size to use", 0, G_MAXUINT, DEFAULT_BUFFER_SIZE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstRTSPMediaFactory:ensure-keyunit-on-start: + * + * If media from this factory should ensure a key unit when a client connects. + * + * This property will ensure that the stream always starts on a key unit + * instead of a delta unit which the client would not be able to decode. + * + * Note that this will only affect non-shared medias for now. + * + * Since: 1.24 + */ + g_object_class_install_property (gobject_class, PROP_ENSURE_KEYUNIT_ON_START, + g_param_spec_boolean ("ensure-keyunit-on-start", + "Ensure keyunit on start", + "If media from this factory should ensure a key unit when a client " + "connects.", + DEFAULT_ENSURE_KEYUNIT_ON_START, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstRTSPMediaFactory:ensure-keyunit-on-start-timeout: + * + * Timeout in milliseconds used to determine if a keyunit should be discarded + * when a client connects. + * + * If the timeout has been reached a new keyframe will be forced, otherwise + * the currently blocking keyframe will be used. + * + * This options is only relevant when ensure-keyunit-on-start is enabled. + * + * Since: 1.24 + */ + g_object_class_install_property (gobject_class, + PROP_ENSURE_KEYUNIT_ON_START_TIMEOUT, + g_param_spec_uint ("ensure-keyunit-on-start-timeout", + "Timeout for discarding old keyunit on start", + "Timeout in milliseconds used to determine if a keyunit should be " + "discarded when a client connects.", 0, G_MAXUINT, + DEFAULT_ENSURE_KEYUNIT_ON_START_TIMEOUT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_LATENCY, g_param_spec_uint ("latency", "Latency", "Latency used for receiving media in milliseconds", 0, G_MAXUINT, @@ -304,6 +352,9 @@ gst_rtsp_media_factory_init (GstRTSPMediaFactory * factory) priv->profiles = DEFAULT_PROFILES; priv->protocols = DEFAULT_PROTOCOLS; priv->buffer_size = DEFAULT_BUFFER_SIZE; + priv->ensure_keyunit_on_start = DEFAULT_ENSURE_KEYUNIT_ON_START; + priv->ensure_keyunit_on_start_timeout = + DEFAULT_ENSURE_KEYUNIT_ON_START_TIMEOUT; priv->latency = DEFAULT_LATENCY; priv->transport_mode = DEFAULT_TRANSPORT_MODE; priv->stop_on_disconnect = DEFAULT_STOP_ON_DISCONNECT; @@ -373,6 +424,14 @@ gst_rtsp_media_factory_get_property (GObject * object, guint propid, g_value_set_uint (value, gst_rtsp_media_factory_get_buffer_size (factory)); break; + case PROP_ENSURE_KEYUNIT_ON_START: + g_value_set_boolean (value, + gst_rtsp_media_factory_get_ensure_keyunit_on_start (factory)); + break; + case PROP_ENSURE_KEYUNIT_ON_START_TIMEOUT: + g_value_set_uint (value, + gst_rtsp_media_factory_get_ensure_keyunit_on_start_timeout (factory)); + break; case PROP_LATENCY: g_value_set_uint (value, gst_rtsp_media_factory_get_latency (factory)); break; @@ -438,6 +497,14 @@ gst_rtsp_media_factory_set_property (GObject * object, guint propid, gst_rtsp_media_factory_set_buffer_size (factory, g_value_get_uint (value)); break; + case PROP_ENSURE_KEYUNIT_ON_START: + gst_rtsp_media_factory_set_ensure_keyunit_on_start (factory, + g_value_get_boolean (value)); + break; + case PROP_ENSURE_KEYUNIT_ON_START_TIMEOUT: + gst_rtsp_media_factory_set_ensure_keyunit_on_start_timeout (factory, + g_value_get_uint (value)); + break; case PROP_LATENCY: gst_rtsp_media_factory_set_latency (factory, g_value_get_uint (value)); break; @@ -853,6 +920,111 @@ gst_rtsp_media_factory_get_buffer_size (GstRTSPMediaFactory * factory) return result; } +/** + * gst_rtsp_media_factory_set_ensure_keyunit_on_start: + * @factory: a #GstRTSPMediaFactory + * @ensure_keyunit_on_start: the new value + * + * If media from this factory should ensure a key unit when a client connects. + * + * Since: 1.24 + */ +void +gst_rtsp_media_factory_set_ensure_keyunit_on_start (GstRTSPMediaFactory * + factory, gboolean ensure_keyunit_on_start) +{ + GstRTSPMediaFactoryPrivate *priv; + + g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory)); + + priv = factory->priv; + + GST_RTSP_MEDIA_FACTORY_LOCK (factory); + priv->ensure_keyunit_on_start = ensure_keyunit_on_start; + GST_RTSP_MEDIA_FACTORY_UNLOCK (factory); +} + +/** + * gst_rtsp_media_factory_get_ensure_keyunit_on_start: + * @factory: a #GstRTSPMediaFactory + * + * Get ensure-keyunit-on-start flag. + * + * Returns: The ensure-keyunit-on-start flag. + * + * Since: 1.24 + */ +gboolean +gst_rtsp_media_factory_get_ensure_keyunit_on_start (GstRTSPMediaFactory * + factory) +{ + GstRTSPMediaFactoryPrivate *priv; + gboolean result; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory), FALSE); + + priv = factory->priv; + + GST_RTSP_MEDIA_FACTORY_LOCK (factory); + result = priv->ensure_keyunit_on_start; + GST_RTSP_MEDIA_FACTORY_UNLOCK (factory); + + return result; +} + +/** + * gst_rtsp_media_factory_set_ensure_keyunit_on_start_timeout: + * @factory: a #GstRTSPMediaFactory + * @timeout: the new value + * + * Configures medias from this factory to consider keyunits older than timeout + * to be expired. Expired keyunits will be discarded. + * + * Since: 1.24 + */ +void +gst_rtsp_media_factory_set_ensure_keyunit_on_start_timeout (GstRTSPMediaFactory + * factory, guint timeout) +{ + GstRTSPMediaFactoryPrivate *priv; + + g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory)); + + priv = factory->priv; + + GST_RTSP_MEDIA_FACTORY_LOCK (factory); + priv->ensure_keyunit_on_start_timeout = timeout; + GST_RTSP_MEDIA_FACTORY_UNLOCK (factory); +} + +/** + * gst_rtsp_media_factory_get_ensure_keyunit_on_start_timeout: + * @factory: a #GstRTSPMediaFactory + * + * Get ensure-keyunit-on-start-timeout time. + * + * Returns: The ensure-keyunit-on-start-timeout time. + * + * Since: 1.24 + */ +guint +gst_rtsp_media_factory_get_ensure_keyunit_on_start_timeout (GstRTSPMediaFactory + * factory) +{ + GstRTSPMediaFactoryPrivate *priv; + guint result; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory), FALSE); + + priv = factory->priv; + + GST_RTSP_MEDIA_FACTORY_LOCK (factory); + result = priv->ensure_keyunit_on_start_timeout; + GST_RTSP_MEDIA_FACTORY_UNLOCK (factory); + + return result; +} + /** * gst_rtsp_media_factory_set_dscp_qos: * @factory: a #GstRTSPMediaFactory @@ -1878,6 +2050,8 @@ default_construct (GstRTSPMediaFactory * factory, const GstRTSPUrl * url) /* We need to call this prior to collecting streams */ gst_rtsp_media_set_enable_rtcp (media, enable_rtcp); + gst_rtsp_media_set_ensure_keyunit_on_start (media, + gst_rtsp_media_factory_get_ensure_keyunit_on_start (factory)); gst_rtsp_media_collect_streams (media); @@ -1928,6 +2102,8 @@ default_configure (GstRTSPMediaFactory * factory, GstRTSPMedia * media) GstRTSPMediaFactoryPrivate *priv = factory->priv; gboolean shared, eos_shutdown, stop_on_disconnect; guint size; + gboolean ensure_keyunit_on_start; + guint ensure_keyunit_on_start_timeout; gint dscp_qos; GstRTSPSuspendMode suspend_mode; GstRTSPProfile profiles; @@ -1949,6 +2125,8 @@ default_configure (GstRTSPMediaFactory * factory, GstRTSPMedia * media) shared = priv->shared; eos_shutdown = priv->eos_shutdown; size = priv->buffer_size; + ensure_keyunit_on_start = priv->ensure_keyunit_on_start; + ensure_keyunit_on_start_timeout = priv->ensure_keyunit_on_start_timeout; dscp_qos = priv->dscp_qos; profiles = priv->profiles; protocols = priv->protocols; @@ -1966,6 +2144,9 @@ default_configure (GstRTSPMediaFactory * factory, GstRTSPMedia * media) gst_rtsp_media_set_shared (media, shared); gst_rtsp_media_set_eos_shutdown (media, eos_shutdown); gst_rtsp_media_set_buffer_size (media, size); + gst_rtsp_media_set_ensure_keyunit_on_start (media, ensure_keyunit_on_start); + gst_rtsp_media_set_ensure_keyunit_on_start_timeout (media, + ensure_keyunit_on_start_timeout); gst_rtsp_media_set_dscp_qos (media, dscp_qos); gst_rtsp_media_set_profiles (media, profiles); gst_rtsp_media_set_protocols (media, protocols); diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media-factory.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media-factory.h index 8e847fda33..242b0f46c9 100644 --- a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media-factory.h +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media-factory.h @@ -191,6 +191,21 @@ void gst_rtsp_media_factory_set_buffer_size (GstRTSPMediaFacto GST_RTSP_SERVER_API guint gst_rtsp_media_factory_get_buffer_size (GstRTSPMediaFactory * factory); +GST_RTSP_SERVER_API +void gst_rtsp_media_factory_set_ensure_keyunit_on_start (GstRTSPMediaFactory * factory, + gboolean ensure_keyunit_on_start); + +GST_RTSP_SERVER_API +gboolean gst_rtsp_media_factory_get_ensure_keyunit_on_start (GstRTSPMediaFactory * factory); + +GST_RTSP_SERVER_API +void gst_rtsp_media_factory_set_ensure_keyunit_on_start_timeout (GstRTSPMediaFactory * factory, + guint timeout); + +GST_RTSP_SERVER_API +guint gst_rtsp_media_factory_get_ensure_keyunit_on_start_timeout (GstRTSPMediaFactory * factory); + + GST_RTSP_SERVER_API void gst_rtsp_media_factory_set_retransmission_time (GstRTSPMediaFactory * factory, GstClockTime time); diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media.c b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media.c index c70fb97304..66a640e95a 100644 --- a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media.c +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media.c @@ -77,6 +77,8 @@ #include #include +#include + #define AES_128_KEY_LEN 16 #define AES_256_KEY_LEN 32 @@ -107,6 +109,10 @@ struct _GstRTSPMediaPrivate gboolean reused; gboolean eos_shutdown; guint buffer_size; + gboolean ensure_keyunit_on_start; + guint ensure_keyunit_on_start_timeout; + gboolean keyunit_is_expired; /* if the blocking keyunit has expired */ + GSource *keyunit_expiration_source; gint dscp_qos; GstRTSPAddressPool *pool; gchar *multicast_iface; @@ -172,6 +178,8 @@ struct _GstRTSPMediaPrivate GST_RTSP_LOWER_TRANS_TCP #define DEFAULT_EOS_SHUTDOWN FALSE #define DEFAULT_BUFFER_SIZE 0x80000 +#define DEFAULT_ENSURE_KEYUNIT_ON_START FALSE +#define DEFAULT_ENSURE_KEYUNIT_ON_START_TIMEOUT 100 #define DEFAULT_DSCP_QOS (-1) #define DEFAULT_TIME_PROVIDER FALSE #define DEFAULT_LATENCY 200 @@ -197,6 +205,8 @@ enum PROP_PROTOCOLS, PROP_EOS_SHUTDOWN, PROP_BUFFER_SIZE, + PROP_ENSURE_KEYUNIT_ON_START, + PROP_ENSURE_KEYUNIT_ON_START_TIMEOUT, PROP_ELEMENT, PROP_TIME_PROVIDER, PROP_LATENCY, @@ -371,6 +381,44 @@ gst_rtsp_media_class_init (GstRTSPMediaClass * klass) "The kernel UDP buffer size to use", 0, G_MAXUINT, DEFAULT_BUFFER_SIZE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +/** + * GstRTSPMedia:ensure-keyunit-on-start: + * + * Whether or not a keyunit should be ensured when a client connects. It + * will also configure the streams to drop delta units to ensure that they start + * on a keyunit. + * + * Note that this will only affect non-shared medias for now. + * + * Since: 1.24 + */ + g_object_class_install_property (gobject_class, PROP_ENSURE_KEYUNIT_ON_START, + g_param_spec_boolean ("ensure-keyunit-on-start", + "Ensure keyunit on start", + "Whether the stream will ensure a keyunit when a client connects.", + DEFAULT_ENSURE_KEYUNIT_ON_START, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + +/** + * GstRTSPMedia:ensure-keyunit-on-start-timeout: + * + * The maximum allowed time before the first keyunit is considered + * expired. + * + * Note that this will only have an effect when ensure-keyunit-on-start is + * enabled. + * + * Since: 1.24 + */ + g_object_class_install_property (gobject_class, + PROP_ENSURE_KEYUNIT_ON_START_TIMEOUT, + g_param_spec_uint ("ensure-keyunit-on-start-timeout", + "Timeout for discarding old keyunit on start", + "Timeout in milliseconds used to determine if a keyunit should be " + "discarded when a client connects.", 0, G_MAXUINT, + DEFAULT_ENSURE_KEYUNIT_ON_START_TIMEOUT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_ELEMENT, g_param_spec_object ("element", "The Element", "The GstBin to use for streaming the media", GST_TYPE_ELEMENT, @@ -504,6 +552,11 @@ gst_rtsp_media_init (GstRTSPMedia * media) priv->protocols = DEFAULT_PROTOCOLS; priv->eos_shutdown = DEFAULT_EOS_SHUTDOWN; priv->buffer_size = DEFAULT_BUFFER_SIZE; + priv->ensure_keyunit_on_start = DEFAULT_ENSURE_KEYUNIT_ON_START; + priv->ensure_keyunit_on_start_timeout = + DEFAULT_ENSURE_KEYUNIT_ON_START_TIMEOUT; + priv->keyunit_is_expired = FALSE; + priv->keyunit_expiration_source = NULL; priv->time_provider = DEFAULT_TIME_PROVIDER; priv->transport_mode = DEFAULT_TRANSPORT_MODE; priv->stop_on_disconnect = DEFAULT_STOP_ON_DISCONNECT; @@ -554,6 +607,12 @@ gst_rtsp_media_finalize (GObject * obj) g_cond_clear (&priv->cond); g_rec_mutex_clear (&priv->state_lock); + if (priv->keyunit_expiration_source != NULL) { + g_source_destroy (priv->keyunit_expiration_source); + g_source_unref (priv->keyunit_expiration_source); + priv->keyunit_expiration_source = NULL; + } + G_OBJECT_CLASS (gst_rtsp_media_parent_class)->finalize (obj); } @@ -588,6 +647,14 @@ gst_rtsp_media_get_property (GObject * object, guint propid, case PROP_BUFFER_SIZE: g_value_set_uint (value, gst_rtsp_media_get_buffer_size (media)); break; + case PROP_ENSURE_KEYUNIT_ON_START: + g_value_set_boolean (value, + gst_rtsp_media_get_ensure_keyunit_on_start (media)); + break; + case PROP_ENSURE_KEYUNIT_ON_START_TIMEOUT: + g_value_set_uint (value, + gst_rtsp_media_get_ensure_keyunit_on_start_timeout (media)); + break; case PROP_TIME_PROVIDER: g_value_set_boolean (value, gst_rtsp_media_is_time_provider (media)); break; @@ -649,6 +716,14 @@ gst_rtsp_media_set_property (GObject * object, guint propid, case PROP_BUFFER_SIZE: gst_rtsp_media_set_buffer_size (media, g_value_get_uint (value)); break; + case PROP_ENSURE_KEYUNIT_ON_START: + gst_rtsp_media_set_ensure_keyunit_on_start (media, + g_value_get_boolean (value)); + break; + case PROP_ENSURE_KEYUNIT_ON_START_TIMEOUT: + gst_rtsp_media_set_ensure_keyunit_on_start_timeout (media, + g_value_get_uint (value)); + break; case PROP_TIME_PROVIDER: gst_rtsp_media_use_time_provider (media, g_value_get_boolean (value)); break; @@ -1487,6 +1562,116 @@ gst_rtsp_media_get_buffer_size (GstRTSPMedia * media) return res; } +/** + * gst_rtsp_media_set_ensure_keyunit_on_start: + * @media: a #GstRTSPMedia + * @ensure_keyunit_on_start: the new value + * + * Set whether or not a keyunit should be ensured when a client connects. It + * will also configure the streams to drop delta units to ensure that they start + * on a keyunit. + * + * Note that this will only affect non-shared medias for now. + * + * Since: 1.24 + */ +void +gst_rtsp_media_set_ensure_keyunit_on_start (GstRTSPMedia * media, + gboolean ensure_keyunit_on_start) +{ + GstRTSPMediaPrivate *priv; + + g_return_if_fail (GST_IS_RTSP_MEDIA (media)); + + priv = media->priv; + + g_mutex_lock (&priv->lock); + priv->ensure_keyunit_on_start = ensure_keyunit_on_start; + g_mutex_unlock (&priv->lock); +} + +/** + * gst_rtsp_media_get_ensure_keyunit_on_start: + * @media: a #GstRTSPMedia + * + * Get ensure-keyunit-on-start flag. + * + * Returns: The ensure-keyunit-on-start flag. + * + * Since: 1.24 + */ +gboolean +gst_rtsp_media_get_ensure_keyunit_on_start (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv; + gboolean result; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); + + priv = media->priv; + + g_mutex_lock (&priv->lock); + result = priv->ensure_keyunit_on_start; + g_mutex_unlock (&priv->lock); + + return result; +} + +/** + * gst_rtsp_media_set_ensure_keyunit_on_start_timeout: + * @media: a #GstRTSPMedia + * @timeout: the new value + * + * Sets the maximum allowed time before the first keyunit is considered + * expired. + * + * Note that this will only have an effect when ensure-keyunit-on-start is + * enabled. + * + * Since: 1.24 + */ +void +gst_rtsp_media_set_ensure_keyunit_on_start_timeout (GstRTSPMedia * media, + guint timeout) +{ + GstRTSPMediaPrivate *priv; + + g_return_if_fail (GST_IS_RTSP_MEDIA (media)); + + priv = media->priv; + + g_mutex_lock (&priv->lock); + priv->ensure_keyunit_on_start_timeout = timeout; + g_mutex_unlock (&priv->lock); +} + +/** + * gst_rtsp_media_get_ensure_keyunit_on_start_timeout + * @media: a #GstRTSPMedia + * + * Get ensure-keyunit-on-start-timeout time. + * + * Returns: The ensure-keyunit-on-start-timeout time. + * + * Since: 1.24 + */ +guint +gst_rtsp_media_get_ensure_keyunit_on_start_timeout (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv; + guint result; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); + + priv = media->priv; + + g_mutex_lock (&priv->lock); + result = priv->ensure_keyunit_on_start_timeout; + g_mutex_unlock (&priv->lock); + + return result; +} + static void do_set_dscp_qos (GstRTSPStream * stream, gint * dscp_qos) { @@ -2502,6 +2687,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_drop_delta_units (stream, priv->ensure_keyunit_on_start); gst_rtsp_stream_set_publish_clock_mode (stream, priv->publish_clock_mode); gst_rtsp_stream_set_rate_control (stream, priv->do_rate_control); @@ -2837,6 +3023,23 @@ media_streams_set_blocked (GstRTSPMedia * media, gboolean blocked) priv->blocking_msg_received = 0; } +static void +stream_install_drop_probe (GstRTSPStream * stream, gpointer user_data) +{ + if (!gst_rtsp_stream_is_complete (stream)) + return; + + gst_rtsp_stream_install_drop_probe (stream); +} + +static void +media_streams_install_drop_probe (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv = media->priv; + + g_ptr_array_foreach (priv->streams, (GFunc) stream_install_drop_probe, NULL); +} + static void gst_rtsp_media_set_status (GstRTSPMedia * media, GstRTSPMediaStatus status) { @@ -4590,6 +4793,15 @@ do_set_seqnum (GstRTSPStream * stream) } } +static gboolean +enable_keyunit_expired (GstRTSPMedia * media) +{ + GST_DEBUG_OBJECT (media, "keyunit has expired"); + media->priv->keyunit_is_expired = TRUE; + + return G_SOURCE_REMOVE; +} + /* call with state_lock */ static gboolean default_suspend (GstRTSPMedia * media) @@ -4629,6 +4841,20 @@ default_suspend (GstRTSPMedia * media) if (ret != GST_STATE_CHANGE_FAILURE && ret != GST_STATE_CHANGE_ASYNC) priv->expected_async_done = FALSE; + /* set expiration date on buffer in case of delayed PLAY request */ + if (priv->ensure_keyunit_on_start) { + /* no need to install the timer if configured to trigger immediately */ + if (priv->ensure_keyunit_on_start_timeout == 0) { + enable_keyunit_expired (media); + } else { + priv->keyunit_expiration_source = + g_timeout_source_new (priv->ensure_keyunit_on_start_timeout); + g_source_set_callback (priv->keyunit_expiration_source, + G_SOURCE_FUNC (enable_keyunit_expired), (gpointer) media, NULL); + g_source_attach (priv->keyunit_expiration_source, priv->thread->context); + } + } + return TRUE; /* ERRORS */ @@ -4684,6 +4910,7 @@ gst_rtsp_media_suspend (GstRTSPMedia * media) } gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_SUSPENDED); + done: g_rec_mutex_unlock (&priv->state_lock); @@ -4710,6 +4937,79 @@ suspend_failed: } } +/* Call with state_lock */ +static gboolean +ensure_new_keyunit (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv = media->priv; + gboolean preroll_ok; + gboolean is_blocking = FALSE; + + /* nothing to be done without complete senders */ + if (get_num_complete_sender_streams (media) == 0) { + GST_DEBUG_OBJECT (media, "no complete senders, skipping force keyunit"); + return TRUE; + } + + is_blocking = media_streams_blocking (media); + + /* if we unsuspend before the keyunit is expired remove the timer so that + * no future buffer is marked as expired */ + if (is_blocking && !priv->keyunit_is_expired) { + GST_DEBUG_OBJECT (media, "using currently blocking keyunit"); + g_source_destroy (priv->keyunit_expiration_source); + g_source_unref (priv->keyunit_expiration_source); + priv->keyunit_expiration_source = NULL; + + return TRUE; + } + + /* set the media to preparing, thus requiring a successful preroll before + * completing unsuspend. */ + gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING); + GST_DEBUG_OBJECT (media, "ensuring new keyunit, doing preroll"); + if (!start_preroll (media)) + goto start_failed; + + if (is_blocking) { + /* if we end up here then the keyunit has expired and the timer callback + * has been removed so reset the flag */ + priv->keyunit_is_expired = FALSE; + + /* install a probe that will drop the currently blocking keyunit on all + * complete streams. */ + GST_DEBUG_OBJECT (media, "media is blocking. Installing drop probe"); + media_streams_install_drop_probe (media); + } + + /* force the keyunit from src */ + GST_DEBUG_OBJECT (media, "sending force keyunit event"); + gst_element_send_event (priv->element, + gst_video_event_new_upstream_force_key_unit (GST_CLOCK_TIME_NONE, + TRUE, 0)); + + /* wait preroll */ + g_rec_mutex_unlock (&priv->state_lock); + preroll_ok = wait_preroll (media); + g_rec_mutex_lock (&priv->state_lock); + + if (!preroll_ok) + goto preroll_failed; + + return TRUE; + +start_failed: + { + GST_WARNING ("failed to preroll pipeline"); + return FALSE; + } +preroll_failed: + { + GST_WARNING ("failed while waiting to preroll pipeline"); + return FALSE; + } +} + /* call with state_lock */ static gboolean default_unsuspend (GstRTSPMedia * media) @@ -4749,14 +5049,19 @@ default_unsuspend (GstRTSPMedia * media) if (!preroll_ok) goto preroll_failed; + break; } default: break; } + if (gst_rtsp_media_get_ensure_keyunit_on_start (media)) { + return ensure_new_keyunit (media); + } + return TRUE; - /* ERRORS */ +/* ERRORS */ start_failed: { GST_WARNING ("failed to preroll pipeline"); @@ -4764,7 +5069,7 @@ start_failed: } preroll_failed: { - GST_WARNING ("failed to preroll pipeline"); + GST_WARNING ("failed while waiting to preroll pipeline"); return FALSE; } } diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media.h index 02607c03d1..e410bf020c 100644 --- a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media.h +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media.h @@ -278,6 +278,20 @@ void gst_rtsp_media_set_buffer_size (GstRTSPMedia *media, guin GST_RTSP_SERVER_API guint gst_rtsp_media_get_buffer_size (GstRTSPMedia *media); +GST_RTSP_SERVER_API +void gst_rtsp_media_set_ensure_keyunit_on_start (GstRTSPMedia* media, + gboolean ensure_keyunit_on_start); + +GST_RTSP_SERVER_API +gboolean gst_rtsp_media_get_ensure_keyunit_on_start (GstRTSPMedia* media); + +GST_RTSP_SERVER_API +void gst_rtsp_media_set_ensure_keyunit_on_start_timeout (GstRTSPMedia* media, + guint timeout); + +GST_RTSP_SERVER_API +guint gst_rtsp_media_get_ensure_keyunit_on_start_timeout (GstRTSPMedia* media); + GST_RTSP_SERVER_API void gst_rtsp_media_set_retransmission_time (GstRTSPMedia *media, GstClockTime time); diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-onvif-media-factory.c b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-onvif-media-factory.c index 420ca052f4..ba8335c12e 100644 --- a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-onvif-media-factory.c +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-onvif-media-factory.c @@ -146,6 +146,10 @@ gst_rtsp_onvif_media_factory_construct (GstRTSPMediaFactory * factory, g_object_new (media_gtype, "element", element, "transport-mode", GST_RTSP_TRANSPORT_MODE_PLAY, NULL); + /* we need to call this prior to collecting streams */ + gst_rtsp_media_set_ensure_keyunit_on_start (media, + gst_rtsp_media_factory_get_ensure_keyunit_on_start (factory)); + /* this adds the non-backchannel streams */ gst_rtsp_media_collect_streams (media); diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-server-internal.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-server-internal.h index 6015d6840e..00a45219e6 100644 --- a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-server-internal.h +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-server-internal.h @@ -63,6 +63,10 @@ gboolean gst_rtsp_stream_is_tcp_receiver (GstRTSPStream * stream void gst_rtsp_media_set_enable_rtcp (GstRTSPMedia *media, gboolean enable); void gst_rtsp_stream_set_enable_rtcp (GstRTSPStream *stream, gboolean enable); +void gst_rtsp_stream_set_drop_delta_units (GstRTSPStream * stream, gboolean drop); + +gboolean gst_rtsp_stream_install_drop_probe (GstRTSPStream * stream); + G_END_DECLS #endif /* __GST_RTSP_SERVER_INTERNAL_H__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-stream.c b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-stream.c index b1348158f9..bf606698ca 100644 --- a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-stream.c +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-stream.c @@ -230,6 +230,14 @@ struct _GstRTSPStreamPrivate gulong block_early_rtcp_probe; GstPad *block_early_rtcp_pad_ipv6; gulong block_early_rtcp_probe_ipv6; + + /* set to drop delta units in blocking pad */ + gboolean drop_delta_units; + + /* used to indicate that the drop probe has dropped a buffer and should be + * removed */ + gboolean remove_drop_probe; + }; #define DEFAULT_CONTROL NULL @@ -357,6 +365,8 @@ gst_rtsp_stream_init (GstRTSPStream * stream) priv->block_early_rtcp_probe = 0; priv->block_early_rtcp_pad_ipv6 = NULL; priv->block_early_rtcp_probe_ipv6 = 0; + priv->drop_delta_units = FALSE; + priv->remove_drop_probe = FALSE; } typedef struct _UdpClientAddrInfo UdpClientAddrInfo; @@ -4317,7 +4327,7 @@ gst_rtsp_stream_get_rtpinfo (GstRTSPStream * stream, else g_object_get (priv->appsink[0], "last-sample", &last_sample, NULL); - if (last_sample) { + if (last_sample && !priv->blocking) { GstCaps *caps; GstBuffer *buffer; GstSegment *segment; @@ -4373,6 +4383,8 @@ gst_rtsp_stream_get_rtpinfo (GstRTSPStream * stream, gst_sample_unref (last_sample); } } else if (priv->blocking) { + if (last_sample != NULL) + gst_sample_unref (last_sample); if (seq) { if (!priv->blocked_buffer) goto stats; @@ -5326,6 +5338,14 @@ rtp_pad_blocking (GstPad * pad, GstPadProbeInfo * info, gpointer user_data) gst_rtp_buffer_unmap (&rtp); } priv->position = GST_BUFFER_TIMESTAMP (buffer); + if (priv->drop_delta_units) { + if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT)) { + g_assert (!priv->blocking); + GST_DEBUG_OBJECT (pad, "dropping delta-unit buffer"); + ret = GST_PAD_PROBE_DROP; + goto done; + } + } } else if ((info->type & GST_PAD_PROBE_TYPE_BUFFER_LIST)) { GstRTPBuffer rtp = GST_RTP_BUFFER_INIT; @@ -5338,12 +5358,20 @@ rtp_pad_blocking (GstPad * pad, GstPadProbeInfo * info, gpointer user_data) gst_rtp_buffer_unmap (&rtp); } priv->position = GST_BUFFER_TIMESTAMP (buffer); + if (priv->drop_delta_units) { + if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT)) { + g_assert (!priv->blocking); + GST_DEBUG_OBJECT (pad, "dropping delta-unit buffer"); + ret = GST_PAD_PROBE_DROP; + goto done; + } + } } else if ((info->type & GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM)) { if (GST_EVENT_TYPE (info->data) == GST_EVENT_GAP) { gst_event_parse_gap (info->data, &priv->position, NULL); } else { ret = GST_PAD_PROBE_PASS; - g_mutex_unlock (&priv->lock); + GST_WARNING ("Passing event."); goto done; } } else { @@ -5371,6 +5399,11 @@ rtp_pad_blocking (GstPad * pad, GstPadProbeInfo * info, gpointer user_data) gst_event_unref (event); } + /* make sure to block on the correct frame type */ + if (priv->drop_delta_units) { + g_assert (!GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT)); + } + priv->blocking = TRUE; GST_DEBUG_OBJECT (pad, "Now blocking"); @@ -5378,14 +5411,44 @@ rtp_pad_blocking (GstPad * pad, GstPadProbeInfo * info, gpointer user_data) GST_DEBUG_OBJECT (stream, "position: %" GST_TIME_FORMAT, GST_TIME_ARGS (priv->position)); - g_mutex_unlock (&priv->lock); - gst_element_post_message (priv->payloader, gst_message_new_element (GST_OBJECT_CAST (priv->payloader), gst_structure_new ("GstRTSPStreamBlocking", "is_complete", G_TYPE_BOOLEAN, priv->is_complete, NULL))); - done: + g_mutex_unlock (&priv->lock); + return ret; +} + +/* this probe will drop a single buffer. It is used when an old buffer is + * blocking the pipeline, such as between a DESCRIBE and a PLAY request. */ +static GstPadProbeReturn +drop_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data) +{ + GstRTSPStreamPrivate *priv; + GstRTSPStream *stream; + /* drop an old buffer stuck in a blocked pipeline */ + GstPadProbeReturn ret = GST_PAD_PROBE_DROP; + + stream = user_data; + priv = stream->priv; + + g_mutex_lock (&priv->lock); + + if ((info->type & GST_PAD_PROBE_TYPE_BUFFER || + info->type & GST_PAD_PROBE_TYPE_BUFFER_LIST)) { + /* if a buffer has been dropped then remove this probe */ + if (priv->remove_drop_probe) { + priv->remove_drop_probe = FALSE; + ret = GST_PAD_PROBE_REMOVE; + } else { + priv->blocking = FALSE; + priv->remove_drop_probe = TRUE; + } + } else { + ret = GST_PAD_PROBE_PASS; + } + g_mutex_unlock (&priv->lock); return ret; } @@ -5423,6 +5486,26 @@ done: return ret; } +static void +install_drop_probe (GstRTSPStream * stream) +{ + GstRTSPStreamPrivate *priv; + + priv = stream->priv; + + /* if receiver */ + if (priv->sinkpad) + return; + + /* install for data channel only */ + if (priv->send_src[0]) { + gst_pad_add_probe (priv->send_src[0], + GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER | + GST_PAD_PROBE_TYPE_BUFFER_LIST | + GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, drop_probe, + g_object_ref (stream), g_object_unref); + } +} static void set_blocked (GstRTSPStream * stream, gboolean blocked) @@ -5499,6 +5582,34 @@ gst_rtsp_stream_set_blocked (GstRTSPStream * stream, gboolean blocked) return TRUE; } +/** + * gst_rtsp_stream_install_drop_probe: + * @stream: a #GstRTSPStream + * + * This probe can be installed when the currently blocking buffer should be + * dropped. When it has successfully dropped the buffer, it will remove itself. + * The goal is to avoid sending old data, typically when there has been a delay + * between a DESCRIBE and a PLAY request. + * + * Returns: %TRUE on success + * + * Since: 1.24 + */ +gboolean +gst_rtsp_stream_install_drop_probe (GstRTSPStream * stream) +{ + GstRTSPStreamPrivate *priv; + + g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE); + + priv = stream->priv; + g_mutex_lock (&priv->lock); + install_drop_probe (stream); + g_mutex_unlock (&priv->lock); + + return TRUE; +} + /** * gst_rtsp_stream_ublock_linked: * @stream: a #GstRTSPStream @@ -6415,3 +6526,25 @@ gst_rtsp_stream_unblock_rtcp (GstRTSPStream * stream) } g_mutex_unlock (&priv->lock); } + +/** + * gst_rtsp_stream_set_drop_delta_units: + * @stream: a #GstRTSPStream + * @drop: TRUE if delta unit frames are supposed to be dropped. + * + * Decide whether the blocking probe is supposed to drop delta units at the + * beginning of a stream. + * + * Since: 1.24 + */ +void +gst_rtsp_stream_set_drop_delta_units (GstRTSPStream * stream, gboolean drop) +{ + GstRTSPStreamPrivate *priv; + + g_return_if_fail (GST_IS_RTSP_STREAM (stream)); + priv = stream->priv; + g_mutex_lock (&priv->lock); + priv->drop_delta_units = drop; + g_mutex_unlock (&priv->lock); +} diff --git a/subprojects/gst-rtsp-server/meson.build b/subprojects/gst-rtsp-server/meson.build index d402c5d1ac..fd21b9a7b2 100644 --- a/subprojects/gst-rtsp-server/meson.build +++ b/subprojects/gst-rtsp-server/meson.build @@ -146,6 +146,8 @@ gstsdp_dep = dependency('gstreamer-sdp-1.0', version : gst_req, fallback : ['gst-plugins-base', 'sdp_dep']) gstapp_dep = dependency('gstreamer-app-1.0', version : gst_req, fallback : ['gst-plugins-base', 'app_dep']) +gstvideo_dep = dependency('gstreamer-video-1.0', version : gst_req, + fallback : ['gst-plugins-base', 'video_dep']) gstnet_dep = dependency('gstreamer-net-1.0', version : gst_req, fallback : ['gstreamer', 'gst_net_dep']) if host_machine.system() != 'windows' diff --git a/subprojects/gst-rtsp-server/tests/check/meson.build b/subprojects/gst-rtsp-server/tests/check/meson.build index ca658f9dda..2112da3962 100644 --- a/subprojects/gst-rtsp-server/tests/check/meson.build +++ b/subprojects/gst-rtsp-server/tests/check/meson.build @@ -57,7 +57,8 @@ foreach test_name : rtsp_server_tests exe = executable(test_name, fname, include_directories : rtspserver_incs, c_args : rtspserver_args + test_c_args, - dependencies : [gstcheck_dep, gstrtsp_dep, gstrtp_dep, gst_rtsp_server_dep] + dependencies : [gstcheck_dep, gstrtsp_dep, gstrtp_dep, gst_rtsp_server_dep, + gstvideo_dep] ) test(test_name, exe, env : env,