diff options
author | Nirbheek Chauhan <nirbheek@centricular.com> | 2017-10-13 18:05:54 +0300 |
---|---|---|
committer | Sebastian Dröge <sebastian@centricular.com> | 2018-01-26 11:53:29 +0200 |
commit | 2124522033525c9c54812a3b650a325f3cd59bf6 (patch) | |
tree | 8e226a5ec541c8f1db978128cf7fa9b5505e34ea | |
parent | af273b4de9eb292c0b6af63665e10ca015895902 (diff) |
rtspsrc: Implement ONVIF backchannel support
Set backchannel=onvif to enable, and use the 'push-backchannel-sample'
action signal with the correct stream id.
-rw-r--r-- | configure.ac | 1 | ||||
-rw-r--r-- | gst/rtsp/gstrtspsrc.c | 266 | ||||
-rw-r--r-- | gst/rtsp/gstrtspsrc.h | 8 | ||||
-rw-r--r-- | tests/examples/Makefile.am | 4 | ||||
-rw-r--r-- | tests/examples/meson.build | 1 | ||||
-rw-r--r-- | tests/examples/rtsp/Makefile.am | 3 | ||||
-rw-r--r-- | tests/examples/rtsp/meson.build | 5 | ||||
-rw-r--r-- | tests/examples/rtsp/test-onvif.c | 107 |
8 files changed, 352 insertions, 43 deletions
diff --git a/configure.ac b/configure.ac index 1a0df3577..745a2e849 100644 --- a/configure.ac +++ b/configure.ac @@ -1103,6 +1103,7 @@ tests/examples/equalizer/Makefile tests/examples/jack/Makefile tests/examples/level/Makefile tests/examples/rtp/Makefile +tests/examples/rtsp/Makefile tests/examples/shapewipe/Makefile tests/examples/spectrum/Makefile tests/examples/v4l2/Makefile diff --git a/gst/rtsp/gstrtspsrc.c b/gst/rtsp/gstrtspsrc.c index d63607396..e424f4702 100644 --- a/gst/rtsp/gstrtspsrc.c +++ b/gst/rtsp/gstrtspsrc.c @@ -126,6 +126,7 @@ enum SIGNAL_REQUEST_RTCP_KEY, SIGNAL_ACCEPT_CERTIFICATE, SIGNAL_BEFORE_SEND, + SIGNAL_PUSH_BACKCHANNEL_BUFFER, LAST_SIGNAL }; @@ -200,6 +201,32 @@ gst_rtsp_src_ntp_time_source_get_type (void) 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_PROTOCOLS GST_RTSP_LOWER_TRANS_UDP | GST_RTSP_LOWER_TRANS_UDP_MCAST | GST_RTSP_LOWER_TRANS_TCP #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 G_GINT64_CONSTANT(3000000000) #define DEFAULT_VERSION GST_RTSP_VERSION_1_0 +#define DEFAULT_BACKCHANNEL GST_RTSP_BACKCHANNEL_NONE enum { @@ -279,6 +307,7 @@ enum PROP_MAX_TS_OFFSET_ADJUSTMENT, PROP_MAX_TS_OFFSET, PROP_DEFAULT_VERSION, + PROP_BACKCHANNEL, }; #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 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 { guint8 pt; @@ -830,6 +862,20 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass) 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: * @rtspsrc: a #GstRTSPSrc * @request: a #GstRTSPMessage @@ -965,6 +1011,19 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass) g_cclosure_marshal_generic, G_TYPE_BOOLEAN, 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->provide_clock = gst_rtspsrc_provide_clock; 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; + klass->push_backchannel_buffer = gst_rtspsrc_push_backchannel_buffer; + 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: rtspsrc->default_version = g_value_get_enum (value); break; + case PROP_BACKCHANNEL: + rtspsrc->backchannel = g_value_get_enum (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1705,7 +1769,9 @@ gst_rtspsrc_collect_payloads (GstRTSPSrc * src, const GstSDPMessage * sdp, else 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; /* Parse global SDP attributes once */ @@ -1779,7 +1845,7 @@ unknown_proto: } recvonly_media: { - GST_DEBUG_OBJECT (src, "recvonly media ignored"); + GST_WARNING_OBJECT (src, "recvonly media ignored, no backchannel"); 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->mikey = NULL; stream->stream_id = NULL; + stream->is_backchannel = FALSE; g_mutex_init (&stream->conninfo.send_lock); g_mutex_init (&stream->conninfo.recv_lock); 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 * session manager to scale RTCP. */ 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]); } } - if (stream->fakesrc) { - gst_element_set_state (stream->fakesrc, GST_STATE_NULL); - gst_bin_remove (GST_BIN_CAST (src), stream->fakesrc); - gst_object_unref (stream->fakesrc); + if (stream->rtpsrc) { + gst_element_set_state (stream->rtpsrc, GST_STATE_NULL); + gst_bin_remove (GST_BIN_CAST (src), stream->rtpsrc); + gst_object_unref (stream->rtpsrc); } if (stream->srcpad) { gst_pad_set_active (stream->srcpad, FALSE); @@ -2763,6 +2835,32 @@ gst_rtspsrc_sink_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) 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 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; } +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 * payloaded RTP packets. We simply ghost the pad here. */ 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_active (stream->srcpad, TRUE); 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) { GST_DEBUG_OBJECT (src, "We added all streams"); @@ -3898,7 +4030,7 @@ gst_rtspsrc_stream_configure_udp_sinks (GstRTSPSrc * src, goto no_destination; /* 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) { GST_DEBUG_OBJECT (src, "configure RTP UDP sink for %s:%d", destination, rtp_port); @@ -3932,25 +4064,36 @@ gst_rtspsrc_stream_configure_udp_sinks (GstRTSPSrc * src, g_object_unref (socket); } - /* the source for the dummy packets to open up NAT */ - stream->fakesrc = gst_element_factory_make ("fakesrc", NULL); - if (stream->fakesrc == NULL) - goto no_fakesrc_element; + if (stream->is_backchannel) { + /* appsrc is for the app to shovel data using push-backchannel-buffer */ + stream->rtpsrc = gst_element_factory_make ("appsrc", NULL); + if (stream->rtpsrc == NULL) + goto no_appsrc_element; - /* random data in 5 buffers, a size of 200 bytes should be fine */ - g_object_set (G_OBJECT (stream->fakesrc), "filltype", 3, "num-buffers", 5, - "sizetype", 2, "sizemax", 200, "silent", TRUE, NULL); + /* interal use only, don't emit signals */ + g_object_set (G_OBJECT (stream->rtpsrc), "emit-signals", TRUE, + "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 */ 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_bin_add (GST_BIN_CAST (src), stream->udpsink[0]); - gst_object_ref (stream->fakesrc); - gst_bin_add (GST_BIN_CAST (src), stream->fakesrc); + gst_object_ref (stream->rtpsrc); + 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); } if (do_rtcp) { @@ -4021,6 +4164,11 @@ no_sink_element: GST_ERROR_OBJECT (src, "no UDP sink element found"); return FALSE; } +no_appsrc_element: + { + GST_ERROR_OBJECT (src, "no appsrc element found"); + return FALSE; + } no_fakesrc_element: { 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: if (!gst_rtspsrc_stream_configure_udp (src, stream, transport, &outpad)) goto transport_failed; - /* configure udpsinks back to the server for RTCP messages and for the - * dummy RTP messages to open NAT. */ + /* configure udpsinks back to the server for RTCP messages, for the + * dummy RTP messages to open NAT, and for the backchannel */ if (!gst_rtspsrc_stream_configure_udp_sinks (src, stream, transport)) goto transport_failed; break; @@ -4103,8 +4251,12 @@ gst_rtspsrc_stream_configure_transport (GstRTSPStream * stream, goto unknown_transport; } - if (outpad) { - GST_DEBUG_OBJECT (src, "creating ghostpad"); + /* using backchannel and no manager, hence no srcpad for this stream */ + 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); @@ -4128,17 +4280,17 @@ gst_rtspsrc_stream_configure_transport (GstRTSPStream * stream, /* ERRORS */ transport_failed: { - GST_DEBUG_OBJECT (src, "failed to configure transport"); + GST_WARNING_OBJECT (src, "failed to configure transport"); return FALSE; } unknown_transport: { - GST_DEBUG_OBJECT (src, "unknown transport"); + GST_WARNING_OBJECT (src, "unknown transport"); return FALSE; } no_manager: { - GST_DEBUG_OBJECT (src, "cannot get a session manager"); + GST_WARNING_OBJECT (src, "cannot get a session manager"); return FALSE; } } @@ -4157,13 +4309,18 @@ gst_rtspsrc_send_dummy_packets (GstRTSPSrc * src) for (walk = src->streams; walk; walk = g_list_next (walk)) { 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_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_PLAYING); - gst_element_set_state (stream->fakesrc, GST_STATE_PLAYING); - } + + gst_element_set_state (stream->udpsink[0], GST_STATE_NULL); + gst_element_set_state (stream->rtpsrc, GST_STATE_NULL); + gst_element_set_state (stream->udpsink[0], GST_STATE_PLAYING); + gst_element_set_state (stream->rtpsrc, GST_STATE_PLAYING); } return TRUE; } @@ -4205,7 +4362,10 @@ gst_rtspsrc_activate_streams (GstRTSPSrc * src) /* add the pad */ if (!stream->added) { 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; } } @@ -6529,7 +6689,7 @@ gst_rtspsrc_setup_streams_start (GstRTSPSrc * src, gboolean async) caps = stream_get_caps_for_pt (stream, stream->default_pt); if (caps == NULL) { - GST_DEBUG_OBJECT (src, "skipping stream %p, no caps", stream); + GST_WARNING_OBJECT (src, "skipping stream %p, no caps", stream); continue; } @@ -6574,13 +6734,14 @@ gst_rtspsrc_setup_streams_start (GstRTSPSrc * src, gboolean async) /* skip setup if we have no URL for it */ 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; } if (src->conninfo.connection == NULL) { 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; } conninfo = &stream->conninfo; @@ -6653,6 +6814,10 @@ gst_rtspsrc_setup_streams_start (GstRTSPSrc * src, gboolean async) /* select transport */ 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 */ if (stream->profile == GST_RTSP_PROFILE_SAVP || stream->profile == GST_RTSP_PROFILE_SAVPF) { @@ -7168,6 +7333,11 @@ restart: gst_rtsp_message_add_header (&request, GST_RTSP_HDR_ACCEPT, "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 */ GST_DEBUG_OBJECT (src, "send describe..."); @@ -7395,6 +7565,10 @@ gst_rtspsrc_close (GstRTSPSrc * src, gboolean async, gboolean only_close) if (res < 0) 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) GST_ELEMENT_PROGRESS (src, CONTINUE, "close", ("Closing stream")); @@ -7736,6 +7910,13 @@ restart: gst_rtsp_message_add_header (&request, GST_RTSP_HDR_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) GST_ELEMENT_PROGRESS (src, CONTINUE, "request", ("Sending PLAY request")); @@ -7856,17 +8037,17 @@ done: /* ERRORS */ open_failed: { - GST_DEBUG_OBJECT (src, "failed to open stream"); + GST_WARNING_OBJECT (src, "failed to open stream"); goto done; } not_supported: { - GST_DEBUG_OBJECT (src, "PLAY is not supported"); + GST_WARNING_OBJECT (src, "PLAY is not supported"); goto done; } was_playing: { - GST_DEBUG_OBJECT (src, "we were already PLAYING"); + GST_WARNING_OBJECT (src, "we were already PLAYING"); goto done; } create_request_failed: @@ -7950,6 +8131,13 @@ gst_rtspsrc_pause (GstRTSPSrc * src, gboolean async) setup_url)) < 0) 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 = gst_rtspsrc_send (src, conninfo, &request, &response, NULL, NULL)) < 0) diff --git a/gst/rtsp/gstrtspsrc.h b/gst/rtsp/gstrtspsrc.h index 57921d2e1..4e5adeff7 100644 --- a/gst/rtsp/gstrtspsrc.h +++ b/gst/rtsp/gstrtspsrc.h @@ -123,8 +123,8 @@ struct _GstRTSPStream { GstElement *udpsink[2]; GstPad *rtcppad; - /* fakesrc for sending dummy data */ - GstElement *fakesrc; + /* fakesrc for sending dummy data or appsrc for sending backchannel data */ + GstElement *rtpsrc; /* state */ guint port; @@ -161,6 +161,7 @@ struct _GstRTSPStream { gchar *destination; gboolean is_multicast; guint ttl; + gboolean is_backchannel; /* A unique and stable id we will use for the stream start event */ gchar *stream_id; @@ -254,6 +255,7 @@ struct _GstRTSPSrc { guint64 max_ts_offset_adjustment; gint64 max_ts_offset; gboolean max_ts_offset_is_set; + gint backchannel; /* state */ GstRTSPState state; @@ -298,6 +300,8 @@ struct _GstRTSPSrc { struct _GstRTSPSrcClass { GstBinClass parent_class; + + GstFlowReturn (*push_backchannel_buffer) (GstRTSPSrc *src, guint id, GstSample *sample); }; GType gst_rtspsrc_get_type(void); diff --git a/tests/examples/Makefile.am b/tests/examples/Makefile.am index 7c10d02a0..b9e717a77 100644 --- a/tests/examples/Makefile.am +++ b/tests/examples/Makefile.am @@ -11,9 +11,9 @@ CAIRO_DIR= endif SUBDIRS = audiofx equalizer $(JACK_DIR) level \ - rtp shapewipe spectrum v4l2 $(CAIRO_DIR) + rtp rtsp shapewipe spectrum v4l2 $(CAIRO_DIR) DIST_SUBDIRS = audiofx equalizer jack level \ - rtp shapewipe spectrum v4l2 cairo + rtp rtsp shapewipe spectrum v4l2 cairo include $(top_srcdir)/common/parallel-subdirs.mak diff --git a/tests/examples/meson.build b/tests/examples/meson.build index bf7013413..d5f7ad0cd 100644 --- a/tests/examples/meson.build +++ b/tests/examples/meson.build @@ -2,6 +2,7 @@ subdir('audiofx') subdir('cairo') subdir('level') subdir('rtp') +subdir('rtsp') subdir('shapewipe') subdir('v4l2') diff --git a/tests/examples/rtsp/Makefile.am b/tests/examples/rtsp/Makefile.am new file mode 100644 index 000000000..126ce8cdd --- /dev/null +++ b/tests/examples/rtsp/Makefile.am @@ -0,0 +1,3 @@ +noinst_PROGRAMS = test-onvif +test_onvif_CFLAGS = $(GST_CFLAGS) +test_onvif_LDADD = $(GST_LIBS) diff --git a/tests/examples/rtsp/meson.build b/tests/examples/rtsp/meson.build new file mode 100644 index 000000000..7434c8a6c --- /dev/null +++ b/tests/examples/rtsp/meson.build @@ -0,0 +1,5 @@ +executable('onvif-test', 'onvif-test.c', + dependencies: [gst_dep], + c_args : gst_plugins_good_args, + include_directories : [configinc], + install: false) diff --git a/tests/examples/rtsp/test-onvif.c b/tests/examples/rtsp/test-onvif.c new file mode 100644 index 000000000..671c20f3c --- /dev/null +++ b/tests/examples/rtsp/test-onvif.c @@ -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); +} |