diff --git a/gst/rtsp-server/rtsp-client.c b/gst/rtsp-server/rtsp-client.c index 596f54b595..ec608815d6 100644 --- a/gst/rtsp-server/rtsp-client.c +++ b/gst/rtsp-server/rtsp-client.c @@ -1833,6 +1833,8 @@ setup_play_mode (GstRTSPClient * client, GstRTSPContext * ctx, GstSeekFlags flags = GST_SEEK_FLAG_NONE; GstRTSPClientClass *klass = GST_RTSP_CLIENT_GET_CLASS (client); GstRTSPStatusCode rtsp_status_code; + GstClockTime trickmode_interval = 0; + gboolean enable_rate_control = TRUE; /* parse the range header if we have one */ res = gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_RANGE, &str, 0); @@ -1876,13 +1878,17 @@ setup_play_mode (GstRTSPClient * client, GstRTSPContext * ctx, /* give the application a chance to tweak range, flags, or rate */ if (klass->adjust_play_mode != NULL) { rtsp_status_code = - klass->adjust_play_mode (client, ctx, &range, &flags, &rate); + klass->adjust_play_mode (client, ctx, &range, &flags, &rate, + &trickmode_interval, &enable_rate_control); if (rtsp_status_code != GST_RTSP_STS_OK) goto adjust_play_mode_failed; } + gst_rtsp_media_set_rate_control (ctx->media, enable_rate_control); + /* now do the seek with the seek options */ - (void) gst_rtsp_media_seek_full_with_rate (ctx->media, range, flags, rate); + gst_rtsp_media_seek_trickmode (ctx->media, range, flags, rate, + trickmode_interval); if (range != NULL) gst_rtsp_range_free (range); @@ -2031,6 +2037,12 @@ handle_play_request (GstRTSPClient * client, GstRTSPContext * ctx) g_strdup_printf ("%1.3f", rate)); } + if (klass->adjust_play_response) { + code = klass->adjust_play_response (client, ctx); + if (code != GST_RTSP_STS_OK) + goto adjust_play_response_failed; + } + send_message (client, ctx, ctx->response, FALSE); /* start playing after sending the response */ @@ -2114,6 +2126,12 @@ get_rates_error: send_generic_response (client, GST_RTSP_STS_INTERNAL_SERVER_ERROR, ctx); return FALSE; } +adjust_play_response_failed: + { + GST_ERROR ("client %p: failed to adjust play response", client); + send_generic_response (client, code, ctx); + return FALSE; + } } static void diff --git a/gst/rtsp-server/rtsp-client.h b/gst/rtsp-server/rtsp-client.h index de958cccd0..604a042399 100644 --- a/gst/rtsp-server/rtsp-client.h +++ b/gst/rtsp-server/rtsp-client.h @@ -109,7 +109,10 @@ struct _GstRTSPClient { * RTSP response(ctx->response) via a call to gst_rtsp_message_init_response() * @make_path_from_uri: called to create path from uri. * @adjust_play_mode: called to give the application the possibility to adjust - * the range, seek flags, and/or rate. Since 1.18 + * the range, seek flags, rate and rate-control. Since 1.18 + * @adjust_play_response: called to give the implementation the possibility to + * adjust the response to a play request, for example if extra headers were + * parsed when #GstRTSPClientClass.adjust_play_mode was called. Since 1.18 * @tunnel_http_response: called when a response to the GET request is about to * be sent for a tunneled connection. The response can be modified. Since: 1.4 * @@ -132,7 +135,12 @@ struct _GstRTSPClientClass { GstRTSPContext * context, GstRTSPTimeRange ** range, GstSeekFlags * flags, - gdouble * rate); + gdouble * rate, + GstClockTime * trickmode_interval, + gboolean * enable_rate_control); + GstRTSPStatusCode (*adjust_play_response) (GstRTSPClient * client, + GstRTSPContext * context); + /* signals */ void (*closed) (GstRTSPClient *client); void (*new_session) (GstRTSPClient *client, GstRTSPSession *session); @@ -169,7 +177,7 @@ struct _GstRTSPClientClass { GstRTSPStatusCode (*pre_record_request) (GstRTSPClient *client, GstRTSPContext *ctx); /*< private >*/ - gpointer _gst_reserved[GST_PADDING_LARGE-17]; + gpointer _gst_reserved[GST_PADDING_LARGE-18]; }; GST_RTSP_SERVER_API diff --git a/gst/rtsp-server/rtsp-media.c b/gst/rtsp-server/rtsp-media.c index e8686fd5dd..4c672ddfc2 100644 --- a/gst/rtsp-server/rtsp-media.c +++ b/gst/rtsp-server/rtsp-media.c @@ -51,7 +51,7 @@ * * The state of the media can be controlled with gst_rtsp_media_set_state (). * Seeking can be done with gst_rtsp_media_seek(), or gst_rtsp_media_seek_full() - * or gst_rtsp_media_seek_full_with_rate() for finer control of the seek. + * or gst_rtsp_media_seek_trickmode() for finer control of the seek. * * With gst_rtsp_media_unprepare() the pipeline is stopped and shut down. When * gst_rtsp_media_set_eos_shutdown() an EOS will be sent to the pipeline to @@ -146,6 +146,7 @@ struct _GstRTSPMediaPrivate gboolean do_retransmission; /* protected by lock */ guint latency; /* protected by lock */ GstClock *clock; /* protected by lock */ + gboolean do_rate_control; /* protected by lock */ GstRTSPPublishClockMode publish_clock_mode; /* Dynamic element handling */ @@ -167,6 +168,7 @@ struct _GstRTSPMediaPrivate #define DEFAULT_STOP_ON_DISCONNECT TRUE #define DEFAULT_MAX_MCAST_TTL 255 #define DEFAULT_BIND_MCAST_ADDRESS FALSE +#define DEFAULT_DO_RATE_CONTROL TRUE #define DEFAULT_DO_RETRANSMISSION FALSE @@ -471,6 +473,7 @@ gst_rtsp_media_init (GstRTSPMedia * media) priv->do_retransmission = DEFAULT_DO_RETRANSMISSION; priv->max_mcast_ttl = DEFAULT_MAX_MCAST_TTL; priv->bind_mcast_address = DEFAULT_BIND_MCAST_ADDRESS; + priv->do_rate_control = DEFAULT_DO_RATE_CONTROL; } static void @@ -2293,6 +2296,7 @@ gst_rtsp_media_create_stream (GstRTSPMedia * media, GstElement * payloader, 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); + gst_rtsp_stream_set_rate_control (stream, priv->do_rate_control); g_ptr_array_add (priv->streams, stream); @@ -2662,13 +2666,15 @@ gst_rtsp_media_get_status (GstRTSPMedia * media) } /** - * gst_rtsp_media_seek_full_with_rate: + * gst_rtsp_media_seek_trickmode: * @media: a #GstRTSPMedia * @range: (transfer none): a #GstRTSPTimeRange * @flags: The minimal set of #GstSeekFlags to use * @rate: the rate to use in the seek + * @trickmode_interval: The trickmode interval to use for KEY_UNITS trick mode * - * Seek the pipeline of @media to @range with the given @flags and @rate. + * Seek the pipeline of @media to @range with the given @flags and @rate, + * and @trickmode_interval. * @media must be prepared with gst_rtsp_media_prepare(). * In order to perform the seek operation, the pipeline must contain all * needed transport parts (transport sinks). @@ -2678,8 +2684,9 @@ gst_rtsp_media_get_status (GstRTSPMedia * media) * Since: 1.18 */ gboolean -gst_rtsp_media_seek_full_with_rate (GstRTSPMedia * media, - GstRTSPTimeRange * range, GstSeekFlags flags, gdouble rate) +gst_rtsp_media_seek_trickmode (GstRTSPMedia * media, + GstRTSPTimeRange * range, GstSeekFlags flags, gdouble rate, + GstClockTime trickmode_interval) { GstRTSPMediaClass *klass; GstRTSPMediaPrivate *priv; @@ -2789,6 +2796,8 @@ gst_rtsp_media_seek_full_with_rate (GstRTSPMedia * media, GST_DEBUG ("no position change, no flags set by caller, so not seeking"); res = TRUE; } else { + GstEvent *seek_event; + gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING); if (rate < 0.0) { @@ -2801,8 +2810,12 @@ gst_rtsp_media_seek_full_with_rate (GstRTSPMedia * media, stop_type = temp_type; } - res = gst_element_seek (priv->pipeline, rate, GST_FORMAT_TIME, - flags, start_type, start, stop_type, stop); + seek_event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags, start_type, + start, stop_type, stop); + + gst_event_set_seek_trickmode_interval (seek_event, trickmode_interval); + + res = gst_element_send_event (priv->pipeline, seek_event); /* and block for the seek to complete */ GST_INFO ("done seeking %d", res); @@ -2880,7 +2893,7 @@ gboolean gst_rtsp_media_seek_full (GstRTSPMedia * media, GstRTSPTimeRange * range, GstSeekFlags flags) { - return gst_rtsp_media_seek_full_with_rate (media, range, flags, 1.0); + return gst_rtsp_media_seek_trickmode (media, range, flags, 1.0, 0); } /** @@ -2896,8 +2909,8 @@ gst_rtsp_media_seek_full (GstRTSPMedia * media, GstRTSPTimeRange * range, gboolean gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range) { - return gst_rtsp_media_seek_full_with_rate (media, range, GST_SEEK_FLAG_NONE, - 1.0); + return gst_rtsp_media_seek_trickmode (media, range, GST_SEEK_FLAG_NONE, + 1.0, 0); } static void @@ -4728,3 +4741,59 @@ gst_rtsp_media_is_receive_only (GstRTSPMedia * media) return receive_only; } + +/** + * gst_rtsp_media_set_rate_control: + * + * Define whether @media will follow the Rate-Control=no behaviour as specified + * in the ONVIF replay spec. + * + * Since: 1.18 + */ +void +gst_rtsp_media_set_rate_control (GstRTSPMedia * media, gboolean enabled) +{ + GstRTSPMediaPrivate *priv; + guint i; + + g_return_if_fail (GST_IS_RTSP_MEDIA (media)); + + GST_LOG_OBJECT (media, "%s rate control", enabled ? "Enabling" : "Disabling"); + + priv = media->priv; + + g_mutex_lock (&priv->lock); + priv->do_rate_control = enabled; + for (i = 0; i < priv->streams->len; i++) { + GstRTSPStream *stream = g_ptr_array_index (priv->streams, i); + + gst_rtsp_stream_set_rate_control (stream, enabled); + + } + g_mutex_unlock (&priv->lock); +} + +/** + * gst_rtsp_media_get_rate_control: + * + * Returns: whether @media will follow the Rate-Control=no behaviour as specified + * in the ONVIF replay spec. + * + * Since: 1.18 + */ +gboolean +gst_rtsp_media_get_rate_control (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv; + gboolean res; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); + + priv = media->priv; + + g_mutex_lock (&priv->lock); + res = priv->do_rate_control; + g_mutex_unlock (&priv->lock); + + return res; +} diff --git a/gst/rtsp-server/rtsp-media.h b/gst/rtsp-server/rtsp-media.h index 58d5aa40af..987a8ad017 100644 --- a/gst/rtsp-server/rtsp-media.h +++ b/gst/rtsp-server/rtsp-media.h @@ -388,10 +388,11 @@ gboolean gst_rtsp_media_seek_full (GstRTSPMedia *media, GstSeekFlags flags); GST_RTSP_SERVER_API -gboolean gst_rtsp_media_seek_full_with_rate (GstRTSPMedia *media, - GstRTSPTimeRange *range, - GstSeekFlags flags, - gdouble rate); +gboolean gst_rtsp_media_seek_trickmode (GstRTSPMedia *media, + GstRTSPTimeRange *range, + GstSeekFlags flags, + gdouble rate, + GstClockTime trickmode_interval); GST_RTSP_SERVER_API GstClockTimeDiff gst_rtsp_media_seekable (GstRTSPMedia *media); @@ -420,6 +421,12 @@ gboolean gst_rtsp_media_complete_pipeline (GstRTSPMedia * media, GP GST_RTSP_SERVER_API gboolean gst_rtsp_media_is_receive_only (GstRTSPMedia * media); +GST_RTSP_SERVER_API +void gst_rtsp_media_set_rate_control (GstRTSPMedia * media, gboolean enabled); + +GST_RTSP_SERVER_API +gboolean gst_rtsp_media_get_rate_control (GstRTSPMedia * media); + #ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstRTSPMedia, gst_object_unref) #endif diff --git a/gst/rtsp-server/rtsp-onvif-client.c b/gst/rtsp-server/rtsp-onvif-client.c index 2fb50fa04f..36b7ee3c75 100644 --- a/gst/rtsp-server/rtsp-onvif-client.c +++ b/gst/rtsp-server/rtsp-onvif-client.c @@ -37,11 +37,14 @@ gst_rtsp_onvif_client_check_requirements (GstRTSPClient * client, GstRTSPMediaFactory *factory = NULL; gchar *path = NULL; gboolean has_backchannel = FALSE; + gboolean has_replay = FALSE; GString *unsupported = g_string_new (""); while (*requirements) { if (strcmp (*requirements, GST_RTSP_ONVIF_BACKCHANNEL_REQUIREMENT) == 0) { has_backchannel = TRUE; + } else if (strcmp (*requirements, GST_RTSP_ONVIF_REPLAY_REQUIREMENT) == 0) { + has_replay = TRUE; } else { if (unsupported->len) g_string_append (unsupported, ", "); @@ -75,6 +78,22 @@ gst_rtsp_onvif_client_check_requirements (GstRTSPClient * client, } } + if (has_replay && !GST_IS_RTSP_ONVIF_MEDIA_FACTORY (factory)) { + if (unsupported->len) + g_string_append (unsupported, ", "); + g_string_append (unsupported, GST_RTSP_ONVIF_REPLAY_REQUIREMENT); + } else if (has_replay) { + GstRTSPOnvifMediaFactory *onvif_factory = + GST_RTSP_ONVIF_MEDIA_FACTORY (factory); + + if (!gst_rtsp_onvif_media_factory_has_replay_support (onvif_factory)) { + if (unsupported->len) + g_string_append (unsupported, ", "); + g_string_append (unsupported, GST_RTSP_ONVIF_REPLAY_REQUIREMENT); + } + } + + out: if (path) g_free (path); @@ -86,15 +105,115 @@ out: return g_string_free (unsupported, FALSE); } +static GstRTSPStatusCode +gst_rtsp_onvif_client_adjust_play_mode (GstRTSPClient * client, + GstRTSPContext * ctx, GstRTSPTimeRange ** range, GstSeekFlags * flags, + gdouble * rate, GstClockTime * trickmode_interval, + gboolean * enable_rate_control) +{ + GstRTSPStatusCode ret = GST_RTSP_STS_BAD_REQUEST; + gchar **split = NULL; + gchar *str; + + if (gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_FRAMES, + &str, 0) == GST_RTSP_OK) { + + split = g_strsplit (str, "/", 2); + + if (!g_strcmp0 (split[0], "intra")) { + if (split[1]) { + guint64 interval; + gchar *end; + + interval = g_ascii_strtoull (split[1], &end, 10); + + if (!end || *end != '\0') { + GST_ERROR ("Unexpected interval value %s", split[1]); + goto done; + } + + *trickmode_interval = interval * GST_MSECOND; + } + *flags |= GST_SEEK_FLAG_TRICKMODE_KEY_UNITS; + } else if (!g_strcmp0 (split[0], "predicted")) { + if (split[1]) { + GST_ERROR ("Predicted frames mode does not allow an interval (%s)", + str); + goto done; + } + *flags |= GST_SEEK_FLAG_TRICKMODE_FORWARD_PREDICTED; + } else { + GST_ERROR ("Invalid frames mode (%s)", str); + goto done; + } + } + + if (gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_RATE_CONTROL, + &str, 0) == GST_RTSP_OK) { + if (!g_strcmp0 (str, "no")) { + *enable_rate_control = FALSE; + } else if (!g_strcmp0 (str, "yes")) { + *enable_rate_control = TRUE; + } else { + GST_ERROR ("Invalid rate control header: %s", str); + goto done; + } + } + + ret = GST_RTSP_STS_OK; + +done: + if (split) + g_strfreev (split); + return ret; +} + +static GstRTSPStatusCode +gst_rtsp_onvif_client_adjust_play_response (GstRTSPClient * client, + GstRTSPContext * ctx) +{ + GstRTSPStatusCode ret = GST_RTSP_STS_OK; + gchar *str; + + if (gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_RATE_CONTROL, + &str, 0) == GST_RTSP_OK) { + gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_RATE_CONTROL, + gst_rtsp_media_get_rate_control (ctx->media) ? "yes" : "no"); + } + + return ret; +} + static void gst_rtsp_onvif_client_class_init (GstRTSPOnvifClientClass * klass) { GstRTSPClientClass *client_klass = (GstRTSPClientClass *) klass; client_klass->check_requirements = gst_rtsp_onvif_client_check_requirements; + client_klass->adjust_play_mode = gst_rtsp_onvif_client_adjust_play_mode; + client_klass->adjust_play_response = + gst_rtsp_onvif_client_adjust_play_response; } static void gst_rtsp_onvif_client_init (GstRTSPOnvifClient * client) { } + +/** + * gst_rtsp_onvif_client_new: + * + * Create a new #GstRTSPOnvifClient instance. + * + * Returns: (transfer full): a new #GstRTSPOnvifClient + * Since: 1.18 + */ +GstRTSPClient * +gst_rtsp_onvif_client_new (void) +{ + GstRTSPClient *result; + + result = g_object_new (GST_TYPE_RTSP_ONVIF_CLIENT, NULL); + + return result; +} diff --git a/gst/rtsp-server/rtsp-onvif-client.h b/gst/rtsp-server/rtsp-onvif-client.h index 2692cbf8c6..8230f23c59 100644 --- a/gst/rtsp-server/rtsp-onvif-client.h +++ b/gst/rtsp-server/rtsp-onvif-client.h @@ -59,4 +59,7 @@ struct GstRTSPOnvifClient GST_RTSP_SERVER_API GType gst_rtsp_onvif_client_get_type (void); +GST_RTSP_SERVER_API +GstRTSPClient * gst_rtsp_onvif_client_new (void); + #endif /* __GST_RTSP_ONVIF_CLIENT_H__ */ diff --git a/gst/rtsp-server/rtsp-onvif-media-factory.c b/gst/rtsp-server/rtsp-onvif-media-factory.c index 7da567091c..ff05cdfb7e 100644 --- a/gst/rtsp-server/rtsp-onvif-media-factory.c +++ b/gst/rtsp-server/rtsp-onvif-media-factory.c @@ -50,6 +50,7 @@ struct GstRTSPOnvifMediaFactoryPrivate GMutex lock; gchar *backchannel_launch; guint backchannel_bandwidth; + gboolean has_replay_support; }; G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPOnvifMediaFactory, @@ -447,6 +448,42 @@ gst_rtsp_onvif_media_factory_has_backchannel_support (GstRTSPOnvifMediaFactory * return FALSE; } +/** + * gst_rtsp_onvif_media_factory_has_replay_support: + * + * Returns: %TRUE if ONVIF replay is supported by the media factory. + * + * Since: 1.18 + */ +gboolean +gst_rtsp_onvif_media_factory_has_replay_support (GstRTSPOnvifMediaFactory * + factory) +{ + gboolean has_replay_support; + + g_mutex_lock (&factory->priv->lock); + has_replay_support = factory->priv->has_replay_support; + g_mutex_unlock (&factory->priv->lock); + + return has_replay_support; +} + +/** + * gst_rtsp_onvif_media_factory_set_replay_support: + * + * Set to %TRUE if ONVIF replay is supported by the media factory. + * + * Since: 1.18 + */ +void +gst_rtsp_onvif_media_factory_set_replay_support (GstRTSPOnvifMediaFactory * + factory, gboolean has_replay_support) +{ + g_mutex_lock (&factory->priv->lock); + factory->priv->has_replay_support = has_replay_support; + g_mutex_unlock (&factory->priv->lock); +} + /** * gst_rtsp_onvif_media_factory_set_backchannel_bandwidth: * @factory: a #GstRTSPMediaFactory diff --git a/gst/rtsp-server/rtsp-onvif-media-factory.h b/gst/rtsp-server/rtsp-onvif-media-factory.h index 25de214a87..bbfcabe7db 100644 --- a/gst/rtsp-server/rtsp-onvif-media-factory.h +++ b/gst/rtsp-server/rtsp-onvif-media-factory.h @@ -74,6 +74,12 @@ gchar * gst_rtsp_onvif_media_factory_get_backchannel_launch (GstRTSPOnvifMediaFa GST_RTSP_SERVER_API gboolean gst_rtsp_onvif_media_factory_has_backchannel_support (GstRTSPOnvifMediaFactory * factory); +GST_RTSP_SERVER_API +gboolean gst_rtsp_onvif_media_factory_has_replay_support (GstRTSPOnvifMediaFactory * factory); + +GST_RTSP_SERVER_API +void gst_rtsp_onvif_media_factory_set_replay_support (GstRTSPOnvifMediaFactory * factory, gboolean has_replay_support); + GST_RTSP_SERVER_API void gst_rtsp_onvif_media_factory_set_backchannel_bandwidth (GstRTSPOnvifMediaFactory * factory, guint bandwidth); GST_RTSP_SERVER_API diff --git a/gst/rtsp-server/rtsp-onvif-media.c b/gst/rtsp-server/rtsp-onvif-media.c index 29fdec294c..04eb406fad 100644 --- a/gst/rtsp-server/rtsp-onvif-media.c +++ b/gst/rtsp-server/rtsp-onvif-media.c @@ -131,6 +131,16 @@ gst_rtsp_onvif_media_setup_sdp (GstRTSPMedia * media, GstSDPMessage * sdp, if (res) { GstSDPMedia *smedia = &g_array_index (sdp->medias, GstSDPMedia, sdp->medias->len - 1); + gchar *x_onvif_track, *media_str; + + media_str = + g_ascii_strup (gst_structure_get_string (s, "media"), -1); + x_onvif_track = + g_strdup_printf ("%s%03d", media_str, sdp->medias->len - 1); + gst_sdp_media_add_attribute (smedia, "x-onvif-track", + x_onvif_track); + g_free (x_onvif_track); + g_free (media_str); if (sinkpad) { GstRTSPOnvifMedia *onvif_media = GST_RTSP_ONVIF_MEDIA (media); diff --git a/gst/rtsp-server/rtsp-onvif-server.h b/gst/rtsp-server/rtsp-onvif-server.h index 0594ba1c03..b04c9b4d5c 100644 --- a/gst/rtsp-server/rtsp-onvif-server.h +++ b/gst/rtsp-server/rtsp-onvif-server.h @@ -62,6 +62,7 @@ GST_RTSP_SERVER_API GstRTSPServer *gst_rtsp_onvif_server_new (void); #define GST_RTSP_ONVIF_BACKCHANNEL_REQUIREMENT "www.onvif.org/ver20/backchannel" +#define GST_RTSP_ONVIF_REPLAY_REQUIREMENT "onvif-replay" #include "rtsp-onvif-client.h" #include "rtsp-onvif-media-factory.h" diff --git a/gst/rtsp-server/rtsp-stream.c b/gst/rtsp-server/rtsp-stream.c index 80f0217e5c..4dfd4352fd 100644 --- a/gst/rtsp-server/rtsp-stream.c +++ b/gst/rtsp-server/rtsp-stream.c @@ -130,6 +130,9 @@ struct _GstRTSPStreamPrivate guint rtx_pt; GstClockTime rtx_time; + /* rate control */ + gboolean do_rate_control; + /* Forward Error Correction with RFC 5109 */ GstElement *ulpfec_decoder; GstElement *ulpfec_encoder; @@ -190,6 +193,7 @@ struct _GstRTSPStreamPrivate GST_RTSP_LOWER_TRANS_TCP #define DEFAULT_MAX_MCAST_TTL 255 #define DEFAULT_BIND_MCAST_ADDRESS FALSE +#define DEFAULT_DO_RATE_CONTROL TRUE enum { @@ -291,6 +295,7 @@ gst_rtsp_stream_init (GstRTSPStream * stream) priv->publish_clock_mode = GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK; priv->max_mcast_ttl = DEFAULT_MAX_MCAST_TTL; priv->bind_mcast_address = DEFAULT_BIND_MCAST_ADDRESS; + priv->do_rate_control = DEFAULT_DO_RATE_CONTROL; g_mutex_init (&priv->lock); @@ -3378,6 +3383,11 @@ create_sender_part (GstRTSPStream * stream, const GstRTSPTransport * transport) return FALSE; } + if (g_object_class_find_property (G_OBJECT_GET_CLASS (priv->payloader), + "onvif-no-rate-control")) + g_object_set (priv->payloader, "onvif-no-rate-control", + !priv->do_rate_control, NULL); + for (i = 0; i < 2; i++) { gboolean link_tee = FALSE; /* For the sender we create this bit of pipeline for both @@ -3436,6 +3446,9 @@ create_sender_part (GstRTSPStream * stream, const GstRTSPTransport * transport) g_object_set (priv->appsink[i], "emit-signals", FALSE, "buffer-list", TRUE, "max-buffers", 1, NULL); + if (i == 0) + g_object_set (priv->appsink[i], "sync", priv->do_rate_control, NULL); + /* we need to set sync and preroll to FALSE for the sink to avoid * deadlock. This is only needed for sink sending RTCP data. */ if (i == 1) @@ -3765,6 +3778,9 @@ gst_rtsp_stream_join_bin (GstRTSPStream * stream, GstBin * bin, g_signal_connect (priv->session, "on-sender-ssrc-active", (GCallback) on_sender_ssrc_active, stream); + g_object_set (priv->session, "disable-sr-timestamp", !priv->do_rate_control, + NULL); + if (priv->srcpad) { /* be notified of caps changes */ priv->caps_sig = g_signal_connect (priv->send_src[0], "notify::caps", @@ -5919,3 +5935,53 @@ gst_rtsp_stream_get_ulpfec_percentage (GstRTSPStream * stream) return res; } + +/** + * gst_rtsp_stream_set_rate_control: + * + * Define whether @stream will follow the Rate-Control=no behaviour as specified + * in the ONVIF replay spec. + * + * Since: 1.18 + */ +void +gst_rtsp_stream_set_rate_control (GstRTSPStream * stream, gboolean enabled) +{ + GST_DEBUG_OBJECT (stream, "%s rate control", + enabled ? "Enabling" : "Disabling"); + + g_mutex_lock (&stream->priv->lock); + stream->priv->do_rate_control = enabled; + if (stream->priv->appsink[0]) + g_object_set (stream->priv->appsink[0], "sync", enabled, NULL); + if (stream->priv->payloader + && g_object_class_find_property (G_OBJECT_GET_CLASS (stream->priv-> + payloader), "onvif-no-rate-control")) + g_object_set (stream->priv->payloader, "onvif-no-rate-control", !enabled, + NULL); + if (stream->priv->session) { + g_object_set (stream->priv->session, "disable-sr-timestamp", !enabled, + NULL); + } + g_mutex_unlock (&stream->priv->lock); +} + +/** + * gst_rtsp_stream_get_rate_control: + * + * Returns: whether @stream will follow the Rate-Control=no behaviour as specified + * in the ONVIF replay spec. + * + * Since: 1.18 + */ +gboolean +gst_rtsp_stream_get_rate_control (GstRTSPStream * stream) +{ + gboolean ret; + + g_mutex_lock (&stream->priv->lock); + ret = stream->priv->do_rate_control; + g_mutex_unlock (&stream->priv->lock); + + return ret; +} diff --git a/gst/rtsp-server/rtsp-stream.h b/gst/rtsp-server/rtsp-stream.h index 424a0230ee..dcb5d7a9a4 100644 --- a/gst/rtsp-server/rtsp-stream.h +++ b/gst/rtsp-server/rtsp-stream.h @@ -359,6 +359,12 @@ void gst_rtsp_stream_set_ulpfec_percentage (GstRTSPStream *stream, GST_RTSP_SERVER_API guint gst_rtsp_stream_get_ulpfec_percentage (GstRTSPStream *stream); +GST_RTSP_SERVER_API +void gst_rtsp_stream_set_rate_control (GstRTSPStream * stream, gboolean enabled); + +GST_RTSP_SERVER_API +gboolean gst_rtsp_stream_get_rate_control (GstRTSPStream * stream); + /** * GstRTSPStreamTransportFilterFunc: * @stream: a #GstRTSPStream object diff --git a/tests/check/gst/media.c b/tests/check/gst/media.c index 58b3e0477c..8a41854baa 100644 --- a/tests/check/gst/media.c +++ b/tests/check/gst/media.c @@ -109,16 +109,16 @@ GST_START_TEST (test_media_seek) fail_unless (applied_rate == 1.0); /* seeking with rate set to 1.5 should result in rate == 1.5 */ - fail_unless (gst_rtsp_media_seek_full_with_rate (media, range, - GST_SEEK_FLAG_NONE, 1.5)); + fail_unless (gst_rtsp_media_seek_trickmode (media, range, + GST_SEEK_FLAG_NONE, 1.5, 0)); fail_unless (gst_rtsp_media_get_rates (media, &rate, &applied_rate)); fail_unless (rate == 1.5); fail_unless (applied_rate == 1.0); /* seeking with rate set to -2.0 should result in rate == -2.0 */ fail_unless (gst_rtsp_range_parse ("npt=10-5", &range) == GST_RTSP_OK); - fail_unless (gst_rtsp_media_seek_full_with_rate (media, range, - GST_SEEK_FLAG_NONE, -2.0)); + fail_unless (gst_rtsp_media_seek_trickmode (media, range, + GST_SEEK_FLAG_NONE, -2.0, 0)); fail_unless (gst_rtsp_media_get_rates (media, &rate, &applied_rate)); fail_unless (rate == -2.0); fail_unless (applied_rate == 1.0); diff --git a/tests/check/gst/onvif.c b/tests/check/gst/onvif.c new file mode 100644 index 0000000000..beb276fbf4 --- /dev/null +++ b/tests/check/gst/onvif.c @@ -0,0 +1,1353 @@ +/* GStreamer + * Copyright (C) 2018 Mathieu Duponchelle + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Test source implementation */ + +#define FRAME_DURATION (GST_MSECOND) + +typedef struct +{ + GstPushSrc element; + + GstSegment *segment; + /* In milliseconds */ + guint trickmode_interval; + GstClockTime ntp_offset; +} TestSrc; + +typedef struct +{ + GstPushSrcClass parent_class; +} TestSrcClass; + +/** + * video/x-dumdum is a very simple encoded video format: + * + * - It has I-frames, P-frames and B-frames for the purpose + * of testing trick modes, and is infinitely scalable, mimicking server-side + * trick modes that would have the server reencode when a trick mode seek with + * an absolute rate different from 1.0 is requested. + * + * - The only source capable of outputting this format, `TestSrc`, happens + * to always output frames following this pattern: + * + * IBBBBPBBBBI + * + * Its framerate is 1000 / 1, each Group of Pictures is thus 10 milliseconds + * long. The first frame in the stream dates back to January the first, + * 1900, at exactly midnight. There are no gaps in the stream. + * + * A nice side effect of this for testing purposes is that as the resolution + * of UTC (clock=) seeks is a hundredth of a second, this coincides with the + * alignment of our Group of Pictures, which means we don't have to worry + * about synchronization points. + * + * - Size is used to distinguish the various frame types: + * + * * I frames: 20 bytes + * * P frames: 10 bytes + * * B frames: 5 bytes + * + */ + +#define TEST_CAPS "video/x-dumdum" + +typedef enum +{ + FRAME_TYPE_I, + FRAME_TYPE_P, + FRAME_TYPE_B, +} FrameType; + +static FrameType +frame_type_for_index (guint64 index) +{ + FrameType ret; + + if (index % 10 == 0) + ret = FRAME_TYPE_I; + else if (index % 5 == 0) + ret = FRAME_TYPE_P; + else + ret = FRAME_TYPE_B; + + return ret; +} + +static GstStaticPadTemplate test_src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (TEST_CAPS) + ); + +GType test_src_get_type (void); + +#define test_src_parent_class parent_class +G_DEFINE_TYPE (TestSrc, test_src, GST_TYPE_PUSH_SRC); + +#define ROUND_UP_TO_10(x) (((x + 10 - 1) / 10) * 10) +#define ROUND_DOWN_TO_10(x) (x - (x % 10)) + +/* + * For now, the theoretical range of our test source is infinite. + * + * When creating a buffer, we use the current segment position to + * determine the PTS, and simply increment it afterwards. + * + * When the stop time of a buffer we have created reaches segment->stop, + * GstBaseSrc will take care of sending an EOS for us, which rtponviftimestamp + * will translate to setting the T flag in the RTP header extension. + */ +static GstFlowReturn +test_src_create (GstPushSrc * psrc, GstBuffer ** buffer) +{ + GstFlowReturn ret = GST_FLOW_OK; + gsize buf_size; + TestSrc *src = (TestSrc *) psrc; + GstClockTime pts, duration; + FrameType ftype; + guint64 n_frames; + + if (src->segment->rate < 1.0) { + if (src->segment->position < src->segment->start) { + ret = GST_FLOW_EOS; + goto done; + } + } else if ((src->segment->position >= src->segment->stop)) { + ret = GST_FLOW_EOS; + goto done; + } + + pts = src->segment->position; + duration = FRAME_DURATION; + + if ((src->segment->flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS)) { + duration = + MAX (duration * 10, + duration * ROUND_UP_TO_10 (src->trickmode_interval)); + } else if ((src->segment-> + flags & GST_SEGMENT_FLAG_TRICKMODE_FORWARD_PREDICTED)) { + duration *= 5; + } + + n_frames = gst_util_uint64_scale (src->segment->position, 1000, GST_SECOND); + + ftype = frame_type_for_index (n_frames); + + switch (ftype) { + case FRAME_TYPE_I: + buf_size = 20; + break; + case FRAME_TYPE_P: + buf_size = 10; + break; + case FRAME_TYPE_B: + buf_size = 5; + break; + } + + *buffer = gst_buffer_new_allocate (NULL, buf_size, NULL); + + if (ftype != FRAME_TYPE_I) { + GST_BUFFER_FLAG_SET (*buffer, GST_BUFFER_FLAG_DELTA_UNIT); + } + + GST_BUFFER_PTS (*buffer) = pts; + GST_BUFFER_DURATION (*buffer) = duration; + + src->segment->position = pts + duration; + + if (!GST_CLOCK_TIME_IS_VALID (src->ntp_offset)) { + GstClock *clock = gst_system_clock_obtain (); + GstClockTime clock_time = gst_clock_get_time (clock); + guint64 real_time = g_get_real_time (); + GstStructure *s; + GstEvent *onvif_event; + + real_time *= 1000; + real_time += (G_GUINT64_CONSTANT (2208988800) * GST_SECOND); + src->ntp_offset = real_time - clock_time; + + s = gst_structure_new ("GstNtpOffset", + "ntp-offset", G_TYPE_UINT64, src->ntp_offset, + "discont", G_TYPE_BOOLEAN, FALSE, NULL); + + onvif_event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s); + + gst_element_send_event (GST_ELEMENT (src), onvif_event); + } + + if (src->segment->rate < 1.0) { + guint64 next_n_frames = + gst_util_uint64_scale (src->segment->position, 1000, GST_SECOND); + + if (src->segment->position > src->segment->stop + || next_n_frames / 10 > n_frames / 10) { + GstStructure *s; + GstEvent *onvif_event; + guint n_gops; + + n_gops = MAX (1, ((int) src->trickmode_interval / 10)); + + next_n_frames = (n_frames / 10 - n_gops) * 10; + + src->segment->position = next_n_frames * GST_MSECOND; + s = gst_structure_new ("GstNtpOffset", + "ntp-offset", G_TYPE_UINT64, src->ntp_offset, + "discont", G_TYPE_BOOLEAN, TRUE, NULL); + + onvif_event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s); + + gst_element_send_event (GST_ELEMENT (src), onvif_event); + } + } + +done: + return ret; +} + +static void +test_src_init (TestSrc * src) +{ + gst_base_src_set_format (GST_BASE_SRC (src), GST_FORMAT_TIME); + gst_base_src_set_automatic_eos (GST_BASE_SRC (src), FALSE); + src->segment = NULL; + src->ntp_offset = GST_CLOCK_TIME_NONE; +} + +/* + * We support seeking, both this method and GstBaseSrc.do_seek must + * be implemented for GstBaseSrc to report TRUE in the seeking query. + */ +static gboolean +test_src_is_seekable (GstBaseSrc * bsrc) +{ + return TRUE; +} + +/* Extremely simple seek handling for now, we simply update our + * segment, which will cause test_src_create to timestamp output + * buffers as expected. + */ +static gboolean +test_src_do_seek (GstBaseSrc * bsrc, GstSegment * segment) +{ + TestSrc *src = (TestSrc *) bsrc; + + if ((segment->flags & GST_SEGMENT_FLAG_TRICKMODE + && ABS (segment->rate) != 1.0)) { + segment->applied_rate = segment->rate; + segment->stop = + segment->start + ((segment->stop - + segment->start) / ABS (segment->rate)); + segment->rate = segment->rate > 0 ? 1.0 : -1.0; + } + + if (src->segment) + gst_segment_free (src->segment); + + src->segment = gst_segment_copy (segment); + + if (src->segment->rate < 0) { + guint64 n_frames = + ROUND_DOWN_TO_10 (gst_util_uint64_scale (src->segment->stop, 1000, + GST_SECOND)); + + src->segment->position = n_frames * GST_MSECOND; + } + + return TRUE; +} + +static gboolean +test_src_event (GstBaseSrc * bsrc, GstEvent * event) +{ + TestSrc *src = (TestSrc *) bsrc; + + if (GST_EVENT_TYPE (event) == GST_EVENT_SEEK) { + GstClockTime interval; + + gst_event_parse_seek_trickmode_interval (event, &interval); + + src->trickmode_interval = interval / 1000000; + } + + return GST_BASE_SRC_CLASS (parent_class)->event (bsrc, event); +} + +static void +test_src_class_init (TestSrcClass * klass) +{ + gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS (klass), + &test_src_template); + GST_PUSH_SRC_CLASS (klass)->create = test_src_create; + GST_BASE_SRC_CLASS (klass)->is_seekable = test_src_is_seekable; + GST_BASE_SRC_CLASS (klass)->do_seek = test_src_do_seek; + GST_BASE_SRC_CLASS (klass)->event = test_src_event; +} + +static GstElement * +test_src_new (void) +{ + return g_object_new (test_src_get_type (), NULL); +} + +/* Test media factory */ + +typedef struct +{ + GstRTSPMediaFactory factory; +} TestMediaFactory; + +typedef struct +{ + GstRTSPMediaFactoryClass parent_class; +} TestMediaFactoryClass; + +GType test_media_factory_get_type (void); + +G_DEFINE_TYPE (TestMediaFactory, test_media_factory, + GST_TYPE_RTSP_MEDIA_FACTORY); + +#define MAKE_AND_ADD(var, pipe, name, label, elem_name) \ +G_STMT_START { \ + if (G_UNLIKELY (!(var = (gst_element_factory_make (name, elem_name))))) { \ + GST_ERROR ("Could not create element %s", name); \ + goto label; \ + } \ + if (G_UNLIKELY (!gst_bin_add (GST_BIN_CAST (pipe), var))) { \ + GST_ERROR ("Could not add element %s", name); \ + goto label; \ + } \ +} G_STMT_END + +static GstElement * +test_media_factory_create_element (GstRTSPMediaFactory * factory, + const GstRTSPUrl * url) +{ + GstElement *ret = gst_bin_new (NULL); + GstElement *pbin = gst_bin_new ("pay0"); + GstElement *src, *pay, *onvifts, *queue; + GstPad *sinkpad, *srcpad; + GstPadLinkReturn link_ret; + + src = test_src_new (); + gst_bin_add (GST_BIN (ret), src); + MAKE_AND_ADD (pay, pbin, "rtpgstpay", fail, NULL); + MAKE_AND_ADD (onvifts, pbin, "rtponviftimestamp", fail, NULL); + MAKE_AND_ADD (queue, pbin, "queue", fail, NULL); + + gst_bin_add (GST_BIN (ret), pbin); + if (!gst_element_link_many (pay, onvifts, queue, NULL)) + goto fail; + + sinkpad = gst_element_get_static_pad (pay, "sink"); + gst_element_add_pad (pbin, gst_ghost_pad_new ("sink", sinkpad)); + gst_object_unref (sinkpad); + + sinkpad = gst_element_get_static_pad (pbin, "sink"); + srcpad = gst_element_get_static_pad (src, "src"); + link_ret = gst_pad_link (srcpad, sinkpad); + gst_object_unref (srcpad); + gst_object_unref (sinkpad); + + if (link_ret != GST_PAD_LINK_OK) + goto fail; + + srcpad = gst_element_get_static_pad (queue, "src"); + gst_element_add_pad (pbin, gst_ghost_pad_new ("src", srcpad)); + gst_object_unref (srcpad); + + g_object_set (pay, "timestamp-offset", 0, NULL); + g_object_set (onvifts, "set-t-bit", TRUE, NULL); + +done: + return ret; + +fail: + gst_object_unref (ret); + ret = NULL; + goto done; +} + +static void +test_media_factory_init (TestMediaFactory * factory) +{ +} + +static void +test_media_factory_class_init (TestMediaFactoryClass * klass) +{ + GST_RTSP_MEDIA_FACTORY_CLASS (klass)->create_element = + test_media_factory_create_element; +} + +static GstRTSPMediaFactory * +test_media_factory_new (void) +{ + GstRTSPMediaFactory *result; + + result = g_object_new (test_media_factory_get_type (), NULL); + + return result; +} + +/* Actual tests implementation */ + +static gchar *session_id; +static gint cseq; +static gboolean terminal_frame; +static gboolean received_rtcp; + +static GstSDPMessage * +sdp_from_message (GstRTSPMessage * msg) +{ + GstSDPMessage *sdp_message; + guint8 *body = NULL; + guint body_size; + + fail_unless (gst_rtsp_message_get_body (msg, &body, + &body_size) == GST_RTSP_OK); + fail_unless (gst_sdp_message_new (&sdp_message) == GST_SDP_OK); + fail_unless (gst_sdp_message_parse_buffer (body, body_size, + sdp_message) == GST_SDP_OK); + + return sdp_message; +} + +static gboolean +test_response_x_onvif_track (GstRTSPClient * client, GstRTSPMessage * response, + gboolean close, gpointer user_data) +{ + GstSDPMessage *sdp = sdp_from_message (response); + guint medias_len = gst_sdp_message_medias_len (sdp); + guint i; + + fail_unless_equals_int (medias_len, 1); + + for (i = 0; i < medias_len; i++) { + const GstSDPMedia *smedia = gst_sdp_message_get_media (sdp, i); + gchar *x_onvif_track = g_strdup_printf ("APPLICATION%03d", i); + + fail_unless_equals_string (gst_sdp_media_get_attribute_val (smedia, + "x-onvif-track"), x_onvif_track); + } + + gst_sdp_message_free (sdp); + + return TRUE; +} + +static gboolean +test_setup_response_200 (GstRTSPClient * client, GstRTSPMessage * response, + gboolean close, gpointer user_data) +{ + GstRTSPStatusCode code; + const gchar *reason; + GstRTSPVersion version; + gchar *str; + GstRTSPSessionPool *session_pool; + GstRTSPSession *session; + gchar **session_hdr_params; + + fail_unless_equals_int (gst_rtsp_message_get_type (response), + GST_RTSP_MESSAGE_RESPONSE); + + fail_unless (gst_rtsp_message_parse_response (response, &code, &reason, + &version) + == GST_RTSP_OK); + fail_unless_equals_int (code, GST_RTSP_STS_OK); + + fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_CSEQ, &str, + 0) == GST_RTSP_OK); + fail_unless (atoi (str) == cseq++); + + fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_SESSION, + &str, 0) == GST_RTSP_OK); + session_hdr_params = g_strsplit (str, ";", -1); + + /* session-id value */ + fail_unless (session_hdr_params[0] != NULL); + + session_pool = gst_rtsp_client_get_session_pool (client); + fail_unless (session_pool != NULL); + + session = gst_rtsp_session_pool_find (session_pool, session_hdr_params[0]); + g_strfreev (session_hdr_params); + + /* remember session id to be able to send teardown */ + if (session_id) + g_free (session_id); + session_id = g_strdup (gst_rtsp_session_get_sessionid (session)); + fail_unless (session_id != NULL); + + fail_unless (session != NULL); + g_object_unref (session); + + g_object_unref (session_pool); + + return TRUE; +} + +static gboolean +test_response_200 (GstRTSPClient * client, GstRTSPMessage * response, + gboolean close, gpointer user_data) +{ + GstRTSPStatusCode code; + const gchar *reason; + GstRTSPVersion version; + + fail_unless_equals_int (gst_rtsp_message_get_type (response), + GST_RTSP_MESSAGE_RESPONSE); + fail_unless (gst_rtsp_message_parse_response (response, &code, &reason, + &version) + == GST_RTSP_OK); + fail_unless_equals_int (code, GST_RTSP_STS_OK); + + return TRUE; +} + +typedef struct +{ + guint32 previous_ts; + gint32 expected_ts_interval; + gint32 expected_i_frame_ts_interval; + guint expected_n_buffers; + guint n_buffers; + guint expected_n_i_frames; + guint n_i_frames; + guint expected_n_p_frames; + guint n_p_frames; + guint expected_n_b_frames; + guint n_b_frames; + guint expected_n_clean_points; + guint n_clean_points; + gboolean timestamped_rtcp; +} RTPCheckData; + +#define EXTENSION_ID 0xABAC +#define EXTENSION_SIZE 3 + +static gboolean +test_play_response_200_and_check_data (GstRTSPClient * client, + GstRTSPMessage * response, gboolean close, gpointer user_data) +{ + GstRTSPStatusCode code; + const gchar *reason; + GstRTSPVersion version; + RTPCheckData *check = (RTPCheckData *) user_data; + + /* We check data in the same send function because client->send_func cannot + * be changed from client->send_func + */ + if (gst_rtsp_message_get_type (response) == GST_RTSP_MESSAGE_DATA) { + GstRTSPStreamTransport *trans; + guint8 channel = 42; + + gst_rtsp_message_parse_data (response, &channel); + fail_unless (trans = + gst_rtsp_client_get_stream_transport (client, channel)); + + if (channel == 0) { /* RTP */ + GstBuffer *buf; + GstRTPBuffer rtp = GST_RTP_BUFFER_INIT; + guint8 *body = NULL; + guint body_size; + guint8 *data; + guint16 bits; + guint wordlen; + guint8 flags; + gint32 expected_interval; + gboolean is_custom_event = FALSE; + + fail_unless (gst_rtsp_message_get_body (response, &body, + &body_size) == GST_RTSP_OK); + + buf = gst_rtp_buffer_new_copy_data (body, body_size); + + switch (body_size) { + case 115: /* Ignore our serialized custom events */ + is_custom_event = TRUE; + break; + case 56: + expected_interval = check->expected_i_frame_ts_interval; + check->n_i_frames += 1; + break; + case 46: + expected_interval = check->expected_ts_interval; + check->n_p_frames += 1; + break; + case 41: + expected_interval = check->expected_ts_interval; + check->n_b_frames += 1; + break; + default: + fail ("Invalid body size %u", body_size); + } + + if (!is_custom_event) { + fail_unless (gst_rtp_buffer_map (buf, GST_MAP_READ, &rtp)); + + if (check->previous_ts) { + fail_unless_equals_int (gst_rtp_buffer_get_timestamp (&rtp) - + check->previous_ts, expected_interval); + } + + check->previous_ts = gst_rtp_buffer_get_timestamp (&rtp); + check->n_buffers += 1; + + fail_unless (gst_rtp_buffer_get_extension_data (&rtp, &bits, + (gpointer) & data, &wordlen)); + + fail_unless (bits == EXTENSION_ID && wordlen == EXTENSION_SIZE); + + flags = GST_READ_UINT8 (data + 8); + + gst_rtp_buffer_unmap (&rtp); + + if (flags & (1 << 7)) { + check->n_clean_points += 1; + } + + /* T flag is set, we are done */ + if (flags & (1 << 4)) { + fail_unless_equals_int (check->expected_n_buffers, check->n_buffers); + fail_unless_equals_int (check->expected_n_i_frames, + check->n_i_frames); + fail_unless_equals_int (check->expected_n_p_frames, + check->n_p_frames); + fail_unless_equals_int (check->expected_n_b_frames, + check->n_b_frames); + fail_unless_equals_int (check->expected_n_clean_points, + check->n_clean_points); + + terminal_frame = TRUE; + + } + } + + gst_buffer_unref (buf); + } else if (channel == 1) { /* RTCP */ + GstBuffer *buf; + guint8 *body = NULL; + guint body_size; + GstRTCPPacket packet; + GstRTCPBuffer rtcp = GST_RTCP_BUFFER_INIT; + guint32 ssrc, rtptime, packet_count, octet_count; + guint64 ntptime; + + received_rtcp = TRUE; + fail_unless (gst_rtsp_message_get_body (response, &body, + &body_size) == GST_RTSP_OK); + + buf = gst_rtp_buffer_new_copy_data (body, body_size); + gst_rtcp_buffer_map (buf, GST_MAP_READ, &rtcp); + gst_rtcp_buffer_get_first_packet (&rtcp, &packet); + + gst_rtcp_packet_sr_get_sender_info (&packet, &ssrc, &ntptime, &rtptime, + &packet_count, &octet_count); + + if (check->timestamped_rtcp) { + fail_unless (rtptime != 0); + fail_unless (ntptime != 0); + } else { + fail_unless (rtptime == 0); + fail_unless (ntptime == 0); + } + + gst_rtcp_buffer_unmap (&rtcp); + gst_buffer_unref (buf); + } + + gst_rtsp_stream_transport_message_sent (trans); + + if (terminal_frame && received_rtcp) { + g_mutex_lock (&check_mutex); + g_cond_broadcast (&check_cond); + g_mutex_unlock (&check_mutex); + } + + return TRUE; + } + + fail_unless (gst_rtsp_message_get_type (response) == + GST_RTSP_MESSAGE_RESPONSE); + + fail_unless (gst_rtsp_message_parse_response (response, &code, &reason, + &version) + == GST_RTSP_OK); + fail_unless (code == GST_RTSP_STS_OK); + + return TRUE; +} + +static gboolean +test_teardown_response_200 (GstRTSPClient * client, + GstRTSPMessage * response, gboolean close, gpointer user_data) +{ + GstRTSPStatusCode code; + const gchar *reason; + GstRTSPVersion version; + + /* We might still be seeing stray RTCP messages */ + if (gst_rtsp_message_get_type (response) == GST_RTSP_MESSAGE_DATA) + return TRUE; + + fail_unless (gst_rtsp_message_get_type (response) == + GST_RTSP_MESSAGE_RESPONSE); + + fail_unless (gst_rtsp_message_parse_response (response, &code, &reason, + &version) + == GST_RTSP_OK); + fail_unless (code == GST_RTSP_STS_OK); + fail_unless (g_str_equal (reason, "OK")); + fail_unless (version == GST_RTSP_VERSION_1_0); + + return TRUE; +} + +static void +send_teardown (GstRTSPClient * client) +{ + GstRTSPMessage request = { 0, }; + gchar *str; + + fail_unless (session_id != NULL); + fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_TEARDOWN, + "rtsp://localhost/test") == GST_RTSP_OK); + str = g_strdup_printf ("%d", cseq); + gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str); + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SESSION, session_id); + gst_rtsp_client_set_send_func (client, test_teardown_response_200, + NULL, NULL); + fail_unless (gst_rtsp_client_handle_message (client, + &request) == GST_RTSP_OK); + gst_rtsp_message_unset (&request); + g_free (session_id); + session_id = NULL; +} + +static GstRTSPClient * +setup_client (const gchar * launch_line) +{ + GstRTSPClient *client; + GstRTSPSessionPool *session_pool; + GstRTSPMountPoints *mount_points; + GstRTSPMediaFactory *factory; + GstRTSPThreadPool *thread_pool; + + client = gst_rtsp_onvif_client_new (); + + session_pool = gst_rtsp_session_pool_new (); + gst_rtsp_client_set_session_pool (client, session_pool); + + mount_points = gst_rtsp_mount_points_new (); + factory = test_media_factory_new (); + + gst_rtsp_media_factory_set_media_gtype (factory, GST_TYPE_RTSP_ONVIF_MEDIA); + + gst_rtsp_mount_points_add_factory (mount_points, "/test", factory); + gst_rtsp_client_set_mount_points (client, mount_points); + + thread_pool = gst_rtsp_thread_pool_new (); + gst_rtsp_client_set_thread_pool (client, thread_pool); + + g_object_unref (mount_points); + g_object_unref (session_pool); + g_object_unref (thread_pool); + + return client; +} + +static void +teardown_client (GstRTSPClient * client) +{ + gst_rtsp_client_set_thread_pool (client, NULL); + g_object_unref (client); +} + +/** + * https://www.onvif.org/specs/stream/ONVIF-Streaming-Spec.pdf + * 6.2 RTSP describe + */ +GST_START_TEST (test_x_onvif_track) +{ + GstRTSPClient *client; + GstRTSPMessage request = { 0, }; + gchar *str; + + client = setup_client (NULL); + fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_DESCRIBE, + "rtsp://localhost/test") == GST_RTSP_OK); + str = g_strdup_printf ("%d", cseq); + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CSEQ, str); + g_free (str); + + gst_rtsp_client_set_send_func (client, test_response_x_onvif_track, NULL, + NULL); + fail_unless (gst_rtsp_client_handle_message (client, + &request) == GST_RTSP_OK); + gst_rtsp_message_unset (&request); + + teardown_client (client); +} + +GST_END_TEST; + +static void +create_connection (GstRTSPConnection ** conn) +{ + GSocket *sock; + GError *error = NULL; + + sock = g_socket_new (G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_TCP, &error); + g_assert_no_error (error); + fail_unless (gst_rtsp_connection_create_from_socket (sock, "127.0.0.1", 444, + NULL, conn) == GST_RTSP_OK); + g_object_unref (sock); +} + +static void +test_seek (const gchar * range, const gchar * speed, const gchar * scale, + const gchar * frames, const gchar * rate_control, RTPCheckData * rtp_check) +{ + GstRTSPClient *client; + GstRTSPConnection *conn; + GstRTSPMessage request = { 0, }; + gchar *str; + + client = setup_client (NULL); + create_connection (&conn); + fail_unless (gst_rtsp_client_set_connection (client, conn)); + + fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP, + "rtsp://localhost/test/stream=0") == GST_RTSP_OK); + str = g_strdup_printf ("%d", cseq); + gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str); + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT, + "RTP/AVP/TCP;unicast"); + + gst_rtsp_client_set_send_func (client, test_setup_response_200, NULL, NULL); + fail_unless (gst_rtsp_client_handle_message (client, + &request) == GST_RTSP_OK); + gst_rtsp_message_unset (&request); + + fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_PLAY, + "rtsp://localhost/test") == GST_RTSP_OK); + str = g_strdup_printf ("%d", cseq); + gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str); + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SESSION, session_id); + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_RANGE, range); + + if (scale) { + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SCALE, scale); + } + + if (speed) { + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SPEED, speed); + } + + if (frames) { + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_FRAMES, frames); + } + + if (rate_control) { + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_RATE_CONTROL, + rate_control); + } + + gst_rtsp_client_set_send_func (client, test_play_response_200_and_check_data, + rtp_check, NULL); + + terminal_frame = FALSE; + received_rtcp = FALSE; + + fail_unless (gst_rtsp_client_handle_message (client, + &request) == GST_RTSP_OK); + gst_rtsp_message_unset (&request); + + g_mutex_lock (&check_mutex); + g_cond_wait (&check_cond, &check_mutex); + g_mutex_unlock (&check_mutex); + + send_teardown (client); + + teardown_client (client); +} + +GST_START_TEST (test_src_seek_simple) +{ + RTPCheckData rtp_check; + + rtp_check.previous_ts = 0; + rtp_check.expected_ts_interval = 90; + rtp_check.expected_i_frame_ts_interval = 90; + rtp_check.expected_n_buffers = 100; + rtp_check.n_buffers = 0; + rtp_check.expected_n_i_frames = 10; + rtp_check.n_i_frames = 0; + rtp_check.expected_n_p_frames = 10; + rtp_check.n_p_frames = 0; + rtp_check.expected_n_b_frames = 80; + rtp_check.n_b_frames = 0; + rtp_check.expected_n_clean_points = 10; + rtp_check.n_clean_points = 0; + rtp_check.timestamped_rtcp = TRUE; + + test_seek ("clock=19000101T010000.00Z-19000101T010000.10Z", NULL, NULL, NULL, + NULL, &rtp_check); +} + +GST_END_TEST; + +/** + * https://www.onvif.org/specs/stream/ONVIF-Streaming-Spec.pdf + * 6.4 RTSP Feature Tag + */ +GST_START_TEST (test_onvif_replay) +{ + GstRTSPClient *client; + GstRTSPConnection *conn; + GstRTSPMessage request = { 0, }; + gchar *str; + + client = setup_client (NULL); + create_connection (&conn); + fail_unless (gst_rtsp_client_set_connection (client, conn)); + + fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_DESCRIBE, + "rtsp://localhost/test") == GST_RTSP_OK); + str = g_strdup_printf ("%d", cseq); + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CSEQ, str); + g_free (str); + + gst_rtsp_client_set_send_func (client, test_response_200, NULL, NULL); + fail_unless (gst_rtsp_client_handle_message (client, + &request) == GST_RTSP_OK); + gst_rtsp_message_unset (&request); + + fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP, + "rtsp://localhost/test/stream=0") == GST_RTSP_OK); + str = g_strdup_printf ("%d", cseq); + gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str); + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT, + "RTP/AVP/TCP;unicast"); + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE, "onvif-replay"); + + gst_rtsp_client_set_send_func (client, test_setup_response_200, NULL, NULL); + fail_unless (gst_rtsp_client_handle_message (client, + &request) == GST_RTSP_OK); + gst_rtsp_message_unset (&request); + + send_teardown (client); + teardown_client (client); +} + +GST_END_TEST; + +GST_START_TEST (test_speed_trick_mode) +{ + RTPCheckData rtp_check; + + rtp_check.previous_ts = 0; + rtp_check.expected_ts_interval = 45; + rtp_check.expected_i_frame_ts_interval = 45; + rtp_check.expected_n_buffers = 100; + rtp_check.n_buffers = 0; + rtp_check.expected_n_i_frames = 10; + rtp_check.n_i_frames = 0; + rtp_check.expected_n_p_frames = 10; + rtp_check.n_p_frames = 0; + rtp_check.expected_n_b_frames = 80; + rtp_check.n_b_frames = 0; + rtp_check.expected_n_clean_points = 10; + rtp_check.n_clean_points = 0; + rtp_check.timestamped_rtcp = TRUE; + + test_seek ("clock=19000101T010000.00Z-19000101T010000.10Z", "2.0", NULL, NULL, + NULL, &rtp_check); +} + +GST_END_TEST; + +GST_START_TEST (test_scale_trick_mode) +{ + RTPCheckData rtp_check; + + rtp_check.previous_ts = 0; + rtp_check.expected_ts_interval = 90; + rtp_check.expected_i_frame_ts_interval = 90; + rtp_check.expected_n_buffers = 50; + rtp_check.n_buffers = 0; + rtp_check.expected_n_i_frames = 5; + rtp_check.n_i_frames = 0; + rtp_check.expected_n_p_frames = 5; + rtp_check.n_p_frames = 0; + rtp_check.expected_n_b_frames = 40; + rtp_check.n_b_frames = 0; + rtp_check.expected_n_clean_points = 5; + rtp_check.n_clean_points = 0; + rtp_check.timestamped_rtcp = TRUE; + + test_seek ("clock=19000101T010000.00Z-19000101T010000.10Z", NULL, "2.0", NULL, + NULL, &rtp_check); +} + +GST_END_TEST; + +GST_START_TEST (test_intra_frames_trick_mode) +{ + RTPCheckData rtp_check; + + rtp_check.previous_ts = 0; + rtp_check.expected_ts_interval = 900; + rtp_check.expected_i_frame_ts_interval = 900; + rtp_check.expected_n_buffers = 10; + rtp_check.n_buffers = 0; + rtp_check.expected_n_i_frames = 10; + rtp_check.n_i_frames = 0; + rtp_check.expected_n_p_frames = 0; + rtp_check.n_p_frames = 0; + rtp_check.expected_n_b_frames = 0; + rtp_check.n_b_frames = 0; + rtp_check.expected_n_clean_points = 10; + rtp_check.n_clean_points = 0; + rtp_check.timestamped_rtcp = TRUE; + + test_seek ("clock=19000101T010000.00Z-19000101T010000.10Z", NULL, NULL, + "intra", NULL, &rtp_check); +} + +GST_END_TEST; + +GST_START_TEST (test_intra_frames_with_interval_trick_mode) +{ + RTPCheckData rtp_check; + + rtp_check.previous_ts = 0; + rtp_check.expected_ts_interval = 1800; + rtp_check.expected_i_frame_ts_interval = 1800; + rtp_check.expected_n_buffers = 5; + rtp_check.n_buffers = 0; + rtp_check.expected_n_i_frames = 5; + rtp_check.n_i_frames = 0; + rtp_check.expected_n_p_frames = 0; + rtp_check.n_p_frames = 0; + rtp_check.expected_n_b_frames = 0; + rtp_check.n_b_frames = 0; + rtp_check.expected_n_clean_points = 5; + rtp_check.n_clean_points = 0; + rtp_check.timestamped_rtcp = TRUE; + + test_seek ("clock=19000101T010000.00Z-19000101T010000.10Z", NULL, NULL, + "intra/20", NULL, &rtp_check); +} + +GST_END_TEST; + +GST_START_TEST (test_predicted_frames_trick_mode) +{ + RTPCheckData rtp_check; + + rtp_check.previous_ts = 0; + rtp_check.expected_ts_interval = 450; + rtp_check.expected_i_frame_ts_interval = 450; + rtp_check.expected_n_buffers = 20; + rtp_check.n_buffers = 0; + rtp_check.expected_n_i_frames = 10; + rtp_check.n_i_frames = 0; + rtp_check.expected_n_p_frames = 10; + rtp_check.n_p_frames = 0; + rtp_check.expected_n_b_frames = 0; + rtp_check.n_b_frames = 0; + rtp_check.expected_n_clean_points = 10; + rtp_check.n_clean_points = 0; + rtp_check.timestamped_rtcp = TRUE; + + test_seek ("clock=19000101T010000.00Z-19000101T010000.10Z", NULL, NULL, + "predicted", NULL, &rtp_check); +} + +GST_END_TEST; + +GST_START_TEST (test_reverse_trick_mode) +{ + RTPCheckData rtp_check; + + rtp_check.previous_ts = 0; + rtp_check.expected_ts_interval = -90; + rtp_check.expected_i_frame_ts_interval = 1710; + rtp_check.expected_n_buffers = 100; + rtp_check.n_buffers = 0; + rtp_check.expected_n_i_frames = 10; + rtp_check.n_i_frames = 0; + rtp_check.expected_n_p_frames = 10; + rtp_check.n_p_frames = 0; + rtp_check.expected_n_b_frames = 80; + rtp_check.n_b_frames = 0; + rtp_check.expected_n_clean_points = 10; + rtp_check.n_clean_points = 0; + rtp_check.timestamped_rtcp = TRUE; + + test_seek ("clock=19000101T010000.10Z-19000101T010000.00Z", NULL, "-1.0", + NULL, NULL, &rtp_check); +} + +GST_END_TEST; + +GST_START_TEST (test_speed_reverse_trick_mode) +{ + RTPCheckData rtp_check; + + rtp_check.previous_ts = 0; + rtp_check.expected_ts_interval = -45; + rtp_check.expected_i_frame_ts_interval = 855; + rtp_check.expected_n_buffers = 100; + rtp_check.n_buffers = 0; + rtp_check.expected_n_i_frames = 10; + rtp_check.n_i_frames = 0; + rtp_check.expected_n_p_frames = 10; + rtp_check.n_p_frames = 0; + rtp_check.expected_n_b_frames = 80; + rtp_check.n_b_frames = 0; + rtp_check.expected_n_clean_points = 10; + rtp_check.n_clean_points = 0; + rtp_check.timestamped_rtcp = TRUE; + + test_seek ("clock=19000101T010000.10Z-19000101T010000.00Z", "2.0", "-1.0", + NULL, NULL, &rtp_check); +} + +GST_END_TEST; + +GST_START_TEST (test_scale_reverse_trick_mode) +{ + RTPCheckData rtp_check; + + rtp_check.previous_ts = 0; + rtp_check.expected_ts_interval = -90; + rtp_check.expected_i_frame_ts_interval = 1710; + rtp_check.expected_n_buffers = 50; + rtp_check.n_buffers = 0; + rtp_check.expected_n_i_frames = 5; + rtp_check.n_i_frames = 0; + rtp_check.expected_n_p_frames = 5; + rtp_check.n_p_frames = 0; + rtp_check.expected_n_b_frames = 40; + rtp_check.n_b_frames = 0; + rtp_check.expected_n_clean_points = 5; + rtp_check.n_clean_points = 0; + rtp_check.timestamped_rtcp = TRUE; + + test_seek ("clock=19000101T010001.10Z-19000101T010001.00Z", NULL, "-2.0", + NULL, NULL, &rtp_check); +} + +GST_END_TEST; + +GST_START_TEST (test_intra_frames_reverse_trick_mode) +{ + RTPCheckData rtp_check; + + rtp_check.previous_ts = 0; + rtp_check.expected_ts_interval = 0; + rtp_check.expected_i_frame_ts_interval = 900; + rtp_check.expected_n_buffers = 10; + rtp_check.n_buffers = 0; + rtp_check.expected_n_i_frames = 10; + rtp_check.n_i_frames = 0; + rtp_check.expected_n_p_frames = 0; + rtp_check.n_p_frames = 0; + rtp_check.expected_n_b_frames = 0; + rtp_check.n_b_frames = 0; + rtp_check.expected_n_clean_points = 10; + rtp_check.n_clean_points = 0; + rtp_check.timestamped_rtcp = TRUE; + + test_seek ("clock=19000101T010001.10Z-19000101T010001.00Z", NULL, "-1.0", + "intra", NULL, &rtp_check); +} + +GST_END_TEST; + +GST_START_TEST (test_predicted_frames_reverse_trick_mode) +{ + RTPCheckData rtp_check; + + rtp_check.previous_ts = 0; + rtp_check.expected_ts_interval = -450; + rtp_check.expected_i_frame_ts_interval = 1350; + rtp_check.expected_n_buffers = 20; + rtp_check.n_buffers = 0; + rtp_check.expected_n_i_frames = 10; + rtp_check.n_i_frames = 0; + rtp_check.expected_n_p_frames = 10; + rtp_check.n_p_frames = 0; + rtp_check.expected_n_b_frames = 0; + rtp_check.n_b_frames = 0; + rtp_check.expected_n_clean_points = 10; + rtp_check.n_clean_points = 0; + rtp_check.timestamped_rtcp = TRUE; + + test_seek ("clock=19000101T010001.10Z-19000101T010001.00Z", NULL, "-1.0", + "predicted", NULL, &rtp_check); +} + +GST_END_TEST; + +GST_START_TEST (test_intra_frames_with_interval_reverse_trick_mode) +{ + RTPCheckData rtp_check; + + rtp_check.previous_ts = 0; + rtp_check.expected_ts_interval = 0; + rtp_check.expected_i_frame_ts_interval = 1800; + rtp_check.expected_n_buffers = 5; + rtp_check.n_buffers = 0; + rtp_check.expected_n_i_frames = 5; + rtp_check.n_i_frames = 0; + rtp_check.expected_n_p_frames = 0; + rtp_check.n_p_frames = 0; + rtp_check.expected_n_b_frames = 0; + rtp_check.n_b_frames = 0; + rtp_check.expected_n_clean_points = 5; + rtp_check.n_clean_points = 0; + rtp_check.timestamped_rtcp = TRUE; + + test_seek ("clock=19000101T010001.10Z-19000101T010001.00Z", NULL, "-1.0", + "intra/20", NULL, &rtp_check); +} + +GST_END_TEST; + +GST_START_TEST (test_rate_control_no_trick_mode) +{ + RTPCheckData rtp_check; + + rtp_check.previous_ts = 0; + rtp_check.expected_ts_interval = 90; + rtp_check.expected_i_frame_ts_interval = 90; + rtp_check.expected_n_buffers = 100; + rtp_check.n_buffers = 0; + rtp_check.expected_n_i_frames = 10; + rtp_check.n_i_frames = 0; + rtp_check.expected_n_p_frames = 10; + rtp_check.n_p_frames = 0; + rtp_check.expected_n_b_frames = 80; + rtp_check.n_b_frames = 0; + rtp_check.expected_n_clean_points = 10; + rtp_check.n_clean_points = 0; + rtp_check.timestamped_rtcp = FALSE; + + test_seek ("clock=19000101T010000.00Z-19000101T010000.10Z", NULL, NULL, NULL, + "no", &rtp_check); +} + +GST_END_TEST; + +GST_START_TEST (test_rate_control_no_reverse_trick_mode) +{ + RTPCheckData rtp_check; + + rtp_check.previous_ts = 0; + rtp_check.expected_ts_interval = 90; + rtp_check.expected_i_frame_ts_interval = -1710; + rtp_check.expected_n_buffers = 100; + rtp_check.n_buffers = 0; + rtp_check.expected_n_i_frames = 10; + rtp_check.n_i_frames = 0; + rtp_check.expected_n_p_frames = 10; + rtp_check.n_p_frames = 0; + rtp_check.expected_n_b_frames = 80; + rtp_check.n_b_frames = 0; + rtp_check.expected_n_clean_points = 10; + rtp_check.n_clean_points = 0; + rtp_check.timestamped_rtcp = FALSE; + + test_seek ("clock=19000101T010000.10Z-19000101T010000.00Z", NULL, "-1.0", + NULL, "no", &rtp_check); +} + +GST_END_TEST; + +GST_START_TEST (test_rate_control_no_frames_trick_mode) +{ + RTPCheckData rtp_check; + + rtp_check.previous_ts = 0; + rtp_check.expected_ts_interval = 900; + rtp_check.expected_i_frame_ts_interval = 900; + rtp_check.expected_n_buffers = 10; + rtp_check.n_buffers = 0; + rtp_check.expected_n_i_frames = 10; + rtp_check.n_i_frames = 0; + rtp_check.expected_n_p_frames = 0; + rtp_check.n_p_frames = 0; + rtp_check.expected_n_b_frames = 0; + rtp_check.n_b_frames = 0; + rtp_check.expected_n_clean_points = 10; + rtp_check.n_clean_points = 0; + rtp_check.timestamped_rtcp = FALSE; + + test_seek ("clock=19000101T010000.00Z-19000101T010000.10Z", NULL, NULL, + "intra", "no", &rtp_check); +} + +GST_END_TEST; +static Suite * +onvif_suite (void) +{ + Suite *s = suite_create ("onvif"); + TCase *tc = tcase_create ("general"); + + suite_add_tcase (s, tc); + + tcase_add_test (tc, test_x_onvif_track); + tcase_add_test (tc, test_onvif_replay); + tcase_add_test (tc, test_src_seek_simple); + tcase_add_test (tc, test_speed_trick_mode); + tcase_add_test (tc, test_scale_trick_mode); + tcase_add_test (tc, test_intra_frames_trick_mode); + tcase_add_test (tc, test_predicted_frames_trick_mode); + tcase_add_test (tc, test_intra_frames_with_interval_trick_mode); + tcase_add_test (tc, test_reverse_trick_mode); + tcase_add_test (tc, test_speed_reverse_trick_mode); + tcase_add_test (tc, test_scale_reverse_trick_mode); + tcase_add_test (tc, test_intra_frames_reverse_trick_mode); + tcase_add_test (tc, test_predicted_frames_reverse_trick_mode); + tcase_add_test (tc, test_intra_frames_with_interval_reverse_trick_mode); + tcase_add_test (tc, test_rate_control_no_trick_mode); + tcase_add_test (tc, test_rate_control_no_reverse_trick_mode); + tcase_add_test (tc, test_rate_control_no_frames_trick_mode); + + return s; +} + +GST_CHECK_MAIN (onvif); diff --git a/tests/check/meson.build b/tests/check/meson.build index 78d083f331..d241611741 100644 --- a/tests/check/meson.build +++ b/tests/check/meson.build @@ -28,6 +28,7 @@ rtsp_server_tests = [ 'gst/stream', 'gst/threadpool', 'gst/token', + 'gst/onvif', ] if not get_option('rtspclientsink').disabled()