mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-14 05:12:09 +00:00
6f1cad9237
default_prepare() takes a transfer-none reference GstRTSPMedia object. Later on a g_idle_source_new() is created and a pointer to the media object is passed as user data. If the media is freed before the idle source is dispatched the media object pointer is invalid, but the idle source callback expects it to still be valid. To fix this a reference to the media object is taken when registering the source callback function and a corresponding release of the reference is done when the souce is destroyed. Fixes https://bugzilla.gnome.org/show_bug.cgi?id=755748
4137 lines
108 KiB
C
4137 lines
108 KiB
C
/* GStreamer
|
|
* Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
|
|
* Copyright (C) 2015 Centricular Ltd
|
|
* Author: Sebastian Dröge <sebastian@centricular.com>
|
|
*
|
|
* 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.
|
|
*/
|
|
/**
|
|
* SECTION:rtsp-media
|
|
* @short_description: The media pipeline
|
|
* @see_also: #GstRTSPMediaFactory, #GstRTSPStream, #GstRTSPSession,
|
|
* #GstRTSPSessionMedia
|
|
*
|
|
* a #GstRTSPMedia contains the complete GStreamer pipeline to manage the
|
|
* streaming to the clients. The actual data transfer is done by the
|
|
* #GstRTSPStream objects that are created and exposed by the #GstRTSPMedia.
|
|
*
|
|
* The #GstRTSPMedia is usually created from a #GstRTSPMediaFactory when the
|
|
* client does a DESCRIBE or SETUP of a resource.
|
|
*
|
|
* A media is created with gst_rtsp_media_new() that takes the element that will
|
|
* provide the streaming elements. For each of the streams, a new #GstRTSPStream
|
|
* object needs to be made with the gst_rtsp_media_create_stream() which takes
|
|
* the payloader element and the source pad that produces the RTP stream.
|
|
*
|
|
* The pipeline of the media is set to PAUSED with gst_rtsp_media_prepare(). The
|
|
* prepare method will add rtpbin and sinks and sources to send and receive RTP
|
|
* and RTCP packets from the clients. Each stream srcpad is connected to an
|
|
* input into the internal rtpbin.
|
|
*
|
|
* It is also possible to dynamically create #GstRTSPStream objects during the
|
|
* prepare phase. With gst_rtsp_media_get_status() you can check the status of
|
|
* the prepare phase.
|
|
*
|
|
* After the media is prepared, it is ready for streaming. It will usually be
|
|
* managed in a session with gst_rtsp_session_manage_media(). See
|
|
* #GstRTSPSession and #GstRTSPSessionMedia.
|
|
*
|
|
* The state of the media can be controlled with gst_rtsp_media_set_state ().
|
|
* Seeking can be done with gst_rtsp_media_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
|
|
* cleanly shut down.
|
|
*
|
|
* With gst_rtsp_media_set_shared(), the media can be shared between multiple
|
|
* clients. With gst_rtsp_media_set_reusable() you can control if the pipeline
|
|
* can be prepared again after an unprepare.
|
|
*
|
|
* Last reviewed on 2013-07-11 (1.0.0)
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <gst/app/gstappsrc.h>
|
|
#include <gst/app/gstappsink.h>
|
|
|
|
#include <gst/sdp/gstmikey.h>
|
|
#include <gst/rtp/gstrtppayloads.h>
|
|
|
|
#define AES_128_KEY_LEN 16
|
|
#define AES_256_KEY_LEN 32
|
|
|
|
#define HMAC_32_KEY_LEN 4
|
|
#define HMAC_80_KEY_LEN 10
|
|
|
|
#include "rtsp-media.h"
|
|
|
|
#define GST_RTSP_MEDIA_GET_PRIVATE(obj) \
|
|
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_RTSP_MEDIA, GstRTSPMediaPrivate))
|
|
|
|
struct _GstRTSPMediaPrivate
|
|
{
|
|
GMutex lock;
|
|
GCond cond;
|
|
|
|
/* protected by lock */
|
|
GstRTSPPermissions *permissions;
|
|
gboolean shared;
|
|
gboolean suspend_mode;
|
|
gboolean reusable;
|
|
GstRTSPProfile profiles;
|
|
GstRTSPLowerTrans protocols;
|
|
gboolean reused;
|
|
gboolean eos_shutdown;
|
|
guint buffer_size;
|
|
GstRTSPAddressPool *pool;
|
|
gboolean blocked;
|
|
GstRTSPTransportMode transport_mode;
|
|
|
|
GstElement *element;
|
|
GRecMutex state_lock; /* locking order: state lock, lock */
|
|
GPtrArray *streams; /* protected by lock */
|
|
GList *dynamic; /* protected by lock */
|
|
GstRTSPMediaStatus status; /* protected by lock */
|
|
gint prepare_count;
|
|
gint n_active;
|
|
gboolean adding;
|
|
|
|
/* the pipeline for the media */
|
|
GstElement *pipeline;
|
|
GstElement *fakesink; /* protected by lock */
|
|
GSource *source;
|
|
guint id;
|
|
GstRTSPThread *thread;
|
|
|
|
gboolean time_provider;
|
|
GstNetTimeProvider *nettime;
|
|
|
|
gboolean is_live;
|
|
gboolean seekable;
|
|
gboolean buffering;
|
|
GstState target_state;
|
|
|
|
/* RTP session manager */
|
|
GstElement *rtpbin;
|
|
|
|
/* the range of media */
|
|
GstRTSPTimeRange range; /* protected by lock */
|
|
GstClockTime range_start;
|
|
GstClockTime range_stop;
|
|
|
|
GList *payloads; /* protected by lock */
|
|
GstClockTime rtx_time; /* protected by lock */
|
|
guint latency; /* protected by lock */
|
|
};
|
|
|
|
#define DEFAULT_SHARED FALSE
|
|
#define DEFAULT_SUSPEND_MODE GST_RTSP_SUSPEND_MODE_NONE
|
|
#define DEFAULT_REUSABLE FALSE
|
|
#define DEFAULT_PROFILES GST_RTSP_PROFILE_AVP
|
|
#define DEFAULT_PROTOCOLS GST_RTSP_LOWER_TRANS_UDP | GST_RTSP_LOWER_TRANS_UDP_MCAST | \
|
|
GST_RTSP_LOWER_TRANS_TCP
|
|
#define DEFAULT_EOS_SHUTDOWN FALSE
|
|
#define DEFAULT_BUFFER_SIZE 0x80000
|
|
#define DEFAULT_TIME_PROVIDER FALSE
|
|
#define DEFAULT_LATENCY 200
|
|
#define DEFAULT_TRANSPORT_MODE GST_RTSP_TRANSPORT_MODE_PLAY
|
|
|
|
/* define to dump received RTCP packets */
|
|
#undef DUMP_STATS
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_SHARED,
|
|
PROP_SUSPEND_MODE,
|
|
PROP_REUSABLE,
|
|
PROP_PROFILES,
|
|
PROP_PROTOCOLS,
|
|
PROP_EOS_SHUTDOWN,
|
|
PROP_BUFFER_SIZE,
|
|
PROP_ELEMENT,
|
|
PROP_TIME_PROVIDER,
|
|
PROP_LATENCY,
|
|
PROP_TRANSPORT_MODE,
|
|
PROP_LAST
|
|
};
|
|
|
|
enum
|
|
{
|
|
SIGNAL_NEW_STREAM,
|
|
SIGNAL_REMOVED_STREAM,
|
|
SIGNAL_PREPARED,
|
|
SIGNAL_UNPREPARED,
|
|
SIGNAL_TARGET_STATE,
|
|
SIGNAL_NEW_STATE,
|
|
SIGNAL_LAST
|
|
};
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (rtsp_media_debug);
|
|
#define GST_CAT_DEFAULT rtsp_media_debug
|
|
|
|
static void gst_rtsp_media_get_property (GObject * object, guint propid,
|
|
GValue * value, GParamSpec * pspec);
|
|
static void gst_rtsp_media_set_property (GObject * object, guint propid,
|
|
const GValue * value, GParamSpec * pspec);
|
|
static void gst_rtsp_media_finalize (GObject * obj);
|
|
|
|
static gboolean default_handle_message (GstRTSPMedia * media,
|
|
GstMessage * message);
|
|
static void finish_unprepare (GstRTSPMedia * media);
|
|
static gboolean default_prepare (GstRTSPMedia * media, GstRTSPThread * thread);
|
|
static gboolean default_unprepare (GstRTSPMedia * media);
|
|
static gboolean default_suspend (GstRTSPMedia * media);
|
|
static gboolean default_unsuspend (GstRTSPMedia * media);
|
|
static gboolean default_convert_range (GstRTSPMedia * media,
|
|
GstRTSPTimeRange * range, GstRTSPRangeUnit unit);
|
|
static gboolean default_query_position (GstRTSPMedia * media,
|
|
gint64 * position);
|
|
static gboolean default_query_stop (GstRTSPMedia * media, gint64 * stop);
|
|
static GstElement *default_create_rtpbin (GstRTSPMedia * media);
|
|
static gboolean default_setup_sdp (GstRTSPMedia * media, GstSDPMessage * sdp,
|
|
GstSDPInfo * info);
|
|
static gboolean default_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp);
|
|
|
|
static gboolean wait_preroll (GstRTSPMedia * media);
|
|
|
|
static GstElement *find_payload_element (GstElement * payloader);
|
|
|
|
static guint gst_rtsp_media_signals[SIGNAL_LAST] = { 0 };
|
|
|
|
#define C_ENUM(v) ((gint) v)
|
|
|
|
GType
|
|
gst_rtsp_suspend_mode_get_type (void)
|
|
{
|
|
static gsize id = 0;
|
|
static const GEnumValue values[] = {
|
|
{C_ENUM (GST_RTSP_SUSPEND_MODE_NONE), "GST_RTSP_SUSPEND_MODE_NONE", "none"},
|
|
{C_ENUM (GST_RTSP_SUSPEND_MODE_PAUSE), "GST_RTSP_SUSPEND_MODE_PAUSE",
|
|
"pause"},
|
|
{C_ENUM (GST_RTSP_SUSPEND_MODE_RESET), "GST_RTSP_SUSPEND_MODE_RESET",
|
|
"reset"},
|
|
{0, NULL, NULL}
|
|
};
|
|
|
|
if (g_once_init_enter (&id)) {
|
|
GType tmp = g_enum_register_static ("GstRTSPSuspendMode", values);
|
|
g_once_init_leave (&id, tmp);
|
|
}
|
|
return (GType) id;
|
|
}
|
|
|
|
#define C_FLAGS(v) ((guint) v)
|
|
|
|
GType
|
|
gst_rtsp_transport_mode_get_type (void)
|
|
{
|
|
static gsize id = 0;
|
|
static const GFlagsValue values[] = {
|
|
{C_FLAGS (GST_RTSP_TRANSPORT_MODE_PLAY), "GST_RTSP_TRANSPORT_MODE_PLAY",
|
|
"play"},
|
|
{C_FLAGS (GST_RTSP_TRANSPORT_MODE_RECORD), "GST_RTSP_TRANSPORT_MODE_RECORD",
|
|
"record"},
|
|
{0, NULL, NULL}
|
|
};
|
|
|
|
if (g_once_init_enter (&id)) {
|
|
GType tmp = g_flags_register_static ("GstRTSPTransportMode", values);
|
|
g_once_init_leave (&id, tmp);
|
|
}
|
|
return (GType) id;
|
|
}
|
|
|
|
G_DEFINE_TYPE (GstRTSPMedia, gst_rtsp_media, G_TYPE_OBJECT);
|
|
|
|
static void
|
|
gst_rtsp_media_class_init (GstRTSPMediaClass * klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
|
|
g_type_class_add_private (klass, sizeof (GstRTSPMediaPrivate));
|
|
|
|
gobject_class = G_OBJECT_CLASS (klass);
|
|
|
|
gobject_class->get_property = gst_rtsp_media_get_property;
|
|
gobject_class->set_property = gst_rtsp_media_set_property;
|
|
gobject_class->finalize = gst_rtsp_media_finalize;
|
|
|
|
g_object_class_install_property (gobject_class, PROP_SHARED,
|
|
g_param_spec_boolean ("shared", "Shared",
|
|
"If this media pipeline can be shared", DEFAULT_SHARED,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_SUSPEND_MODE,
|
|
g_param_spec_enum ("suspend-mode", "Suspend Mode",
|
|
"How to suspend the media in PAUSED", GST_TYPE_RTSP_SUSPEND_MODE,
|
|
DEFAULT_SUSPEND_MODE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_REUSABLE,
|
|
g_param_spec_boolean ("reusable", "Reusable",
|
|
"If this media pipeline can be reused after an unprepare",
|
|
DEFAULT_REUSABLE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_PROFILES,
|
|
g_param_spec_flags ("profiles", "Profiles",
|
|
"Allowed transfer profiles", GST_TYPE_RTSP_PROFILE,
|
|
DEFAULT_PROFILES, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_PROTOCOLS,
|
|
g_param_spec_flags ("protocols", "Protocols",
|
|
"Allowed lower transport protocols", GST_TYPE_RTSP_LOWER_TRANS,
|
|
DEFAULT_PROTOCOLS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_EOS_SHUTDOWN,
|
|
g_param_spec_boolean ("eos-shutdown", "EOS Shutdown",
|
|
"Send an EOS event to the pipeline before unpreparing",
|
|
DEFAULT_EOS_SHUTDOWN, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_BUFFER_SIZE,
|
|
g_param_spec_uint ("buffer-size", "Buffer Size",
|
|
"The kernel UDP buffer size to use", 0, G_MAXUINT,
|
|
DEFAULT_BUFFER_SIZE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_ELEMENT,
|
|
g_param_spec_object ("element", "The Element",
|
|
"The GstBin to use for streaming the media", GST_TYPE_ELEMENT,
|
|
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_TIME_PROVIDER,
|
|
g_param_spec_boolean ("time-provider", "Time Provider",
|
|
"Use a NetTimeProvider for clients",
|
|
DEFAULT_TIME_PROVIDER, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_LATENCY,
|
|
g_param_spec_uint ("latency", "Latency",
|
|
"Latency used for receiving media in milliseconds", 0, G_MAXUINT,
|
|
DEFAULT_BUFFER_SIZE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_TRANSPORT_MODE,
|
|
g_param_spec_flags ("transport-mode", "Transport Mode",
|
|
"If this media pipeline can be used for PLAY or RECORD",
|
|
GST_TYPE_RTSP_TRANSPORT_MODE, DEFAULT_TRANSPORT_MODE,
|
|
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,
|
|
g_cclosure_marshal_generic, G_TYPE_NONE, 1, GST_TYPE_RTSP_STREAM);
|
|
|
|
gst_rtsp_media_signals[SIGNAL_REMOVED_STREAM] =
|
|
g_signal_new ("removed-stream", G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPMediaClass, removed_stream),
|
|
NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1,
|
|
GST_TYPE_RTSP_STREAM);
|
|
|
|
gst_rtsp_media_signals[SIGNAL_PREPARED] =
|
|
g_signal_new ("prepared", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GstRTSPMediaClass, prepared), NULL, NULL,
|
|
g_cclosure_marshal_generic, G_TYPE_NONE, 0, G_TYPE_NONE);
|
|
|
|
gst_rtsp_media_signals[SIGNAL_UNPREPARED] =
|
|
g_signal_new ("unprepared", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GstRTSPMediaClass, unprepared), NULL, NULL,
|
|
g_cclosure_marshal_generic, G_TYPE_NONE, 0, G_TYPE_NONE);
|
|
|
|
gst_rtsp_media_signals[SIGNAL_TARGET_STATE] =
|
|
g_signal_new ("target-state", G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPMediaClass, target_state),
|
|
NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_INT);
|
|
|
|
gst_rtsp_media_signals[SIGNAL_NEW_STATE] =
|
|
g_signal_new ("new-state", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GstRTSPMediaClass, new_state), NULL, NULL,
|
|
g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_INT);
|
|
|
|
GST_DEBUG_CATEGORY_INIT (rtsp_media_debug, "rtspmedia", 0, "GstRTSPMedia");
|
|
|
|
klass->handle_message = default_handle_message;
|
|
klass->prepare = default_prepare;
|
|
klass->unprepare = default_unprepare;
|
|
klass->suspend = default_suspend;
|
|
klass->unsuspend = default_unsuspend;
|
|
klass->convert_range = default_convert_range;
|
|
klass->query_position = default_query_position;
|
|
klass->query_stop = default_query_stop;
|
|
klass->create_rtpbin = default_create_rtpbin;
|
|
klass->setup_sdp = default_setup_sdp;
|
|
klass->handle_sdp = default_handle_sdp;
|
|
}
|
|
|
|
static void
|
|
gst_rtsp_media_init (GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaPrivate *priv = GST_RTSP_MEDIA_GET_PRIVATE (media);
|
|
|
|
media->priv = priv;
|
|
|
|
priv->streams = g_ptr_array_new_with_free_func (g_object_unref);
|
|
g_mutex_init (&priv->lock);
|
|
g_cond_init (&priv->cond);
|
|
g_rec_mutex_init (&priv->state_lock);
|
|
|
|
priv->shared = DEFAULT_SHARED;
|
|
priv->suspend_mode = DEFAULT_SUSPEND_MODE;
|
|
priv->reusable = DEFAULT_REUSABLE;
|
|
priv->profiles = DEFAULT_PROFILES;
|
|
priv->protocols = DEFAULT_PROTOCOLS;
|
|
priv->eos_shutdown = DEFAULT_EOS_SHUTDOWN;
|
|
priv->buffer_size = DEFAULT_BUFFER_SIZE;
|
|
priv->time_provider = DEFAULT_TIME_PROVIDER;
|
|
priv->transport_mode = DEFAULT_TRANSPORT_MODE;
|
|
}
|
|
|
|
static void
|
|
gst_rtsp_media_finalize (GObject * obj)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
GstRTSPMedia *media;
|
|
|
|
media = GST_RTSP_MEDIA (obj);
|
|
priv = media->priv;
|
|
|
|
GST_INFO ("finalize media %p", media);
|
|
|
|
if (priv->permissions)
|
|
gst_rtsp_permissions_unref (priv->permissions);
|
|
|
|
g_ptr_array_unref (priv->streams);
|
|
|
|
g_list_free_full (priv->dynamic, gst_object_unref);
|
|
|
|
if (priv->pipeline)
|
|
gst_object_unref (priv->pipeline);
|
|
if (priv->nettime)
|
|
gst_object_unref (priv->nettime);
|
|
gst_object_unref (priv->element);
|
|
if (priv->pool)
|
|
g_object_unref (priv->pool);
|
|
if (priv->payloads)
|
|
g_list_free (priv->payloads);
|
|
g_mutex_clear (&priv->lock);
|
|
g_cond_clear (&priv->cond);
|
|
g_rec_mutex_clear (&priv->state_lock);
|
|
|
|
G_OBJECT_CLASS (gst_rtsp_media_parent_class)->finalize (obj);
|
|
}
|
|
|
|
static void
|
|
gst_rtsp_media_get_property (GObject * object, guint propid,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstRTSPMedia *media = GST_RTSP_MEDIA (object);
|
|
|
|
switch (propid) {
|
|
case PROP_ELEMENT:
|
|
g_value_set_object (value, media->priv->element);
|
|
break;
|
|
case PROP_SHARED:
|
|
g_value_set_boolean (value, gst_rtsp_media_is_shared (media));
|
|
break;
|
|
case PROP_SUSPEND_MODE:
|
|
g_value_set_enum (value, gst_rtsp_media_get_suspend_mode (media));
|
|
break;
|
|
case PROP_REUSABLE:
|
|
g_value_set_boolean (value, gst_rtsp_media_is_reusable (media));
|
|
break;
|
|
case PROP_PROFILES:
|
|
g_value_set_flags (value, gst_rtsp_media_get_profiles (media));
|
|
break;
|
|
case PROP_PROTOCOLS:
|
|
g_value_set_flags (value, gst_rtsp_media_get_protocols (media));
|
|
break;
|
|
case PROP_EOS_SHUTDOWN:
|
|
g_value_set_boolean (value, gst_rtsp_media_is_eos_shutdown (media));
|
|
break;
|
|
case PROP_BUFFER_SIZE:
|
|
g_value_set_uint (value, gst_rtsp_media_get_buffer_size (media));
|
|
break;
|
|
case PROP_TIME_PROVIDER:
|
|
g_value_set_boolean (value, gst_rtsp_media_is_time_provider (media));
|
|
break;
|
|
case PROP_LATENCY:
|
|
g_value_set_uint (value, gst_rtsp_media_get_latency (media));
|
|
break;
|
|
case PROP_TRANSPORT_MODE:
|
|
g_value_set_flags (value, gst_rtsp_media_get_transport_mode (media));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_rtsp_media_set_property (GObject * object, guint propid,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstRTSPMedia *media = GST_RTSP_MEDIA (object);
|
|
|
|
switch (propid) {
|
|
case PROP_ELEMENT:
|
|
media->priv->element = g_value_get_object (value);
|
|
gst_object_ref_sink (media->priv->element);
|
|
break;
|
|
case PROP_SHARED:
|
|
gst_rtsp_media_set_shared (media, g_value_get_boolean (value));
|
|
break;
|
|
case PROP_SUSPEND_MODE:
|
|
gst_rtsp_media_set_suspend_mode (media, g_value_get_enum (value));
|
|
break;
|
|
case PROP_REUSABLE:
|
|
gst_rtsp_media_set_reusable (media, g_value_get_boolean (value));
|
|
break;
|
|
case PROP_PROFILES:
|
|
gst_rtsp_media_set_profiles (media, g_value_get_flags (value));
|
|
break;
|
|
case PROP_PROTOCOLS:
|
|
gst_rtsp_media_set_protocols (media, g_value_get_flags (value));
|
|
break;
|
|
case PROP_EOS_SHUTDOWN:
|
|
gst_rtsp_media_set_eos_shutdown (media, g_value_get_boolean (value));
|
|
break;
|
|
case PROP_BUFFER_SIZE:
|
|
gst_rtsp_media_set_buffer_size (media, g_value_get_uint (value));
|
|
break;
|
|
case PROP_TIME_PROVIDER:
|
|
gst_rtsp_media_use_time_provider (media, g_value_get_boolean (value));
|
|
break;
|
|
case PROP_LATENCY:
|
|
gst_rtsp_media_set_latency (media, g_value_get_uint (value));
|
|
break;
|
|
case PROP_TRANSPORT_MODE:
|
|
gst_rtsp_media_set_transport_mode (media, g_value_get_flags (value));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
|
|
}
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
gint64 position;
|
|
gboolean ret;
|
|
} DoQueryPositionData;
|
|
|
|
static void
|
|
do_query_position (GstRTSPStream * stream, DoQueryPositionData * data)
|
|
{
|
|
gint64 tmp;
|
|
|
|
if (gst_rtsp_stream_query_position (stream, &tmp)) {
|
|
data->position = MAX (data->position, tmp);
|
|
data->ret = TRUE;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
default_query_position (GstRTSPMedia * media, gint64 * position)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
DoQueryPositionData data;
|
|
|
|
priv = media->priv;
|
|
|
|
data.position = -1;
|
|
data.ret = FALSE;
|
|
|
|
g_ptr_array_foreach (priv->streams, (GFunc) do_query_position, &data);
|
|
|
|
*position = data.position;
|
|
|
|
return data.ret;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
gint64 stop;
|
|
gboolean ret;
|
|
} DoQueryStopData;
|
|
|
|
static void
|
|
do_query_stop (GstRTSPStream * stream, DoQueryStopData * data)
|
|
{
|
|
gint64 tmp;
|
|
|
|
if (gst_rtsp_stream_query_stop (stream, &tmp)) {
|
|
data->stop = MAX (data->stop, tmp);
|
|
data->ret = TRUE;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
default_query_stop (GstRTSPMedia * media, gint64 * stop)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
DoQueryStopData data;
|
|
|
|
priv = media->priv;
|
|
|
|
data.stop = -1;
|
|
data.ret = FALSE;
|
|
|
|
g_ptr_array_foreach (priv->streams, (GFunc) do_query_stop, &data);
|
|
|
|
*stop = data.stop;
|
|
|
|
return data.ret;
|
|
}
|
|
|
|
static GstElement *
|
|
default_create_rtpbin (GstRTSPMedia * media)
|
|
{
|
|
GstElement *rtpbin;
|
|
|
|
rtpbin = gst_element_factory_make ("rtpbin", NULL);
|
|
|
|
return rtpbin;
|
|
}
|
|
|
|
/* must be called with state lock */
|
|
static void
|
|
collect_media_stats (GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaPrivate *priv = media->priv;
|
|
gint64 position = 0, stop = -1;
|
|
|
|
if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED &&
|
|
priv->status != GST_RTSP_MEDIA_STATUS_PREPARING)
|
|
return;
|
|
|
|
priv->range.unit = GST_RTSP_RANGE_NPT;
|
|
|
|
GST_INFO ("collect media stats");
|
|
|
|
if (priv->is_live) {
|
|
priv->range.min.type = GST_RTSP_TIME_NOW;
|
|
priv->range.min.seconds = -1;
|
|
priv->range_start = -1;
|
|
priv->range.max.type = GST_RTSP_TIME_END;
|
|
priv->range.max.seconds = -1;
|
|
priv->range_stop = -1;
|
|
} else {
|
|
GstRTSPMediaClass *klass;
|
|
gboolean ret;
|
|
|
|
klass = GST_RTSP_MEDIA_GET_CLASS (media);
|
|
|
|
/* get the position */
|
|
ret = FALSE;
|
|
if (klass->query_position)
|
|
ret = klass->query_position (media, &position);
|
|
|
|
if (!ret) {
|
|
GST_INFO ("position query failed");
|
|
position = 0;
|
|
}
|
|
|
|
/* get the current segment stop */
|
|
ret = FALSE;
|
|
if (klass->query_stop)
|
|
ret = klass->query_stop (media, &stop);
|
|
|
|
if (!ret) {
|
|
GST_INFO ("stop query failed");
|
|
stop = -1;
|
|
}
|
|
|
|
GST_INFO ("stats: position %" GST_TIME_FORMAT ", stop %"
|
|
GST_TIME_FORMAT, GST_TIME_ARGS (position), GST_TIME_ARGS (stop));
|
|
|
|
if (position == -1) {
|
|
priv->range.min.type = GST_RTSP_TIME_NOW;
|
|
priv->range.min.seconds = -1;
|
|
priv->range_start = -1;
|
|
} else {
|
|
priv->range.min.type = GST_RTSP_TIME_SECONDS;
|
|
priv->range.min.seconds = ((gdouble) position) / GST_SECOND;
|
|
priv->range_start = position;
|
|
}
|
|
if (stop == -1) {
|
|
priv->range.max.type = GST_RTSP_TIME_END;
|
|
priv->range.max.seconds = -1;
|
|
priv->range_stop = -1;
|
|
} else {
|
|
priv->range.max.type = GST_RTSP_TIME_SECONDS;
|
|
priv->range.max.seconds = ((gdouble) stop) / GST_SECOND;
|
|
priv->range_stop = stop;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_new:
|
|
* @element: (transfer full): a #GstElement
|
|
*
|
|
* Create a new #GstRTSPMedia instance. @element is the bin element that
|
|
* provides the different streams. The #GstRTSPMedia object contains the
|
|
* element to produce RTP data for one or more related (audio/video/..)
|
|
* streams.
|
|
*
|
|
* Ownership is taken of @element.
|
|
*
|
|
* Returns: (transfer full): a new #GstRTSPMedia object.
|
|
*/
|
|
GstRTSPMedia *
|
|
gst_rtsp_media_new (GstElement * element)
|
|
{
|
|
GstRTSPMedia *result;
|
|
|
|
g_return_val_if_fail (GST_IS_ELEMENT (element), NULL);
|
|
|
|
result = g_object_new (GST_TYPE_RTSP_MEDIA, "element", element, NULL);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_get_element:
|
|
* @media: a #GstRTSPMedia
|
|
*
|
|
* Get the element that was used when constructing @media.
|
|
*
|
|
* Returns: (transfer full): a #GstElement. Unref after usage.
|
|
*/
|
|
GstElement *
|
|
gst_rtsp_media_get_element (GstRTSPMedia * media)
|
|
{
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
|
|
|
|
return gst_object_ref (media->priv->element);
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_take_pipeline:
|
|
* @media: a #GstRTSPMedia
|
|
* @pipeline: (transfer full): a #GstPipeline
|
|
*
|
|
* Set @pipeline as the #GstPipeline for @media. Ownership is
|
|
* taken of @pipeline.
|
|
*/
|
|
void
|
|
gst_rtsp_media_take_pipeline (GstRTSPMedia * media, GstPipeline * pipeline)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
GstElement *old;
|
|
GstNetTimeProvider *nettime;
|
|
|
|
g_return_if_fail (GST_IS_RTSP_MEDIA (media));
|
|
g_return_if_fail (GST_IS_PIPELINE (pipeline));
|
|
|
|
priv = media->priv;
|
|
|
|
g_mutex_lock (&priv->lock);
|
|
old = priv->pipeline;
|
|
priv->pipeline = GST_ELEMENT_CAST (pipeline);
|
|
nettime = priv->nettime;
|
|
priv->nettime = NULL;
|
|
g_mutex_unlock (&priv->lock);
|
|
|
|
if (old)
|
|
gst_object_unref (old);
|
|
|
|
if (nettime)
|
|
gst_object_unref (nettime);
|
|
|
|
gst_bin_add (GST_BIN_CAST (pipeline), priv->element);
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_set_permissions:
|
|
* @media: a #GstRTSPMedia
|
|
* @permissions: (transfer none): a #GstRTSPPermissions
|
|
*
|
|
* Set @permissions on @media.
|
|
*/
|
|
void
|
|
gst_rtsp_media_set_permissions (GstRTSPMedia * media,
|
|
GstRTSPPermissions * permissions)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
|
|
g_return_if_fail (GST_IS_RTSP_MEDIA (media));
|
|
|
|
priv = media->priv;
|
|
|
|
g_mutex_lock (&priv->lock);
|
|
if (priv->permissions)
|
|
gst_rtsp_permissions_unref (priv->permissions);
|
|
if ((priv->permissions = permissions))
|
|
gst_rtsp_permissions_ref (permissions);
|
|
g_mutex_unlock (&priv->lock);
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_get_permissions:
|
|
* @media: a #GstRTSPMedia
|
|
*
|
|
* Get the permissions object from @media.
|
|
*
|
|
* Returns: (transfer full): a #GstRTSPPermissions object, unref after usage.
|
|
*/
|
|
GstRTSPPermissions *
|
|
gst_rtsp_media_get_permissions (GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
GstRTSPPermissions *result;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
|
|
|
|
priv = media->priv;
|
|
|
|
g_mutex_lock (&priv->lock);
|
|
if ((result = priv->permissions))
|
|
gst_rtsp_permissions_ref (result);
|
|
g_mutex_unlock (&priv->lock);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_set_suspend_mode:
|
|
* @media: a #GstRTSPMedia
|
|
* @mode: the new #GstRTSPSuspendMode
|
|
*
|
|
* Control how @ media will be suspended after the SDP has been generated and
|
|
* after a PAUSE request has been performed.
|
|
*
|
|
* Media must be unprepared when setting the suspend mode.
|
|
*/
|
|
void
|
|
gst_rtsp_media_set_suspend_mode (GstRTSPMedia * media, GstRTSPSuspendMode mode)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
|
|
g_return_if_fail (GST_IS_RTSP_MEDIA (media));
|
|
|
|
priv = media->priv;
|
|
|
|
g_rec_mutex_lock (&priv->state_lock);
|
|
if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARED)
|
|
goto was_prepared;
|
|
priv->suspend_mode = mode;
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
|
|
return;
|
|
|
|
/* ERRORS */
|
|
was_prepared:
|
|
{
|
|
GST_WARNING ("media %p was prepared", media);
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_get_suspend_mode:
|
|
* @media: a #GstRTSPMedia
|
|
*
|
|
* Get how @media will be suspended.
|
|
*
|
|
* Returns: #GstRTSPSuspendMode.
|
|
*/
|
|
GstRTSPSuspendMode
|
|
gst_rtsp_media_get_suspend_mode (GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
GstRTSPSuspendMode res;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), GST_RTSP_SUSPEND_MODE_NONE);
|
|
|
|
priv = media->priv;
|
|
|
|
g_rec_mutex_lock (&priv->state_lock);
|
|
res = priv->suspend_mode;
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_set_shared:
|
|
* @media: a #GstRTSPMedia
|
|
* @shared: the new value
|
|
*
|
|
* Set or unset if the pipeline for @media can be shared will multiple clients.
|
|
* When @shared is %TRUE, client requests for this media will share the media
|
|
* pipeline.
|
|
*/
|
|
void
|
|
gst_rtsp_media_set_shared (GstRTSPMedia * media, gboolean shared)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
|
|
g_return_if_fail (GST_IS_RTSP_MEDIA (media));
|
|
|
|
priv = media->priv;
|
|
|
|
g_mutex_lock (&priv->lock);
|
|
priv->shared = shared;
|
|
g_mutex_unlock (&priv->lock);
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_is_shared:
|
|
* @media: a #GstRTSPMedia
|
|
*
|
|
* Check if the pipeline for @media can be shared between multiple clients.
|
|
*
|
|
* Returns: %TRUE if the media can be shared between clients.
|
|
*/
|
|
gboolean
|
|
gst_rtsp_media_is_shared (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->shared;
|
|
g_mutex_unlock (&priv->lock);
|
|
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_set_reusable:
|
|
* @media: a #GstRTSPMedia
|
|
* @reusable: the new value
|
|
*
|
|
* Set or unset if the pipeline for @media can be reused after the pipeline has
|
|
* been unprepared.
|
|
*/
|
|
void
|
|
gst_rtsp_media_set_reusable (GstRTSPMedia * media, gboolean reusable)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
|
|
g_return_if_fail (GST_IS_RTSP_MEDIA (media));
|
|
|
|
priv = media->priv;
|
|
|
|
g_mutex_lock (&priv->lock);
|
|
priv->reusable = reusable;
|
|
g_mutex_unlock (&priv->lock);
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_is_reusable:
|
|
* @media: a #GstRTSPMedia
|
|
*
|
|
* Check if the pipeline for @media can be reused after an unprepare.
|
|
*
|
|
* Returns: %TRUE if the media can be reused
|
|
*/
|
|
gboolean
|
|
gst_rtsp_media_is_reusable (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->reusable;
|
|
g_mutex_unlock (&priv->lock);
|
|
|
|
return res;
|
|
}
|
|
|
|
static void
|
|
do_set_profiles (GstRTSPStream * stream, GstRTSPProfile * profiles)
|
|
{
|
|
gst_rtsp_stream_set_profiles (stream, *profiles);
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_set_profiles:
|
|
* @media: a #GstRTSPMedia
|
|
* @profiles: the new flags
|
|
*
|
|
* Configure the allowed lower transport for @media.
|
|
*/
|
|
void
|
|
gst_rtsp_media_set_profiles (GstRTSPMedia * media, GstRTSPProfile profiles)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
|
|
g_return_if_fail (GST_IS_RTSP_MEDIA (media));
|
|
|
|
priv = media->priv;
|
|
|
|
g_mutex_lock (&priv->lock);
|
|
priv->profiles = profiles;
|
|
g_ptr_array_foreach (priv->streams, (GFunc) do_set_profiles, &profiles);
|
|
g_mutex_unlock (&priv->lock);
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_get_profiles:
|
|
* @media: a #GstRTSPMedia
|
|
*
|
|
* Get the allowed profiles of @media.
|
|
*
|
|
* Returns: a #GstRTSPProfile
|
|
*/
|
|
GstRTSPProfile
|
|
gst_rtsp_media_get_profiles (GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
GstRTSPProfile res;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), GST_RTSP_PROFILE_UNKNOWN);
|
|
|
|
priv = media->priv;
|
|
|
|
g_mutex_lock (&priv->lock);
|
|
res = priv->profiles;
|
|
g_mutex_unlock (&priv->lock);
|
|
|
|
return res;
|
|
}
|
|
|
|
static void
|
|
do_set_protocols (GstRTSPStream * stream, GstRTSPLowerTrans * protocols)
|
|
{
|
|
gst_rtsp_stream_set_protocols (stream, *protocols);
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_set_protocols:
|
|
* @media: a #GstRTSPMedia
|
|
* @protocols: the new flags
|
|
*
|
|
* Configure the allowed lower transport for @media.
|
|
*/
|
|
void
|
|
gst_rtsp_media_set_protocols (GstRTSPMedia * media, GstRTSPLowerTrans protocols)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
|
|
g_return_if_fail (GST_IS_RTSP_MEDIA (media));
|
|
|
|
priv = media->priv;
|
|
|
|
g_mutex_lock (&priv->lock);
|
|
priv->protocols = protocols;
|
|
g_ptr_array_foreach (priv->streams, (GFunc) do_set_protocols, &protocols);
|
|
g_mutex_unlock (&priv->lock);
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_get_protocols:
|
|
* @media: a #GstRTSPMedia
|
|
*
|
|
* Get the allowed protocols of @media.
|
|
*
|
|
* Returns: a #GstRTSPLowerTrans
|
|
*/
|
|
GstRTSPLowerTrans
|
|
gst_rtsp_media_get_protocols (GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
GstRTSPLowerTrans res;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media),
|
|
GST_RTSP_LOWER_TRANS_UNKNOWN);
|
|
|
|
priv = media->priv;
|
|
|
|
g_mutex_lock (&priv->lock);
|
|
res = priv->protocols;
|
|
g_mutex_unlock (&priv->lock);
|
|
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_set_eos_shutdown:
|
|
* @media: a #GstRTSPMedia
|
|
* @eos_shutdown: the new value
|
|
*
|
|
* Set or unset if an EOS event will be sent to the pipeline for @media before
|
|
* it is unprepared.
|
|
*/
|
|
void
|
|
gst_rtsp_media_set_eos_shutdown (GstRTSPMedia * media, gboolean eos_shutdown)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
|
|
g_return_if_fail (GST_IS_RTSP_MEDIA (media));
|
|
|
|
priv = media->priv;
|
|
|
|
g_mutex_lock (&priv->lock);
|
|
priv->eos_shutdown = eos_shutdown;
|
|
g_mutex_unlock (&priv->lock);
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_is_eos_shutdown:
|
|
* @media: a #GstRTSPMedia
|
|
*
|
|
* Check if the pipeline for @media will send an EOS down the pipeline before
|
|
* unpreparing.
|
|
*
|
|
* Returns: %TRUE if the media will send EOS before unpreparing.
|
|
*/
|
|
gboolean
|
|
gst_rtsp_media_is_eos_shutdown (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->eos_shutdown;
|
|
g_mutex_unlock (&priv->lock);
|
|
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_set_buffer_size:
|
|
* @media: a #GstRTSPMedia
|
|
* @size: the new value
|
|
*
|
|
* Set the kernel UDP buffer size.
|
|
*/
|
|
void
|
|
gst_rtsp_media_set_buffer_size (GstRTSPMedia * media, guint size)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
guint i;
|
|
|
|
g_return_if_fail (GST_IS_RTSP_MEDIA (media));
|
|
|
|
GST_LOG_OBJECT (media, "set buffer size %u", size);
|
|
|
|
priv = media->priv;
|
|
|
|
g_mutex_lock (&priv->lock);
|
|
priv->buffer_size = size;
|
|
|
|
for (i = 0; i < priv->streams->len; i++) {
|
|
GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
|
|
gst_rtsp_stream_set_buffer_size (stream, size);
|
|
}
|
|
g_mutex_unlock (&priv->lock);
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_get_buffer_size:
|
|
* @media: a #GstRTSPMedia
|
|
*
|
|
* Get the kernel UDP buffer size.
|
|
*
|
|
* Returns: the kernel UDP buffer size.
|
|
*/
|
|
guint
|
|
gst_rtsp_media_get_buffer_size (GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
guint res;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
|
|
|
|
priv = media->priv;
|
|
|
|
g_mutex_lock (&priv->lock);
|
|
res = priv->buffer_size;
|
|
g_mutex_unlock (&priv->lock);
|
|
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_set_retransmission_time:
|
|
* @media: a #GstRTSPMedia
|
|
* @time: the new value
|
|
*
|
|
* Set the amount of time to store retransmission packets.
|
|
*/
|
|
void
|
|
gst_rtsp_media_set_retransmission_time (GstRTSPMedia * media, GstClockTime time)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
guint i;
|
|
|
|
g_return_if_fail (GST_IS_RTSP_MEDIA (media));
|
|
|
|
GST_LOG_OBJECT (media, "set retransmission time %" G_GUINT64_FORMAT, time);
|
|
|
|
priv = media->priv;
|
|
|
|
g_mutex_lock (&priv->lock);
|
|
priv->rtx_time = time;
|
|
for (i = 0; i < priv->streams->len; i++) {
|
|
GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
|
|
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_get_retransmission_time:
|
|
* @media: a #GstRTSPMedia
|
|
*
|
|
* Get the amount of time to store retransmission data.
|
|
*
|
|
* Returns: the amount of time to store retransmission data.
|
|
*/
|
|
GstClockTime
|
|
gst_rtsp_media_get_retransmission_time (GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
GstClockTime res;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
|
|
|
|
priv = media->priv;
|
|
|
|
g_mutex_unlock (&priv->lock);
|
|
res = priv->rtx_time;
|
|
g_mutex_unlock (&priv->lock);
|
|
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_set_latncy:
|
|
* @media: a #GstRTSPMedia
|
|
* @latency: latency in milliseconds
|
|
*
|
|
* Configure the latency used for receiving media.
|
|
*/
|
|
void
|
|
gst_rtsp_media_set_latency (GstRTSPMedia * media, guint latency)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
|
|
g_return_if_fail (GST_IS_RTSP_MEDIA (media));
|
|
|
|
GST_LOG_OBJECT (media, "set latency %ums", latency);
|
|
|
|
priv = media->priv;
|
|
|
|
g_mutex_lock (&priv->lock);
|
|
priv->latency = latency;
|
|
if (priv->rtpbin)
|
|
g_object_set (priv->rtpbin, "latency", latency, NULL);
|
|
g_mutex_unlock (&priv->lock);
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_get_latency:
|
|
* @media: a #GstRTSPMedia
|
|
*
|
|
* Get the latency that is used for receiving media.
|
|
*
|
|
* Returns: latency in milliseconds
|
|
*/
|
|
guint
|
|
gst_rtsp_media_get_latency (GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
guint res;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
|
|
|
|
priv = media->priv;
|
|
|
|
g_mutex_unlock (&priv->lock);
|
|
res = priv->latency;
|
|
g_mutex_unlock (&priv->lock);
|
|
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_use_time_provider:
|
|
* @media: a #GstRTSPMedia
|
|
* @time_provider: if a #GstNetTimeProvider should be used
|
|
*
|
|
* Set @media to provide a #GstNetTimeProvider.
|
|
*/
|
|
void
|
|
gst_rtsp_media_use_time_provider (GstRTSPMedia * media, gboolean time_provider)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
|
|
g_return_if_fail (GST_IS_RTSP_MEDIA (media));
|
|
|
|
priv = media->priv;
|
|
|
|
g_mutex_lock (&priv->lock);
|
|
priv->time_provider = time_provider;
|
|
g_mutex_unlock (&priv->lock);
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_is_time_provider:
|
|
* @media: a #GstRTSPMedia
|
|
*
|
|
* Check if @media can provide a #GstNetTimeProvider for its pipeline clock.
|
|
*
|
|
* Use gst_rtsp_media_get_time_provider() to get the network clock.
|
|
*
|
|
* Returns: %TRUE if @media can provide a #GstNetTimeProvider.
|
|
*/
|
|
gboolean
|
|
gst_rtsp_media_is_time_provider (GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
gboolean res;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
|
|
|
|
priv = media->priv;
|
|
|
|
g_mutex_unlock (&priv->lock);
|
|
res = priv->time_provider;
|
|
g_mutex_unlock (&priv->lock);
|
|
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_set_address_pool:
|
|
* @media: a #GstRTSPMedia
|
|
* @pool: (transfer none): a #GstRTSPAddressPool
|
|
*
|
|
* configure @pool to be used as the address pool of @media.
|
|
*/
|
|
void
|
|
gst_rtsp_media_set_address_pool (GstRTSPMedia * media,
|
|
GstRTSPAddressPool * pool)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
GstRTSPAddressPool *old;
|
|
|
|
g_return_if_fail (GST_IS_RTSP_MEDIA (media));
|
|
|
|
priv = media->priv;
|
|
|
|
GST_LOG_OBJECT (media, "set address pool %p", pool);
|
|
|
|
g_mutex_lock (&priv->lock);
|
|
if ((old = priv->pool) != pool)
|
|
priv->pool = pool ? g_object_ref (pool) : NULL;
|
|
else
|
|
old = NULL;
|
|
g_ptr_array_foreach (priv->streams, (GFunc) gst_rtsp_stream_set_address_pool,
|
|
pool);
|
|
g_mutex_unlock (&priv->lock);
|
|
|
|
if (old)
|
|
g_object_unref (old);
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_get_address_pool:
|
|
* @media: a #GstRTSPMedia
|
|
*
|
|
* Get the #GstRTSPAddressPool used as the address pool of @media.
|
|
*
|
|
* Returns: (transfer full): the #GstRTSPAddressPool of @media. g_object_unref() after
|
|
* usage.
|
|
*/
|
|
GstRTSPAddressPool *
|
|
gst_rtsp_media_get_address_pool (GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
GstRTSPAddressPool *result;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
|
|
|
|
priv = media->priv;
|
|
|
|
g_mutex_lock (&priv->lock);
|
|
if ((result = priv->pool))
|
|
g_object_ref (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
|
|
_next_available_pt (GList * payloads)
|
|
{
|
|
guint i;
|
|
|
|
for (i = 96; i <= 127; i++) {
|
|
GList *iter = g_list_find (payloads, GINT_TO_POINTER (i));
|
|
if (!iter)
|
|
return GPOINTER_TO_UINT (i);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_collect_streams:
|
|
* @media: a #GstRTSPMedia
|
|
*
|
|
* Find all payloader elements, they should be named pay\%d in the
|
|
* element of @media, and create #GstRTSPStreams for them.
|
|
*
|
|
* Collect all dynamic elements, named dynpay\%d, and add them to
|
|
* the list of dynamic elements.
|
|
*
|
|
* Find all depayloader elements, they should be named depay\%d in the
|
|
* element of @media, and create #GstRTSPStreams for them.
|
|
*/
|
|
void
|
|
gst_rtsp_media_collect_streams (GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
GstElement *element, *elem;
|
|
GstPad *pad;
|
|
gint i;
|
|
gboolean have_elem;
|
|
gboolean more_elem_remaining = TRUE;
|
|
GstRTSPTransportMode mode = 0;
|
|
|
|
g_return_if_fail (GST_IS_RTSP_MEDIA (media));
|
|
|
|
priv = media->priv;
|
|
element = priv->element;
|
|
|
|
have_elem = FALSE;
|
|
for (i = 0; more_elem_remaining; i++) {
|
|
gchar *name;
|
|
|
|
more_elem_remaining = FALSE;
|
|
|
|
name = g_strdup_printf ("pay%d", i);
|
|
if ((elem = gst_bin_get_by_name (GST_BIN (element), name))) {
|
|
GstElement *pay;
|
|
GST_INFO ("found stream %d with payloader %p", i, elem);
|
|
|
|
/* take the pad of the payloader */
|
|
pad = gst_element_get_static_pad (elem, "src");
|
|
|
|
/* find the real payload element in case elem is a GstBin */
|
|
pay = find_payload_element (elem);
|
|
|
|
/* create the stream */
|
|
if (pay == NULL) {
|
|
GST_WARNING ("could not find real payloader, using bin");
|
|
gst_rtsp_media_create_stream (media, elem, pad);
|
|
} else {
|
|
gst_rtsp_media_create_stream (media, pay, pad);
|
|
gst_object_unref (pay);
|
|
}
|
|
|
|
gst_object_unref (pad);
|
|
gst_object_unref (elem);
|
|
|
|
have_elem = TRUE;
|
|
more_elem_remaining = TRUE;
|
|
mode |= GST_RTSP_TRANSPORT_MODE_PLAY;
|
|
}
|
|
g_free (name);
|
|
|
|
name = g_strdup_printf ("dynpay%d", i);
|
|
if ((elem = gst_bin_get_by_name (GST_BIN (element), name))) {
|
|
/* a stream that will dynamically create pads to provide RTP packets */
|
|
GST_INFO ("found dynamic element %d, %p", i, elem);
|
|
|
|
g_mutex_lock (&priv->lock);
|
|
priv->dynamic = g_list_prepend (priv->dynamic, elem);
|
|
g_mutex_unlock (&priv->lock);
|
|
|
|
have_elem = TRUE;
|
|
more_elem_remaining = TRUE;
|
|
mode |= GST_RTSP_TRANSPORT_MODE_PLAY;
|
|
}
|
|
g_free (name);
|
|
|
|
name = g_strdup_printf ("depay%d", i);
|
|
if ((elem = gst_bin_get_by_name (GST_BIN (element), name))) {
|
|
GST_INFO ("found stream %d with depayloader %p", i, elem);
|
|
|
|
/* take the pad of the payloader */
|
|
pad = gst_element_get_static_pad (elem, "sink");
|
|
/* create the stream */
|
|
gst_rtsp_media_create_stream (media, elem, pad);
|
|
gst_object_unref (pad);
|
|
gst_object_unref (elem);
|
|
|
|
have_elem = TRUE;
|
|
more_elem_remaining = TRUE;
|
|
mode |= GST_RTSP_TRANSPORT_MODE_RECORD;
|
|
}
|
|
g_free (name);
|
|
}
|
|
|
|
if (have_elem) {
|
|
if (priv->transport_mode != mode)
|
|
GST_WARNING ("found different mode than expected (0x%02x != 0x%02d)",
|
|
priv->transport_mode, mode);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_create_stream:
|
|
* @media: a #GstRTSPMedia
|
|
* @payloader: a #GstElement
|
|
* @pad: a #GstPad
|
|
*
|
|
* Create a new stream in @media that provides RTP data on @pad.
|
|
* @pad should be a pad of an element inside @media->element.
|
|
*
|
|
* Returns: (transfer none): a new #GstRTSPStream that remains valid for as long
|
|
* as @media exists.
|
|
*/
|
|
GstRTSPStream *
|
|
gst_rtsp_media_create_stream (GstRTSPMedia * media, GstElement * payloader,
|
|
GstPad * pad)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
GstRTSPStream *stream;
|
|
GstPad *ghostpad;
|
|
gchar *name;
|
|
gint idx;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
|
|
g_return_val_if_fail (GST_IS_ELEMENT (payloader), NULL);
|
|
g_return_val_if_fail (GST_IS_PAD (pad), NULL);
|
|
|
|
priv = media->priv;
|
|
|
|
g_mutex_lock (&priv->lock);
|
|
idx = priv->streams->len;
|
|
|
|
GST_DEBUG ("media %p: creating stream with index %d", media, idx);
|
|
|
|
if (GST_PAD_IS_SRC (pad))
|
|
name = g_strdup_printf ("src_%u", idx);
|
|
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);
|
|
g_free (name);
|
|
|
|
stream = gst_rtsp_stream_new (idx, payloader, ghostpad);
|
|
if (priv->pool)
|
|
gst_rtsp_stream_set_address_pool (stream, priv->pool);
|
|
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);
|
|
|
|
g_ptr_array_add (priv->streams, stream);
|
|
|
|
if (GST_PAD_IS_SRC (pad)) {
|
|
gint i, n;
|
|
|
|
if (priv->payloads)
|
|
g_list_free (priv->payloads);
|
|
priv->payloads = _find_payload_types (media);
|
|
|
|
n = priv->streams->len;
|
|
for (i = 0; i < n; i++) {
|
|
GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
|
|
guint rtx_pt = _next_available_pt (priv->payloads);
|
|
|
|
if (rtx_pt == 0) {
|
|
GST_WARNING ("Ran out of space of dynamic payload types");
|
|
break;
|
|
}
|
|
|
|
gst_rtsp_stream_set_retransmission_pt (stream, rtx_pt);
|
|
|
|
priv->payloads =
|
|
g_list_append (priv->payloads, GUINT_TO_POINTER (rtx_pt));
|
|
}
|
|
}
|
|
g_mutex_unlock (&priv->lock);
|
|
|
|
g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_NEW_STREAM], 0, stream,
|
|
NULL);
|
|
|
|
return stream;
|
|
}
|
|
|
|
static void
|
|
gst_rtsp_media_remove_stream (GstRTSPMedia * media, GstRTSPStream * stream)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
GstPad *srcpad;
|
|
|
|
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);
|
|
gst_object_unref (srcpad);
|
|
/* now remove the stream */
|
|
g_object_ref (stream);
|
|
g_ptr_array_remove (priv->streams, stream);
|
|
g_mutex_unlock (&priv->lock);
|
|
|
|
g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_REMOVED_STREAM], 0,
|
|
stream, NULL);
|
|
|
|
g_object_unref (stream);
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_n_streams:
|
|
* @media: a #GstRTSPMedia
|
|
*
|
|
* Get the number of streams in this media.
|
|
*
|
|
* Returns: The number of streams.
|
|
*/
|
|
guint
|
|
gst_rtsp_media_n_streams (GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
guint res;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), 0);
|
|
|
|
priv = media->priv;
|
|
|
|
g_mutex_lock (&priv->lock);
|
|
res = priv->streams->len;
|
|
g_mutex_unlock (&priv->lock);
|
|
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_get_stream:
|
|
* @media: a #GstRTSPMedia
|
|
* @idx: the stream index
|
|
*
|
|
* Retrieve the stream with index @idx from @media.
|
|
*
|
|
* Returns: (nullable) (transfer none): the #GstRTSPStream at index
|
|
* @idx or %NULL when a stream with that index did not exist.
|
|
*/
|
|
GstRTSPStream *
|
|
gst_rtsp_media_get_stream (GstRTSPMedia * media, guint idx)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
GstRTSPStream *res;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
|
|
|
|
priv = media->priv;
|
|
|
|
g_mutex_lock (&priv->lock);
|
|
if (idx < priv->streams->len)
|
|
res = g_ptr_array_index (priv->streams, idx);
|
|
else
|
|
res = NULL;
|
|
g_mutex_unlock (&priv->lock);
|
|
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_find_stream:
|
|
* @media: a #GstRTSPMedia
|
|
* @control: the control of the stream
|
|
*
|
|
* Find a stream in @media with @control as the control uri.
|
|
*
|
|
* Returns: (nullable) (transfer none): the #GstRTSPStream with
|
|
* control uri @control or %NULL when a stream with that control did
|
|
* not exist.
|
|
*/
|
|
GstRTSPStream *
|
|
gst_rtsp_media_find_stream (GstRTSPMedia * media, const gchar * control)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
GstRTSPStream *res;
|
|
gint i;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
|
|
g_return_val_if_fail (control != NULL, NULL);
|
|
|
|
priv = media->priv;
|
|
|
|
res = NULL;
|
|
|
|
g_mutex_lock (&priv->lock);
|
|
for (i = 0; i < priv->streams->len; i++) {
|
|
GstRTSPStream *test;
|
|
|
|
test = g_ptr_array_index (priv->streams, i);
|
|
if (gst_rtsp_stream_has_control (test, control)) {
|
|
res = test;
|
|
break;
|
|
}
|
|
}
|
|
g_mutex_unlock (&priv->lock);
|
|
|
|
return res;
|
|
}
|
|
|
|
/* called with state-lock */
|
|
static gboolean
|
|
default_convert_range (GstRTSPMedia * media, GstRTSPTimeRange * range,
|
|
GstRTSPRangeUnit unit)
|
|
{
|
|
return gst_rtsp_range_convert_units (range, unit);
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_get_range_string:
|
|
* @media: a #GstRTSPMedia
|
|
* @play: for the PLAY request
|
|
* @unit: the unit to use for the string
|
|
*
|
|
* 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.
|
|
*/
|
|
gchar *
|
|
gst_rtsp_media_get_range_string (GstRTSPMedia * media, gboolean play,
|
|
GstRTSPRangeUnit unit)
|
|
{
|
|
GstRTSPMediaClass *klass;
|
|
GstRTSPMediaPrivate *priv;
|
|
gchar *result;
|
|
GstRTSPTimeRange range;
|
|
|
|
klass = GST_RTSP_MEDIA_GET_CLASS (media);
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
|
|
g_return_val_if_fail (klass->convert_range != NULL, FALSE);
|
|
|
|
priv = media->priv;
|
|
|
|
g_rec_mutex_lock (&priv->state_lock);
|
|
if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED &&
|
|
priv->status != GST_RTSP_MEDIA_STATUS_SUSPENDED)
|
|
goto not_prepared;
|
|
|
|
g_mutex_lock (&priv->lock);
|
|
|
|
/* Update the range value with current position/duration */
|
|
collect_media_stats (media);
|
|
|
|
/* make copy */
|
|
range = priv->range;
|
|
|
|
if (!play && priv->n_active > 0) {
|
|
range.min.type = GST_RTSP_TIME_NOW;
|
|
range.min.seconds = -1;
|
|
}
|
|
g_mutex_unlock (&priv->lock);
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
|
|
if (!klass->convert_range (media, &range, unit))
|
|
goto conversion_failed;
|
|
|
|
result = gst_rtsp_range_to_string (&range);
|
|
|
|
return result;
|
|
|
|
/* ERRORS */
|
|
not_prepared:
|
|
{
|
|
GST_WARNING ("media %p was not prepared", media);
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
return NULL;
|
|
}
|
|
conversion_failed:
|
|
{
|
|
GST_WARNING ("range conversion to unit %d failed", unit);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
stream_update_blocked (GstRTSPStream * stream, GstRTSPMedia * media)
|
|
{
|
|
gst_rtsp_stream_set_blocked (stream, media->priv->blocked);
|
|
}
|
|
|
|
static void
|
|
media_streams_set_blocked (GstRTSPMedia * media, gboolean blocked)
|
|
{
|
|
GstRTSPMediaPrivate *priv = media->priv;
|
|
|
|
GST_DEBUG ("media %p set blocked %d", media, blocked);
|
|
priv->blocked = blocked;
|
|
g_ptr_array_foreach (priv->streams, (GFunc) stream_update_blocked, media);
|
|
}
|
|
|
|
static void
|
|
gst_rtsp_media_set_status (GstRTSPMedia * media, GstRTSPMediaStatus status)
|
|
{
|
|
GstRTSPMediaPrivate *priv = media->priv;
|
|
|
|
g_mutex_lock (&priv->lock);
|
|
priv->status = status;
|
|
GST_DEBUG ("setting new status to %d", status);
|
|
g_cond_broadcast (&priv->cond);
|
|
g_mutex_unlock (&priv->lock);
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_get_status:
|
|
* @media: a #GstRTSPMedia
|
|
*
|
|
* Get the status of @media. When @media is busy preparing, this function waits
|
|
* until @media is prepared or in error.
|
|
*
|
|
* Returns: the status of @media.
|
|
*/
|
|
GstRTSPMediaStatus
|
|
gst_rtsp_media_get_status (GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaPrivate *priv = media->priv;
|
|
GstRTSPMediaStatus result;
|
|
gint64 end_time;
|
|
|
|
g_mutex_lock (&priv->lock);
|
|
end_time = g_get_monotonic_time () + 20 * G_TIME_SPAN_SECOND;
|
|
/* while we are preparing, wait */
|
|
while (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING) {
|
|
GST_DEBUG ("waiting for status change");
|
|
if (!g_cond_wait_until (&priv->cond, &priv->lock, end_time)) {
|
|
GST_DEBUG ("timeout, assuming error status");
|
|
priv->status = GST_RTSP_MEDIA_STATUS_ERROR;
|
|
}
|
|
}
|
|
/* could be success or error */
|
|
result = priv->status;
|
|
GST_DEBUG ("got status %d", result);
|
|
g_mutex_unlock (&priv->lock);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
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);
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
|
|
g_return_val_if_fail (range != NULL, FALSE);
|
|
g_return_val_if_fail (klass->convert_range != NULL, FALSE);
|
|
|
|
priv = media->priv;
|
|
|
|
g_rec_mutex_lock (&priv->state_lock);
|
|
if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED)
|
|
goto not_prepared;
|
|
|
|
/* 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);
|
|
}
|
|
|
|
if (!priv->seekable)
|
|
goto not_seekable;
|
|
|
|
start_type = stop_type = GST_SEEK_TYPE_NONE;
|
|
|
|
if (!klass->convert_range (media, range, GST_RTSP_RANGE_NPT))
|
|
goto not_supported;
|
|
gst_rtsp_range_get_times (range, &start, &stop);
|
|
|
|
GST_INFO ("got %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
|
|
GST_INFO ("current %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (priv->range_start), GST_TIME_ARGS (priv->range_stop));
|
|
|
|
current_position = -1;
|
|
if (klass->query_position)
|
|
klass->query_position (media, ¤t_position);
|
|
GST_INFO ("current media position %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (current_position));
|
|
|
|
if (start != GST_CLOCK_TIME_NONE)
|
|
start_type = GST_SEEK_TYPE_SET;
|
|
|
|
if (priv->range_stop == stop)
|
|
stop = GST_CLOCK_TIME_NONE;
|
|
else if (stop != GST_CLOCK_TIME_NONE)
|
|
stop_type = GST_SEEK_TYPE_SET;
|
|
|
|
if (start != GST_CLOCK_TIME_NONE || stop != GST_CLOCK_TIME_NONE) {
|
|
GstSeekFlags flags;
|
|
|
|
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 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
|
|
* so we end up at exactly the same position after the seek. */
|
|
if (range->min.type == GST_RTSP_TIME_END) { /* Yepp, that's right! */
|
|
if (current_position == -1) {
|
|
GST_WARNING ("current position unknown");
|
|
} else {
|
|
GST_DEBUG ("doing accurate seek to %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (current_position));
|
|
start = current_position;
|
|
start_type = GST_SEEK_TYPE_SET;
|
|
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 (start == current_position && stop_type == GST_SEEK_TYPE_NONE) {
|
|
GST_DEBUG ("not seeking because no position change");
|
|
res = TRUE;
|
|
} else {
|
|
gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING);
|
|
if (priv->blocked)
|
|
media_streams_set_blocked (media, TRUE);
|
|
|
|
/* FIXME, we only do forwards playback, no trick modes yet */
|
|
res = gst_element_seek (priv->pipeline, 1.0, GST_FORMAT_TIME,
|
|
flags, start_type, start, stop_type, stop);
|
|
|
|
/* and block for the seek to complete */
|
|
GST_INFO ("done seeking %d", res);
|
|
if (!res)
|
|
goto seek_failed;
|
|
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
|
|
/* wait until pipeline is prerolled again, this will also collect stats */
|
|
if (!wait_preroll (media))
|
|
goto preroll_failed;
|
|
|
|
g_rec_mutex_lock (&priv->state_lock);
|
|
GST_INFO ("prerolled again");
|
|
}
|
|
} else {
|
|
GST_INFO ("no seek needed");
|
|
res = TRUE;
|
|
}
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
|
|
return res;
|
|
|
|
/* ERRORS */
|
|
not_prepared:
|
|
{
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
GST_INFO ("media %p is not prepared", media);
|
|
return FALSE;
|
|
}
|
|
not_seekable:
|
|
{
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
GST_INFO ("pipeline is not seekable");
|
|
return FALSE;
|
|
}
|
|
not_supported:
|
|
{
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
GST_WARNING ("conversion to npt not supported");
|
|
return FALSE;
|
|
}
|
|
seek_failed:
|
|
{
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
GST_INFO ("seeking failed");
|
|
gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR);
|
|
return FALSE;
|
|
}
|
|
preroll_failed:
|
|
{
|
|
GST_WARNING ("failed to preroll after seek");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
stream_collect_blocking (GstRTSPStream * stream, gboolean * blocked)
|
|
{
|
|
*blocked &= gst_rtsp_stream_is_blocking (stream);
|
|
}
|
|
|
|
static gboolean
|
|
media_streams_blocking (GstRTSPMedia * media)
|
|
{
|
|
gboolean blocking = TRUE;
|
|
|
|
g_ptr_array_foreach (media->priv->streams, (GFunc) stream_collect_blocking,
|
|
&blocking);
|
|
|
|
return blocking;
|
|
}
|
|
|
|
static GstStateChangeReturn
|
|
set_state (GstRTSPMedia * media, GstState state)
|
|
{
|
|
GstRTSPMediaPrivate *priv = media->priv;
|
|
GstStateChangeReturn ret;
|
|
|
|
GST_INFO ("set state to %s for media %p", gst_element_state_get_name (state),
|
|
media);
|
|
ret = gst_element_set_state (priv->pipeline, state);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GstStateChangeReturn
|
|
set_target_state (GstRTSPMedia * media, GstState state, gboolean do_state)
|
|
{
|
|
GstRTSPMediaPrivate *priv = media->priv;
|
|
GstStateChangeReturn ret;
|
|
|
|
GST_INFO ("set target state to %s for media %p",
|
|
gst_element_state_get_name (state), media);
|
|
priv->target_state = state;
|
|
|
|
g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_TARGET_STATE], 0,
|
|
priv->target_state, NULL);
|
|
|
|
if (do_state)
|
|
ret = set_state (media, state);
|
|
else
|
|
ret = GST_STATE_CHANGE_SUCCESS;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* called with state-lock */
|
|
static gboolean
|
|
default_handle_message (GstRTSPMedia * media, GstMessage * message)
|
|
{
|
|
GstRTSPMediaPrivate *priv = media->priv;
|
|
GstMessageType type;
|
|
|
|
type = GST_MESSAGE_TYPE (message);
|
|
|
|
switch (type) {
|
|
case GST_MESSAGE_STATE_CHANGED:
|
|
{
|
|
GstState old, new, pending;
|
|
|
|
if (GST_MESSAGE_SRC (message) != GST_OBJECT (priv->pipeline))
|
|
break;
|
|
|
|
gst_message_parse_state_changed (message, &old, &new, &pending);
|
|
|
|
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) {
|
|
GST_INFO ("%p: went to PAUSED, prepared now", media);
|
|
collect_media_stats (media);
|
|
|
|
if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING)
|
|
gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case GST_MESSAGE_BUFFERING:
|
|
{
|
|
gint percent;
|
|
|
|
gst_message_parse_buffering (message, &percent);
|
|
|
|
/* no state management needed for live pipelines */
|
|
if (priv->is_live)
|
|
break;
|
|
|
|
if (percent == 100) {
|
|
/* a 100% message means buffering is done */
|
|
priv->buffering = FALSE;
|
|
/* if the desired state is playing, go back */
|
|
if (priv->target_state == GST_STATE_PLAYING) {
|
|
GST_INFO ("Buffering done, setting pipeline to PLAYING");
|
|
set_state (media, GST_STATE_PLAYING);
|
|
} else {
|
|
GST_INFO ("Buffering done");
|
|
}
|
|
} else {
|
|
/* buffering busy */
|
|
if (priv->buffering == FALSE) {
|
|
if (priv->target_state == GST_STATE_PLAYING) {
|
|
/* we were not buffering but PLAYING, PAUSE the pipeline. */
|
|
GST_INFO ("Buffering, setting pipeline to PAUSED ...");
|
|
set_state (media, GST_STATE_PAUSED);
|
|
} else {
|
|
GST_INFO ("Buffering ...");
|
|
}
|
|
}
|
|
priv->buffering = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case GST_MESSAGE_LATENCY:
|
|
{
|
|
gst_bin_recalculate_latency (GST_BIN_CAST (priv->pipeline));
|
|
break;
|
|
}
|
|
case GST_MESSAGE_ERROR:
|
|
{
|
|
GError *gerror;
|
|
gchar *debug;
|
|
|
|
gst_message_parse_error (message, &gerror, &debug);
|
|
GST_WARNING ("%p: got error %s (%s)", media, gerror->message, debug);
|
|
g_error_free (gerror);
|
|
g_free (debug);
|
|
|
|
gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR);
|
|
break;
|
|
}
|
|
case GST_MESSAGE_WARNING:
|
|
{
|
|
GError *gerror;
|
|
gchar *debug;
|
|
|
|
gst_message_parse_warning (message, &gerror, &debug);
|
|
GST_WARNING ("%p: got warning %s (%s)", media, gerror->message, debug);
|
|
g_error_free (gerror);
|
|
g_free (debug);
|
|
break;
|
|
}
|
|
case GST_MESSAGE_ELEMENT:
|
|
{
|
|
const GstStructure *s;
|
|
|
|
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");
|
|
collect_media_stats (media);
|
|
|
|
if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING)
|
|
gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
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->status == GST_RTSP_MEDIA_STATUS_PREPARING)
|
|
gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED);
|
|
}
|
|
break;
|
|
case GST_MESSAGE_EOS:
|
|
GST_INFO ("%p: got EOS", media);
|
|
|
|
if (priv->status == GST_RTSP_MEDIA_STATUS_UNPREPARING) {
|
|
GST_DEBUG ("shutting down after EOS");
|
|
finish_unprepare (media);
|
|
}
|
|
break;
|
|
default:
|
|
GST_INFO ("%p: got message type %d (%s)", media, type,
|
|
gst_message_type_get_name (type));
|
|
break;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
bus_message (GstBus * bus, GstMessage * message, GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaPrivate *priv = media->priv;
|
|
GstRTSPMediaClass *klass;
|
|
gboolean ret;
|
|
|
|
klass = GST_RTSP_MEDIA_GET_CLASS (media);
|
|
|
|
g_rec_mutex_lock (&priv->state_lock);
|
|
if (klass->handle_message)
|
|
ret = klass->handle_message (media, message);
|
|
else
|
|
ret = FALSE;
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
watch_destroyed (GstRTSPMedia * media)
|
|
{
|
|
GST_DEBUG_OBJECT (media, "source destroyed");
|
|
g_object_unref (media);
|
|
}
|
|
|
|
static GstElement *
|
|
find_payload_element (GstElement * payloader)
|
|
{
|
|
GstElement *pay = NULL;
|
|
|
|
if (GST_IS_BIN (payloader)) {
|
|
GstIterator *iter;
|
|
GValue item = { 0 };
|
|
|
|
iter = gst_bin_iterate_recurse (GST_BIN (payloader));
|
|
while (gst_iterator_next (iter, &item) == GST_ITERATOR_OK) {
|
|
GstElement *element = (GstElement *) g_value_get_object (&item);
|
|
GstElementClass *eclass = GST_ELEMENT_GET_CLASS (element);
|
|
const gchar *klass;
|
|
|
|
klass =
|
|
gst_element_class_get_metadata (eclass, GST_ELEMENT_METADATA_KLASS);
|
|
if (klass == NULL)
|
|
continue;
|
|
|
|
if (strstr (klass, "Payloader") && strstr (klass, "RTP")) {
|
|
pay = gst_object_ref (element);
|
|
g_value_unset (&item);
|
|
break;
|
|
}
|
|
g_value_unset (&item);
|
|
}
|
|
gst_iterator_free (iter);
|
|
} else {
|
|
pay = g_object_ref (payloader);
|
|
}
|
|
|
|
return pay;
|
|
}
|
|
|
|
/* called from streaming threads */
|
|
static void
|
|
pad_added_cb (GstElement * element, GstPad * pad, GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaPrivate *priv = media->priv;
|
|
GstRTSPStream *stream;
|
|
GstElement *pay;
|
|
|
|
/* find the real payload element */
|
|
pay = find_payload_element (element);
|
|
stream = gst_rtsp_media_create_stream (media, pay, pad);
|
|
gst_object_unref (pay);
|
|
|
|
GST_INFO ("pad added %s:%s, stream %p", GST_DEBUG_PAD_NAME (pad), stream);
|
|
|
|
g_rec_mutex_lock (&priv->state_lock);
|
|
if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARING)
|
|
goto not_preparing;
|
|
|
|
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),
|
|
priv->rtpbin, GST_STATE_PAUSED)) {
|
|
GST_WARNING ("failed to join bin element");
|
|
}
|
|
|
|
priv->adding = FALSE;
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
|
|
return;
|
|
|
|
/* ERRORS */
|
|
not_preparing:
|
|
{
|
|
gst_rtsp_media_remove_stream (media, stream);
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
GST_INFO ("ignore pad because we are not preparing");
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void
|
|
pad_removed_cb (GstElement * element, GstPad * pad, GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaPrivate *priv = media->priv;
|
|
GstRTSPStream *stream;
|
|
|
|
stream = g_object_get_data (G_OBJECT (pad), "gst-rtsp-dynpad-stream");
|
|
if (stream == NULL)
|
|
return;
|
|
|
|
GST_INFO ("pad removed %s:%s, stream %p", GST_DEBUG_PAD_NAME (pad), stream);
|
|
|
|
g_rec_mutex_lock (&priv->state_lock);
|
|
gst_rtsp_stream_leave_bin (stream, GST_BIN (priv->pipeline), priv->rtpbin);
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
|
|
gst_rtsp_media_remove_stream (media, stream);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
typedef struct _DynPaySignalHandlers DynPaySignalHandlers;
|
|
|
|
struct _DynPaySignalHandlers
|
|
{
|
|
gulong pad_added_handler;
|
|
gulong pad_removed_handler;
|
|
gulong no_more_pads_handler;
|
|
};
|
|
|
|
static gboolean
|
|
start_preroll (GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaPrivate *priv = media->priv;
|
|
GstStateChangeReturn ret;
|
|
|
|
GST_INFO ("setting pipeline to PAUSED for media %p", media);
|
|
/* first go to PAUSED */
|
|
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->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;
|
|
break;
|
|
case GST_STATE_CHANGE_FAILURE:
|
|
goto state_failed;
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
state_failed:
|
|
{
|
|
GST_WARNING ("failed to preroll pipeline");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
wait_preroll (GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaStatus status;
|
|
|
|
GST_DEBUG ("wait to preroll pipeline");
|
|
|
|
/* wait until pipeline is prerolled */
|
|
status = gst_rtsp_media_get_status (media);
|
|
if (status == GST_RTSP_MEDIA_STATUS_ERROR)
|
|
goto preroll_failed;
|
|
|
|
return TRUE;
|
|
|
|
preroll_failed:
|
|
{
|
|
GST_WARNING ("failed to preroll pipeline");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static GstElement *
|
|
request_aux_sender (GstElement * rtpbin, guint sessid, GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaPrivate *priv = media->priv;
|
|
GstRTSPStream *stream = NULL;
|
|
guint i;
|
|
|
|
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;
|
|
}
|
|
g_mutex_unlock (&priv->lock);
|
|
|
|
return gst_rtsp_stream_request_aux_sender (stream, sessid);
|
|
}
|
|
|
|
static gboolean
|
|
start_prepare (GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaPrivate *priv = media->priv;
|
|
guint i;
|
|
GList *walk;
|
|
|
|
/* link streams we already have, other streams might appear when we have
|
|
* dynamic elements */
|
|
for (i = 0; i < priv->streams->len; i++) {
|
|
GstRTSPStream *stream;
|
|
|
|
stream = g_ptr_array_index (priv->streams, i);
|
|
|
|
if (priv->rtx_time > 0) {
|
|
/* enable retransmission by setting rtprtxsend as the "aux" element of rtpbin */
|
|
g_signal_connect (priv->rtpbin, "request-aux-sender",
|
|
(GCallback) request_aux_sender, 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);
|
|
|
|
for (walk = priv->dynamic; walk; walk = g_list_next (walk)) {
|
|
GstElement *elem = walk->data;
|
|
DynPaySignalHandlers *handlers = g_slice_new (DynPaySignalHandlers);
|
|
|
|
GST_INFO ("adding callbacks for dynamic element %p", elem);
|
|
|
|
handlers->pad_added_handler = g_signal_connect (elem, "pad-added",
|
|
(GCallback) pad_added_cb, media);
|
|
handlers->pad_removed_handler = g_signal_connect (elem, "pad-removed",
|
|
(GCallback) pad_removed_cb, media);
|
|
handlers->no_more_pads_handler = g_signal_connect (elem, "no-more-pads",
|
|
(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))
|
|
goto preroll_failed;
|
|
|
|
return FALSE;
|
|
|
|
join_bin_failed:
|
|
{
|
|
GST_WARNING ("failed to join bin element");
|
|
gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR);
|
|
return FALSE;
|
|
}
|
|
preroll_failed:
|
|
{
|
|
GST_WARNING ("failed to preroll pipeline");
|
|
gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
default_prepare (GstRTSPMedia * media, GstRTSPThread * thread)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
GstRTSPMediaClass *klass;
|
|
GstBus *bus;
|
|
GMainContext *context;
|
|
GSource *source;
|
|
|
|
priv = media->priv;
|
|
|
|
klass = GST_RTSP_MEDIA_GET_CLASS (media);
|
|
|
|
if (!klass->create_rtpbin)
|
|
goto no_create_rtpbin;
|
|
|
|
priv->rtpbin = klass->create_rtpbin (media);
|
|
if (priv->rtpbin != NULL) {
|
|
gboolean success = TRUE;
|
|
|
|
g_object_set (priv->rtpbin, "latency", priv->latency, NULL);
|
|
|
|
if (klass->setup_rtpbin)
|
|
success = klass->setup_rtpbin (media, priv->rtpbin);
|
|
|
|
if (success == FALSE) {
|
|
gst_object_unref (priv->rtpbin);
|
|
priv->rtpbin = NULL;
|
|
}
|
|
}
|
|
if (priv->rtpbin == NULL)
|
|
goto no_rtpbin;
|
|
|
|
priv->thread = thread;
|
|
context = (thread != NULL) ? (thread->context) : NULL;
|
|
|
|
bus = gst_pipeline_get_bus (GST_PIPELINE_CAST (priv->pipeline));
|
|
|
|
/* add the pipeline bus to our custom mainloop */
|
|
priv->source = gst_bus_create_watch (bus);
|
|
gst_object_unref (bus);
|
|
|
|
g_source_set_callback (priv->source, (GSourceFunc) bus_message,
|
|
g_object_ref (media), (GDestroyNotify) watch_destroyed);
|
|
|
|
priv->id = g_source_attach (priv->source, context);
|
|
|
|
/* add stuff to the bin */
|
|
gst_bin_add (GST_BIN (priv->pipeline), priv->rtpbin);
|
|
|
|
/* do remainder in context */
|
|
source = g_idle_source_new ();
|
|
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);
|
|
|
|
return TRUE;
|
|
|
|
/* ERRORS */
|
|
no_create_rtpbin:
|
|
{
|
|
GST_ERROR ("no create_rtpbin function");
|
|
g_critical ("no create_rtpbin vmethod function set");
|
|
return FALSE;
|
|
}
|
|
no_rtpbin:
|
|
{
|
|
GST_WARNING ("no rtpbin element");
|
|
g_warning ("failed to create element 'rtpbin', check your installation");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_prepare:
|
|
* @media: a #GstRTSPMedia
|
|
* @thread: (transfer full) (allow-none): a #GstRTSPThread to run the
|
|
* bus handler or %NULL
|
|
*
|
|
* Prepare @media for streaming. This function will create the objects
|
|
* to manage the streaming. A pipeline must have been set on @media with
|
|
* gst_rtsp_media_take_pipeline().
|
|
*
|
|
* It will preroll the pipeline and collect vital information about the streams
|
|
* such as the duration.
|
|
*
|
|
* Returns: %TRUE on success.
|
|
*/
|
|
gboolean
|
|
gst_rtsp_media_prepare (GstRTSPMedia * media, GstRTSPThread * thread)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
GstRTSPMediaClass *klass;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
|
|
|
|
priv = media->priv;
|
|
|
|
g_rec_mutex_lock (&priv->state_lock);
|
|
priv->prepare_count++;
|
|
|
|
if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARED ||
|
|
priv->status == GST_RTSP_MEDIA_STATUS_SUSPENDED)
|
|
goto was_prepared;
|
|
|
|
if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING)
|
|
goto is_preparing;
|
|
|
|
if (priv->status != GST_RTSP_MEDIA_STATUS_UNPREPARED)
|
|
goto not_unprepared;
|
|
|
|
if (!priv->reusable && priv->reused)
|
|
goto is_reused;
|
|
|
|
GST_INFO ("preparing media %p", media);
|
|
|
|
/* reset some variables */
|
|
priv->is_live = FALSE;
|
|
priv->seekable = FALSE;
|
|
priv->buffering = FALSE;
|
|
|
|
/* we're preparing now */
|
|
gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING);
|
|
|
|
klass = GST_RTSP_MEDIA_GET_CLASS (media);
|
|
if (klass->prepare) {
|
|
if (!klass->prepare (media, thread))
|
|
goto prepare_failed;
|
|
}
|
|
|
|
wait_status:
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
|
|
/* now wait for all pads to be prerolled, FIXME, we should somehow be
|
|
* able to do this async so that we don't block the server thread. */
|
|
if (!wait_preroll (media))
|
|
goto preroll_failed;
|
|
|
|
g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_PREPARED], 0, NULL);
|
|
|
|
GST_INFO ("object %p is prerolled", media);
|
|
|
|
return TRUE;
|
|
|
|
/* OK */
|
|
is_preparing:
|
|
{
|
|
/* we are not going to use the giving thread, so stop it. */
|
|
if (thread)
|
|
gst_rtsp_thread_stop (thread);
|
|
goto wait_status;
|
|
}
|
|
was_prepared:
|
|
{
|
|
GST_LOG ("media %p was prepared", media);
|
|
/* we are not going to use the giving thread, so stop it. */
|
|
if (thread)
|
|
gst_rtsp_thread_stop (thread);
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
return TRUE;
|
|
}
|
|
/* ERRORS */
|
|
not_unprepared:
|
|
{
|
|
/* we are not going to use the giving thread, so stop it. */
|
|
if (thread)
|
|
gst_rtsp_thread_stop (thread);
|
|
GST_WARNING ("media %p was not unprepared", media);
|
|
priv->prepare_count--;
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
return FALSE;
|
|
}
|
|
is_reused:
|
|
{
|
|
/* we are not going to use the giving thread, so stop it. */
|
|
if (thread)
|
|
gst_rtsp_thread_stop (thread);
|
|
priv->prepare_count--;
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
GST_WARNING ("can not reuse media %p", media);
|
|
return FALSE;
|
|
}
|
|
prepare_failed:
|
|
{
|
|
/* we are not going to use the giving thread, so stop it. */
|
|
if (thread)
|
|
gst_rtsp_thread_stop (thread);
|
|
priv->prepare_count--;
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
GST_ERROR ("failed to prepare media");
|
|
return FALSE;
|
|
}
|
|
preroll_failed:
|
|
{
|
|
GST_WARNING ("failed to preroll pipeline");
|
|
gst_rtsp_media_unprepare (media);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* must be called with state-lock */
|
|
static void
|
|
finish_unprepare (GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaPrivate *priv = media->priv;
|
|
gint i;
|
|
GList *walk;
|
|
|
|
GST_DEBUG ("shutting down");
|
|
|
|
/* release the lock on shutdown, otherwise pad_added_cb might try to
|
|
* acquire the lock and then we deadlock */
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
set_state (media, GST_STATE_NULL);
|
|
g_rec_mutex_lock (&priv->state_lock);
|
|
remove_fakesink (priv);
|
|
|
|
for (i = 0; i < priv->streams->len; i++) {
|
|
GstRTSPStream *stream;
|
|
|
|
GST_INFO ("Removing elements of stream %d from pipeline", i);
|
|
|
|
stream = g_ptr_array_index (priv->streams, i);
|
|
|
|
gst_rtsp_stream_leave_bin (stream, GST_BIN (priv->pipeline), priv->rtpbin);
|
|
}
|
|
|
|
/* remove the pad signal handlers */
|
|
for (walk = priv->dynamic; walk; walk = g_list_next (walk)) {
|
|
GstElement *elem = walk->data;
|
|
DynPaySignalHandlers *handlers;
|
|
|
|
handlers =
|
|
g_object_steal_data (G_OBJECT (elem), "gst-rtsp-dynpay-handlers");
|
|
g_assert (handlers != NULL);
|
|
|
|
g_signal_handler_disconnect (G_OBJECT (elem), handlers->pad_added_handler);
|
|
g_signal_handler_disconnect (G_OBJECT (elem),
|
|
handlers->pad_removed_handler);
|
|
g_signal_handler_disconnect (G_OBJECT (elem),
|
|
handlers->no_more_pads_handler);
|
|
|
|
g_slice_free (DynPaySignalHandlers, handlers);
|
|
}
|
|
|
|
gst_bin_remove (GST_BIN (priv->pipeline), priv->rtpbin);
|
|
priv->rtpbin = NULL;
|
|
|
|
if (priv->nettime)
|
|
gst_object_unref (priv->nettime);
|
|
priv->nettime = NULL;
|
|
|
|
priv->reused = TRUE;
|
|
gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_UNPREPARED);
|
|
|
|
/* when the media is not reusable, this will effectively unref the media and
|
|
* recreate it */
|
|
g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_UNPREPARED], 0, NULL);
|
|
|
|
/* the source has the last ref to the media */
|
|
if (priv->source) {
|
|
GST_DEBUG ("destroy source");
|
|
g_source_destroy (priv->source);
|
|
g_source_unref (priv->source);
|
|
}
|
|
if (priv->thread) {
|
|
GST_DEBUG ("stop thread");
|
|
gst_rtsp_thread_stop (priv->thread);
|
|
}
|
|
}
|
|
|
|
/* called with state-lock */
|
|
static gboolean
|
|
default_unprepare (GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaPrivate *priv = media->priv;
|
|
|
|
gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_UNPREPARING);
|
|
|
|
if (priv->eos_shutdown) {
|
|
GST_DEBUG ("sending EOS for shutdown");
|
|
/* ref so that we don't disappear */
|
|
gst_element_send_event (priv->pipeline, gst_event_new_eos ());
|
|
/* we need to go to playing again for the EOS to propagate, normally in this
|
|
* state, nothing is receiving data from us anymore so this is ok. */
|
|
set_state (media, GST_STATE_PLAYING);
|
|
} else {
|
|
finish_unprepare (media);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_unprepare:
|
|
* @media: a #GstRTSPMedia
|
|
*
|
|
* Unprepare @media. After this call, the media should be prepared again before
|
|
* it can be used again. If the media is set to be non-reusable, a new instance
|
|
* must be created.
|
|
*
|
|
* Returns: %TRUE on success.
|
|
*/
|
|
gboolean
|
|
gst_rtsp_media_unprepare (GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
gboolean success;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
|
|
|
|
priv = media->priv;
|
|
|
|
g_rec_mutex_lock (&priv->state_lock);
|
|
if (priv->status == GST_RTSP_MEDIA_STATUS_UNPREPARED)
|
|
goto was_unprepared;
|
|
|
|
priv->prepare_count--;
|
|
if (priv->prepare_count > 0)
|
|
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 (priv->status == GST_RTSP_MEDIA_STATUS_PREPARED) {
|
|
GstRTSPMediaClass *klass;
|
|
|
|
klass = GST_RTSP_MEDIA_GET_CLASS (media);
|
|
if (klass->unprepare)
|
|
success = klass->unprepare (media);
|
|
} else {
|
|
finish_unprepare (media);
|
|
}
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
|
|
return success;
|
|
|
|
was_unprepared:
|
|
{
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
GST_INFO ("media %p was already unprepared", media);
|
|
return TRUE;
|
|
}
|
|
is_busy:
|
|
{
|
|
GST_INFO ("media %p still prepared %d times", media, priv->prepare_count);
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
/* should be called with state-lock */
|
|
static GstClock *
|
|
get_clock_unlocked (GstRTSPMedia * media)
|
|
{
|
|
if (media->priv->status != GST_RTSP_MEDIA_STATUS_PREPARED) {
|
|
GST_DEBUG_OBJECT (media, "media was not prepared");
|
|
return NULL;
|
|
}
|
|
return gst_pipeline_get_clock (GST_PIPELINE_CAST (media->priv->pipeline));
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_get_clock:
|
|
* @media: a #GstRTSPMedia
|
|
*
|
|
* Get the clock that is used by the pipeline in @media.
|
|
*
|
|
* @media must be prepared before this method returns a valid clock object.
|
|
*
|
|
* Returns: (transfer full): the #GstClock used by @media. unref after usage.
|
|
*/
|
|
GstClock *
|
|
gst_rtsp_media_get_clock (GstRTSPMedia * media)
|
|
{
|
|
GstClock *clock;
|
|
GstRTSPMediaPrivate *priv;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
|
|
|
|
priv = media->priv;
|
|
|
|
g_rec_mutex_lock (&priv->state_lock);
|
|
clock = get_clock_unlocked (media);
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
|
|
return clock;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_get_base_time:
|
|
* @media: a #GstRTSPMedia
|
|
*
|
|
* Get the base_time that is used by the pipeline in @media.
|
|
*
|
|
* @media must be prepared before this method returns a valid base_time.
|
|
*
|
|
* Returns: the base_time used by @media.
|
|
*/
|
|
GstClockTime
|
|
gst_rtsp_media_get_base_time (GstRTSPMedia * media)
|
|
{
|
|
GstClockTime result;
|
|
GstRTSPMediaPrivate *priv;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), GST_CLOCK_TIME_NONE);
|
|
|
|
priv = media->priv;
|
|
|
|
g_rec_mutex_lock (&priv->state_lock);
|
|
if (media->priv->status != GST_RTSP_MEDIA_STATUS_PREPARED)
|
|
goto not_prepared;
|
|
|
|
result = gst_element_get_base_time (media->priv->pipeline);
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
|
|
return result;
|
|
|
|
/* ERRORS */
|
|
not_prepared:
|
|
{
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
GST_DEBUG_OBJECT (media, "media was not prepared");
|
|
return GST_CLOCK_TIME_NONE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_get_time_provider:
|
|
* @media: a #GstRTSPMedia
|
|
* @address: (allow-none): an address or %NULL
|
|
* @port: a port or 0
|
|
*
|
|
* Get the #GstNetTimeProvider for the clock used by @media. The time provider
|
|
* will listen on @address and @port for client time requests.
|
|
*
|
|
* Returns: (transfer full): the #GstNetTimeProvider of @media.
|
|
*/
|
|
GstNetTimeProvider *
|
|
gst_rtsp_media_get_time_provider (GstRTSPMedia * media, const gchar * address,
|
|
guint16 port)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
GstNetTimeProvider *provider = NULL;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
|
|
|
|
priv = media->priv;
|
|
|
|
g_rec_mutex_lock (&priv->state_lock);
|
|
if (priv->time_provider) {
|
|
if ((provider = priv->nettime) == NULL) {
|
|
GstClock *clock;
|
|
|
|
if (priv->time_provider && (clock = get_clock_unlocked (media))) {
|
|
provider = gst_net_time_provider_new (clock, address, port);
|
|
gst_object_unref (clock);
|
|
|
|
priv->nettime = provider;
|
|
}
|
|
}
|
|
}
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
|
|
if (provider)
|
|
gst_object_ref (provider);
|
|
|
|
return provider;
|
|
}
|
|
|
|
static gboolean
|
|
default_setup_sdp (GstRTSPMedia * media, GstSDPMessage * sdp, GstSDPInfo * info)
|
|
{
|
|
return gst_rtsp_sdp_from_media (sdp, info, media);
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_setup_sdp:
|
|
* @media: a #GstRTSPMedia
|
|
* @sdp: (transfer none): a #GstSDPMessage
|
|
* @info: (transfer none): a #GstSDPInfo
|
|
*
|
|
* Add @media specific info to @sdp. @info is used to configure the connection
|
|
* information in the SDP.
|
|
*
|
|
* Returns: TRUE on success.
|
|
*/
|
|
gboolean
|
|
gst_rtsp_media_setup_sdp (GstRTSPMedia * media, GstSDPMessage * sdp,
|
|
GstSDPInfo * info)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
GstRTSPMediaClass *klass;
|
|
gboolean res;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
|
|
g_return_val_if_fail (sdp != NULL, FALSE);
|
|
g_return_val_if_fail (info != NULL, FALSE);
|
|
|
|
priv = media->priv;
|
|
|
|
g_rec_mutex_lock (&priv->state_lock);
|
|
|
|
klass = GST_RTSP_MEDIA_GET_CLASS (media);
|
|
|
|
if (!klass->setup_sdp)
|
|
goto no_setup_sdp;
|
|
|
|
res = klass->setup_sdp (media, sdp, info);
|
|
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
|
|
return res;
|
|
|
|
/* ERRORS */
|
|
no_setup_sdp:
|
|
{
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
GST_ERROR ("no setup_sdp function");
|
|
g_critical ("no setup_sdp vmethod function set");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
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;
|
|
gsize size;
|
|
guchar *data;
|
|
GstMIKEYMessage *msg;
|
|
const GstMIKEYPayload *payload;
|
|
const gchar *srtp_cipher;
|
|
const gchar *srtp_auth;
|
|
|
|
{
|
|
gchar *orig_value;
|
|
gchar *p, *kmpid;
|
|
|
|
p = orig_value = g_strdup (keymgmt);
|
|
|
|
SKIP_SPACES (p);
|
|
if (*p == '\0') {
|
|
g_free (orig_value);
|
|
return FALSE;
|
|
}
|
|
|
|
PARSE_STRING (p, " ", kmpid);
|
|
if (kmpid == NULL || !g_str_equal (kmpid, "mikey")) {
|
|
g_free (orig_value);
|
|
return FALSE;
|
|
}
|
|
data = g_base64_decode (p, &size);
|
|
|
|
g_free (orig_value); /* Don't need this any more */
|
|
}
|
|
|
|
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)
|
|
{
|
|
GstRTSPMediaPrivate *priv = media->priv;
|
|
gint i, medias_len;
|
|
|
|
medias_len = gst_sdp_message_medias_len (sdp);
|
|
if (medias_len != priv->streams->len) {
|
|
GST_ERROR ("%p: Media has more or less streams than SDP (%d /= %d)", media,
|
|
priv->streams->len, medias_len);
|
|
return FALSE;
|
|
}
|
|
|
|
for (i = 0; i < medias_len; i++) {
|
|
const gchar *proto, *media_type;
|
|
const GstSDPMedia *sdp_media = gst_sdp_message_get_media (sdp, i);
|
|
GstRTSPStream *stream;
|
|
gint j, formats_len;
|
|
const gchar *control;
|
|
GstRTSPProfile profile, profiles;
|
|
|
|
stream = g_ptr_array_index (priv->streams, i);
|
|
|
|
/* TODO: Should we do something with the other SDP information? */
|
|
|
|
/* get proto */
|
|
proto = gst_sdp_media_get_proto (sdp_media);
|
|
if (proto == NULL) {
|
|
GST_ERROR ("%p: SDP media %d has no proto", media, i);
|
|
return FALSE;
|
|
}
|
|
|
|
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);
|
|
return FALSE;
|
|
}
|
|
|
|
profiles = gst_rtsp_stream_get_profiles (stream);
|
|
if ((profiles & profile) == 0) {
|
|
GST_ERROR ("%p: unsupported profile '%s' for stream %d", media, proto, i);
|
|
return FALSE;
|
|
}
|
|
|
|
formats_len = gst_sdp_media_formats_len (sdp_media);
|
|
for (j = 0; j < formats_len; j++) {
|
|
gint pt;
|
|
GstCaps *caps;
|
|
GstStructure *s;
|
|
|
|
pt = atoi (gst_sdp_media_get_format (sdp_media, j));
|
|
|
|
GST_DEBUG (" looking at %d pt: %d", j, pt);
|
|
|
|
/* convert caps */
|
|
caps = media_to_caps (pt, sdp_media);
|
|
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_DEBUG ("mapping sdp media level attributes to caps");
|
|
sdp_attributes_to_caps (sdp_media->attributes, caps);
|
|
|
|
s = gst_caps_get_structure (caps, 0);
|
|
gst_structure_set_name (s, media_type);
|
|
|
|
gst_rtsp_stream_set_pt_map (stream, pt, caps);
|
|
gst_caps_unref (caps);
|
|
}
|
|
|
|
control = gst_sdp_media_get_attribute_val (sdp_media, "control");
|
|
if (control)
|
|
gst_rtsp_stream_set_control (stream, control);
|
|
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_handle_sdp:
|
|
* @media: a #GstRTSPMedia
|
|
* @sdp: (transfer none): a #GstSDPMessage
|
|
*
|
|
* Configure an SDP on @media for receiving streams
|
|
*
|
|
* Returns: TRUE on success.
|
|
*/
|
|
gboolean
|
|
gst_rtsp_media_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
GstRTSPMediaClass *klass;
|
|
gboolean res;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
|
|
g_return_val_if_fail (sdp != NULL, FALSE);
|
|
|
|
priv = media->priv;
|
|
|
|
g_rec_mutex_lock (&priv->state_lock);
|
|
|
|
klass = GST_RTSP_MEDIA_GET_CLASS (media);
|
|
|
|
if (!klass->handle_sdp)
|
|
goto no_handle_sdp;
|
|
|
|
res = klass->handle_sdp (media, sdp);
|
|
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
|
|
return res;
|
|
|
|
/* ERRORS */
|
|
no_handle_sdp:
|
|
{
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
GST_ERROR ("no handle_sdp function");
|
|
g_critical ("no handle_sdp vmethod function set");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
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);
|
|
}
|
|
|
|
/* call with state_lock */
|
|
static gboolean
|
|
default_suspend (GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaPrivate *priv = media->priv;
|
|
GstStateChangeReturn ret;
|
|
gboolean unblock = FALSE;
|
|
|
|
switch (priv->suspend_mode) {
|
|
case GST_RTSP_SUSPEND_MODE_NONE:
|
|
GST_DEBUG ("media %p no suspend", media);
|
|
break;
|
|
case GST_RTSP_SUSPEND_MODE_PAUSE:
|
|
GST_DEBUG ("media %p suspend to PAUSED", media);
|
|
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);
|
|
ret = set_target_state (media, GST_STATE_NULL, TRUE);
|
|
if (ret == GST_STATE_CHANGE_FAILURE)
|
|
goto state_failed;
|
|
/* Because payloader needs to set the sequence number as
|
|
* monotonic, we need to preserve the sequence number
|
|
* after pause. (otherwise going from pause to play, which
|
|
* 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 */
|
|
state_failed:
|
|
{
|
|
GST_WARNING ("failed changing pipeline's state for media %p", media);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_suspend:
|
|
* @media: a #GstRTSPMedia
|
|
*
|
|
* Suspend @media. The state of the pipeline managed by @media is set to
|
|
* GST_STATE_NULL but all streams are kept. @media can be prepared again
|
|
* with gst_rtsp_media_unsuspend()
|
|
*
|
|
* @media must be prepared with gst_rtsp_media_prepare();
|
|
*
|
|
* Returns: %TRUE on success.
|
|
*/
|
|
gboolean
|
|
gst_rtsp_media_suspend (GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaPrivate *priv = media->priv;
|
|
GstRTSPMediaClass *klass;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
|
|
|
|
GST_FIXME ("suspend for dynamic pipelines needs fixing");
|
|
|
|
g_rec_mutex_lock (&priv->state_lock);
|
|
if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED)
|
|
goto not_prepared;
|
|
|
|
/* don't attempt to suspend when something is busy */
|
|
if (priv->n_active > 0)
|
|
goto done;
|
|
|
|
klass = GST_RTSP_MEDIA_GET_CLASS (media);
|
|
if (klass->suspend) {
|
|
if (!klass->suspend (media))
|
|
goto suspend_failed;
|
|
}
|
|
|
|
gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_SUSPENDED);
|
|
done:
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
|
|
return TRUE;
|
|
|
|
/* ERRORS */
|
|
not_prepared:
|
|
{
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
GST_WARNING ("media %p was not prepared", media);
|
|
return FALSE;
|
|
}
|
|
suspend_failed:
|
|
{
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR);
|
|
GST_WARNING ("failed to suspend media %p", media);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* call with state_lock */
|
|
static gboolean
|
|
default_unsuspend (GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaPrivate *priv = media->priv;
|
|
|
|
switch (priv->suspend_mode) {
|
|
case GST_RTSP_SUSPEND_MODE_NONE:
|
|
gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED);
|
|
break;
|
|
case GST_RTSP_SUSPEND_MODE_PAUSE:
|
|
gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED);
|
|
break;
|
|
case GST_RTSP_SUSPEND_MODE_RESET:
|
|
{
|
|
gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING);
|
|
if (!start_preroll (media))
|
|
goto start_failed;
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
|
|
if (!wait_preroll (media))
|
|
goto preroll_failed;
|
|
|
|
g_rec_mutex_lock (&priv->state_lock);
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
/* ERRORS */
|
|
start_failed:
|
|
{
|
|
GST_WARNING ("failed to preroll pipeline");
|
|
return FALSE;
|
|
}
|
|
preroll_failed:
|
|
{
|
|
GST_WARNING ("failed to preroll pipeline");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_unsuspend:
|
|
* @media: a #GstRTSPMedia
|
|
*
|
|
* Unsuspend @media if it was in a suspended state. This method does nothing
|
|
* when the media was not in the suspended state.
|
|
*
|
|
* Returns: %TRUE on success.
|
|
*/
|
|
gboolean
|
|
gst_rtsp_media_unsuspend (GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaPrivate *priv = media->priv;
|
|
GstRTSPMediaClass *klass;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
|
|
|
|
g_rec_mutex_lock (&priv->state_lock);
|
|
if (priv->status != GST_RTSP_MEDIA_STATUS_SUSPENDED)
|
|
goto done;
|
|
|
|
klass = GST_RTSP_MEDIA_GET_CLASS (media);
|
|
if (klass->unsuspend) {
|
|
if (!klass->unsuspend (media))
|
|
goto unsuspend_failed;
|
|
}
|
|
|
|
done:
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
|
|
return TRUE;
|
|
|
|
/* ERRORS */
|
|
unsuspend_failed:
|
|
{
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
GST_WARNING ("failed to unsuspend media %p", media);
|
|
gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* must be called with state-lock */
|
|
static void
|
|
media_set_pipeline_state_locked (GstRTSPMedia * media, GstState state)
|
|
{
|
|
GstRTSPMediaPrivate *priv = media->priv;
|
|
|
|
if (state == GST_STATE_NULL) {
|
|
gst_rtsp_media_unprepare (media);
|
|
} else {
|
|
GST_INFO ("state %s media %p", gst_element_state_get_name (state), media);
|
|
set_target_state (media, state, FALSE);
|
|
/* when we are buffering, don't update the state yet, this will be done
|
|
* when buffering finishes */
|
|
if (priv->buffering) {
|
|
GST_INFO ("Buffering busy, delay state change");
|
|
} else {
|
|
if (state == GST_STATE_PLAYING)
|
|
/* make sure pads are not blocking anymore when going to PLAYING */
|
|
media_streams_set_blocked (media, FALSE);
|
|
|
|
set_state (media, state);
|
|
|
|
/* and suspend after pause */
|
|
if (state == GST_STATE_PAUSED)
|
|
gst_rtsp_media_suspend (media);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_set_pipeline_state:
|
|
* @media: a #GstRTSPMedia
|
|
* @state: the target state of the pipeline
|
|
*
|
|
* Set the state of the pipeline managed by @media to @state
|
|
*/
|
|
void
|
|
gst_rtsp_media_set_pipeline_state (GstRTSPMedia * media, GstState state)
|
|
{
|
|
g_return_if_fail (GST_IS_RTSP_MEDIA (media));
|
|
|
|
g_rec_mutex_lock (&media->priv->state_lock);
|
|
media_set_pipeline_state_locked (media, state);
|
|
g_rec_mutex_unlock (&media->priv->state_lock);
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_set_state:
|
|
* @media: a #GstRTSPMedia
|
|
* @state: the target state of the media
|
|
* @transports: (transfer none) (element-type GstRtspServer.RTSPStreamTransport):
|
|
* a #GPtrArray of #GstRTSPStreamTransport pointers
|
|
*
|
|
* Set the state of @media to @state and for the transports in @transports.
|
|
*
|
|
* @media must be prepared with gst_rtsp_media_prepare();
|
|
*
|
|
* Returns: %TRUE on success.
|
|
*/
|
|
gboolean
|
|
gst_rtsp_media_set_state (GstRTSPMedia * media, GstState state,
|
|
GPtrArray * transports)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
gint i;
|
|
gboolean activate, deactivate, do_state;
|
|
gint old_active;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
|
|
g_return_val_if_fail (transports != NULL, FALSE);
|
|
|
|
priv = media->priv;
|
|
|
|
g_rec_mutex_lock (&priv->state_lock);
|
|
if (priv->status == GST_RTSP_MEDIA_STATUS_ERROR)
|
|
goto error_status;
|
|
if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED &&
|
|
priv->status != GST_RTSP_MEDIA_STATUS_SUSPENDED)
|
|
goto not_prepared;
|
|
|
|
/* NULL and READY are the same */
|
|
if (state == GST_STATE_READY)
|
|
state = GST_STATE_NULL;
|
|
|
|
activate = deactivate = FALSE;
|
|
|
|
GST_INFO ("going to state %s media %p, target state %s",
|
|
gst_element_state_get_name (state), media,
|
|
gst_element_state_get_name (priv->target_state));
|
|
|
|
switch (state) {
|
|
case GST_STATE_NULL:
|
|
/* we're going from PLAYING or PAUSED to READY or NULL, deactivate */
|
|
if (priv->target_state >= GST_STATE_PAUSED)
|
|
deactivate = TRUE;
|
|
break;
|
|
case GST_STATE_PAUSED:
|
|
/* we're going from PLAYING to PAUSED, READY or NULL, deactivate */
|
|
if (priv->target_state == GST_STATE_PLAYING)
|
|
deactivate = TRUE;
|
|
break;
|
|
case GST_STATE_PLAYING:
|
|
/* we're going to PLAYING, activate */
|
|
activate = TRUE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
old_active = priv->n_active;
|
|
|
|
GST_DEBUG ("%d transports, activate %d, deactivate %d", transports->len,
|
|
activate, deactivate);
|
|
for (i = 0; i < transports->len; i++) {
|
|
GstRTSPStreamTransport *trans;
|
|
|
|
/* we need a non-NULL entry in the array */
|
|
trans = g_ptr_array_index (transports, i);
|
|
if (trans == NULL)
|
|
continue;
|
|
|
|
if (activate) {
|
|
if (gst_rtsp_stream_transport_set_active (trans, TRUE))
|
|
priv->n_active++;
|
|
} else if (deactivate) {
|
|
if (gst_rtsp_stream_transport_set_active (trans, FALSE))
|
|
priv->n_active--;
|
|
}
|
|
}
|
|
|
|
/* we just activated the first media, do the playing state change */
|
|
if (old_active == 0 && activate)
|
|
do_state = TRUE;
|
|
/* if we have no more active media, do the downward state changes */
|
|
else if (priv->n_active == 0)
|
|
do_state = TRUE;
|
|
else
|
|
do_state = FALSE;
|
|
|
|
GST_INFO ("state %d active %d media %p do_state %d", state, priv->n_active,
|
|
media, do_state);
|
|
|
|
if (priv->target_state != 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);
|
|
}
|
|
|
|
/* remember where we are */
|
|
if (state != GST_STATE_NULL && (state == GST_STATE_PAUSED ||
|
|
old_active != priv->n_active))
|
|
collect_media_stats (media);
|
|
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
|
|
return TRUE;
|
|
|
|
/* ERRORS */
|
|
not_prepared:
|
|
{
|
|
GST_WARNING ("media %p was not prepared", media);
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
return FALSE;
|
|
}
|
|
error_status:
|
|
{
|
|
GST_WARNING ("media %p in error status while changing to state %d",
|
|
media, state);
|
|
if (state == GST_STATE_NULL) {
|
|
for (i = 0; i < transports->len; i++) {
|
|
GstRTSPStreamTransport *trans;
|
|
|
|
/* we need a non-NULL entry in the array */
|
|
trans = g_ptr_array_index (transports, i);
|
|
if (trans == NULL)
|
|
continue;
|
|
|
|
gst_rtsp_stream_transport_set_active (trans, FALSE);
|
|
}
|
|
priv->n_active = 0;
|
|
}
|
|
g_rec_mutex_unlock (&priv->state_lock);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_set_transport_mode:
|
|
* @media: a #GstRTSPMedia
|
|
* @mode: the new value
|
|
*
|
|
* Sets if the media pipeline can work in PLAY or RECORD mode
|
|
*/
|
|
void
|
|
gst_rtsp_media_set_transport_mode (GstRTSPMedia * media,
|
|
GstRTSPTransportMode mode)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
|
|
g_return_if_fail (GST_IS_RTSP_MEDIA (media));
|
|
|
|
priv = media->priv;
|
|
|
|
g_mutex_lock (&priv->lock);
|
|
priv->transport_mode = mode;
|
|
g_mutex_unlock (&priv->lock);
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_get_transport_mode:
|
|
* @media: a #GstRTSPMedia
|
|
*
|
|
* Check if the pipeline for @media can be used for PLAY or RECORD methods.
|
|
*
|
|
* Returns: The transport mode.
|
|
*/
|
|
GstRTSPTransportMode
|
|
gst_rtsp_media_get_transport_mode (GstRTSPMedia * media)
|
|
{
|
|
GstRTSPMediaPrivate *priv;
|
|
GstRTSPTransportMode res;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
|
|
|
|
priv = media->priv;
|
|
|
|
g_mutex_lock (&priv->lock);
|
|
res = priv->transport_mode;
|
|
g_mutex_unlock (&priv->lock);
|
|
|
|
return res;
|
|
}
|