rtspsrc: expose and implement onvif-mode property

Refactor the code for parsing and generating the Range, taking
advantage of existing API in GstRtspTimeRange.

Only use the TCP protocol in that mode, as per the specification.

Generate an accurate segment when in that mode, and signal to the
depayloader that it should not generate its own segment, through
the "onvif-mode" field in the caps, see
<https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/merge_requests/328>
for more information.

Translate trickmode seek flags to their ONVIF representation

Expose an onvif-rate-control property
This commit is contained in:
Mathieu Duponchelle 2019-07-12 22:33:08 +02:00 committed by Mathieu Duponchelle
parent 544f8fecf4
commit 5f1a732bc7
2 changed files with 193 additions and 24 deletions

View file

@ -280,6 +280,8 @@ gst_rtsp_backchannel_get_type (void)
#define DEFAULT_VERSION GST_RTSP_VERSION_1_0 #define DEFAULT_VERSION GST_RTSP_VERSION_1_0
#define DEFAULT_BACKCHANNEL GST_RTSP_BACKCHANNEL_NONE #define DEFAULT_BACKCHANNEL GST_RTSP_BACKCHANNEL_NONE
#define DEFAULT_TEARDOWN_TIMEOUT (100 * GST_MSECOND) #define DEFAULT_TEARDOWN_TIMEOUT (100 * GST_MSECOND)
#define DEFAULT_ONVIF_MODE FALSE
#define DEFAULT_ONVIF_RATE_CONTROL TRUE
enum enum
{ {
@ -325,6 +327,8 @@ enum
PROP_DEFAULT_VERSION, PROP_DEFAULT_VERSION,
PROP_BACKCHANNEL, PROP_BACKCHANNEL,
PROP_TEARDOWN_TIMEOUT, PROP_TEARDOWN_TIMEOUT,
PROP_ONVIF_MODE,
PROP_ONVIF_RATE_CONTROL
}; };
#define GST_TYPE_RTSP_NAT_METHOD (gst_rtsp_nat_method_get_type()) #define GST_TYPE_RTSP_NAT_METHOD (gst_rtsp_nat_method_get_type())
@ -936,6 +940,43 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass)
0, G_MAXUINT64, DEFAULT_TEARDOWN_TIMEOUT, 0, G_MAXUINT64, DEFAULT_TEARDOWN_TIMEOUT,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstRtspSrc:onvif-mode
*
* Act as an ONVIF client. When set to %TRUE:
*
* - seeks will be interpreted as nanoseconds since prime epoch (1900-01-01)
*
* - #GstRtspSrc:onvif-rate-control can be used to request that the server sends
* data as fast as it can
*
* - TCP is picked as the transport protocol
*
* - Trickmode flags in seek events are transformed into the appropriate ONVIF
* request headers
*
* Since: 1.18
*/
g_object_class_install_property (gobject_class, PROP_ONVIF_MODE,
g_param_spec_boolean ("onvif-mode", "Onvif Mode",
"Act as an ONVIF client",
DEFAULT_ONVIF_MODE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstRtspSrc:onvif-rate-control
*
* When in onvif-mode, whether to set Rate-Control to yes or no. When set
* to %FALSE, the server will deliver data as fast as the client can consume
* it.
*
* Since: 1.18
*/
g_object_class_install_property (gobject_class, PROP_ONVIF_RATE_CONTROL,
g_param_spec_boolean ("onvif-rate-control", "Onvif Rate Control",
"When in onvif-mode, whether to set Rate-Control to yes or no",
DEFAULT_ONVIF_RATE_CONTROL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/** /**
* GstRTSPSrc::handle-request: * GstRTSPSrc::handle-request:
* @rtspsrc: a #GstRTSPSrc * @rtspsrc: a #GstRTSPSrc
@ -1336,6 +1377,8 @@ gst_rtspsrc_init (GstRTSPSrc * src)
src->default_version = DEFAULT_VERSION; src->default_version = DEFAULT_VERSION;
src->version = GST_RTSP_VERSION_INVALID; src->version = GST_RTSP_VERSION_INVALID;
src->teardown_timeout = DEFAULT_TEARDOWN_TIMEOUT; src->teardown_timeout = DEFAULT_TEARDOWN_TIMEOUT;
src->onvif_mode = DEFAULT_ONVIF_MODE;
src->onvif_rate_control = DEFAULT_ONVIF_RATE_CONTROL;
/* get a list of all extensions */ /* get a list of all extensions */
src->extensions = gst_rtsp_ext_list_get (); src->extensions = gst_rtsp_ext_list_get ();
@ -1674,6 +1717,12 @@ gst_rtspsrc_set_property (GObject * object, guint prop_id, const GValue * value,
case PROP_TEARDOWN_TIMEOUT: case PROP_TEARDOWN_TIMEOUT:
rtspsrc->teardown_timeout = g_value_get_uint64 (value); rtspsrc->teardown_timeout = g_value_get_uint64 (value);
break; break;
case PROP_ONVIF_MODE:
rtspsrc->onvif_mode = g_value_get_boolean (value);
break;
case PROP_ONVIF_RATE_CONTROL:
rtspsrc->onvif_rate_control = g_value_get_boolean (value);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break; break;
@ -1838,6 +1887,12 @@ gst_rtspsrc_get_property (GObject * object, guint prop_id, GValue * value,
case PROP_TEARDOWN_TIMEOUT: case PROP_TEARDOWN_TIMEOUT:
g_value_set_uint64 (value, rtspsrc->teardown_timeout); g_value_set_uint64 (value, rtspsrc->teardown_timeout);
break; break;
case PROP_ONVIF_MODE:
g_value_set_boolean (value, rtspsrc->onvif_mode);
break;
case PROP_ONVIF_RATE_CONTROL:
g_value_set_boolean (value, rtspsrc->onvif_rate_control);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break; break;
@ -2714,6 +2769,8 @@ gst_rtspsrc_perform_seek (GstRTSPSrc * src, GstEvent * event)
flush = flags & GST_SEEK_FLAG_FLUSH; flush = flags & GST_SEEK_FLAG_FLUSH;
server_side_trickmode = flags & GST_SEEK_FLAG_TRICKMODE; server_side_trickmode = flags & GST_SEEK_FLAG_TRICKMODE;
gst_event_parse_seek_trickmode_interval (event, &src->trickmode_interval);
/* now we need to make sure the streaming thread is stopped. We do this by /* now we need to make sure the streaming thread is stopped. We do this by
* either sending a FLUSH_START event downstream which will cause the * either sending a FLUSH_START event downstream which will cause the
* streaming thread to stop with a WRONG_STATE. * streaming thread to stop with a WRONG_STATE.
@ -2745,6 +2802,7 @@ gst_rtspsrc_perform_seek (GstRTSPSrc * src, GstEvent * event)
/* configure the seek parameters in the seeksegment. We will then have the /* configure the seek parameters in the seeksegment. We will then have the
* right values in the segment to perform the seek */ * right values in the segment to perform the seek */
GST_DEBUG_OBJECT (src, "configuring seek"); GST_DEBUG_OBJECT (src, "configuring seek");
seeksegment.duration = GST_CLOCK_TIME_NONE;
gst_segment_do_seek (&seeksegment, rate, format, flags, gst_segment_do_seek (&seeksegment, rate, format, flags,
cur_type, cur, stop_type, stop, &update); cur_type, cur, stop_type, stop, &update);
@ -4737,8 +4795,8 @@ gst_rtspsrc_configure_caps (GstRTSPSrc * src, GstSegment * segment,
GST_DEBUG_OBJECT (src, "configuring stream caps"); GST_DEBUG_OBJECT (src, "configuring stream caps");
start = segment->position; start = segment->rate > 0.0 ? segment->start : segment->stop;
stop = segment->duration; stop = segment->rate > 0.0 ? segment->stop : segment->start;
play_speed = segment->rate; play_speed = segment->rate;
play_scale = segment->applied_rate; play_scale = segment->applied_rate;
@ -4770,6 +4828,8 @@ gst_rtspsrc_configure_caps (GstRTSPSrc * src, GstSegment * segment,
gst_caps_set_simple (caps, "npt-stop", G_TYPE_UINT64, stop, NULL); gst_caps_set_simple (caps, "npt-stop", G_TYPE_UINT64, stop, NULL);
gst_caps_set_simple (caps, "play-speed", G_TYPE_DOUBLE, play_speed, NULL); gst_caps_set_simple (caps, "play-speed", G_TYPE_DOUBLE, play_speed, NULL);
gst_caps_set_simple (caps, "play-scale", G_TYPE_DOUBLE, play_scale, NULL); gst_caps_set_simple (caps, "play-scale", G_TYPE_DOUBLE, play_scale, NULL);
gst_caps_set_simple (caps, "onvif-mode", G_TYPE_BOOLEAN, src->onvif_mode,
NULL);
item->caps = caps; item->caps = caps;
GST_DEBUG_OBJECT (src, "stream %p, pt %d, caps %" GST_PTR_FORMAT, stream, GST_DEBUG_OBJECT (src, "stream %p, pt %d, caps %" GST_PTR_FORMAT, stream,
@ -5358,10 +5418,15 @@ gst_rtspsrc_handle_data (GstRTSPSrc * src, GstRTSPMessage * message)
/* If needed send a new segment, don't forget we are live and buffer are /* If needed send a new segment, don't forget we are live and buffer are
* timestamped with running time */ * timestamped with running time */
if (src->need_segment) { if (src->need_segment) {
GstSegment segment;
src->need_segment = FALSE; src->need_segment = FALSE;
gst_segment_init (&segment, GST_FORMAT_TIME); if (src->onvif_mode) {
gst_rtspsrc_push_event (src, gst_event_new_segment (&segment)); gst_rtspsrc_push_event (src, gst_event_new_segment (&src->out_segment));
} else {
GstSegment segment;
gst_segment_init (&segment, GST_FORMAT_TIME);
gst_rtspsrc_push_event (src, gst_event_new_segment (&segment));
}
} }
if (stream->need_caps) { if (stream->need_caps) {
@ -7073,6 +7138,10 @@ gst_rtspsrc_setup_streams_start (GstRTSPSrc * src, gboolean async)
protocols = src->cur_protocols; protocols = src->cur_protocols;
} }
/* In ONVIF mode, we only want to try TCP transport */
if (src->onvif_mode && (protocols & GST_RTSP_LOWER_TRANS_TCP))
protocols = GST_RTSP_LOWER_TRANS_TCP;
if (protocols == 0) if (protocols == 0)
goto no_protocols; goto no_protocols;
@ -7429,8 +7498,9 @@ cleanup_error:
static gboolean static gboolean
gst_rtspsrc_parse_range (GstRTSPSrc * src, const gchar * range, gst_rtspsrc_parse_range (GstRTSPSrc * src, const gchar * range,
GstSegment * segment) GstSegment * segment, gboolean update_duration)
{ {
GstClockTime begin_seconds, end_seconds;
gint64 seconds; gint64 seconds;
GstRTSPTimeRange *therange; GstRTSPTimeRange *therange;
@ -7447,6 +7517,8 @@ gst_rtspsrc_parse_range (GstRTSPSrc * src, const gchar * range,
return FALSE; return FALSE;
} }
gst_rtsp_range_get_times (therange, &begin_seconds, &end_seconds);
GST_DEBUG_OBJECT (src, "range: type %d, min %f - type %d, max %f ", GST_DEBUG_OBJECT (src, "range: type %d, min %f - type %d, max %f ",
therange->min.type, therange->min.seconds, therange->max.type, therange->min.type, therange->min.seconds, therange->max.type,
therange->max.seconds); therange->max.seconds);
@ -7456,14 +7528,18 @@ gst_rtspsrc_parse_range (GstRTSPSrc * src, const gchar * range,
else if (therange->min.type == GST_RTSP_TIME_END) else if (therange->min.type == GST_RTSP_TIME_END)
seconds = 0; seconds = 0;
else else
seconds = therange->min.seconds * GST_SECOND; seconds = begin_seconds;
GST_DEBUG_OBJECT (src, "range: min %" GST_TIME_FORMAT, GST_DEBUG_OBJECT (src, "range: min %" GST_TIME_FORMAT,
GST_TIME_ARGS (seconds)); GST_TIME_ARGS (seconds));
/* we need to start playback without clipping from the position reported by /* we need to start playback without clipping from the position reported by
* the server */ * the server */
segment->start = seconds; if (segment->rate > 0.0)
segment->start = seconds;
else
segment->stop = seconds;
segment->position = seconds; segment->position = seconds;
if (therange->max.type == GST_RTSP_TIME_NOW) if (therange->max.type == GST_RTSP_TIME_NOW)
@ -7471,7 +7547,7 @@ gst_rtspsrc_parse_range (GstRTSPSrc * src, const gchar * range,
else if (therange->max.type == GST_RTSP_TIME_END) else if (therange->max.type == GST_RTSP_TIME_END)
seconds = -1; seconds = -1;
else else
seconds = therange->max.seconds * GST_SECOND; seconds = end_seconds;
GST_DEBUG_OBJECT (src, "range: max %" GST_TIME_FORMAT, GST_DEBUG_OBJECT (src, "range: max %" GST_TIME_FORMAT,
GST_TIME_ARGS (seconds)); GST_TIME_ARGS (seconds));
@ -7484,13 +7560,22 @@ gst_rtspsrc_parse_range (GstRTSPSrc * src, const gchar * range,
} }
/* live (WMS) might send min == max, which is not worth recording */ /* live (WMS) might send min == max, which is not worth recording */
if (segment->duration == -1 && seconds == segment->start) if (segment->duration == -1 && seconds == begin_seconds)
seconds = -1; seconds = -1;
/* don't change duration with unknown value, we might have a valid value /* don't change duration with unknown value, we might have a valid value
* there that we want to keep. */ * there that we want to keep. Also, the total duration of the stream
if (seconds != -1) * can only be determined from the response to a DESCRIBE request, not
* from a PLAY request where we might have requested a custom range, so
* don't update duration in that case */
if (update_duration && seconds != -1) {
segment->duration = seconds; segment->duration = seconds;
}
if (segment->rate > 0.0)
segment->stop = seconds;
else
segment->start = seconds;
return TRUE; return TRUE;
} }
@ -7592,7 +7677,7 @@ gst_rtspsrc_open_from_sdp (GstRTSPSrc * src, GstSDPMessage * sdp,
break; break;
/* keep track of the range and configure it in the segment */ /* keep track of the range and configure it in the segment */
if (gst_rtspsrc_parse_range (src, range, &src->segment)) if (gst_rtspsrc_parse_range (src, range, &src->segment, TRUE))
break; break;
} }
} }
@ -7657,6 +7742,7 @@ gst_rtspsrc_open_from_sdp (GstRTSPSrc * src, GstSDPMessage * sdp,
/* reset our state */ /* reset our state */
src->need_range = TRUE; src->need_range = TRUE;
src->server_side_trickmode = FALSE; src->server_side_trickmode = FALSE;
src->trickmode_interval = 0;
src->state = GST_RTSP_STATE_READY; src->state = GST_RTSP_STATE_READY;
@ -8172,19 +8258,67 @@ gst_rtspsrc_get_float (const gchar * dstr)
static gchar * static gchar *
gen_range_header (GstRTSPSrc * src, GstSegment * segment) gen_range_header (GstRTSPSrc * src, GstSegment * segment)
{ {
gchar val_str[G_ASCII_DTOSTR_BUF_SIZE] = { 0, }; GstRTSPTimeRange range = { 0, };
gdouble begin_seconds, end_seconds;
if (src->range && src->range->min.type == GST_RTSP_TIME_NOW) { if (segment->rate > 0) {
g_strlcpy (val_str, "now", sizeof (val_str)); begin_seconds = (gdouble) segment->start / GST_SECOND;
end_seconds = (gdouble) segment->stop / GST_SECOND;
} else { } else {
if (segment->position == 0) { begin_seconds = (gdouble) segment->stop / GST_SECOND;
g_strlcpy (val_str, "0", sizeof (val_str)); end_seconds = (gdouble) segment->start / GST_SECOND;
} else {
g_ascii_dtostr (val_str, sizeof (val_str),
((gdouble) segment->position) / GST_SECOND);
}
} }
return g_strdup_printf ("npt=%s-", val_str);
if (src->onvif_mode) {
GDateTime *prime_epoch, *datetime;
range.unit = GST_RTSP_RANGE_CLOCK;
prime_epoch = g_date_time_new_utc (1900, 1, 1, 0, 0, 0);
datetime = g_date_time_add_seconds (prime_epoch, begin_seconds);
range.min.type = GST_RTSP_TIME_UTC;
range.min2.year = g_date_time_get_year (datetime);
range.min2.month = g_date_time_get_month (datetime);
range.min2.day = g_date_time_get_day_of_month (datetime);
range.min.seconds =
g_date_time_get_seconds (datetime) +
g_date_time_get_minute (datetime) * 60 +
g_date_time_get_hour (datetime) * 60 * 60;
g_date_time_unref (datetime);
datetime = g_date_time_add_seconds (prime_epoch, end_seconds);
range.max.type = GST_RTSP_TIME_UTC;
range.max2.year = g_date_time_get_year (datetime);
range.max2.month = g_date_time_get_month (datetime);
range.max2.day = g_date_time_get_day_of_month (datetime);
range.max.seconds =
g_date_time_get_seconds (datetime) +
g_date_time_get_minute (datetime) * 60 +
g_date_time_get_hour (datetime) * 60 * 60;
g_date_time_unref (datetime);
g_date_time_unref (prime_epoch);
} else {
range.unit = GST_RTSP_RANGE_NPT;
range.min.type = GST_RTSP_TIME_SECONDS;
range.min.seconds = begin_seconds;
range.max.type = GST_RTSP_TIME_SECONDS;
range.max.seconds = end_seconds;
}
/* Don't set end bounds when not required to */
if (!GST_CLOCK_TIME_IS_VALID (segment->stop)) {
if (segment->rate > 0)
range.max.type = GST_RTSP_TIME_END;
else
range.min.type = GST_RTSP_TIME_END;
}
return gst_rtsp_range_to_string (&range);
} }
static void static void
@ -8328,6 +8462,26 @@ restart:
} }
} }
if (src->onvif_mode) {
if (segment->flags & GST_SEEK_FLAG_TRICKMODE_KEY_UNITS) {
gchar *hval;
if (src->trickmode_interval)
hval =
g_strdup_printf ("intra/%" G_GUINT64_FORMAT,
src->trickmode_interval / GST_MSECOND);
else
hval = g_strdup ("intra");
gst_rtsp_message_add_header (&request, GST_RTSP_HDR_FRAMES, hval);
g_free (hval);
} else if (segment->flags & GST_SEEK_FLAG_TRICKMODE_FORWARD_PREDICTED) {
gst_rtsp_message_add_header (&request, GST_RTSP_HDR_FRAMES,
"predicted");
}
}
if (seek_style) if (seek_style)
gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SEEK_STYLE, gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SEEK_STYLE,
seek_style); seek_style);
@ -8339,6 +8493,14 @@ restart:
gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE, gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE,
BACKCHANNEL_ONVIF_HDR_REQUIRE_VAL); BACKCHANNEL_ONVIF_HDR_REQUIRE_VAL);
if (src->onvif_mode) {
if (src->onvif_rate_control)
gst_rtsp_message_add_header (&request, GST_RTSP_HDR_RATE_CONTROL,
"yes");
else
gst_rtsp_message_add_header (&request, GST_RTSP_HDR_RATE_CONTROL, "no");
}
if (async) if (async)
GST_ELEMENT_PROGRESS (src, CONTINUE, "request", ("Sending PLAY request")); GST_ELEMENT_PROGRESS (src, CONTINUE, "request", ("Sending PLAY request"));
@ -8392,7 +8554,7 @@ restart:
* Play Time) and should be put in the NEWSEGMENT position field. */ * Play Time) and should be put in the NEWSEGMENT position field. */
if (gst_rtsp_message_get_header (&response, GST_RTSP_HDR_RANGE, &hval, if (gst_rtsp_message_get_header (&response, GST_RTSP_HDR_RANGE, &hval,
0) == GST_RTSP_OK) 0) == GST_RTSP_OK)
gst_rtspsrc_parse_range (src, hval, segment); gst_rtspsrc_parse_range (src, hval, segment, FALSE);
/* assume 1.0 rate now, overwrite when the SCALE or SPEED headers are present. */ /* assume 1.0 rate now, overwrite when the SCALE or SPEED headers are present. */
segment->rate = 1.0; segment->rate = 1.0;
@ -8427,6 +8589,9 @@ restart:
if (control) if (control)
break; break;
} }
memcpy (&src->out_segment, segment, sizeof (GstSegment));
/* configure the caps of the streams after we parsed all headers. Only reset /* configure the caps of the streams after we parsed all headers. Only reset
* the manager object when we set a new Range header (we did a seek) */ * the manager object when we set a new Range header (we did a seek) */
gst_rtspsrc_configure_caps (src, segment, src->need_range); gst_rtspsrc_configure_caps (src, segment, src->need_range);

View file

@ -208,8 +208,10 @@ struct _GstRTSPSrc {
gboolean running; gboolean running;
gboolean need_range; gboolean need_range;
gboolean server_side_trickmode; gboolean server_side_trickmode;
GstClockTime trickmode_interval;
gint free_channel; gint free_channel;
gboolean need_segment; gboolean need_segment;
GstSegment out_segment;
GstClockTime base_time; GstClockTime base_time;
/* UDP mode loop */ /* UDP mode loop */
@ -273,6 +275,8 @@ struct _GstRTSPSrc {
gboolean max_ts_offset_is_set; gboolean max_ts_offset_is_set;
gint backchannel; gint backchannel;
GstClockTime teardown_timeout; GstClockTime teardown_timeout;
gboolean onvif_mode;
gboolean onvif_rate_control;
/* state */ /* state */
GstRTSPState state; GstRTSPState state;