mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-22 00:06:36 +00:00
de7c72dec2
Make GObjects from the remaining simple structures. Remove GstRTSPSessionStream, it's not needed. Rename GstRTSPMediaStream -> GstRTSPStream: It is shorter Rename GstRTSPMediaTrans -> GstRTSPStreamTransport: It describes how a GstRTSPStream should be transported to a client. Rename GstRTSPMediaFactory::get_element -> create_element because that more accurately describes what it does. Make nice methods instead of poking in the structures. Move some methods inside the relevant object source code. Use GPtrArray to store objects instead of plain arrays, it is more natural and allows us to more easily clean up. Move the allocation of udp ports to the Stream object. The Stream object contains the elements needed to stream the media to a client. Improve the prepare and unprepare methods. Unprepare should now undo everything prepare did. Improve also async unprepare when doing EOS on shutdown. Make sure we always unprepare correctly.
581 lines
16 KiB
C
581 lines
16 KiB
C
/* GStreamer
|
|
* Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.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., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#include <string.h>
|
|
|
|
#include "rtsp-media-factory-uri.h"
|
|
|
|
#define DEFAULT_URI NULL
|
|
#define DEFAULT_USE_GSTPAY FALSE
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_URI,
|
|
PROP_USE_GSTPAY,
|
|
PROP_LAST
|
|
};
|
|
|
|
|
|
#define RAW_VIDEO_CAPS \
|
|
"video/x-raw"
|
|
|
|
#define RAW_AUDIO_CAPS \
|
|
"audio/x-raw"
|
|
|
|
static GstStaticCaps raw_video_caps = GST_STATIC_CAPS (RAW_VIDEO_CAPS);
|
|
static GstStaticCaps raw_audio_caps = GST_STATIC_CAPS (RAW_AUDIO_CAPS);
|
|
|
|
typedef struct
|
|
{
|
|
GstRTSPMediaFactoryURI *factory;
|
|
guint pt;
|
|
} FactoryData;
|
|
|
|
static void
|
|
free_data (FactoryData * data)
|
|
{
|
|
g_object_unref (data->factory);
|
|
g_free (data);
|
|
}
|
|
|
|
static const gchar *factory_key = "GstRTSPMediaFactoryURI";
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (rtsp_media_factory_uri_debug);
|
|
#define GST_CAT_DEFAULT rtsp_media_factory_uri_debug
|
|
|
|
static void gst_rtsp_media_factory_uri_get_property (GObject * object,
|
|
guint propid, GValue * value, GParamSpec * pspec);
|
|
static void gst_rtsp_media_factory_uri_set_property (GObject * object,
|
|
guint propid, const GValue * value, GParamSpec * pspec);
|
|
static void gst_rtsp_media_factory_uri_finalize (GObject * obj);
|
|
|
|
static GstElement *rtsp_media_factory_uri_create_element (GstRTSPMediaFactory *
|
|
factory, const GstRTSPUrl * url);
|
|
|
|
G_DEFINE_TYPE (GstRTSPMediaFactoryURI, gst_rtsp_media_factory_uri,
|
|
GST_TYPE_RTSP_MEDIA_FACTORY);
|
|
|
|
static void
|
|
gst_rtsp_media_factory_uri_class_init (GstRTSPMediaFactoryURIClass * klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
GstRTSPMediaFactoryClass *mediafactory_class;
|
|
|
|
gobject_class = G_OBJECT_CLASS (klass);
|
|
mediafactory_class = GST_RTSP_MEDIA_FACTORY_CLASS (klass);
|
|
|
|
gobject_class->get_property = gst_rtsp_media_factory_uri_get_property;
|
|
gobject_class->set_property = gst_rtsp_media_factory_uri_set_property;
|
|
gobject_class->finalize = gst_rtsp_media_factory_uri_finalize;
|
|
|
|
/**
|
|
* GstRTSPMediaFactoryURI::uri:
|
|
*
|
|
* The uri of the resource that will be served by this factory.
|
|
*/
|
|
g_object_class_install_property (gobject_class, PROP_URI,
|
|
g_param_spec_string ("uri", "URI",
|
|
"The URI of the resource to stream", DEFAULT_URI,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
/**
|
|
* GstRTSPMediaFactoryURI::use-gstpay:
|
|
*
|
|
* Allow the usage of gstpay in order to avoid decoding of compressed formats
|
|
* without a payloader.
|
|
*/
|
|
g_object_class_install_property (gobject_class, PROP_USE_GSTPAY,
|
|
g_param_spec_boolean ("use-gstpay", "Use gstpay",
|
|
"Use the gstpay payloader to avoid decoding", DEFAULT_USE_GSTPAY,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
mediafactory_class->create_element = rtsp_media_factory_uri_create_element;
|
|
|
|
GST_DEBUG_CATEGORY_INIT (rtsp_media_factory_uri_debug, "rtspmediafactoryuri",
|
|
0, "GstRTSPMediaFactoryUri");
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
GList *demux;
|
|
GList *payload;
|
|
GList *decode;
|
|
} FilterData;
|
|
|
|
static gboolean
|
|
payloader_filter (GstPluginFeature * feature, FilterData * data)
|
|
{
|
|
const gchar *klass;
|
|
GstElementFactory *fact;
|
|
GList **list = NULL;
|
|
|
|
/* we only care about element factories */
|
|
if (G_UNLIKELY (!GST_IS_ELEMENT_FACTORY (feature)))
|
|
return FALSE;
|
|
|
|
if (gst_plugin_feature_get_rank (feature) < GST_RANK_MARGINAL)
|
|
return FALSE;
|
|
|
|
fact = GST_ELEMENT_FACTORY_CAST (feature);
|
|
|
|
klass = gst_element_factory_get_klass (fact);
|
|
|
|
if (strstr (klass, "Decoder"))
|
|
list = &data->decode;
|
|
else if (strstr (klass, "Demux"))
|
|
list = &data->demux;
|
|
else if (strstr (klass, "Parser") && strstr (klass, "Codec"))
|
|
list = &data->demux;
|
|
else if (strstr (klass, "Payloader") && strstr (klass, "RTP"))
|
|
list = &data->payload;
|
|
|
|
if (list) {
|
|
GST_DEBUG ("adding %s", GST_OBJECT_NAME (fact));
|
|
*list = g_list_prepend (*list, fact);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
gst_rtsp_media_factory_uri_init (GstRTSPMediaFactoryURI * factory)
|
|
{
|
|
FilterData data = { NULL, NULL, NULL };
|
|
|
|
factory->uri = g_strdup (DEFAULT_URI);
|
|
factory->use_gstpay = DEFAULT_USE_GSTPAY;
|
|
|
|
/* get the feature list using the filter */
|
|
gst_registry_feature_filter (gst_registry_get (), (GstPluginFeatureFilter)
|
|
payloader_filter, FALSE, &data);
|
|
/* sort */
|
|
factory->demuxers =
|
|
g_list_sort (data.demux, gst_plugin_feature_rank_compare_func);
|
|
factory->payloaders =
|
|
g_list_sort (data.payload, gst_plugin_feature_rank_compare_func);
|
|
factory->decoders =
|
|
g_list_sort (data.decode, gst_plugin_feature_rank_compare_func);
|
|
|
|
factory->raw_vcaps = gst_static_caps_get (&raw_video_caps);
|
|
factory->raw_acaps = gst_static_caps_get (&raw_audio_caps);
|
|
}
|
|
|
|
static void
|
|
gst_rtsp_media_factory_uri_finalize (GObject * obj)
|
|
{
|
|
GstRTSPMediaFactoryURI *factory = GST_RTSP_MEDIA_FACTORY_URI (obj);
|
|
|
|
g_free (factory->uri);
|
|
gst_plugin_feature_list_free (factory->demuxers);
|
|
gst_plugin_feature_list_free (factory->payloaders);
|
|
gst_plugin_feature_list_free (factory->decoders);
|
|
gst_caps_unref (factory->raw_vcaps);
|
|
gst_caps_unref (factory->raw_acaps);
|
|
|
|
G_OBJECT_CLASS (gst_rtsp_media_factory_uri_parent_class)->finalize (obj);
|
|
}
|
|
|
|
static void
|
|
gst_rtsp_media_factory_uri_get_property (GObject * object, guint propid,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstRTSPMediaFactoryURI *factory = GST_RTSP_MEDIA_FACTORY_URI (object);
|
|
|
|
switch (propid) {
|
|
case PROP_URI:
|
|
g_value_take_string (value, gst_rtsp_media_factory_uri_get_uri (factory));
|
|
break;
|
|
case PROP_USE_GSTPAY:
|
|
g_value_set_boolean (value, factory->use_gstpay);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_rtsp_media_factory_uri_set_property (GObject * object, guint propid,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstRTSPMediaFactoryURI *factory = GST_RTSP_MEDIA_FACTORY_URI (object);
|
|
|
|
switch (propid) {
|
|
case PROP_URI:
|
|
gst_rtsp_media_factory_uri_set_uri (factory, g_value_get_string (value));
|
|
break;
|
|
case PROP_USE_GSTPAY:
|
|
factory->use_gstpay = g_value_get_boolean (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_factory_uri_new:
|
|
*
|
|
* Create a new #GstRTSPMediaFactoryURI instance.
|
|
*
|
|
* Returns: a new #GstRTSPMediaFactoryURI object.
|
|
*/
|
|
GstRTSPMediaFactoryURI *
|
|
gst_rtsp_media_factory_uri_new (void)
|
|
{
|
|
GstRTSPMediaFactoryURI *result;
|
|
|
|
result = g_object_new (GST_TYPE_RTSP_MEDIA_FACTORY_URI, NULL);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_factory_uri_set_uri:
|
|
* @factory: a #GstRTSPMediaFactory
|
|
* @uri: the uri the stream
|
|
*
|
|
* Set the URI of the resource that will be streamed by this factory.
|
|
*/
|
|
void
|
|
gst_rtsp_media_factory_uri_set_uri (GstRTSPMediaFactoryURI * factory,
|
|
const gchar * uri)
|
|
{
|
|
g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY_URI (factory));
|
|
g_return_if_fail (uri != NULL);
|
|
|
|
GST_RTSP_MEDIA_FACTORY_LOCK (factory);
|
|
g_free (factory->uri);
|
|
factory->uri = g_strdup (uri);
|
|
GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_media_factory_uri_get_uri:
|
|
* @factory: a #GstRTSPMediaFactory
|
|
*
|
|
* Get the URI that will provide media for this factory.
|
|
*
|
|
* Returns: the configured URI. g_free() after usage.
|
|
*/
|
|
gchar *
|
|
gst_rtsp_media_factory_uri_get_uri (GstRTSPMediaFactoryURI * factory)
|
|
{
|
|
gchar *result;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY_URI (factory), NULL);
|
|
|
|
GST_RTSP_MEDIA_FACTORY_LOCK (factory);
|
|
result = g_strdup (factory->uri);
|
|
GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GstElementFactory *
|
|
find_payloader (GstRTSPMediaFactoryURI * urifact, GstCaps * caps)
|
|
{
|
|
GList *list;
|
|
GstElementFactory *factory = NULL;
|
|
gboolean autoplug_more = FALSE;
|
|
|
|
/* first find a demuxer that can link */
|
|
list = gst_element_factory_list_filter (urifact->demuxers, caps,
|
|
GST_PAD_SINK, FALSE);
|
|
|
|
if (list) {
|
|
GstStructure *structure = gst_caps_get_structure (caps, 0);
|
|
gboolean parsed = FALSE;
|
|
|
|
gst_structure_get_boolean (structure, "parsed", &parsed);
|
|
|
|
/* Avoid plugging parsers in a loop. This is not 100% correct, as some
|
|
* parsers don't set parsed=true in caps. We should do something like
|
|
* decodebin does and track decode chains and elements plugged in those
|
|
* chains...
|
|
*/
|
|
if (parsed) {
|
|
GList *walk;
|
|
const gchar *klass;
|
|
|
|
for (walk = list; walk; walk = walk->next) {
|
|
factory = GST_ELEMENT_FACTORY (walk->data);
|
|
klass = gst_element_factory_get_klass (factory);
|
|
if (strstr (klass, "Parser"))
|
|
/* caps have parsed=true, so skip this parser to avoid loops */
|
|
continue;
|
|
|
|
autoplug_more = TRUE;
|
|
break;
|
|
}
|
|
} else {
|
|
/* caps don't have parsed=true set and we have a demuxer/parser */
|
|
autoplug_more = TRUE;
|
|
}
|
|
|
|
gst_plugin_feature_list_free (list);
|
|
}
|
|
|
|
if (autoplug_more)
|
|
/* we have a demuxer, try that one first */
|
|
return NULL;
|
|
|
|
/* no demuxer try a depayloader */
|
|
list = gst_element_factory_list_filter (urifact->payloaders, caps,
|
|
GST_PAD_SINK, FALSE);
|
|
|
|
if (list == NULL) {
|
|
if (urifact->use_gstpay) {
|
|
/* no depayloader or parser/demuxer, use gstpay when allowed */
|
|
factory = gst_element_factory_find ("rtpgstpay");
|
|
} else {
|
|
/* no depayloader, try a decoder, we'll get to a payloader for a decoded
|
|
* video or audio format, worst case. */
|
|
list = gst_element_factory_list_filter (urifact->decoders, caps,
|
|
GST_PAD_SINK, FALSE);
|
|
|
|
if (list != NULL) {
|
|
/* we have a decoder, try that one first */
|
|
gst_plugin_feature_list_free (list);
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (list != NULL) {
|
|
factory = GST_ELEMENT_FACTORY_CAST (list->data);
|
|
g_object_ref (factory);
|
|
gst_plugin_feature_list_free (list);
|
|
}
|
|
return factory;
|
|
}
|
|
|
|
static gboolean
|
|
autoplug_continue_cb (GstElement * uribin, GstPad * pad, GstCaps * caps,
|
|
GstElement * element)
|
|
{
|
|
FactoryData *data;
|
|
GstElementFactory *factory;
|
|
|
|
GST_DEBUG ("found pad %s:%s of caps %" GST_PTR_FORMAT,
|
|
GST_DEBUG_PAD_NAME (pad), caps);
|
|
|
|
data = g_object_get_data (G_OBJECT (element), factory_key);
|
|
|
|
if (!(factory = find_payloader (data->factory, caps)))
|
|
goto no_factory;
|
|
|
|
/* we found a payloader, stop autoplugging so we can plug the
|
|
* payloader. */
|
|
GST_DEBUG ("found factory %s",
|
|
gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)));
|
|
gst_object_unref (factory);
|
|
|
|
return FALSE;
|
|
|
|
/* ERRORS */
|
|
no_factory:
|
|
{
|
|
/* no payloader, continue autoplugging */
|
|
GST_DEBUG ("no payloader found");
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
pad_added_cb (GstElement * uribin, GstPad * pad, GstElement * element)
|
|
{
|
|
GstRTSPMediaFactoryURI *urifact;
|
|
FactoryData *data;
|
|
GstElementFactory *factory;
|
|
GstElement *payloader;
|
|
GstCaps *caps;
|
|
GstPad *sinkpad, *srcpad, *ghostpad;
|
|
GstElement *convert;
|
|
gchar *padname;
|
|
|
|
GST_DEBUG ("added pad %s:%s", GST_DEBUG_PAD_NAME (pad));
|
|
|
|
/* link the element now and expose the pad */
|
|
data = g_object_get_data (G_OBJECT (element), factory_key);
|
|
urifact = data->factory;
|
|
|
|
/* ref to make refcounting easier later */
|
|
gst_object_ref (pad);
|
|
padname = gst_pad_get_name (pad);
|
|
|
|
/* get pad caps first, then call get_caps, then fail */
|
|
if ((caps = gst_pad_get_current_caps (pad)) == NULL)
|
|
if ((caps = gst_pad_query_caps (pad, NULL)) == NULL)
|
|
goto no_caps;
|
|
|
|
/* check for raw caps */
|
|
if (gst_caps_can_intersect (caps, urifact->raw_vcaps)) {
|
|
/* we have raw video caps, insert converter */
|
|
convert = gst_element_factory_make ("videoconvert", NULL);
|
|
} else if (gst_caps_can_intersect (caps, urifact->raw_acaps)) {
|
|
/* we have raw audio caps, insert converter */
|
|
convert = gst_element_factory_make ("audioconvert", NULL);
|
|
} else {
|
|
convert = NULL;
|
|
}
|
|
|
|
if (convert) {
|
|
gst_bin_add (GST_BIN_CAST (element), convert);
|
|
gst_element_set_state (convert, GST_STATE_PLAYING);
|
|
|
|
sinkpad = gst_element_get_static_pad (convert, "sink");
|
|
gst_pad_link (pad, sinkpad);
|
|
gst_object_unref (sinkpad);
|
|
|
|
/* unref old pad, we reffed before */
|
|
gst_object_unref (pad);
|
|
|
|
/* continue with new pad and caps */
|
|
pad = gst_element_get_static_pad (convert, "src");
|
|
if ((caps = gst_pad_get_current_caps (pad)) == NULL)
|
|
if ((caps = gst_pad_query_caps (pad, NULL)) == NULL)
|
|
goto no_caps;
|
|
}
|
|
|
|
if (!(factory = find_payloader (urifact, caps)))
|
|
goto no_factory;
|
|
|
|
gst_caps_unref (caps);
|
|
|
|
/* we have a payloader now */
|
|
GST_DEBUG ("found payloader factory %s",
|
|
gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)));
|
|
|
|
payloader = gst_element_factory_create (factory, NULL);
|
|
if (payloader == NULL)
|
|
goto no_payloader;
|
|
|
|
g_object_set (payloader, "pt", data->pt, NULL);
|
|
data->pt++;
|
|
|
|
if (g_object_class_find_property (G_OBJECT_GET_CLASS (payloader),
|
|
"buffer-list"))
|
|
g_object_set (payloader, "buffer-list", TRUE, NULL);
|
|
|
|
/* add the payloader to the pipeline */
|
|
gst_bin_add (GST_BIN_CAST (element), payloader);
|
|
gst_element_set_state (payloader, GST_STATE_PLAYING);
|
|
|
|
/* link the pad to the sinkpad of the payloader */
|
|
sinkpad = gst_element_get_static_pad (payloader, "sink");
|
|
gst_pad_link (pad, sinkpad);
|
|
gst_object_unref (sinkpad);
|
|
gst_object_unref (pad);
|
|
|
|
/* now expose the srcpad of the payloader as a ghostpad with the same name
|
|
* as the uridecodebin pad name. */
|
|
srcpad = gst_element_get_static_pad (payloader, "src");
|
|
ghostpad = gst_ghost_pad_new (padname, srcpad);
|
|
gst_object_unref (srcpad);
|
|
g_free (padname);
|
|
|
|
gst_pad_set_active (ghostpad, TRUE);
|
|
gst_element_add_pad (element, ghostpad);
|
|
|
|
return;
|
|
|
|
/* ERRORS */
|
|
no_caps:
|
|
{
|
|
GST_WARNING ("could not get caps from pad");
|
|
g_free (padname);
|
|
gst_object_unref (pad);
|
|
return;
|
|
}
|
|
no_factory:
|
|
{
|
|
GST_DEBUG ("no payloader found");
|
|
g_free (padname);
|
|
gst_caps_unref (caps);
|
|
gst_object_unref (pad);
|
|
return;
|
|
}
|
|
no_payloader:
|
|
{
|
|
GST_ERROR ("could not create payloader from factory");
|
|
g_free (padname);
|
|
gst_caps_unref (caps);
|
|
gst_object_unref (pad);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void
|
|
no_more_pads_cb (GstElement * uribin, GstElement * element)
|
|
{
|
|
GST_DEBUG ("no-more-pads");
|
|
gst_element_no_more_pads (element);
|
|
}
|
|
|
|
static GstElement *
|
|
rtsp_media_factory_uri_create_element (GstRTSPMediaFactory * factory,
|
|
const GstRTSPUrl * url)
|
|
{
|
|
GstElement *topbin, *element, *uribin;
|
|
GstRTSPMediaFactoryURI *urifact;
|
|
FactoryData *data;
|
|
|
|
urifact = GST_RTSP_MEDIA_FACTORY_URI_CAST (factory);
|
|
|
|
GST_LOG ("creating element");
|
|
|
|
topbin = gst_bin_new ("GstRTSPMediaFactoryURI");
|
|
g_assert (topbin != NULL);
|
|
|
|
/* our bin will dynamically expose payloaded pads */
|
|
element = gst_bin_new ("dynpay0");
|
|
g_assert (element != NULL);
|
|
|
|
uribin = gst_element_factory_make ("uridecodebin", "uribin");
|
|
if (uribin == NULL)
|
|
goto no_uridecodebin;
|
|
|
|
g_object_set (uribin, "uri", urifact->uri, NULL);
|
|
|
|
/* keep factory data around */
|
|
data = g_new0 (FactoryData, 1);
|
|
data->factory = g_object_ref (factory);
|
|
data->pt = 96;
|
|
|
|
g_object_set_data_full (G_OBJECT (element), factory_key,
|
|
data, (GDestroyNotify) free_data);
|
|
|
|
/* connect to the signals */
|
|
g_signal_connect (uribin, "autoplug-continue",
|
|
(GCallback) autoplug_continue_cb, element);
|
|
g_signal_connect (uribin, "pad-added", (GCallback) pad_added_cb, element);
|
|
g_signal_connect (uribin, "no-more-pads", (GCallback) no_more_pads_cb,
|
|
element);
|
|
|
|
gst_bin_add (GST_BIN_CAST (element), uribin);
|
|
gst_bin_add (GST_BIN_CAST (topbin), element);
|
|
|
|
return topbin;
|
|
|
|
no_uridecodebin:
|
|
{
|
|
g_critical ("can't create uridecodebin element");
|
|
g_object_unref (element);
|
|
return NULL;
|
|
}
|
|
}
|