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,