mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-01-11 01:45:33 +00:00
rtspsrc: Implement ONVIF backchannel support
Set backchannel=onvif to enable, and use the 'push-backchannel-sample' action signal with the correct stream id.
This commit is contained in:
parent
d61066e6b5
commit
befa41cdf6
8 changed files with 352 additions and 43 deletions
|
@ -1294,6 +1294,7 @@ tests/examples/gtk/Makefile
|
||||||
tests/examples/jack/Makefile
|
tests/examples/jack/Makefile
|
||||||
tests/examples/level/Makefile
|
tests/examples/level/Makefile
|
||||||
tests/examples/rtp/Makefile
|
tests/examples/rtp/Makefile
|
||||||
|
tests/examples/rtsp/Makefile
|
||||||
tests/examples/shapewipe/Makefile
|
tests/examples/shapewipe/Makefile
|
||||||
tests/examples/spectrum/Makefile
|
tests/examples/spectrum/Makefile
|
||||||
tests/examples/v4l2/Makefile
|
tests/examples/v4l2/Makefile
|
||||||
|
|
|
@ -126,6 +126,7 @@ enum
|
||||||
SIGNAL_REQUEST_RTCP_KEY,
|
SIGNAL_REQUEST_RTCP_KEY,
|
||||||
SIGNAL_ACCEPT_CERTIFICATE,
|
SIGNAL_ACCEPT_CERTIFICATE,
|
||||||
SIGNAL_BEFORE_SEND,
|
SIGNAL_BEFORE_SEND,
|
||||||
|
SIGNAL_PUSH_BACKCHANNEL_BUFFER,
|
||||||
LAST_SIGNAL
|
LAST_SIGNAL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -200,6 +201,32 @@ gst_rtsp_src_ntp_time_source_get_type (void)
|
||||||
return ntp_time_source_type;
|
return ntp_time_source_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum _GstRtspBackchannel
|
||||||
|
{
|
||||||
|
BACKCHANNEL_NONE,
|
||||||
|
BACKCHANNEL_ONVIF
|
||||||
|
};
|
||||||
|
|
||||||
|
#define GST_TYPE_RTSP_BACKCHANNEL (gst_rtsp_backchannel_get_type())
|
||||||
|
static GType
|
||||||
|
gst_rtsp_backchannel_get_type (void)
|
||||||
|
{
|
||||||
|
static GType backchannel_type = 0;
|
||||||
|
static const GEnumValue backchannel_values[] = {
|
||||||
|
{BACKCHANNEL_NONE, "No backchannel", "none"},
|
||||||
|
{BACKCHANNEL_ONVIF, "ONVIF audio backchannel", "onvif"},
|
||||||
|
{0, NULL, NULL},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (G_UNLIKELY (backchannel_type == 0)) {
|
||||||
|
backchannel_type =
|
||||||
|
g_enum_register_static ("GstRTSPBackchannel", backchannel_values);
|
||||||
|
}
|
||||||
|
return backchannel_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define BACKCHANNEL_ONVIF_HDR_REQUIRE_VAL "www.onvif.org/ver20/backchannel"
|
||||||
|
|
||||||
#define DEFAULT_LOCATION NULL
|
#define DEFAULT_LOCATION NULL
|
||||||
#define DEFAULT_PROTOCOLS GST_RTSP_LOWER_TRANS_UDP | GST_RTSP_LOWER_TRANS_UDP_MCAST | GST_RTSP_LOWER_TRANS_TCP
|
#define DEFAULT_PROTOCOLS GST_RTSP_LOWER_TRANS_UDP | GST_RTSP_LOWER_TRANS_UDP_MCAST | GST_RTSP_LOWER_TRANS_TCP
|
||||||
#define DEFAULT_DEBUG FALSE
|
#define DEFAULT_DEBUG FALSE
|
||||||
|
@ -236,6 +263,7 @@ gst_rtsp_src_ntp_time_source_get_type (void)
|
||||||
#define DEFAULT_MAX_TS_OFFSET_ADJUSTMENT G_GUINT64_CONSTANT(0)
|
#define DEFAULT_MAX_TS_OFFSET_ADJUSTMENT G_GUINT64_CONSTANT(0)
|
||||||
#define DEFAULT_MAX_TS_OFFSET G_GINT64_CONSTANT(3000000000)
|
#define DEFAULT_MAX_TS_OFFSET G_GINT64_CONSTANT(3000000000)
|
||||||
#define DEFAULT_VERSION GST_RTSP_VERSION_1_0
|
#define DEFAULT_VERSION GST_RTSP_VERSION_1_0
|
||||||
|
#define DEFAULT_BACKCHANNEL GST_RTSP_BACKCHANNEL_NONE
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
|
@ -279,6 +307,7 @@ enum
|
||||||
PROP_MAX_TS_OFFSET_ADJUSTMENT,
|
PROP_MAX_TS_OFFSET_ADJUSTMENT,
|
||||||
PROP_MAX_TS_OFFSET,
|
PROP_MAX_TS_OFFSET,
|
||||||
PROP_DEFAULT_VERSION,
|
PROP_DEFAULT_VERSION,
|
||||||
|
PROP_BACKCHANNEL,
|
||||||
};
|
};
|
||||||
|
|
||||||
#define GST_TYPE_RTSP_NAT_METHOD (gst_rtsp_nat_method_get_type())
|
#define GST_TYPE_RTSP_NAT_METHOD (gst_rtsp_nat_method_get_type())
|
||||||
|
@ -359,6 +388,9 @@ gst_rtspsrc_print_rtsp_message (GstRTSPSrc * src, const GstRTSPMessage * msg);
|
||||||
static void
|
static void
|
||||||
gst_rtspsrc_print_sdp_message (GstRTSPSrc * src, const GstSDPMessage * msg);
|
gst_rtspsrc_print_sdp_message (GstRTSPSrc * src, const GstSDPMessage * msg);
|
||||||
|
|
||||||
|
static GstFlowReturn gst_rtspsrc_push_backchannel_buffer (GstRTSPSrc * src,
|
||||||
|
guint id, GstSample * sample);
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
guint8 pt;
|
guint8 pt;
|
||||||
|
@ -829,6 +861,20 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass)
|
||||||
"changed to 0 (no limit)", 0, G_MAXINT64, DEFAULT_MAX_TS_OFFSET,
|
"changed to 0 (no limit)", 0, G_MAXINT64, DEFAULT_MAX_TS_OFFSET,
|
||||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GstRtpSrc:backchannel
|
||||||
|
*
|
||||||
|
* Select a type of backchannel to setup with the RTSP server.
|
||||||
|
* Default value is "none". Allowed values are "none" and "onvif".
|
||||||
|
*
|
||||||
|
* Since: 1.14
|
||||||
|
*/
|
||||||
|
g_object_class_install_property (gobject_class, PROP_BACKCHANNEL,
|
||||||
|
g_param_spec_enum ("backchannel", "Backchannel type",
|
||||||
|
"The type of backchannel to setup. Default is 'none'.",
|
||||||
|
GST_TYPE_RTSP_BACKCHANNEL, BACKCHANNEL_NONE,
|
||||||
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GstRTSPSrc::handle-request:
|
* GstRTSPSrc::handle-request:
|
||||||
* @rtspsrc: a #GstRTSPSrc
|
* @rtspsrc: a #GstRTSPSrc
|
||||||
|
@ -965,6 +1011,19 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass)
|
||||||
g_cclosure_marshal_generic, G_TYPE_BOOLEAN,
|
g_cclosure_marshal_generic, G_TYPE_BOOLEAN,
|
||||||
1, GST_TYPE_RTSP_MESSAGE | G_SIGNAL_TYPE_STATIC_SCOPE);
|
1, GST_TYPE_RTSP_MESSAGE | G_SIGNAL_TYPE_STATIC_SCOPE);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GstRTSPSrc::push-backchannel-buffer:
|
||||||
|
* @rtspsrc: a #GstRTSPSrc
|
||||||
|
* @buffer: RTP buffer to send back
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
gst_rtspsrc_signals[SIGNAL_PUSH_BACKCHANNEL_BUFFER] =
|
||||||
|
g_signal_new ("push-backchannel-buffer", G_TYPE_FROM_CLASS (klass),
|
||||||
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GstRTSPSrcClass,
|
||||||
|
push_backchannel_buffer), NULL, NULL, NULL, GST_TYPE_FLOW_RETURN, 2,
|
||||||
|
G_TYPE_UINT, GST_TYPE_BUFFER);
|
||||||
|
|
||||||
gstelement_class->send_event = gst_rtspsrc_send_event;
|
gstelement_class->send_event = gst_rtspsrc_send_event;
|
||||||
gstelement_class->provide_clock = gst_rtspsrc_provide_clock;
|
gstelement_class->provide_clock = gst_rtspsrc_provide_clock;
|
||||||
gstelement_class->change_state = gst_rtspsrc_change_state;
|
gstelement_class->change_state = gst_rtspsrc_change_state;
|
||||||
|
@ -980,6 +1039,8 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass)
|
||||||
|
|
||||||
gstbin_class->handle_message = gst_rtspsrc_handle_message;
|
gstbin_class->handle_message = gst_rtspsrc_handle_message;
|
||||||
|
|
||||||
|
klass->push_backchannel_buffer = gst_rtspsrc_push_backchannel_buffer;
|
||||||
|
|
||||||
gst_rtsp_ext_list_init ();
|
gst_rtsp_ext_list_init ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1335,6 +1396,9 @@ gst_rtspsrc_set_property (GObject * object, guint prop_id, const GValue * value,
|
||||||
case PROP_DEFAULT_VERSION:
|
case PROP_DEFAULT_VERSION:
|
||||||
rtspsrc->default_version = g_value_get_enum (value);
|
rtspsrc->default_version = g_value_get_enum (value);
|
||||||
break;
|
break;
|
||||||
|
case PROP_BACKCHANNEL:
|
||||||
|
rtspsrc->backchannel = g_value_get_enum (value);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
break;
|
break;
|
||||||
|
@ -1705,7 +1769,9 @@ gst_rtspsrc_collect_payloads (GstRTSPSrc * src, const GstSDPMessage * sdp,
|
||||||
else
|
else
|
||||||
goto unknown_proto;
|
goto unknown_proto;
|
||||||
|
|
||||||
if (gst_sdp_media_get_attribute_val (media, "recvonly") != NULL)
|
if (gst_sdp_media_get_attribute_val (media, "recvonly") != NULL &&
|
||||||
|
/* We want to setup caps for streams configured as backchannel */
|
||||||
|
!stream->is_backchannel)
|
||||||
goto recvonly_media;
|
goto recvonly_media;
|
||||||
|
|
||||||
/* Parse global SDP attributes once */
|
/* Parse global SDP attributes once */
|
||||||
|
@ -1779,7 +1845,7 @@ unknown_proto:
|
||||||
}
|
}
|
||||||
recvonly_media:
|
recvonly_media:
|
||||||
{
|
{
|
||||||
GST_DEBUG_OBJECT (src, "recvonly media ignored");
|
GST_WARNING_OBJECT (src, "recvonly media ignored, no backchannel");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1839,10 +1905,16 @@ gst_rtspsrc_create_stream (GstRTSPSrc * src, GstSDPMessage * sdp, gint idx,
|
||||||
stream->ptmap = g_array_new (FALSE, FALSE, sizeof (PtMapItem));
|
stream->ptmap = g_array_new (FALSE, FALSE, sizeof (PtMapItem));
|
||||||
stream->mikey = NULL;
|
stream->mikey = NULL;
|
||||||
stream->stream_id = NULL;
|
stream->stream_id = NULL;
|
||||||
|
stream->is_backchannel = FALSE;
|
||||||
g_mutex_init (&stream->conninfo.send_lock);
|
g_mutex_init (&stream->conninfo.send_lock);
|
||||||
g_mutex_init (&stream->conninfo.recv_lock);
|
g_mutex_init (&stream->conninfo.recv_lock);
|
||||||
g_array_set_clear_func (stream->ptmap, (GDestroyNotify) clear_ptmap_item);
|
g_array_set_clear_func (stream->ptmap, (GDestroyNotify) clear_ptmap_item);
|
||||||
|
|
||||||
|
/* stream is recvonly and onvif backchannel is requested */
|
||||||
|
if (gst_sdp_media_get_attribute_val (media, "recvonly") != NULL &&
|
||||||
|
src->backchannel != BACKCHANNEL_NONE)
|
||||||
|
stream->is_backchannel = TRUE;
|
||||||
|
|
||||||
/* collect bandwidth information for this steam. FIXME, configure in the RTP
|
/* collect bandwidth information for this steam. FIXME, configure in the RTP
|
||||||
* session manager to scale RTCP. */
|
* session manager to scale RTCP. */
|
||||||
gst_rtspsrc_collect_bandwidth (src, sdp, media, stream);
|
gst_rtspsrc_collect_bandwidth (src, sdp, media, stream);
|
||||||
|
@ -1940,10 +2012,10 @@ gst_rtspsrc_stream_free (GstRTSPSrc * src, GstRTSPStream * stream)
|
||||||
gst_object_unref (stream->udpsink[i]);
|
gst_object_unref (stream->udpsink[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (stream->fakesrc) {
|
if (stream->rtpsrc) {
|
||||||
gst_element_set_state (stream->fakesrc, GST_STATE_NULL);
|
gst_element_set_state (stream->rtpsrc, GST_STATE_NULL);
|
||||||
gst_bin_remove (GST_BIN_CAST (src), stream->fakesrc);
|
gst_bin_remove (GST_BIN_CAST (src), stream->rtpsrc);
|
||||||
gst_object_unref (stream->fakesrc);
|
gst_object_unref (stream->rtpsrc);
|
||||||
}
|
}
|
||||||
if (stream->srcpad) {
|
if (stream->srcpad) {
|
||||||
gst_pad_set_active (stream->srcpad, FALSE);
|
gst_pad_set_active (stream->srcpad, FALSE);
|
||||||
|
@ -2763,6 +2835,32 @@ gst_rtspsrc_sink_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static GstFlowReturn
|
||||||
|
gst_rtspsrc_push_backchannel_buffer (GstRTSPSrc * src, guint id,
|
||||||
|
GstSample * sample)
|
||||||
|
{
|
||||||
|
GstFlowReturn res = GST_FLOW_OK;
|
||||||
|
GstRTSPStream *stream;
|
||||||
|
|
||||||
|
if (!src->conninfo.connected || src->state != GST_RTSP_STATE_PLAYING)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
stream = find_stream (src, &id, (gpointer) find_stream_by_id);
|
||||||
|
if (stream == NULL) {
|
||||||
|
GST_ERROR_OBJECT (src, "no stream with id %u", id);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_signal_emit_by_name (stream->rtpsrc, "push-sample", sample, &res);
|
||||||
|
GST_DEBUG_OBJECT (src, "sent backchannel RTP sample %p: %s", sample,
|
||||||
|
gst_flow_get_name (res));
|
||||||
|
|
||||||
|
out:
|
||||||
|
gst_sample_unref (sample);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
static GstPadProbeReturn
|
static GstPadProbeReturn
|
||||||
pad_blocked (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
|
pad_blocked (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
|
||||||
{
|
{
|
||||||
|
@ -2801,6 +2899,35 @@ copy_sticky_events (GstPad * pad, GstEvent ** event, gpointer user_data)
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
add_backchannel_fakesink (GstRTSPSrc * src, GstRTSPStream * stream,
|
||||||
|
GstPad * srcpad)
|
||||||
|
{
|
||||||
|
GstPad *sinkpad;
|
||||||
|
GstElement *fakesink;
|
||||||
|
|
||||||
|
fakesink = gst_element_factory_make ("fakesink", NULL);
|
||||||
|
if (fakesink == NULL) {
|
||||||
|
GST_ERROR_OBJECT (src, "no fakesink");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
sinkpad = gst_element_get_static_pad (fakesink, "sink");
|
||||||
|
|
||||||
|
GST_DEBUG_OBJECT (src, "backchannel stream %p, hooking fakesink", stream);
|
||||||
|
|
||||||
|
gst_bin_add (GST_BIN_CAST (src), fakesink);
|
||||||
|
if (gst_pad_link (srcpad, sinkpad) != GST_PAD_LINK_OK) {
|
||||||
|
GST_WARNING_OBJECT (src, "could not link to fakesink");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
gst_object_unref (sinkpad);
|
||||||
|
|
||||||
|
gst_element_sync_state_with_parent (fakesink);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
/* this callback is called when the session manager generated a new src pad with
|
/* this callback is called when the session manager generated a new src pad with
|
||||||
* payloaded RTP packets. We simply ghost the pad here. */
|
* payloaded RTP packets. We simply ghost the pad here. */
|
||||||
static void
|
static void
|
||||||
|
@ -2868,7 +2995,12 @@ new_manager_pad (GstElement * manager, GstPad * pad, GstRTSPSrc * src)
|
||||||
gst_pad_set_query_function (stream->srcpad, gst_rtspsrc_handle_src_query);
|
gst_pad_set_query_function (stream->srcpad, gst_rtspsrc_handle_src_query);
|
||||||
gst_pad_set_active (stream->srcpad, TRUE);
|
gst_pad_set_active (stream->srcpad, TRUE);
|
||||||
gst_pad_sticky_events_foreach (pad, copy_sticky_events, stream->srcpad);
|
gst_pad_sticky_events_foreach (pad, copy_sticky_events, stream->srcpad);
|
||||||
gst_element_add_pad (GST_ELEMENT_CAST (src), stream->srcpad);
|
|
||||||
|
/* don't add the srcpad if this is a recvonly stream */
|
||||||
|
if (stream->is_backchannel)
|
||||||
|
add_backchannel_fakesink (src, stream, stream->srcpad);
|
||||||
|
else
|
||||||
|
gst_element_add_pad (GST_ELEMENT_CAST (src), stream->srcpad);
|
||||||
|
|
||||||
if (all_added) {
|
if (all_added) {
|
||||||
GST_DEBUG_OBJECT (src, "We added all streams");
|
GST_DEBUG_OBJECT (src, "We added all streams");
|
||||||
|
@ -3898,7 +4030,7 @@ gst_rtspsrc_stream_configure_udp_sinks (GstRTSPSrc * src,
|
||||||
goto no_destination;
|
goto no_destination;
|
||||||
|
|
||||||
/* try to construct the fakesrc to the RTP port of the server to open up any
|
/* try to construct the fakesrc to the RTP port of the server to open up any
|
||||||
* NAT firewalls */
|
* NAT firewalls or, if backchannel, construct an appsrc */
|
||||||
if (do_rtp) {
|
if (do_rtp) {
|
||||||
GST_DEBUG_OBJECT (src, "configure RTP UDP sink for %s:%d", destination,
|
GST_DEBUG_OBJECT (src, "configure RTP UDP sink for %s:%d", destination,
|
||||||
rtp_port);
|
rtp_port);
|
||||||
|
@ -3932,25 +4064,36 @@ gst_rtspsrc_stream_configure_udp_sinks (GstRTSPSrc * src,
|
||||||
g_object_unref (socket);
|
g_object_unref (socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* the source for the dummy packets to open up NAT */
|
if (stream->is_backchannel) {
|
||||||
stream->fakesrc = gst_element_factory_make ("fakesrc", NULL);
|
/* appsrc is for the app to shovel data using push-backchannel-buffer */
|
||||||
if (stream->fakesrc == NULL)
|
stream->rtpsrc = gst_element_factory_make ("appsrc", NULL);
|
||||||
goto no_fakesrc_element;
|
if (stream->rtpsrc == NULL)
|
||||||
|
goto no_appsrc_element;
|
||||||
|
|
||||||
/* random data in 5 buffers, a size of 200 bytes should be fine */
|
/* interal use only, don't emit signals */
|
||||||
g_object_set (G_OBJECT (stream->fakesrc), "filltype", 3, "num-buffers", 5,
|
g_object_set (G_OBJECT (stream->rtpsrc), "emit-signals", TRUE,
|
||||||
"sizetype", 2, "sizemax", 200, "silent", TRUE, NULL);
|
"is-live", TRUE, NULL);
|
||||||
|
} else {
|
||||||
|
/* the source for the dummy packets to open up NAT */
|
||||||
|
stream->rtpsrc = gst_element_factory_make ("fakesrc", NULL);
|
||||||
|
if (stream->rtpsrc == NULL)
|
||||||
|
goto no_fakesrc_element;
|
||||||
|
|
||||||
|
/* random data in 5 buffers, a size of 200 bytes should be fine */
|
||||||
|
g_object_set (G_OBJECT (stream->rtpsrc), "filltype", 3, "num-buffers", 5,
|
||||||
|
"sizetype", 2, "sizemax", 200, "silent", TRUE, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
/* keep everything locked */
|
/* keep everything locked */
|
||||||
gst_element_set_locked_state (stream->udpsink[0], TRUE);
|
gst_element_set_locked_state (stream->udpsink[0], TRUE);
|
||||||
gst_element_set_locked_state (stream->fakesrc, TRUE);
|
gst_element_set_locked_state (stream->rtpsrc, TRUE);
|
||||||
|
|
||||||
gst_object_ref (stream->udpsink[0]);
|
gst_object_ref (stream->udpsink[0]);
|
||||||
gst_bin_add (GST_BIN_CAST (src), stream->udpsink[0]);
|
gst_bin_add (GST_BIN_CAST (src), stream->udpsink[0]);
|
||||||
gst_object_ref (stream->fakesrc);
|
gst_object_ref (stream->rtpsrc);
|
||||||
gst_bin_add (GST_BIN_CAST (src), stream->fakesrc);
|
gst_bin_add (GST_BIN_CAST (src), stream->rtpsrc);
|
||||||
|
|
||||||
gst_element_link_pads_full (stream->fakesrc, "src", stream->udpsink[0],
|
gst_element_link_pads_full (stream->rtpsrc, "src", stream->udpsink[0],
|
||||||
"sink", GST_PAD_LINK_CHECK_NOTHING);
|
"sink", GST_PAD_LINK_CHECK_NOTHING);
|
||||||
}
|
}
|
||||||
if (do_rtcp) {
|
if (do_rtcp) {
|
||||||
|
@ -4021,6 +4164,11 @@ no_sink_element:
|
||||||
GST_ERROR_OBJECT (src, "no UDP sink element found");
|
GST_ERROR_OBJECT (src, "no UDP sink element found");
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
no_appsrc_element:
|
||||||
|
{
|
||||||
|
GST_ERROR_OBJECT (src, "no appsrc element found");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
no_fakesrc_element:
|
no_fakesrc_element:
|
||||||
{
|
{
|
||||||
GST_ERROR_OBJECT (src, "no fakesrc element found");
|
GST_ERROR_OBJECT (src, "no fakesrc element found");
|
||||||
|
@ -4094,8 +4242,8 @@ gst_rtspsrc_stream_configure_transport (GstRTSPStream * stream,
|
||||||
case GST_RTSP_LOWER_TRANS_UDP:
|
case GST_RTSP_LOWER_TRANS_UDP:
|
||||||
if (!gst_rtspsrc_stream_configure_udp (src, stream, transport, &outpad))
|
if (!gst_rtspsrc_stream_configure_udp (src, stream, transport, &outpad))
|
||||||
goto transport_failed;
|
goto transport_failed;
|
||||||
/* configure udpsinks back to the server for RTCP messages and for the
|
/* configure udpsinks back to the server for RTCP messages, for the
|
||||||
* dummy RTP messages to open NAT. */
|
* dummy RTP messages to open NAT, and for the backchannel */
|
||||||
if (!gst_rtspsrc_stream_configure_udp_sinks (src, stream, transport))
|
if (!gst_rtspsrc_stream_configure_udp_sinks (src, stream, transport))
|
||||||
goto transport_failed;
|
goto transport_failed;
|
||||||
break;
|
break;
|
||||||
|
@ -4103,8 +4251,12 @@ gst_rtspsrc_stream_configure_transport (GstRTSPStream * stream,
|
||||||
goto unknown_transport;
|
goto unknown_transport;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outpad) {
|
/* using backchannel and no manager, hence no srcpad for this stream */
|
||||||
GST_DEBUG_OBJECT (src, "creating ghostpad");
|
if (outpad && stream->is_backchannel) {
|
||||||
|
add_backchannel_fakesink (src, stream, outpad);
|
||||||
|
gst_object_unref (outpad);
|
||||||
|
} else if (outpad) {
|
||||||
|
GST_DEBUG_OBJECT (src, "creating ghostpad for stream %p", stream);
|
||||||
|
|
||||||
gst_pad_use_fixed_caps (outpad);
|
gst_pad_use_fixed_caps (outpad);
|
||||||
|
|
||||||
|
@ -4128,17 +4280,17 @@ gst_rtspsrc_stream_configure_transport (GstRTSPStream * stream,
|
||||||
/* ERRORS */
|
/* ERRORS */
|
||||||
transport_failed:
|
transport_failed:
|
||||||
{
|
{
|
||||||
GST_DEBUG_OBJECT (src, "failed to configure transport");
|
GST_WARNING_OBJECT (src, "failed to configure transport");
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
unknown_transport:
|
unknown_transport:
|
||||||
{
|
{
|
||||||
GST_DEBUG_OBJECT (src, "unknown transport");
|
GST_WARNING_OBJECT (src, "unknown transport");
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
no_manager:
|
no_manager:
|
||||||
{
|
{
|
||||||
GST_DEBUG_OBJECT (src, "cannot get a session manager");
|
GST_WARNING_OBJECT (src, "cannot get a session manager");
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4157,13 +4309,18 @@ gst_rtspsrc_send_dummy_packets (GstRTSPSrc * src)
|
||||||
for (walk = src->streams; walk; walk = g_list_next (walk)) {
|
for (walk = src->streams; walk; walk = g_list_next (walk)) {
|
||||||
GstRTSPStream *stream = (GstRTSPStream *) walk->data;
|
GstRTSPStream *stream = (GstRTSPStream *) walk->data;
|
||||||
|
|
||||||
if (stream->fakesrc && stream->udpsink[0]) {
|
if (!stream->rtpsrc || !stream->udpsink[0])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (stream->is_backchannel)
|
||||||
|
GST_DEBUG_OBJECT (src, "starting backchannel stream %p", stream);
|
||||||
|
else
|
||||||
GST_DEBUG_OBJECT (src, "sending dummy packet to stream %p", stream);
|
GST_DEBUG_OBJECT (src, "sending dummy packet to stream %p", stream);
|
||||||
gst_element_set_state (stream->udpsink[0], GST_STATE_NULL);
|
|
||||||
gst_element_set_state (stream->fakesrc, GST_STATE_NULL);
|
gst_element_set_state (stream->udpsink[0], GST_STATE_NULL);
|
||||||
gst_element_set_state (stream->udpsink[0], GST_STATE_PLAYING);
|
gst_element_set_state (stream->rtpsrc, GST_STATE_NULL);
|
||||||
gst_element_set_state (stream->fakesrc, GST_STATE_PLAYING);
|
gst_element_set_state (stream->udpsink[0], GST_STATE_PLAYING);
|
||||||
}
|
gst_element_set_state (stream->rtpsrc, GST_STATE_PLAYING);
|
||||||
}
|
}
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
@ -4205,7 +4362,10 @@ gst_rtspsrc_activate_streams (GstRTSPSrc * src)
|
||||||
/* add the pad */
|
/* add the pad */
|
||||||
if (!stream->added) {
|
if (!stream->added) {
|
||||||
GST_DEBUG_OBJECT (src, "adding stream pad %p", stream);
|
GST_DEBUG_OBJECT (src, "adding stream pad %p", stream);
|
||||||
gst_element_add_pad (GST_ELEMENT_CAST (src), stream->srcpad);
|
if (stream->is_backchannel)
|
||||||
|
add_backchannel_fakesink (src, stream, stream->srcpad);
|
||||||
|
else
|
||||||
|
gst_element_add_pad (GST_ELEMENT_CAST (src), stream->srcpad);
|
||||||
stream->added = TRUE;
|
stream->added = TRUE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6529,7 +6689,7 @@ gst_rtspsrc_setup_streams_start (GstRTSPSrc * src, gboolean async)
|
||||||
|
|
||||||
caps = stream_get_caps_for_pt (stream, stream->default_pt);
|
caps = stream_get_caps_for_pt (stream, stream->default_pt);
|
||||||
if (caps == NULL) {
|
if (caps == NULL) {
|
||||||
GST_DEBUG_OBJECT (src, "skipping stream %p, no caps", stream);
|
GST_WARNING_OBJECT (src, "skipping stream %p, no caps", stream);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6574,13 +6734,14 @@ gst_rtspsrc_setup_streams_start (GstRTSPSrc * src, gboolean async)
|
||||||
|
|
||||||
/* skip setup if we have no URL for it */
|
/* skip setup if we have no URL for it */
|
||||||
if (stream->conninfo.location == NULL) {
|
if (stream->conninfo.location == NULL) {
|
||||||
GST_DEBUG_OBJECT (src, "skipping stream %p, no setup", stream);
|
GST_WARNING_OBJECT (src, "skipping stream %p, no setup", stream);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (src->conninfo.connection == NULL) {
|
if (src->conninfo.connection == NULL) {
|
||||||
if (!gst_rtsp_conninfo_connect (src, &stream->conninfo, async)) {
|
if (!gst_rtsp_conninfo_connect (src, &stream->conninfo, async)) {
|
||||||
GST_DEBUG_OBJECT (src, "skipping stream %p, failed to connect", stream);
|
GST_WARNING_OBJECT (src, "skipping stream %p, failed to connect",
|
||||||
|
stream);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
conninfo = &stream->conninfo;
|
conninfo = &stream->conninfo;
|
||||||
|
@ -6653,6 +6814,10 @@ gst_rtspsrc_setup_streams_start (GstRTSPSrc * src, gboolean async)
|
||||||
/* select transport */
|
/* select transport */
|
||||||
gst_rtsp_message_take_header (&request, GST_RTSP_HDR_TRANSPORT, transports);
|
gst_rtsp_message_take_header (&request, GST_RTSP_HDR_TRANSPORT, transports);
|
||||||
|
|
||||||
|
if (stream->is_backchannel && src->backchannel == BACKCHANNEL_ONVIF)
|
||||||
|
gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE,
|
||||||
|
BACKCHANNEL_ONVIF_HDR_REQUIRE_VAL);
|
||||||
|
|
||||||
/* set up keys */
|
/* set up keys */
|
||||||
if (stream->profile == GST_RTSP_PROFILE_SAVP ||
|
if (stream->profile == GST_RTSP_PROFILE_SAVP ||
|
||||||
stream->profile == GST_RTSP_PROFILE_SAVPF) {
|
stream->profile == GST_RTSP_PROFILE_SAVPF) {
|
||||||
|
@ -7168,6 +7333,11 @@ restart:
|
||||||
gst_rtsp_message_add_header (&request, GST_RTSP_HDR_ACCEPT,
|
gst_rtsp_message_add_header (&request, GST_RTSP_HDR_ACCEPT,
|
||||||
"application/sdp");
|
"application/sdp");
|
||||||
|
|
||||||
|
if (src->backchannel == BACKCHANNEL_ONVIF)
|
||||||
|
gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE,
|
||||||
|
BACKCHANNEL_ONVIF_HDR_REQUIRE_VAL);
|
||||||
|
/* TODO: Handle the case when backchannel is unsupported and goto restart */
|
||||||
|
|
||||||
/* send DESCRIBE */
|
/* send DESCRIBE */
|
||||||
GST_DEBUG_OBJECT (src, "send describe...");
|
GST_DEBUG_OBJECT (src, "send describe...");
|
||||||
|
|
||||||
|
@ -7395,6 +7565,10 @@ gst_rtspsrc_close (GstRTSPSrc * src, gboolean async, gboolean only_close)
|
||||||
if (res < 0)
|
if (res < 0)
|
||||||
goto create_request_failed;
|
goto create_request_failed;
|
||||||
|
|
||||||
|
if (stream->is_backchannel && src->backchannel == BACKCHANNEL_ONVIF)
|
||||||
|
gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE,
|
||||||
|
BACKCHANNEL_ONVIF_HDR_REQUIRE_VAL);
|
||||||
|
|
||||||
if (async)
|
if (async)
|
||||||
GST_ELEMENT_PROGRESS (src, CONTINUE, "close", ("Closing stream"));
|
GST_ELEMENT_PROGRESS (src, CONTINUE, "close", ("Closing stream"));
|
||||||
|
|
||||||
|
@ -7736,6 +7910,13 @@ restart:
|
||||||
gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SEEK_STYLE,
|
gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SEEK_STYLE,
|
||||||
seek_style);
|
seek_style);
|
||||||
|
|
||||||
|
/* when we have an ONVIF audio backchannel, the PLAY request must have the
|
||||||
|
* Require: header when doing either aggregate or non-aggregate control */
|
||||||
|
if (src->backchannel == BACKCHANNEL_ONVIF &&
|
||||||
|
(control || stream->is_backchannel))
|
||||||
|
gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE,
|
||||||
|
BACKCHANNEL_ONVIF_HDR_REQUIRE_VAL);
|
||||||
|
|
||||||
if (async)
|
if (async)
|
||||||
GST_ELEMENT_PROGRESS (src, CONTINUE, "request", ("Sending PLAY request"));
|
GST_ELEMENT_PROGRESS (src, CONTINUE, "request", ("Sending PLAY request"));
|
||||||
|
|
||||||
|
@ -7856,17 +8037,17 @@ done:
|
||||||
/* ERRORS */
|
/* ERRORS */
|
||||||
open_failed:
|
open_failed:
|
||||||
{
|
{
|
||||||
GST_DEBUG_OBJECT (src, "failed to open stream");
|
GST_WARNING_OBJECT (src, "failed to open stream");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
not_supported:
|
not_supported:
|
||||||
{
|
{
|
||||||
GST_DEBUG_OBJECT (src, "PLAY is not supported");
|
GST_WARNING_OBJECT (src, "PLAY is not supported");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
was_playing:
|
was_playing:
|
||||||
{
|
{
|
||||||
GST_DEBUG_OBJECT (src, "we were already PLAYING");
|
GST_WARNING_OBJECT (src, "we were already PLAYING");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
create_request_failed:
|
create_request_failed:
|
||||||
|
@ -7950,6 +8131,13 @@ gst_rtspsrc_pause (GstRTSPSrc * src, gboolean async)
|
||||||
setup_url)) < 0)
|
setup_url)) < 0)
|
||||||
goto create_request_failed;
|
goto create_request_failed;
|
||||||
|
|
||||||
|
/* when we have an ONVIF audio backchannel, the PAUSE request must have the
|
||||||
|
* Require: header when doing either aggregate or non-aggregate control */
|
||||||
|
if (src->backchannel == BACKCHANNEL_ONVIF &&
|
||||||
|
(control || stream->is_backchannel))
|
||||||
|
gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE,
|
||||||
|
BACKCHANNEL_ONVIF_HDR_REQUIRE_VAL);
|
||||||
|
|
||||||
if ((res =
|
if ((res =
|
||||||
gst_rtspsrc_send (src, conninfo, &request, &response, NULL,
|
gst_rtspsrc_send (src, conninfo, &request, &response, NULL,
|
||||||
NULL)) < 0)
|
NULL)) < 0)
|
||||||
|
|
|
@ -123,8 +123,8 @@ struct _GstRTSPStream {
|
||||||
GstElement *udpsink[2];
|
GstElement *udpsink[2];
|
||||||
GstPad *rtcppad;
|
GstPad *rtcppad;
|
||||||
|
|
||||||
/* fakesrc for sending dummy data */
|
/* fakesrc for sending dummy data or appsrc for sending backchannel data */
|
||||||
GstElement *fakesrc;
|
GstElement *rtpsrc;
|
||||||
|
|
||||||
/* state */
|
/* state */
|
||||||
guint port;
|
guint port;
|
||||||
|
@ -161,6 +161,7 @@ struct _GstRTSPStream {
|
||||||
gchar *destination;
|
gchar *destination;
|
||||||
gboolean is_multicast;
|
gboolean is_multicast;
|
||||||
guint ttl;
|
guint ttl;
|
||||||
|
gboolean is_backchannel;
|
||||||
|
|
||||||
/* A unique and stable id we will use for the stream start event */
|
/* A unique and stable id we will use for the stream start event */
|
||||||
gchar *stream_id;
|
gchar *stream_id;
|
||||||
|
@ -254,6 +255,7 @@ struct _GstRTSPSrc {
|
||||||
guint64 max_ts_offset_adjustment;
|
guint64 max_ts_offset_adjustment;
|
||||||
gint64 max_ts_offset;
|
gint64 max_ts_offset;
|
||||||
gboolean max_ts_offset_is_set;
|
gboolean max_ts_offset_is_set;
|
||||||
|
gint backchannel;
|
||||||
|
|
||||||
/* state */
|
/* state */
|
||||||
GstRTSPState state;
|
GstRTSPState state;
|
||||||
|
@ -298,6 +300,8 @@ struct _GstRTSPSrc {
|
||||||
|
|
||||||
struct _GstRTSPSrcClass {
|
struct _GstRTSPSrcClass {
|
||||||
GstBinClass parent_class;
|
GstBinClass parent_class;
|
||||||
|
|
||||||
|
GstFlowReturn (*push_backchannel_buffer) (GstRTSPSrc *src, guint id, GstSample *sample);
|
||||||
};
|
};
|
||||||
|
|
||||||
GType gst_rtspsrc_get_type(void);
|
GType gst_rtspsrc_get_type(void);
|
||||||
|
|
|
@ -17,9 +17,9 @@ CAIRO_DIR=
|
||||||
endif
|
endif
|
||||||
|
|
||||||
SUBDIRS = audiofx equalizer $(GTK_DIR) $(JACK_DIR) level \
|
SUBDIRS = audiofx equalizer $(GTK_DIR) $(JACK_DIR) level \
|
||||||
rtp shapewipe spectrum v4l2 $(CAIRO_DIR)
|
rtp rtsp shapewipe spectrum v4l2 $(CAIRO_DIR)
|
||||||
|
|
||||||
DIST_SUBDIRS = audiofx equalizer gtk jack level \
|
DIST_SUBDIRS = audiofx equalizer gtk jack level \
|
||||||
rtp shapewipe spectrum v4l2 cairo
|
rtp rtsp shapewipe spectrum v4l2 cairo
|
||||||
|
|
||||||
include $(top_srcdir)/common/parallel-subdirs.mak
|
include $(top_srcdir)/common/parallel-subdirs.mak
|
||||||
|
|
|
@ -4,6 +4,7 @@ subdir('cairo')
|
||||||
subdir('level')
|
subdir('level')
|
||||||
#FIXME: subdir('qt')
|
#FIXME: subdir('qt')
|
||||||
subdir('rtp')
|
subdir('rtp')
|
||||||
|
subdir('rtsp')
|
||||||
subdir('shapewipe')
|
subdir('shapewipe')
|
||||||
subdir('v4l2')
|
subdir('v4l2')
|
||||||
|
|
||||||
|
|
3
tests/examples/rtsp/Makefile.am
Normal file
3
tests/examples/rtsp/Makefile.am
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
noinst_PROGRAMS = test-onvif
|
||||||
|
test_onvif_CFLAGS = $(GST_CFLAGS)
|
||||||
|
test_onvif_LDADD = $(GST_LIBS)
|
5
tests/examples/rtsp/meson.build
Normal file
5
tests/examples/rtsp/meson.build
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
executable('onvif-test', 'onvif-test.c',
|
||||||
|
dependencies: [gst_dep],
|
||||||
|
c_args : gst_plugins_good_args,
|
||||||
|
include_directories : [configinc],
|
||||||
|
install: false)
|
107
tests/examples/rtsp/test-onvif.c
Normal file
107
tests/examples/rtsp/test-onvif.c
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
#include <gst/gst.h>
|
||||||
|
|
||||||
|
static GMainLoop *loop = NULL;
|
||||||
|
static GstElement *backpipe = NULL;
|
||||||
|
static gint stream_id = -1;
|
||||||
|
|
||||||
|
#define PCMU_CAPS "application/x-rtp, media=audio, payload=0, clock-rate=8000, encoding-name=PCMU"
|
||||||
|
|
||||||
|
static GstFlowReturn
|
||||||
|
new_sample (GstElement * appsink, GstElement * rtspsrc)
|
||||||
|
{
|
||||||
|
GstSample *sample;
|
||||||
|
GstFlowReturn ret = GST_FLOW_OK;
|
||||||
|
|
||||||
|
g_assert (stream_id != -1);
|
||||||
|
|
||||||
|
g_signal_emit_by_name (appsink, "pull-sample", &sample);
|
||||||
|
|
||||||
|
if (!sample)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
g_signal_emit_by_name (rtspsrc, "push-backchannel-buffer", stream_id, sample,
|
||||||
|
&ret);
|
||||||
|
|
||||||
|
out:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
setup_backchannel_shoveler (GstElement * rtspsrc, GstCaps * caps)
|
||||||
|
{
|
||||||
|
GstElement *appsink;
|
||||||
|
|
||||||
|
backpipe = gst_parse_launch ("audiotestsrc is-live=true wave=red-noise ! "
|
||||||
|
"mulawenc ! rtppcmupay ! appsink name=out", NULL);
|
||||||
|
if (!backpipe)
|
||||||
|
g_error ("Could not setup backchannel pipeline");
|
||||||
|
|
||||||
|
appsink = gst_bin_get_by_name (GST_BIN (backpipe), "out");
|
||||||
|
g_object_set (G_OBJECT (appsink), "caps", caps, "emit-signals", TRUE, NULL);
|
||||||
|
|
||||||
|
g_signal_connect (appsink, "new-sample", G_CALLBACK (new_sample), rtspsrc);
|
||||||
|
|
||||||
|
g_print ("Playing backchannel shoveler\n");
|
||||||
|
gst_element_set_state (backpipe, GST_STATE_PLAYING);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
remove_extra_fields (GQuark field_id, GValue * value G_GNUC_UNUSED,
|
||||||
|
gpointer user_data G_GNUC_UNUSED)
|
||||||
|
{
|
||||||
|
return !g_str_has_prefix (g_quark_to_string (field_id), "a-");
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
find_backchannel (GstElement * rtspsrc, guint idx, GstCaps * caps,
|
||||||
|
gpointer user_data G_GNUC_UNUSED)
|
||||||
|
{
|
||||||
|
GstStructure *s;
|
||||||
|
gchar *caps_str = gst_caps_to_string (caps);
|
||||||
|
g_print ("Selecting stream idx %u, caps %s\n", idx, caps_str);
|
||||||
|
g_free (caps_str);
|
||||||
|
|
||||||
|
s = gst_caps_get_structure (caps, 0);
|
||||||
|
if (gst_structure_has_field (s, "a-recvonly")) {
|
||||||
|
stream_id = idx;
|
||||||
|
caps = gst_caps_new_empty ();
|
||||||
|
s = gst_structure_copy (s);
|
||||||
|
gst_structure_set_name (s, "application/x-rtp");
|
||||||
|
gst_structure_filter_and_map_in_place (s, remove_extra_fields, NULL);
|
||||||
|
gst_caps_append_structure (caps, s);
|
||||||
|
setup_backchannel_shoveler (rtspsrc, caps);
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main (int argc, char *argv[])
|
||||||
|
{
|
||||||
|
GstElement *pipeline, *rtspsrc;
|
||||||
|
const gchar *location;
|
||||||
|
|
||||||
|
gst_init (&argc, &argv);
|
||||||
|
|
||||||
|
if (argc >= 2)
|
||||||
|
location = argv[1];
|
||||||
|
else
|
||||||
|
location = "rtsp://127.0.0.1:8554/test";
|
||||||
|
|
||||||
|
loop = g_main_loop_new (NULL, FALSE);
|
||||||
|
|
||||||
|
pipeline = gst_parse_launch ("rtspsrc backchannel=onvif debug=true name=r "
|
||||||
|
"r. ! queue ! decodebin ! queue ! xvimagesink async=false "
|
||||||
|
"r. ! queue ! decodebin ! queue ! pulsesink async=false ", NULL);
|
||||||
|
if (!pipeline)
|
||||||
|
g_error ("Failed to parse pipeline");
|
||||||
|
|
||||||
|
rtspsrc = gst_bin_get_by_name (GST_BIN (pipeline), "r");
|
||||||
|
g_object_set (G_OBJECT (rtspsrc), "location", location, NULL);
|
||||||
|
g_signal_connect (rtspsrc, "select-stream", G_CALLBACK (find_backchannel),
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||||
|
|
||||||
|
g_main_loop_run (loop);
|
||||||
|
}
|
Loading…
Reference in a new issue