#define DEFAULT_VERSION GST_RTSP_VERSION_1_0
#define DEFAULT_BACKCHANNEL GST_RTSP_BACKCHANNEL_NONE
#define DEFAULT_TEARDOWN_TIMEOUT (100 * GST_MSECOND)
+#define DEFAULT_ONVIF_MODE FALSE
+#define DEFAULT_ONVIF_RATE_CONTROL TRUE
enum
{
PROP_DEFAULT_VERSION,
PROP_BACKCHANNEL,
PROP_TEARDOWN_TIMEOUT,
+ PROP_ONVIF_MODE,
+ PROP_ONVIF_RATE_CONTROL
};
#define GST_TYPE_RTSP_NAT_METHOD (gst_rtsp_nat_method_get_type())
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:
* @rtspsrc: a #GstRTSPSrc
* @request: a #GstRTSPMessage
src->default_version = DEFAULT_VERSION;
src->version = GST_RTSP_VERSION_INVALID;
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 */
src->extensions = gst_rtsp_ext_list_get ();
case PROP_TEARDOWN_TIMEOUT:
rtspsrc->teardown_timeout = g_value_get_uint64 (value);
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:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
case PROP_TEARDOWN_TIMEOUT:
g_value_set_uint64 (value, rtspsrc->teardown_timeout);
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:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
flush = flags & GST_SEEK_FLAG_FLUSH;
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
* either sending a FLUSH_START event downstream which will cause the
* streaming thread to stop with a WRONG_STATE.
/* configure the seek parameters in the seeksegment. We will then have the
* right values in the segment to perform the seek */
GST_DEBUG_OBJECT (src, "configuring seek");
+ seeksegment.duration = GST_CLOCK_TIME_NONE;
gst_segment_do_seek (&seeksegment, rate, format, flags,
cur_type, cur, stop_type, stop, &update);
GST_DEBUG_OBJECT (src, "configuring stream caps");
- start = segment->position;
- stop = segment->duration;
+ start = segment->rate > 0.0 ? segment->start : segment->stop;
+ stop = segment->rate > 0.0 ? segment->stop : segment->start;
play_speed = segment->rate;
play_scale = segment->applied_rate;
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-scale", G_TYPE_DOUBLE, play_scale, NULL);
+ gst_caps_set_simple (caps, "onvif-mode", G_TYPE_BOOLEAN, src->onvif_mode,
+ NULL);
item->caps = caps;
GST_DEBUG_OBJECT (src, "stream %p, pt %d, caps %" GST_PTR_FORMAT, stream,
/* If needed send a new segment, don't forget we are live and buffer are
* timestamped with running time */
if (src->need_segment) {
- GstSegment segment;
src->need_segment = FALSE;
- gst_segment_init (&segment, GST_FORMAT_TIME);
- gst_rtspsrc_push_event (src, gst_event_new_segment (&segment));
+ if (src->onvif_mode) {
+ 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) {
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)
goto no_protocols;
static gboolean
gst_rtspsrc_parse_range (GstRTSPSrc * src, const gchar * range,
- GstSegment * segment)
+ GstSegment * segment, gboolean update_duration)
{
+ GstClockTime begin_seconds, end_seconds;
gint64 seconds;
GstRTSPTimeRange *therange;
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 ",
therange->min.type, therange->min.seconds, therange->max.type,
therange->max.seconds);
else if (therange->min.type == GST_RTSP_TIME_END)
seconds = 0;
else
- seconds = therange->min.seconds * GST_SECOND;
+ seconds = begin_seconds;
GST_DEBUG_OBJECT (src, "range: min %" GST_TIME_FORMAT,
GST_TIME_ARGS (seconds));
/* we need to start playback without clipping from the position reported by
* the server */
- segment->start = seconds;
+ if (segment->rate > 0.0)
+ segment->start = seconds;
+ else
+ segment->stop = seconds;
+
segment->position = seconds;
if (therange->max.type == GST_RTSP_TIME_NOW)
else if (therange->max.type == GST_RTSP_TIME_END)
seconds = -1;
else
- seconds = therange->max.seconds * GST_SECOND;
+ seconds = end_seconds;
GST_DEBUG_OBJECT (src, "range: max %" GST_TIME_FORMAT,
GST_TIME_ARGS (seconds));
}
/* 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;
/* don't change duration with unknown value, we might have a valid value
- * there that we want to keep. */
- if (seconds != -1)
+ * there that we want to keep. Also, the total duration of the stream
+ * 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;
+ }
+
+ if (segment->rate > 0.0)
+ segment->stop = seconds;
+ else
+ segment->start = seconds;
return TRUE;
}
break;
/* 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;
}
}
/* reset our state */
src->need_range = TRUE;
src->server_side_trickmode = FALSE;
+ src->trickmode_interval = 0;
src->state = GST_RTSP_STATE_READY;
static gchar *
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) {
- g_strlcpy (val_str, "now", sizeof (val_str));
+ if (segment->rate > 0) {
+ begin_seconds = (gdouble) segment->start / GST_SECOND;
+ end_seconds = (gdouble) segment->stop / GST_SECOND;
} else {
- if (segment->position == 0) {
- g_strlcpy (val_str, "0", sizeof (val_str));
- } else {
- g_ascii_dtostr (val_str, sizeof (val_str),
- ((gdouble) segment->position) / GST_SECOND);
- }
+ begin_seconds = (gdouble) segment->stop / GST_SECOND;
+ end_seconds = (gdouble) segment->start / 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
}
}
+ 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)
gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SEEK_STYLE,
seek_style);
gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE,
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)
GST_ELEMENT_PROGRESS (src, CONTINUE, "request", ("Sending PLAY request"));
* Play Time) and should be put in the NEWSEGMENT position field. */
if (gst_rtsp_message_get_header (&response, GST_RTSP_HDR_RANGE, &hval,
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. */
segment->rate = 1.0;
if (control)
break;
}
+
+ memcpy (&src->out_segment, segment, sizeof (GstSegment));
+
/* 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) */
gst_rtspsrc_configure_caps (src, segment, src->need_range);