gboolean eos_shutdown;
guint buffer_size;
GstRTSPAddressPool *pool;
+ gchar *multicast_iface;
gboolean blocked;
GstRTSPTransportMode transport_mode;
+ gboolean stop_on_disconnect;
GstElement *element;
GRecMutex state_lock; /* locking order: state lock, lock */
GstRTSPMediaStatus status; /* protected by lock */
gint prepare_count;
gint n_active;
- gboolean adding;
+ gboolean complete;
/* the pipeline for the media */
GstElement *pipeline;
- GstElement *fakesink; /* protected by lock */
GSource *source;
guint id;
GstRTSPThread *thread;
+ GList *pending_pipeline_elements;
gboolean time_provider;
GstNetTimeProvider *nettime;
gboolean is_live;
- gboolean seekable;
+ GstClockTimeDiff seekable;
gboolean buffering;
GstState target_state;
GList *payloads; /* protected by lock */
GstClockTime rtx_time; /* protected by lock */
+ gboolean do_retransmission; /* protected by lock */
guint latency; /* protected by lock */
+ GstClock *clock; /* protected by lock */
+ GstRTSPPublishClockMode publish_clock_mode;
+
+ /* Dynamic element handling */
+ guint nb_dynamic_elements;
+ guint no_more_pads_pending;
};
#define DEFAULT_SHARED FALSE
#define DEFAULT_TIME_PROVIDER FALSE
#define DEFAULT_LATENCY 200
#define DEFAULT_TRANSPORT_MODE GST_RTSP_TRANSPORT_MODE_PLAY
+#define DEFAULT_STOP_ON_DISCONNECT TRUE
+
+#define DEFAULT_DO_RETRANSMISSION FALSE
/* define to dump received RTCP packets */
#undef DUMP_STATS
PROP_TIME_PROVIDER,
PROP_LATENCY,
PROP_TRANSPORT_MODE,
+ PROP_STOP_ON_DISCONNECT,
+ PROP_CLOCK,
PROP_LAST
};
static guint gst_rtsp_media_signals[SIGNAL_LAST] = { 0 };
+static gboolean check_complete (GstRTSPMedia * media);
+
#define C_ENUM(v) ((gint) v)
GType
return (GType) id;
}
+GType
+gst_rtsp_publish_clock_mode_get_type (void)
+{
+ static gsize id = 0;
+ static const GEnumValue values[] = {
+ {C_ENUM (GST_RTSP_PUBLISH_CLOCK_MODE_NONE),
+ "GST_RTSP_PUBLISH_CLOCK_MODE_NONE", "none"},
+ {C_ENUM (GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK),
+ "GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK",
+ "clock"},
+ {C_ENUM (GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET),
+ "GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET",
+ "clock-and-offset"},
+ {0, NULL, NULL}
+ };
+
+ if (g_once_init_enter (&id)) {
+ GType tmp = g_enum_register_static ("GstRTSPPublishClockMode", values);
+ g_once_init_leave (&id, tmp);
+ }
+ return (GType) id;
+}
+
G_DEFINE_TYPE (GstRTSPMedia, gst_rtsp_media, G_TYPE_OBJECT);
static void
GST_TYPE_RTSP_TRANSPORT_MODE, DEFAULT_TRANSPORT_MODE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_STOP_ON_DISCONNECT,
+ g_param_spec_boolean ("stop-on-disconnect", "Stop On Disconnect",
+ "If this media pipeline should be stopped "
+ "when a client disconnects without TEARDOWN",
+ DEFAULT_STOP_ON_DISCONNECT,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_CLOCK,
+ g_param_spec_object ("clock", "Clock",
+ "Clock to be used by the media pipeline",
+ GST_TYPE_CLOCK, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
gst_rtsp_media_signals[SIGNAL_NEW_STREAM] =
g_signal_new ("new-stream", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GstRTSPMediaClass, new_stream), NULL, NULL,
priv->buffer_size = DEFAULT_BUFFER_SIZE;
priv->time_provider = DEFAULT_TIME_PROVIDER;
priv->transport_mode = DEFAULT_TRANSPORT_MODE;
+ priv->stop_on_disconnect = DEFAULT_STOP_ON_DISCONNECT;
+ priv->publish_clock_mode = GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK;
+ priv->do_retransmission = DEFAULT_DO_RETRANSMISSION;
}
static void
g_ptr_array_unref (priv->streams);
g_list_free_full (priv->dynamic, gst_object_unref);
+ g_list_free_full (priv->pending_pipeline_elements, gst_object_unref);
if (priv->pipeline)
gst_object_unref (priv->pipeline);
g_object_unref (priv->pool);
if (priv->payloads)
g_list_free (priv->payloads);
+ g_free (priv->multicast_iface);
g_mutex_clear (&priv->lock);
g_cond_clear (&priv->cond);
g_rec_mutex_clear (&priv->state_lock);
case PROP_TRANSPORT_MODE:
g_value_set_flags (value, gst_rtsp_media_get_transport_mode (media));
break;
+ case PROP_STOP_ON_DISCONNECT:
+ g_value_set_boolean (value, gst_rtsp_media_is_stop_on_disconnect (media));
+ break;
+ case PROP_CLOCK:
+ g_value_take_object (value, gst_rtsp_media_get_clock (media));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
}
case PROP_TRANSPORT_MODE:
gst_rtsp_media_set_transport_mode (media, g_value_get_flags (value));
break;
+ case PROP_STOP_ON_DISCONNECT:
+ gst_rtsp_media_set_stop_on_disconnect (media,
+ g_value_get_boolean (value));
+ break;
+ case PROP_CLOCK:
+ gst_rtsp_media_set_clock (media, g_value_get_object (value));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
}
typedef struct
{
gint64 position;
+ gboolean complete_streams_only;
gboolean ret;
} DoQueryPositionData;
{
gint64 tmp;
+ if (!gst_rtsp_stream_is_sender (stream))
+ return;
+
+ if (data->complete_streams_only && !gst_rtsp_stream_is_complete (stream)) {
+ GST_DEBUG_OBJECT (stream, "stream not complete, do not query position");
+ return;
+ }
+
if (gst_rtsp_stream_query_position (stream, &tmp)) {
- data->position = MAX (data->position, tmp);
+ data->position = MIN (data->position, tmp);
data->ret = TRUE;
}
+
+ GST_INFO_OBJECT (stream, "media position: %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (data->position));
}
static gboolean
priv = media->priv;
- data.position = -1;
+ data.position = G_MAXINT64;
data.ret = FALSE;
+ /* if the media is complete, i.e. one or more streams have been configured
+ * with sinks, then we want to query the position on those streams only.
+ * a query on an incmplete stream may return a position that originates from
+ * an earlier preroll */
+ if (check_complete (media))
+ data.complete_streams_only = TRUE;
+ else
+ data.complete_streams_only = FALSE;
+
g_ptr_array_foreach (priv->streams, (GFunc) do_query_position, &data);
- *position = data.position;
+ if (!data.ret)
+ *position = GST_CLOCK_TIME_NONE;
+ else
+ *position = data.position;
return data.ret;
}
static void
do_query_stop (GstRTSPStream * stream, DoQueryStopData * data)
{
- gint64 tmp;
+ gint64 tmp = 0;
if (gst_rtsp_stream_query_stop (stream, &tmp)) {
data->stop = MAX (data->stop, tmp);
return rtpbin;
}
+static gboolean
+is_receive_only (GstRTSPMedia * media)
+{
+ GstRTSPMediaPrivate *priv = media->priv;
+ gboolean recive_only = TRUE;
+ guint i;
+
+ for (i = 0; i < priv->streams->len; i++) {
+ GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
+ if (gst_rtsp_stream_is_sender (stream) ||
+ !gst_rtsp_stream_is_receiver (stream)) {
+ recive_only = FALSE;
+ break;
+ }
+ }
+
+ return recive_only;
+}
+
+/* must be called with state lock */
+static void
+check_seekable (GstRTSPMedia * media)
+{
+ GstQuery *query;
+ GstRTSPMediaPrivate *priv = media->priv;
+
+ /* Update the seekable state of the pipeline in case it changed */
+ if (is_receive_only (media)) {
+ /* TODO: Seeking for "receive-only"? */
+ priv->seekable = -1;
+ } else {
+ guint i, n = priv->streams->len;
+
+ for (i = 0; i < n; i++) {
+ GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
+
+ if (gst_rtsp_stream_get_publish_clock_mode (stream) ==
+ GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET) {
+ priv->seekable = -1;
+ return;
+ }
+ }
+ }
+
+ query = gst_query_new_seeking (GST_FORMAT_TIME);
+ if (gst_element_query (priv->pipeline, query)) {
+ GstFormat format;
+ gboolean seekable;
+ gint64 start, end;
+
+ gst_query_parse_seeking (query, &format, &seekable, &start, &end);
+ priv->seekable = seekable ? G_MAXINT64 : 0;
+ } else if (priv->streams->len) {
+ gboolean seekable = TRUE;
+ guint i, n = priv->streams->len;
+
+ GST_DEBUG_OBJECT (media, "Checking %d streams", n);
+ for (i = 0; i < n; i++) {
+ GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
+ seekable &= gst_rtsp_stream_seekable (stream);
+ }
+ priv->seekable = seekable ? G_MAXINT64 : -1;
+ }
+
+ GST_DEBUG_OBJECT (media, "seekable:%" G_GINT64_FORMAT, priv->seekable);
+
+ gst_query_unref (query);
+}
+
+/* must be called with state lock */
+static gboolean
+check_complete (GstRTSPMedia * media)
+{
+ GstRTSPMediaPrivate *priv = media->priv;
+
+ guint i, n = priv->streams->len;
+
+ for (i = 0; i < n; i++) {
+ GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
+
+ if (gst_rtsp_stream_is_complete (stream))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
/* must be called with state lock */
static void
collect_media_stats (GstRTSPMedia * media)
priv->range.max.seconds = ((gdouble) stop) / GST_SECOND;
priv->range_stop = stop;
}
+
+ check_seekable (media);
}
}
GstRTSPMediaPrivate *priv;
GstElement *old;
GstNetTimeProvider *nettime;
+ GList *l;
g_return_if_fail (GST_IS_RTSP_MEDIA (media));
g_return_if_fail (GST_IS_PIPELINE (pipeline));
gst_object_unref (nettime);
gst_bin_add (GST_BIN_CAST (pipeline), priv->element);
+
+ for (l = priv->pending_pipeline_elements; l; l = l->next) {
+ gst_bin_add (GST_BIN_CAST (pipeline), l->data);
+ }
+ g_list_free (priv->pending_pipeline_elements);
+ priv->pending_pipeline_elements = NULL;
}
/**
* gst_rtsp_media_set_permissions:
* @media: a #GstRTSPMedia
- * @permissions: (transfer none): a #GstRTSPPermissions
+ * @permissions: (transfer none) (nullable): a #GstRTSPPermissions
*
* Set @permissions on @media.
*/
*
* Get the permissions object from @media.
*
- * Returns: (transfer full): a #GstRTSPPermissions object, unref after usage.
+ * Returns: (transfer full) (nullable): a #GstRTSPPermissions object, unref after usage.
*/
GstRTSPPermissions *
gst_rtsp_media_get_permissions (GstRTSPMedia * media)
}
/**
+ * gst_rtsp_media_set_stop_on_disconnect:
+ * @media: a #GstRTSPMedia
+ * @stop_on_disconnect: the new value
+ *
+ * Set or unset if the pipeline for @media should be stopped when a
+ * client disconnects without sending TEARDOWN.
+ */
+void
+gst_rtsp_media_set_stop_on_disconnect (GstRTSPMedia * media,
+ gboolean stop_on_disconnect)
+{
+ GstRTSPMediaPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ priv->stop_on_disconnect = stop_on_disconnect;
+ g_mutex_unlock (&priv->lock);
+}
+
+/**
+ * gst_rtsp_media_is_stop_on_disconnect:
+ * @media: a #GstRTSPMedia
+ *
+ * Check if the pipeline for @media will be stopped when a client disconnects
+ * without sending TEARDOWN.
+ *
+ * Returns: %TRUE if the media will be stopped when a client disconnects
+ * without sending TEARDOWN.
+ */
+gboolean
+gst_rtsp_media_is_stop_on_disconnect (GstRTSPMedia * media)
+{
+ GstRTSPMediaPrivate *priv;
+ gboolean res;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), TRUE);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ res = priv->stop_on_disconnect;
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+}
+
+/**
* gst_rtsp_media_set_retransmission_time:
* @media: a #GstRTSPMedia
* @time: the new value
gst_rtsp_stream_set_retransmission_time (stream, time);
}
-
- if (priv->rtpbin)
- g_object_set (priv->rtpbin, "do-retransmission", time > 0, NULL);
g_mutex_unlock (&priv->lock);
}
priv = media->priv;
- g_mutex_unlock (&priv->lock);
+ g_mutex_lock (&priv->lock);
res = priv->rtx_time;
g_mutex_unlock (&priv->lock);
}
/**
- * gst_rtsp_media_set_latncy:
+ * gst_rtsp_media_set_do_retransmission:
+ *
+ * Set whether retransmission requests will be sent
+ *
+ * Since: 1.16
+ */
+void
+gst_rtsp_media_set_do_retransmission (GstRTSPMedia * media,
+ gboolean do_retransmission)
+{
+ GstRTSPMediaPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ priv->do_retransmission = do_retransmission;
+
+ if (priv->rtpbin)
+ g_object_set (priv->rtpbin, "do-retransmission", do_retransmission, NULL);
+ g_mutex_unlock (&priv->lock);
+}
+
+/**
+ * gst_rtsp_media_get_do_retransmission:
+ *
+ * Returns: Whether retransmission requests will be sent
+ *
+ * Since: 1.16
+ */
+gboolean
+gst_rtsp_media_get_do_retransmission (GstRTSPMedia * media)
+{
+ GstRTSPMediaPrivate *priv;
+ gboolean res;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), 0);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ res = priv->do_retransmission;
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+}
+
+/**
+ * gst_rtsp_media_set_latency:
* @media: a #GstRTSPMedia
* @latency: latency in milliseconds
*
gst_rtsp_media_set_latency (GstRTSPMedia * media, guint latency)
{
GstRTSPMediaPrivate *priv;
+ guint i;
g_return_if_fail (GST_IS_RTSP_MEDIA (media));
g_mutex_lock (&priv->lock);
priv->latency = latency;
- if (priv->rtpbin)
+ if (priv->rtpbin) {
g_object_set (priv->rtpbin, "latency", latency, NULL);
+
+ for (i = 0; i < media->priv->streams->len; i++) {
+ GObject *storage = NULL;
+
+ g_signal_emit_by_name (G_OBJECT (media->priv->rtpbin), "get-storage",
+ i, &storage);
+ if (storage)
+ g_object_set (storage, "size-time", (media->priv->latency + 50) * GST_MSECOND, NULL);
+ }
+ }
+
g_mutex_unlock (&priv->lock);
}
priv = media->priv;
- g_mutex_unlock (&priv->lock);
+ g_mutex_lock (&priv->lock);
res = priv->latency;
g_mutex_unlock (&priv->lock);
priv = media->priv;
- g_mutex_unlock (&priv->lock);
+ g_mutex_lock (&priv->lock);
res = priv->time_provider;
g_mutex_unlock (&priv->lock);
}
/**
+ * gst_rtsp_media_set_clock:
+ * @media: a #GstRTSPMedia
+ * @clock: (nullable): #GstClock to be used
+ *
+ * Configure the clock used for the media.
+ */
+void
+gst_rtsp_media_set_clock (GstRTSPMedia * media, GstClock * clock)
+{
+ GstRTSPMediaPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+ g_return_if_fail (GST_IS_CLOCK (clock) || clock == NULL);
+
+ GST_LOG_OBJECT (media, "setting clock %" GST_PTR_FORMAT, clock);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ if (priv->clock)
+ gst_object_unref (priv->clock);
+ priv->clock = clock ? gst_object_ref (clock) : NULL;
+ if (priv->pipeline) {
+ if (clock)
+ gst_pipeline_use_clock (GST_PIPELINE_CAST (priv->pipeline), clock);
+ else
+ gst_pipeline_auto_clock (GST_PIPELINE_CAST (priv->pipeline));
+ }
+
+ g_mutex_unlock (&priv->lock);
+}
+
+/**
+ * gst_rtsp_media_set_publish_clock_mode:
+ * @media: a #GstRTSPMedia
+ * @mode: the clock publish mode
+ *
+ * Sets if and how the media clock should be published according to RFC7273.
+ *
+ * Since: 1.8
+ */
+void
+gst_rtsp_media_set_publish_clock_mode (GstRTSPMedia * media,
+ GstRTSPPublishClockMode mode)
+{
+ GstRTSPMediaPrivate *priv;
+ guint i, n;
+
+ priv = media->priv;
+ g_mutex_lock (&priv->lock);
+ priv->publish_clock_mode = mode;
+
+ n = priv->streams->len;
+ for (i = 0; i < n; i++) {
+ GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
+
+ gst_rtsp_stream_set_publish_clock_mode (stream, mode);
+ }
+ g_mutex_unlock (&priv->lock);
+}
+
+/**
+ * gst_rtsp_media_get_publish_clock_mode:
+ * @media: a #GstRTSPMedia
+ *
+ * Gets if and how the media clock should be published according to RFC7273.
+ *
+ * Returns: The GstRTSPPublishClockMode
+ *
+ * Since: 1.8
+ */
+GstRTSPPublishClockMode
+gst_rtsp_media_get_publish_clock_mode (GstRTSPMedia * media)
+{
+ GstRTSPMediaPrivate *priv;
+ GstRTSPPublishClockMode ret;
+
+ priv = media->priv;
+ g_mutex_lock (&priv->lock);
+ ret = priv->publish_clock_mode;
+ g_mutex_unlock (&priv->lock);
+
+ return ret;
+}
+
+/**
* gst_rtsp_media_set_address_pool:
* @media: a #GstRTSPMedia
- * @pool: (transfer none): a #GstRTSPAddressPool
+ * @pool: (transfer none) (nullable): a #GstRTSPAddressPool
*
* configure @pool to be used as the address pool of @media.
*/
*
* Get the #GstRTSPAddressPool used as the address pool of @media.
*
- * Returns: (transfer full): the #GstRTSPAddressPool of @media. g_object_unref() after
- * usage.
+ * Returns: (transfer full) (nullable): the #GstRTSPAddressPool of @media.
+ * g_object_unref() after usage.
*/
GstRTSPAddressPool *
gst_rtsp_media_get_address_pool (GstRTSPMedia * media)
return result;
}
-static GList *
-_find_payload_types (GstRTSPMedia * media)
+/**
+ * gst_rtsp_media_set_multicast_iface:
+ * @media: a #GstRTSPMedia
+ * @multicast_iface: (transfer none) (nullable): a multicast interface name
+ *
+ * configure @multicast_iface to be used for @media.
+ */
+void
+gst_rtsp_media_set_multicast_iface (GstRTSPMedia * media,
+ const gchar * multicast_iface)
{
- gint i, n;
- GQueue queue = G_QUEUE_INIT;
+ GstRTSPMediaPrivate *priv;
+ gchar *old;
- n = media->priv->streams->len;
- for (i = 0; i < n; i++) {
- GstRTSPStream *stream = g_ptr_array_index (media->priv->streams, i);
- guint pt = gst_rtsp_stream_get_pt (stream);
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
- g_queue_push_tail (&queue, GUINT_TO_POINTER (pt));
- }
+ priv = media->priv;
- return queue.head;
+ GST_LOG_OBJECT (media, "set multicast interface %s", multicast_iface);
+
+ g_mutex_lock (&priv->lock);
+ if ((old = priv->multicast_iface) != multicast_iface)
+ priv->multicast_iface = multicast_iface ? g_strdup (multicast_iface) : NULL;
+ else
+ old = NULL;
+ g_ptr_array_foreach (priv->streams,
+ (GFunc) gst_rtsp_stream_set_multicast_iface, (gchar *) multicast_iface);
+ g_mutex_unlock (&priv->lock);
+
+ if (old)
+ g_free (old);
+}
+
+/**
+ * gst_rtsp_media_get_multicast_iface:
+ * @media: a #GstRTSPMedia
+ *
+ * Get the multicast interface used for @media.
+ *
+ * Returns: (transfer full) (nullable): the multicast interface for @media.
+ * g_free() after usage.
+ */
+gchar *
+gst_rtsp_media_get_multicast_iface (GstRTSPMedia * media)
+{
+ GstRTSPMediaPrivate *priv;
+ gchar *result;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ if ((result = priv->multicast_iface))
+ result = g_strdup (result);
+ g_mutex_unlock (&priv->lock);
+
+ return result;
+}
+
+static GList *
+_find_payload_types (GstRTSPMedia * media)
+{
+ gint i, n;
+ GQueue queue = G_QUEUE_INIT;
+
+ n = media->priv->streams->len;
+ for (i = 0; i < n; i++) {
+ GstRTSPStream *stream = g_ptr_array_index (media->priv->streams, i);
+ guint pt = gst_rtsp_stream_get_pt (stream);
+
+ g_queue_push_tail (&queue, GUINT_TO_POINTER (pt));
+ }
+
+ return queue.head;
}
static guint
priv->dynamic = g_list_prepend (priv->dynamic, elem);
g_mutex_unlock (&priv->lock);
+ priv->nb_dynamic_elements++;
+
have_elem = TRUE;
more_elem_remaining = TRUE;
mode |= GST_RTSP_TRANSPORT_MODE_PLAY;
}
}
+typedef struct
+{
+ GstElement *appsink, *appsrc;
+ GstRTSPStream *stream;
+} AppSinkSrcData;
+
+static GstFlowReturn
+appsink_new_sample (GstAppSink * appsink, gpointer user_data)
+{
+ AppSinkSrcData *data = user_data;
+ GstSample *sample;
+ GstFlowReturn ret;
+
+ sample = gst_app_sink_pull_sample (appsink);
+ if (!sample)
+ return GST_FLOW_FLUSHING;
+
+
+ ret = gst_app_src_push_sample (GST_APP_SRC (data->appsrc), sample);
+ gst_sample_unref (sample);
+ return ret;
+}
+
+static GstAppSinkCallbacks appsink_callbacks = {
+ NULL,
+ NULL,
+ appsink_new_sample,
+};
+
+static GstPadProbeReturn
+appsink_pad_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
+{
+ AppSinkSrcData *data = user_data;
+
+ if (GST_IS_EVENT (info->data)
+ && GST_EVENT_TYPE (info->data) == GST_EVENT_LATENCY) {
+ GstClockTime min, max;
+
+ if (gst_base_sink_query_latency (GST_BASE_SINK (data->appsink), NULL, NULL,
+ &min, &max)) {
+ g_object_set (data->appsrc, "min-latency", min, "max-latency", max, NULL);
+ GST_DEBUG ("setting latency to min %" GST_TIME_FORMAT " max %"
+ GST_TIME_FORMAT, GST_TIME_ARGS (min), GST_TIME_ARGS (max));
+ }
+ } else if (GST_IS_QUERY (info->data)) {
+ GstPad *srcpad = gst_element_get_static_pad (data->appsrc, "src");
+ if (gst_pad_peer_query (srcpad, GST_QUERY_CAST (info->data))) {
+ gst_object_unref (srcpad);
+ return GST_PAD_PROBE_HANDLED;
+ }
+ gst_object_unref (srcpad);
+ }
+
+ return GST_PAD_PROBE_OK;
+}
+
+static GstPadProbeReturn
+appsrc_pad_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
+{
+ AppSinkSrcData *data = user_data;
+
+ if (GST_IS_QUERY (info->data)) {
+ GstPad *sinkpad = gst_element_get_static_pad (data->appsink, "sink");
+ if (gst_pad_peer_query (sinkpad, GST_QUERY_CAST (info->data))) {
+ gst_object_unref (sinkpad);
+ return GST_PAD_PROBE_HANDLED;
+ }
+ gst_object_unref (sinkpad);
+ }
+
+ return GST_PAD_PROBE_OK;
+}
+
/**
* gst_rtsp_media_create_stream:
* @media: a #GstRTSPMedia
{
GstRTSPMediaPrivate *priv;
GstRTSPStream *stream;
- GstPad *ghostpad;
+ GstPad *streampad;
gchar *name;
gint idx;
+ AppSinkSrcData *data = NULL;
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
g_return_val_if_fail (GST_IS_ELEMENT (payloader), NULL);
else
name = g_strdup_printf ("sink_%u", idx);
- ghostpad = gst_ghost_pad_new (name, pad);
- gst_pad_set_active (ghostpad, TRUE);
- gst_element_add_pad (priv->element, ghostpad);
+ if ((GST_PAD_IS_SRC (pad) && priv->element->numsinkpads > 0) ||
+ (GST_PAD_IS_SINK (pad) && priv->element->numsrcpads > 0)) {
+ GstElement *appsink, *appsrc;
+ GstPad *sinkpad, *srcpad;
+
+ appsink = gst_element_factory_make ("appsink", NULL);
+ appsrc = gst_element_factory_make ("appsrc", NULL);
+
+ if (GST_PAD_IS_SINK (pad)) {
+ srcpad = gst_element_get_static_pad (appsrc, "src");
+
+ gst_bin_add (GST_BIN (priv->element), appsrc);
+
+ gst_pad_link (srcpad, pad);
+ gst_object_unref (srcpad);
+
+ streampad = gst_element_get_static_pad (appsink, "sink");
+
+ priv->pending_pipeline_elements =
+ g_list_prepend (priv->pending_pipeline_elements, appsink);
+ } else {
+ sinkpad = gst_element_get_static_pad (appsink, "sink");
+
+ gst_pad_link (pad, sinkpad);
+ gst_object_unref (sinkpad);
+
+ streampad = gst_element_get_static_pad (appsrc, "src");
+
+ priv->pending_pipeline_elements =
+ g_list_prepend (priv->pending_pipeline_elements, appsrc);
+ }
+
+ g_object_set (appsrc, "block", TRUE, "format", GST_FORMAT_TIME, "is-live",
+ TRUE, NULL);
+ g_object_set (appsink, "sync", FALSE, "async", FALSE, NULL);
+
+ data = g_new0 (AppSinkSrcData, 1);
+ data->appsink = appsink;
+ data->appsrc = appsrc;
+
+ sinkpad = gst_element_get_static_pad (appsink, "sink");
+ gst_pad_add_probe (sinkpad,
+ GST_PAD_PROBE_TYPE_EVENT_UPSTREAM | GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM,
+ appsink_pad_probe, data, NULL);
+ gst_object_unref (sinkpad);
+
+ srcpad = gst_element_get_static_pad (appsrc, "src");
+ gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_QUERY_UPSTREAM,
+ appsrc_pad_probe, data, NULL);
+ gst_object_unref (srcpad);
+
+ gst_app_sink_set_callbacks (GST_APP_SINK (appsink), &appsink_callbacks,
+ data, NULL);
+ g_object_set_data_full (G_OBJECT (streampad), "media-appsink-appsrc", data,
+ g_free);
+ } else {
+ streampad = gst_ghost_pad_new (name, pad);
+ gst_pad_set_active (streampad, TRUE);
+ gst_element_add_pad (priv->element, streampad);
+ }
g_free (name);
- stream = gst_rtsp_stream_new (idx, payloader, ghostpad);
+ stream = gst_rtsp_stream_new (idx, payloader, streampad);
+ if (data)
+ data->stream = stream;
if (priv->pool)
gst_rtsp_stream_set_address_pool (stream, priv->pool);
+ gst_rtsp_stream_set_multicast_iface (stream, priv->multicast_iface);
gst_rtsp_stream_set_profiles (stream, priv->profiles);
gst_rtsp_stream_set_protocols (stream, priv->protocols);
gst_rtsp_stream_set_retransmission_time (stream, priv->rtx_time);
gst_rtsp_stream_set_buffer_size (stream, priv->buffer_size);
+ gst_rtsp_stream_set_publish_clock_mode (stream, priv->publish_clock_mode);
g_ptr_array_add (priv->streams, stream);
{
GstRTSPMediaPrivate *priv;
GstPad *srcpad;
+ AppSinkSrcData *data;
priv = media->priv;
g_mutex_lock (&priv->lock);
/* remove the ghostpad */
srcpad = gst_rtsp_stream_get_srcpad (stream);
- gst_element_remove_pad (priv->element, srcpad);
+ data = g_object_get_data (G_OBJECT (srcpad), "media-appsink-appsrc");
+ if (data) {
+ if (GST_OBJECT_PARENT (data->appsrc) == GST_OBJECT_CAST (priv->pipeline))
+ gst_bin_remove (GST_BIN_CAST (priv->pipeline), data->appsrc);
+ else if (GST_OBJECT_PARENT (data->appsrc) ==
+ GST_OBJECT_CAST (priv->element))
+ gst_bin_remove (GST_BIN_CAST (priv->element), data->appsrc);
+ if (GST_OBJECT_PARENT (data->appsink) == GST_OBJECT_CAST (priv->pipeline))
+ gst_bin_remove (GST_BIN_CAST (priv->pipeline), data->appsink);
+ else if (GST_OBJECT_PARENT (data->appsink) ==
+ GST_OBJECT_CAST (priv->element))
+ gst_bin_remove (GST_BIN_CAST (priv->element), data->appsink);
+ } else {
+ gst_element_remove_pad (priv->element, srcpad);
+ }
gst_object_unref (srcpad);
/* now remove the stream */
g_object_ref (stream);
* Get the current range as a string. @media must be prepared with
* gst_rtsp_media_prepare ().
*
- * Returns: (transfer full): The range as a string, g_free() after usage.
+ * Returns: (transfer full) (nullable): The range as a string, g_free() after usage.
*/
gchar *
gst_rtsp_media_get_range_string (GstRTSPMedia * media, gboolean play,
}
static void
+stream_unblock (GstRTSPStream * stream, GstRTSPMedia * media)
+{
+ gst_rtsp_stream_unblock_linked (stream);
+}
+
+static void
+media_unblock_linked (GstRTSPMedia * media)
+{
+ GstRTSPMediaPrivate *priv = media->priv;
+
+ GST_DEBUG ("media %p unblocking linked streams", media);
+ g_ptr_array_foreach (priv->streams, (GFunc) stream_unblock, media);
+}
+
+static void
gst_rtsp_media_set_status (GstRTSPMedia * media, GstRTSPMediaStatus status)
{
GstRTSPMediaPrivate *priv = media->priv;
}
/**
- * gst_rtsp_media_seek:
+ * gst_rtsp_media_seek_full:
* @media: a #GstRTSPMedia
* @range: (transfer none): a #GstRTSPTimeRange
+ * @flags: The minimal set of #GstSeekFlags to use
*
* Seek the pipeline of @media to @range. @media must be prepared with
* gst_rtsp_media_prepare().
* Returns: %TRUE on success.
*/
gboolean
-gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range)
+gst_rtsp_media_seek_full (GstRTSPMedia * media, GstRTSPTimeRange * range,
+ GstSeekFlags flags)
{
GstRTSPMediaClass *klass;
GstRTSPMediaPrivate *priv;
gboolean res;
GstClockTime start, stop;
GstSeekType start_type, stop_type;
- GstQuery *query;
gint64 current_position;
klass = GST_RTSP_MEDIA_GET_CLASS (media);
if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED)
goto not_prepared;
+ /* check if the media pipeline is complete in order to perform a
+ * seek operation on it */
+ if (!check_complete (media))
+ goto not_complete;
+
/* Update the seekable state of the pipeline in case it changed */
- if ((priv->transport_mode & GST_RTSP_TRANSPORT_MODE_RECORD)) {
- /* TODO: Seeking for RECORD? */
- priv->seekable = FALSE;
- } else {
- query = gst_query_new_seeking (GST_FORMAT_TIME);
- if (gst_element_query (priv->pipeline, query)) {
- GstFormat format;
- gboolean seekable;
- gint64 start, end;
-
- gst_query_parse_seeking (query, &format, &seekable, &start, &end);
- priv->seekable = seekable;
- }
- gst_query_unref (query);
- }
+ check_seekable (media);
+
+ if (priv->seekable == 0) {
+ GST_FIXME_OBJECT (media, "Handle going back to 0 for none live"
+ " not seekable streams.");
- if (!priv->seekable)
goto not_seekable;
+ } else if (priv->seekable < 0) {
+ goto not_seekable;
+ }
start_type = stop_type = GST_SEEK_TYPE_NONE;
stop_type = GST_SEEK_TYPE_SET;
if (start != GST_CLOCK_TIME_NONE || stop != GST_CLOCK_TIME_NONE) {
- GstSeekFlags flags;
+ gboolean had_flags = flags != 0;
GST_INFO ("seeking to %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
/* depends on the current playing state of the pipeline. We might need to
* queue this until we get EOS. */
- flags = GST_SEEK_FLAG_FLUSH;
+ if (had_flags)
+ flags |= GST_SEEK_FLAG_FLUSH;
+ else
+ flags = GST_SEEK_FLAG_FLUSH;
+
/* if range start was not supplied we must continue from current position.
* but since we're doing a flushing seek, let us query the current position
GST_TIME_ARGS (current_position));
start = current_position;
start_type = GST_SEEK_TYPE_SET;
- flags |= GST_SEEK_FLAG_ACCURATE;
+ if (!had_flags)
+ flags |= GST_SEEK_FLAG_ACCURATE;
}
} else {
/* only set keyframe flag when modifying start */
if (start_type != GST_SEEK_TYPE_NONE)
- flags |= GST_SEEK_FLAG_KEY_UNIT;
+ if (!had_flags)
+ flags |= GST_SEEK_FLAG_KEY_UNIT;
}
if (start == current_position && stop_type == GST_SEEK_TYPE_NONE) {
GST_INFO ("media %p is not prepared", media);
return FALSE;
}
+not_complete:
+ {
+ g_rec_mutex_unlock (&priv->state_lock);
+ GST_INFO ("pipeline is not complete");
+ return FALSE;
+ }
not_seekable:
{
g_rec_mutex_unlock (&priv->state_lock);
}
}
+
+/**
+ * gst_rtsp_media_seek:
+ * @media: a #GstRTSPMedia
+ * @range: (transfer none): a #GstRTSPTimeRange
+ *
+ * Seek the pipeline of @media to @range. @media must be prepared with
+ * gst_rtsp_media_prepare().
+ *
+ * Returns: %TRUE on success.
+ */
+gboolean
+gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range)
+{
+ return gst_rtsp_media_seek_full (media, range, 0);
+}
+
+
static void
stream_collect_blocking (GstRTSPStream * stream, gboolean * blocked)
{
GST_DEBUG ("%p: went from %s to %s (pending %s)", media,
gst_element_state_get_name (old), gst_element_state_get_name (new),
gst_element_state_get_name (pending));
- if ((priv->transport_mode & GST_RTSP_TRANSPORT_MODE_RECORD)
- && old == GST_STATE_READY && new == GST_STATE_PAUSED) {
+ if (priv->no_more_pads_pending == 0 && is_receive_only (media) &&
+ old == GST_STATE_READY && new == GST_STATE_PAUSED) {
GST_INFO ("%p: went to PAUSED, prepared now", media);
collect_media_stats (media);
s = gst_message_get_structure (message);
if (gst_structure_has_name (s, "GstRTSPStreamBlocking")) {
GST_DEBUG ("media received blocking message");
- if (priv->blocked && media_streams_blocking (media)) {
- GST_DEBUG ("media is blocking");
+ if (priv->blocked && media_streams_blocking (media) &&
+ priv->no_more_pads_pending == 0) {
+ GST_DEBUG_OBJECT (GST_MESSAGE_SRC (message), "media is blocking");
collect_media_stats (media);
if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING)
case GST_MESSAGE_STREAM_STATUS:
break;
case GST_MESSAGE_ASYNC_DONE:
- if (priv->adding) {
- /* when we are dynamically adding pads, the addition of the udpsrc will
- * temporarily produce ASYNC_DONE messages. We have to ignore them and
- * wait for the final ASYNC_DONE after everything prerolled */
- GST_INFO ("%p: ignoring ASYNC_DONE", media);
- } else {
- GST_INFO ("%p: got ASYNC_DONE", media);
- collect_media_stats (media);
-
+ if (priv->complete) {
+ /* receive the final ASYNC_DONE, that is posted by the media pipeline
+ * after all the transport parts have been successfully added to
+ * the media streams. */
+ GST_DEBUG_OBJECT (media, "got async-done");
if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING)
gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED);
}
g_object_set_data (G_OBJECT (pad), "gst-rtsp-dynpad-stream", stream);
- /* we will be adding elements below that will cause ASYNC_DONE to be
- * posted in the bus. We want to ignore those messages until the
- * pipeline really prerolled. */
- priv->adding = TRUE;
-
/* join the element in the PAUSED state because this callback is
* called from the streaming thread and it is PAUSED */
if (!gst_rtsp_stream_join_bin (stream, GST_BIN (priv->pipeline),
GST_WARNING ("failed to join bin element");
}
- priv->adding = FALSE;
+ if (priv->blocked)
+ gst_rtsp_stream_set_blocked (stream, TRUE);
+
g_rec_mutex_unlock (&priv->state_lock);
return;
}
static void
-remove_fakesink (GstRTSPMediaPrivate * priv)
-{
- GstElement *fakesink;
-
- g_mutex_lock (&priv->lock);
- if ((fakesink = priv->fakesink))
- gst_object_ref (fakesink);
- priv->fakesink = NULL;
- g_mutex_unlock (&priv->lock);
-
- if (fakesink) {
- gst_bin_remove (GST_BIN (priv->pipeline), fakesink);
- gst_element_set_state (fakesink, GST_STATE_NULL);
- gst_object_unref (fakesink);
- GST_INFO ("removed fakesink");
- }
-}
-
-static void
no_more_pads_cb (GstElement * element, GstRTSPMedia * media)
{
GstRTSPMediaPrivate *priv = media->priv;
- GST_INFO ("no more pads");
- remove_fakesink (priv);
+ GST_INFO_OBJECT (element, "no more pads");
+ g_mutex_lock (&priv->lock);
+ priv->no_more_pads_pending--;
+ g_mutex_unlock (&priv->lock);
}
typedef struct _DynPaySignalHandlers DynPaySignalHandlers;
GstStateChangeReturn ret;
GST_INFO ("setting pipeline to PAUSED for media %p", media);
- /* first go to PAUSED */
+
+ /* start blocked since it is possible that there are no sink elements yet */
+ media_streams_set_blocked (media, TRUE);
ret = set_target_state (media, GST_STATE_PAUSED, TRUE);
switch (ret) {
case GST_STATE_CHANGE_SUCCESS:
GST_INFO ("SUCCESS state change for media %p", media);
- priv->seekable = TRUE;
break;
case GST_STATE_CHANGE_ASYNC:
GST_INFO ("ASYNC state change for media %p", media);
- priv->seekable = TRUE;
break;
case GST_STATE_CHANGE_NO_PREROLL:
/* we need to go to PLAYING */
GST_INFO ("NO_PREROLL state change: live media %p", media);
/* FIXME we disable seeking for live streams for now. We should perform a
* seeking query in preroll instead */
- priv->seekable = FALSE;
+ priv->seekable = -1;
priv->is_live = TRUE;
- if (!(priv->transport_mode & GST_RTSP_TRANSPORT_MODE_RECORD)) {
- /* start blocked to make sure nothing goes to the sink */
- media_streams_set_blocked (media, TRUE);
- }
+
ret = set_state (media, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE)
goto state_failed;
GstRTSPMediaPrivate *priv = media->priv;
GstRTSPStream *stream = NULL;
guint i;
+ GstElement *res = NULL;
g_mutex_lock (&priv->lock);
for (i = 0; i < priv->streams->len; i++) {
if (sessid == gst_rtsp_stream_get_index (stream))
break;
+
+ stream = NULL;
}
g_mutex_unlock (&priv->lock);
- return gst_rtsp_stream_request_aux_sender (stream, sessid);
+ if (stream)
+ res = gst_rtsp_stream_request_aux_sender (stream, sessid);
+
+ return res;
+}
+
+static GstElement *
+request_aux_receiver (GstElement * rtpbin, guint sessid, GstRTSPMedia * media)
+{
+ GstRTSPMediaPrivate *priv = media->priv;
+ GstRTSPStream *stream = NULL;
+ guint i;
+ GstElement *res = NULL;
+
+ g_mutex_lock (&priv->lock);
+ for (i = 0; i < priv->streams->len; i++) {
+ stream = g_ptr_array_index (priv->streams, i);
+
+ if (sessid == gst_rtsp_stream_get_index (stream))
+ break;
+
+ stream = NULL;
+ }
+ g_mutex_unlock (&priv->lock);
+
+ if (stream)
+ res = gst_rtsp_stream_request_aux_receiver (stream, sessid);
+
+ return res;
+}
+
+static GstElement *
+request_fec_decoder (GstElement * rtpbin, guint sessid, GstRTSPMedia * media)
+{
+ GstRTSPMediaPrivate *priv = media->priv;
+ GstRTSPStream *stream = NULL;
+ guint i;
+ GstElement *res = NULL;
+
+ g_mutex_lock (&priv->lock);
+ for (i = 0; i < priv->streams->len; i++) {
+ stream = g_ptr_array_index (priv->streams, i);
+
+ if (sessid == gst_rtsp_stream_get_index (stream))
+ break;
+
+ stream = NULL;
+ }
+ g_mutex_unlock (&priv->lock);
+
+ if (stream) {
+ res = gst_rtsp_stream_request_ulpfec_decoder (stream, rtpbin, sessid);
+ }
+
+ return res;
+}
+
+static void
+new_storage_cb (GstElement * rtpbin, GObject * storage, guint sessid, GstRTSPMedia * media)
+{
+ g_object_set (storage, "size-time", (media->priv->latency + 50) * GST_MSECOND, NULL);
}
static gboolean
guint i;
GList *walk;
+ g_rec_mutex_lock (&priv->state_lock);
+ if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARING)
+ goto no_longer_preparing;
+
+ g_signal_connect (priv->rtpbin, "new-storage", G_CALLBACK (new_storage_cb), media);
+ g_signal_connect (priv->rtpbin, "request-fec-decoder", G_CALLBACK (request_fec_decoder), media);
+
/* link streams we already have, other streams might appear when we have
* dynamic elements */
for (i = 0; i < priv->streams->len; i++) {
(GCallback) request_aux_sender, media);
}
+ if (priv->do_retransmission) {
+ g_signal_connect (priv->rtpbin, "request-aux-receiver",
+ (GCallback) request_aux_receiver, media);
+ }
+
if (!gst_rtsp_stream_join_bin (stream, GST_BIN (priv->pipeline),
priv->rtpbin, GST_STATE_NULL)) {
goto join_bin_failed;
}
if (priv->rtpbin)
- g_object_set (priv->rtpbin, "do-retransmission", priv->rtx_time > 0, NULL);
+ g_object_set (priv->rtpbin, "do-retransmission", priv->do_retransmission,
+ "do-lost", TRUE, NULL);
for (walk = priv->dynamic; walk; walk = g_list_next (walk)) {
GstElement *elem = walk->data;
(GCallback) no_more_pads_cb, media);
g_object_set_data (G_OBJECT (elem), "gst-rtsp-dynpay-handlers", handlers);
-
- if (!priv->fakesink) {
- /* we add a fakesink here in order to make the state change async. We remove
- * the fakesink again in the no-more-pads callback. */
- priv->fakesink = gst_element_factory_make ("fakesink", "fakesink");
- gst_bin_add (GST_BIN (priv->pipeline), priv->fakesink);
- }
}
- if (!start_preroll (media))
+ if (priv->nb_dynamic_elements == 0 && is_receive_only (media)) {
+ /* If we are receive_only (RECORD), do not try to preroll, to avoid
+ * a second ASYNC state change failing */
+ priv->is_live = TRUE;
+ gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED);
+ } else if (!start_preroll (media)) {
goto preroll_failed;
+ }
+
+ g_rec_mutex_unlock (&priv->state_lock);
return FALSE;
+no_longer_preparing:
+ {
+ GST_INFO ("media is no longer preparing");
+ g_rec_mutex_unlock (&priv->state_lock);
+ return FALSE;
+ }
join_bin_failed:
{
GST_WARNING ("failed to join bin element");
gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR);
+ g_rec_mutex_unlock (&priv->state_lock);
return FALSE;
}
preroll_failed:
{
GST_WARNING ("failed to preroll pipeline");
gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR);
+ g_rec_mutex_unlock (&priv->state_lock);
return FALSE;
}
}
/* do remainder in context */
source = g_idle_source_new ();
- g_source_set_callback (source, (GSourceFunc) start_prepare, media, NULL);
+ g_source_set_callback (source, (GSourceFunc) start_prepare,
+ g_object_ref (media), (GDestroyNotify) g_object_unref);
g_source_attach (source, context);
g_source_unref (source);
/* reset some variables */
priv->is_live = FALSE;
- priv->seekable = FALSE;
+ priv->seekable = -1;
priv->buffering = FALSE;
+ priv->no_more_pads_pending = priv->nb_dynamic_elements;
/* we're preparing now */
gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING);
g_rec_mutex_unlock (&priv->state_lock);
set_state (media, GST_STATE_NULL);
g_rec_mutex_lock (&priv->state_lock);
- remove_fakesink (priv);
+
+ media_streams_set_blocked (media, FALSE);
+
+ if (priv->status != GST_RTSP_MEDIA_STATUS_UNPREPARING)
+ return;
for (i = 0; i < priv->streams->len; i++) {
GstRTSPStream *stream;
goto is_busy;
GST_INFO ("unprepare media %p", media);
- if (priv->blocked)
- media_streams_set_blocked (media, FALSE);
set_target_state (media, GST_STATE_NULL, FALSE);
success = TRUE;
if (klass->unprepare)
success = klass->unprepare (media);
} else {
+ gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_UNPREPARING);
finish_unprepare (media);
}
g_rec_mutex_unlock (&priv->state_lock);
*
* @media must be prepared before this method returns a valid clock object.
*
- * Returns: (transfer full): the #GstClock used by @media. unref after usage.
+ * Returns: (transfer full) (nullable): the #GstClock used by @media. unref after usage.
*/
GstClock *
gst_rtsp_media_get_clock (GstRTSPMedia * media)
}
}
-static const gchar *
-rtsp_get_attribute_for_pt (const GstSDPMedia * media, const gchar * name,
- gint pt)
-{
- guint i;
-
- for (i = 0;; i++) {
- const gchar *attr;
- gint val;
-
- if ((attr = gst_sdp_media_get_attribute_val_n (media, name, i)) == NULL)
- break;
-
- if (sscanf (attr, "%d ", &val) != 1)
- continue;
-
- if (val == pt)
- return attr;
- }
- return NULL;
-}
-
-#define PARSE_INT(p, del, res) \
-G_STMT_START { \
- gchar *t = p; \
- p = strstr (p, del); \
- if (p == NULL) \
- res = -1; \
- else { \
- *p = '\0'; \
- p++; \
- res = atoi (t); \
- } \
-} G_STMT_END
-
-#define PARSE_STRING(p, del, res) \
-G_STMT_START { \
- gchar *t = p; \
- p = strstr (p, del); \
- if (p == NULL) { \
- res = NULL; \
- p = t; \
- } \
- else { \
- *p = '\0'; \
- p++; \
- res = t; \
- } \
-} G_STMT_END
-
-#define SKIP_SPACES(p) \
- while (*p && g_ascii_isspace (*p)) \
- p++;
-
-/* rtpmap contains:
- *
- * <payload> <encoding_name>/<clock_rate>[/<encoding_params>]
- */
-static gboolean
-parse_rtpmap (const gchar * rtpmap, gint * payload, gchar ** name,
- gint * rate, gchar ** params)
-{
- gchar *p, *t;
-
- p = (gchar *) rtpmap;
-
- PARSE_INT (p, " ", *payload);
- if (*payload == -1)
- return FALSE;
-
- SKIP_SPACES (p);
- if (*p == '\0')
- return FALSE;
-
- PARSE_STRING (p, "/", *name);
- if (*name == NULL) {
- GST_DEBUG ("no rate, name %s", p);
- /* no rate, assume -1 then, this is not supposed to happen but RealMedia
- * streams seem to omit the rate. */
- *name = p;
- *rate = -1;
- return TRUE;
- }
-
- t = p;
- p = strstr (p, "/");
- if (p == NULL) {
- *rate = atoi (t);
- return TRUE;
- }
- *p = '\0';
- p++;
- *rate = atoi (t);
-
- t = p;
- if (*p == '\0')
- return TRUE;
- *params = t;
-
- return TRUE;
-}
-
-/*
- * Mapping of caps to and from SDP fields:
- *
- * a=rtpmap:<payload> <encoding_name>/<clock_rate>[/<encoding_params>]
- * a=framesize:<payload> <width>-<height>
- * a=fmtp:<payload> <param>[=<value>];...
- */
-static GstCaps *
-media_to_caps (gint pt, const GstSDPMedia * media)
-{
- GstCaps *caps;
- const gchar *rtpmap;
- const gchar *fmtp;
- const gchar *framesize;
- gchar *name = NULL;
- gint rate = -1;
- gchar *params = NULL;
- gchar *tmp;
- GstStructure *s;
- gint payload = 0;
- gboolean ret;
-
- /* get and parse rtpmap */
- rtpmap = rtsp_get_attribute_for_pt (media, "rtpmap", pt);
-
- if (rtpmap) {
- ret = parse_rtpmap (rtpmap, &payload, &name, &rate, ¶ms);
- if (!ret) {
- g_warning ("error parsing rtpmap, ignoring");
- rtpmap = NULL;
- }
- }
- /* dynamic payloads need rtpmap or we fail */
- if (rtpmap == NULL && pt >= 96)
- goto no_rtpmap;
-
- /* check if we have a rate, if not, we need to look up the rate from the
- * default rates based on the payload types. */
- if (rate == -1) {
- const GstRTPPayloadInfo *info;
-
- if (GST_RTP_PAYLOAD_IS_DYNAMIC (pt)) {
- /* dynamic types, use media and encoding_name */
- tmp = g_ascii_strdown (media->media, -1);
- info = gst_rtp_payload_info_for_name (tmp, name);
- g_free (tmp);
- } else {
- /* static types, use payload type */
- info = gst_rtp_payload_info_for_pt (pt);
- }
-
- if (info) {
- if ((rate = info->clock_rate) == 0)
- rate = -1;
- }
- /* we fail if we cannot find one */
- if (rate == -1)
- goto no_rate;
- }
-
- tmp = g_ascii_strdown (media->media, -1);
- caps = gst_caps_new_simple ("application/x-unknown",
- "media", G_TYPE_STRING, tmp, "payload", G_TYPE_INT, pt, NULL);
- g_free (tmp);
- s = gst_caps_get_structure (caps, 0);
-
- gst_structure_set (s, "clock-rate", G_TYPE_INT, rate, NULL);
-
- /* encoding name must be upper case */
- if (name != NULL) {
- tmp = g_ascii_strup (name, -1);
- gst_structure_set (s, "encoding-name", G_TYPE_STRING, tmp, NULL);
- g_free (tmp);
- }
-
- /* params must be lower case */
- if (params != NULL) {
- tmp = g_ascii_strdown (params, -1);
- gst_structure_set (s, "encoding-params", G_TYPE_STRING, tmp, NULL);
- g_free (tmp);
- }
-
- /* parse optional fmtp: field */
- if ((fmtp = rtsp_get_attribute_for_pt (media, "fmtp", pt))) {
- gchar *p;
- gint payload = 0;
-
- p = (gchar *) fmtp;
-
- /* p is now of the format <payload> <param>[=<value>];... */
- PARSE_INT (p, " ", payload);
- if (payload != -1 && payload == pt) {
- gchar **pairs;
- gint i;
-
- /* <param>[=<value>] are separated with ';' */
- pairs = g_strsplit (p, ";", 0);
- for (i = 0; pairs[i]; i++) {
- gchar *valpos;
- const gchar *val, *key;
- gint j;
- const gchar *reserved_keys[] =
- { "media", "payload", "clock-rate", "encoding-name",
- "encoding-params"
- };
-
- /* the key may not have a '=', the value can have other '='s */
- valpos = strstr (pairs[i], "=");
- if (valpos) {
- /* we have a '=' and thus a value, remove the '=' with \0 */
- *valpos = '\0';
- /* value is everything between '=' and ';'. We split the pairs at ;
- * boundaries so we can take the remainder of the value. Some servers
- * put spaces around the value which we strip off here. Alternatively
- * we could strip those spaces in the depayloaders should these spaces
- * actually carry any meaning in the future. */
- val = g_strstrip (valpos + 1);
- } else {
- /* simple <param>;.. is translated into <param>=1;... */
- val = "1";
- }
- /* strip the key of spaces, convert key to lowercase but not the value. */
- key = g_strstrip (pairs[i]);
-
- /* skip keys from the fmtp, which we already use ourselves for the
- * caps. Some software is adding random things like clock-rate into
- * the fmtp, and we would otherwise here set a string-typed clock-rate
- * in the caps... and thus fail to create valid RTP caps
- */
- for (j = 0; j < G_N_ELEMENTS (reserved_keys); j++) {
- if (g_ascii_strcasecmp (reserved_keys[j], key) == 0) {
- key = "";
- break;
- }
- }
-
- if (strlen (key) > 1) {
- tmp = g_ascii_strdown (key, -1);
- gst_structure_set (s, tmp, G_TYPE_STRING, val, NULL);
- g_free (tmp);
- }
- }
- g_strfreev (pairs);
- }
- }
-
- /* parse framesize: field */
- if ((framesize = gst_sdp_media_get_attribute_val (media, "framesize"))) {
- gchar *p;
-
- /* p is now of the format <payload> <width>-<height> */
- p = (gchar *) framesize;
-
- PARSE_INT (p, " ", payload);
- if (payload != -1 && payload == pt) {
- gst_structure_set (s, "a-framesize", G_TYPE_STRING, p, NULL);
- }
- }
- return caps;
-
- /* ERRORS */
-no_rtpmap:
- {
- g_warning ("rtpmap type not given for dynamic payload %d", pt);
- return NULL;
- }
-no_rate:
- {
- g_warning ("rate unknown for payload type %d", pt);
- return NULL;
- }
-}
-
-static gboolean
-parse_keymgmt (const gchar * keymgmt, GstCaps * caps)
-{
- gboolean res = FALSE;
- gchar *p, *kmpid;
- gsize size;
- guchar *data;
- GstMIKEYMessage *msg;
- const GstMIKEYPayload *payload;
- const gchar *srtp_cipher;
- const gchar *srtp_auth;
-
- p = (gchar *) keymgmt;
-
- SKIP_SPACES (p);
- if (*p == '\0')
- return FALSE;
-
- PARSE_STRING (p, " ", kmpid);
- if (!g_str_equal (kmpid, "mikey"))
- return FALSE;
-
- data = g_base64_decode (p, &size);
- if (data == NULL)
- return FALSE;
-
- msg = gst_mikey_message_new_from_data (data, size, NULL, NULL);
- g_free (data);
- if (msg == NULL)
- return FALSE;
-
- srtp_cipher = "aes-128-icm";
- srtp_auth = "hmac-sha1-80";
-
- /* check the Security policy if any */
- if ((payload = gst_mikey_message_find_payload (msg, GST_MIKEY_PT_SP, 0))) {
- GstMIKEYPayloadSP *p = (GstMIKEYPayloadSP *) payload;
- guint len, i;
-
- if (p->proto != GST_MIKEY_SEC_PROTO_SRTP)
- goto done;
-
- len = gst_mikey_payload_sp_get_n_params (payload);
- for (i = 0; i < len; i++) {
- const GstMIKEYPayloadSPParam *param =
- gst_mikey_payload_sp_get_param (payload, i);
-
- switch (param->type) {
- case GST_MIKEY_SP_SRTP_ENC_ALG:
- switch (param->val[0]) {
- case 0:
- srtp_cipher = "null";
- break;
- case 2:
- case 1:
- srtp_cipher = "aes-128-icm";
- break;
- default:
- break;
- }
- break;
- case GST_MIKEY_SP_SRTP_ENC_KEY_LEN:
- switch (param->val[0]) {
- case AES_128_KEY_LEN:
- srtp_cipher = "aes-128-icm";
- break;
- case AES_256_KEY_LEN:
- srtp_cipher = "aes-256-icm";
- break;
- default:
- break;
- }
- break;
- case GST_MIKEY_SP_SRTP_AUTH_ALG:
- switch (param->val[0]) {
- case 0:
- srtp_auth = "null";
- break;
- case 2:
- case 1:
- srtp_auth = "hmac-sha1-80";
- break;
- default:
- break;
- }
- break;
- case GST_MIKEY_SP_SRTP_AUTH_KEY_LEN:
- switch (param->val[0]) {
- case HMAC_32_KEY_LEN:
- srtp_auth = "hmac-sha1-32";
- break;
- case HMAC_80_KEY_LEN:
- srtp_auth = "hmac-sha1-80";
- break;
- default:
- break;
- }
- break;
- case GST_MIKEY_SP_SRTP_SRTP_ENC:
- break;
- case GST_MIKEY_SP_SRTP_SRTCP_ENC:
- break;
- default:
- break;
- }
- }
- }
-
- if (!(payload = gst_mikey_message_find_payload (msg, GST_MIKEY_PT_KEMAC, 0)))
- goto done;
- else {
- GstMIKEYPayloadKEMAC *p = (GstMIKEYPayloadKEMAC *) payload;
- const GstMIKEYPayload *sub;
- GstMIKEYPayloadKeyData *pkd;
- GstBuffer *buf;
-
- if (p->enc_alg != GST_MIKEY_ENC_NULL || p->mac_alg != GST_MIKEY_MAC_NULL)
- goto done;
-
- if (!(sub = gst_mikey_payload_kemac_get_sub (payload, 0)))
- goto done;
-
- if (sub->type != GST_MIKEY_PT_KEY_DATA)
- goto done;
-
- pkd = (GstMIKEYPayloadKeyData *) sub;
- buf =
- gst_buffer_new_wrapped (g_memdup (pkd->key_data, pkd->key_len),
- pkd->key_len);
- gst_caps_set_simple (caps, "srtp-key", GST_TYPE_BUFFER, buf, NULL);
- }
-
- gst_caps_set_simple (caps,
- "srtp-cipher", G_TYPE_STRING, srtp_cipher,
- "srtp-auth", G_TYPE_STRING, srtp_auth,
- "srtcp-cipher", G_TYPE_STRING, srtp_cipher,
- "srtcp-auth", G_TYPE_STRING, srtp_auth, NULL);
-
- res = TRUE;
-done:
- gst_mikey_message_unref (msg);
-
- return res;
-}
-
-/*
- * Mapping SDP attributes to caps
- *
- * prepend 'a-' to IANA registered sdp attributes names
- * (ie: not prefixed with 'x-') in order to avoid
- * collision with gstreamer standard caps properties names
- */
-static void
-sdp_attributes_to_caps (GArray * attributes, GstCaps * caps)
-{
- if (attributes->len > 0) {
- GstStructure *s;
- guint i;
-
- s = gst_caps_get_structure (caps, 0);
-
- for (i = 0; i < attributes->len; i++) {
- GstSDPAttribute *attr = &g_array_index (attributes, GstSDPAttribute, i);
- gchar *tofree, *key;
-
- key = attr->key;
-
- /* skip some of the attribute we already handle */
- if (!strcmp (key, "fmtp"))
- continue;
- if (!strcmp (key, "rtpmap"))
- continue;
- if (!strcmp (key, "control"))
- continue;
- if (!strcmp (key, "range"))
- continue;
- if (!strcmp (key, "framesize"))
- continue;
- if (g_str_equal (key, "key-mgmt")) {
- parse_keymgmt (attr->value, caps);
- continue;
- }
-
- /* string must be valid UTF8 */
- if (!g_utf8_validate (attr->value, -1, NULL))
- continue;
-
- if (!g_str_has_prefix (key, "x-"))
- tofree = key = g_strdup_printf ("a-%s", key);
- else
- tofree = NULL;
-
- GST_DEBUG ("adding caps: %s=%s", key, attr->value);
- gst_structure_set (s, key, G_TYPE_STRING, attr->value, NULL);
- g_free (tofree);
- }
- }
-}
-
static gboolean
default_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp)
{
}
for (i = 0; i < medias_len; i++) {
- const gchar *proto, *media_type;
+ const gchar *proto;
const GstSDPMedia *sdp_media = gst_sdp_message_get_media (sdp, i);
GstRTSPStream *stream;
gint j, formats_len;
}
if (g_str_equal (proto, "RTP/AVP")) {
- media_type = "application/x-rtp";
profile = GST_RTSP_PROFILE_AVP;
} else if (g_str_equal (proto, "RTP/SAVP")) {
- media_type = "application/x-srtp";
profile = GST_RTSP_PROFILE_SAVP;
} else if (g_str_equal (proto, "RTP/AVPF")) {
- media_type = "application/x-rtp";
profile = GST_RTSP_PROFILE_AVPF;
} else if (g_str_equal (proto, "RTP/SAVPF")) {
- media_type = "application/x-srtp";
profile = GST_RTSP_PROFILE_SAVPF;
} else {
GST_ERROR ("%p: unsupported profile '%s' for stream %d", media, proto, i);
GST_DEBUG (" looking at %d pt: %d", j, pt);
/* convert caps */
- caps = media_to_caps (pt, sdp_media);
+ caps = gst_sdp_media_get_caps_from_media (sdp_media, pt);
if (caps == NULL) {
GST_WARNING (" skipping pt %d without caps", pt);
continue;
/* do some tweaks */
GST_DEBUG ("mapping sdp session level attributes to caps");
- sdp_attributes_to_caps (sdp->attributes, caps);
+ gst_sdp_message_attributes_to_caps (sdp, caps);
GST_DEBUG ("mapping sdp media level attributes to caps");
- sdp_attributes_to_caps (sdp_media->attributes, caps);
+ gst_sdp_media_attributes_to_caps (sdp_media, caps);
s = gst_caps_get_structure (caps, 0);
- gst_structure_set_name (s, media_type);
+ gst_structure_set_name (s, "application/x-rtp");
+
+ if (!g_strcmp0 (gst_structure_get_string (s, "encoding-name"), "ULPFEC"))
+ gst_structure_set (s, "is-fec", G_TYPE_BOOLEAN, TRUE, NULL);
gst_rtsp_stream_set_pt_map (stream, pt, caps);
gst_caps_unref (caps);
do_set_seqnum (GstRTSPStream * stream)
{
guint16 seq_num;
- seq_num = gst_rtsp_stream_get_current_seqnum (stream);
- gst_rtsp_stream_set_seqnum_offset (stream, seq_num + 1);
+
+ if (gst_rtsp_stream_is_sender (stream)) {
+ seq_num = gst_rtsp_stream_get_current_seqnum (stream);
+ gst_rtsp_stream_set_seqnum_offset (stream, seq_num + 1);
+ }
}
/* call with state_lock */
{
GstRTSPMediaPrivate *priv = media->priv;
GstStateChangeReturn ret;
- gboolean unblock = FALSE;
switch (priv->suspend_mode) {
case GST_RTSP_SUSPEND_MODE_NONE:
ret = set_target_state (media, GST_STATE_PAUSED, TRUE);
if (ret == GST_STATE_CHANGE_FAILURE)
goto state_failed;
- unblock = TRUE;
break;
case GST_RTSP_SUSPEND_MODE_RESET:
GST_DEBUG ("media %p suspend to NULL", media);
* is actually from NULL to PLAY will create a new sequence
* number. */
g_ptr_array_foreach (priv->streams, (GFunc) do_set_seqnum, NULL);
- unblock = TRUE;
break;
default:
break;
}
- /* let the streams do the state changes freely, if any */
- if (unblock)
- media_streams_set_blocked (media, FALSE);
-
return TRUE;
/* ERRORS */
default_unsuspend (GstRTSPMedia * media)
{
GstRTSPMediaPrivate *priv = media->priv;
+ gboolean preroll_ok;
switch (priv->suspend_mode) {
case GST_RTSP_SUSPEND_MODE_NONE:
- gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED);
+ if (is_receive_only (media))
+ break;
+ if (media_streams_blocking (media)) {
+ gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING);
+ /* at this point the media pipeline has been updated and contain all
+ * specific transport parts: all active streams contain at least one sink
+ * element and it's safe to unblock any blocked streams that are active */
+ media_unblock_linked (media);
+ } else {
+ /* streams are not blocked and media is suspended from PAUSED */
+ gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED);
+ }
+ g_rec_mutex_unlock (&priv->state_lock);
+ if (gst_rtsp_media_get_status (media) == GST_RTSP_MEDIA_STATUS_ERROR) {
+ g_rec_mutex_lock (&priv->state_lock);
+ goto preroll_failed;
+ }
+ g_rec_mutex_lock (&priv->state_lock);
break;
case GST_RTSP_SUSPEND_MODE_PAUSE:
gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED);
case GST_RTSP_SUSPEND_MODE_RESET:
{
gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING);
+ /* at this point the media pipeline has been updated and contain all
+ * specific transport parts: all active streams contain at least one sink
+ * element and it's safe to unblock any blocked streams that are active */
+ media_unblock_linked (media);
if (!start_preroll (media))
goto start_failed;
+
g_rec_mutex_unlock (&priv->state_lock);
+ preroll_ok = wait_preroll (media);
+ g_rec_mutex_lock (&priv->state_lock);
- if (!wait_preroll (media))
+ if (!preroll_ok)
goto preroll_failed;
-
- g_rec_mutex_lock (&priv->state_lock);
}
default:
break;
} else {
if (state == GST_STATE_PLAYING)
/* make sure pads are not blocking anymore when going to PLAYING */
- media_streams_set_blocked (media, FALSE);
+ media_unblock_linked (media);
set_state (media, state);
media, do_state);
if (priv->target_state != state) {
- if (do_state)
+ if (do_state) {
media_set_pipeline_state_locked (media, state);
-
- g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_NEW_STATE], 0, state,
- NULL);
+ g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_NEW_STATE], 0, state,
+ NULL);
+ }
}
/* remember where we are */
return res;
}
+
+/**
+ * gst_rtsp_media_get_seekable:
+ * @media: a #GstRTSPMedia
+ *
+ * Check if the pipeline for @media seek and up to what point in time,
+ * it can seek.
+ *
+ * Returns: -1 if the stream is not seekable, 0 if seekable only to the beginning
+ * and > 0 to indicate the longest duration between any two random access points.
+ * %G_MAXINT64 means any value is possible.
+ */
+GstClockTimeDiff
+gst_rtsp_media_seekable (GstRTSPMedia * media)
+{
+ GstRTSPMediaPrivate *priv;
+ GstClockTimeDiff res;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+
+ priv = media->priv;
+
+ /* Currently we are not able to seek on live streams,
+ * and no stream is seekable only to the beginning */
+ g_mutex_lock (&priv->lock);
+ res = priv->seekable;
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+}
+
+/**
+ * gst_rtsp_media_complete_pipeline:
+ * @media: a #GstRTSPMedia
+ * @transports: (element-type GstRTSPTransport): a list of #GstRTSPTransport
+ *
+ * Add a receiver and sender parts to the pipeline based on the transport from
+ * SETUP.
+ *
+ * Returns: %TRUE if the media pipeline has been sucessfully updated.
+ */
+gboolean
+gst_rtsp_media_complete_pipeline (GstRTSPMedia * media, GPtrArray * transports)
+{
+ GstRTSPMediaPrivate *priv;
+ guint i;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+ g_return_val_if_fail (transports, FALSE);
+
+ GST_DEBUG_OBJECT (media, "complete pipeline");
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ for (i = 0; i < priv->streams->len; i++) {
+ GstRTSPStreamTransport *transport;
+ GstRTSPStream *stream;
+ const GstRTSPTransport *rtsp_transport;
+
+ transport = g_ptr_array_index (transports, i);
+ if (!transport)
+ continue;
+
+ stream = gst_rtsp_stream_transport_get_stream (transport);
+ if (!stream)
+ continue;
+
+ rtsp_transport = gst_rtsp_stream_transport_get_transport (transport);
+
+ if (!gst_rtsp_stream_complete_stream (stream, rtsp_transport)) {
+ g_mutex_unlock (&priv->lock);
+ return FALSE;
+ }
+ }
+
+ priv->complete = TRUE;
+ g_mutex_unlock (&priv->lock);
+
+ return TRUE;
+}