From 1894293d6378c69548d974d2965e9decc1527654 Mon Sep 17 00:00:00 2001 From: Matthew Waters Date: Tue, 31 Jan 2017 20:56:59 +1100 Subject: [PATCH] webrtcbin: an element that handles the transport aspects of webrtc connections SDP's are generated and consumed according to the W3C PeerConnection API available from https://www.w3.org/TR/webrtc/ The SDP is either created initially from the connected sink pads/attached transceivers as in the case of generating an offer or intersected with the connected sink pads/attached transceivers as in the case for creating an answer. In both cases, the rtp payloaded streams sent by the peer are exposed as separate src pads. The implementation supports trickle ICE, RTCP muxing, reduced size RTCP. With contributions from: Nirbheek Chauhan Mathieu Duponchelle Edward Hervey https://bugzilla.gnome.org/show_bug.cgi?id=792523 --- .gitignore | 5 + configure.ac | 16 + docs/libs/Makefile.am | 1 + docs/libs/gst-plugins-bad-libs-docs.sgml | 10 + docs/libs/gst-plugins-bad-libs-sections.txt | 101 + docs/libs/gst-plugins-bad-libs.types | 20 + ext/Makefile.am | 12 +- ext/meson.build | 1 + ext/webrtc/Makefile.am | 53 + ext/webrtc/fwd.h | 58 + ext/webrtc/gstwebrtc.c | 39 + ext/webrtc/gstwebrtcbin.c | 3530 +++++++++++++++++ ext/webrtc/gstwebrtcbin.h | 154 + ext/webrtc/gstwebrtcice.c | 887 +++++ ext/webrtc/gstwebrtcice.h | 83 + ext/webrtc/gstwebrtcstats.c | 549 +++ ext/webrtc/gstwebrtcstats.h | 35 + ext/webrtc/icestream.c | 239 ++ ext/webrtc/icestream.h | 63 + ext/webrtc/meson.build | 27 + ext/webrtc/nicetransport.c | 268 ++ ext/webrtc/nicetransport.h | 58 + ext/webrtc/transportreceivebin.c | 376 ++ ext/webrtc/transportreceivebin.h | 65 + ext/webrtc/transportsendbin.c | 471 +++ ext/webrtc/transportsendbin.h | 58 + ext/webrtc/transportstream.c | 252 ++ ext/webrtc/transportstream.h | 69 + ext/webrtc/utils.c | 138 + ext/webrtc/utils.h | 65 + ext/webrtc/webrtcsdp.c | 716 ++++ ext/webrtc/webrtcsdp.h | 80 + ext/webrtc/webrtctransceiver.c | 149 + ext/webrtc/webrtctransceiver.h | 57 + gst-libs/gst/Makefile.am | 4 +- gst-libs/gst/meson.build | 1 + gst-libs/gst/webrtc/Makefile.am | 54 + gst-libs/gst/webrtc/dtlstransport.c | 238 ++ gst-libs/gst/webrtc/dtlstransport.h | 70 + gst-libs/gst/webrtc/icetransport.c | 204 + gst-libs/gst/webrtc/icetransport.h | 76 + gst-libs/gst/webrtc/meson.build | 59 + gst-libs/gst/webrtc/rtcsessiondescription.c | 123 + gst-libs/gst/webrtc/rtcsessiondescription.h | 58 + gst-libs/gst/webrtc/rtpreceiver.c | 135 + gst-libs/gst/webrtc/rtpreceiver.h | 76 + gst-libs/gst/webrtc/rtpsender.c | 141 + gst-libs/gst/webrtc/rtpsender.h | 77 + gst-libs/gst/webrtc/rtptransceiver.c | 186 + gst-libs/gst/webrtc/rtptransceiver.h | 69 + gst-libs/gst/webrtc/webrtc.h | 33 + gst-libs/gst/webrtc/webrtc_fwd.h | 251 ++ gst-libs/gst/webrtc/webrtc_mkenum.py | 55 + pkgconfig/Makefile.am | 4 + .../gstreamer-plugins-bad-uninstalled.pc.in | 2 +- pkgconfig/gstreamer-webrtc-uninstalled.pc.in | 12 + pkgconfig/gstreamer-webrtc.pc.in | 12 + pkgconfig/meson.build | 2 + tests/check/Makefile.am | 14 + tests/check/elements/.gitignore | 1 + tests/check/elements/webrtcbin.c | 1382 +++++++ tests/check/meson.build | 1 + tests/examples/Makefile.am | 10 +- tests/examples/meson.build | 1 + tests/examples/webrtc/Makefile.am | 41 + tests/examples/webrtc/meson.build | 15 + tests/examples/webrtc/webrtc.c | 187 + tests/examples/webrtc/webrtcbidirectional.c | 197 + tests/examples/webrtc/webrtcswap.c | 215 + 69 files changed, 12704 insertions(+), 7 deletions(-) create mode 100644 ext/webrtc/Makefile.am create mode 100644 ext/webrtc/fwd.h create mode 100644 ext/webrtc/gstwebrtc.c create mode 100644 ext/webrtc/gstwebrtcbin.c create mode 100644 ext/webrtc/gstwebrtcbin.h create mode 100644 ext/webrtc/gstwebrtcice.c create mode 100644 ext/webrtc/gstwebrtcice.h create mode 100644 ext/webrtc/gstwebrtcstats.c create mode 100644 ext/webrtc/gstwebrtcstats.h create mode 100644 ext/webrtc/icestream.c create mode 100644 ext/webrtc/icestream.h create mode 100644 ext/webrtc/meson.build create mode 100644 ext/webrtc/nicetransport.c create mode 100644 ext/webrtc/nicetransport.h create mode 100644 ext/webrtc/transportreceivebin.c create mode 100644 ext/webrtc/transportreceivebin.h create mode 100644 ext/webrtc/transportsendbin.c create mode 100644 ext/webrtc/transportsendbin.h create mode 100644 ext/webrtc/transportstream.c create mode 100644 ext/webrtc/transportstream.h create mode 100644 ext/webrtc/utils.c create mode 100644 ext/webrtc/utils.h create mode 100644 ext/webrtc/webrtcsdp.c create mode 100644 ext/webrtc/webrtcsdp.h create mode 100644 ext/webrtc/webrtctransceiver.c create mode 100644 ext/webrtc/webrtctransceiver.h create mode 100644 gst-libs/gst/webrtc/Makefile.am create mode 100644 gst-libs/gst/webrtc/dtlstransport.c create mode 100644 gst-libs/gst/webrtc/dtlstransport.h create mode 100644 gst-libs/gst/webrtc/icetransport.c create mode 100644 gst-libs/gst/webrtc/icetransport.h create mode 100644 gst-libs/gst/webrtc/meson.build create mode 100644 gst-libs/gst/webrtc/rtcsessiondescription.c create mode 100644 gst-libs/gst/webrtc/rtcsessiondescription.h create mode 100644 gst-libs/gst/webrtc/rtpreceiver.c create mode 100644 gst-libs/gst/webrtc/rtpreceiver.h create mode 100644 gst-libs/gst/webrtc/rtpsender.c create mode 100644 gst-libs/gst/webrtc/rtpsender.h create mode 100644 gst-libs/gst/webrtc/rtptransceiver.c create mode 100644 gst-libs/gst/webrtc/rtptransceiver.h create mode 100644 gst-libs/gst/webrtc/webrtc.h create mode 100644 gst-libs/gst/webrtc/webrtc_fwd.h create mode 100755 gst-libs/gst/webrtc/webrtc_mkenum.py create mode 100644 pkgconfig/gstreamer-webrtc-uninstalled.pc.in create mode 100644 pkgconfig/gstreamer-webrtc.pc.in create mode 100644 tests/check/elements/webrtcbin.c create mode 100644 tests/examples/webrtc/Makefile.am create mode 100644 tests/examples/webrtc/meson.build create mode 100644 tests/examples/webrtc/webrtc.c create mode 100644 tests/examples/webrtc/webrtcbidirectional.c create mode 100644 tests/examples/webrtc/webrtcswap.c diff --git a/.gitignore b/.gitignore index 5a64122c80..7a0aef6046 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,8 @@ Makefile tmp-orc.c gst*orc.h +/gst-libs/gst/*/*-enumtypes.[ch] + /tests/check/orc /tests/check/media/ /tests/check/libs/player @@ -71,6 +73,9 @@ gst*orc.h /tests/examples/opencv/gstfacedetect_test /tests/examples/playout /tests/examples/waylandsink/gtkwaylandsink +/tests/examples/webrtc/webrtc +/tests/examples/webrtc/webrtcbidirectional +/tests/examples/webrtc/webrtcswap Build *.user diff --git a/configure.ac b/configure.ac index b413720e58..197c4991bf 100644 --- a/configure.ac +++ b/configure.ac @@ -2425,6 +2425,16 @@ AG_GST_CHECK_FEATURE(WEBRTCDSP, [WebRTC Audio Processing], webrtcdsp, [ AC_LANG_POP([C++]) ]) +dnl *** WebRTC *** +translit(dnm, m, l) AM_CONDITIONAL(USE_WEBRTC, true) +AG_GST_CHECK_FEATURE(WEBRTC, [WebRTC], webrtc, [ + AG_GST_PKG_CHECK_MODULES(GST_SDP, gstreamer-sdp-1.0) + PKG_CHECK_MODULES(NICE, nice >= 0.1, [ + HAVE_WEBRTC="yes" ], [ + HAVE_WEBRTC="no" + ]) +]) + else dnl not building plugins with external dependencies, @@ -2501,6 +2511,7 @@ AM_CONDITIONAL(USE_RTMP, false) AM_CONDITIONAL(USE_TELETEXTDEC, false) AM_CONDITIONAL(USE_UVCH264, false) AM_CONDITIONAL(USE_WEBP, false) +AM_CONDITIONAL(USE_WEBRTC, false) AM_CONDITIONAL(USE_WEBRTCDSP, false) AM_CONDITIONAL(USE_OPENH264, false) AM_CONDITIONAL(USE_X265, false) @@ -2676,6 +2687,7 @@ gst-libs/gst/codecparsers/Makefile gst-libs/gst/mpegts/Makefile gst-libs/gst/uridownloader/Makefile gst-libs/gst/wayland/Makefile +gst-libs/gst/webrtc/Makefile gst-libs/gst/player/Makefile gst-libs/gst/video/Makefile gst-libs/gst/audio/Makefile @@ -2724,6 +2736,7 @@ tests/examples/mxf/Makefile tests/examples/opencv/Makefile tests/examples/uvch264/Makefile tests/examples/waylandsink/Makefile +tests/examples/webrtc/Makefile tests/icles/Makefile ext/voamrwbenc/Makefile ext/voaacenc/Makefile @@ -2793,6 +2806,7 @@ ext/webp/Makefile ext/x265/Makefile ext/zbar/Makefile ext/dtls/Makefile +ext/webrtc/Makefile ext/webrtcdsp/Makefile ext/ttml/Makefile po/Makefile.in @@ -2813,6 +2827,8 @@ pkgconfig/gstreamer-player.pc pkgconfig/gstreamer-player-uninstalled.pc pkgconfig/gstreamer-wayland.pc pkgconfig/gstreamer-wayland-uninstalled.pc +pkgconfig/gstreamer-webrtc.pc +pkgconfig/gstreamer-webrtc-uninstalled.pc pkgconfig/gstreamer-bad-video.pc pkgconfig/gstreamer-bad-video-uninstalled.pc pkgconfig/gstreamer-bad-audio.pc diff --git a/docs/libs/Makefile.am b/docs/libs/Makefile.am index dfc20ff531..ef2d37f8e3 100644 --- a/docs/libs/Makefile.am +++ b/docs/libs/Makefile.am @@ -61,6 +61,7 @@ GTKDOC_LIBS = \ $(top_builddir)/gst-libs/gst/insertbin/libgstinsertbin-@GST_API_VERSION@.la \ $(top_builddir)/gst-libs/gst/mpegts/libgstmpegts-@GST_API_VERSION@.la \ $(top_builddir)/gst-libs/gst/player/libgstplayer-@GST_API_VERSION@.la \ + $(top_builddir)/gst-libs/gst/webrtc/libgstwebrtc-@GST_API_VERSION@.la \ $(GST_BASE_LIBS) # If you need to override some of the declarations, place them in this file diff --git a/docs/libs/gst-plugins-bad-libs-docs.sgml b/docs/libs/gst-plugins-bad-libs-docs.sgml index 530af2b4da..6a237c8da3 100644 --- a/docs/libs/gst-plugins-bad-libs-docs.sgml +++ b/docs/libs/gst-plugins-bad-libs-docs.sgml @@ -73,6 +73,16 @@ + + WebRTC Library + + + + + + + + Interfaces diff --git a/docs/libs/gst-plugins-bad-libs-sections.txt b/docs/libs/gst-plugins-bad-libs-sections.txt index 7becdeb685..7231614ccc 100644 --- a/docs/libs/gst-plugins-bad-libs-sections.txt +++ b/docs/libs/gst-plugins-bad-libs-sections.txt @@ -1065,3 +1065,104 @@ GstPlayerSubtitleInfoClass gst_player_subtitle_info_get_type + +
+gstwebrtc-dtlstransport +GstWebRTCDTLSTransportState + +gst_webrtc_dtls_transport_new + + +GST_TYPE_WEBRTC_DTLS_TRANSPORT +gst_webrtc_dtls_transport_get_type +GstWebRTCDTLSTransport +GST_WEBRTC_DTLS_TRANSPORT +GST_IS_WEBRTC_DTLS_TRANSPORT +GstWebRTCDTLSTransportClass +GST_WEBRTC_DTLS_TRANSPORT_CLASS +GST_WEBRTC_DTLS_TRANSPORT_GET_CLASS +GST_IS_WEBRTC_DTLS_TRANSPORT_CLASS +
+ +
+gstwebrtc-icetransport +GstWebRTCIceRole +GstWebRTCICEConnectionState +GstWebRTCICEGatheringState + + + + +GST_TYPE_WEBRTC_ICE_TRANSPORT +gst_webrtc_ice_transport_get_type +GstWebRTCICETransport +GST_WEBRTC_ICE_TRANSPORT +GST_IS_WEBRTC_ICE_TRANSPORT +GstWebRTCICETransportClass +GST_WEBRTC_ICE_TRANSPORT_CLASS +GST_WEBRTC_ICE_TRANSPORT_GET_CLASS +GST_IS_WEBRTC_ICE_TRANSPORT_CLASS +
+ +
+gstwebrtc-receiver +gst_webrtc_rtp_receiver_new +gst_webrtc_rtp_receiver_get_parameters +gst_webrtc_rtp_receiver_set_parameters +gst_webrtc_rtp_receiver_set_rtcp_transport +gst_webrtc_rtp_receiver_set_transport + +GST_TYPE_WEBRTC_RTP_RECEIVER +gst_webrtc_rtp_receiver_get_type +GstWebRTCRTPReceiver +GST_WEBRTC_RTP_RECEIVER +GST_IS_WEBRTC_RTP_RECEIVER +GstWebRTCRTPReceiverClass +GST_WEBRTC_RTP_RECEIVER_CLASS +GST_WEBRTC_RTP_RECEIVER_GET_CLASS +GST_IS_WEBRTC_RTP_RECEIVER_CLASS +
+ +
+gstwebrtc-sender +gst_webrtc_rtp_sender_new +gst_webrtc_rtp_sender_get_parameters +gst_webrtc_rtp_sender_set_parameters +gst_webrtc_rtp_sender_set_rtcp_transport +gst_webrtc_rtp_sender_set_transport + +GST_TYPE_WEBRTC_RTP_SENDER +gst_webrtc_rtp_sender_get_type +GstWebRTCRTPSender +GST_WEBRTC_RTP_SENDER +GST_IS_WEBRTC_RTP_SENDER +GstWebRTCRTPSenderClass +GST_WEBRTC_RTP_SENDER_CLASS +GST_WEBRTC_RTP_SENDER_GET_CLASS +GST_IS_WEBRTC_RTP_SENDER_CLASS +
+ +
+gstwebrtc-sessiondescription +GstWebRTCSessionDescription +gst_webrtc_session_description_new +gst_webrtc_session_description_copy +gst_webrtc_session_description_free + +gst_webrtc_session_description_get_type +GST_TYPE_WEBRTC_SESSION_DESCRIPTION +
+ +
+gstwebrtc-transceiver + +GST_TYPE_WEBRTC_RTP_TRANSCEIVER +gst_webrtc_rtp_transceiver_get_type +GstWebRTCRTPTransceiver +GST_WEBRTC_RTP_TRANSCEIVER +GST_IS_WEBRTC_RTP_TRANSCEIVER +GstWebRTCRTPTransceiverClass +GST_WEBRTC_RTP_TRANSCEIVER_CLASS +GST_WEBRTC_RTP_TRANSCEIVER_GET_CLASS +GST_IS_WEBRTC_RTP_TRANSCEIVER_CLASS +
diff --git a/docs/libs/gst-plugins-bad-libs.types b/docs/libs/gst-plugins-bad-libs.types index 1b3a555343..7b4851c421 100644 --- a/docs/libs/gst-plugins-bad-libs.types +++ b/docs/libs/gst-plugins-bad-libs.types @@ -7,6 +7,7 @@ #include #include #include +#include gst_audio_aggregator_get_type gst_audio_aggregator_pad_get_type @@ -49,3 +50,22 @@ gst_player_video_overlay_video_renderer_get_type gst_player_video_renderer_get_type gst_player_visualization_get_type + +gst_webrtc_dtls_setup_get_type +gst_webrtc_dtls_transport_get_type +gst_webrtc_dtls_transport_state_get_type +gst_webrtc_ice_component_get_type +gst_webrtc_ice_connection_state_get_type +gst_webrtc_ice_gathering_state_get_type +gst_webrtc_ice_role_get_type +gst_webrtc_sdp_type_get_type +gst_webrtc_ice_transport_get_type +gst_webrtc_peer_connection_state_get_type +gst_webrtc_rtp_receiver_get_type +gst_webrtc_rtp_sender_get_type +gst_webrtc_session_description_get_type +gst_webrtc_signaling_state_get_type +gst_webrtc_rtp_transceiver_direction_get_type +gst_webrtc_rtp_transceiver_get_type +gst_webrtc_stats_type_get_type + diff --git a/ext/Makefile.am b/ext/Makefile.am index e4e0db7d68..cbea2e0bda 100644 --- a/ext/Makefile.am +++ b/ext/Makefile.am @@ -406,6 +406,12 @@ else WEBRTCDSP_DIR= endif +if USE_WEBRTC +WEBRTC_DIR=webrtc +else +WEBRTC_DIR= +endif + if USE_TTML TTML_DIR=ttml else @@ -482,7 +488,8 @@ SUBDIRS=\ $(DTLS_DIR) \ $(VULKAN_DIR) \ $(WEBRTCDSP_DIR) \ - $(TTML_DIR) + $(TTML_DIR) \ + $(WEBRTC_DIR) DIST_SUBDIRS = \ assrender \ @@ -551,6 +558,7 @@ DIST_SUBDIRS = \ dtls \ vulkan \ webrtcdsp \ - ttml + ttml \ + webrtc include $(top_srcdir)/common/parallel-subdirs.mak diff --git a/ext/meson.build b/ext/meson.build index d4730b7cef..a2960b8ee4 100644 --- a/ext/meson.build +++ b/ext/meson.build @@ -65,6 +65,7 @@ subdir('voaacenc') subdir('vulkan') subdir('wayland') subdir('webrtcdsp') +subdir('webrtc') subdir('webp') subdir('x265') subdir('zbar') diff --git a/ext/webrtc/Makefile.am b/ext/webrtc/Makefile.am new file mode 100644 index 0000000000..5f9a714882 --- /dev/null +++ b/ext/webrtc/Makefile.am @@ -0,0 +1,53 @@ +plugin_LTLIBRARIES = libgstwebrtc.la + +noinst_HEADERS = \ + fwd.h \ + gstwebrtcbin.h \ + gstwebrtcice.h \ + gstwebrtcstats.h \ + icestream.h \ + nicetransport.h \ + transportstream.h \ + transportsendbin.h \ + transportreceivebin.h \ + utils.h \ + webrtcsdp.h \ + webrtctransceiver.h + +libgstwebrtc_la_SOURCES = \ + gstwebrtc.c \ + gstwebrtcbin.c \ + gstwebrtcice.c \ + gstwebrtcstats.c \ + icestream.c \ + nicetransport.c \ + transportstream.c \ + transportsendbin.c \ + transportreceivebin.c \ + utils.c \ + webrtcsdp.c \ + webrtctransceiver.c + +libgstwebrtc_la_SOURCES += $(BUILT_SOURCES) +noinst_HEADERS += $(built_headers) + +libgstwebrtc_la_CFLAGS = \ + -I$(top_builddir)/gst-libs \ + -I$(top_srcdir)/gst-libs \ + $(GST_PLUGINS_BASE_CFLAGS) \ + $(GST_BASE_CFLAGS) \ + $(GST_CFLAGS) \ + $(GST_SDP_CFLAGS) \ + $(NICE_CFLAGS) +libgstwebrtc_la_LIBADD = \ + $(GST_PLUGINS_BASE_LIBS) \ + $(GST_BASE_LIBS) \ + $(GST_LIBS) \ + $(GST_SDP_LIBS) \ + $(NICE_LIBS) \ + $(top_builddir)/gst-libs/gst/webrtc/libgstwebrtc-@GST_API_VERSION@.la + +libgstwebrtc_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstwebrtc_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS) + +include $(top_srcdir)/common/gst-glib-gen.mak diff --git a/ext/webrtc/fwd.h b/ext/webrtc/fwd.h new file mode 100644 index 0000000000..903145fbf9 --- /dev/null +++ b/ext/webrtc/fwd.h @@ -0,0 +1,58 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifndef __WEBRTC_FWD_H__ +#define __WEBRTC_FWD_H__ + +#include +#include + +G_BEGIN_DECLS + +typedef struct _GstWebRTCBin GstWebRTCBin; +typedef struct _GstWebRTCBinClass GstWebRTCBinClass; +typedef struct _GstWebRTCBinPrivate GstWebRTCBinPrivate; + +typedef struct _GstWebRTCICE GstWebRTCICE; +typedef struct _GstWebRTCICEClass GstWebRTCICEClass; +typedef struct _GstWebRTCICEPrivate GstWebRTCICEPrivate; + +typedef struct _GstWebRTCICEStream GstWebRTCICEStream; +typedef struct _GstWebRTCICEStreamClass GstWebRTCICEStreamClass; +typedef struct _GstWebRTCICEStreamPrivate GstWebRTCICEStreamPrivate; + +typedef struct _GstWebRTCNiceTransport GstWebRTCNiceTransport; +typedef struct _GstWebRTCNiceTransportClass GstWebRTCNiceTransportClass; +typedef struct _GstWebRTCNiceTransportPrivate GstWebRTCNiceTransportPrivate; + +typedef struct _TransportStream TransportStream; +typedef struct _TransportStreamClass TransportStreamClass; + +typedef struct _TransportSendBin TransportSendBin; +typedef struct _TransportSendBinClass TransportSendBinClass; + +typedef struct _TransportReceiveBin TransportReceiveBin; +typedef struct _TransportReceiveBinClass TransportReceiveBinClass; + +typedef struct _WebRTCTransceiver WebRTCTransceiver; +typedef struct _WebRTCTransceiverClass WebRTCTransceiverClass; + +G_END_DECLS + +#endif /* __WEBRTC_FWD_H__ */ diff --git a/ext/webrtc/gstwebrtc.c b/ext/webrtc/gstwebrtc.c new file mode 100644 index 0000000000..27dc642b42 --- /dev/null +++ b/ext/webrtc/gstwebrtc.c @@ -0,0 +1,39 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "gstwebrtcbin.h" + +static gboolean +plugin_init (GstPlugin * plugin) +{ + if (!gst_element_register (plugin, "webrtcbin", GST_RANK_PRIMARY, + GST_TYPE_WEBRTC_BIN)) + return FALSE; + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + webrtc, + "WebRTC plugins", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/ext/webrtc/gstwebrtcbin.c b/ext/webrtc/gstwebrtcbin.c new file mode 100644 index 0000000000..a834a22c59 --- /dev/null +++ b/ext/webrtc/gstwebrtcbin.c @@ -0,0 +1,3530 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "gstwebrtcbin.h" +#include "gstwebrtcstats.h" +#include "transportstream.h" +#include "transportreceivebin.h" +#include "utils.h" +#include "webrtcsdp.h" +#include "webrtctransceiver.h" + +#include +#include +#include + +#define RANDOM_SESSION_ID \ + ((((((guint64) g_random_int()) << 32) | \ + (guint64) g_random_int ())) & \ + G_GUINT64_CONSTANT (0x7fffffffffffffff)) + +#define PC_GET_LOCK(w) (&w->priv->pc_lock) +#define PC_LOCK(w) (g_mutex_lock (PC_GET_LOCK(w))) +#define PC_UNLOCK(w) (g_mutex_unlock (PC_GET_LOCK(w))) + +#define PC_GET_COND(w) (&w->priv->pc_cond) +#define PC_COND_WAIT(w) (g_cond_wait(PC_GET_COND(w), PC_GET_LOCK(w))) +#define PC_COND_BROADCAST(w) (g_cond_broadcast(PC_GET_COND(w))) +#define PC_COND_SIGNAL(w) (g_cond_signal(PC_GET_COND(w))) + +/* + * This webrtcbin implements the majority of the W3's peerconnection API and + * implementation guide where possible. Generating offers, answers and setting + * local and remote SDP's are all supported. To start with, only the media + * interface has been implemented (no datachannel yet). + * + * Each input/output pad is equivalent to a Track in W3 parlance which are + * added/removed from the bin. The number of requested sink pads is the number + * of streams that will be sent to the receiver and will be associated with a + * GstWebRTCRTPTransceiver (very similar to W3 RTPTransceiver's). + * + * On the receiving side, RTPTransceiver's are created in response to setting + * a remote description. Output pads for the receiving streams in the set + * description are also created. + */ + +/* + * TODO: + * assert sending payload type matches the stream + * reconfiguration (of anything) + * LS groups + * bundling + * setting custom DTLS certificates + * data channel + * + * seperate session id's from mlineindex properly + * how to deal with replacing a input/output track/stream + */ + +#define GST_CAT_DEFAULT gst_webrtc_bin_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +GQuark +gst_webrtc_bin_error_quark (void) +{ + return g_quark_from_static_string ("gst-webrtc-bin-error-quark"); +} + +G_DEFINE_TYPE (GstWebRTCBinPad, gst_webrtc_bin_pad, GST_TYPE_GHOST_PAD); + +static void +gst_webrtc_bin_pad_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_webrtc_bin_pad_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_webrtc_bin_pad_finalize (GObject * object) +{ + GstWebRTCBinPad *pad = GST_WEBRTC_BIN_PAD (object); + + if (pad->trans) + gst_object_unref (pad->trans); + pad->trans = NULL; + + G_OBJECT_CLASS (gst_webrtc_bin_pad_parent_class)->finalize (object); +} + +static void +gst_webrtc_bin_pad_class_init (GstWebRTCBinPadClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->get_property = gst_webrtc_bin_pad_get_property; + gobject_class->set_property = gst_webrtc_bin_pad_set_property; + gobject_class->finalize = gst_webrtc_bin_pad_finalize; +} + +static GstCaps * +_transport_stream_get_caps_for_pt (TransportStream * stream, guint pt) +{ + guint i, len; + + len = stream->ptmap->len; + for (i = 0; i < len; i++) { + PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, i); + if (item->pt == pt) + return item->caps; + } + return NULL; +} + +static void +gst_webrtc_bin_pad_init (GstWebRTCBinPad * pad) +{ +} + +static GstWebRTCBinPad * +gst_webrtc_bin_pad_new (const gchar * name, GstPadDirection direction) +{ + GstWebRTCBinPad *pad = + g_object_new (gst_webrtc_bin_pad_get_type (), "name", name, "direction", + direction, NULL); + + if (!gst_ghost_pad_construct (GST_GHOST_PAD (pad))) { + gst_object_unref (pad); + return NULL; + } + + GST_DEBUG_OBJECT (pad, "new visible pad with direction %s", + direction == GST_PAD_SRC ? "src" : "sink"); + return pad; +} + +#define gst_webrtc_bin_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstWebRTCBin, gst_webrtc_bin, GST_TYPE_BIN, + GST_DEBUG_CATEGORY_INIT (gst_webrtc_bin_debug, "webrtcbin", 0, + "webrtcbin element"); + ); + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink_%u", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS ("application/x-rtp")); + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src_%u", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS ("application/x-rtp")); + +enum +{ + SIGNAL_0, + CREATE_OFFER_SIGNAL, + CREATE_ANSWER_SIGNAL, + SET_LOCAL_DESCRIPTION_SIGNAL, + SET_REMOTE_DESCRIPTION_SIGNAL, + ADD_ICE_CANDIDATE_SIGNAL, + ON_NEGOTIATION_NEEDED_SIGNAL, + ON_ICE_CANDIDATE_SIGNAL, + GET_STATS_SIGNAL, + ADD_TRANSCEIVER_SIGNAL, + GET_TRANSCEIVERS_SIGNAL, + LAST_SIGNAL, +}; + +enum +{ + PROP_0, + PROP_CONNECTION_STATE, + PROP_SIGNALING_STATE, + PROP_ICE_GATHERING_STATE, + PROP_ICE_CONNECTION_STATE, + PROP_LOCAL_DESCRIPTION, + PROP_CURRENT_LOCAL_DESCRIPTION, + PROP_PENDING_LOCAL_DESCRIPTION, + PROP_REMOTE_DESCRIPTION, + PROP_CURRENT_REMOTE_DESCRIPTION, + PROP_PENDING_REMOTE_DESCRIPTION, + PROP_STUN_SERVER, + PROP_TURN_SERVER, +}; + +static guint gst_webrtc_bin_signals[LAST_SIGNAL] = { 0 }; + +static GstWebRTCDTLSTransport * +_transceiver_get_transport (GstWebRTCRTPTransceiver * trans) +{ + if (trans->sender) { + return trans->sender->transport; + } else if (trans->receiver) { + return trans->receiver->transport; + } + + return NULL; +} + +static GstWebRTCDTLSTransport * +_transceiver_get_rtcp_transport (GstWebRTCRTPTransceiver * trans) +{ + if (trans->sender) { + return trans->sender->rtcp_transport; + } else if (trans->receiver) { + return trans->receiver->rtcp_transport; + } + + return NULL; +} + +typedef struct +{ + guint session_id; + GstWebRTCICEStream *stream; +} IceStreamItem; + +/* FIXME: locking? */ +GstWebRTCICEStream * +_find_ice_stream_for_session (GstWebRTCBin * webrtc, guint session_id) +{ + int i; + + for (i = 0; i < webrtc->priv->ice_stream_map->len; i++) { + IceStreamItem *item = + &g_array_index (webrtc->priv->ice_stream_map, IceStreamItem, i); + + if (item->session_id == session_id) { + GST_TRACE_OBJECT (webrtc, "Found ice stream id %" GST_PTR_FORMAT " for " + "session %u", item->stream, session_id); + return item->stream; + } + } + + GST_TRACE_OBJECT (webrtc, "No ice stream available for session %u", + session_id); + return NULL; +} + +void +_add_ice_stream_item (GstWebRTCBin * webrtc, guint session_id, + GstWebRTCICEStream * stream) +{ + IceStreamItem item = { session_id, stream }; + + GST_TRACE_OBJECT (webrtc, "adding ice stream %" GST_PTR_FORMAT " for " + "session %u", stream, session_id); + g_array_append_val (webrtc->priv->ice_stream_map, item); +} + +typedef struct +{ + guint session_id; + gchar *mid; +} SessionMidItem; + +static void +clear_session_mid_item (SessionMidItem * item) +{ + g_free (item->mid); +} + +typedef gboolean (*FindTransceiverFunc) (GstWebRTCRTPTransceiver * p1, + gconstpointer data); + +static GstWebRTCRTPTransceiver * +_find_transceiver (GstWebRTCBin * webrtc, gconstpointer data, + FindTransceiverFunc func) +{ + int i; + + for (i = 0; i < webrtc->priv->transceivers->len; i++) { + GstWebRTCRTPTransceiver *transceiver = + g_array_index (webrtc->priv->transceivers, GstWebRTCRTPTransceiver *, + i); + + if (func (transceiver, data)) + return transceiver; + } + + return NULL; +} + +static gboolean +match_for_mid (GstWebRTCRTPTransceiver * trans, const gchar * mid) +{ + return g_strcmp0 (trans->mid, mid) == 0; +} + +static gboolean +transceiver_match_for_mline (GstWebRTCRTPTransceiver * trans, guint * mline) +{ + return trans->mline == *mline; +} + +static GstWebRTCRTPTransceiver * +_find_transceiver_for_mline (GstWebRTCBin * webrtc, guint mlineindex) +{ + GstWebRTCRTPTransceiver *trans; + + trans = _find_transceiver (webrtc, &mlineindex, + (FindTransceiverFunc) transceiver_match_for_mline); + + GST_TRACE_OBJECT (webrtc, + "Found transceiver %" GST_PTR_FORMAT " for mlineindex %u", trans, + mlineindex); + + return trans; +} + +typedef gboolean (*FindTransportFunc) (TransportStream * p1, + gconstpointer data); + +static TransportStream * +_find_transport (GstWebRTCBin * webrtc, gconstpointer data, + FindTransportFunc func) +{ + int i; + + for (i = 0; i < webrtc->priv->transports->len; i++) { + TransportStream *stream = + g_array_index (webrtc->priv->transports, TransportStream *, + i); + + if (func (stream, data)) + return stream; + } + + return NULL; +} + +static gboolean +match_stream_for_session (TransportStream * trans, guint * session) +{ + return trans->session_id == *session; +} + +static TransportStream * +_find_transport_for_session (GstWebRTCBin * webrtc, guint session_id) +{ + TransportStream *stream; + + stream = _find_transport (webrtc, &session_id, + (FindTransportFunc) match_stream_for_session); + + GST_TRACE_OBJECT (webrtc, + "Found transport %" GST_PTR_FORMAT " for session %u", stream, session_id); + + return stream; +} + +typedef gboolean (*FindPadFunc) (GstWebRTCBinPad * p1, gconstpointer data); + +static GstWebRTCBinPad * +_find_pad (GstWebRTCBin * webrtc, gconstpointer data, FindPadFunc func) +{ + GstElement *element = GST_ELEMENT (webrtc); + GList *l; + + GST_OBJECT_LOCK (webrtc); + l = element->pads; + for (; l; l = g_list_next (l)) { + if (!GST_IS_WEBRTC_BIN_PAD (l->data)) + continue; + if (func (l->data, data)) { + gst_object_ref (l->data); + GST_OBJECT_UNLOCK (webrtc); + return l->data; + } + } + + l = webrtc->priv->pending_pads; + for (; l; l = g_list_next (l)) { + if (!GST_IS_WEBRTC_BIN_PAD (l->data)) + continue; + if (func (l->data, data)) { + gst_object_ref (l->data); + GST_OBJECT_UNLOCK (webrtc); + return l->data; + } + } + GST_OBJECT_UNLOCK (webrtc); + + return NULL; +} + +static void +_add_pad_to_list (GstWebRTCBin * webrtc, GstWebRTCBinPad * pad) +{ + GST_OBJECT_LOCK (webrtc); + webrtc->priv->pending_pads = g_list_prepend (webrtc->priv->pending_pads, pad); + GST_OBJECT_UNLOCK (webrtc); +} + +static void +_remove_pending_pad (GstWebRTCBin * webrtc, GstWebRTCBinPad * pad) +{ + GST_OBJECT_LOCK (webrtc); + webrtc->priv->pending_pads = g_list_remove (webrtc->priv->pending_pads, pad); + GST_OBJECT_UNLOCK (webrtc); +} + +static void +_add_pad (GstWebRTCBin * webrtc, GstWebRTCBinPad * pad) +{ + _remove_pending_pad (webrtc, pad); + + if (webrtc->priv->running) + gst_pad_set_active (GST_PAD (pad), TRUE); + gst_element_add_pad (GST_ELEMENT (webrtc), GST_PAD (pad)); +} + +static void +_remove_pad (GstWebRTCBin * webrtc, GstWebRTCBinPad * pad) +{ + _remove_pending_pad (webrtc, pad); + + gst_element_remove_pad (GST_ELEMENT (webrtc), GST_PAD (pad)); +} + +typedef struct +{ + GstPadDirection direction; + guint mlineindex; +} MLineMatch; + +static gboolean +pad_match_for_mline (GstWebRTCBinPad * pad, const MLineMatch * match) +{ + return GST_PAD_DIRECTION (pad) == match->direction + && pad->mlineindex == match->mlineindex; +} + +static GstWebRTCBinPad * +_find_pad_for_mline (GstWebRTCBin * webrtc, GstPadDirection direction, + guint mlineindex) +{ + MLineMatch m = { direction, mlineindex }; + + return _find_pad (webrtc, &m, (FindPadFunc) pad_match_for_mline); +} + +typedef struct +{ + GstPadDirection direction; + GstWebRTCRTPTransceiver *trans; +} TransMatch; + +static gboolean +pad_match_for_transceiver (GstWebRTCBinPad * pad, TransMatch * m) +{ + return GST_PAD_DIRECTION (pad) == m->direction && pad->trans == m->trans; +} + +static GstWebRTCBinPad * +_find_pad_for_transceiver (GstWebRTCBin * webrtc, GstPadDirection direction, + GstWebRTCRTPTransceiver * trans) +{ + TransMatch m = { direction, trans }; + + return _find_pad (webrtc, &m, (FindPadFunc) pad_match_for_transceiver); +} + +#if 0 +static gboolean +match_for_ssrc (GstWebRTCBinPad * pad, guint * ssrc) +{ + return pad->ssrc == *ssrc; +} + +static gboolean +match_for_pad (GstWebRTCBinPad * pad, GstWebRTCBinPad * other) +{ + return pad == other; +} +#endif + +static gboolean +_unlock_pc_thread (GMutex * lock) +{ + g_mutex_unlock (lock); + return G_SOURCE_REMOVE; +} + +static gpointer +_gst_pc_thread (GstWebRTCBin * webrtc) +{ + PC_LOCK (webrtc); + webrtc->priv->main_context = g_main_context_new (); + webrtc->priv->loop = g_main_loop_new (webrtc->priv->main_context, FALSE); + + PC_COND_BROADCAST (webrtc); + g_main_context_invoke (webrtc->priv->main_context, + (GSourceFunc) _unlock_pc_thread, PC_GET_LOCK (webrtc)); + + /* Having the thread be the thread default GMainContext will break the + * required queue-like ordering (from W3's peerconnection spec) of re-entrant + * tasks */ + g_main_loop_run (webrtc->priv->loop); + + PC_LOCK (webrtc); + g_main_context_unref (webrtc->priv->main_context); + webrtc->priv->main_context = NULL; + g_main_loop_unref (webrtc->priv->loop); + webrtc->priv->loop = NULL; + PC_COND_BROADCAST (webrtc); + PC_UNLOCK (webrtc); + + return NULL; +} + +static void +_start_thread (GstWebRTCBin * webrtc) +{ + PC_LOCK (webrtc); + webrtc->priv->thread = g_thread_new ("gst-pc-ops", + (GThreadFunc) _gst_pc_thread, webrtc); + + while (!webrtc->priv->loop) + PC_COND_WAIT (webrtc); + webrtc->priv->is_closed = FALSE; + PC_UNLOCK (webrtc); +} + +static void +_stop_thread (GstWebRTCBin * webrtc) +{ + PC_LOCK (webrtc); + webrtc->priv->is_closed = TRUE; + g_main_loop_quit (webrtc->priv->loop); + while (webrtc->priv->loop) + PC_COND_WAIT (webrtc); + PC_UNLOCK (webrtc); + + g_thread_unref (webrtc->priv->thread); +} + +static gboolean +_execute_op (GstWebRTCBinTask * op) +{ + PC_LOCK (op->webrtc); + if (op->webrtc->priv->is_closed) { + GST_DEBUG_OBJECT (op->webrtc, + "Peerconnection is closed, aborting execution"); + goto out; + } + + op->op (op->webrtc, op->data); + +out: + PC_UNLOCK (op->webrtc); + return G_SOURCE_REMOVE; +} + +static void +_free_op (GstWebRTCBinTask * op) +{ + if (op->notify) + op->notify (op->data); + g_free (op); +} + +void +gst_webrtc_bin_enqueue_task (GstWebRTCBin * webrtc, GstWebRTCBinFunc func, + gpointer data, GDestroyNotify notify) +{ + GstWebRTCBinTask *op; + GSource *source; + + g_return_if_fail (GST_IS_WEBRTC_BIN (webrtc)); + + if (webrtc->priv->is_closed) { + GST_DEBUG_OBJECT (webrtc, "Peerconnection is closed, aborting execution"); + if (notify) + notify (data); + return; + } + op = g_new0 (GstWebRTCBinTask, 1); + op->webrtc = webrtc; + op->op = func; + op->data = data; + op->notify = notify; + + source = g_idle_source_new (); + g_source_set_priority (source, G_PRIORITY_DEFAULT); + g_source_set_callback (source, (GSourceFunc) _execute_op, op, + (GDestroyNotify) _free_op); + g_source_attach (source, webrtc->priv->main_context); + g_source_unref (source); +} + +/* https://www.w3.org/TR/webrtc/#dom-rtciceconnectionstate */ +static GstWebRTCICEConnectionState +_collate_ice_connection_states (GstWebRTCBin * webrtc) +{ +#define STATE(val) GST_WEBRTC_ICE_CONNECTION_STATE_ ## val + GstWebRTCICEConnectionState any_state = 0; + gboolean all_closed = TRUE; + int i; + + for (i = 0; i < webrtc->priv->transceivers->len; i++) { + GstWebRTCRTPTransceiver *rtp_trans = + g_array_index (webrtc->priv->transceivers, GstWebRTCRTPTransceiver *, + i); + WebRTCTransceiver *trans = WEBRTC_TRANSCEIVER (rtp_trans); + TransportStream *stream = trans->stream; + GstWebRTCICETransport *transport, *rtcp_transport; + GstWebRTCICEConnectionState ice_state; + gboolean rtcp_mux = FALSE; + + if (rtp_trans->stopped) + continue; + if (!rtp_trans->mid) + continue; + + g_object_get (stream, "rtcp-mux", &rtcp_mux, NULL); + + transport = _transceiver_get_transport (rtp_trans)->transport; + + /* get transport state */ + g_object_get (transport, "state", &ice_state, NULL); + any_state |= (1 << ice_state); + if (ice_state != STATE (CLOSED)) + all_closed = FALSE; + + rtcp_transport = _transceiver_get_rtcp_transport (rtp_trans)->transport; + + if (!rtcp_mux && rtcp_transport && transport != rtcp_transport) { + g_object_get (rtcp_transport, "state", &ice_state, NULL); + any_state |= (1 << ice_state); + if (ice_state != STATE (CLOSED)) + all_closed = FALSE; + } + } + + GST_TRACE_OBJECT (webrtc, "ICE connection state: 0x%x", any_state); + + if (webrtc->priv->is_closed) { + GST_TRACE_OBJECT (webrtc, "returning closed"); + return STATE (CLOSED); + } + /* Any of the RTCIceTransport s are in the failed state. */ + if (any_state & (1 << STATE (FAILED))) { + GST_TRACE_OBJECT (webrtc, "returning failed"); + return STATE (FAILED); + } + /* Any of the RTCIceTransport s are in the disconnected state and + * none of them are in the failed state. */ + if (any_state & (1 << STATE (DISCONNECTED))) { + GST_TRACE_OBJECT (webrtc, "returning disconnected"); + return STATE (DISCONNECTED); + } + /* Any of the RTCIceTransport's are in the checking state and none of them + * are in the failed or disconnected state. */ + if (any_state & (1 << STATE (CHECKING))) { + GST_TRACE_OBJECT (webrtc, "returning checking"); + return STATE (CHECKING); + } + /* Any of the RTCIceTransport s are in the new state and none of them are + * in the checking, failed or disconnected state, or all RTCIceTransport's + * are in the closed state. */ + if ((any_state & (1 << STATE (NEW))) || all_closed) { + GST_TRACE_OBJECT (webrtc, "returning new"); + return STATE (NEW); + } + /* All RTCIceTransport s are in the connected, completed or closed state + * and at least one of them is in the connected state. */ + if (any_state & (1 << STATE (CONNECTED) | 1 << STATE (COMPLETED) | 1 << + STATE (CLOSED)) && any_state & (1 << STATE (CONNECTED))) { + GST_TRACE_OBJECT (webrtc, "returning connected"); + return STATE (CONNECTED); + } + /* All RTCIceTransport s are in the completed or closed state and at least + * one of them is in the completed state. */ + if (any_state & (1 << STATE (COMPLETED) | 1 << STATE (CLOSED)) + && any_state & (1 << STATE (COMPLETED))) { + GST_TRACE_OBJECT (webrtc, "returning connected"); + return STATE (CONNECTED); + } + + GST_FIXME ("unspecified situation, returning new"); + return STATE (NEW); +#undef STATE +} + +/* https://www.w3.org/TR/webrtc/#dom-rtcicegatheringstate */ +static GstWebRTCICEGatheringState +_collate_ice_gathering_states (GstWebRTCBin * webrtc) +{ +#define STATE(val) GST_WEBRTC_ICE_GATHERING_STATE_ ## val + GstWebRTCICEGatheringState any_state = 0; + gboolean all_completed = webrtc->priv->transceivers->len > 0; + int i; + + for (i = 0; i < webrtc->priv->transceivers->len; i++) { + GstWebRTCRTPTransceiver *rtp_trans = + g_array_index (webrtc->priv->transceivers, GstWebRTCRTPTransceiver *, + i); + WebRTCTransceiver *trans = WEBRTC_TRANSCEIVER (rtp_trans); + TransportStream *stream = trans->stream; + GstWebRTCICETransport *transport, *rtcp_transport; + GstWebRTCICEGatheringState ice_state; + gboolean rtcp_mux = FALSE; + + if (rtp_trans->stopped) + continue; + if (!rtp_trans->mid) + continue; + + g_object_get (stream, "rtcp-mux", &rtcp_mux, NULL); + + transport = _transceiver_get_transport (rtp_trans)->transport; + + /* get gathering state */ + g_object_get (transport, "gathering-state", &ice_state, NULL); + any_state |= (1 << ice_state); + if (ice_state != STATE (COMPLETE)) + all_completed = FALSE; + + rtcp_transport = _transceiver_get_rtcp_transport (rtp_trans)->transport; + + if (!rtcp_mux && rtcp_transport && rtcp_transport != transport) { + g_object_get (rtcp_transport, "gathering-state", &ice_state, NULL); + any_state |= (1 << ice_state); + if (ice_state != STATE (COMPLETE)) + all_completed = FALSE; + } + } + + GST_TRACE_OBJECT (webrtc, "ICE gathering state: 0x%x", any_state); + + /* Any of the RTCIceTransport s are in the gathering state. */ + if (any_state & (1 << STATE (GATHERING))) { + GST_TRACE_OBJECT (webrtc, "returning gathering"); + return STATE (GATHERING); + } + /* At least one RTCIceTransport exists, and all RTCIceTransport s are in + * the completed gathering state. */ + if (all_completed) { + GST_TRACE_OBJECT (webrtc, "returning complete"); + return STATE (COMPLETE); + } + + /* Any of the RTCIceTransport s are in the new gathering state and none + * of the transports are in the gathering state, or there are no transports. */ + GST_TRACE_OBJECT (webrtc, "returning new"); + return STATE (NEW); +#undef STATE +} + +/* https://www.w3.org/TR/webrtc/#rtcpeerconnectionstate-enum */ +static GstWebRTCPeerConnectionState +_collate_peer_connection_states (GstWebRTCBin * webrtc) +{ +#define STATE(v) GST_WEBRTC_PEER_CONNECTION_STATE_ ## v +#define ICE_STATE(v) GST_WEBRTC_ICE_CONNECTION_STATE_ ## v +#define DTLS_STATE(v) GST_WEBRTC_DTLS_TRANSPORT_STATE_ ## v + GstWebRTCICEConnectionState any_ice_state = 0; + GstWebRTCDTLSTransportState any_dtls_state = 0; + int i; + + for (i = 0; i < webrtc->priv->transceivers->len; i++) { + GstWebRTCRTPTransceiver *rtp_trans = + g_array_index (webrtc->priv->transceivers, GstWebRTCRTPTransceiver *, + i); + WebRTCTransceiver *trans = WEBRTC_TRANSCEIVER (rtp_trans); + TransportStream *stream = trans->stream; + GstWebRTCDTLSTransport *transport, *rtcp_transport; + GstWebRTCICEGatheringState ice_state; + GstWebRTCDTLSTransportState dtls_state; + gboolean rtcp_mux = FALSE; + + if (rtp_trans->stopped) + continue; + if (!rtp_trans->mid) + continue; + + g_object_get (stream, "rtcp-mux", &rtcp_mux, NULL); + transport = _transceiver_get_transport (rtp_trans); + + /* get transport state */ + g_object_get (transport, "state", &dtls_state, NULL); + any_dtls_state |= (1 << dtls_state); + g_object_get (transport->transport, "state", &ice_state, NULL); + any_ice_state |= (1 << ice_state); + + rtcp_transport = _transceiver_get_rtcp_transport (rtp_trans); + + if (!rtcp_mux && rtcp_transport && rtcp_transport != transport) { + g_object_get (rtcp_transport, "state", &dtls_state, NULL); + any_dtls_state |= (1 << dtls_state); + g_object_get (rtcp_transport->transport, "state", &ice_state, NULL); + any_ice_state |= (1 << ice_state); + } + } + + GST_TRACE_OBJECT (webrtc, "ICE connection state: 0x%x. DTLS connection " + "state: 0x%x", any_ice_state, any_dtls_state); + + /* The RTCPeerConnection object's [[ isClosed]] slot is true. */ + if (webrtc->priv->is_closed) { + GST_TRACE_OBJECT (webrtc, "returning closed"); + return STATE (CLOSED); + } + + /* Any of the RTCIceTransport s or RTCDtlsTransport s are in a failed state. */ + if (any_ice_state & (1 << ICE_STATE (FAILED))) { + GST_TRACE_OBJECT (webrtc, "returning failed"); + return STATE (FAILED); + } + if (any_dtls_state & (1 << DTLS_STATE (FAILED))) { + GST_TRACE_OBJECT (webrtc, "returning failed"); + return STATE (FAILED); + } + + /* Any of the RTCIceTransport's or RTCDtlsTransport's are in the connecting + * or checking state and none of them is in the failed state. */ + if (any_ice_state & (1 << ICE_STATE (CHECKING))) { + GST_TRACE_OBJECT (webrtc, "returning connecting"); + return STATE (CONNECTING); + } + if (any_dtls_state & (1 << DTLS_STATE (CONNECTING))) { + GST_TRACE_OBJECT (webrtc, "returning connecting"); + return STATE (CONNECTING); + } + + /* Any of the RTCIceTransport's or RTCDtlsTransport's are in the disconnected + * state and none of them are in the failed or connecting or checking state. */ + if (any_ice_state & (1 << ICE_STATE (DISCONNECTED))) { + GST_TRACE_OBJECT (webrtc, "returning disconnected"); + return STATE (DISCONNECTED); + } + + /* All RTCIceTransport's and RTCDtlsTransport's are in the connected, + * completed or closed state and at least of them is in the connected or + * completed state. */ + if (!(any_ice_state & ~(1 << ICE_STATE (CONNECTED) | 1 << + ICE_STATE (COMPLETED) | 1 << ICE_STATE (CLOSED))) + && !(any_dtls_state & ~(1 << DTLS_STATE (CONNECTED) | 1 << + DTLS_STATE (CLOSED))) + && (any_ice_state & (1 << ICE_STATE (CONNECTED) | 1 << + ICE_STATE (COMPLETED)) + || any_dtls_state & (1 << DTLS_STATE (CONNECTED)))) { + GST_TRACE_OBJECT (webrtc, "returning connected"); + return STATE (CONNECTED); + } + + /* Any of the RTCIceTransport's or RTCDtlsTransport's are in the new state + * and none of the transports are in the connecting, checking, failed or + * disconnected state, or all transports are in the closed state. */ + if (!(any_ice_state & ~(1 << ICE_STATE (CLOSED)))) { + GST_TRACE_OBJECT (webrtc, "returning new"); + return STATE (NEW); + } + if ((any_ice_state & (1 << ICE_STATE (NEW)) + || any_dtls_state & (1 << DTLS_STATE (NEW))) + && !(any_ice_state & (1 << ICE_STATE (CHECKING) | 1 << ICE_STATE (FAILED) + | (1 << ICE_STATE (DISCONNECTED)))) + && !(any_dtls_state & (1 << DTLS_STATE (CONNECTING) | 1 << + DTLS_STATE (FAILED)))) { + GST_TRACE_OBJECT (webrtc, "returning new"); + return STATE (NEW); + } + + GST_FIXME_OBJECT (webrtc, "Undefined situation detected, returning new"); + return STATE (NEW); +#undef DTLS_STATE +#undef ICE_STATE +#undef STATE +} + +static void +_update_ice_gathering_state_task (GstWebRTCBin * webrtc, gpointer data) +{ + GstWebRTCICEGatheringState old_state = webrtc->ice_gathering_state; + GstWebRTCICEGatheringState new_state; + + new_state = _collate_ice_gathering_states (webrtc); + + if (new_state != webrtc->ice_gathering_state) { + gchar *old_s, *new_s; + + old_s = _enum_value_to_string (GST_TYPE_WEBRTC_ICE_GATHERING_STATE, + old_state); + new_s = _enum_value_to_string (GST_TYPE_WEBRTC_ICE_GATHERING_STATE, + new_state); + GST_INFO_OBJECT (webrtc, "ICE gathering state change from %s(%u) to %s(%u)", + old_s, old_state, new_s, new_state); + g_free (old_s); + g_free (new_s); + + webrtc->ice_gathering_state = new_state; + PC_UNLOCK (webrtc); + g_object_notify (G_OBJECT (webrtc), "ice-gathering-state"); + PC_LOCK (webrtc); + } +} + +static void +_update_ice_gathering_state (GstWebRTCBin * webrtc) +{ + gst_webrtc_bin_enqueue_task (webrtc, _update_ice_gathering_state_task, NULL, + NULL); +} + +static void +_update_ice_connection_state_task (GstWebRTCBin * webrtc, gpointer data) +{ + GstWebRTCICEConnectionState old_state = webrtc->ice_connection_state; + GstWebRTCICEConnectionState new_state; + + new_state = _collate_ice_connection_states (webrtc); + + if (new_state != old_state) { + gchar *old_s, *new_s; + + old_s = _enum_value_to_string (GST_TYPE_WEBRTC_ICE_CONNECTION_STATE, + old_state); + new_s = _enum_value_to_string (GST_TYPE_WEBRTC_ICE_CONNECTION_STATE, + new_state); + GST_INFO_OBJECT (webrtc, + "ICE connection state change from %s(%u) to %s(%u)", old_s, old_state, + new_s, new_state); + g_free (old_s); + g_free (new_s); + + webrtc->ice_connection_state = new_state; + PC_UNLOCK (webrtc); + g_object_notify (G_OBJECT (webrtc), "ice-connection-state"); + PC_LOCK (webrtc); + } +} + +static void +_update_ice_connection_state (GstWebRTCBin * webrtc) +{ + gst_webrtc_bin_enqueue_task (webrtc, _update_ice_connection_state_task, NULL, + NULL); +} + +static void +_update_peer_connection_state_task (GstWebRTCBin * webrtc, gpointer data) +{ + GstWebRTCPeerConnectionState old_state = webrtc->peer_connection_state; + GstWebRTCPeerConnectionState new_state; + + new_state = _collate_peer_connection_states (webrtc); + + if (new_state != old_state) { + gchar *old_s, *new_s; + + old_s = _enum_value_to_string (GST_TYPE_WEBRTC_PEER_CONNECTION_STATE, + old_state); + new_s = _enum_value_to_string (GST_TYPE_WEBRTC_PEER_CONNECTION_STATE, + new_state); + GST_INFO_OBJECT (webrtc, + "Peer connection state change from %s(%u) to %s(%u)", old_s, old_state, + new_s, new_state); + g_free (old_s); + g_free (new_s); + + webrtc->peer_connection_state = new_state; + PC_UNLOCK (webrtc); + g_object_notify (G_OBJECT (webrtc), "connection-state"); + PC_LOCK (webrtc); + } +} + +static void +_update_peer_connection_state (GstWebRTCBin * webrtc) +{ + gst_webrtc_bin_enqueue_task (webrtc, _update_peer_connection_state_task, + NULL, NULL); +} + +/* http://w3c.github.io/webrtc-pc/#dfn-check-if-negotiation-is-needed */ +static gboolean +_check_if_negotiation_is_needed (GstWebRTCBin * webrtc) +{ + int i; + + GST_LOG_OBJECT (webrtc, "checking if negotiation is needed"); + + /* If any implementation-specific negotiation is required, as described at + * the start of this section, return "true". + * FIXME */ + /* FIXME: emit when input caps/format changes? */ + + /* If connection has created any RTCDataChannel's, and no m= section has + * been negotiated yet for data, return "true". + * FIXME */ + + if (!webrtc->current_local_description) { + GST_LOG_OBJECT (webrtc, "no local description set"); + return TRUE; + } + + if (!webrtc->current_remote_description) { + GST_LOG_OBJECT (webrtc, "no remote description set"); + return TRUE; + } + + for (i = 0; i < webrtc->priv->transceivers->len; i++) { + GstWebRTCRTPTransceiver *trans; + + trans = + g_array_index (webrtc->priv->transceivers, GstWebRTCRTPTransceiver *, + i); + + if (trans->stopped) { + /* FIXME: If t is stopped and is associated with an m= section according to + * [JSEP] (section 3.4.1.), but the associated m= section is not yet + * rejected in connection's currentLocalDescription or + * currentRemoteDescription , return "true". */ + GST_FIXME_OBJECT (webrtc, + "check if the transceiver is rejected in descriptions"); + } else { + const GstSDPMedia *media; + GstWebRTCRTPTransceiverDirection local_dir, remote_dir; + + if (trans->mline == -1) { + GST_LOG_OBJECT (webrtc, "unassociated transceiver %i %" GST_PTR_FORMAT, + i, trans); + return TRUE; + } + /* internal inconsistency */ + g_assert (trans->mline < + gst_sdp_message_medias_len (webrtc->current_local_description->sdp)); + g_assert (trans->mline < + gst_sdp_message_medias_len (webrtc->current_remote_description->sdp)); + + /* FIXME: msid handling + * If t's direction is "sendrecv" or "sendonly", and the associated m= + * section in connection's currentLocalDescription doesn't contain an + * "a=msid" line, return "true". */ + + media = + gst_sdp_message_get_media (webrtc->current_local_description->sdp, + trans->mline); + local_dir = _get_direction_from_media (media); + + media = + gst_sdp_message_get_media (webrtc->current_remote_description->sdp, + trans->mline); + remote_dir = _get_direction_from_media (media); + + if (webrtc->current_local_description->type == GST_WEBRTC_SDP_TYPE_OFFER) { + /* If connection's currentLocalDescription if of type "offer", and + * the direction of the associated m= section in neither the offer + * nor answer matches t's direction, return "true". */ + + if (local_dir != trans->direction && remote_dir != trans->direction) { + GST_LOG_OBJECT (webrtc, + "transceiver direction doesn't match description"); + return TRUE; + } + } else if (webrtc->current_local_description->type == + GST_WEBRTC_SDP_TYPE_ANSWER) { + GstWebRTCRTPTransceiverDirection intersect_dir; + + /* If connection's currentLocalDescription if of type "answer", and + * the direction of the associated m= section in the answer does not + * match t's direction intersected with the offered direction (as + * described in [JSEP] (section 5.3.1.)), return "true". */ + + /* remote is the offer, local is the answer */ + intersect_dir = _intersect_answer_directions (remote_dir, local_dir); + + if (intersect_dir != trans->direction) { + GST_LOG_OBJECT (webrtc, + "transceiver direction doesn't match description"); + return TRUE; + } + } + } + } + + GST_LOG_OBJECT (webrtc, "no negotiation needed"); + return FALSE; +} + +static void +_check_need_negotiation_task (GstWebRTCBin * webrtc, gpointer unused) +{ + if (webrtc->priv->need_negotiation) { + GST_TRACE_OBJECT (webrtc, "emitting on-negotiation-needed"); + PC_UNLOCK (webrtc); + g_signal_emit (webrtc, gst_webrtc_bin_signals[ON_NEGOTIATION_NEEDED_SIGNAL], + 0); + PC_LOCK (webrtc); + } +} + +/* http://w3c.github.io/webrtc-pc/#dfn-update-the-negotiation-needed-flag */ +static void +_update_need_negotiation (GstWebRTCBin * webrtc) +{ + /* If connection's [[isClosed]] slot is true, abort these steps. */ + if (webrtc->priv->is_closed) + return; + /* If connection's signaling state is not "stable", abort these steps. */ + if (webrtc->signaling_state != GST_WEBRTC_SIGNALING_STATE_STABLE) + return; + + /* If the result of checking if negotiation is needed is "false", clear the + * negotiation-needed flag by setting connection's [[ needNegotiation]] slot + * to false, and abort these steps. */ + if (!_check_if_negotiation_is_needed (webrtc)) { + webrtc->priv->need_negotiation = FALSE; + return; + } + /* If connection's [[needNegotiation]] slot is already true, abort these steps. */ + if (webrtc->priv->need_negotiation) + return; + /* Set connection's [[needNegotiation]] slot to true. */ + webrtc->priv->need_negotiation = TRUE; + /* Queue a task to check connection's [[ needNegotiation]] slot and, if still + * true, fire a simple event named negotiationneeded at connection. */ + gst_webrtc_bin_enqueue_task (webrtc, _check_need_negotiation_task, NULL, + NULL); +} + +static GstCaps * +_find_codec_preferences (GstWebRTCBin * webrtc, GstWebRTCRTPTransceiver * trans, + GstPadDirection direction, guint media_idx) +{ + GstCaps *ret = NULL; + + GST_LOG_OBJECT (webrtc, "retreiving codec preferences from %" GST_PTR_FORMAT, + trans); + + if (trans->codec_preferences) { + GST_LOG_OBJECT (webrtc, "Using codec preferences: %" GST_PTR_FORMAT, + trans->codec_preferences); + ret = gst_caps_ref (trans->codec_preferences); + } else { + GstWebRTCBinPad *pad = _find_pad_for_mline (webrtc, direction, media_idx); + if (pad) { + GstCaps *caps = gst_pad_get_current_caps (GST_PAD (pad)); + if (caps) { + GST_LOG_OBJECT (webrtc, "Using current pad caps: %" GST_PTR_FORMAT, + caps); + } else { + if ((caps = gst_pad_peer_query_caps (GST_PAD (pad), NULL))) + GST_LOG_OBJECT (webrtc, "Using peer query caps: %" GST_PTR_FORMAT, + caps); + } + if (caps) + ret = caps; + gst_object_unref (pad); + } + } + + return ret; +} + +static GstCaps * +_add_supported_attributes_to_caps (const GstCaps * caps) +{ + GstCaps *ret; + int i; + + ret = gst_caps_make_writable (caps); + + for (i = 0; i < gst_caps_get_size (ret); i++) { + GstStructure *s = gst_caps_get_structure (ret, i); + + if (!gst_structure_has_field (s, "rtcp-fb-nack")) + gst_structure_set (s, "rtcp-fb-nack", G_TYPE_BOOLEAN, TRUE, NULL); + if (!gst_structure_has_field (s, "rtcp-fb-nack-pli")) + gst_structure_set (s, "rtcp-fb-nack-pli", G_TYPE_BOOLEAN, TRUE, NULL); + /* FIXME: is this needed? */ + /*if (!gst_structure_has_field (s, "rtcp-fb-transport-cc")) + gst_structure_set (s, "rtcp-fb-nack-pli", G_TYPE_BOOLEAN, TRUE, NULL); */ + + /* FIXME: codec-specific paramters? */ + } + + return ret; +} + +static void +_on_ice_transport_notify_state (GstWebRTCICETransport * transport, + GParamSpec * pspec, GstWebRTCBin * webrtc) +{ + _update_ice_connection_state (webrtc); + _update_peer_connection_state (webrtc); +} + +static void +_on_ice_transport_notify_gathering_state (GstWebRTCICETransport * transport, + GParamSpec * pspec, GstWebRTCBin * webrtc) +{ + _update_ice_gathering_state (webrtc); +} + +static void +_on_dtls_transport_notify_state (GstWebRTCDTLSTransport * transport, + GParamSpec * pspec, GstWebRTCBin * webrtc) +{ + _update_peer_connection_state (webrtc); +} + +static WebRTCTransceiver * +_create_webrtc_transceiver (GstWebRTCBin * webrtc) +{ + WebRTCTransceiver *trans; + GstWebRTCRTPTransceiver *rtp_trans; + GstWebRTCRTPSender *sender; + GstWebRTCRTPReceiver *receiver; + + sender = gst_webrtc_rtp_sender_new (NULL); + receiver = gst_webrtc_rtp_receiver_new (); + trans = webrtc_transceiver_new (webrtc, sender, receiver); + rtp_trans = GST_WEBRTC_RTP_TRANSCEIVER (trans); + rtp_trans->direction = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV; + rtp_trans->mline = -1; + + g_array_append_val (webrtc->priv->transceivers, trans); + + gst_object_unref (sender); + gst_object_unref (receiver); + + return trans; +} + +static TransportStream * +_create_transport_channel (GstWebRTCBin * webrtc, guint session_id) +{ + GstWebRTCDTLSTransport *transport; + TransportStream *ret; + gchar *pad_name; + + /* FIXME: how to parametrize the sender and the receiver */ + ret = transport_stream_new (webrtc, session_id); + transport = ret->transport; + + g_signal_connect (G_OBJECT (transport->transport), "notify::state", + G_CALLBACK (_on_ice_transport_notify_state), webrtc); + g_signal_connect (G_OBJECT (transport->transport), + "notify::gathering-state", + G_CALLBACK (_on_ice_transport_notify_gathering_state), webrtc); + g_signal_connect (G_OBJECT (transport), "notify::state", + G_CALLBACK (_on_dtls_transport_notify_state), webrtc); + + if ((transport = ret->rtcp_transport)) { + g_signal_connect (G_OBJECT (transport->transport), + "notify::state", G_CALLBACK (_on_ice_transport_notify_state), webrtc); + g_signal_connect (G_OBJECT (transport->transport), + "notify::gathering-state", + G_CALLBACK (_on_ice_transport_notify_gathering_state), webrtc); + g_signal_connect (G_OBJECT (transport), "notify::state", + G_CALLBACK (_on_dtls_transport_notify_state), webrtc); + } + + gst_bin_add (GST_BIN (webrtc), GST_ELEMENT (ret->send_bin)); + gst_bin_add (GST_BIN (webrtc), GST_ELEMENT (ret->receive_bin)); + + pad_name = g_strdup_printf ("recv_rtcp_sink_%u", ret->session_id); + if (!gst_element_link_pads (GST_ELEMENT (ret->receive_bin), "rtcp_src", + GST_ELEMENT (webrtc->rtpbin), pad_name)) + g_warn_if_reached (); + g_free (pad_name); + + pad_name = g_strdup_printf ("send_rtcp_src_%u", ret->session_id); + if (!gst_element_link_pads (GST_ELEMENT (webrtc->rtpbin), pad_name, + GST_ELEMENT (ret->send_bin), "rtcp_sink")) + g_warn_if_reached (); + g_free (pad_name); + + g_array_append_val (webrtc->priv->transports, ret); + + GST_TRACE_OBJECT (webrtc, + "Create transport %" GST_PTR_FORMAT " for session %u", ret, session_id); + + gst_element_sync_state_with_parent (GST_ELEMENT (ret->send_bin)); + gst_element_sync_state_with_parent (GST_ELEMENT (ret->receive_bin)); + + return ret; +} + +/* based off https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-18#section-5.2.1 */ +static gboolean +sdp_media_from_transceiver (GstWebRTCBin * webrtc, GstSDPMedia * media, + GstWebRTCRTPTransceiver * trans, GstWebRTCSDPType type, guint media_idx) +{ + /* TODO: + * rtp header extensions + * ice attributes + * rtx + * fec + * msid-semantics + * msid + * dtls fingerprints + * multiple dtls fingerprints https://tools.ietf.org/html/draft-ietf-mmusic-4572-update-05 + */ + gchar *direction, *sdp_mid; + GstCaps *caps; + int i; + + /* "An m= section is generated for each RtpTransceiver that has been added + * to the Bin, excluding any stopped RtpTransceivers." */ + if (trans->stopped) + return FALSE; + if (trans->direction == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE + || trans->direction == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE) + return FALSE; + + gst_sdp_media_set_port_info (media, 9, 0); + gst_sdp_media_set_proto (media, "UDP/TLS/RTP/SAVPF"); + gst_sdp_media_add_connection (media, "IN", "IP4", "0.0.0.0", 0, 0); + + direction = + _enum_value_to_string (GST_TYPE_WEBRTC_RTP_TRANSCEIVER_DIRECTION, + trans->direction); + gst_sdp_media_add_attribute (media, direction, ""); + g_free (direction); + /* FIXME: negotiate this */ + gst_sdp_media_add_attribute (media, "rtcp-mux", ""); + gst_sdp_media_add_attribute (media, "rtcp-rsize", NULL); + + if (type == GST_WEBRTC_SDP_TYPE_OFFER) { + caps = _find_codec_preferences (webrtc, trans, GST_PAD_SINK, media_idx); + caps = _add_supported_attributes_to_caps (caps); + } else if (type == GST_WEBRTC_SDP_TYPE_ANSWER) { + caps = _find_codec_preferences (webrtc, trans, GST_PAD_SRC, media_idx); + /* FIXME: add rtcp-fb paramaters */ + } else { + g_assert_not_reached (); + } + + if (!caps || gst_caps_is_empty (caps) || gst_caps_is_any (caps)) { + GST_WARNING_OBJECT (webrtc, "no caps available for transceiver, skipping"); + if (caps) + gst_caps_unref (caps); + return FALSE; + } + + for (i = 0; i < gst_caps_get_size (caps); i++) { + GstCaps *format = gst_caps_new_empty (); + const GstStructure *s = gst_caps_get_structure (caps, i); + + gst_caps_append_structure (format, gst_structure_copy (s)); + + GST_DEBUG_OBJECT (webrtc, "Adding %u-th caps %" GST_PTR_FORMAT + " to %u-th media", i, format, media_idx); + + /* this only looks at the first structure so we loop over the given caps + * and add each structure inside it piecemeal */ + gst_sdp_media_set_media_from_caps (format, media); + + gst_caps_unref (format); + } + + /* Some identifier; we also add the media name to it so it's identifiable */ + sdp_mid = g_strdup_printf ("%s%u", gst_sdp_media_get_media (media), + webrtc->priv->media_counter++); + gst_sdp_media_add_attribute (media, "mid", sdp_mid); + g_free (sdp_mid); + + if (trans->sender) { + gchar *cert, *fingerprint, *val; + + if (!trans->sender->transport) { + TransportStream *item; + /* FIXME: bundle */ + item = _find_transport_for_session (webrtc, media_idx); + if (!item) + item = _create_transport_channel (webrtc, media_idx); + webrtc_transceiver_set_transport (WEBRTC_TRANSCEIVER (trans), item); + } + + g_object_get (trans->sender->transport, "certificate", &cert, NULL); + + fingerprint = + _generate_fingerprint_from_certificate (cert, G_CHECKSUM_SHA256); + g_free (cert); + val = + g_strdup_printf ("%s %s", + _g_checksum_to_webrtc_string (G_CHECKSUM_SHA256), fingerprint); + g_free (fingerprint); + + gst_sdp_media_add_attribute (media, "fingerprint", val); + g_free (val); + } + + gst_caps_unref (caps); + + return TRUE; +} + +static GstSDPMessage * +_create_offer_task (GstWebRTCBin * webrtc, const GstStructure * options) +{ + GstSDPMessage *ret; + int i; + + gst_sdp_message_new (&ret); + + gst_sdp_message_set_version (ret, "0"); + { + /* FIXME: session id and version need special handling depending on the state we're in */ + gchar *sess_id = g_strdup_printf ("%" G_GUINT64_FORMAT, RANDOM_SESSION_ID); + gst_sdp_message_set_origin (ret, "-", sess_id, "0", "IN", "IP4", "0.0.0.0"); + g_free (sess_id); + } + gst_sdp_message_set_session_name (ret, "-"); + gst_sdp_message_add_time (ret, "0", "0", NULL); + gst_sdp_message_add_attribute (ret, "ice-options", "trickle"); + + /* for each rtp transceiver */ + for (i = 0; i < webrtc->priv->transceivers->len; i++) { + GstWebRTCRTPTransceiver *trans; + GstSDPMedia media = { 0, }; + gchar *ufrag, *pwd; + + trans = + g_array_index (webrtc->priv->transceivers, GstWebRTCRTPTransceiver *, + i); + + gst_sdp_media_init (&media); + /* mandated by JSEP */ + gst_sdp_media_add_attribute (&media, "setup", "actpass"); + + /* FIXME: only needed when restarting ICE */ + _generate_ice_credentials (&ufrag, &pwd); + gst_sdp_media_add_attribute (&media, "ice-ufrag", ufrag); + gst_sdp_media_add_attribute (&media, "ice-pwd", pwd); + g_free (ufrag); + g_free (pwd); + + if (sdp_media_from_transceiver (webrtc, &media, trans, + GST_WEBRTC_SDP_TYPE_OFFER, i)) + gst_sdp_message_add_media (ret, &media); + else + gst_sdp_media_uninit (&media); + } + + /* FIXME: pre-emptively setup receiving elements when needed */ + + /* XXX: only true for the initial offerer */ + g_object_set (webrtc->priv->ice, "controller", TRUE, NULL); + + return ret; +} + +static GstSDPMessage * +_create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options) +{ + GstSDPMessage *ret = NULL; + const GstWebRTCSessionDescription *pending_remote = + webrtc->pending_remote_description; + int i; + + if (!webrtc->pending_remote_description) { + GST_ERROR_OBJECT (webrtc, + "Asked to create an answer without a remote description"); + return NULL; + } + + gst_sdp_message_new (&ret); + + /* FIXME: session id and version need special handling depending on the state we're in */ + gst_sdp_message_set_version (ret, "0"); + { + const GstSDPOrigin *offer_origin = + gst_sdp_message_get_origin (pending_remote->sdp); + gst_sdp_message_set_origin (ret, "-", offer_origin->sess_id, "0", "IN", + "IP4", "0.0.0.0"); + } + gst_sdp_message_set_session_name (ret, "-"); + + for (i = 0; i < gst_sdp_message_attributes_len (pending_remote->sdp); i++) { + const GstSDPAttribute *attr = + gst_sdp_message_get_attribute (pending_remote->sdp, i); + + if (g_strcmp0 (attr->key, "ice-options") == 0) { + gst_sdp_message_add_attribute (ret, attr->key, attr->value); + } + } + + for (i = 0; i < gst_sdp_message_medias_len (pending_remote->sdp); i++) { + /* FIXME: + * bundle policy + */ + GstSDPMedia *media = NULL; + GstSDPMedia *offer_media; + GstWebRTCRTPTransceiver *rtp_trans = NULL; + WebRTCTransceiver *trans = NULL; + GstWebRTCRTPTransceiverDirection offer_dir, answer_dir; + GstWebRTCDTLSSetup offer_setup, answer_setup; + GstCaps *offer_caps, *answer_caps = NULL; + gchar *cert; + int j; + + gst_sdp_media_new (&media); + gst_sdp_media_set_port_info (media, 9, 0); + gst_sdp_media_set_proto (media, "UDP/TLS/RTP/SAVPF"); + gst_sdp_media_add_connection (media, "IN", "IP4", "0.0.0.0", 0, 0); + + { + /* FIXME: only needed when restarting ICE */ + gchar *ufrag, *pwd; + _generate_ice_credentials (&ufrag, &pwd); + gst_sdp_media_add_attribute (media, "ice-ufrag", ufrag); + gst_sdp_media_add_attribute (media, "ice-pwd", pwd); + g_free (ufrag); + g_free (pwd); + } + + offer_media = + (GstSDPMedia *) gst_sdp_message_get_media (pending_remote->sdp, i); + for (j = 0; j < gst_sdp_media_attributes_len (offer_media); j++) { + const GstSDPAttribute *attr = + gst_sdp_media_get_attribute (offer_media, j); + + if (g_strcmp0 (attr->key, "mid") == 0 + || g_strcmp0 (attr->key, "rtcp-mux") == 0) { + gst_sdp_media_add_attribute (media, attr->key, attr->value); + /* FIXME: handle anything we want to keep */ + } + } + + offer_caps = gst_caps_new_empty (); + for (j = 0; j < gst_sdp_media_formats_len (offer_media); j++) { + guint pt = atoi (gst_sdp_media_get_format (offer_media, j)); + GstCaps *caps; + int k; + + caps = gst_sdp_media_get_caps_from_media (offer_media, pt); + + /* gst_sdp_media_get_caps_from_media() produces caps with name + * "application/x-unknown" which will fail intersection with + * "application/x-rtp" caps so mangle the returns caps to have the + * correct name here */ + for (k = 0; k < gst_caps_get_size (caps); k++) { + GstStructure *s = gst_caps_get_structure (caps, k); + gst_structure_set_name (s, "application/x-rtp"); + } + + gst_caps_append (offer_caps, caps); + } + + for (j = 0; j < webrtc->priv->transceivers->len; j++) { + GstCaps *trans_caps; + + rtp_trans = + g_array_index (webrtc->priv->transceivers, GstWebRTCRTPTransceiver *, + j); + trans_caps = _find_codec_preferences (webrtc, rtp_trans, GST_PAD_SINK, i); + + GST_TRACE_OBJECT (webrtc, "trying to compare %" GST_PTR_FORMAT + " and %" GST_PTR_FORMAT, offer_caps, trans_caps); + + /* FIXME: technically this is a little overreaching as some fields we + * we can deal with not having and/or we may have unrecognized fields + * that we cannot actually support */ + if (trans_caps) { + answer_caps = gst_caps_intersect (offer_caps, trans_caps); + if (answer_caps && !gst_caps_is_empty (answer_caps)) { + GST_LOG_OBJECT (webrtc, + "found compatible transceiver %" GST_PTR_FORMAT + " for offer media %u", trans, i); + if (trans_caps) + gst_caps_unref (trans_caps); + break; + } else { + if (answer_caps) { + gst_caps_unref (answer_caps); + answer_caps = NULL; + } + if (trans_caps) + gst_caps_unref (trans_caps); + rtp_trans = NULL; + } + } else { + rtp_trans = NULL; + } + } + + if (rtp_trans) { + answer_dir = rtp_trans->direction; + if (!answer_caps) + goto rejected; + } else { + /* if no transceiver, then we only receive that stream and respond with + * the exact same caps */ + /* FIXME: how to validate that subsequent elements can actually receive + * this payload/format */ + answer_dir = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY; + answer_caps = gst_caps_ref (offer_caps); + } + /* respond with the requested caps */ + if (answer_caps) { + gst_sdp_media_set_media_from_caps (answer_caps, media); + gst_caps_unref (answer_caps); + answer_caps = NULL; + } + if (!rtp_trans) { + trans = _create_webrtc_transceiver (webrtc); + rtp_trans = GST_WEBRTC_RTP_TRANSCEIVER (trans); + rtp_trans->direction = answer_dir; + rtp_trans->mline = i; + } else { + trans = WEBRTC_TRANSCEIVER (rtp_trans); + } + + /* set the new media direction */ + offer_dir = _get_direction_from_media (offer_media); + answer_dir = _intersect_answer_directions (offer_dir, answer_dir); + if (answer_dir == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE) { + GST_WARNING_OBJECT (webrtc, "Could not intersect offer direction with " + "transceiver direction"); + goto rejected; + } + _media_replace_direction (media, answer_dir); + + /* set the a=setup: attribute */ + offer_setup = _get_dtls_setup_from_media (offer_media); + answer_setup = _intersect_dtls_setup (offer_setup); + if (answer_setup == GST_WEBRTC_DTLS_SETUP_NONE) { + GST_WARNING_OBJECT (webrtc, "Could not intersect offer direction with " + "transceiver direction"); + goto rejected; + } + _media_replace_setup (media, answer_setup); + + /* FIXME: bundle! */ + if (!trans->stream) { + TransportStream *item = _find_transport_for_session (webrtc, i); + if (!item) + item = _create_transport_channel (webrtc, i); + webrtc_transceiver_set_transport (trans, item); + } + /* set the a=fingerprint: for this transport */ + g_object_get (trans->stream->transport, "certificate", &cert, NULL); + + { + gchar *fingerprint, *val; + + fingerprint = + _generate_fingerprint_from_certificate (cert, G_CHECKSUM_SHA256); + g_free (cert); + val = + g_strdup_printf ("%s %s", + _g_checksum_to_webrtc_string (G_CHECKSUM_SHA256), fingerprint); + g_free (fingerprint); + + gst_sdp_media_add_attribute (media, "fingerprint", val); + g_free (val); + } + + if (0) { + rejected: + GST_INFO_OBJECT (webrtc, "media %u rejected", i); + gst_sdp_media_free (media); + gst_sdp_media_copy (offer_media, &media); + gst_sdp_media_set_port_info (media, 0, 0); + } + gst_sdp_message_add_media (ret, media); + gst_sdp_media_free (media); + + gst_caps_unref (offer_caps); + } + + /* FIXME: can we add not matched transceivers? */ + + /* XXX: only true for the initial offerer */ + g_object_set (webrtc->priv->ice, "controller", FALSE, NULL); + + return ret; +} + +struct create_sdp +{ + GstStructure *options; + GstPromise *promise; + GstWebRTCSDPType type; +}; + +static void +_create_sdp_task (GstWebRTCBin * webrtc, struct create_sdp *data) +{ + GstWebRTCSessionDescription *desc = NULL; + GstSDPMessage *sdp = NULL; + GstStructure *s = NULL; + + GST_INFO_OBJECT (webrtc, "creating %s sdp with options %" GST_PTR_FORMAT, + gst_webrtc_sdp_type_to_string (data->type), data->options); + + if (data->type == GST_WEBRTC_SDP_TYPE_OFFER) + sdp = _create_offer_task (webrtc, data->options); + else if (data->type == GST_WEBRTC_SDP_TYPE_ANSWER) + sdp = _create_answer_task (webrtc, data->options); + else { + g_assert_not_reached (); + goto out; + } + + if (sdp) { + desc = gst_webrtc_session_description_new (data->type, sdp); + s = gst_structure_new ("application/x-gst-promise", + gst_webrtc_sdp_type_to_string (data->type), + GST_TYPE_WEBRTC_SESSION_DESCRIPTION, desc, NULL); + } + +out: + PC_UNLOCK (webrtc); + gst_promise_reply (data->promise, s); + PC_LOCK (webrtc); + + if (desc) + gst_webrtc_session_description_free (desc); +} + +static void +_free_create_sdp_data (struct create_sdp *data) +{ + if (data->options) + gst_structure_free (data->options); + gst_promise_unref (data->promise); + g_free (data); +} + +static void +gst_webrtc_bin_create_offer (GstWebRTCBin * webrtc, + const GstStructure * options, GstPromise * promise) +{ + struct create_sdp *data = g_new0 (struct create_sdp, 1); + + if (options) + data->options = gst_structure_copy (options); + data->promise = gst_promise_ref (promise); + data->type = GST_WEBRTC_SDP_TYPE_OFFER; + + gst_webrtc_bin_enqueue_task (webrtc, (GstWebRTCBinFunc) _create_sdp_task, + data, (GDestroyNotify) _free_create_sdp_data); +} + +static void +gst_webrtc_bin_create_answer (GstWebRTCBin * webrtc, + const GstStructure * options, GstPromise * promise) +{ + struct create_sdp *data = g_new0 (struct create_sdp, 1); + + if (options) + data->options = gst_structure_copy (options); + data->promise = gst_promise_ref (promise); + data->type = GST_WEBRTC_SDP_TYPE_ANSWER; + + gst_webrtc_bin_enqueue_task (webrtc, (GstWebRTCBinFunc) _create_sdp_task, + data, (GDestroyNotify) _free_create_sdp_data); +} + +static GstWebRTCBinPad * +_create_pad_for_sdp_media (GstWebRTCBin * webrtc, GstPadDirection direction, + guint media_idx) +{ + GstWebRTCBinPad *pad; + gchar *pad_name; + + pad_name = + g_strdup_printf ("%s_%u", direction == GST_PAD_SRC ? "src" : "sink", + media_idx); + pad = gst_webrtc_bin_pad_new (pad_name, direction); + g_free (pad_name); + pad->mlineindex = media_idx; + + return pad; +} + +static GstWebRTCRTPTransceiver * +_find_transceiver_for_sdp_media (GstWebRTCBin * webrtc, + const GstSDPMessage * sdp, guint media_idx) +{ + const GstSDPMedia *media = gst_sdp_message_get_media (sdp, media_idx); + GstWebRTCRTPTransceiver *ret = NULL; + int i; + + for (i = 0; i < gst_sdp_media_attributes_len (media); i++) { + const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i); + + if (g_strcmp0 (attr->key, "mid") == 0) { + if ((ret = + _find_transceiver (webrtc, attr->value, + (FindTransceiverFunc) match_for_mid))) + goto out; + } + } + + ret = _find_transceiver (webrtc, &media_idx, + (FindTransceiverFunc) transceiver_match_for_mline); + +out: + GST_TRACE_OBJECT (webrtc, "Found transceiver %" GST_PTR_FORMAT, ret); + return ret; +} + +static GstPad * +_connect_input_stream (GstWebRTCBin * webrtc, GstWebRTCBinPad * pad) +{ +/* + * ,-------------------------webrtcbin-------------------------, + * ; ; + * ; ,-------rtpbin-------, ,--transport_send_%u--, ; + * ; ; send_rtp_src_%u o---o rtp_sink ; ; + * ; ; ; ; ; ; + * ; ; send_rtcp_src_%u o---o rtcp_sink ; ; + * ; sink_%u ; ; '---------------------' ; + * o----------o send_rtp_sink_%u ; ; + * ; '--------------------' ; + * '--------------------- -------------------------------------' + */ + GstPadTemplate *rtp_templ; + GstPad *rtp_sink; + gchar *pad_name; + WebRTCTransceiver *trans; + + g_return_val_if_fail (pad->trans != NULL, NULL); + + GST_INFO_OBJECT (pad, "linking input stream %u", pad->mlineindex); + + rtp_templ = + _find_pad_template (webrtc->rtpbin, GST_PAD_SINK, GST_PAD_REQUEST, + "send_rtp_sink_%u"); + g_assert (rtp_templ); + + pad_name = g_strdup_printf ("send_rtp_sink_%u", pad->mlineindex); + rtp_sink = + gst_element_request_pad (webrtc->rtpbin, rtp_templ, pad_name, NULL); + g_free (pad_name); + gst_ghost_pad_set_target (GST_GHOST_PAD (pad), rtp_sink); + gst_object_unref (rtp_sink); + + trans = WEBRTC_TRANSCEIVER (pad->trans); + if (!trans->stream) { + TransportStream *item; + /* FIXME: bundle */ + item = _find_transport_for_session (webrtc, pad->mlineindex); + if (!item) + item = _create_transport_channel (webrtc, pad->mlineindex); + webrtc_transceiver_set_transport (trans, item); + } + + pad_name = g_strdup_printf ("send_rtp_src_%u", pad->mlineindex); + if (!gst_element_link_pads (GST_ELEMENT (webrtc->rtpbin), pad_name, + GST_ELEMENT (trans->stream->send_bin), "rtp_sink")) + g_warn_if_reached (); + g_free (pad_name); + + gst_element_sync_state_with_parent (GST_ELEMENT (trans->stream->send_bin)); + + return GST_PAD (pad); +} + +/* output pads are receiving elements */ +static GstWebRTCBinPad * +_connect_output_stream (GstWebRTCBin * webrtc, GstWebRTCBinPad * pad) +{ +/* + * ,------------------------webrtcbin------------------------, + * ; ,---------rtpbin---------, ; + * ; ,-transport_receive_%u--, ; ; ; + * ; ; rtp_src o---o recv_rtp_sink_%u ; ; + * ; ; ; ; ; ; + * ; ; rtcp_src o---o recv_rtcp_sink_%u ; ; + * ; '-----------------------' ; ; ; src_%u + * ; ; recv_rtp_src_%u_%u_%u o--o + * ; '------------------------' ; + * '---------------------------------------------------------' + */ + gchar *pad_name; + WebRTCTransceiver *trans; + + g_return_val_if_fail (pad->trans != NULL, NULL); + + GST_INFO_OBJECT (pad, "linking output stream %u", pad->mlineindex); + + trans = WEBRTC_TRANSCEIVER (pad->trans); + if (!trans->stream) { + TransportStream *item; + /* FIXME: bundle */ + item = _find_transport_for_session (webrtc, pad->mlineindex); + if (!item) + item = _create_transport_channel (webrtc, pad->mlineindex); + webrtc_transceiver_set_transport (trans, item); + } + + pad_name = g_strdup_printf ("recv_rtp_sink_%u", pad->mlineindex); + if (!gst_element_link_pads (GST_ELEMENT (trans->stream->receive_bin), + "rtp_src", GST_ELEMENT (webrtc->rtpbin), pad_name)) + g_warn_if_reached (); + g_free (pad_name); + + gst_element_sync_state_with_parent (GST_ELEMENT (trans->stream->receive_bin)); + + return pad; +} + +typedef struct +{ + guint mlineindex; + gchar *candidate; +} IceCandidateItem; + +static void +_clear_ice_candidate_item (IceCandidateItem ** item) +{ + g_free ((*item)->candidate); + g_free (*item); +} + +static void +_add_ice_candidate (GstWebRTCBin * webrtc, IceCandidateItem * item) +{ + GstWebRTCICEStream *stream; + + stream = _find_ice_stream_for_session (webrtc, item->mlineindex); + if (stream == NULL) { + GST_WARNING_OBJECT (webrtc, "Unknown mline %u, ignoring", item->mlineindex); + return; + } + + GST_LOG_OBJECT (webrtc, "adding ICE candidate with mline:%u, %s", + item->mlineindex, item->candidate); + + gst_webrtc_ice_add_candidate (webrtc->priv->ice, stream, item->candidate); +} + +static void +_update_transceiver_from_sdp_media (GstWebRTCBin * webrtc, + const GstSDPMessage * sdp, guint media_idx, + GstWebRTCRTPTransceiver * rtp_trans) +{ + WebRTCTransceiver *trans = WEBRTC_TRANSCEIVER (rtp_trans); + TransportStream *stream = trans->stream; + GstWebRTCRTPTransceiverDirection prev_dir = rtp_trans->current_direction; + GstWebRTCRTPTransceiverDirection new_dir; + const GstSDPMedia *media = gst_sdp_message_get_media (sdp, media_idx); + GstWebRTCDTLSSetup new_setup; + gboolean new_rtcp_mux, new_rtcp_rsize; + int i; + + rtp_trans->mline = media_idx; + + for (i = 0; i < gst_sdp_media_attributes_len (media); i++) { + const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i); + + if (g_strcmp0 (attr->key, "mid") == 0) { + g_free (rtp_trans->mid); + rtp_trans->mid = g_strdup (attr->value); + } + } + + if (!stream) { + /* FIXME: find an existing transport for e.g. bundle/reconfiguration */ + stream = _find_transport_for_session (webrtc, media_idx); + if (!stream) + stream = _create_transport_channel (webrtc, media_idx); + webrtc_transceiver_set_transport (trans, stream); + } + + { + const GstSDPMedia *local_media, *remote_media; + GstWebRTCRTPTransceiverDirection local_dir, remote_dir; + GstWebRTCDTLSSetup local_setup, remote_setup; + guint i, len; + const gchar *proto; + GstCaps *global_caps; + + local_media = + gst_sdp_message_get_media (webrtc->current_local_description->sdp, + media_idx); + remote_media = + gst_sdp_message_get_media (webrtc->current_remote_description->sdp, + media_idx); + + local_setup = _get_dtls_setup_from_media (local_media); + remote_setup = _get_dtls_setup_from_media (remote_media); + new_setup = _get_final_setup (local_setup, remote_setup); + if (new_setup == GST_WEBRTC_DTLS_SETUP_NONE) + return; + + local_dir = _get_direction_from_media (local_media); + remote_dir = _get_direction_from_media (remote_media); + new_dir = _get_final_direction (local_dir, remote_dir); + if (new_dir == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE) + return; + + /* get proto */ + proto = gst_sdp_media_get_proto (media); + if (proto != NULL) { + /* Parse global SDP attributes once */ + global_caps = gst_caps_new_empty_simple ("application/x-unknown"); + GST_DEBUG_OBJECT (webrtc, "mapping sdp session level attributes to caps"); + gst_sdp_message_attributes_to_caps (sdp, global_caps); + GST_DEBUG_OBJECT (webrtc, "mapping sdp media level attributes to caps"); + gst_sdp_media_attributes_to_caps (media, global_caps); + + /* clear the ptmap */ + g_array_set_size (stream->ptmap, 0); + + len = gst_sdp_media_formats_len (media); + for (i = 0; i < len; i++) { + GstCaps *caps, *outcaps; + GstStructure *s; + PtMapItem item; + gint pt; + + pt = atoi (gst_sdp_media_get_format (media, i)); + + GST_DEBUG_OBJECT (webrtc, " looking at %d pt: %d", i, pt); + + /* convert caps */ + caps = gst_sdp_media_get_caps_from_media (media, pt); + if (caps == NULL) { + GST_WARNING_OBJECT (webrtc, " skipping pt %d without caps", pt); + continue; + } + + /* Merge in global caps */ + /* Intersect will merge in missing fields to the current caps */ + outcaps = gst_caps_intersect (caps, global_caps); + gst_caps_unref (caps); + + s = gst_caps_get_structure (outcaps, 0); + gst_structure_set_name (s, "application/x-rtp"); + + item.pt = pt; + item.caps = outcaps; + + g_array_append_val (stream->ptmap, item); + } + + gst_caps_unref (global_caps); + } + + new_rtcp_mux = _media_has_attribute_key (local_media, "rtcp-mux") + && _media_has_attribute_key (remote_media, "rtcp-mux"); + new_rtcp_rsize = _media_has_attribute_key (local_media, "rtcp-rsize") + && _media_has_attribute_key (remote_media, "rtcp-rsize"); + + { + GObject *session; + g_signal_emit_by_name (webrtc->rtpbin, "get-internal-session", + media_idx, &session); + if (session) { + g_object_set (session, "rtcp-reduced-size", new_rtcp_rsize, NULL); + g_object_unref (session); + } + } + } + + if (prev_dir != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE + && prev_dir != new_dir) { + GST_FIXME_OBJECT (webrtc, "implement transceiver direction changes"); + return; + } + + /* FIXME: bundle! */ + g_object_set (stream, "rtcp-mux", new_rtcp_mux, NULL); + + if (new_dir != prev_dir) { + TransportReceiveBin *receive; + + GST_TRACE_OBJECT (webrtc, "transceiver direction change"); + + /* FIXME: this may not always be true. e.g. bundle */ + g_assert (media_idx == stream->session_id); + + if (new_dir == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY || + new_dir == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV) { + GstWebRTCBinPad *pad = + _find_pad_for_mline (webrtc, GST_PAD_SINK, media_idx); + if (pad) { + GST_DEBUG_OBJECT (webrtc, "found existing send pad %" GST_PTR_FORMAT + " for transceiver %" GST_PTR_FORMAT, pad, trans); + g_assert (pad->trans == rtp_trans); + g_assert (pad->mlineindex == media_idx); + gst_object_unref (pad); + } else { + GST_DEBUG_OBJECT (webrtc, + "creating new pad send pad for transceiver %" GST_PTR_FORMAT, + trans); + pad = _create_pad_for_sdp_media (webrtc, GST_PAD_SINK, media_idx); + pad->trans = gst_object_ref (rtp_trans); + _connect_input_stream (webrtc, pad); + _add_pad (webrtc, pad); + } + g_object_set (stream, "dtls-client", + new_setup == GST_WEBRTC_DTLS_SETUP_ACTIVE, NULL); + } + if (new_dir == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY || + new_dir == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV) { + GstWebRTCBinPad *pad = + _find_pad_for_mline (webrtc, GST_PAD_SRC, media_idx); + if (pad) { + GST_DEBUG_OBJECT (webrtc, "found existing receive pad %" GST_PTR_FORMAT + " for transceiver %" GST_PTR_FORMAT, pad, trans); + g_assert (pad->trans == rtp_trans); + g_assert (pad->mlineindex == media_idx); + gst_object_unref (pad); + } else { + GST_DEBUG_OBJECT (webrtc, + "creating new receive pad for transceiver %" GST_PTR_FORMAT, trans); + pad = _create_pad_for_sdp_media (webrtc, GST_PAD_SRC, media_idx); + pad->trans = gst_object_ref (rtp_trans); + _connect_output_stream (webrtc, pad); + /* delay adding the pad until rtpbin creates the recv output pad + * to ghost to so queries/events travel through the pipeline correctly + * as soon as the pad is added */ + _add_pad_to_list (webrtc, pad); + } + g_object_set (stream, "dtls-client", + new_setup == GST_WEBRTC_DTLS_SETUP_ACTIVE, NULL); + } + + receive = TRANSPORT_RECEIVE_BIN (stream->receive_bin); + if (new_dir == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY || + new_dir == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV) + transport_receive_bin_set_receive_state (receive, RECEIVE_STATE_PASS); + else + transport_receive_bin_set_receive_state (receive, RECEIVE_STATE_DROP); + + rtp_trans->mline = media_idx; + rtp_trans->current_direction = new_dir; + } +} + +static gboolean +_find_compatible_unassociated_transceiver (GstWebRTCRTPTransceiver * p1, + gconstpointer data) +{ + if (p1->mid) + return FALSE; + if (p1->mline != -1) + return FALSE; + + return TRUE; +} + +static gboolean +_update_transceivers_from_sdp (GstWebRTCBin * webrtc, SDPSource source, + GstWebRTCSessionDescription * sdp) +{ + int i; + + for (i = 0; i < gst_sdp_message_medias_len (sdp->sdp); i++) { + const GstSDPMedia *media = gst_sdp_message_get_media (sdp->sdp, i); + GstWebRTCRTPTransceiver *trans; + + /* skip rejected media */ + if (gst_sdp_media_get_port (media) == 0) + continue; + + trans = _find_transceiver_for_sdp_media (webrtc, sdp->sdp, i); + + if (source == SDP_LOCAL && sdp->type == GST_WEBRTC_SDP_TYPE_OFFER && !trans) { + GST_ERROR ("State mismatch. Could not find local transceiver by mline."); + return FALSE; + } else { + if (trans) { + _update_transceiver_from_sdp_media (webrtc, sdp->sdp, i, trans); + } else { + trans = _find_transceiver (webrtc, NULL, + (FindTransceiverFunc) _find_compatible_unassociated_transceiver); + if (!trans) + trans = + GST_WEBRTC_RTP_TRANSCEIVER (_create_webrtc_transceiver (webrtc)); + /* XXX: default to the advertised direction in the sdp for new + * transceviers. The spec doesn't actually say what happens here, only + * that calls to setDirection will change the value. Nothing about + * a default value when the transceiver is created internally */ + trans->direction = _get_direction_from_media (media); + _update_transceiver_from_sdp_media (webrtc, sdp->sdp, i, trans); + } + } + } + + return TRUE; +} + +static void +_get_ice_credentials_from_sdp_media (const GstSDPMessage * sdp, guint media_idx, + gchar ** ufrag, gchar ** pwd) +{ + int i; + + *ufrag = NULL; + *pwd = NULL; + + { + /* search in the corresponding media section */ + const GstSDPMedia *media = gst_sdp_message_get_media (sdp, media_idx); + const gchar *tmp_ufrag = + gst_sdp_media_get_attribute_val (media, "ice-ufrag"); + const gchar *tmp_pwd = gst_sdp_media_get_attribute_val (media, "ice-pwd"); + if (tmp_ufrag && tmp_pwd) { + *ufrag = g_strdup (tmp_ufrag); + *pwd = g_strdup (tmp_pwd); + return; + } + } + + /* then in the sdp message itself */ + for (i = 0; i < gst_sdp_message_attributes_len (sdp); i++) { + const GstSDPAttribute *attr = gst_sdp_message_get_attribute (sdp, i); + + if (g_strcmp0 (attr->key, "ice-ufrag") == 0) { + g_assert (!*ufrag); + *ufrag = g_strdup (attr->value); + } else if (g_strcmp0 (attr->key, "ice-pwd") == 0) { + g_assert (!*pwd); + *pwd = g_strdup (attr->value); + } + } + if (!*ufrag && !*pwd) { + /* Check in the medias themselves. According to JSEP, they should be + * identical FIXME: only for bundle-d streams */ + for (i = 0; i < gst_sdp_message_medias_len (sdp); i++) { + const GstSDPMedia *media = gst_sdp_message_get_media (sdp, i); + const gchar *tmp_ufrag = + gst_sdp_media_get_attribute_val (media, "ice-ufrag"); + const gchar *tmp_pwd = gst_sdp_media_get_attribute_val (media, "ice-pwd"); + if (tmp_ufrag && tmp_pwd) { + *ufrag = g_strdup (tmp_ufrag); + *pwd = g_strdup (tmp_pwd); + break; + } + } + } +} + +struct set_description +{ + GstPromise *promise; + SDPSource source; + GstWebRTCSessionDescription *sdp; +}; + +/* http://w3c.github.io/webrtc-pc/#set-description */ +static void +_set_description_task (GstWebRTCBin * webrtc, struct set_description *sd) +{ + GstWebRTCSignalingState new_signaling_state = webrtc->signaling_state; + GError *error = NULL; + + { + gchar *state = _enum_value_to_string (GST_TYPE_WEBRTC_SIGNALING_STATE, + webrtc->signaling_state); + gchar *type_str = + _enum_value_to_string (GST_TYPE_WEBRTC_SDP_TYPE, sd->sdp->type); + gchar *sdp_text = gst_sdp_message_as_text (sd->sdp->sdp); + GST_INFO_OBJECT (webrtc, "Attempting to set %s %s in the %s state", + _sdp_source_to_string (sd->source), type_str, state); + GST_TRACE_OBJECT (webrtc, "SDP contents\n%s", sdp_text); + g_free (sdp_text); + g_free (state); + g_free (type_str); + } + + if (!validate_sdp (webrtc, sd->source, sd->sdp, &error)) { + GST_ERROR_OBJECT (webrtc, "%s", error->message); + goto out; + } + + if (webrtc->priv->is_closed) { + GST_WARNING_OBJECT (webrtc, "we are closed"); + goto out; + } + + switch (sd->sdp->type) { + case GST_WEBRTC_SDP_TYPE_OFFER:{ + if (sd->source == SDP_LOCAL) { + if (webrtc->pending_local_description) + gst_webrtc_session_description_free + (webrtc->pending_local_description); + webrtc->pending_local_description = + gst_webrtc_session_description_copy (sd->sdp); + new_signaling_state = GST_WEBRTC_SIGNALING_STATE_HAVE_LOCAL_OFFER; + } else { + if (webrtc->pending_remote_description) + gst_webrtc_session_description_free + (webrtc->pending_remote_description); + webrtc->pending_remote_description = + gst_webrtc_session_description_copy (sd->sdp); + new_signaling_state = GST_WEBRTC_SIGNALING_STATE_HAVE_REMOTE_OFFER; + } + break; + } + case GST_WEBRTC_SDP_TYPE_ANSWER:{ + if (sd->source == SDP_LOCAL) { + if (webrtc->current_local_description) + gst_webrtc_session_description_free + (webrtc->current_local_description); + webrtc->current_local_description = + gst_webrtc_session_description_copy (sd->sdp); + + if (webrtc->current_remote_description) + gst_webrtc_session_description_free + (webrtc->current_remote_description); + webrtc->current_remote_description = webrtc->pending_remote_description; + webrtc->pending_remote_description = NULL; + } else { + if (webrtc->current_remote_description) + gst_webrtc_session_description_free + (webrtc->current_remote_description); + webrtc->current_remote_description = + gst_webrtc_session_description_copy (sd->sdp); + + if (webrtc->current_local_description) + gst_webrtc_session_description_free + (webrtc->current_local_description); + webrtc->current_local_description = webrtc->pending_local_description; + webrtc->pending_local_description = NULL; + } + + if (webrtc->pending_local_description) + gst_webrtc_session_description_free (webrtc->pending_local_description); + webrtc->pending_local_description = NULL; + + if (webrtc->pending_remote_description) + gst_webrtc_session_description_free + (webrtc->pending_remote_description); + webrtc->pending_remote_description = NULL; + + new_signaling_state = GST_WEBRTC_SIGNALING_STATE_STABLE; + break; + } + case GST_WEBRTC_SDP_TYPE_ROLLBACK:{ + GST_FIXME_OBJECT (webrtc, "rollbacks are completely untested"); + if (sd->source == SDP_LOCAL) { + if (webrtc->pending_local_description) + gst_webrtc_session_description_free + (webrtc->pending_local_description); + webrtc->pending_local_description = NULL; + } else { + if (webrtc->pending_remote_description) + gst_webrtc_session_description_free + (webrtc->pending_remote_description); + webrtc->pending_remote_description = NULL; + } + + new_signaling_state = GST_WEBRTC_SIGNALING_STATE_STABLE; + break; + } + case GST_WEBRTC_SDP_TYPE_PRANSWER:{ + GST_FIXME_OBJECT (webrtc, "pranswers are completely untested"); + if (sd->source == SDP_LOCAL) { + if (webrtc->pending_local_description) + gst_webrtc_session_description_free + (webrtc->pending_local_description); + webrtc->pending_local_description = + gst_webrtc_session_description_copy (sd->sdp); + + new_signaling_state = GST_WEBRTC_SIGNALING_STATE_HAVE_LOCAL_PRANSWER; + } else { + if (webrtc->pending_remote_description) + gst_webrtc_session_description_free + (webrtc->pending_remote_description); + webrtc->pending_remote_description = + gst_webrtc_session_description_copy (sd->sdp); + + new_signaling_state = GST_WEBRTC_SIGNALING_STATE_HAVE_REMOTE_PRANSWER; + } + break; + } + } + + if (new_signaling_state != webrtc->signaling_state) { + gchar *from = _enum_value_to_string (GST_TYPE_WEBRTC_SIGNALING_STATE, + webrtc->signaling_state); + gchar *to = _enum_value_to_string (GST_TYPE_WEBRTC_SIGNALING_STATE, + new_signaling_state); + GST_TRACE_OBJECT (webrtc, "notify signaling-state from %s " + "to %s", from, to); + webrtc->signaling_state = new_signaling_state; + PC_UNLOCK (webrtc); + g_object_notify (G_OBJECT (webrtc), "signaling-state"); + PC_LOCK (webrtc); + + g_free (from); + g_free (to); + } + + /* TODO: necessary data channel modifications */ + + if (sd->sdp->type == GST_WEBRTC_SDP_TYPE_ROLLBACK) { + /* FIXME: + * If the mid value of an RTCRtpTransceiver was set to a non-null value + * by the RTCSessionDescription that is being rolled back, set the mid + * value of that transceiver to null, as described by [JSEP] + * (section 4.1.7.2.). + * If an RTCRtpTransceiver was created by applying the + * RTCSessionDescription that is being rolled back, and a track has not + * been attached to it via addTrack, remove that transceiver from + * connection's set of transceivers, as described by [JSEP] + * (section 4.1.7.2.). + * Restore the value of connection's [[ sctpTransport]] internal slot + * to its value at the last stable signaling state. + */ + } + + if (webrtc->signaling_state == GST_WEBRTC_SIGNALING_STATE_STABLE) { + gboolean prev_need_negotiation = webrtc->priv->need_negotiation; + + /* media modifications */ + _update_transceivers_from_sdp (webrtc, sd->source, sd->sdp); + + /* If connection's signaling state is now stable, update the + * negotiation-needed flag. If connection's [[ needNegotiation]] slot + * was true both before and after this update, queue a task to check + * connection's [[needNegotiation]] slot and, if still true, fire a + * simple event named negotiationneeded at connection.*/ + _update_need_negotiation (webrtc); + if (prev_need_negotiation && webrtc->priv->need_negotiation) { + _check_need_negotiation_task (webrtc, NULL); + } + } + + if (sd->source == SDP_LOCAL) { + int i; + + for (i = 0; i < gst_sdp_message_medias_len (sd->sdp->sdp); i++) { + gchar *ufrag, *pwd; + TransportStream *item; + + /* FIXME: bundle */ + item = _find_transport_for_session (webrtc, i); + if (!item) + item = _create_transport_channel (webrtc, i); + + _get_ice_credentials_from_sdp_media (sd->sdp->sdp, i, &ufrag, &pwd); + gst_webrtc_ice_set_local_credentials (webrtc->priv->ice, + item->stream, ufrag, pwd); + g_free (ufrag); + g_free (pwd); + } + } + + if (sd->source == SDP_REMOTE) { + int i; + + for (i = 0; i < gst_sdp_message_medias_len (sd->sdp->sdp); i++) { + gchar *ufrag, *pwd; + TransportStream *item; + + /* FIXME: bundle */ + item = _find_transport_for_session (webrtc, i); + if (!item) + item = _create_transport_channel (webrtc, i); + + _get_ice_credentials_from_sdp_media (sd->sdp->sdp, i, &ufrag, &pwd); + gst_webrtc_ice_set_remote_credentials (webrtc->priv->ice, + item->stream, ufrag, pwd); + g_free (ufrag); + g_free (pwd); + } + } + + { + int i; + for (i = 0; i < webrtc->priv->ice_stream_map->len; i++) { + IceStreamItem *item = + &g_array_index (webrtc->priv->ice_stream_map, IceStreamItem, i); + + gst_webrtc_ice_gather_candidates (webrtc->priv->ice, item->stream); + } + } + + if (webrtc->current_local_description && webrtc->current_remote_description) { + int i; + + for (i = 0; i < webrtc->priv->pending_ice_candidates->len; i++) { + IceCandidateItem *item = + g_array_index (webrtc->priv->pending_ice_candidates, + IceCandidateItem *, i); + + _add_ice_candidate (webrtc, item); + } + g_array_set_size (webrtc->priv->pending_ice_candidates, 0); + } + +out: + PC_UNLOCK (webrtc); + gst_promise_reply (sd->promise, NULL); + PC_LOCK (webrtc); +} + +static void +_free_set_description_data (struct set_description *sd) +{ + if (sd->promise) + gst_promise_unref (sd->promise); + if (sd->sdp) + gst_webrtc_session_description_free (sd->sdp); + g_free (sd); +} + +static void +gst_webrtc_bin_set_remote_description (GstWebRTCBin * webrtc, + GstWebRTCSessionDescription * remote_sdp, GstPromise * promise) +{ + struct set_description *sd; + + if (remote_sdp == NULL) + goto bad_input; + if (remote_sdp->sdp == NULL) + goto bad_input; + + sd = g_new0 (struct set_description, 1); + if (promise != NULL) + sd->promise = gst_promise_ref (promise); + sd->source = SDP_REMOTE; + sd->sdp = gst_webrtc_session_description_copy (remote_sdp); + + gst_webrtc_bin_enqueue_task (webrtc, (GstWebRTCBinFunc) _set_description_task, + sd, (GDestroyNotify) _free_set_description_data); + + return; + +bad_input: + { + gst_promise_reply (promise, NULL); + g_return_if_reached (); + } +} + +static void +gst_webrtc_bin_set_local_description (GstWebRTCBin * webrtc, + GstWebRTCSessionDescription * local_sdp, GstPromise * promise) +{ + struct set_description *sd; + + if (local_sdp == NULL) + goto bad_input; + if (local_sdp->sdp == NULL) + goto bad_input; + + sd = g_new0 (struct set_description, 1); + if (promise != NULL) + sd->promise = gst_promise_ref (promise); + sd->source = SDP_LOCAL; + sd->sdp = gst_webrtc_session_description_copy (local_sdp); + + gst_webrtc_bin_enqueue_task (webrtc, (GstWebRTCBinFunc) _set_description_task, + sd, (GDestroyNotify) _free_set_description_data); + + return; + +bad_input: + { + gst_promise_reply (promise, NULL); + g_return_if_reached (); + } +} + +static void +_add_ice_candidate_task (GstWebRTCBin * webrtc, IceCandidateItem * item) +{ + if (!webrtc->current_local_description || !webrtc->current_remote_description) { + IceCandidateItem *new = g_new0 (IceCandidateItem, 1); + new->mlineindex = item->mlineindex; + new->candidate = g_strdup (item->candidate); + + g_array_append_val (webrtc->priv->pending_ice_candidates, new); + } else { + _add_ice_candidate (webrtc, item); + } +} + +static void +_free_ice_candidate_item (IceCandidateItem * item) +{ + _clear_ice_candidate_item (&item); +} + +static void +gst_webrtc_bin_add_ice_candidate (GstWebRTCBin * webrtc, guint mline, + const gchar * attr) +{ + IceCandidateItem *item; + + item = g_new0 (IceCandidateItem, 1); + item->mlineindex = mline; + if (!g_ascii_strncasecmp (attr, "a=candidate:", 12)) + item->candidate = g_strdup (attr); + else if (!g_ascii_strncasecmp (attr, "candidate:", 10)) + item->candidate = g_strdup_printf ("a=%s", attr); + gst_webrtc_bin_enqueue_task (webrtc, + (GstWebRTCBinFunc) _add_ice_candidate_task, item, + (GDestroyNotify) _free_ice_candidate_item); +} + +static void +_on_ice_candidate_task (GstWebRTCBin * webrtc, IceCandidateItem * item) +{ + const gchar *cand = item->candidate; + + if (!g_ascii_strncasecmp (cand, "a=candidate:", 12)) { + /* stripping away "a=" */ + cand += 2; + } + + GST_TRACE_OBJECT (webrtc, "produced ICE candidate for mline:%u and %s", + item->mlineindex, cand); + + PC_UNLOCK (webrtc); + g_signal_emit (webrtc, gst_webrtc_bin_signals[ON_ICE_CANDIDATE_SIGNAL], + 0, item->mlineindex, cand); + PC_LOCK (webrtc); +} + +static void +_on_ice_candidate (GstWebRTCICE * ice, guint session_id, + gchar * candidate, GstWebRTCBin * webrtc) +{ + IceCandidateItem *item = g_new0 (IceCandidateItem, 1); + + /* FIXME: bundle support */ + item->mlineindex = session_id; + item->candidate = g_strdup (candidate); + + gst_webrtc_bin_enqueue_task (webrtc, + (GstWebRTCBinFunc) _on_ice_candidate_task, item, + (GDestroyNotify) _free_ice_candidate_item); +} + +/* https://www.w3.org/TR/webrtc/#dfn-stats-selection-algorithm */ +static GstStructure * +_get_stats_from_selector (GstWebRTCBin * webrtc, gpointer selector) +{ + if (selector) + GST_FIXME_OBJECT (webrtc, "Implement stats selection"); + + return gst_structure_copy (webrtc->priv->stats); +} + +struct get_stats +{ + GstPad *pad; + GstPromise *promise; +}; + +static void +_free_get_stats (struct get_stats *stats) +{ + if (stats->pad) + gst_object_unref (stats->pad); + if (stats->promise) + gst_promise_unref (stats->promise); + g_free (stats); +} + +/* https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-getstats() */ +static void +_get_stats_task (GstWebRTCBin * webrtc, struct get_stats *stats) +{ + GstStructure *s; + gpointer selector = NULL; + + gst_webrtc_bin_update_stats (webrtc); + + if (stats->pad) { + GstWebRTCBinPad *wpad = GST_WEBRTC_BIN_PAD (stats->pad); + + if (wpad->trans) { + if (GST_PAD_DIRECTION (wpad) == GST_PAD_SRC) { + selector = wpad->trans->receiver; + } else { + selector = wpad->trans->sender; + } + } + } + + s = _get_stats_from_selector (webrtc, selector); + gst_promise_reply (stats->promise, s); +} + +static void +gst_webrtc_bin_get_stats (GstWebRTCBin * webrtc, GstPad * pad, + GstPromise * promise) +{ + struct get_stats *stats; + + g_return_if_fail (promise != NULL); + g_return_if_fail (pad == NULL || GST_IS_WEBRTC_BIN_PAD (pad)); + + stats = g_new0 (struct get_stats, 1); + stats->promise = gst_promise_ref (promise); + /* FIXME: check that pad exists in element */ + if (pad) + stats->pad = gst_object_ref (pad); + + gst_webrtc_bin_enqueue_task (webrtc, (GstWebRTCBinFunc) _get_stats_task, + stats, (GDestroyNotify) _free_get_stats); +} + +static GstWebRTCRTPTransceiver * +gst_webrtc_bin_add_transceiver (GstWebRTCBin * webrtc, + GstWebRTCRTPTransceiverDirection direction, GstCaps * caps) +{ + WebRTCTransceiver *trans; + GstWebRTCRTPTransceiver *rtp_trans; + + g_return_val_if_fail (direction != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE, + NULL); + + trans = _create_webrtc_transceiver (webrtc); + rtp_trans = GST_WEBRTC_RTP_TRANSCEIVER (trans); + rtp_trans->direction = direction; + if (caps) + rtp_trans->codec_preferences = gst_caps_ref (caps); + + return gst_object_ref (trans); +} + +static void +_deref_and_unref (GstObject ** object) +{ + if (object) + gst_object_unref (*object); +} + +static GArray * +gst_webrtc_bin_get_transceivers (GstWebRTCBin * webrtc) +{ + GArray *arr = g_array_new (FALSE, TRUE, sizeof (gpointer)); + int i; + + g_array_set_clear_func (arr, (GDestroyNotify) _deref_and_unref); + + for (i = 0; i < webrtc->priv->transceivers->len; i++) { + GstWebRTCRTPTransceiver *trans = + g_array_index (webrtc->priv->transceivers, GstWebRTCRTPTransceiver *, + i); + gst_object_ref (trans); + g_array_append_val (arr, trans); + } + + return arr; +} + +/* === rtpbin signal implementations === */ + +static void +on_rtpbin_pad_added (GstElement * rtpbin, GstPad * new_pad, + GstWebRTCBin * webrtc) +{ + gchar *new_pad_name = NULL; + + new_pad_name = gst_pad_get_name (new_pad); + GST_TRACE_OBJECT (webrtc, "new rtpbin pad %s", new_pad_name); + if (g_str_has_prefix (new_pad_name, "recv_rtp_src_")) { + guint32 session_id = 0, ssrc = 0, pt = 0; + GstWebRTCRTPTransceiver *rtp_trans; + WebRTCTransceiver *trans; + TransportStream *stream; + GstWebRTCBinPad *pad; + + sscanf (new_pad_name, "recv_rtp_src_%u_%u_%u", &session_id, &ssrc, &pt); + + stream = _find_transport_for_session (webrtc, session_id); + if (!stream) + g_warn_if_reached (); + + /* FIXME: bundle! */ + rtp_trans = _find_transceiver_for_mline (webrtc, session_id); + if (!rtp_trans) + g_warn_if_reached (); + trans = WEBRTC_TRANSCEIVER (rtp_trans); + g_assert (trans->stream == stream); + + pad = _find_pad_for_transceiver (webrtc, GST_PAD_SRC, rtp_trans); + + GST_TRACE_OBJECT (webrtc, "found pad %" GST_PTR_FORMAT + " for rtpbin pad name %s", pad, new_pad_name); + if (!pad) + g_warn_if_reached (); + gst_ghost_pad_set_target (GST_GHOST_PAD (pad), GST_PAD (new_pad)); + + if (webrtc->priv->running) + gst_pad_set_active (GST_PAD (pad), TRUE); + gst_element_add_pad (GST_ELEMENT (webrtc), GST_PAD (pad)); + _remove_pending_pad (webrtc, pad); + + gst_object_unref (pad); + } + g_free (new_pad_name); +} + +/* only used for the receiving streams */ +static GstCaps * +on_rtpbin_request_pt_map (GstElement * rtpbin, guint session_id, guint pt, + GstWebRTCBin * webrtc) +{ + TransportStream *stream; + GstCaps *ret; + + GST_DEBUG_OBJECT (webrtc, "getting pt map for pt %d in session %d", pt, + session_id); + + stream = _find_transport_for_session (webrtc, session_id); + if (!stream) + goto unknown_session; + + if ((ret = _transport_stream_get_caps_for_pt (stream, pt))) + gst_caps_ref (ret); + + GST_TRACE_OBJECT (webrtc, "Found caps %" GST_PTR_FORMAT " for pt %d in " + "session %d", ret, pt, session_id); + + return ret; + +unknown_session: + { + GST_DEBUG_OBJECT (webrtc, "unknown session %d", session_id); + return NULL; + } +} + +static GstElement * +on_rtpbin_request_aux_sender (GstElement * rtpbin, guint session_id, + GstWebRTCBin * webrtc) +{ + return NULL; +} + +static GstElement * +on_rtpbin_request_aux_receiver (GstElement * rtpbin, guint session_id, + GstWebRTCBin * webrtc) +{ + return NULL; +} + +static void +on_rtpbin_ssrc_active (GstElement * rtpbin, guint session_id, guint ssrc, + GstWebRTCBin * webrtc) +{ +} + +static void +on_rtpbin_new_jitterbuffer (GstElement * rtpbin, GstElement * jitterbuffer, + guint session_id, guint ssrc, GstWebRTCBin * webrtc) +{ +} + +static GstElement * +_create_rtpbin (GstWebRTCBin * webrtc) +{ + GstElement *rtpbin; + + if (!(rtpbin = gst_element_factory_make ("rtpbin", "rtpbin"))) + return NULL; + + /* mandated by WebRTC */ + gst_util_set_object_arg (G_OBJECT (rtpbin), "rtp-profile", "savpf"); + + g_signal_connect (rtpbin, "pad-added", G_CALLBACK (on_rtpbin_pad_added), + webrtc); + g_signal_connect (rtpbin, "request-pt-map", + G_CALLBACK (on_rtpbin_request_pt_map), webrtc); + g_signal_connect (rtpbin, "request-aux-sender", + G_CALLBACK (on_rtpbin_request_aux_sender), webrtc); + g_signal_connect (rtpbin, "request-aux-receiver", + G_CALLBACK (on_rtpbin_request_aux_receiver), webrtc); + g_signal_connect (rtpbin, "on-ssrc-active", + G_CALLBACK (on_rtpbin_ssrc_active), webrtc); + g_signal_connect (rtpbin, "new-jitterbuffer", + G_CALLBACK (on_rtpbin_new_jitterbuffer), webrtc); + + return rtpbin; +} + +static GstStateChangeReturn +gst_webrtc_bin_change_state (GstElement * element, GstStateChange transition) +{ + GstWebRTCBin *webrtc = GST_WEBRTC_BIN (element); + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + GST_DEBUG ("changing state: %s => %s", + gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)), + gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition))); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY:{ + GstElement *nice; + if (!webrtc->rtpbin) { + /* FIXME: is this the right thing for a missing plugin? */ + GST_ELEMENT_ERROR (webrtc, CORE, MISSING_PLUGIN, (NULL), + ("%s", "rtpbin element is not available")); + return GST_STATE_CHANGE_FAILURE; + } + nice = gst_element_factory_make ("nicesrc", NULL); + if (!nice) { + /* FIXME: is this the right thing for a missing plugin? */ + GST_ELEMENT_ERROR (webrtc, CORE, MISSING_PLUGIN, (NULL), + ("%s", "libnice elements are not available")); + return GST_STATE_CHANGE_FAILURE; + } + gst_object_unref (nice); + nice = gst_element_factory_make ("nicesink", NULL); + if (!nice) { + /* FIXME: is this the right thing for a missing plugin? */ + GST_ELEMENT_ERROR (webrtc, CORE, MISSING_PLUGIN, (NULL), + ("%s", "libnice elements are not available")); + return GST_STATE_CHANGE_FAILURE; + } + gst_object_unref (nice); + _update_need_negotiation (webrtc); + break; + } + case GST_STATE_CHANGE_READY_TO_PAUSED: + webrtc->priv->running = TRUE; + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + /* Mangle the return value to NO_PREROLL as that's what really is + * occurring here however cannot be propagated correctly due to nicesrc + * requiring that it be in PLAYING already in order to send/receive + * correctly :/ */ + ret = GST_STATE_CHANGE_NO_PREROLL; + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + webrtc->priv->running = FALSE; + break; + default: + break; + } + + return ret; +} + +static GstPad * +gst_webrtc_bin_request_new_pad (GstElement * element, GstPadTemplate * templ, + const gchar * name, const GstCaps * caps) +{ + GstWebRTCBin *webrtc = GST_WEBRTC_BIN (element); + GstWebRTCBinPad *pad = NULL; + GstPluginFeature *feature; + guint serial; + + feature = gst_registry_lookup_feature (gst_registry_get (), "nicesrc"); + if (feature) { + gst_object_unref (feature); + } else { + GST_ELEMENT_ERROR (element, CORE, MISSING_PLUGIN, NULL, + ("%s", "libnice elements are not available")); + return NULL; + } + + feature = gst_registry_lookup_feature (gst_registry_get (), "nicesink"); + if (feature) { + gst_object_unref (feature); + } else { + GST_ELEMENT_ERROR (element, CORE, MISSING_PLUGIN, NULL, + ("%s", "libnice elements are not available")); + return NULL; + } + + if (templ->direction == GST_PAD_SINK || + g_strcmp0 (templ->name_template, "sink_%u") == 0) { + GstWebRTCRTPTransceiver *trans; + + GST_OBJECT_LOCK (webrtc); + if (name == NULL || strlen (name) < 6 || !g_str_has_prefix (name, "sink_")) { + /* no name given when requesting the pad, use next available int */ + serial = webrtc->priv->max_sink_pad_serial++; + } else { + /* parse serial number from requested padname */ + serial = g_ascii_strtoull (&name[5], NULL, 10); + if (serial > webrtc->priv->max_sink_pad_serial) + webrtc->priv->max_sink_pad_serial = serial; + } + GST_OBJECT_UNLOCK (webrtc); + + pad = _create_pad_for_sdp_media (webrtc, GST_PAD_SINK, serial); + trans = _find_transceiver_for_mline (webrtc, serial); + if (!(trans = + GST_WEBRTC_RTP_TRANSCEIVER (_create_webrtc_transceiver (webrtc)))) { + trans->direction = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV; + trans->mline = serial; + } + pad->trans = gst_object_ref (trans); + _connect_input_stream (webrtc, pad); + + /* TODO: update negotiation-needed */ + _add_pad (webrtc, pad); + } + + return GST_PAD (pad); +} + +static void +gst_webrtc_bin_release_pad (GstElement * element, GstPad * pad) +{ + GstWebRTCBin *webrtc = GST_WEBRTC_BIN (element); + GstWebRTCBinPad *webrtc_pad = GST_WEBRTC_BIN_PAD (pad); + + if (webrtc_pad->trans) + gst_object_unref (webrtc_pad->trans); + webrtc_pad->trans = NULL; + + _remove_pad (webrtc, webrtc_pad); +} + +static void +gst_webrtc_bin_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstWebRTCBin *webrtc = GST_WEBRTC_BIN (object); + + switch (prop_id) { + case PROP_STUN_SERVER: + case PROP_TURN_SERVER: + g_object_set_property (G_OBJECT (webrtc->priv->ice), pspec->name, value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_webrtc_bin_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstWebRTCBin *webrtc = GST_WEBRTC_BIN (object); + + PC_LOCK (webrtc); + switch (prop_id) { + case PROP_CONNECTION_STATE: + g_value_set_enum (value, webrtc->peer_connection_state); + break; + case PROP_SIGNALING_STATE: + g_value_set_enum (value, webrtc->signaling_state); + break; + case PROP_ICE_GATHERING_STATE: + g_value_set_enum (value, webrtc->ice_gathering_state); + break; + case PROP_ICE_CONNECTION_STATE: + g_value_set_enum (value, webrtc->ice_connection_state); + break; + case PROP_LOCAL_DESCRIPTION: + if (webrtc->pending_local_description) + g_value_set_boxed (value, webrtc->pending_local_description); + else if (webrtc->current_local_description) + g_value_set_boxed (value, webrtc->current_local_description); + else + g_value_set_boxed (value, NULL); + break; + case PROP_CURRENT_LOCAL_DESCRIPTION: + g_value_set_boxed (value, webrtc->current_local_description); + break; + case PROP_PENDING_LOCAL_DESCRIPTION: + g_value_set_boxed (value, webrtc->pending_local_description); + break; + case PROP_REMOTE_DESCRIPTION: + if (webrtc->pending_remote_description) + g_value_set_boxed (value, webrtc->pending_remote_description); + else if (webrtc->current_remote_description) + g_value_set_boxed (value, webrtc->current_remote_description); + else + g_value_set_boxed (value, NULL); + break; + case PROP_CURRENT_REMOTE_DESCRIPTION: + g_value_set_boxed (value, webrtc->current_remote_description); + break; + case PROP_PENDING_REMOTE_DESCRIPTION: + g_value_set_boxed (value, webrtc->pending_remote_description); + break; + case PROP_STUN_SERVER: + case PROP_TURN_SERVER: + g_object_get_property (G_OBJECT (webrtc->priv->ice), pspec->name, value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + PC_UNLOCK (webrtc); +} + +static void +_free_pending_pad (GstPad * pad) +{ + gst_object_unref (pad); +} + +static void +gst_webrtc_bin_dispose (GObject * object) +{ + GstWebRTCBin *webrtc = GST_WEBRTC_BIN (object); + + _stop_thread (webrtc); + + if (webrtc->priv->ice) + gst_object_unref (webrtc->priv->ice); + webrtc->priv->ice = NULL; + + if (webrtc->priv->ice_stream_map) + g_array_free (webrtc->priv->ice_stream_map, TRUE); + webrtc->priv->ice_stream_map = NULL; + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_webrtc_bin_finalize (GObject * object) +{ + GstWebRTCBin *webrtc = GST_WEBRTC_BIN (object); + + if (webrtc->priv->transports) + g_array_free (webrtc->priv->transports, TRUE); + webrtc->priv->transports = NULL; + + if (webrtc->priv->transceivers) + g_array_free (webrtc->priv->transceivers, TRUE); + webrtc->priv->transceivers = NULL; + + if (webrtc->priv->pending_ice_candidates) + g_array_free (webrtc->priv->pending_ice_candidates, TRUE); + webrtc->priv->pending_ice_candidates = NULL; + + if (webrtc->priv->session_mid_map) + g_array_free (webrtc->priv->session_mid_map, TRUE); + webrtc->priv->session_mid_map = NULL; + + if (webrtc->priv->pending_pads) + g_list_free_full (webrtc->priv->pending_pads, + (GDestroyNotify) _free_pending_pad); + webrtc->priv->pending_pads = NULL; + + if (webrtc->current_local_description) + gst_webrtc_session_description_free (webrtc->current_local_description); + webrtc->current_local_description = NULL; + if (webrtc->pending_local_description) + gst_webrtc_session_description_free (webrtc->pending_local_description); + webrtc->pending_local_description = NULL; + + if (webrtc->current_remote_description) + gst_webrtc_session_description_free (webrtc->current_remote_description); + webrtc->current_remote_description = NULL; + if (webrtc->pending_remote_description) + gst_webrtc_session_description_free (webrtc->pending_remote_description); + webrtc->pending_remote_description = NULL; + + if (webrtc->priv->stats) + gst_structure_free (webrtc->priv->stats); + webrtc->priv->stats = NULL; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_webrtc_bin_class_init (GstWebRTCBinClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GstElementClass *element_class = (GstElementClass *) klass; + + g_type_class_add_private (klass, sizeof (GstWebRTCBinPrivate)); + + element_class->request_new_pad = gst_webrtc_bin_request_new_pad; + element_class->release_pad = gst_webrtc_bin_release_pad; + element_class->change_state = gst_webrtc_bin_change_state; + + gst_element_class_add_static_pad_template (element_class, &sink_template); + gst_element_class_add_static_pad_template (element_class, &src_template); + + gst_element_class_set_metadata (element_class, "WebRTC Bin", + "Filter/Network/WebRTC", "A bin for webrtc connections", + "Matthew Waters "); + + gobject_class->get_property = gst_webrtc_bin_get_property; + gobject_class->set_property = gst_webrtc_bin_set_property; + gobject_class->dispose = gst_webrtc_bin_dispose; + gobject_class->finalize = gst_webrtc_bin_finalize; + + g_object_class_install_property (gobject_class, + PROP_LOCAL_DESCRIPTION, + g_param_spec_boxed ("local-description", "Local Description", + "The local SDP description to use for this connection", + GST_TYPE_WEBRTC_SESSION_DESCRIPTION, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_REMOTE_DESCRIPTION, + g_param_spec_boxed ("remote-description", "Remote Description", + "The remote SDP description to use for this connection", + GST_TYPE_WEBRTC_SESSION_DESCRIPTION, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_STUN_SERVER, + g_param_spec_string ("stun-server", "STUN Server", + "The STUN server of the form stun://hostname:port", + NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_TURN_SERVER, + g_param_spec_string ("turn-server", "TURN Server", + "The TURN server of the form turn(s)://username:password@host:port", + NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_CONNECTION_STATE, + g_param_spec_enum ("connection-state", "Connection State", + "The overall connection state of this element", + GST_TYPE_WEBRTC_PEER_CONNECTION_STATE, + GST_WEBRTC_PEER_CONNECTION_STATE_NEW, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_SIGNALING_STATE, + g_param_spec_enum ("signaling-state", "Signaling State", + "The signaling state of this element", + GST_TYPE_WEBRTC_SIGNALING_STATE, + GST_WEBRTC_SIGNALING_STATE_STABLE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_ICE_CONNECTION_STATE, + g_param_spec_enum ("ice-connection-state", "ICE connection state", + "The collective connection state of all ICETransport's", + GST_TYPE_WEBRTC_ICE_CONNECTION_STATE, + GST_WEBRTC_ICE_CONNECTION_STATE_NEW, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_ICE_GATHERING_STATE, + g_param_spec_enum ("ice-gathering-state", "ICE gathering state", + "The collective gathering state of all ICETransport's", + GST_TYPE_WEBRTC_ICE_GATHERING_STATE, + GST_WEBRTC_ICE_GATHERING_STATE_NEW, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GstWebRTCBin::create-offer: + * @object: the #GstWebRtcBin + * @options: create-offer options + * @promise: a #GstPromise which will contain the offer + */ + gst_webrtc_bin_signals[CREATE_OFFER_SIGNAL] = + g_signal_new_class_handler ("create-offer", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_CALLBACK (gst_webrtc_bin_create_offer), NULL, NULL, + g_cclosure_marshal_generic, G_TYPE_NONE, 2, GST_TYPE_STRUCTURE, + GST_TYPE_PROMISE); + + /** + * GstWebRTCBin::create-answer: + * @object: the #GstWebRtcBin + * @options: create-answer options + * @promise: a #GstPromise which will contain the answer + */ + gst_webrtc_bin_signals[CREATE_ANSWER_SIGNAL] = + g_signal_new_class_handler ("create-answer", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_CALLBACK (gst_webrtc_bin_create_answer), NULL, NULL, + g_cclosure_marshal_generic, G_TYPE_NONE, 2, GST_TYPE_STRUCTURE, + GST_TYPE_PROMISE); + + /** + * GstWebRTCBin::set-local-description: + * @object: the #GstWebRtcBin + * @type: the type of description being set + * @sdp: a #GstSDPMessage description + * @promise (allow-none): a #GstPromise to be notified when it's set + */ + gst_webrtc_bin_signals[SET_LOCAL_DESCRIPTION_SIGNAL] = + g_signal_new_class_handler ("set-local-description", + G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_CALLBACK (gst_webrtc_bin_set_local_description), NULL, NULL, + g_cclosure_marshal_generic, G_TYPE_NONE, 2, + GST_TYPE_WEBRTC_SESSION_DESCRIPTION, GST_TYPE_PROMISE); + + /** + * GstWebRTCBin::set-remote-description: + * @object: the #GstWebRtcBin + * @type: the type of description being set + * @sdp: a #GstSDPMessage description + * @promise (allow-none): a #GstPromise to be notified when it's set + */ + gst_webrtc_bin_signals[SET_REMOTE_DESCRIPTION_SIGNAL] = + g_signal_new_class_handler ("set-remote-description", + G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_CALLBACK (gst_webrtc_bin_set_remote_description), NULL, NULL, + g_cclosure_marshal_generic, G_TYPE_NONE, 2, + GST_TYPE_WEBRTC_SESSION_DESCRIPTION, GST_TYPE_PROMISE); + + /** + * GstWebRTCBin::add-ice-candidate: + * @object: the #GstWebRtcBin + * @ice-candidate: an ice candidate + */ + gst_webrtc_bin_signals[ADD_ICE_CANDIDATE_SIGNAL] = + g_signal_new_class_handler ("add-ice-candidate", + G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_CALLBACK (gst_webrtc_bin_add_ice_candidate), NULL, NULL, + g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING); + + /** + * GstWebRTCBin::get-stats: + * @object: the #GstWebRtcBin + * @promise: a #GstPromise for the result + * + * The @promise will contain the result of retrieving the session statistics. + * The structure will be named 'application/x-webrtc-stats and contain the + * following based on the webrtc-stats spec available from + * https://www.w3.org/TR/webrtc-stats/. As the webrtc-stats spec is a draft + * and is constantly changing these statistics may be changed to fit with + * the latest spec. + * + * Each field key is a unique identifer for each RTCStats + * (https://www.w3.org/TR/webrtc/#rtcstats-dictionary) value (another + * GstStructure) in the RTCStatsReport + * (https://www.w3.org/TR/webrtc/#rtcstatsreport-object). Each supported + * field in the RTCStats subclass is outlined below. + * + * Each statistics structure contains the following values as defined by + * the RTCStats dictionary (https://www.w3.org/TR/webrtc/#rtcstats-dictionary). + * + * "timestamp" G_TYPE_DOUBLE timestamp the statistics were generated + * "type" GST_TYPE_WEBRTC_STATS_TYPE the type of statistics reported + * "id" G_TYPE_STRING unique identifier + * + * RTCCodecStats supported fields (https://w3c.github.io/webrtc-stats/#codec-dict*) + * + * "payload-type" G_TYPE_UINT the rtp payload number in use + * "clock-rate" G_TYPE_UINT the rtp clock-rate + * + * RTCRTPStreamStats supported fields (https://w3c.github.io/webrtc-stats/#streamstats-dict*) + * + * "ssrc" G_TYPE_STRING the rtp sequence src in use + * "transport-id" G_TYPE_STRING identifier for the associated RTCTransportStats for this stream + * "codec-id" G_TYPE_STRING identifier for the associated RTCCodecStats for this stream + * "fir-count" G_TYPE_UINT FIR requests received by the sender (only for local statistics) + * "pli-count" G_TYPE_UINT PLI requests received by the sender (only for local statistics) + * "nack-count" G_TYPE_UINT NACK requests received by the sender (only for local statistics) + * + * RTCReceivedStreamStats supported fields (https://w3c.github.io/webrtc-stats/#receivedrtpstats-dict*) + * + * "packets-received" G_TYPE_UINT64 number of packets received (only for local inbound) + * "bytes-received" G_TYPE_UINT64 number of bytes received (only for local inbound) + * "packets-lost" G_TYPE_UINT number of packets lost + * "jitter" G_TYPE_DOUBLE packet jitter measured in secondss + * + * RTCInboundRTPStreamStats supported fields (https://w3c.github.io/webrtc-stats/#inboundrtpstats-dict*) + * + * "remote-id" G_TYPE_STRING identifier for the associated RTCRemoteOutboundRTPSTreamStats + * + * RTCRemoteInboundRTPStreamStats supported fields (https://w3c.github.io/webrtc-stats/#remoteinboundrtpstats-dict*) + * + * "local-id" G_TYPE_STRING identifier for the associated RTCOutboundRTPSTreamStats + * "round-trip-time" G_TYPE_DOUBLE round trip time of packets measured in seconds + * + * RTCSentRTPStreamStats supported fields (https://w3c.github.io/webrtc-stats/#sentrtpstats-dict*) + * + * "packets-sent" G_TYPE_UINT64 number of packets sent (only for local outbound) + * "bytes-sent" G_TYPE_UINT64 number of packets sent (only for local outbound) + * + * RTCOutboundRTPStreamStats supported fields (https://w3c.github.io/webrtc-stats/#outboundrtpstats-dict*) + * + * "remote-id" G_TYPE_STRING identifier for the associated RTCRemoteInboundRTPSTreamStats + * + * RTCRemoteOutboundRTPStreamStats supported fields (https://w3c.github.io/webrtc-stats/#remoteoutboundrtpstats-dict*) + * + * "local-id" G_TYPE_STRING identifier for the associated RTCInboundRTPSTreamStats + * + */ + gst_webrtc_bin_signals[GET_STATS_SIGNAL] = + g_signal_new_class_handler ("get-stats", + G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_CALLBACK (gst_webrtc_bin_get_stats), NULL, NULL, + g_cclosure_marshal_generic, G_TYPE_NONE, 2, GST_TYPE_PAD, + GST_TYPE_PROMISE); + + /** + * GstWebRTCBin::on-negotiation-needed: + * @object: the #GstWebRtcBin + */ + gst_webrtc_bin_signals[ON_NEGOTIATION_NEEDED_SIGNAL] = + g_signal_new ("on-negotiation-needed", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic, + G_TYPE_NONE, 0); + + /** + * GstWebRTCBin::on-ice-candidate: + * @object: the #GstWebRtcBin + * @candidate: the ICE candidate + */ + gst_webrtc_bin_signals[ON_ICE_CANDIDATE_SIGNAL] = + g_signal_new ("on-ice-candidate", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic, + G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING); + + /** + * GstWebRTCBin::add-transceiver: + * @object: the #GstWebRtcBin + * @direction: the direction of the new transceiver + * @caps: (allow none): the codec preferences for this transceiver + * + * Returns: the new #GstWebRTCRTPTransceiver + */ + gst_webrtc_bin_signals[ADD_TRANSCEIVER_SIGNAL] = + g_signal_new_class_handler ("add-transceiver", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_CALLBACK (gst_webrtc_bin_add_transceiver), NULL, NULL, + g_cclosure_marshal_generic, GST_TYPE_WEBRTC_RTP_TRANSCEIVER, 2, + GST_TYPE_WEBRTC_RTP_TRANSCEIVER_DIRECTION, GST_TYPE_CAPS); + + /** + * GstWebRTCBin::get-transceivers: + * @object: the #GstWebRtcBin + * + * Returns: a #GArray of #GstWebRTCRTPTransceivers + */ + gst_webrtc_bin_signals[GET_TRANSCEIVERS_SIGNAL] = + g_signal_new_class_handler ("get-transceivers", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_CALLBACK (gst_webrtc_bin_get_transceivers), NULL, NULL, + g_cclosure_marshal_generic, G_TYPE_ARRAY, 0); +} + +static void +_deref_unparent_and_unref (GObject ** object) +{ + GstObject *obj = GST_OBJECT (*object); + + GST_OBJECT_PARENT (obj) = NULL; + + gst_object_unref (*object); +} + +static void +_transport_free (GObject ** object) +{ + TransportStream *stream = (TransportStream *) * object; + GstWebRTCBin *webrtc; + + webrtc = GST_WEBRTC_BIN (GST_OBJECT_PARENT (stream)); + + if (stream->transport) { + g_signal_handlers_disconnect_by_data (stream->transport->transport, webrtc); + g_signal_handlers_disconnect_by_data (stream->transport, webrtc); + } + if (stream->rtcp_transport) { + g_signal_handlers_disconnect_by_data (stream->rtcp_transport->transport, + webrtc); + g_signal_handlers_disconnect_by_data (stream->rtcp_transport, webrtc); + } + + gst_object_unref (*object); +} + +static void +gst_webrtc_bin_init (GstWebRTCBin * webrtc) +{ + webrtc->priv = + G_TYPE_INSTANCE_GET_PRIVATE ((webrtc), GST_TYPE_WEBRTC_BIN, + GstWebRTCBinPrivate); + + _start_thread (webrtc); + + webrtc->rtpbin = _create_rtpbin (webrtc); + gst_bin_add (GST_BIN (webrtc), webrtc->rtpbin); + + webrtc->priv->transceivers = g_array_new (FALSE, TRUE, sizeof (gpointer)); + g_array_set_clear_func (webrtc->priv->transceivers, + (GDestroyNotify) _deref_unparent_and_unref); + + webrtc->priv->transports = g_array_new (FALSE, TRUE, sizeof (gpointer)); + g_array_set_clear_func (webrtc->priv->transports, + (GDestroyNotify) _transport_free); + + webrtc->priv->session_mid_map = + g_array_new (FALSE, TRUE, sizeof (SessionMidItem)); + g_array_set_clear_func (webrtc->priv->session_mid_map, + (GDestroyNotify) clear_session_mid_item); + + webrtc->priv->ice = gst_webrtc_ice_new (); + g_signal_connect (webrtc->priv->ice, "on-ice-candidate", + G_CALLBACK (_on_ice_candidate), webrtc); + webrtc->priv->ice_stream_map = + g_array_new (FALSE, TRUE, sizeof (IceStreamItem)); + webrtc->priv->pending_ice_candidates = + g_array_new (FALSE, TRUE, sizeof (IceCandidateItem *)); + g_array_set_clear_func (webrtc->priv->pending_ice_candidates, + (GDestroyNotify) _clear_ice_candidate_item); +} diff --git a/ext/webrtc/gstwebrtcbin.h b/ext/webrtc/gstwebrtcbin.h new file mode 100644 index 0000000000..bbcc5f5079 --- /dev/null +++ b/ext/webrtc/gstwebrtcbin.h @@ -0,0 +1,154 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifndef __GST_WEBRTC_BIN_H__ +#define __GST_WEBRTC_BIN_H__ + +#include +#include "fwd.h" +#include "gstwebrtcice.h" + +G_BEGIN_DECLS + +#define GST_WEBRTC_BIN_ERROR gst_webrtc_bin_error_quark () +GQuark gst_webrtc_bin_error_quark (void); + +typedef enum +{ + GST_WEBRTC_BIN_ERROR_FAILED, + GST_WEBRTC_BIN_ERROR_INVALID_SYNTAX, + GST_WEBRTC_BIN_ERROR_INVALID_MODIFICATION, + GST_WEBRTC_BIN_ERROR_INVALID_STATE, + GST_WEBRTC_BIN_ERROR_BAD_SDP, + GST_WEBRTC_BIN_ERROR_FINGERPRINT, +} GstWebRTCJSEPSDPError; + +GType gst_webrtc_bin_pad_get_type(void); +#define GST_TYPE_WEBRTC_BIN_PAD (gst_webrtc_bin_pad_get_type()) +#define GST_WEBRTC_BIN_PAD(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_WEBRTC_BIN_PAD,GstWebRTCBinPad)) +#define GST_IS_WEBRTC_BIN_PAD(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_WEBRTC_BIN_PAD)) +#define GST_WEBRTC_BIN_PAD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_WEBRTC_BIN_PAD,GstWebRTCBinPadClass)) +#define GST_IS_WEBRTC_BIN_PAD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_WEBRTC_BIN_PAD)) +#define GST_WEBRTC_BIN_PAD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_WEBRTC_BIN_PAD,GstWebRTCBinPadClass)) + +typedef struct _GstWebRTCBinPad GstWebRTCBinPad; +typedef struct _GstWebRTCBinPadClass GstWebRTCBinPadClass; + +struct _GstWebRTCBinPad +{ + GstGhostPad parent; + + guint mlineindex; + + GstWebRTCRTPTransceiver *trans; +}; + +struct _GstWebRTCBinPadClass +{ + GstGhostPadClass parent_class; +}; + +GType gst_webrtc_bin_get_type(void); +#define GST_TYPE_WEBRTC_BIN (gst_webrtc_bin_get_type()) +#define GST_WEBRTC_BIN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_WEBRTC_BIN,GstWebRTCBin)) +#define GST_IS_WEBRTC_BIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_WEBRTC_BIN)) +#define GST_WEBRTC_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_WEBRTC_BIN,GstWebRTCBinClass)) +#define GST_IS_WEBRTC_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_WEBRTC_BIN)) +#define GST_WEBRTC_BIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_WEBRTC_BIN,GstWebRTCBinClass)) + +struct _GstWebRTCBin +{ + GstBin parent; + + GstElement *rtpbin; + + GstWebRTCSignalingState signaling_state; + GstWebRTCICEGatheringState ice_gathering_state; + GstWebRTCICEConnectionState ice_connection_state; + GstWebRTCPeerConnectionState peer_connection_state; + + GstWebRTCSessionDescription *current_local_description; + GstWebRTCSessionDescription *pending_local_description; + GstWebRTCSessionDescription *current_remote_description; + GstWebRTCSessionDescription *pending_remote_description; + + GstWebRTCBinPrivate *priv; +}; + +struct _GstWebRTCBinClass +{ + GstBinClass parent_class; +}; + +struct _GstWebRTCBinPrivate +{ + guint max_sink_pad_serial; + + gboolean bundle; + GArray *transceivers; + GArray *session_mid_map; + GArray *transports; + + GstWebRTCICE *ice; + GArray *ice_stream_map; + GArray *pending_ice_candidates; + + /* peerconnection variables */ + gboolean is_closed; + gboolean need_negotiation; + gpointer sctp_transport; /* FIXME */ + + /* peerconnection helper thread for promises */ + GMainContext *main_context; + GMainLoop *loop; + GThread *thread; + GMutex pc_lock; + GCond pc_cond; + + gboolean running; + gboolean async_pending; + + GList *pending_pads; + + /* count of the number of media streams we've offered for uniqueness */ + /* FIXME: overflow? */ + guint media_counter; + + GstStructure *stats; +}; + +typedef void (*GstWebRTCBinFunc) (GstWebRTCBin * webrtc, gpointer data); + +typedef struct +{ + GstWebRTCBin *webrtc; + GstWebRTCBinFunc op; + gpointer data; + GDestroyNotify notify; +// GstPromise *promise; /* FIXME */ +} GstWebRTCBinTask; + +void gst_webrtc_bin_enqueue_task (GstWebRTCBin * pc, + GstWebRTCBinFunc func, + gpointer data, + GDestroyNotify notify); + +G_END_DECLS + +#endif /* __GST_WEBRTC_BIN_H__ */ diff --git a/ext/webrtc/gstwebrtcice.c b/ext/webrtc/gstwebrtcice.c new file mode 100644 index 0000000000..ce30d3b44f --- /dev/null +++ b/ext/webrtc/gstwebrtcice.c @@ -0,0 +1,887 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "gstwebrtcice.h" +/* libnice */ +#include +#include "icestream.h" +#include "nicetransport.h" + +/* XXX: + * + * - are locally generated remote candidates meant to be readded to libnice? + */ + +#define GST_CAT_DEFAULT gst_webrtc_ice_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +#define gst_webrtc_ice_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstWebRTCICE, gst_webrtc_ice, + GST_TYPE_OBJECT, + GST_DEBUG_CATEGORY_INIT (gst_webrtc_ice_debug, "webrtcice", 0, "webrtcice"); + ); + +GQuark +gst_webrtc_ice_error_quark (void) +{ + return g_quark_from_static_string ("gst-webrtc-ice-error-quark"); +} + +enum +{ + SIGNAL_0, + ON_ICE_CANDIDATE_SIGNAL, + ON_ICE_GATHERING_STATE_CHANGE_SIGNAL, + LAST_SIGNAL, +}; + +enum +{ + PROP_0, + PROP_ICE_GATHERING_STATE, + PROP_STUN_SERVER, + PROP_TURN_SERVER, + PROP_CONTROLLER, + PROP_AGENT, +}; + +static guint gst_webrtc_ice_signals[LAST_SIGNAL] = { 0 }; + +struct _GstWebRTCICEPrivate +{ + NiceAgent *nice_agent; + + GArray *nice_stream_map; + + GThread *thread; + GMainContext *main_context; + GMainLoop *loop; + GMutex lock; + GCond cond; +}; + +static gboolean +_unlock_pc_thread (GMutex * lock) +{ + g_mutex_unlock (lock); + return G_SOURCE_REMOVE; +} + +static gpointer +_gst_nice_thread (GstWebRTCICE * ice) +{ + g_mutex_lock (&ice->priv->lock); + ice->priv->main_context = g_main_context_new (); + ice->priv->loop = g_main_loop_new (ice->priv->main_context, FALSE); + + g_cond_broadcast (&ice->priv->cond); + g_main_context_invoke (ice->priv->main_context, + (GSourceFunc) _unlock_pc_thread, &ice->priv->lock); + + g_main_loop_run (ice->priv->loop); + + g_mutex_lock (&ice->priv->lock); + g_main_context_unref (ice->priv->main_context); + ice->priv->main_context = NULL; + g_main_loop_unref (ice->priv->loop); + ice->priv->loop = NULL; + g_cond_broadcast (&ice->priv->cond); + g_mutex_unlock (&ice->priv->lock); + + return NULL; +} + +static void +_start_thread (GstWebRTCICE * ice) +{ + g_mutex_lock (&ice->priv->lock); + ice->priv->thread = g_thread_new ("gst-nice-ops", + (GThreadFunc) _gst_nice_thread, ice); + + while (!ice->priv->loop) + g_cond_wait (&ice->priv->cond, &ice->priv->lock); + g_mutex_unlock (&ice->priv->lock); +} + +static void +_stop_thread (GstWebRTCICE * ice) +{ + g_mutex_lock (&ice->priv->lock); + g_main_loop_quit (ice->priv->loop); + while (ice->priv->loop) + g_cond_wait (&ice->priv->cond, &ice->priv->lock); + g_mutex_unlock (&ice->priv->lock); + + g_thread_unref (ice->priv->thread); +} + +#if 0 +static NiceComponentType +_webrtc_component_to_nice (GstWebRTCICEComponent comp) +{ + switch (comp) { + case GST_WEBRTC_ICE_COMPONENT_RTP: + return NICE_COMPONENT_TYPE_RTP; + case GST_WEBRTC_ICE_COMPONENT_RTCP: + return NICE_COMPONENT_TYPE_RTCP; + default: + g_assert_not_reached (); + return 0; + } +} + +static GstWebRTCICEComponent +_nice_component_to_webrtc (NiceComponentType comp) +{ + switch (comp) { + case NICE_COMPONENT_TYPE_RTP: + return GST_WEBRTC_ICE_COMPONENT_RTP; + case NICE_COMPONENT_TYPE_RTCP: + return GST_WEBRTC_ICE_COMPONENT_RTCP; + default: + g_assert_not_reached (); + return 0; + } +} +#endif +struct NiceStreamItem +{ + guint session_id; + guint nice_stream_id; + GstWebRTCICEStream *stream; +}; + +/* TRUE to continue, FALSE to stop */ +typedef gboolean (*NiceStreamItemForeachFunc) (struct NiceStreamItem * item, + gpointer user_data); + +static void +_nice_stream_item_foreach (GstWebRTCICE * ice, NiceStreamItemForeachFunc func, + gpointer data) +{ + int i, len; + + len = ice->priv->nice_stream_map->len; + for (i = 0; i < len; i++) { + struct NiceStreamItem *item = + &g_array_index (ice->priv->nice_stream_map, struct NiceStreamItem, + i); + + if (!func (item, data)) + break; + } +} + +/* TRUE for match, FALSE otherwise */ +typedef gboolean (*NiceStreamItemFindFunc) (struct NiceStreamItem * item, + gpointer user_data); + +struct nice_find +{ + NiceStreamItemFindFunc func; + gpointer data; + struct NiceStreamItem *ret; +}; + +static gboolean +_find_nice_item (struct NiceStreamItem *item, gpointer user_data) +{ + struct nice_find *f = user_data; + if (f->func (item, f->data)) { + f->ret = item; + return FALSE; + } + return TRUE; +} + +static struct NiceStreamItem * +_nice_stream_item_find (GstWebRTCICE * ice, NiceStreamItemFindFunc func, + gpointer data) +{ + struct nice_find f; + + f.func = func; + f.data = data; + f.ret = NULL; + + _nice_stream_item_foreach (ice, _find_nice_item, &f); + + return f.ret; +} + +#define NICE_MATCH_INIT { -1, -1, NULL } + +static gboolean +_match (struct NiceStreamItem *item, struct NiceStreamItem *m) +{ + if (m->session_id != -1 && m->session_id != item->session_id) + return FALSE; + if (m->nice_stream_id != -1 && m->nice_stream_id != item->nice_stream_id) + return FALSE; + if (m->stream != NULL && m->stream != item->stream) + return FALSE; + + return TRUE; +} + +static struct NiceStreamItem * +_find_item (GstWebRTCICE * ice, guint session_id, guint nice_stream_id, + GstWebRTCICEStream * stream) +{ + struct NiceStreamItem m = NICE_MATCH_INIT; + + m.session_id = session_id; + m.nice_stream_id = nice_stream_id; + m.stream = stream; + + return _nice_stream_item_find (ice, (NiceStreamItemFindFunc) _match, &m); +} + +static struct NiceStreamItem * +_create_nice_stream_item (GstWebRTCICE * ice, guint session_id) +{ + struct NiceStreamItem item; + + item.session_id = session_id; + item.nice_stream_id = nice_agent_add_stream (ice->priv->nice_agent, 2); + item.stream = gst_webrtc_ice_stream_new (ice, item.nice_stream_id); + g_array_append_val (ice->priv->nice_stream_map, item); + + return _find_item (ice, item.session_id, item.nice_stream_id, item.stream); +} + +static void +_parse_userinfo (const gchar * userinfo, gchar ** user, gchar ** pass) +{ + const gchar *colon; + + if (!userinfo) { + *user = NULL; + *pass = NULL; + return; + } + + colon = g_strstr_len (userinfo, -1, ":"); + if (!colon) { + *user = g_strdup (userinfo); + *pass = NULL; + return; + } + + *user = g_strndup (userinfo, colon - userinfo); + *pass = g_strdup (&colon[1]); +} + +GstWebRTCICEStream * +gst_webrtc_ice_add_stream (GstWebRTCICE * ice, guint session_id) +{ + struct NiceStreamItem m = NICE_MATCH_INIT; + struct NiceStreamItem *item; + + m.session_id = session_id; + item = _nice_stream_item_find (ice, (NiceStreamItemFindFunc) _match, &m); + if (item) { + GST_ERROR_OBJECT (ice, "stream already added with session_id=%u", + session_id); + return 0; + } + + item = _create_nice_stream_item (ice, session_id); + + if (ice->turn_server) { + gboolean ret; + gchar *user, *pass; + const gchar *userinfo, *transport, *scheme; + NiceRelayType relays[4] = { 0, }; + int i, relay_n = 0; + + scheme = gst_uri_get_scheme (ice->turn_server); + transport = gst_uri_get_query_value (ice->turn_server, "transport"); + userinfo = gst_uri_get_userinfo (ice->turn_server); + _parse_userinfo (userinfo, &user, &pass); + + if (g_strcmp0 (scheme, "turns") == 0) { + relays[relay_n++] = NICE_RELAY_TYPE_TURN_TLS; + } else if (g_strcmp0 (scheme, "turn") == 0) { + if (!transport || g_strcmp0 (transport, "udp") == 0) + relays[relay_n++] = NICE_RELAY_TYPE_TURN_UDP; + if (!transport || g_strcmp0 (transport, "tcp") == 0) + relays[relay_n++] = NICE_RELAY_TYPE_TURN_TCP; + } + g_assert (relay_n < G_N_ELEMENTS (relays)); + + for (i = 0; i < relay_n; i++) { + ret = nice_agent_set_relay_info (ice->priv->nice_agent, + item->nice_stream_id, NICE_COMPONENT_TYPE_RTP, + gst_uri_get_host (ice->turn_server), + gst_uri_get_port (ice->turn_server), user, pass, relays[i]); + if (!ret) { + gchar *uri = gst_uri_to_string (ice->turn_server); + GST_ERROR_OBJECT (ice, "Failed to set TURN server '%s'", uri); + g_free (uri); + break; + } + ret = nice_agent_set_relay_info (ice->priv->nice_agent, + item->nice_stream_id, NICE_COMPONENT_TYPE_RTCP, + gst_uri_get_host (ice->turn_server), + gst_uri_get_port (ice->turn_server), user, pass, relays[i]); + if (!ret) { + gchar *uri = gst_uri_to_string (ice->turn_server); + GST_ERROR_OBJECT (ice, "Failed to set TURN server '%s'", uri); + g_free (uri); + break; + } + } + g_free (user); + g_free (pass); + } + + return item->stream; +} + +static void +_on_new_candidate (NiceAgent * agent, NiceCandidate * candidate, + GstWebRTCICE * ice) +{ + struct NiceStreamItem *item; + gchar *attr; + + item = _find_item (ice, -1, candidate->stream_id, NULL); + if (!item) { + GST_WARNING_OBJECT (ice, "received signal for non-existent stream %u", + candidate->stream_id); + return; + } + + if (!candidate->username || !candidate->password) { + gboolean got_credentials; + gchar *ufrag, *password; + + got_credentials = nice_agent_get_local_credentials (ice->priv->nice_agent, + candidate->stream_id, &ufrag, &password); + g_warn_if_fail (got_credentials); + + if (!candidate->username) + candidate->username = ufrag; + else + g_free (ufrag); + + if (!candidate->password) + candidate->password = password; + else + g_free (password); + } + + attr = nice_agent_generate_local_candidate_sdp (agent, candidate); + g_signal_emit (ice, gst_webrtc_ice_signals[ON_ICE_CANDIDATE_SIGNAL], + 0, item->session_id, attr); + g_free (attr); +} + +GstWebRTCICETransport * +gst_webrtc_ice_find_transport (GstWebRTCICE * ice, GstWebRTCICEStream * stream, + GstWebRTCICEComponent component) +{ + struct NiceStreamItem *item; + + item = _find_item (ice, -1, -1, stream); + g_return_val_if_fail (item != NULL, NULL); + + return gst_webrtc_ice_stream_find_transport (item->stream, component); +} + +#if 0 +/* TODO don't rely on libnice to (de)serialize candidates */ +static NiceCandidateType +_candidate_type_from_string (const gchar * s) +{ + if (g_strcmp0 (s, "host") == 0) { + return NICE_CANDIDATE_TYPE_HOST; + } else if (g_strcmp0 (s, "srflx") == 0) { + return NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE; + } else if (g_strcmp0 (s, "prflx") == 0) { /* FIXME: is the right string? */ + return NICE_CANDIDATE_TYPE_PEER_REFLEXIVE; + } else if (g_strcmp0 (s, "relay") == 0) { + return NICE_CANDIDATE_TYPE_RELAY; + } else { + g_assert_not_reached (); + return 0; + } +} + +static const gchar * +_candidate_type_to_string (NiceCandidateType type) +{ + switch (type) { + case NICE_CANDIDATE_TYPE_HOST: + return "host"; + case NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE: + return "srflx"; + case NICE_CANDIDATE_TYPE_PEER_REFLEXIVE: + return "prflx"; + case NICE_CANDIDATE_TYPE_RELAY: + return "relay"; + default: + g_assert_not_reached (); + return NULL; + } +} + +static NiceCandidateTransport +_candidate_transport_from_string (const gchar * s) +{ + if (g_strcmp0 (s, "UDP") == 0) { + return NICE_CANDIDATE_TRANSPORT_UDP; + } else if (g_strcmp0 (s, "TCP tcptype") == 0) { + return NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE; + } else if (g_strcmp0 (s, "tcp-passive") == 0) { /* FIXME: is the right string? */ + return NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE; + } else if (g_strcmp0 (s, "tcp-so") == 0) { + return NICE_CANDIDATE_TRANSPORT_TCP_SO; + } else { + g_assert_not_reached (); + return 0; + } +} + +static const gchar * +_candidate_type_to_string (NiceCandidateType type) +{ + switch (type) { + case NICE_CANDIDATE_TYPE_HOST: + return "host"; + case NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE: + return "srflx"; + case NICE_CANDIDATE_TYPE_PEER_REFLEXIVE: + return "prflx"; + case NICE_CANDIDATE_TYPE_RELAY: + return "relay"; + default: + g_assert_not_reached (); + return NULL; + } +} +#endif + +/* must start with "a=candidate:" */ +void +gst_webrtc_ice_add_candidate (GstWebRTCICE * ice, GstWebRTCICEStream * stream, + const gchar * candidate) +{ + struct NiceStreamItem *item; + NiceCandidate *cand; + GSList *candidates = NULL; + + item = _find_item (ice, -1, -1, stream); + g_return_if_fail (item != NULL); + + cand = + nice_agent_parse_remote_candidate_sdp (ice->priv->nice_agent, + item->nice_stream_id, candidate); + if (!cand) { + GST_WARNING_OBJECT (ice, "Could not parse candidate \'%s\'", candidate); + return; + } + + candidates = g_slist_append (candidates, cand); + + nice_agent_set_remote_candidates (ice->priv->nice_agent, item->nice_stream_id, + cand->component_id, candidates); + + g_slist_free (candidates); + nice_candidate_free (cand); +} + +gboolean +gst_webrtc_ice_set_remote_credentials (GstWebRTCICE * ice, + GstWebRTCICEStream * stream, gchar * ufrag, gchar * pwd) +{ + struct NiceStreamItem *item; + + g_return_val_if_fail (ufrag != NULL, FALSE); + g_return_val_if_fail (pwd != NULL, FALSE); + item = _find_item (ice, -1, -1, stream); + g_return_val_if_fail (item != NULL, FALSE); + + GST_DEBUG_OBJECT (ice, "Setting remote ICE credentials on " + "ICE stream %u ufrag:%s pwd:%s", item->nice_stream_id, ufrag, pwd); + + nice_agent_set_remote_credentials (ice->priv->nice_agent, + item->nice_stream_id, ufrag, pwd); + + return TRUE; +} + +gboolean +gst_webrtc_ice_set_local_credentials (GstWebRTCICE * ice, + GstWebRTCICEStream * stream, gchar * ufrag, gchar * pwd) +{ + struct NiceStreamItem *item; + + g_return_val_if_fail (ufrag != NULL, FALSE); + g_return_val_if_fail (pwd != NULL, FALSE); + item = _find_item (ice, -1, -1, stream); + g_return_val_if_fail (item != NULL, FALSE); + + GST_DEBUG_OBJECT (ice, "Setting local ICE credentials on " + "ICE stream %u ufrag:%s pwd:%s", item->nice_stream_id, ufrag, pwd); + + nice_agent_set_local_credentials (ice->priv->nice_agent, item->nice_stream_id, + ufrag, pwd); + + return TRUE; +} + +gboolean +gst_webrtc_ice_gather_candidates (GstWebRTCICE * ice, + GstWebRTCICEStream * stream) +{ + struct NiceStreamItem *item; + + item = _find_item (ice, -1, -1, stream); + g_return_val_if_fail (item != NULL, FALSE); + + GST_DEBUG_OBJECT (ice, "gather candidates for stream %u", + item->nice_stream_id); + + return gst_webrtc_ice_stream_gather_candidates (stream); +} + +static void +_clear_ice_stream (struct NiceStreamItem *item) +{ + if (!item) + return; + + if (item->stream) { + g_signal_handlers_disconnect_by_data (item->stream->ice->priv->nice_agent, + item->stream); + gst_object_unref (item->stream); + } +} + +static gchar * +_resolve_host (const gchar * host) +{ + GResolver *resolver = g_resolver_get_default (); + GError *error = NULL; + GInetAddress *addr; + GList *addresses; + + if (!(addresses = g_resolver_lookup_by_name (resolver, host, NULL, &error))) { + GST_ERROR ("%s", error->message); + g_clear_error (&error); + return NULL; + } + + /* XXX: only the first address is used */ + addr = addresses->data; + + return g_inet_address_to_string (addr); +} + +static void +_set_turn_server (GstWebRTCICE * ice, const gchar * s) +{ + GstUri *uri = gst_uri_from_string (s); + const gchar *userinfo, *host, *scheme; + GList *keys = NULL, *l; + gchar *ip = NULL, *user = NULL, *pass = NULL; + gboolean turn_tls = FALSE; + guint port; + + GST_DEBUG_OBJECT (ice, "setting turn server, %s", s); + + if (!uri) { + GST_ERROR_OBJECT (ice, "Could not parse turn server '%s'", s); + return; + } + + scheme = gst_uri_get_scheme (uri); + if (g_strcmp0 (scheme, "turn") == 0) { + } else if (g_strcmp0 (scheme, "turns") == 0) { + turn_tls = TRUE; + } else { + GST_ERROR_OBJECT (ice, "unknown scheme '%s'", scheme); + goto out; + } + + keys = gst_uri_get_query_keys (uri); + for (l = keys; l; l = l->next) { + gchar *key = l->data; + + if (g_strcmp0 (key, "transport") == 0) { + const gchar *transport = gst_uri_get_query_value (uri, "transport"); + if (!transport) { + } else if (g_strcmp0 (transport, "udp") == 0) { + } else if (g_strcmp0 (transport, "tcp") == 0) { + } else { + GST_ERROR_OBJECT (ice, "unknown transport value, '%s'", transport); + goto out; + } + } else { + GST_ERROR_OBJECT (ice, "unknown query key, '%s'", key); + goto out; + } + } + + /* TODO: Implement error checking similar to the stun server below */ + userinfo = gst_uri_get_userinfo (uri); + _parse_userinfo (userinfo, &user, &pass); + if (!user) { + GST_ERROR_OBJECT (ice, "No username specified in '%s'", s); + goto out; + } + if (!pass) { + GST_ERROR_OBJECT (ice, "No password specified in '%s'", s); + goto out; + } + + host = gst_uri_get_host (uri); + if (!host) { + GST_ERROR_OBJECT (ice, "Turn server has no host"); + goto out; + } + ip = _resolve_host (host); + if (!ip) { + GST_ERROR_OBJECT (ice, "Failed to resolve turn server '%s'", host); + goto out; + } + port = gst_uri_get_port (uri); + + if (port == GST_URI_NO_PORT) { + if (turn_tls) { + gst_uri_set_port (uri, 5349); + } else { + gst_uri_set_port (uri, 3478); + } + } + /* Set the resolved IP as the host since that's what libnice wants */ + gst_uri_set_host (uri, ip); + + if (ice->turn_server) + gst_uri_unref (ice->turn_server); + ice->turn_server = uri; + +out: + g_list_free (keys); + g_free (ip); + g_free (user); + g_free (pass); +} + +static void +gst_webrtc_ice_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstWebRTCICE *ice = GST_WEBRTC_ICE (object); + + switch (prop_id) { + case PROP_STUN_SERVER:{ + const gchar *s = g_value_get_string (value); + GstUri *uri = gst_uri_from_string (s); + const gchar *msg = "must be of the form stun://:"; + const gchar *host; + gchar *ip; + guint port; + + GST_DEBUG_OBJECT (ice, "setting stun server, %s", s); + + if (!uri) { + GST_ERROR_OBJECT (ice, "Couldn't parse stun server '%s', %s", s, msg); + return; + } + + host = gst_uri_get_host (uri); + if (!host) { + GST_ERROR_OBJECT (ice, "Stun server '%s' has no host, %s", s, msg); + return; + } + port = gst_uri_get_port (uri); + if (port == GST_URI_NO_PORT) { + GST_INFO_OBJECT (ice, "Stun server '%s' has no port, assuming 3478", s); + port = 3478; + gst_uri_set_port (uri, port); + } + + ip = _resolve_host (host); + if (!ip) { + GST_ERROR_OBJECT (ice, "Failed to resolve stun server '%s'", host); + return; + } + + if (ice->stun_server) + gst_uri_unref (ice->stun_server); + ice->stun_server = uri; + + g_object_set (ice->priv->nice_agent, "stun-server", ip, + "stun-server-port", port, NULL); + + g_free (ip); + break; + } + case PROP_TURN_SERVER:{ + _set_turn_server (ice, g_value_get_string (value)); + break; + } + case PROP_CONTROLLER: + g_object_set_property (G_OBJECT (ice->priv->nice_agent), + "controlling-mode", value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_webrtc_ice_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstWebRTCICE *ice = GST_WEBRTC_ICE (object); + + switch (prop_id) { + case PROP_STUN_SERVER: + if (ice->stun_server) + g_value_take_string (value, gst_uri_to_string (ice->stun_server)); + else + g_value_take_string (value, NULL); + break; + case PROP_TURN_SERVER: + if (ice->turn_server) + g_value_take_string (value, gst_uri_to_string (ice->turn_server)); + else + g_value_take_string (value, NULL); + break; + case PROP_CONTROLLER: + g_object_get_property (G_OBJECT (ice->priv->nice_agent), + "controlling-mode", value); + break; + case PROP_AGENT: + g_value_set_object (value, ice->priv->nice_agent); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_webrtc_ice_finalize (GObject * object) +{ + GstWebRTCICE *ice = GST_WEBRTC_ICE (object); + + g_signal_handlers_disconnect_by_data (ice->priv->nice_agent, ice); + + _stop_thread (ice); + + if (ice->turn_server) + gst_uri_unref (ice->turn_server); + if (ice->stun_server) + gst_uri_unref (ice->stun_server); + + g_mutex_clear (&ice->priv->lock); + g_cond_clear (&ice->priv->cond); + + g_array_free (ice->priv->nice_stream_map, TRUE); + + g_object_unref (ice->priv->nice_agent); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_webrtc_ice_class_init (GstWebRTCICEClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + g_type_class_add_private (klass, sizeof (GstWebRTCICEPrivate)); + + gobject_class->get_property = gst_webrtc_ice_get_property; + gobject_class->set_property = gst_webrtc_ice_set_property; + gobject_class->finalize = gst_webrtc_ice_finalize; + + g_object_class_install_property (gobject_class, + PROP_STUN_SERVER, + g_param_spec_string ("stun-server", "STUN Server", + "The STUN server of the form stun://hostname:port", + NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_TURN_SERVER, + g_param_spec_string ("turn-server", "TURN Server", + "The TURN server of the form turn(s)://username:password@host:port", + NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_CONTROLLER, + g_param_spec_boolean ("controller", "ICE controller", + "Whether the ICE agent is the controller or controlled. " + "In WebRTC, the initial offerrer is the ICE controller.", FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_AGENT, + g_param_spec_object ("agent", "ICE agent", + "ICE agent in use by this object", NICE_TYPE_AGENT, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GstWebRTCICE::on-ice-candidate: + * @object: the #GstWebRtcBin + * @candidate: the ICE candidate + */ + gst_webrtc_ice_signals[ON_ICE_CANDIDATE_SIGNAL] = + g_signal_new ("on-ice-candidate", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic, + G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING); +} + +static void +gst_webrtc_ice_init (GstWebRTCICE * ice) +{ + ice->priv = + G_TYPE_INSTANCE_GET_PRIVATE ((ice), GST_TYPE_WEBRTC_ICE, + GstWebRTCICEPrivate); + + g_mutex_init (&ice->priv->lock); + g_cond_init (&ice->priv->cond); + + _start_thread (ice); + + ice->priv->nice_agent = nice_agent_new (ice->priv->main_context, + NICE_COMPATIBILITY_RFC5245); + g_signal_connect (ice->priv->nice_agent, "new-candidate-full", + G_CALLBACK (_on_new_candidate), ice); + + ice->priv->nice_stream_map = + g_array_new (FALSE, TRUE, sizeof (struct NiceStreamItem)); + g_array_set_clear_func (ice->priv->nice_stream_map, + (GDestroyNotify) _clear_ice_stream); +} + +GstWebRTCICE * +gst_webrtc_ice_new (void) +{ + return g_object_new (GST_TYPE_WEBRTC_ICE, NULL); +} diff --git a/ext/webrtc/gstwebrtcice.h b/ext/webrtc/gstwebrtcice.h new file mode 100644 index 0000000000..cacf497a88 --- /dev/null +++ b/ext/webrtc/gstwebrtcice.h @@ -0,0 +1,83 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifndef __GST_WEBRTC_ICE_H__ +#define __GST_WEBRTC_ICE_H__ + +#include +#include +#include +#include "fwd.h" + +G_BEGIN_DECLS + +#define GST_WEBRTC_ICE_ERROR gst_webrtc_ice_error_quark () +GQuark gst_webrtc_ice_error_quark (void); + +GType gst_webrtc_ice_get_type(void); +#define GST_TYPE_WEBRTC_ICE (gst_webrtc_ice_get_type()) +#define GST_WEBRTC_ICE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_WEBRTC_ICE,GstWebRTCICE)) +#define GST_IS_WEBRTC_ICE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_WEBRTC_ICE)) +#define GST_WEBRTC_ICE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_WEBRTC_ICE,GstWebRTCICEClass)) +#define GST_IS_WEBRTC_ICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_WEBRTC_ICE)) +#define GST_WEBRTC_ICE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_WEBRTC_ICE,GstWebRTCICEClass)) + +struct _GstWebRTCICE +{ + GstObject parent; + + GstWebRTCICEGatheringState ice_gathering_state; + GstWebRTCICEConnectionState ice_connection_state; + + GstUri *stun_server; + GstUri *turn_server; + + GstWebRTCICEPrivate *priv; +}; + +struct _GstWebRTCICEClass +{ + GstObjectClass parent_class; +}; + +GstWebRTCICE * gst_webrtc_ice_new (void); +GstWebRTCICEStream * gst_webrtc_ice_add_stream (GstWebRTCICE * ice, + guint session_id); +GstWebRTCICETransport * gst_webrtc_ice_find_transport (GstWebRTCICE * ice, + GstWebRTCICEStream * stream, + GstWebRTCICEComponent component); + +gboolean gst_webrtc_ice_gather_candidates (GstWebRTCICE * ice, + GstWebRTCICEStream * stream); +/* FIXME: GstStructure-ize the candidate */ +void gst_webrtc_ice_add_candidate (GstWebRTCICE * ice, + GstWebRTCICEStream * stream, + const gchar * candidate); +gboolean gst_webrtc_ice_set_local_credentials (GstWebRTCICE * ice, + GstWebRTCICEStream * stream, + gchar * ufrag, + gchar * pwd); +gboolean gst_webrtc_ice_set_remote_credentials (GstWebRTCICE * ice, + GstWebRTCICEStream * stream, + gchar * ufrag, + gchar * pwd); + +G_END_DECLS + +#endif /* __GST_WEBRTC_ICE_H__ */ diff --git a/ext/webrtc/gstwebrtcstats.c b/ext/webrtc/gstwebrtcstats.c new file mode 100644 index 0000000000..38a6a02a24 --- /dev/null +++ b/ext/webrtc/gstwebrtcstats.c @@ -0,0 +1,549 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* for GValueArray... */ +#define GLIB_DISABLE_DEPRECATION_WARNINGS + +#include "gstwebrtcstats.h" +#include "gstwebrtcbin.h" +#include "transportstream.h" +#include "transportreceivebin.h" +#include "utils.h" +#include "webrtctransceiver.h" + +#define GST_CAT_DEFAULT gst_webrtc_stats_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +static void +_init_debug (void) +{ + static gsize _init = 0; + + if (g_once_init_enter (&_init)) { + GST_DEBUG_CATEGORY_INIT (gst_webrtc_stats_debug, "webrtcice", 0, + "webrtcice"); + g_once_init_leave (&_init, 1); + } +} + +static double +monotonic_time_as_double_milliseconds (void) +{ + return g_get_monotonic_time () / 1000.0; +} + +static void +_set_base_stats (GstStructure * s, GstWebRTCStatsType type, double ts, + const char *id) +{ + gchar *name = _enum_value_to_string (GST_TYPE_WEBRTC_STATS_TYPE, + type); + + g_return_if_fail (name != NULL); + + gst_structure_set_name (s, name); + gst_structure_set (s, "type", GST_TYPE_WEBRTC_STATS_TYPE, type, "timestamp", + G_TYPE_DOUBLE, ts, "id", G_TYPE_STRING, id, NULL); + + g_free (name); +} + +static GstStructure * +_get_peer_connection_stats (GstWebRTCBin * webrtc) +{ + GstStructure *s = gst_structure_new_empty ("unused"); + + /* FIXME: datachannel */ + gst_structure_set (s, "data-channels-opened", G_TYPE_UINT, 0, + "data-channels-closed", G_TYPE_UINT, 0, "data-channels-requested", + G_TYPE_UINT, 0, "data-channels-accepted", G_TYPE_UINT, 0, NULL); + + return s; +} + +#define CLOCK_RATE_VALUE_TO_SECONDS(v,r) ((double) v / (double) clock_rate) + +/* https://www.w3.org/TR/webrtc-stats/#inboundrtpstats-dict* + https://www.w3.org/TR/webrtc-stats/#outboundrtpstats-dict* */ +static void +_get_stats_from_rtp_source_stats (GstWebRTCBin * webrtc, + const GstStructure * source_stats, const gchar * codec_id, + const gchar * transport_id, GstStructure * s) +{ + GstStructure *in, *out, *r_in, *r_out; + gchar *in_id, *out_id, *r_in_id, *r_out_id; + guint ssrc, fir, pli, nack, jitter; + int lost, clock_rate; + guint64 packets, bytes; + gboolean have_rb = FALSE, sent_rb = FALSE; + double ts; + + gst_structure_get_double (s, "timestamp", &ts); + gst_structure_get_uint (source_stats, "ssrc", &ssrc); + gst_structure_get (source_stats, "have-rb", G_TYPE_BOOLEAN, &have_rb, + "sent_rb", G_TYPE_BOOLEAN, &sent_rb, "clock-rate", G_TYPE_INT, + &clock_rate, NULL); + + in_id = g_strdup_printf ("rtp-inbound-stream-stats_%u", ssrc); + out_id = g_strdup_printf ("rtp-outbound-stream-stats_%u", ssrc); + r_in_id = g_strdup_printf ("rtp-remote-inbound-stream-stats_%u", ssrc); + r_out_id = g_strdup_printf ("rtp-remote-outbound-stream-stats_%u", ssrc); + + in = gst_structure_new_empty (in_id); + _set_base_stats (in, GST_WEBRTC_STATS_INBOUND_RTP, ts, in_id); + + /* RTCStreamStats */ + gst_structure_set (in, "ssrc", G_TYPE_UINT, ssrc, NULL); + gst_structure_set (in, "codec-id", G_TYPE_STRING, codec_id, NULL); + gst_structure_set (in, "transport-id", G_TYPE_STRING, transport_id, NULL); + if (gst_structure_get_uint (source_stats, "recv-fir-count", &fir)) + gst_structure_set (in, "fir-count", G_TYPE_UINT, fir, NULL); + if (gst_structure_get_uint (source_stats, "recv-pli-count", &pli)) + gst_structure_set (in, "pli-count", G_TYPE_UINT, pli, NULL); + if (gst_structure_get_uint (source_stats, "recv-nack-count", &nack)) + gst_structure_set (in, "nack-count", G_TYPE_UINT, nack, NULL); + /* XXX: mediaType, trackId, sliCount, qpSum */ + + /* RTCReceivedRTPStreamStats */ + if (gst_structure_get_uint64 (source_stats, "packets-received", &packets)) + gst_structure_set (in, "packets-received", G_TYPE_UINT64, packets, NULL); + if (gst_structure_get_uint64 (source_stats, "octets-received", &bytes)) + gst_structure_set (in, "bytes-received", G_TYPE_UINT64, bytes, NULL); + if (gst_structure_get_int (source_stats, "packets-lost", &lost)) + gst_structure_set (in, "packets-lost", G_TYPE_INT, lost, NULL); + if (gst_structure_get_uint (source_stats, "jitter", &jitter)) + gst_structure_set (in, "jitter", G_TYPE_DOUBLE, + CLOCK_RATE_VALUE_TO_SECONDS (jitter, clock_rate), NULL); +/* + RTCReceivedRTPStreamStats + double fractionLost; + unsigned long packetsDiscarded; + unsigned long packetsFailedDecryption; + unsigned long packetsRepaired; + unsigned long burstPacketsLost; + unsigned long burstPacketsDiscarded; + unsigned long burstLossCount; + unsigned long burstDiscardCount; + double burstLossRate; + double burstDiscardRate; + double gapLossRate; + double gapDiscardRate; +*/ + + /* RTCInboundRTPStreamStats */ + gst_structure_set (in, "remote-id", G_TYPE_STRING, r_out_id, NULL); + /* XXX: framesDecoded, lastPacketReceivedTimestamp */ + + r_in = gst_structure_new_empty (r_in_id); + _set_base_stats (r_in, GST_WEBRTC_STATS_REMOTE_INBOUND_RTP, ts, r_in_id); + + /* RTCStreamStats */ + gst_structure_set (r_in, "ssrc", G_TYPE_UINT, ssrc, NULL); + gst_structure_set (r_in, "codec-id", G_TYPE_STRING, codec_id, NULL); + gst_structure_set (r_in, "transport-id", G_TYPE_STRING, transport_id, NULL); + /* XXX: mediaType, trackId, sliCount, qpSum */ + + /* RTCReceivedRTPStreamStats */ + if (sent_rb) { + if (gst_structure_get_uint (source_stats, "sent-rb-jitter", &jitter)) + gst_structure_set (r_in, "jitter", G_TYPE_DOUBLE, + CLOCK_RATE_VALUE_TO_SECONDS (jitter, clock_rate), NULL); + if (gst_structure_get_int (source_stats, "sent-rb-packetslost", &lost)) + gst_structure_set (r_in, "packets-lost", G_TYPE_INT, lost, NULL); + /* packetsReceived, bytesReceived */ + } else { + /* default values */ + gst_structure_set (r_in, "jitter", G_TYPE_DOUBLE, 0.0, "packets-lost", + G_TYPE_INT, 0, NULL); + } +/* XXX: RTCReceivedRTPStreamStats + double fractionLost; + unsigned long packetsDiscarded; + unsigned long packetsFailedDecryption; + unsigned long packetsRepaired; + unsigned long burstPacketsLost; + unsigned long burstPacketsDiscarded; + unsigned long burstLossCount; + unsigned long burstDiscardCount; + double burstLossRate; + double burstDiscardRate; + double gapLossRate; + double gapDiscardRate; +*/ + + /* RTCRemoteInboundRTPStreamStats */ + gst_structure_set (r_in, "local-id", G_TYPE_STRING, out_id, NULL); + if (have_rb) { + guint32 rtt; + if (gst_structure_get_uint (source_stats, "rb-round-trip", &rtt)) { + /* 16.16 fixed point to double */ + double val = + (double) ((rtt & 0xffff0000) >> 16) + ((rtt & 0xffff) / 65536.0); + gst_structure_set (r_in, "round-trip-time", G_TYPE_DOUBLE, val, NULL); + } + } else { + /* default values */ + gst_structure_set (r_in, "round-trip-time", G_TYPE_DOUBLE, 0.0, NULL); + } + /* XXX: framesDecoded, lastPacketReceivedTimestamp */ + + out = gst_structure_new_empty (out_id); + _set_base_stats (out, GST_WEBRTC_STATS_OUTBOUND_RTP, ts, out_id); + + /* RTCStreamStats */ + gst_structure_set (out, "ssrc", G_TYPE_UINT, ssrc, NULL); + gst_structure_set (out, "codec-id", G_TYPE_STRING, codec_id, NULL); + gst_structure_set (out, "transport-id", G_TYPE_STRING, transport_id, NULL); + if (gst_structure_get_uint (source_stats, "sent-fir-count", &fir)) + gst_structure_set (out, "fir-count", G_TYPE_UINT, fir, NULL); + if (gst_structure_get_uint (source_stats, "sent-pli-count", &pli)) + gst_structure_set (out, "pli-count", G_TYPE_UINT, pli, NULL); + if (gst_structure_get_uint (source_stats, "sent-nack-count", &nack)) + gst_structure_set (out, "nack-count", G_TYPE_UINT, nack, NULL); + /* XXX: mediaType, trackId, sliCount, qpSum */ + +/* RTCSentRTPStreamStats */ + if (gst_structure_get_uint64 (source_stats, "octets-sent", &bytes)) + gst_structure_set (out, "bytes-sent", G_TYPE_UINT64, bytes, NULL); + if (gst_structure_get_uint64 (source_stats, "packets-sent", &packets)) + gst_structure_set (out, "packets-sent", G_TYPE_UINT64, packets, NULL); +/* XXX: + unsigned long packetsDiscardedOnSend; + unsigned long long bytesDiscardedOnSend; +*/ + + /* RTCOutboundRTPStreamStats */ + gst_structure_set (out, "remote-id", G_TYPE_STRING, r_in_id, NULL); +/* XXX: + DOMHighResTimeStamp lastPacketSentTimestamp; + double targetBitrate; + unsigned long framesEncoded; + double totalEncodeTime; + double averageRTCPInterval; +*/ + + r_out = gst_structure_new_empty (r_out_id); + _set_base_stats (r_out, GST_WEBRTC_STATS_REMOTE_OUTBOUND_RTP, ts, r_out_id); + /* RTCStreamStats */ + gst_structure_set (r_out, "ssrc", G_TYPE_UINT, ssrc, NULL); + gst_structure_set (r_out, "codec-id", G_TYPE_STRING, codec_id, NULL); + gst_structure_set (r_out, "transport-id", G_TYPE_STRING, transport_id, NULL); + /* XXX: mediaType, trackId, sliCount, qpSum */ + +/* RTCSentRTPStreamStats */ +/* if (gst_structure_get_uint64 (source_stats, "octets-sent", &bytes)) + gst_structure_set (r_out, "bytes-sent", G_TYPE_UINT64, bytes, NULL); + if (gst_structure_get_uint64 (source_stats, "packets-sent", &packets)) + gst_structure_set (r_out, "packets-sent", G_TYPE_UINT64, packets, NULL);*/ +/* XXX: + unsigned long packetsDiscardedOnSend; + unsigned long long bytesDiscardedOnSend; +*/ + + gst_structure_set (r_out, "local-id", G_TYPE_STRING, in_id, NULL); + + gst_structure_set (s, in_id, GST_TYPE_STRUCTURE, in, NULL); + gst_structure_set (s, out_id, GST_TYPE_STRUCTURE, out, NULL); + gst_structure_set (s, r_in_id, GST_TYPE_STRUCTURE, r_in, NULL); + gst_structure_set (s, r_out_id, GST_TYPE_STRUCTURE, r_out, NULL); + + gst_structure_free (in); + gst_structure_free (out); + gst_structure_free (r_in); + gst_structure_free (r_out); + + g_free (in_id); + g_free (out_id); + g_free (r_in_id); + g_free (r_out_id); +} + +/* https://www.w3.org/TR/webrtc-stats/#candidatepair-dict* */ +static gchar * +_get_stats_from_ice_transport (GstWebRTCBin * webrtc, + GstWebRTCICETransport * transport, GstStructure * s) +{ + GstStructure *stats; + gchar *id; + double ts; + + gst_structure_get_double (s, "timestamp", &ts); + + id = g_strdup_printf ("ice-candidate-pair_%s", GST_OBJECT_NAME (transport)); + stats = gst_structure_new_empty (id); + _set_base_stats (stats, GST_WEBRTC_STATS_TRANSPORT, ts, id); + +/* XXX: RTCIceCandidatePairStats + DOMString transportId; + DOMString localCandidateId; + DOMString remoteCandidateId; + RTCStatsIceCandidatePairState state; + unsigned long long priority; + boolean nominated; + unsigned long packetsSent; + unsigned long packetsReceived; + unsigned long long bytesSent; + unsigned long long bytesReceived; + DOMHighResTimeStamp lastPacketSentTimestamp; + DOMHighResTimeStamp lastPacketReceivedTimestamp; + DOMHighResTimeStamp firstRequestTimestamp; + DOMHighResTimeStamp lastRequestTimestamp; + DOMHighResTimeStamp lastResponseTimestamp; + double totalRoundTripTime; + double currentRoundTripTime; + double availableOutgoingBitrate; + double availableIncomingBitrate; + unsigned long circuitBreakerTriggerCount; + unsigned long long requestsReceived; + unsigned long long requestsSent; + unsigned long long responsesReceived; + unsigned long long responsesSent; + unsigned long long retransmissionsReceived; + unsigned long long retransmissionsSent; + unsigned long long consentRequestsSent; + DOMHighResTimeStamp consentExpiredTimestamp; +*/ + +/* XXX: RTCIceCandidateStats + DOMString transportId; + boolean isRemote; + RTCNetworkType networkType; + DOMString ip; + long port; + DOMString protocol; + RTCIceCandidateType candidateType; + long priority; + DOMString url; + DOMString relayProtocol; + boolean deleted = false; +}; +*/ + + gst_structure_set (s, id, GST_TYPE_STRUCTURE, stats, NULL); + gst_structure_free (stats); + + return id; +} + +/* https://www.w3.org/TR/webrtc-stats/#dom-rtctransportstats */ +static gchar * +_get_stats_from_dtls_transport (GstWebRTCBin * webrtc, + GstWebRTCDTLSTransport * transport, GstStructure * s) +{ + GstStructure *stats; + gchar *id; + double ts; + + gst_structure_get_double (s, "timestamp", &ts); + + id = g_strdup_printf ("transport-stats_%s", GST_OBJECT_NAME (transport)); + stats = gst_structure_new_empty (id); + _set_base_stats (stats, GST_WEBRTC_STATS_TRANSPORT, ts, id); + +/* XXX: RTCTransportStats + unsigned long packetsSent; + unsigned long packetsReceived; + unsigned long long bytesSent; + unsigned long long bytesReceived; + DOMString rtcpTransportStatsId; + RTCIceRole iceRole; + RTCDtlsTransportState dtlsState; + DOMString selectedCandidatePairId; + DOMString localCertificateId; + DOMString remoteCertificateId; +*/ + +/* XXX: RTCCertificateStats + DOMString fingerprint; + DOMString fingerprintAlgorithm; + DOMString base64Certificate; + DOMString issuerCertificateId; +*/ + +/* XXX: RTCIceCandidateStats + DOMString transportId; + boolean isRemote; + DOMString ip; + long port; + DOMString protocol; + RTCIceCandidateType candidateType; + long priority; + DOMString url; + boolean deleted = false; +*/ + + gst_structure_set (s, id, GST_TYPE_STRUCTURE, stats, NULL); + gst_structure_free (stats); + + _get_stats_from_ice_transport (webrtc, transport->transport, s); + + return id; +} + +static void +_get_stats_from_transport_channel (GstWebRTCBin * webrtc, + TransportStream * stream, const gchar * codec_id, GstStructure * s) +{ + GstWebRTCDTLSTransport *transport; + GObject *rtp_session; + GstStructure *rtp_stats; + GValueArray *source_stats; + gchar *transport_id; + double ts; + int i; + + gst_structure_get_double (s, "timestamp", &ts); + + transport = stream->transport; + if (!transport) + transport = stream->transport; + if (!transport) + return; + + g_signal_emit_by_name (webrtc->rtpbin, "get-internal-session", + stream->session_id, &rtp_session); + g_object_get (rtp_session, "stats", &rtp_stats, NULL); + + gst_structure_get (rtp_stats, "source-stats", G_TYPE_VALUE_ARRAY, + &source_stats, NULL); + + GST_DEBUG_OBJECT (webrtc, "retrieving rtp stream stats from transport %" + GST_PTR_FORMAT " rtp session %" GST_PTR_FORMAT " with %u rtp sources, " + "transport %" GST_PTR_FORMAT, stream, rtp_session, source_stats->n_values, + transport); + + transport_id = _get_stats_from_dtls_transport (webrtc, transport, s); + + /* construct stats objects */ + for (i = 0; i < source_stats->n_values; i++) { + const GstStructure *stats; + const GValue *val = g_value_array_get_nth (source_stats, i); + gboolean internal; + + stats = gst_value_get_structure (val); + + /* skip internal sources */ + gst_structure_get (stats, "internal", G_TYPE_BOOLEAN, &internal, NULL); + if (internal) + continue; + + _get_stats_from_rtp_source_stats (webrtc, stats, codec_id, transport_id, s); + } + + g_object_unref (rtp_session); + gst_structure_free (rtp_stats); + g_value_array_free (source_stats); + g_free (transport_id); +} + +/* https://www.w3.org/TR/webrtc-stats/#codec-dict* */ +static gchar * +_get_codec_stats_from_pad (GstWebRTCBin * webrtc, GstPad * pad, + GstStructure * s) +{ + GstStructure *stats; + GstCaps *caps; + gchar *id; + double ts; + + gst_structure_get_double (s, "timestamp", &ts); + + stats = gst_structure_new_empty ("unused"); + id = g_strdup_printf ("codec-stats-%s", GST_OBJECT_NAME (pad)); + _set_base_stats (stats, GST_WEBRTC_STATS_CODEC, ts, id); + + caps = gst_pad_get_current_caps (pad); + if (caps && gst_caps_is_fixed (caps)) { + GstStructure *caps_s = gst_caps_get_structure (caps, 0); + gint pt, clock_rate; + + if (gst_structure_get_int (caps_s, "payload", &pt)) + gst_structure_set (stats, "payload-type", G_TYPE_UINT, pt, NULL); + + if (gst_structure_get_int (caps_s, "clock-rate", &clock_rate)) + gst_structure_set (stats, "clock-rate", G_TYPE_UINT, clock_rate, NULL); + + /* FIXME: codecType, mimeType, channels, sdpFmtpLine, implementation, transportId */ + } + + if (caps) + gst_caps_unref (caps); + + gst_structure_set (s, id, GST_TYPE_STRUCTURE, stats, NULL); + gst_structure_free (stats); + + return id; +} + +static gboolean +_get_stats_from_pad (GstWebRTCBin * webrtc, GstPad * pad, GstStructure * s) +{ + GstWebRTCBinPad *wpad = GST_WEBRTC_BIN_PAD (pad); + gchar *codec_id; + + codec_id = _get_codec_stats_from_pad (webrtc, pad, s); + if (wpad->trans) { + WebRTCTransceiver *trans; + trans = WEBRTC_TRANSCEIVER (wpad->trans); + if (trans->stream) + _get_stats_from_transport_channel (webrtc, trans->stream, codec_id, s); + } + + g_free (codec_id); + + return TRUE; +} + +void +gst_webrtc_bin_update_stats (GstWebRTCBin * webrtc) +{ + GstStructure *s = gst_structure_new_empty ("application/x-webrtc-stats"); + double ts = monotonic_time_as_double_milliseconds (); + GstStructure *pc_stats; + + _init_debug (); + + gst_structure_set (s, "timestamp", G_TYPE_DOUBLE, ts, NULL); + + /* FIXME: better unique IDs */ + /* FIXME: rate limitting stat updates? */ + /* FIXME: all stats need to be kept forever */ + + GST_DEBUG_OBJECT (webrtc, "updating stats at time %f", ts); + + if ((pc_stats = _get_peer_connection_stats (webrtc))) { + const gchar *id = "peer-connection-stats"; + _set_base_stats (pc_stats, GST_WEBRTC_STATS_PEER_CONNECTION, ts, id); + gst_structure_set (s, id, GST_TYPE_STRUCTURE, pc_stats, NULL); + gst_structure_free (pc_stats); + } + + gst_element_foreach_pad (GST_ELEMENT (webrtc), + (GstElementForeachPadFunc) _get_stats_from_pad, s); + + gst_structure_remove_field (s, "timestamp"); + + if (webrtc->priv->stats) + gst_structure_free (webrtc->priv->stats); + webrtc->priv->stats = s; +} diff --git a/ext/webrtc/gstwebrtcstats.h b/ext/webrtc/gstwebrtcstats.h new file mode 100644 index 0000000000..e67ba47d67 --- /dev/null +++ b/ext/webrtc/gstwebrtcstats.h @@ -0,0 +1,35 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifndef __GST_WEBRTC_STATS_H__ +#define __GST_WEBRTC_STATS_H__ + +#include +#include +#include +#include "fwd.h" + +G_BEGIN_DECLS + +G_GNUC_INTERNAL +void gst_webrtc_bin_update_stats (GstWebRTCBin * webrtc); + +G_END_DECLS + +#endif /* __GST_WEBRTC_STATS_H__ */ diff --git a/ext/webrtc/icestream.c b/ext/webrtc/icestream.c new file mode 100644 index 0000000000..dd48524685 --- /dev/null +++ b/ext/webrtc/icestream.c @@ -0,0 +1,239 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "icestream.h" +#include "nicetransport.h" + +#define GST_CAT_DEFAULT gst_webrtc_ice_stream_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +#define gst_webrtc_ice_stream_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstWebRTCICEStream, gst_webrtc_ice_stream, + GST_TYPE_OBJECT, + GST_DEBUG_CATEGORY_INIT (gst_webrtc_ice_stream_debug, + "webrtcicestream", 0, "webrtcicestream");); + +enum +{ + SIGNAL_0, + LAST_SIGNAL, +}; + +enum +{ + PROP_0, + PROP_ICE, + PROP_STREAM_ID, +}; + +//static guint gst_webrtc_ice_stream_signals[LAST_SIGNAL] = { 0 }; + +struct _GstWebRTCICEStreamPrivate +{ + gboolean gathered; + GList *transports; +}; + +static void +gst_webrtc_ice_stream_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstWebRTCICEStream *stream = GST_WEBRTC_ICE_STREAM (object); + + switch (prop_id) { + case PROP_ICE: + /* XXX: weak-ref this? */ + stream->ice = g_value_get_object (value); + break; + case PROP_STREAM_ID: + stream->stream_id = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_webrtc_ice_stream_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstWebRTCICEStream *stream = GST_WEBRTC_ICE_STREAM (object); + + switch (prop_id) { + case PROP_ICE: + g_value_set_object (value, stream->ice); + break; + case PROP_STREAM_ID: + g_value_set_uint (value, stream->stream_id); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_webrtc_ice_stream_finalize (GObject * object) +{ + GstWebRTCICEStream *stream = GST_WEBRTC_ICE_STREAM (object); + + g_list_free (stream->priv->transports); + stream->priv->transports = NULL; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +_on_candidate_gathering_done (NiceAgent * agent, guint stream_id, + GstWebRTCICEStream * ice) +{ + GList *l; + + if (stream_id != ice->stream_id) + return; + + GST_DEBUG_OBJECT (ice, "%u gathering done", stream_id); + + ice->priv->gathered = TRUE; + + for (l = ice->priv->transports; l; l = l->next) { + GstWebRTCICETransport *ice = l->data; + + gst_webrtc_ice_transport_gathering_state_change (ice, + GST_WEBRTC_ICE_GATHERING_STATE_COMPLETE); + } +} + +GstWebRTCICETransport * +gst_webrtc_ice_stream_find_transport (GstWebRTCICEStream * stream, + GstWebRTCICEComponent component) +{ + GstWebRTCICEComponent trans_comp; + GstWebRTCICETransport *ret; + GList *l; + + g_return_val_if_fail (GST_IS_WEBRTC_ICE_STREAM (stream), NULL); + + for (l = stream->priv->transports; l; l = l->next) { + GstWebRTCICETransport *trans = l->data; + g_object_get (trans, "component", &trans_comp, NULL); + + if (component == trans_comp) + return gst_object_ref (trans); + } + + ret = + GST_WEBRTC_ICE_TRANSPORT (gst_webrtc_nice_transport_new (stream, + component)); + stream->priv->transports = g_list_prepend (stream->priv->transports, ret); + + return ret; +} + +static void +gst_webrtc_ice_stream_constructed (GObject * object) +{ + GstWebRTCICEStream *stream = GST_WEBRTC_ICE_STREAM (object); + NiceAgent *agent; + + g_object_get (stream->ice, "agent", &agent, NULL); + g_signal_connect (agent, "candidate-gathering-done", + G_CALLBACK (_on_candidate_gathering_done), stream); + + g_object_unref (agent); + + G_OBJECT_CLASS (parent_class)->constructed (object); +} + +gboolean +gst_webrtc_ice_stream_gather_candidates (GstWebRTCICEStream * stream) +{ + NiceAgent *agent; + GList *l; + + g_return_val_if_fail (GST_IS_WEBRTC_ICE_STREAM (stream), FALSE); + + GST_DEBUG_OBJECT (stream, "start gathering candidates"); + + if (stream->priv->gathered) + return TRUE; + + for (l = stream->priv->transports; l; l = l->next) { + GstWebRTCICETransport *trans = l->data; + + gst_webrtc_ice_transport_gathering_state_change (trans, + GST_WEBRTC_ICE_GATHERING_STATE_GATHERING); + } + + g_object_get (stream->ice, "agent", &agent, NULL); + if (!nice_agent_gather_candidates (agent, stream->stream_id)) { + g_object_unref (agent); + return FALSE; + } + + g_object_unref (agent); + return TRUE; +} + +static void +gst_webrtc_ice_stream_class_init (GstWebRTCICEStreamClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + g_type_class_add_private (klass, sizeof (GstWebRTCICEStreamPrivate)); + + gobject_class->constructed = gst_webrtc_ice_stream_constructed; + gobject_class->get_property = gst_webrtc_ice_stream_get_property; + gobject_class->set_property = gst_webrtc_ice_stream_set_property; + gobject_class->finalize = gst_webrtc_ice_stream_finalize; + + g_object_class_install_property (gobject_class, + PROP_ICE, + g_param_spec_object ("ice", + "ICE", "ICE agent associated with this stream", + GST_TYPE_WEBRTC_ICE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_STREAM_ID, + g_param_spec_uint ("stream-id", + "ICE stream id", "ICE stream id associated with this stream", + 0, G_MAXUINT, 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); +} + +static void +gst_webrtc_ice_stream_init (GstWebRTCICEStream * ice) +{ + ice->priv = + G_TYPE_INSTANCE_GET_PRIVATE ((ice), GST_TYPE_WEBRTC_ICE_STREAM, + GstWebRTCICEStreamPrivate); +} + +GstWebRTCICEStream * +gst_webrtc_ice_stream_new (GstWebRTCICE * ice, guint stream_id) +{ + return g_object_new (GST_TYPE_WEBRTC_ICE_STREAM, "ice", ice, + "stream-id", stream_id, NULL); +} diff --git a/ext/webrtc/icestream.h b/ext/webrtc/icestream.h new file mode 100644 index 0000000000..6bf67ea78a --- /dev/null +++ b/ext/webrtc/icestream.h @@ -0,0 +1,63 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifndef __GST_WEBRTC_ICE_STREAM_H__ +#define __GST_WEBRTC_ICE_STREAM_H__ + +#include +/* libice */ +#include +#include +#include "gstwebrtcice.h" + +G_BEGIN_DECLS + +GType gst_webrtc_ice_stream_get_type(void); +#define GST_TYPE_WEBRTC_ICE_STREAM (gst_webrtc_ice_stream_get_type()) +#define GST_WEBRTC_ICE_STREAM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_WEBRTC_ICE_STREAM,GstWebRTCICEStream)) +#define GST_IS_WEBRTC_ICE_STREAM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_WEBRTC_ICE_STREAM)) +#define GST_WEBRTC_ICE_STREAM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_WEBRTC_ICE_STREAM,GstWebRTCICEStreamClass)) +#define GST_IS_WEBRTC_ICE_STREAM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_WEBRTC_ICE_STREAM)) +#define GST_WEBRTC_ICE_STREAM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_WEBRTC_ICE_STREAM,GstWebRTCICEStreamClass)) + +struct _GstWebRTCICEStream +{ + GstObject parent; + + GstWebRTCICE *ice; + + guint stream_id; + + GstWebRTCICEStreamPrivate *priv; +}; + +struct _GstWebRTCICEStreamClass +{ + GstObjectClass parent_class; +}; + +GstWebRTCICEStream * gst_webrtc_ice_stream_new (GstWebRTCICE * ice, + guint stream_id); +GstWebRTCICETransport * gst_webrtc_ice_stream_find_transport (GstWebRTCICEStream * stream, + GstWebRTCICEComponent component); +gboolean gst_webrtc_ice_stream_gather_candidates (GstWebRTCICEStream * ice); + +G_END_DECLS + +#endif /* __GST_WEBRTC_ICE_STREAM_H__ */ diff --git a/ext/webrtc/meson.build b/ext/webrtc/meson.build new file mode 100644 index 0000000000..c98bd0d89c --- /dev/null +++ b/ext/webrtc/meson.build @@ -0,0 +1,27 @@ +webrtc_sources = [ + 'gstwebrtc.c', + 'gstwebrtcice.c', + 'gstwebrtcstats.c', + 'icestream.c', + 'nicetransport.c', + 'gstwebrtcbin.c', + 'transportreceivebin.c', + 'transportsendbin.c', + 'transportstream.c', + 'utils.c', + 'webrtcsdp.c', + 'webrtctransceiver.c', +] + +libnice_dep = dependency('nice', version : '>=0.1.14', required : false) + +if libnice_dep.found() + library('gstwebrtc', + webrtc_sources, + c_args : gst_plugins_bad_args + ['-DGST_USE_UNSTABLE_API'], + include_directories : [configinc], + dependencies : [libnice_dep, gstbase_dep, gstsdp_dep, gstwebrtc_dep], + install : true, + install_dir : plugins_install_dir, + ) +endif diff --git a/ext/webrtc/nicetransport.c b/ext/webrtc/nicetransport.c new file mode 100644 index 0000000000..2365cfd52b --- /dev/null +++ b/ext/webrtc/nicetransport.c @@ -0,0 +1,268 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "nicetransport.h" +#include "icestream.h" + +#define GST_CAT_DEFAULT gst_webrtc_nice_transport_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +#define gst_webrtc_nice_transport_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstWebRTCNiceTransport, gst_webrtc_nice_transport, + GST_TYPE_WEBRTC_ICE_TRANSPORT, + GST_DEBUG_CATEGORY_INIT (gst_webrtc_nice_transport_debug, + "webrtcnicetransport", 0, "webrtcnicetransport"); + ); + +enum +{ + SIGNAL_0, + LAST_SIGNAL, +}; + +enum +{ + PROP_0, + PROP_STREAM, +}; + +//static guint gst_webrtc_nice_transport_signals[LAST_SIGNAL] = { 0 }; + +struct _GstWebRTCNiceTransportPrivate +{ + gboolean running; +}; + +static NiceComponentType +_gst_component_to_nice (GstWebRTCICEComponent component) +{ + switch (component) { + case GST_WEBRTC_ICE_COMPONENT_RTP: + return NICE_COMPONENT_TYPE_RTP; + case GST_WEBRTC_ICE_COMPONENT_RTCP: + return NICE_COMPONENT_TYPE_RTCP; + default: + g_assert_not_reached (); + return 0; + } +} + +static GstWebRTCICEComponent +_nice_component_to_gst (NiceComponentType component) +{ + switch (component) { + case NICE_COMPONENT_TYPE_RTP: + return GST_WEBRTC_ICE_COMPONENT_RTP; + case NICE_COMPONENT_TYPE_RTCP: + return GST_WEBRTC_ICE_COMPONENT_RTCP; + default: + g_assert_not_reached (); + return 0; + } +} + +static GstWebRTCICEConnectionState +_nice_component_state_to_gst (NiceComponentState state) +{ + switch (state) { + case NICE_COMPONENT_STATE_DISCONNECTED: + return GST_WEBRTC_ICE_CONNECTION_STATE_DISCONNECTED; + case NICE_COMPONENT_STATE_GATHERING: + return GST_WEBRTC_ICE_CONNECTION_STATE_NEW; + case NICE_COMPONENT_STATE_CONNECTING: + return GST_WEBRTC_ICE_CONNECTION_STATE_CHECKING; + case NICE_COMPONENT_STATE_CONNECTED: + return GST_WEBRTC_ICE_CONNECTION_STATE_CONNECTED; + case NICE_COMPONENT_STATE_READY: + return GST_WEBRTC_ICE_CONNECTION_STATE_COMPLETED; + case NICE_COMPONENT_STATE_FAILED: + return GST_WEBRTC_ICE_CONNECTION_STATE_FAILED; + default: + g_assert_not_reached (); + return 0; + } +} + +static void +gst_webrtc_nice_transport_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstWebRTCNiceTransport *nice = GST_WEBRTC_NICE_TRANSPORT (object); + + switch (prop_id) { + case PROP_STREAM: + if (nice->stream) + gst_object_unref (nice->stream); + nice->stream = g_value_dup_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_webrtc_nice_transport_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstWebRTCNiceTransport *nice = GST_WEBRTC_NICE_TRANSPORT (object); + + switch (prop_id) { + case PROP_STREAM: + g_value_set_object (value, nice->stream); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_webrtc_nice_transport_finalize (GObject * object) +{ + GstWebRTCNiceTransport *nice = GST_WEBRTC_NICE_TRANSPORT (object); + + gst_object_unref (nice->stream); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +_on_new_selected_pair (NiceAgent * agent, guint stream_id, + NiceComponentType component, NiceCandidate * lcandidate, + NiceCandidate * rcandidate, GstWebRTCNiceTransport * nice) +{ + GstWebRTCICETransport *ice = GST_WEBRTC_ICE_TRANSPORT (nice); + GstWebRTCICEComponent comp = _nice_component_to_gst (component); + guint our_stream_id; + + g_object_get (nice->stream, "stream-id", &our_stream_id, NULL); + + if (stream_id != our_stream_id) + return; + if (comp != ice->component) + return; + + gst_webrtc_ice_transport_selected_pair_change (ice); +} + +static void +_on_component_state_changed (NiceAgent * agent, guint stream_id, + NiceComponentType component, NiceComponentState state, + GstWebRTCNiceTransport * nice) +{ + GstWebRTCICETransport *ice = GST_WEBRTC_ICE_TRANSPORT (nice); + GstWebRTCICEComponent comp = _nice_component_to_gst (component); + guint our_stream_id; + + g_object_get (nice->stream, "stream-id", &our_stream_id, NULL); + + if (stream_id != our_stream_id) + return; + if (comp != ice->component) + return; + + GST_DEBUG_OBJECT (ice, "%u %u %s", stream_id, component, + nice_component_state_to_string (state)); + + gst_webrtc_ice_transport_connection_state_change (ice, + _nice_component_state_to_gst (state)); +} + +static void +gst_webrtc_nice_transport_constructed (GObject * object) +{ + GstWebRTCNiceTransport *nice = GST_WEBRTC_NICE_TRANSPORT (object); + GstWebRTCICETransport *ice = GST_WEBRTC_ICE_TRANSPORT (object); + NiceComponentType component = _gst_component_to_nice (ice->component); + gboolean controlling_mode; + guint our_stream_id; + NiceAgent *agent; + + g_object_get (nice->stream, "stream-id", &our_stream_id, NULL); + g_object_get (nice->stream->ice, "agent", &agent, NULL); + + g_object_get (agent, "controlling-mode", &controlling_mode, NULL); + ice->role = + controlling_mode ? GST_WEBRTC_ICE_ROLE_CONTROLLING : + GST_WEBRTC_ICE_ROLE_CONTROLLED; + + g_signal_connect (agent, "component-state-changed", + G_CALLBACK (_on_component_state_changed), nice); + g_signal_connect (agent, "new-selected-pair-full", + G_CALLBACK (_on_new_selected_pair), nice); + + ice->src = gst_element_factory_make ("nicesrc", NULL); + if (ice->src) { + g_object_set (ice->src, "agent", agent, "stream", our_stream_id, + "component", component, NULL); + } + ice->sink = gst_element_factory_make ("nicesink", NULL); + if (ice->sink) { + g_object_set (ice->sink, "agent", agent, "stream", our_stream_id, + "component", component, "async", FALSE, "enable-last-sample", FALSE, + NULL); + if (ice->component == GST_WEBRTC_ICE_COMPONENT_RTCP) + g_object_set (ice->sink, "sync", FALSE, NULL); + } + + g_object_unref (agent); + + G_OBJECT_CLASS (parent_class)->constructed (object); +} + +static void +gst_webrtc_nice_transport_class_init (GstWebRTCNiceTransportClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + g_type_class_add_private (klass, sizeof (GstWebRTCNiceTransportPrivate)); + + gobject_class->constructed = gst_webrtc_nice_transport_constructed; + gobject_class->get_property = gst_webrtc_nice_transport_get_property; + gobject_class->set_property = gst_webrtc_nice_transport_set_property; + gobject_class->finalize = gst_webrtc_nice_transport_finalize; + + g_object_class_install_property (gobject_class, + PROP_STREAM, + g_param_spec_object ("stream", + "WebRTC ICE Stream", "ICE stream associated with this transport", + GST_TYPE_WEBRTC_ICE_STREAM, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); +} + +static void +gst_webrtc_nice_transport_init (GstWebRTCNiceTransport * nice) +{ + nice->priv = + G_TYPE_INSTANCE_GET_PRIVATE ((nice), GST_TYPE_WEBRTC_NICE_TRANSPORT, + GstWebRTCNiceTransportPrivate); +} + +GstWebRTCNiceTransport * +gst_webrtc_nice_transport_new (GstWebRTCICEStream * stream, + GstWebRTCICEComponent component) +{ + return g_object_new (GST_TYPE_WEBRTC_NICE_TRANSPORT, "stream", stream, + "component", component, NULL); +} diff --git a/ext/webrtc/nicetransport.h b/ext/webrtc/nicetransport.h new file mode 100644 index 0000000000..f36e1ccb9c --- /dev/null +++ b/ext/webrtc/nicetransport.h @@ -0,0 +1,58 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifndef __GST_WEBRTC_NICE_TRANSPORT_H__ +#define __GST_WEBRTC_NICE_TRANSPORT_H__ + +#include +/* libnice */ +#include +#include +#include "gstwebrtcice.h" + +G_BEGIN_DECLS + +GType gst_webrtc_nice_transport_get_type(void); +#define GST_TYPE_WEBRTC_NICE_TRANSPORT (gst_webrtc_nice_transport_get_type()) +#define GST_WEBRTC_NICE_TRANSPORT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_WEBRTC_NICE_TRANSPORT,GstWebRTCNiceTransport)) +#define GST_IS_WEBRTC_NICE_TRANSPORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_WEBRTC_NICE_TRANSPORT)) +#define GST_WEBRTC_NICE_TRANSPORT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_WEBRTC_NICE_TRANSPORT,GstWebRTCNiceTransportClass)) +#define GST_IS_WEBRTC_NICE_TRANSPORT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_WEBRTC_NICE_TRANSPORT)) +#define GST_WEBRTC_NICE_TRANSPORT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_WEBRTC_NICE_TRANSPORT,GstWebRTCNiceTransportClass)) + +struct _GstWebRTCNiceTransport +{ + GstWebRTCICETransport parent; + + GstWebRTCICEStream *stream; + + GstWebRTCNiceTransportPrivate *priv; +}; + +struct _GstWebRTCNiceTransportClass +{ + GstWebRTCICETransportClass parent_class; +}; + +GstWebRTCNiceTransport * gst_webrtc_nice_transport_new (GstWebRTCICEStream * stream, + GstWebRTCICEComponent component); + +G_END_DECLS + +#endif /* __GST_WEBRTC_NICE_TRANSPORT_H__ */ diff --git a/ext/webrtc/transportreceivebin.c b/ext/webrtc/transportreceivebin.c new file mode 100644 index 0000000000..6730b1fb72 --- /dev/null +++ b/ext/webrtc/transportreceivebin.c @@ -0,0 +1,376 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "transportreceivebin.h" +#include "utils.h" + +/* + * ,----------------------------transport_receive_%u-----------------------------, + * ; (rtp) ; + * ; ,---nicesrc----, ,-capsfilter-, ,----dtlssrtpdec----, ,--funnel--, ; + * ; ; src o--o sink src o--o sink rtp_src o------o sink_0 ; ; + * ; '--------------' '------------' ; ; ; src o--o rtp_src + * ; ; rtcp_src o-, ,--o sink_1 ; ; + * ; '-------------------' ; ; '----------' ; + * ; ; ; ,--funnel--, ; + * ; '-+--o sink_0 ; ; + * ; ,-' ; src o--o rtcp_src + * ; (rtcp) ; ,-o sink_1 ; ; + * ; ,---nicesrc----, ,-capsfilter-, ,----dtlssrtpdec----, ; ; '----------' ; + * ; ; src o--o sink src o--o sink rtp_src o-' ; ; + * ; '--------------' '------------' ; ; ; ; + * ; ; rtcp_src o----' ; + * ; '-------------------' ; + * '-----------------------------------------------------------------------------' + * + * Do we really wnat to be *that* permissive in what we accept? + * + * FIXME: When and how do we want to clear the possibly stored buffers? + */ + +#define GST_CAT_DEFAULT gst_webrtc_transport_receive_bin_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +#define transport_receive_bin_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (TransportReceiveBin, transport_receive_bin, + GST_TYPE_BIN, + GST_DEBUG_CATEGORY_INIT (gst_webrtc_transport_receive_bin_debug, + "webrtctransportreceivebin", 0, "webrtctransportreceivebin"); + ); + +static GstStaticPadTemplate rtp_sink_template = +GST_STATIC_PAD_TEMPLATE ("rtp_src", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-rtp")); + +static GstStaticPadTemplate rtcp_sink_template = +GST_STATIC_PAD_TEMPLATE ("rtcp_src", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-rtp")); + +enum +{ + PROP_0, + PROP_STREAM, +}; + +static const gchar * +_receive_state_to_string (ReceiveState state) +{ + switch (state) { + case RECEIVE_STATE_BLOCK: + return "block"; + case RECEIVE_STATE_DROP: + return "drop"; + case RECEIVE_STATE_PASS: + return "pass"; + default: + return "Unknown"; + } +} + +static GstPadProbeReturn +pad_block (GstPad * pad, GstPadProbeInfo * info, TransportReceiveBin * receive) +{ + GstPadProbeReturn ret; + + g_mutex_lock (&receive->pad_block_lock); + while (receive->receive_state == RECEIVE_STATE_BLOCK) { + g_cond_wait (&receive->pad_block_cond, &receive->pad_block_lock); + GST_DEBUG_OBJECT (pad, "probe waited. new state %s", + _receive_state_to_string (receive->receive_state)); + } + ret = GST_PAD_PROBE_PASS; + + if (receive->receive_state == RECEIVE_STATE_DROP) { + ret = GST_PAD_PROBE_DROP; + } else if (receive->receive_state == RECEIVE_STATE_PASS) { + ret = GST_PAD_PROBE_OK; + } + + g_mutex_unlock (&receive->pad_block_lock); + + return ret; +} + +void +transport_receive_bin_set_receive_state (TransportReceiveBin * receive, + ReceiveState state) +{ + g_mutex_lock (&receive->pad_block_lock); + receive->receive_state = state; + GST_DEBUG_OBJECT (receive, "changing receive state to %s", + _receive_state_to_string (state)); + g_cond_signal (&receive->pad_block_cond); + g_mutex_unlock (&receive->pad_block_lock); +} + +static void +transport_receive_bin_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + TransportReceiveBin *receive = TRANSPORT_RECEIVE_BIN (object); + + GST_OBJECT_LOCK (receive); + switch (prop_id) { + case PROP_STREAM: + /* XXX: weak-ref this? */ + receive->stream = TRANSPORT_STREAM (g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + GST_OBJECT_UNLOCK (receive); +} + +static void +transport_receive_bin_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + TransportReceiveBin *receive = TRANSPORT_RECEIVE_BIN (object); + + GST_OBJECT_LOCK (receive); + switch (prop_id) { + case PROP_STREAM: + g_value_set_object (value, receive->stream); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + GST_OBJECT_UNLOCK (receive); +} + +static void +transport_receive_bin_finalize (GObject * object) +{ + TransportReceiveBin *receive = TRANSPORT_RECEIVE_BIN (object); + + g_mutex_clear (&receive->pad_block_lock); + g_cond_clear (&receive->pad_block_cond); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static GstStateChangeReturn +transport_receive_bin_change_state (GstElement * element, + GstStateChange transition) +{ + TransportReceiveBin *receive = TRANSPORT_RECEIVE_BIN (element); + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + GST_DEBUG ("changing state: %s => %s", + gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)), + gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition))); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY:{ + GstElement *elem; + + receive->rtp_block = + _create_pad_block (GST_ELEMENT (receive), receive->rtp_src, 0, NULL, + NULL); + receive->rtp_block->block_id = + gst_pad_add_probe (receive->rtp_src, GST_PAD_PROBE_TYPE_ALL_BOTH, + (GstPadProbeCallback) pad_block, receive, NULL); + + /* XXX: because nice needs the nicesrc internal main loop running in order + * correctly STUN... */ + /* FIXME: this races with the pad exposure later and may get not-linked */ + elem = receive->stream->transport->transport->src; + gst_element_set_locked_state (elem, TRUE); + gst_element_set_state (elem, GST_STATE_PLAYING); + elem = receive->stream->rtcp_transport->transport->src; + gst_element_set_locked_state (elem, TRUE); + gst_element_set_state (elem, GST_STATE_PLAYING); + break; + } + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_NULL:{ + GstElement *elem; + + elem = receive->stream->transport->transport->src; + gst_element_set_locked_state (elem, FALSE); + gst_element_set_state (elem, GST_STATE_NULL); + elem = receive->stream->rtcp_transport->transport->src; + gst_element_set_locked_state (elem, FALSE); + gst_element_set_state (elem, GST_STATE_NULL); + + if (receive->rtp_block) + _free_pad_block (receive->rtp_block); + receive->rtp_block = NULL; + break; + } + default: + break; + } + + return ret; +} + +static void +rtp_queue_overrun (GstElement * queue, TransportReceiveBin * receive) +{ + GST_WARNING_OBJECT (receive, "Internal receive queue overrun. Dropping data"); +} + +static void +transport_receive_bin_constructed (GObject * object) +{ + TransportReceiveBin *receive = TRANSPORT_RECEIVE_BIN (object); + GstWebRTCDTLSTransport *transport; + GstPad *ghost, *pad; + GstElement *capsfilter, *funnel, *queue; + GstCaps *caps; + + g_return_if_fail (receive->stream); + + /* link ice src, dtlsrtp together for rtp */ + transport = receive->stream->transport; + gst_bin_add (GST_BIN (receive), GST_ELEMENT (transport->dtlssrtpdec)); + + capsfilter = gst_element_factory_make ("capsfilter", NULL); + caps = gst_caps_new_empty_simple ("application/x-rtp"); + g_object_set (capsfilter, "caps", caps, NULL); + gst_caps_unref (caps); + + gst_bin_add (GST_BIN (receive), GST_ELEMENT (capsfilter)); + if (!gst_element_link_pads (capsfilter, "src", transport->dtlssrtpdec, + "sink")) + g_warn_if_reached (); + + gst_bin_add (GST_BIN (receive), GST_ELEMENT (transport->transport->src)); + + if (!gst_element_link_pads (GST_ELEMENT (transport->transport->src), "src", + GST_ELEMENT (capsfilter), "sink")) + g_warn_if_reached (); + + /* link ice src, dtlsrtp together for rtcp */ + transport = receive->stream->rtcp_transport; + gst_bin_add (GST_BIN (receive), GST_ELEMENT (transport->dtlssrtpdec)); + + capsfilter = gst_element_factory_make ("capsfilter", NULL); + caps = gst_caps_new_empty_simple ("application/x-rtcp"); + g_object_set (capsfilter, "caps", caps, NULL); + gst_caps_unref (caps); + + gst_bin_add (GST_BIN (receive), GST_ELEMENT (capsfilter)); + if (!gst_element_link_pads (capsfilter, "src", transport->dtlssrtpdec, + "sink")) + g_warn_if_reached (); + + gst_bin_add (GST_BIN (receive), GST_ELEMENT (transport->transport->src)); + + if (!gst_element_link_pads (GST_ELEMENT (transport->transport->src), "src", + GST_ELEMENT (capsfilter), "sink")) + g_warn_if_reached (); + + /* create funnel for rtp_src */ + funnel = gst_element_factory_make ("funnel", NULL); + gst_bin_add (GST_BIN (receive), funnel); + if (!gst_element_link_pads (receive->stream->transport->dtlssrtpdec, + "rtp_src", funnel, "sink_0")) + g_warn_if_reached (); + if (!gst_element_link_pads (receive->stream->rtcp_transport->dtlssrtpdec, + "rtp_src", funnel, "sink_1")) + g_warn_if_reached (); + + queue = gst_element_factory_make ("queue", NULL); + /* FIXME: make this configurable? */ + g_object_set (queue, "leaky", 2, "max-size-time", (guint64) 0, + "max-size-buffers", 0, "max-size-bytes", 5 * 1024 * 1024, NULL); + g_signal_connect (queue, "overrun", G_CALLBACK (rtp_queue_overrun), receive); + gst_bin_add (GST_BIN (receive), queue); + if (!gst_element_link_pads (funnel, "src", queue, "sink")) + g_warn_if_reached (); + + pad = gst_element_get_static_pad (queue, "src"); + receive->rtp_src = gst_ghost_pad_new ("rtp_src", pad); + + gst_element_add_pad (GST_ELEMENT (receive), receive->rtp_src); + gst_object_unref (pad); + + /* create funnel for rtcp_src */ + funnel = gst_element_factory_make ("funnel", NULL); + gst_bin_add (GST_BIN (receive), funnel); + if (!gst_element_link_pads (receive->stream->transport->dtlssrtpdec, + "rtcp_src", funnel, "sink_0")) + g_warn_if_reached (); + if (!gst_element_link_pads (receive->stream->rtcp_transport->dtlssrtpdec, + "rtcp_src", funnel, "sink_1")) + g_warn_if_reached (); + + pad = gst_element_get_static_pad (funnel, "src"); + ghost = gst_ghost_pad_new ("rtcp_src", pad); + gst_element_add_pad (GST_ELEMENT (receive), ghost); + gst_object_unref (pad); + + G_OBJECT_CLASS (parent_class)->constructed (object); +} + +static void +transport_receive_bin_class_init (TransportReceiveBinClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GstElementClass *element_class = (GstElementClass *) klass; + + element_class->change_state = transport_receive_bin_change_state; + + gst_element_class_add_static_pad_template (element_class, &rtp_sink_template); + gst_element_class_add_static_pad_template (element_class, + &rtcp_sink_template); + + gst_element_class_set_metadata (element_class, "WebRTC Transport Receive Bin", + "Filter/Network/WebRTC", "A bin for webrtc connections", + "Matthew Waters "); + + gobject_class->constructed = transport_receive_bin_constructed; + gobject_class->get_property = transport_receive_bin_get_property; + gobject_class->set_property = transport_receive_bin_set_property; + gobject_class->finalize = transport_receive_bin_finalize; + + g_object_class_install_property (gobject_class, + PROP_STREAM, + g_param_spec_object ("stream", "Stream", + "The TransportStream for this receiveing bin", + transport_stream_get_type (), + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); +} + +static void +transport_receive_bin_init (TransportReceiveBin * receive) +{ + g_mutex_init (&receive->pad_block_lock); + g_cond_init (&receive->pad_block_cond); +} diff --git a/ext/webrtc/transportreceivebin.h b/ext/webrtc/transportreceivebin.h new file mode 100644 index 0000000000..f26b4ad0f8 --- /dev/null +++ b/ext/webrtc/transportreceivebin.h @@ -0,0 +1,65 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifndef __TRANSPORT_RECEIVE_BIN_H__ +#define __TRANSPORT_RECEIVE_BIN_H__ + +#include +#include "transportstream.h" + +G_BEGIN_DECLS + +GType transport_receive_bin_get_type(void); +#define GST_TYPE_WEBRTC_TRANSPORT_RECEIVE_BIN (transport_receive_bin_get_type()) +#define TRANSPORT_RECEIVE_BIN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_WEBRTC_TRANSPORT_RECEIVE_BIN,TransportReceiveBin)) +#define TRANSPORT_RECEIVE_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_WEBRTC_TRANSPORT_RECEIVE_BIN,TransportReceiveBinClass)) +#define TRANSPORT_RECEIVE_BIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_WEBRTC_TRANSPORT_RECEIVE_BIN,TransportReceiveBinClass)) + +typedef enum +{ + RECEIVE_STATE_BLOCK = 1, + RECEIVE_STATE_DROP, + RECEIVE_STATE_PASS, +} ReceiveState; + +struct _TransportReceiveBin +{ + GstBin parent; + + TransportStream *stream; /* parent transport stream */ + gboolean rtcp_mux; + + GstPad *rtp_src; + struct pad_block *rtp_block; + GMutex pad_block_lock; + GCond pad_block_cond; + ReceiveState receive_state; +}; + +struct _TransportReceiveBinClass +{ + GstBinClass parent_class; +}; + +void transport_receive_bin_set_receive_state (TransportReceiveBin * receive, + ReceiveState state); + +G_END_DECLS + +#endif /* __TRANSPORT_RECEIVE_BIN_H__ */ diff --git a/ext/webrtc/transportsendbin.c b/ext/webrtc/transportsendbin.c new file mode 100644 index 0000000000..8acb740255 --- /dev/null +++ b/ext/webrtc/transportsendbin.c @@ -0,0 +1,471 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "transportsendbin.h" +#include "utils.h" + +/* + * ,------------------------transport_send_%u-------------------------, + * ; ,-----dtlssrtpenc---, ; + * rtp_sink o--------------------------o rtp_sink_0 ; ,---nicesink---, ; + * ; ; src o--o sink ; ; + * ; ,--outputselector--, ,-o rtcp_sink_0 ; '--------------' ; + * ; ; src_0 o-' '-------------------' ; + * rtcp_sink ;---o sink ; ,----dtlssrtpenc----, ,---nicesink---, ; + * ; ; src_1 o---o rtcp_sink_0 src o--o sink ; ; + * ; '------------------' '-------------------' '--------------' ; + * '------------------------------------------------------------------' + * + * outputselecter is used to switch between rtcp-mux and no rtcp-mux + * + * FIXME: Do we need a valve drop=TRUE for the no RTCP case? + */ + +#define GST_CAT_DEFAULT gst_webrtc_transport_send_bin_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +#define transport_send_bin_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (TransportSendBin, transport_send_bin, GST_TYPE_BIN, + GST_DEBUG_CATEGORY_INIT (gst_webrtc_transport_send_bin_debug, + "webrtctransportsendbin", 0, "webrtctransportsendbin");); + +static GstStaticPadTemplate rtp_sink_template = +GST_STATIC_PAD_TEMPLATE ("rtp_sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-rtp")); + +static GstStaticPadTemplate rtcp_sink_template = +GST_STATIC_PAD_TEMPLATE ("rtcp_sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-rtp")); + +enum +{ + PROP_0, + PROP_STREAM, + PROP_RTCP_MUX, +}; + +static void +_set_rtcp_mux (TransportSendBin * send, gboolean rtcp_mux) +{ + GstPad *active_pad; + + if (rtcp_mux) + active_pad = gst_element_get_static_pad (send->outputselector, "src_0"); + else + active_pad = gst_element_get_static_pad (send->outputselector, "src_1"); + send->rtcp_mux = rtcp_mux; + GST_OBJECT_UNLOCK (send); + + g_object_set (send->outputselector, "active-pad", active_pad, NULL); + + gst_object_unref (active_pad); + GST_OBJECT_LOCK (send); +} + +static void +transport_send_bin_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + TransportSendBin *send = TRANSPORT_SEND_BIN (object); + + GST_OBJECT_LOCK (send); + switch (prop_id) { + case PROP_STREAM: + /* XXX: weak-ref this? */ + send->stream = TRANSPORT_STREAM (g_value_get_object (value)); + break; + case PROP_RTCP_MUX: + _set_rtcp_mux (send, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + GST_OBJECT_UNLOCK (send); +} + +static void +transport_send_bin_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + TransportSendBin *send = TRANSPORT_SEND_BIN (object); + + GST_OBJECT_LOCK (send); + switch (prop_id) { + case PROP_STREAM: + g_value_set_object (value, send->stream); + break; + case PROP_RTCP_MUX: + g_value_set_boolean (value, send->rtcp_mux); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + GST_OBJECT_UNLOCK (send); +} + +static GstPadProbeReturn +pad_block (GstPad * pad, GstPadProbeInfo * info, gpointer unused) +{ + GST_LOG_OBJECT (pad, "blocking pad with data %" GST_PTR_FORMAT, info->data); + + return GST_PAD_PROBE_OK; +} + +static GstStateChangeReturn +transport_send_bin_change_state (GstElement * element, + GstStateChange transition) +{ + TransportSendBin *send = TRANSPORT_SEND_BIN (element); + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + GST_DEBUG_OBJECT (element, "changing state: %s => %s", + gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)), + gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition))); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY:{ + /* XXX: don't change state until the client-ness has been chosen + * arguably the element should be able to deal with this itself or + * we should only add it once/if we get the encoding keys */ + + gst_element_set_locked_state (send->stream->transport->dtlssrtpenc, TRUE); + gst_element_set_locked_state (send->stream->rtcp_transport->dtlssrtpenc, + TRUE); + break; + } + case GST_STATE_CHANGE_READY_TO_PAUSED:{ + GstElement *elem; + GstPad *pad; + + /* unblock the encoder once the key is set, this should also be automatic */ + elem = send->stream->transport->dtlssrtpenc; + pad = gst_element_get_static_pad (elem, "rtp_sink_0"); + send->rtp_block = _create_pad_block (elem, pad, 0, NULL, NULL); + send->rtp_block->block_id = + gst_pad_add_probe (pad, + GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER | + GST_PAD_PROBE_TYPE_BUFFER_LIST, (GstPadProbeCallback) pad_block, NULL, + NULL); + gst_object_unref (pad); + + /* unblock the encoder once the key is set, this should also be automatic */ + pad = gst_element_get_static_pad (elem, "rtcp_sink_0"); + send->rtcp_mux_block = _create_pad_block (elem, pad, 0, NULL, NULL); + send->rtcp_mux_block->block_id = + gst_pad_add_probe (pad, + GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER | + GST_PAD_PROBE_TYPE_BUFFER_LIST, (GstPadProbeCallback) pad_block, NULL, + NULL); + gst_object_unref (pad); + + + elem = send->stream->rtcp_transport->dtlssrtpenc; + /* unblock the encoder once the key is set, this should also be automatic */ + pad = gst_element_get_static_pad (elem, "rtcp_sink_0"); + send->rtcp_block = _create_pad_block (elem, pad, 0, NULL, NULL); + send->rtcp_block->block_id = + gst_pad_add_probe (pad, + GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER | + GST_PAD_PROBE_TYPE_BUFFER_LIST, (GstPadProbeCallback) pad_block, NULL, + NULL); + gst_object_unref (pad); + + /* unblock ice sink once a connection is made, this should also be automatic */ + elem = send->stream->transport->transport->sink; + pad = gst_element_get_static_pad (elem, "sink"); + send->rtp_nice_block = _create_pad_block (elem, pad, 0, NULL, NULL); + send->rtp_nice_block->block_id = + gst_pad_add_probe (pad, + GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER | + GST_PAD_PROBE_TYPE_BUFFER_LIST, (GstPadProbeCallback) pad_block, NULL, + NULL); + gst_object_unref (pad); + + /* unblock ice sink once a connection is made, this should also be automatic */ + elem = send->stream->rtcp_transport->transport->sink; + pad = gst_element_get_static_pad (elem, "sink"); + send->rtcp_nice_block = _create_pad_block (elem, pad, 0, NULL, NULL); + send->rtcp_nice_block->block_id = + gst_pad_add_probe (pad, + GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER | + GST_PAD_PROBE_TYPE_BUFFER_LIST, (GstPadProbeCallback) pad_block, NULL, + NULL); + gst_object_unref (pad); + break; + } + case GST_STATE_CHANGE_PAUSED_TO_READY: + { + /* Release pad blocks */ + if (send->rtp_block && send->rtp_block->block_id) { + gst_pad_set_active (send->rtp_block->pad, FALSE); + gst_pad_remove_probe (send->rtp_block->pad, send->rtp_block->block_id); + send->rtp_block->block_id = 0; + } + if (send->rtcp_mux_block && send->rtcp_mux_block->block_id) { + gst_pad_set_active (send->rtcp_mux_block->pad, FALSE); + gst_pad_remove_probe (send->rtcp_mux_block->pad, + send->rtcp_mux_block->block_id); + send->rtcp_mux_block->block_id = 0; + } + if (send->rtcp_block && send->rtcp_block->block_id) { + gst_pad_set_active (send->rtcp_block->pad, FALSE); + gst_pad_remove_probe (send->rtcp_block->pad, + send->rtcp_block->block_id); + send->rtcp_block->block_id = 0; + } + if (send->rtp_nice_block && send->rtp_nice_block->block_id) { + gst_pad_set_active (send->rtp_nice_block->pad, FALSE); + gst_pad_remove_probe (send->rtp_nice_block->pad, + send->rtp_nice_block->block_id); + send->rtp_nice_block->block_id = 0; + } + if (send->rtcp_nice_block && send->rtcp_nice_block->block_id) { + gst_pad_set_active (send->rtcp_nice_block->pad, FALSE); + gst_pad_remove_probe (send->rtcp_nice_block->pad, + send->rtcp_nice_block->block_id); + send->rtcp_nice_block->block_id = 0; + } + break; + } + case GST_STATE_CHANGE_READY_TO_NULL:{ + GstElement *elem; + + if (send->rtp_block) + _free_pad_block (send->rtp_block); + send->rtp_block = NULL; + if (send->rtcp_mux_block) + _free_pad_block (send->rtcp_mux_block); + send->rtcp_mux_block = NULL; + elem = send->stream->transport->dtlssrtpenc; + gst_element_set_locked_state (elem, FALSE); + + if (send->rtcp_block) + _free_pad_block (send->rtcp_block); + send->rtcp_block = NULL; + elem = send->stream->rtcp_transport->dtlssrtpenc; + gst_element_set_locked_state (elem, FALSE); + + if (send->rtp_nice_block) + _free_pad_block (send->rtp_nice_block); + send->rtp_nice_block = NULL; + if (send->rtcp_nice_block) + _free_pad_block (send->rtcp_nice_block); + send->rtcp_nice_block = NULL; + break; + } + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + return ret; +} + +static void +_on_dtls_enc_key_set (GstElement * element, TransportSendBin * send) +{ + if (element == send->stream->transport->dtlssrtpenc) { + GST_LOG_OBJECT (send, "Unblocking pad %" GST_PTR_FORMAT, + send->rtp_block->pad); + _free_pad_block (send->rtp_block); + send->rtp_block = NULL; + GST_LOG_OBJECT (send, "Unblocking pad %" GST_PTR_FORMAT, + send->rtcp_mux_block->pad); + _free_pad_block (send->rtcp_mux_block); + send->rtcp_mux_block = NULL; + } else if (element == send->stream->rtcp_transport->dtlssrtpenc) { + GST_LOG_OBJECT (send, "Unblocking pad %" GST_PTR_FORMAT, + send->rtcp_block->pad); + _free_pad_block (send->rtcp_block); + send->rtcp_block = NULL; + } +} + +static void +_on_notify_ice_connection_state (GstWebRTCICETransport * transport, + GParamSpec * pspec, TransportSendBin * send) +{ + GstWebRTCICEConnectionState state; + + g_object_get (transport, "state", &state, NULL); + + if (state == GST_WEBRTC_ICE_CONNECTION_STATE_CONNECTED || + state == GST_WEBRTC_ICE_CONNECTION_STATE_COMPLETED) { + GST_OBJECT_LOCK (send); + if (transport == send->stream->transport->transport) { + if (send->rtp_nice_block) { + GST_LOG_OBJECT (send, "Unblocking pad %" GST_PTR_FORMAT, + send->rtp_nice_block->pad); + _free_pad_block (send->rtp_nice_block); + } + send->rtp_nice_block = NULL; + } else if (transport == send->stream->rtcp_transport->transport) { + if (send->rtcp_nice_block) { + GST_LOG_OBJECT (send, "Unblocking pad %" GST_PTR_FORMAT, + send->rtcp_nice_block->pad); + _free_pad_block (send->rtcp_nice_block); + } + send->rtcp_nice_block = NULL; + } + GST_OBJECT_UNLOCK (send); + } +} + +static void +transport_send_bin_constructed (GObject * object) +{ + TransportSendBin *send = TRANSPORT_SEND_BIN (object); + GstWebRTCDTLSTransport *transport; + GstPadTemplate *templ; + GstPad *ghost, *pad; + + g_return_if_fail (send->stream); + + g_object_bind_property (send, "rtcp-mux", send->stream, "rtcp-mux", + G_BINDING_BIDIRECTIONAL); + + transport = send->stream->transport; + + templ = _find_pad_template (transport->dtlssrtpenc, + GST_PAD_SINK, GST_PAD_REQUEST, "rtp_sink_%d"); + pad = gst_element_request_pad (transport->dtlssrtpenc, templ, "rtp_sink_0", + NULL); + + /* unblock the encoder once the key is set */ + g_signal_connect (transport->dtlssrtpenc, "on-key-set", + G_CALLBACK (_on_dtls_enc_key_set), send); + gst_bin_add (GST_BIN (send), GST_ELEMENT (transport->dtlssrtpenc)); + + /* unblock ice sink once it signals a connection */ + g_signal_connect (transport->transport, "notify::state", + G_CALLBACK (_on_notify_ice_connection_state), send); + gst_bin_add (GST_BIN (send), GST_ELEMENT (transport->transport->sink)); + + if (!gst_element_link_pads (GST_ELEMENT (transport->dtlssrtpenc), "src", + GST_ELEMENT (transport->transport->sink), "sink")) + g_warn_if_reached (); + + send->outputselector = gst_element_factory_make ("output-selector", NULL); + gst_bin_add (GST_BIN (send), send->outputselector); + + if (!gst_element_link_pads (GST_ELEMENT (send->outputselector), "src_0", + GST_ELEMENT (transport->dtlssrtpenc), "rtcp_sink_0")) + g_warn_if_reached (); + + ghost = gst_ghost_pad_new ("rtp_sink", pad); + gst_element_add_pad (GST_ELEMENT (send), ghost); + gst_object_unref (pad); + + transport = send->stream->rtcp_transport; + + templ = _find_pad_template (transport->dtlssrtpenc, + GST_PAD_SINK, GST_PAD_REQUEST, "rtcp_sink_%d"); + + /* unblock the encoder once the key is set */ + g_signal_connect (transport->dtlssrtpenc, "on-key-set", + G_CALLBACK (_on_dtls_enc_key_set), send); + gst_bin_add (GST_BIN (send), GST_ELEMENT (transport->dtlssrtpenc)); + + /* unblock ice sink once it signals a connection */ + g_signal_connect (transport->transport, "notify::state", + G_CALLBACK (_on_notify_ice_connection_state), send); + gst_bin_add (GST_BIN (send), GST_ELEMENT (transport->transport->sink)); + + if (!gst_element_link_pads (GST_ELEMENT (transport->dtlssrtpenc), "src", + GST_ELEMENT (transport->transport->sink), "sink")) + g_warn_if_reached (); + + if (!gst_element_link_pads (GST_ELEMENT (send->outputselector), "src_1", + GST_ELEMENT (transport->dtlssrtpenc), "rtcp_sink_0")) + g_warn_if_reached (); + + pad = gst_element_get_static_pad (send->outputselector, "sink"); + + ghost = gst_ghost_pad_new ("rtcp_sink", pad); + gst_element_add_pad (GST_ELEMENT (send), ghost); + gst_object_unref (pad); + + G_OBJECT_CLASS (parent_class)->constructed (object); +} + +static void +transport_send_bin_dispose (GObject * object) +{ + TransportSendBin *send = TRANSPORT_SEND_BIN (object); + + if (send->stream) { + g_signal_handlers_disconnect_by_data (send->stream->transport->transport, + send); + g_signal_handlers_disconnect_by_data (send->stream-> + rtcp_transport->transport, send); + } + send->stream = NULL; + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +transport_send_bin_class_init (TransportSendBinClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GstElementClass *element_class = (GstElementClass *) klass; + + element_class->change_state = transport_send_bin_change_state; + + gst_element_class_add_static_pad_template (element_class, &rtp_sink_template); + gst_element_class_add_static_pad_template (element_class, + &rtcp_sink_template); + + gst_element_class_set_metadata (element_class, "WebRTC Transport Send Bin", + "Filter/Network/WebRTC", "A bin for webrtc connections", + "Matthew Waters "); + + gobject_class->constructed = transport_send_bin_constructed; + gobject_class->dispose = transport_send_bin_dispose; + gobject_class->get_property = transport_send_bin_get_property; + gobject_class->set_property = transport_send_bin_set_property; + + g_object_class_install_property (gobject_class, + PROP_STREAM, + g_param_spec_object ("stream", "Stream", + "The TransportStream for this sending bin", + transport_stream_get_type (), + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_RTCP_MUX, + g_param_spec_boolean ("rtcp-mux", "RTCP Mux", + "Whether RTCP packets are muxed with RTP packets", + FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +static void +transport_send_bin_init (TransportSendBin * send) +{ +} diff --git a/ext/webrtc/transportsendbin.h b/ext/webrtc/transportsendbin.h new file mode 100644 index 0000000000..fc5faf8da6 --- /dev/null +++ b/ext/webrtc/transportsendbin.h @@ -0,0 +1,58 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifndef __TRANSPORT_SEND_BIN_H__ +#define __TRANSPORT_SEND_BIN_H__ + +#include +#include "transportstream.h" +#include "utils.h" + +G_BEGIN_DECLS + +GType transport_send_bin_get_type(void); +#define GST_TYPE_WEBRTC_TRANSPORT_SEND_BIN (transport_send_bin_get_type()) +#define TRANSPORT_SEND_BIN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_WEBRTC_TRANSPORT_SEND_BIN,TransportSendBin)) +#define TRANSPORT_SEND_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_WEBRTC_TRANSPORT_SEND_BIN,TransportSendBinClass)) +#define TRANSPORT_SEND_BIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_WEBRTC_TRANSPORT_SEND_BIN,TransportSendBinClass)) + +struct _TransportSendBin +{ + GstBin parent; + + TransportStream *stream; /* parent transport stream */ + gboolean rtcp_mux; + + GstElement *outputselector; + + struct pad_block *rtp_block; + struct pad_block *rtcp_mux_block; + struct pad_block *rtcp_block; + struct pad_block *rtp_nice_block; + struct pad_block *rtcp_nice_block; +}; + +struct _TransportSendBinClass +{ + GstBinClass parent_class; +}; + +G_END_DECLS + +#endif /* __TRANSPORT_SEND_BIN_H__ */ diff --git a/ext/webrtc/transportstream.c b/ext/webrtc/transportstream.c new file mode 100644 index 0000000000..894b3217ab --- /dev/null +++ b/ext/webrtc/transportstream.c @@ -0,0 +1,252 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "transportstream.h" +#include "transportsendbin.h" +#include "transportreceivebin.h" +#include "gstwebrtcice.h" +#include "gstwebrtcbin.h" +#include "utils.h" + +#define transport_stream_parent_class parent_class +G_DEFINE_TYPE (TransportStream, transport_stream, GST_TYPE_OBJECT); + +enum +{ + PROP_0, + PROP_WEBRTC, + PROP_SESSION_ID, + PROP_RTCP_MUX, + PROP_DTLS_CLIENT, +}; + +static void +transport_stream_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + TransportStream *stream = TRANSPORT_STREAM (object); + + switch (prop_id) { + case PROP_WEBRTC: + gst_object_set_parent (GST_OBJECT (stream), g_value_get_object (value)); + break; + } + + GST_OBJECT_LOCK (stream); + switch (prop_id) { + case PROP_WEBRTC: + break; + case PROP_SESSION_ID: + stream->session_id = g_value_get_uint (value); + break; + case PROP_RTCP_MUX: + stream->rtcp_mux = g_value_get_boolean (value); + break; + case PROP_DTLS_CLIENT: + stream->dtls_client = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + GST_OBJECT_UNLOCK (stream); +} + +static void +transport_stream_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + TransportStream *stream = TRANSPORT_STREAM (object); + + GST_OBJECT_LOCK (stream); + switch (prop_id) { + case PROP_SESSION_ID: + g_value_set_uint (value, stream->session_id); + break; + case PROP_RTCP_MUX: + g_value_set_boolean (value, stream->rtcp_mux); + break; + case PROP_DTLS_CLIENT: + g_value_set_boolean (value, stream->dtls_client); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + GST_OBJECT_UNLOCK (stream); +} + +static void +transport_stream_dispose (GObject * object) +{ + TransportStream *stream = TRANSPORT_STREAM (object); + + if (stream->send_bin) + gst_object_unref (stream->send_bin); + stream->send_bin = NULL; + + if (stream->receive_bin) + gst_object_unref (stream->receive_bin); + stream->receive_bin = NULL; + + if (stream->transport) + gst_object_unref (stream->transport); + stream->transport = NULL; + + if (stream->rtcp_transport) + gst_object_unref (stream->rtcp_transport); + stream->rtcp_transport = NULL; + + GST_OBJECT_PARENT (object) = NULL; + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +transport_stream_finalize (GObject * object) +{ + TransportStream *stream = TRANSPORT_STREAM (object); + + g_array_free (stream->ptmap, TRUE); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +transport_stream_constructed (GObject * object) +{ + TransportStream *stream = TRANSPORT_STREAM (object); + GstWebRTCBin *webrtc; + GstWebRTCICETransport *ice_trans; + + stream->transport = gst_webrtc_dtls_transport_new (stream->session_id, FALSE); + stream->rtcp_transport = + gst_webrtc_dtls_transport_new (stream->session_id, TRUE); + + webrtc = GST_WEBRTC_BIN (gst_object_get_parent (GST_OBJECT (object))); + + g_object_bind_property (stream->transport, "client", stream, "dtls-client", + G_BINDING_BIDIRECTIONAL); + g_object_bind_property (stream->rtcp_transport, "client", stream, + "dtls-client", G_BINDING_BIDIRECTIONAL); + + g_object_bind_property (stream->transport, "certificate", + stream->rtcp_transport, "certificate", G_BINDING_BIDIRECTIONAL); + + /* Need to go full Java and have a transport manager? + * Or make the caller set the ICE transport up? */ + + stream->stream = _find_ice_stream_for_session (webrtc, stream->session_id); + if (stream->stream == NULL) { + stream->stream = gst_webrtc_ice_add_stream (webrtc->priv->ice, + stream->session_id); + _add_ice_stream_item (webrtc, stream->session_id, stream->stream); + } + ice_trans = + gst_webrtc_ice_find_transport (webrtc->priv->ice, stream->stream, + GST_WEBRTC_ICE_COMPONENT_RTP); + gst_webrtc_dtls_transport_set_transport (stream->transport, ice_trans); + gst_object_unref (ice_trans); + + ice_trans = + gst_webrtc_ice_find_transport (webrtc->priv->ice, stream->stream, + GST_WEBRTC_ICE_COMPONENT_RTCP); + gst_webrtc_dtls_transport_set_transport (stream->rtcp_transport, ice_trans); + gst_object_unref (ice_trans); + + stream->send_bin = g_object_new (transport_send_bin_get_type (), "stream", + stream, NULL); + gst_object_ref_sink (stream->send_bin); + stream->receive_bin = g_object_new (transport_receive_bin_get_type (), + "stream", stream, NULL); + gst_object_ref_sink (stream->receive_bin); + + gst_object_unref (webrtc); + + G_OBJECT_CLASS (parent_class)->constructed (object); +} + +static void +transport_stream_class_init (TransportStreamClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->constructed = transport_stream_constructed; + gobject_class->get_property = transport_stream_get_property; + gobject_class->set_property = transport_stream_set_property; + gobject_class->dispose = transport_stream_dispose; + gobject_class->finalize = transport_stream_finalize; + + /* some acrobatics are required to set the parent before _constructed() + * has been called */ + g_object_class_install_property (gobject_class, + PROP_WEBRTC, + g_param_spec_object ("webrtc", "Parent webrtcbin", + "Parent webrtcbin", + GST_TYPE_WEBRTC_BIN, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_SESSION_ID, + g_param_spec_uint ("session-id", "Session ID", + "Session ID used for this transport", + 0, G_MAXUINT, 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_RTCP_MUX, + g_param_spec_boolean ("rtcp-mux", "RTCP Mux", + "Whether RTCP packets are muxed with RTP packets", + FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_DTLS_CLIENT, + g_param_spec_boolean ("dtls-client", "DTLS client", + "Whether we take the client role in DTLS negotiation", + FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +static void +clear_ptmap_item (PtMapItem * item) +{ + if (item->caps) + gst_caps_unref (item->caps); +} + +static void +transport_stream_init (TransportStream * stream) +{ + stream->ptmap = g_array_new (FALSE, TRUE, sizeof (PtMapItem)); + g_array_set_clear_func (stream->ptmap, (GDestroyNotify) clear_ptmap_item); +} + +TransportStream * +transport_stream_new (GstWebRTCBin * webrtc, guint session_id) +{ + TransportStream *stream; + + stream = g_object_new (transport_stream_get_type (), "webrtc", webrtc, + "session-id", session_id, NULL); + + return stream; +} diff --git a/ext/webrtc/transportstream.h b/ext/webrtc/transportstream.h new file mode 100644 index 0000000000..9c4e4a0ded --- /dev/null +++ b/ext/webrtc/transportstream.h @@ -0,0 +1,69 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifndef __TRANSPORT_STREAM_H__ +#define __TRANSPORT_STREAM_H__ + +#include "fwd.h" +#include + +G_BEGIN_DECLS + +GType transport_stream_get_type(void); +#define GST_TYPE_WEBRTC_TRANSPORT_STREAM (transport_stream_get_type()) +#define TRANSPORT_STREAM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_WEBRTC_TRANSPORT_STREAM,TransportStream)) +#define TRANSPORT_STREAM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_WEBRTC_TRANSPORT_STREAM,TransportStreamClass)) +#define TRANSPORT_STREAM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_WEBRTC_TRANSPORT_STREAM,TransportStreamClass)) + +typedef struct +{ + guint8 pt; + GstCaps *caps; +} PtMapItem; + +struct _TransportStream +{ + GstObject parent; + + guint session_id; /* session_id */ + gboolean rtcp; + gboolean rtcp_mux; + gboolean rtcp_rsize; + gboolean dtls_client; + TransportSendBin *send_bin; /* bin containing all the sending transport elements */ + TransportReceiveBin *receive_bin; /* bin containing all the receiving transport elements */ + GstWebRTCICEStream *stream; + + GstWebRTCDTLSTransport *transport; + GstWebRTCDTLSTransport *rtcp_transport; + + GArray *ptmap; /* array of PtMapItem's */ +}; + +struct _TransportStreamClass +{ + GstObjectClass parent_class; +}; + +TransportStream * transport_stream_new (GstWebRTCBin * webrtc, + guint session_id); + +G_END_DECLS + +#endif /* __TRANSPORT_STREAM_H__ */ diff --git a/ext/webrtc/utils.c b/ext/webrtc/utils.c new file mode 100644 index 0000000000..2b99c7047f --- /dev/null +++ b/ext/webrtc/utils.c @@ -0,0 +1,138 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "utils.h" +#include "gstwebrtcbin.h" + +GstPadTemplate * +_find_pad_template (GstElement * element, GstPadDirection direction, + GstPadPresence presence, const gchar * name) +{ + GstElementClass *element_class = GST_ELEMENT_GET_CLASS (element); + const GList *l = gst_element_class_get_pad_template_list (element_class); + GstPadTemplate *templ = NULL; + + for (; l; l = l->next) { + templ = l->data; + if (templ->direction != direction) + continue; + if (templ->presence != presence) + continue; + if (g_strcmp0 (templ->name_template, name) == 0) { + return templ; + } + } + + return NULL; +} + +GstSDPMessage * +_get_latest_sdp (GstWebRTCBin * webrtc) +{ + if (webrtc->current_local_description && + webrtc->current_local_description->type == GST_WEBRTC_SDP_TYPE_ANSWER) { + return webrtc->current_local_description->sdp; + } + if (webrtc->current_remote_description && + webrtc->current_remote_description->type == GST_WEBRTC_SDP_TYPE_ANSWER) { + return webrtc->current_remote_description->sdp; + } + if (webrtc->current_local_description && + webrtc->current_local_description->type == GST_WEBRTC_SDP_TYPE_OFFER) { + return webrtc->current_local_description->sdp; + } + if (webrtc->current_remote_description && + webrtc->current_remote_description->type == GST_WEBRTC_SDP_TYPE_OFFER) { + return webrtc->current_remote_description->sdp; + } + + return NULL; +} + +struct pad_block * +_create_pad_block (GstElement * element, GstPad * pad, gulong block_id, + gpointer user_data, GDestroyNotify notify) +{ + struct pad_block *ret = g_new0 (struct pad_block, 1); + + ret->element = gst_object_ref (element); + ret->pad = gst_object_ref (pad); + ret->block_id = block_id; + ret->user_data = user_data; + ret->notify = notify; + + return ret; +} + +void +_free_pad_block (struct pad_block *block) +{ + if (!block) + return; + + if (block->block_id) + gst_pad_remove_probe (block->pad, block->block_id); + gst_object_unref (block->element); + gst_object_unref (block->pad); + if (block->notify) + block->notify (block->user_data); + g_free (block); +} + +gchar * +_enum_value_to_string (GType type, guint value) +{ + GEnumClass *enum_class; + GEnumValue *enum_value; + gchar *str = NULL; + + enum_class = g_type_class_ref (type); + enum_value = g_enum_get_value (enum_class, value); + + if (enum_value) + str = g_strdup (enum_value->value_nick); + + g_type_class_unref (enum_class); + + return str; +} + +const gchar * +_g_checksum_to_webrtc_string (GChecksumType type) +{ + switch (type) { + case G_CHECKSUM_SHA1: + return "sha-1"; + case G_CHECKSUM_SHA256: + return "sha-256"; +#ifdef G_CHECKSUM_SHA384 + case G_CHECKSUM_SHA384: + return "sha-384"; +#endif + case G_CHECKSUM_SHA512: + return "sha-512"; + default: + g_warning ("unknown GChecksumType!"); + return NULL; + } +} diff --git a/ext/webrtc/utils.h b/ext/webrtc/utils.h new file mode 100644 index 0000000000..f76f850d92 --- /dev/null +++ b/ext/webrtc/utils.h @@ -0,0 +1,65 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifndef __WEBRTC_UTILS_H__ +#define __WEBRTC_UTILS_H__ + +#include +#include +#include "fwd.h" + +G_BEGIN_DECLS + +GstPadTemplate * _find_pad_template (GstElement * element, + GstPadDirection direction, + GstPadPresence presence, + const gchar * name); + +GstSDPMessage * _get_latest_sdp (GstWebRTCBin * webrtc); + +GstWebRTCICEStream * _find_ice_stream_for_session (GstWebRTCBin * webrtc, + guint session_id); +void _add_ice_stream_item (GstWebRTCBin * webrtc, + guint session_id, + GstWebRTCICEStream * stream); + +struct pad_block +{ + GstElement *element; + GstPad *pad; + gulong block_id; + gpointer user_data; + GDestroyNotify notify; +}; + +void _free_pad_block (struct pad_block *block); +struct pad_block * _create_pad_block (GstElement * element, + GstPad * pad, + gulong block_id, + gpointer user_data, + GDestroyNotify notify); + +G_GNUC_INTERNAL +gchar * _enum_value_to_string (GType type, guint value); +G_GNUC_INTERNAL +const gchar * _g_checksum_to_webrtc_string (GChecksumType type); + +G_END_DECLS + +#endif /* __WEBRTC_UTILS_H__ */ diff --git a/ext/webrtc/webrtcsdp.c b/ext/webrtc/webrtcsdp.c new file mode 100644 index 0000000000..5584d9bd11 --- /dev/null +++ b/ext/webrtc/webrtcsdp.c @@ -0,0 +1,716 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "webrtcsdp.h" + +#include "utils.h" +#include "gstwebrtcbin.h" + +#include + +#define IS_EMPTY_SDP_ATTRIBUTE(val) (val == NULL || g_strcmp0(val, "") == 0) + +const gchar * +_sdp_source_to_string (SDPSource source) +{ + switch (source) { + case SDP_LOCAL: + return "local"; + case SDP_REMOTE: + return "remote"; + default: + return "none"; + } +} + +static gboolean +_check_valid_state_for_sdp_change (GstWebRTCBin * webrtc, SDPSource source, + GstWebRTCSDPType type, GError ** error) +{ + GstWebRTCSignalingState state = webrtc->signaling_state; +#define STATE(val) GST_WEBRTC_SIGNALING_STATE_ ## val +#define TYPE(val) GST_WEBRTC_SDP_TYPE_ ## val + + if (source == SDP_LOCAL && type == TYPE (OFFER) && state == STATE (STABLE)) + return TRUE; + if (source == SDP_LOCAL && type == TYPE (OFFER) + && state == STATE (HAVE_LOCAL_OFFER)) + return TRUE; + if (source == SDP_LOCAL && type == TYPE (ANSWER) + && state == STATE (HAVE_REMOTE_OFFER)) + return TRUE; + if (source == SDP_LOCAL && type == TYPE (PRANSWER) + && state == STATE (HAVE_REMOTE_OFFER)) + return TRUE; + if (source == SDP_LOCAL && type == TYPE (PRANSWER) + && state == STATE (HAVE_LOCAL_PRANSWER)) + return TRUE; + + if (source == SDP_REMOTE && type == TYPE (OFFER) && state == STATE (STABLE)) + return TRUE; + if (source == SDP_REMOTE && type == TYPE (OFFER) + && state == STATE (HAVE_REMOTE_OFFER)) + return TRUE; + if (source == SDP_REMOTE && type == TYPE (ANSWER) + && state == STATE (HAVE_LOCAL_OFFER)) + return TRUE; + if (source == SDP_REMOTE && type == TYPE (PRANSWER) + && state == STATE (HAVE_LOCAL_OFFER)) + return TRUE; + if (source == SDP_REMOTE && type == TYPE (PRANSWER) + && state == STATE (HAVE_REMOTE_PRANSWER)) + return TRUE; + + { + gchar *state = _enum_value_to_string (GST_TYPE_WEBRTC_SIGNALING_STATE, + webrtc->signaling_state); + gchar *type_str = _enum_value_to_string (GST_TYPE_WEBRTC_SDP_TYPE, type); + g_set_error (error, GST_WEBRTC_BIN_ERROR, + GST_WEBRTC_BIN_ERROR_INVALID_STATE, + "Not in the correct state (%s) for setting %s %s description", state, + _sdp_source_to_string (source), type_str); + g_free (state); + g_free (type_str); + } + + return FALSE; + +#undef STATE +#undef TYPE +} + +static gboolean +_check_sdp_crypto (GstWebRTCBin * webrtc, SDPSource source, + GstWebRTCSessionDescription * sdp, GError ** error) +{ + const gchar *message_fingerprint, *fingerprint; + const GstSDPKey *key; + int i; + + key = gst_sdp_message_get_key (sdp->sdp); + if (!IS_EMPTY_SDP_ATTRIBUTE (key->data)) { + g_set_error_literal (error, GST_WEBRTC_BIN_ERROR, + GST_WEBRTC_BIN_ERROR_BAD_SDP, "sdp contains a k line"); + return FALSE; + } + + message_fingerprint = fingerprint = + gst_sdp_message_get_attribute_val (sdp->sdp, "fingerprint"); + for (i = 0; i < gst_sdp_message_medias_len (sdp->sdp); i++) { + const GstSDPMedia *media = gst_sdp_message_get_media (sdp->sdp, i); + const gchar *media_fingerprint = + gst_sdp_media_get_attribute_val (media, "fingerprint"); + + if (!IS_EMPTY_SDP_ATTRIBUTE (message_fingerprint) + && !IS_EMPTY_SDP_ATTRIBUTE (media_fingerprint)) { + g_set_error (error, GST_WEBRTC_BIN_ERROR, + GST_WEBRTC_BIN_ERROR_FINGERPRINT, + "No fingerprint lines in sdp for media %u", i); + return FALSE; + } + if (IS_EMPTY_SDP_ATTRIBUTE (fingerprint)) { + fingerprint = media_fingerprint; + } + if (!IS_EMPTY_SDP_ATTRIBUTE (media_fingerprint) + && g_strcmp0 (fingerprint, media_fingerprint) != 0) { + g_set_error (error, GST_WEBRTC_BIN_ERROR, + GST_WEBRTC_BIN_ERROR_FINGERPRINT, + "Fingerprint in media %u differs from %s fingerprint. " + "\'%s\' != \'%s\'", i, message_fingerprint ? "global" : "previous", + fingerprint, media_fingerprint); + return FALSE; + } + } + + return TRUE; +} + +#if 0 +static gboolean +_session_has_attribute_key (const GstSDPMessage * msg, const gchar * key) +{ + int i; + for (i = 0; i < gst_sdp_message_attributes_len (msg); i++) { + const GstSDPAttribute *attr = gst_sdp_message_get_attribute (msg, i); + + if (g_strcmp0 (attr->key, key) == 0) + return TRUE; + } + + return FALSE; +} + +static gboolean +_session_has_attribute_key_value (const GstSDPMessage * msg, const gchar * key, + const gchar * value) +{ + int i; + for (i = 0; i < gst_sdp_message_attributes_len (msg); i++) { + const GstSDPAttribute *attr = gst_sdp_message_get_attribute (msg, i); + + if (g_strcmp0 (attr->key, key) == 0 && g_strcmp0 (attr->value, value) == 0) + return TRUE; + } + + return FALSE; +} + +static gboolean +_check_trickle_ice (GstSDPMessage * msg, GError ** error) +{ + if (!_session_has_attribute_key_value (msg, "ice-options", "trickle")) { + g_set_error_literal (error, GST_WEBRTC_BIN_ERROR, + GST_WEBRTC_BIN_ERROR_BAD_SDP, + "No required \'a=ice-options:trickle\' line in sdp"); + } + return TRUE; +} +#endif +gboolean +_media_has_attribute_key (const GstSDPMedia * media, const gchar * key) +{ + int i; + for (i = 0; i < gst_sdp_media_attributes_len (media); i++) { + const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i); + + if (g_strcmp0 (attr->key, key) == 0) + return TRUE; + } + + return FALSE; +} + +static gboolean +_media_has_mid (const GstSDPMedia * media, guint media_idx, GError ** error) +{ + const gchar *mid = gst_sdp_media_get_attribute_val (media, "mid"); + if (IS_EMPTY_SDP_ATTRIBUTE (mid)) { + g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP, + "media %u is missing or contains an empty \'mid\' attribute", + media_idx); + return FALSE; + } + return TRUE; +} + +static const gchar * +_media_get_ice_ufrag (const GstSDPMessage * msg, guint media_idx) +{ + const gchar *ice_ufrag; + + ice_ufrag = gst_sdp_message_get_attribute_val (msg, "ice-ufrag"); + if (IS_EMPTY_SDP_ATTRIBUTE (ice_ufrag)) { + const GstSDPMedia *media = gst_sdp_message_get_media (msg, media_idx); + ice_ufrag = gst_sdp_media_get_attribute_val (media, "ice-ufrag"); + if (IS_EMPTY_SDP_ATTRIBUTE (ice_ufrag)) + return NULL; + } + return ice_ufrag; +} + +static const gchar * +_media_get_ice_pwd (const GstSDPMessage * msg, guint media_idx) +{ + const gchar *ice_pwd; + + ice_pwd = gst_sdp_message_get_attribute_val (msg, "ice-pwd"); + if (IS_EMPTY_SDP_ATTRIBUTE (ice_pwd)) { + const GstSDPMedia *media = gst_sdp_message_get_media (msg, media_idx); + ice_pwd = gst_sdp_media_get_attribute_val (media, "ice-pwd"); + if (IS_EMPTY_SDP_ATTRIBUTE (ice_pwd)) + return NULL; + } + return ice_pwd; +} + +static gboolean +_media_has_setup (const GstSDPMedia * media, guint media_idx, GError ** error) +{ + static const gchar *valid_setups[] = { "actpass", "active", "passive", NULL }; + const gchar *setup = gst_sdp_media_get_attribute_val (media, "setup"); + if (IS_EMPTY_SDP_ATTRIBUTE (setup)) { + g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP, + "media %u is missing or contains an empty \'setup\' attribute", + media_idx); + return FALSE; + } + if (!g_strv_contains (valid_setups, setup)) { + g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP, + "media %u contains unknown \'setup\' attribute, \'%s\'", media_idx, + setup); + return FALSE; + } + return TRUE; +} + +#if 0 +static gboolean +_media_has_dtls_id (const GstSDPMedia * media, guint media_idx, GError ** error) +{ + const gchar *dtls_id = gst_sdp_media_get_attribute_val (media, "ice-pwd"); + if (IS_EMPTY_SDP_ATTRIBUTE (dtls_id)) { + g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP, + "media %u is missing or contains an empty \'dtls-id\' attribute", + media_idx); + return FALSE; + } + return TRUE; +} +#endif +gboolean +validate_sdp (GstWebRTCBin * webrtc, SDPSource source, + GstWebRTCSessionDescription * sdp, GError ** error) +{ +#if 0 + const gchar *group, *bundle_ice_ufrag = NULL, *bundle_ice_pwd = NULL; + gchar **group_members = NULL; + gboolean is_bundle = FALSE; +#endif + int i; + + if (!_check_valid_state_for_sdp_change (webrtc, source, sdp->type, error)) + return FALSE; + if (!_check_sdp_crypto (webrtc, source, sdp, error)) + return FALSE; +/* not explicitly required + if (ICE && !_check_trickle_ice (sdp->sdp)) + return FALSE; + group = gst_sdp_message_get_attribute_val (sdp->sdp, "group"); + is_bundle = g_str_has_prefix (group, "BUNDLE"); + if (is_bundle) + group_members = g_strsplit (&group[6], " ", -1);*/ + + for (i = 0; i < gst_sdp_message_medias_len (sdp->sdp); i++) { + const GstSDPMedia *media = gst_sdp_message_get_media (sdp->sdp, i); +#if 0 + const gchar *mid; + gboolean media_in_bundle = FALSE, first_media_in_bundle = FALSE; + gboolean bundle_only = FALSE; +#endif + if (!_media_has_mid (media, i, error)) + goto fail; +#if 0 + mid = gst_sdp_media_get_attribute_val (media, "mid"); + media_in_bundle = is_bundle && g_strv_contains (group_members, mid); + if (media_in_bundle) + bundle_only = + gst_sdp_media_get_attribute_val (media, "bundle-only") != NULL; + first_media_in_bundle = media_in_bundle + && g_strcmp0 (mid, group_members[0]) == 0; +#endif + if (!_media_get_ice_ufrag (sdp->sdp, i)) { + g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP, + "media %u is missing or contains an empty \'ice-ufrag\' attribute", + i); + goto fail; + } + if (!_media_get_ice_pwd (sdp->sdp, i)) { + g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP, + "media %u is missing or contains an empty \'ice-pwd\' attribute", i); + goto fail; + } + if (!_media_has_setup (media, i, error)) + goto fail; +#if 0 + /* check paramaters in bundle are the same */ + if (media_in_bundle) { + const gchar *ice_ufrag = + gst_sdp_media_get_attribute_val (media, "ice-ufrag"); + const gchar *ice_pwd = gst_sdp_media_get_attribute_val (media, "ice-pwd"); + if (!bundle_ice_ufrag) + bundle_ice_ufrag = ice_ufrag; + else if (!g_strcmp0 (bundle_ice_ufrag, ice_ufrag) != 0) { + g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP, + "media %u has different ice-ufrag values in bundle. " + "%s != %s", i, bundle_ice_ufrag, ice_ufrag); + goto fail; + } + if (!bundle_ice_pwd) { + bundle_ice_pwd = ice_pwd; + } else if (g_strcmp0 (bundle_ice_pwd, ice_pwd) == 0) { + g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP, + "media %u has different ice-ufrag values in bundle. " + "%s != %s", i, bundle_ice_ufrag, ice_ufrag); + goto fail; + } + } +#endif + } + +// g_strv_free (group_members); + + return TRUE; + +fail: +// g_strv_free (group_members); + return FALSE; +} + +GstWebRTCRTPTransceiverDirection +_get_direction_from_media (const GstSDPMedia * media) +{ + GstWebRTCRTPTransceiverDirection new_dir = + GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE; + int i; + + for (i = 0; i < gst_sdp_media_attributes_len (media); i++) { + const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i); + + if (g_strcmp0 (attr->key, "sendonly") == 0) { + if (new_dir != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE) { + GST_ERROR ("Multiple direction attributes"); + return GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE; + } + new_dir = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY; + } else if (g_strcmp0 (attr->key, "sendrecv") == 0) { + if (new_dir != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE) { + GST_ERROR ("Multiple direction attributes"); + return GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE; + } + new_dir = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV; + } else if (g_strcmp0 (attr->key, "recvonly") == 0) { + if (new_dir != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE) { + GST_ERROR ("Multiple direction attributes"); + return GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE; + } + new_dir = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY; + } else if (g_strcmp0 (attr->key, "inactive") == 0) { + if (new_dir != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE) { + GST_ERROR ("Multiple direction attributes"); + return GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE; + } + new_dir = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE; + } + } + + return new_dir; +} + +#define DIR(val) GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_ ## val +GstWebRTCRTPTransceiverDirection +_intersect_answer_directions (GstWebRTCRTPTransceiverDirection offer, + GstWebRTCRTPTransceiverDirection answer) +{ + if (offer == DIR (SENDONLY) && answer == DIR (SENDRECV)) + return DIR (RECVONLY); + if (offer == DIR (SENDONLY) && answer == DIR (RECVONLY)) + return DIR (RECVONLY); + if (offer == DIR (RECVONLY) && answer == DIR (SENDRECV)) + return DIR (SENDONLY); + if (offer == DIR (RECVONLY) && answer == DIR (SENDONLY)) + return DIR (SENDONLY); + if (offer == DIR (SENDRECV) && answer == DIR (SENDRECV)) + return DIR (SENDRECV); + if (offer == DIR (SENDRECV) && answer == DIR (SENDONLY)) + return DIR (SENDONLY); + if (offer == DIR (SENDRECV) && answer == DIR (RECVONLY)) + return DIR (RECVONLY); + + return DIR (NONE); +} + +void +_media_replace_direction (GstSDPMedia * media, + GstWebRTCRTPTransceiverDirection direction) +{ + gchar *dir_str; + int i; + + dir_str = + _enum_value_to_string (GST_TYPE_WEBRTC_RTP_TRANSCEIVER_DIRECTION, + direction); + + for (i = 0; i < gst_sdp_media_attributes_len (media); i++) { + const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i); + + if (g_strcmp0 (attr->key, "sendonly") == 0 + || g_strcmp0 (attr->key, "sendrecv") == 0 + || g_strcmp0 (attr->key, "recvonly") == 0) { + GstSDPAttribute new_attr = { 0, }; + GST_TRACE ("replace %s with %s", attr->key, dir_str); + gst_sdp_attribute_set (&new_attr, dir_str, ""); + gst_sdp_media_replace_attribute (media, i, &new_attr); + return; + } + } + + GST_TRACE ("add %s", dir_str); + gst_sdp_media_add_attribute (media, dir_str, ""); + g_free (dir_str); +} + +GstWebRTCRTPTransceiverDirection +_get_final_direction (GstWebRTCRTPTransceiverDirection local_dir, + GstWebRTCRTPTransceiverDirection remote_dir) +{ + GstWebRTCRTPTransceiverDirection new_dir; + new_dir = DIR (NONE); + switch (local_dir) { + case DIR (INACTIVE): + new_dir = DIR (INACTIVE); + break; + case DIR (SENDONLY): + if (remote_dir == DIR (SENDONLY)) { + GST_ERROR ("remote SDP has the same directionality. " + "This is not legal."); + return DIR (NONE); + } else if (remote_dir == DIR (INACTIVE)) { + new_dir = DIR (INACTIVE); + } else { + new_dir = DIR (SENDONLY); + } + break; + case DIR (RECVONLY): + if (remote_dir == DIR (RECVONLY)) { + GST_ERROR ("remote SDP has the same directionality. " + "This is not legal."); + return DIR (NONE); + } else if (remote_dir == DIR (INACTIVE)) { + new_dir = DIR (INACTIVE); + } else { + new_dir = DIR (RECVONLY); + } + break; + case DIR (SENDRECV): + if (remote_dir == DIR (INACTIVE)) { + new_dir = DIR (INACTIVE); + } else if (remote_dir == DIR (SENDONLY)) { + new_dir = DIR (RECVONLY); + } else if (remote_dir == DIR (RECVONLY)) { + new_dir = DIR (SENDONLY); + } else if (remote_dir == DIR (SENDRECV)) { + new_dir = DIR (SENDRECV); + } + break; + default: + g_assert_not_reached (); + break; + } + + if (new_dir == DIR (NONE)) { + GST_ERROR ("Abnormal situation!"); + return DIR (NONE); + } + + return new_dir; +} + +#undef DIR + +#define SETUP(val) GST_WEBRTC_DTLS_SETUP_ ## val +GstWebRTCDTLSSetup +_get_dtls_setup_from_media (const GstSDPMedia * media) +{ + int i; + + for (i = 0; i < gst_sdp_media_attributes_len (media); i++) { + const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i); + + if (g_strcmp0 (attr->key, "setup") == 0) { + if (g_strcmp0 (attr->value, "actpass") == 0) { + return SETUP (ACTPASS); + } else if (g_strcmp0 (attr->value, "active") == 0) { + return SETUP (ACTIVE); + } else if (g_strcmp0 (attr->value, "passive") == 0) { + return SETUP (PASSIVE); + } else { + GST_ERROR ("unknown setup value %s", attr->value); + return SETUP (NONE); + } + } + } + + GST_LOG ("no setup attribute in media"); + return SETUP (NONE); +} + +GstWebRTCDTLSSetup +_intersect_dtls_setup (GstWebRTCDTLSSetup offer) +{ + switch (offer) { + case SETUP (NONE): /* default is active */ + case SETUP (ACTPASS): + case SETUP (PASSIVE): + return SETUP (ACTIVE); + case SETUP (ACTIVE): + return SETUP (PASSIVE); + default: + return SETUP (NONE); + } +} + +void +_media_replace_setup (GstSDPMedia * media, GstWebRTCDTLSSetup setup) +{ + gchar *setup_str; + int i; + + setup_str = _enum_value_to_string (GST_TYPE_WEBRTC_DTLS_SETUP, setup); + + for (i = 0; i < gst_sdp_media_attributes_len (media); i++) { + const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i); + + if (g_strcmp0 (attr->key, "setup") == 0) { + GstSDPAttribute new_attr = { 0, }; + GST_TRACE ("replace setup:%s with setup:%s", attr->value, setup_str); + gst_sdp_attribute_set (&new_attr, "setup", setup_str); + gst_sdp_media_replace_attribute (media, i, &new_attr); + return; + } + } + + GST_TRACE ("add setup:%s", setup_str); + gst_sdp_media_add_attribute (media, "setup", setup_str); + g_free (setup_str); +} + +GstWebRTCDTLSSetup +_get_final_setup (GstWebRTCDTLSSetup local_setup, + GstWebRTCDTLSSetup remote_setup) +{ + GstWebRTCDTLSSetup new_setup; + + new_setup = SETUP (NONE); + switch (local_setup) { + case SETUP (NONE): + /* someone's done a bad job of mangling the SDP. or bugs */ + g_critical ("Received a locally generated sdp without a parseable " + "\'a=setup\' line. This indicates a bug somewhere. Bailing"); + return SETUP (NONE); + case SETUP (ACTIVE): + if (remote_setup == SETUP (ACTIVE)) { + GST_ERROR ("remote SDP has the same " + "\'a=setup:active\' attribute. This is not legal"); + return SETUP (NONE); + } + new_setup = SETUP (ACTIVE); + break; + case SETUP (PASSIVE): + if (remote_setup == SETUP (PASSIVE)) { + GST_ERROR ("remote SDP has the same " + "\'a=setup:passive\' attribute. This is not legal"); + return SETUP (NONE); + } + new_setup = SETUP (PASSIVE); + break; + case SETUP (ACTPASS): + if (remote_setup == SETUP (ACTPASS)) { + GST_ERROR ("remote SDP has the same " + "\'a=setup:actpass\' attribute. This is not legal"); + return SETUP (NONE); + } + if (remote_setup == SETUP (ACTIVE)) + new_setup = SETUP (PASSIVE); + else if (remote_setup == SETUP (PASSIVE)) + new_setup = SETUP (ACTIVE); + else if (remote_setup == SETUP (NONE)) { + /* XXX: what to do here? */ + GST_WARNING ("unspecified situation. local: " + "\'a=setup:actpass\' remote: none/unparseable"); + new_setup = SETUP (ACTIVE); + } + break; + default: + g_assert_not_reached (); + return SETUP (NONE); + } + if (new_setup == SETUP (NONE)) { + GST_ERROR ("Abnormal situation!"); + return SETUP (NONE); + } + + return new_setup; +} + +#undef SETUP + +gchar * +_generate_fingerprint_from_certificate (gchar * certificate, + GChecksumType checksum_type) +{ + gchar **lines, *line; + guchar *tmp, *decoded, *digest; + GChecksum *checksum; + GString *fingerprint; + gsize decoded_length, digest_size; + gint state = 0; + guint save = 0; + int i; + + g_return_val_if_fail (certificate != NULL, NULL); + + /* 1. decode the certificate removing newlines and the certificate header + * and footer */ + decoded = tmp = g_new0 (guchar, (strlen (certificate) / 4) * 3 + 3); + lines = g_strsplit (certificate, "\n", 0); + for (i = 0, line = lines[i]; line; line = lines[++i]) { + if (line[0] && !g_str_has_prefix (line, "-----")) + tmp += g_base64_decode_step (line, strlen (line), tmp, &state, &save); + } + g_strfreev (lines); + decoded_length = tmp - decoded; + + /* 2. compute a checksum of the decoded certificate */ + checksum = g_checksum_new (checksum_type); + digest_size = g_checksum_type_get_length (checksum_type); + digest = g_new (guint8, digest_size); + g_checksum_update (checksum, decoded, decoded_length); + g_checksum_get_digest (checksum, digest, &digest_size); + g_free (decoded); + + /* 3. hex encode the checksum separated with ':'s */ + fingerprint = g_string_new (NULL); + for (i = 0; i < digest_size; i++) { + if (i) + g_string_append (fingerprint, ":"); + g_string_append_printf (fingerprint, "%02X", digest[i]); + } + + g_free (digest); + g_checksum_free (checksum); + + return g_string_free (fingerprint, FALSE); +} + +#define DEFAULT_ICE_UFRAG_LEN 32 +#define DEFAULT_ICE_PASSWORD_LEN 32 +static const gchar *ice_credential_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789" "+/"; + +void +_generate_ice_credentials (gchar ** ufrag, gchar ** password) +{ + int i; + + *ufrag = g_malloc0 (DEFAULT_ICE_UFRAG_LEN + 1); + for (i = 0; i < DEFAULT_ICE_UFRAG_LEN; i++) + (*ufrag)[i] = + ice_credential_chars[g_random_int_range (0, + strlen (ice_credential_chars))]; + + *password = g_malloc0 (DEFAULT_ICE_PASSWORD_LEN + 1); + for (i = 0; i < DEFAULT_ICE_PASSWORD_LEN; i++) + (*password)[i] = + ice_credential_chars[g_random_int_range (0, + strlen (ice_credential_chars))]; +} diff --git a/ext/webrtc/webrtcsdp.h b/ext/webrtc/webrtcsdp.h new file mode 100644 index 0000000000..779dcc2763 --- /dev/null +++ b/ext/webrtc/webrtcsdp.h @@ -0,0 +1,80 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifndef __WEBRTC_SDP_H__ +#define __WEBRTC_SDP_H__ + +#include +#include +#include "fwd.h" + +G_BEGIN_DECLS + +typedef enum +{ + SDP_NONE, + SDP_LOCAL, + SDP_REMOTE, +} SDPSource; + +G_GNUC_INTERNAL +const gchar * _sdp_source_to_string (SDPSource source); + + +G_GNUC_INTERNAL +gboolean validate_sdp (GstWebRTCBin * webrtc, + SDPSource source, + GstWebRTCSessionDescription * sdp, + GError ** error); + +G_GNUC_INTERNAL +GstWebRTCRTPTransceiverDirection _get_direction_from_media (const GstSDPMedia * media); +G_GNUC_INTERNAL +GstWebRTCRTPTransceiverDirection _intersect_answer_directions (GstWebRTCRTPTransceiverDirection offer, + GstWebRTCRTPTransceiverDirection answer); +G_GNUC_INTERNAL +void _media_replace_direction (GstSDPMedia * media, + GstWebRTCRTPTransceiverDirection direction); +G_GNUC_INTERNAL +GstWebRTCRTPTransceiverDirection _get_final_direction (GstWebRTCRTPTransceiverDirection local_dir, + GstWebRTCRTPTransceiverDirection remote_dir); + +G_GNUC_INTERNAL +GstWebRTCDTLSSetup _get_dtls_setup_from_media (const GstSDPMedia * media); +G_GNUC_INTERNAL +GstWebRTCDTLSSetup _intersect_dtls_setup (GstWebRTCDTLSSetup offer); +G_GNUC_INTERNAL +void _media_replace_setup (GstSDPMedia * media, + GstWebRTCDTLSSetup setup); +G_GNUC_INTERNAL +GstWebRTCDTLSSetup _get_final_setup (GstWebRTCDTLSSetup local_setup, + GstWebRTCDTLSSetup remote_setup); +G_GNUC_INTERNAL +gchar * _generate_fingerprint_from_certificate (gchar * certificate, + GChecksumType checksum_type); +G_GNUC_INTERNAL +void _generate_ice_credentials (gchar ** ufrag, + gchar ** password); + +G_GNUC_INTERNAL +gboolean _media_has_attribute_key (const GstSDPMedia * media, + const gchar * key); + + +#endif /* __WEBRTC_UTILS_H__ */ diff --git a/ext/webrtc/webrtctransceiver.c b/ext/webrtc/webrtctransceiver.c new file mode 100644 index 0000000000..310956f2d9 --- /dev/null +++ b/ext/webrtc/webrtctransceiver.c @@ -0,0 +1,149 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "gstwebrtcbin.h" +#include "utils.h" +#include "webrtctransceiver.h" + +#define webrtc_transceiver_parent_class parent_class +G_DEFINE_TYPE (WebRTCTransceiver, webrtc_transceiver, + GST_TYPE_WEBRTC_RTP_TRANSCEIVER); + +enum +{ + PROP_0, + PROP_WEBRTC, +}; + +void +webrtc_transceiver_set_transport (WebRTCTransceiver * trans, + TransportStream * stream) +{ + GstWebRTCRTPTransceiver *rtp_trans; + + g_return_if_fail (WEBRTC_IS_TRANSCEIVER (trans)); + + rtp_trans = GST_WEBRTC_RTP_TRANSCEIVER (trans); + + gst_object_replace ((GstObject **) & trans->stream, (GstObject *) stream); + + if (rtp_trans->sender) + gst_object_replace ((GstObject **) & rtp_trans->sender->transport, + (GstObject *) stream->transport); + if (rtp_trans->receiver) + gst_object_replace ((GstObject **) & rtp_trans->receiver->transport, + (GstObject *) stream->transport); + + if (rtp_trans->sender) + gst_object_replace ((GstObject **) & rtp_trans->sender->rtcp_transport, + (GstObject *) stream->rtcp_transport); + if (rtp_trans->receiver) + gst_object_replace ((GstObject **) & rtp_trans->receiver->rtcp_transport, + (GstObject *) stream->rtcp_transport); +} + +static void +webrtc_transceiver_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + WebRTCTransceiver *trans = WEBRTC_TRANSCEIVER (object); + + switch (prop_id) { + case PROP_WEBRTC: + gst_object_set_parent (GST_OBJECT (trans), g_value_get_object (value)); + break; + } + + GST_OBJECT_LOCK (trans); + switch (prop_id) { + case PROP_WEBRTC: + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + GST_OBJECT_UNLOCK (trans); +} + +static void +webrtc_transceiver_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + WebRTCTransceiver *trans = WEBRTC_TRANSCEIVER (object); + + GST_OBJECT_LOCK (trans); + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + GST_OBJECT_UNLOCK (trans); +} + +static void +webrtc_transceiver_finalize (GObject * object) +{ + WebRTCTransceiver *trans = WEBRTC_TRANSCEIVER (object); + + if (trans->stream) + gst_object_unref (trans->stream); + trans->stream = NULL; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +webrtc_transceiver_class_init (WebRTCTransceiverClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->get_property = webrtc_transceiver_get_property; + gobject_class->set_property = webrtc_transceiver_set_property; + gobject_class->finalize = webrtc_transceiver_finalize; + + /* some acrobatics are required to set the parent before _constructed() + * has been called */ + g_object_class_install_property (gobject_class, + PROP_WEBRTC, + g_param_spec_object ("webrtc", "Parent webrtcbin", + "Parent webrtcbin", + GST_TYPE_WEBRTC_BIN, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); +} + +static void +webrtc_transceiver_init (WebRTCTransceiver * trans) +{ +} + +WebRTCTransceiver * +webrtc_transceiver_new (GstWebRTCBin * webrtc, GstWebRTCRTPSender * sender, + GstWebRTCRTPReceiver * receiver) +{ + WebRTCTransceiver *trans; + + trans = g_object_new (webrtc_transceiver_get_type (), "sender", sender, + "receiver", receiver, "webrtc", webrtc, NULL); + + return trans; +} diff --git a/ext/webrtc/webrtctransceiver.h b/ext/webrtc/webrtctransceiver.h new file mode 100644 index 0000000000..b90fea043a --- /dev/null +++ b/ext/webrtc/webrtctransceiver.h @@ -0,0 +1,57 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifndef __WEBRTC_TRANSCEIVER_H__ +#define __WEBRTC_TRANSCEIVER_H__ + +#include "fwd.h" +#include +#include "transportstream.h" + +G_BEGIN_DECLS + +GType webrtc_transceiver_get_type(void); +#define WEBRTC_TYPE_TRANSCEIVER (webrtc_transceiver_get_type()) +#define WEBRTC_TRANSCEIVER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),WEBRTC_TYPE_TRANSCEIVER,WebRTCTransceiver)) +#define WEBRTC_IS_TRANSCEIVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),WEBRTC_TYPE_TRANSCEIVER)) +#define WEBRTC_TRANSCEIVER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,WEBRTC_TYPE_TRANSCEIVER,WebRTCTransceiverClass)) +#define WEBRTC_TRANSCEIVER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,WEBRTC_TYPE_TRANSCEIVER,WebRTCTransceiverClass)) + +struct _WebRTCTransceiver +{ + GstWebRTCRTPTransceiver parent; + + TransportStream *stream; +}; + +struct _WebRTCTransceiverClass +{ + GstWebRTCRTPTransceiverClass parent_class; +}; + +WebRTCTransceiver * webrtc_transceiver_new (GstWebRTCBin * webrtc, + GstWebRTCRTPSender * sender, + GstWebRTCRTPReceiver * receiver); + +void webrtc_transceiver_set_transport (WebRTCTransceiver * trans, + TransportStream * stream); + +G_END_DECLS + +#endif /* __WEBRTC_TRANSCEIVER_H__ */ diff --git a/gst-libs/gst/Makefile.am b/gst-libs/gst/Makefile.am index ae541aaf81..db67fc89fc 100644 --- a/gst-libs/gst/Makefile.am +++ b/gst-libs/gst/Makefile.am @@ -7,12 +7,12 @@ OPENCV_DIR=opencv endif SUBDIRS = uridownloader adaptivedemux interfaces basecamerabinsrc codecparsers \ - insertbin mpegts video audio player isoff $(WAYLAND_DIR) \ + insertbin mpegts video audio player isoff webrtc $(WAYLAND_DIR) \ $(OPENCV_DIR) noinst_HEADERS = gst-i18n-plugin.h gettext.h glib-compat-private.h DIST_SUBDIRS = uridownloader adaptivedemux interfaces basecamerabinsrc \ - codecparsers insertbin mpegts wayland opencv video audio player isoff + codecparsers insertbin mpegts wayland opencv video audio player isoff webrtc adaptivedemux: uridownloader diff --git a/gst-libs/gst/meson.build b/gst-libs/gst/meson.build index aac5398af7..2e579540e7 100644 --- a/gst-libs/gst/meson.build +++ b/gst-libs/gst/meson.build @@ -12,3 +12,4 @@ subdir('opencv') subdir('player') subdir('video') subdir('wayland') +subdir('webrtc') diff --git a/gst-libs/gst/webrtc/Makefile.am b/gst-libs/gst/webrtc/Makefile.am new file mode 100644 index 0000000000..49bb95a017 --- /dev/null +++ b/gst-libs/gst/webrtc/Makefile.am @@ -0,0 +1,54 @@ +lib_LTLIBRARIES = libgstwebrtc-@GST_API_VERSION@.la + +glib_enum_headers = dtlstransport.h icetransport.h rtptransceiver.h webrtc_fwd.h +glib_enum_define = GST_WEBRTC +glib_gen_prefix = gst_webrtc +glib_gen_basename = webrtc +glib_gen_decl_banner=GST_EXPORT + +built_sources = webrtc-enumtypes.c +built_headers = webrtc-enumtypes.h +BUILT_SOURCES = $(built_sources) $(built_headers) +CLEANFILES = $(BUILT_SOURCES) + +libgstwebrtc_@GST_API_VERSION@_la_SOURCES = \ + dtlstransport.c \ + icetransport.c \ + rtcsessiondescription.c \ + rtpreceiver.c \ + rtpsender.c \ + rtptransceiver.c + +nodist_libgstwebrtc_@GST_API_VERSION@_la_SOURCES = $(built_sources) + +libgstwebrtc_@GST_API_VERSION@includedir = $(includedir)/gstreamer-@GST_API_VERSION@/gst/webrtc +libgstwebrtc_@GST_API_VERSION@include_HEADERS = \ + dtlstransport.h \ + icetransport.h \ + rtcsessiondescription.h \ + rtpreceiver.h \ + rtpsender.h \ + rtptransceiver.h \ + webrtc_fwd.h \ + webrtc.h + +nodist_libgstwebrtc_@GST_API_VERSION@include_HEADERS = $(built_headers) + +libgstwebrtc_@GST_API_VERSION@_la_CFLAGS = \ + -I$(top_builddir)/gst-libs \ + -I$(top_srcdir)/gst-libs \ + $(GST_PLUGINS_BASE_CFLAGS) \ + $(GST_BASE_CFLAGS) \ + $(GST_CFLAGS) \ + $(GST_SDP_CFLAGS) +libgstwebrtc_@GST_API_VERSION@_la_LIBADD = \ + $(GST_PLUGINS_BASE_LIBS) \ + $(GST_BASE_LIBS) \ + $(GST_LIBS) \ + $(GST_SDP_LIBS) +libgstwebrtc_@GST_API_VERSION@_la_LDFLAGS = \ + $(GST_LIB_LDFLAGS) \ + $(GST_ALL_LDFLAGS) \ + $(GST_LT_LDFLAGS) + +include $(top_srcdir)/common/gst-glib-gen.mak diff --git a/gst-libs/gst/webrtc/dtlstransport.c b/gst-libs/gst/webrtc/dtlstransport.c new file mode 100644 index 0000000000..31324c34d7 --- /dev/null +++ b/gst-libs/gst/webrtc/dtlstransport.c @@ -0,0 +1,238 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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:gstwebrtc-dtlstransport + * @short_description: RTCDtlsTransport object + * @title: GstWebRTCDTLSTransport + * @see_also: #GstWebRTCRTPSender, #GstWebRTCRTPReceiver, #GstWebRTCICETransport + * + * https://www.w3.org/TR/webrtc/#rtcdtlstransport + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "dtlstransport.h" + +#define GST_CAT_DEFAULT gst_webrtc_dtls_transport_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +#define gst_webrtc_dtls_transport_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstWebRTCDTLSTransport, gst_webrtc_dtls_transport, + GST_TYPE_OBJECT, GST_DEBUG_CATEGORY_INIT (gst_webrtc_dtls_transport_debug, + "dtlstransport", 0, "dtlstransport"); + ); + +enum +{ + SIGNAL_0, + LAST_SIGNAL, +}; + +enum +{ + PROP_0, + PROP_SESSION_ID, + PROP_TRANSPORT, + PROP_STATE, + PROP_CLIENT, + PROP_CERTIFICATE, + PROP_REMOTE_CERTIFICATE, + PROP_RTCP, +}; + +void +gst_webrtc_dtls_transport_set_transport (GstWebRTCDTLSTransport * transport, + GstWebRTCICETransport * ice) +{ + g_return_if_fail (GST_IS_WEBRTC_DTLS_TRANSPORT (transport)); + g_return_if_fail (GST_IS_WEBRTC_ICE_TRANSPORT (ice)); + + gst_object_replace ((GstObject **) & transport->transport, GST_OBJECT (ice)); +} + +static void +gst_webrtc_dtls_transport_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstWebRTCDTLSTransport *webrtc = GST_WEBRTC_DTLS_TRANSPORT (object); + + switch (prop_id) { + case PROP_SESSION_ID: + webrtc->session_id = g_value_get_uint (value); + break; + case PROP_CLIENT: + g_object_set_property (G_OBJECT (webrtc->dtlssrtpenc), "is-client", + value); + gst_element_set_locked_state (webrtc->dtlssrtpenc, FALSE); + gst_element_sync_state_with_parent (webrtc->dtlssrtpenc); + break; + case PROP_CERTIFICATE: + g_object_set_property (G_OBJECT (webrtc->dtlssrtpdec), "pem", value); + break; + case PROP_RTCP: + webrtc->is_rtcp = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_webrtc_dtls_transport_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstWebRTCDTLSTransport *webrtc = GST_WEBRTC_DTLS_TRANSPORT (object); + + switch (prop_id) { + case PROP_SESSION_ID: + g_value_set_uint (value, webrtc->session_id); + break; + case PROP_TRANSPORT: + g_value_set_object (value, webrtc->transport); + break; + case PROP_STATE: + g_value_set_enum (value, webrtc->state); + break; + case PROP_CLIENT: + g_object_get_property (G_OBJECT (webrtc->dtlssrtpenc), "is-client", + value); + break; + case PROP_CERTIFICATE: + g_object_get_property (G_OBJECT (webrtc->dtlssrtpdec), "pem", value); + break; + case PROP_REMOTE_CERTIFICATE: + g_object_get_property (G_OBJECT (webrtc->dtlssrtpdec), "peer-pem", value); + break; + case PROP_RTCP: + g_value_set_boolean (value, webrtc->is_rtcp); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_webrtc_dtls_transport_finalize (GObject * object) +{ + GstWebRTCDTLSTransport *webrtc = GST_WEBRTC_DTLS_TRANSPORT (object); + + if (webrtc->transport) { + gst_object_unref (webrtc->transport); + } + webrtc->transport = NULL; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_webrtc_dtls_transport_constructed (GObject * object) +{ + GstWebRTCDTLSTransport *webrtc = GST_WEBRTC_DTLS_TRANSPORT (object); + gchar *connection_id; + + /* XXX: this may collide with another connection_id however this is only a + * problem if multiple dtls element sets are being used within the same + * process */ + connection_id = g_strdup_printf ("%s_%u_%u", webrtc->is_rtcp ? "rtcp" : "rtp", + webrtc->session_id, g_random_int ()); + + webrtc->dtlssrtpenc = gst_element_factory_make ("dtlssrtpenc", NULL); + g_object_set (webrtc->dtlssrtpenc, "connection-id", connection_id, + "is-client", webrtc->client, NULL); + + webrtc->dtlssrtpdec = gst_element_factory_make ("dtlssrtpdec", NULL); + g_object_set (webrtc->dtlssrtpdec, "connection-id", connection_id, NULL); + g_free (connection_id); + + G_OBJECT_CLASS (parent_class)->constructed (object); +} + +static void +gst_webrtc_dtls_transport_class_init (GstWebRTCDTLSTransportClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->constructed = gst_webrtc_dtls_transport_constructed; + gobject_class->get_property = gst_webrtc_dtls_transport_get_property; + gobject_class->set_property = gst_webrtc_dtls_transport_set_property; + gobject_class->finalize = gst_webrtc_dtls_transport_finalize; + + g_object_class_install_property (gobject_class, + PROP_SESSION_ID, + g_param_spec_uint ("session-id", "Session ID", + "Unique session ID", 0, G_MAXUINT, 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_TRANSPORT, + g_param_spec_object ("transport", "ICE transport", + "ICE transport used by this dtls transport", + GST_TYPE_WEBRTC_ICE_TRANSPORT, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /* FIXME: implement */ + g_object_class_install_property (gobject_class, + PROP_STATE, + g_param_spec_enum ("state", "DTLS state", + "State of the DTLS transport", + GST_TYPE_WEBRTC_DTLS_TRANSPORT_STATE, + GST_WEBRTC_DTLS_TRANSPORT_STATE_NEW, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_CLIENT, + g_param_spec_boolean ("client", "DTLS client", + "Are we the client in the DTLS handshake?", FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_CERTIFICATE, + g_param_spec_string ("certificate", "DTLS certificate", + "DTLS certificate", NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_REMOTE_CERTIFICATE, + g_param_spec_string ("remote-certificate", "Remote DTLS certificate", + "Remote DTLS certificate", NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_RTCP, + g_param_spec_boolean ("rtcp", "RTCP", + "The transport is being used solely for RTCP", FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); +} + +static void +gst_webrtc_dtls_transport_init (GstWebRTCDTLSTransport * webrtc) +{ +} + +GstWebRTCDTLSTransport * +gst_webrtc_dtls_transport_new (guint session_id, gboolean is_rtcp) +{ + return g_object_new (GST_TYPE_WEBRTC_DTLS_TRANSPORT, "session-id", session_id, + "rtcp", is_rtcp, NULL); +} diff --git a/gst-libs/gst/webrtc/dtlstransport.h b/gst-libs/gst/webrtc/dtlstransport.h new file mode 100644 index 0000000000..366a602a24 --- /dev/null +++ b/gst-libs/gst/webrtc/dtlstransport.h @@ -0,0 +1,70 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifndef __GST_WEBRTC_DTLS_TRANSPORT_H__ +#define __GST_WEBRTC_DTLS_TRANSPORT_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +GST_EXPORT +GType gst_webrtc_dtls_transport_get_type(void); +#define GST_TYPE_WEBRTC_DTLS_TRANSPORT (gst_webrtc_dtls_transport_get_type()) +#define GST_WEBRTC_DTLS_TRANSPORT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_WEBRTC_DTLS_TRANSPORT,GstWebRTCDTLSTransport)) +#define GST_IS_WEBRTC_DTLS_TRANSPORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_WEBRTC_DTLS_TRANSPORT)) +#define GST_WEBRTC_DTLS_TRANSPORT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_WEBRTC_DTLS_TRANSPORT,GstWebRTCDTLSTransportClass)) +#define GST_IS_WEBRTC_DTLS_TRANSPORT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_WEBRTC_DTLS_TRANSPORT)) +#define GST_WEBRTC_DTLS_TRANSPORT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_WEBRTC_DTLS_TRANSPORT,GstWebRTCDTLSTransportClass)) + +struct _GstWebRTCDTLSTransport +{ + GstObject parent; + + GstWebRTCICETransport *transport; + GstWebRTCDTLSTransportState state; + + gboolean is_rtcp; + gboolean client; + guint session_id; + GstElement *dtlssrtpenc; + GstElement *dtlssrtpdec; + + gpointer _padding[GST_PADDING]; +}; + +struct _GstWebRTCDTLSTransportClass +{ + GstBinClass parent_class; + + gpointer _padding[GST_PADDING]; +}; + +GST_EXPORT +GstWebRTCDTLSTransport * gst_webrtc_dtls_transport_new (guint session_id, gboolean rtcp); + +GST_EXPORT +void gst_webrtc_dtls_transport_set_transport (GstWebRTCDTLSTransport * transport, + GstWebRTCICETransport * ice); + +G_END_DECLS + +#endif /* __GST_WEBRTC_DTLS_TRANSPORT_H__ */ diff --git a/gst-libs/gst/webrtc/icetransport.c b/gst-libs/gst/webrtc/icetransport.c new file mode 100644 index 0000000000..d5ed0605e6 --- /dev/null +++ b/gst-libs/gst/webrtc/icetransport.c @@ -0,0 +1,204 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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:gstwebrtc-icetransport + * @short_description: RTCIceTransport object + * @title: GstWebRTCICETransport + * @see_also: #GstWebRTCRTPSender, #GstWebRTCRTPReceiver, #GstWebRTCDTLSTransport + * + * https://www.w3.org/TR/webrtc/#rtcicetransport + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "icetransport.h" +#include "webrtc-enumtypes.h" + +#define GST_CAT_DEFAULT gst_webrtc_ice_transport_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +#define gst_webrtc_ice_transport_parent_class parent_class +/* We would inherit from GstBin however when combined with the dtls transport, + * this causes loops in the graph. */ +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstWebRTCICETransport, + gst_webrtc_ice_transport, GST_TYPE_OBJECT, + GST_DEBUG_CATEGORY_INIT (gst_webrtc_ice_transport_debug, + "webrtcicetransport", 0, "webrtcicetransport");); + +enum +{ + SIGNAL_0, + ON_SELECTED_CANDIDATE_PAIR_CHANGE_SIGNAL, + ON_NEW_CANDIDATE_SIGNAL, + LAST_SIGNAL, +}; + +enum +{ + PROP_0, + PROP_COMPONENT, + PROP_STATE, + PROP_GATHERING_STATE, +}; + +static guint gst_webrtc_ice_transport_signals[LAST_SIGNAL] = { 0 }; + +void +gst_webrtc_ice_transport_connection_state_change (GstWebRTCICETransport * ice, + GstWebRTCICEConnectionState new_state) +{ + ice->state = new_state; + g_object_notify (G_OBJECT (ice), "state"); +} + +void +gst_webrtc_ice_transport_gathering_state_change (GstWebRTCICETransport * ice, + GstWebRTCICEGatheringState new_state) +{ + ice->gathering_state = new_state; + g_object_notify (G_OBJECT (ice), "gathering-state"); +} + +void +gst_webrtc_ice_transport_selected_pair_change (GstWebRTCICETransport * ice) +{ + g_signal_emit (ice, + gst_webrtc_ice_transport_signals + [ON_SELECTED_CANDIDATE_PAIR_CHANGE_SIGNAL], 0); +} + +void +gst_webrtc_ice_transport_new_candidate (GstWebRTCICETransport * ice, + guint stream_id, GstWebRTCICEComponent component, gchar * attr) +{ + g_signal_emit (ice, gst_webrtc_ice_transport_signals[ON_NEW_CANDIDATE_SIGNAL], + stream_id, component, attr); +} + +static void +gst_webrtc_ice_transport_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstWebRTCICETransport *webrtc = GST_WEBRTC_ICE_TRANSPORT (object); + + switch (prop_id) { + case PROP_COMPONENT: + webrtc->component = g_value_get_enum (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_webrtc_ice_transport_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstWebRTCICETransport *webrtc = GST_WEBRTC_ICE_TRANSPORT (object); + + switch (prop_id) { + case PROP_COMPONENT: + g_value_set_enum (value, webrtc->component); + break; + case PROP_STATE: + g_value_set_enum (value, webrtc->state); + break; + case PROP_GATHERING_STATE: + g_value_set_enum (value, webrtc->gathering_state); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_webrtc_ice_transport_finalize (GObject * object) +{ +// GstWebRTCICETransport *webrtc = GST_WEBRTC_ICE_TRANSPORT (object); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_webrtc_ice_transport_constructed (GObject * object) +{ +// GstWebRTCICETransport *webrtc = GST_WEBRTC_ICE_TRANSPORT (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); +} + +static void +gst_webrtc_ice_transport_class_init (GstWebRTCICETransportClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->constructed = gst_webrtc_ice_transport_constructed; + gobject_class->get_property = gst_webrtc_ice_transport_get_property; + gobject_class->set_property = gst_webrtc_ice_transport_set_property; + gobject_class->finalize = gst_webrtc_ice_transport_finalize; + + g_object_class_install_property (gobject_class, + PROP_COMPONENT, + g_param_spec_enum ("component", + "ICE component", "The ICE component of this transport", + GST_TYPE_WEBRTC_ICE_COMPONENT, 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_STATE, + g_param_spec_enum ("state", + "ICE connection state", "The ICE connection state of this transport", + GST_TYPE_WEBRTC_ICE_CONNECTION_STATE, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_GATHERING_STATE, + g_param_spec_enum ("gathering-state", + "ICE gathering state", "The ICE gathering state of this transport", + GST_TYPE_WEBRTC_ICE_GATHERING_STATE, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GstWebRTC::on-selected_candidate-pair-change: + * @object: the #GstWebRTCICETransport + */ + gst_webrtc_ice_transport_signals[ON_SELECTED_CANDIDATE_PAIR_CHANGE_SIGNAL] = + g_signal_new ("on-selected-candidate-pair-change", + G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_generic, G_TYPE_NONE, 0); + + /** + * GstWebRTC::on-new-candidate: + * @object: the #GstWebRTCICETransport + */ + gst_webrtc_ice_transport_signals[ON_NEW_CANDIDATE_SIGNAL] = + g_signal_new ("on-new-candidate", + G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_STRING); +} + +static void +gst_webrtc_ice_transport_init (GstWebRTCICETransport * webrtc) +{ +} diff --git a/gst-libs/gst/webrtc/icetransport.h b/gst-libs/gst/webrtc/icetransport.h new file mode 100644 index 0000000000..30730fa9b5 --- /dev/null +++ b/gst-libs/gst/webrtc/icetransport.h @@ -0,0 +1,76 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifndef __GST_WEBRTC_ICE_TRANSPORT_H__ +#define __GST_WEBRTC_ICE_TRANSPORT_H__ + +#include +#include + +G_BEGIN_DECLS + +GST_EXPORT +GType gst_webrtc_ice_transport_get_type(void); +#define GST_TYPE_WEBRTC_ICE_TRANSPORT (gst_webrtc_ice_transport_get_type()) +#define GST_WEBRTC_ICE_TRANSPORT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_WEBRTC_ICE_TRANSPORT,GstWebRTCICETransport)) +#define GST_IS_WEBRTC_ICE_TRANSPORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_WEBRTC_ICE_TRANSPORT)) +#define GST_WEBRTC_ICE_TRANSPORT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_WEBRTC_ICE_TRANSPORT,GstWebRTCICETransportClass)) +#define GST_IS_WEBRTC_ICE_TRANSPORT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_WEBRTC_ICE_TRANSPORT)) +#define GST_WEBRTC_ICE_TRANSPORT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_WEBRTC_ICE_TRANSPORT,GstWebRTCICETransportClass)) + +struct _GstWebRTCICETransport +{ + GstObject parent; + + GstWebRTCIceRole role; + GstWebRTCICEComponent component; + + GstWebRTCICEConnectionState state; + GstWebRTCICEGatheringState gathering_state; + + /* Filled by subclasses */ + GstElement *src; + GstElement *sink; + + gpointer _padding[GST_PADDING]; +}; + +struct _GstWebRTCICETransportClass +{ + GstBinClass parent_class; + + gboolean (*gather_candidates) (GstWebRTCICETransport * transport); + + gpointer _padding[GST_PADDING]; +}; + +GST_EXPORT +void gst_webrtc_ice_transport_connection_state_change (GstWebRTCICETransport * ice, + GstWebRTCICEConnectionState new_state); +GST_EXPORT +void gst_webrtc_ice_transport_gathering_state_change (GstWebRTCICETransport * ice, + GstWebRTCICEGatheringState new_state); +GST_EXPORT +void gst_webrtc_ice_transport_selected_pair_change (GstWebRTCICETransport * ice); +GST_EXPORT +void gst_webrtc_ice_transport_new_candidate (GstWebRTCICETransport * ice, guint stream_id, GstWebRTCICEComponent component, gchar * attr); + +G_END_DECLS + +#endif /* __GST_WEBRTC_ICE_TRANSPORT_H__ */ diff --git a/gst-libs/gst/webrtc/meson.build b/gst-libs/gst/webrtc/meson.build new file mode 100644 index 0000000000..c670eadb5f --- /dev/null +++ b/gst-libs/gst/webrtc/meson.build @@ -0,0 +1,59 @@ +webrtc_sources = [ + 'dtlstransport.c', + 'icetransport.c', + 'rtcsessiondescription.c', + 'rtpreceiver.c', + 'rtpsender.c', + 'rtptransceiver.c', +] + +webrtc_headers = [ + 'dtlstransport.h', + 'icetransport.h', + 'rtcsessiondescription.h', + 'rtpreceiver.h', + 'rtpsender.h', + 'rtptransceiver.h', + 'webrtc_fwd.h', + 'webrtc.h', +] + +webrtc_enumtypes_headers = [ + 'dtlstransport.h', + 'icetransport.h', + 'rtptransceiver.h', + 'webrtc_fwd.h', +] + +mkenums = find_program('webrtc_mkenum.py') +gstwebrtc_h = custom_target('gstwebrtcenum_h', + output : 'webrtc-enumtypes.h', + input : webrtc_enumtypes_headers, + install : true, + install_dir : 'include/gstreamer-1.0/gst/webrtc/', + command : [mkenums, glib_mkenums, '@OUTPUT@', '@INPUT@']) + +gstwebrtc_c = custom_target('gstwebrtcenum_c', + output : 'webrtc-enumtypes.c', + input : webrtc_enumtypes_headers, + depends : [gstwebrtc_h], + command : [mkenums, glib_mkenums, '@OUTPUT@', '@INPUT@']) +webrtc_gen_sources = [gstwebrtc_h] + +gstwebrtc_dependencies = [gstbase_dep, gstpbutils_dep, gstsdp_dep] + +gstwebrtc = library('gstwebrtc-' + api_version, + webrtc_sources, gstwebrtc_c, gstwebrtc_h, + c_args : gst_plugins_bad_args + ['-DGST_USE_UNSTABLE_API'], + include_directories : [configinc, libsinc], + version : libversion, + soversion : soversion, + install : true, + dependencies : gstwebrtc_dependencies, +) + +install_headers(webrtc_headers, subdir : 'gstreamer-1.0/gst/webrtc') + +gstwebrtc_dep = declare_dependency(link_with: gstwebrtc, + include_directories : libsinc, + dependencies: gstwebrtc_dependencies) diff --git a/gst-libs/gst/webrtc/rtcsessiondescription.c b/gst-libs/gst/webrtc/rtcsessiondescription.c new file mode 100644 index 0000000000..3987ab63f4 --- /dev/null +++ b/gst-libs/gst/webrtc/rtcsessiondescription.c @@ -0,0 +1,123 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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:gstwebrtc-sessiondescription + * @short_description: RTCSessionDescription object + * @title: GstWebRTCSessionDescription + * + * https://www.w3.org/TR/webrtc/#rtcsessiondescription-class + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "rtcsessiondescription.h" + +#define GST_CAT_DEFAULT gst_webrtc_peerconnection_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +/** + * gst_webrtc_sdp_type_to_string: + * @type: a #GstWebRTCSDPType + * + * Returns: the string representation of @type or "unknown" when @type is not + * recognized. + */ +const gchar * +gst_webrtc_sdp_type_to_string (GstWebRTCSDPType type) +{ + switch (type) { + case GST_WEBRTC_SDP_TYPE_OFFER: + return "offer"; + case GST_WEBRTC_SDP_TYPE_PRANSWER: + return "pranswer"; + case GST_WEBRTC_SDP_TYPE_ANSWER: + return "answer"; + case GST_WEBRTC_SDP_TYPE_ROLLBACK: + return "rollback"; + default: + return "unknown"; + } +} + +/** + * gst_webrtc_session_description_copy: + * @src: (transfer none): a #GstWebRTCSessionDescription + * + * Returns: (transfer full): a new copy of @src + */ +GstWebRTCSessionDescription * +gst_webrtc_session_description_copy (const GstWebRTCSessionDescription * src) +{ + GstWebRTCSessionDescription *ret; + + if (!src) + return NULL; + + ret = g_new0 (GstWebRTCSessionDescription, 1); + + ret->type = src->type; + gst_sdp_message_copy (src->sdp, &ret->sdp); + + return ret; +} + +/** + * gst_webrtc_session_description_free: + * @desc: (transfer full): a #GstWebRTCSessionDescription + * + * Free @desc and all associated resources + */ +void +gst_webrtc_session_description_free (GstWebRTCSessionDescription * desc) +{ + g_return_if_fail (desc != NULL); + + gst_sdp_message_free (desc->sdp); + g_free (desc); +} + +/** + * gst_webrtc_session_description_new: + * @type: a #GstWebRTCSDPType + * @sdp: a #GstSDPMessage + * + * Returns: (transfer full): a new #GstWebRTCSessionDescription from @type + * and @sdp + */ +GstWebRTCSessionDescription * +gst_webrtc_session_description_new (GstWebRTCSDPType type, GstSDPMessage * sdp) +{ + GstWebRTCSessionDescription *ret; + + ret = g_new0 (GstWebRTCSessionDescription, 1); + + ret->type = type; + ret->sdp = sdp; + + return ret; +} + +G_DEFINE_BOXED_TYPE_WITH_CODE (GstWebRTCSessionDescription, + gst_webrtc_session_description, gst_webrtc_session_description_copy, + gst_webrtc_session_description_free, + GST_DEBUG_CATEGORY_INIT (gst_webrtc_peerconnection_debug, + "webrtcsessiondescription", 0, "webrtcsessiondescription")); diff --git a/gst-libs/gst/webrtc/rtcsessiondescription.h b/gst-libs/gst/webrtc/rtcsessiondescription.h new file mode 100644 index 0000000000..080d21c7e2 --- /dev/null +++ b/gst-libs/gst/webrtc/rtcsessiondescription.h @@ -0,0 +1,58 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifndef __GST_WEBRTC_SESSION_DESCRIPTION_H__ +#define __GST_WEBRTC_SESSION_DESCRIPTION_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +GST_EXPORT +const gchar * gst_webrtc_sdp_type_to_string (GstWebRTCSDPType type); + +#define GST_TYPE_WEBRTC_SESSION_DESCRIPTION (gst_webrtc_session_description_get_type()) +GST_EXPORT +GType gst_webrtc_session_description_get_type (void); + +/** + * GstWebRTCSessionDescription: + * type: the #GstWebRTCSDPType of the description + * sdp: the #GstSDPMessage of the description + * + * See https://www.w3.org/TR/webrtc/#rtcsessiondescription-class + */ +struct _GstWebRTCSessionDescription +{ + GstWebRTCSDPType type; + GstSDPMessage *sdp; +}; + +GST_EXPORT +GstWebRTCSessionDescription * gst_webrtc_session_description_new (GstWebRTCSDPType type, GstSDPMessage *sdp); +GST_EXPORT +GstWebRTCSessionDescription * gst_webrtc_session_description_copy (const GstWebRTCSessionDescription * src); +GST_EXPORT +void gst_webrtc_session_description_free (GstWebRTCSessionDescription * desc); + +G_END_DECLS + +#endif /* __GST_WEBRTC_PEERCONNECTION_H__ */ diff --git a/gst-libs/gst/webrtc/rtpreceiver.c b/gst-libs/gst/webrtc/rtpreceiver.c new file mode 100644 index 0000000000..edf6e201bb --- /dev/null +++ b/gst-libs/gst/webrtc/rtpreceiver.c @@ -0,0 +1,135 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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:gstwebrtc-receiver + * @short_description: RTCRtpReceiver object + * @title: GstWebRTCRTPReceiver + * @see_also: #GstWebRTCRTPSender, #GstWebRTCRTPTransceiver + * + * https://www.w3.org/TR/webrtc/#rtcrtpreceiver-interface + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "rtpreceiver.h" + +#define GST_CAT_DEFAULT gst_webrtc_rtp_receiver_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +#define gst_webrtc_rtp_receiver_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstWebRTCRTPReceiver, gst_webrtc_rtp_receiver, + GST_TYPE_OBJECT, GST_DEBUG_CATEGORY_INIT (gst_webrtc_rtp_receiver_debug, + "webrtcreceiver", 0, "webrtcreceiver");); + +enum +{ + SIGNAL_0, + LAST_SIGNAL, +}; + +enum +{ + PROP_0, +}; + +//static guint gst_webrtc_rtp_receiver_signals[LAST_SIGNAL] = { 0 }; + +void +gst_webrtc_rtp_receiver_set_transport (GstWebRTCRTPReceiver * receiver, + GstWebRTCDTLSTransport * transport) +{ + g_return_if_fail (GST_IS_WEBRTC_RTP_RECEIVER (receiver)); + g_return_if_fail (GST_IS_WEBRTC_DTLS_TRANSPORT (transport)); + + gst_object_replace ((GstObject **) & receiver->transport, + GST_OBJECT (transport)); +} + +void +gst_webrtc_rtp_receiver_set_rtcp_transport (GstWebRTCRTPReceiver * receiver, + GstWebRTCDTLSTransport * transport) +{ + g_return_if_fail (GST_IS_WEBRTC_RTP_RECEIVER (receiver)); + g_return_if_fail (GST_IS_WEBRTC_DTLS_TRANSPORT (transport)); + + gst_object_replace ((GstObject **) & receiver->rtcp_transport, + GST_OBJECT (transport)); +} + +static void +gst_webrtc_rtp_receiver_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_webrtc_rtp_receiver_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_webrtc_rtp_receiver_finalize (GObject * object) +{ + GstWebRTCRTPReceiver *webrtc = GST_WEBRTC_RTP_RECEIVER (object); + + if (webrtc->transport) + gst_object_unref (webrtc->transport); + webrtc->transport = NULL; + + if (webrtc->rtcp_transport) + gst_object_unref (webrtc->rtcp_transport); + webrtc->rtcp_transport = NULL; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_webrtc_rtp_receiver_class_init (GstWebRTCRTPReceiverClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->get_property = gst_webrtc_rtp_receiver_get_property; + gobject_class->set_property = gst_webrtc_rtp_receiver_set_property; + gobject_class->finalize = gst_webrtc_rtp_receiver_finalize; +} + +static void +gst_webrtc_rtp_receiver_init (GstWebRTCRTPReceiver * webrtc) +{ +} + +GstWebRTCRTPReceiver * +gst_webrtc_rtp_receiver_new (void) +{ + return g_object_new (GST_TYPE_WEBRTC_RTP_RECEIVER, NULL); +} diff --git a/gst-libs/gst/webrtc/rtpreceiver.h b/gst-libs/gst/webrtc/rtpreceiver.h new file mode 100644 index 0000000000..969c4de65b --- /dev/null +++ b/gst-libs/gst/webrtc/rtpreceiver.h @@ -0,0 +1,76 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifndef __GST_WEBRTC_RTP_RECEIVER_H__ +#define __GST_WEBRTC_RTP_RECEIVER_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +GST_EXPORT +GType gst_webrtc_rtp_receiver_get_type(void); +#define GST_TYPE_WEBRTC_RTP_RECEIVER (gst_webrtc_rtp_receiver_get_type()) +#define GST_WEBRTC_RTP_RECEIVER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_WEBRTC_RTP_RECEIVER,GstWebRTCRTPReceiver)) +#define GST_IS_WEBRTC_RTP_RECEIVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_WEBRTC_RTP_RECEIVER)) +#define GST_WEBRTC_RTP_RECEIVER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_WEBRTC_RTP_RECEIVER,GstWebRTCRTPReceiverClass)) +#define GST_IS_WEBRTC_RTP_RECEIVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_WEBRTC_RTP_RECEIVER)) +#define GST_WEBRTC_RTP_RECEIVER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_WEBRTC_RTP_RECEIVER,GstWebRTCRTPReceiverClass)) + +typedef struct _GstWebRTCRTPReceiver GstWebRTCRTPReceiver; +typedef struct _GstWebRTCRTPReceiverClass GstWebRTCRTPReceiverClass; + +struct _GstWebRTCRTPReceiver +{ + GstObject parent; + + /* The MediStreamTrack is represented by the stream and is output into @transport/@rtcp_transport as necessary */ + GstWebRTCDTLSTransport *transport; + GstWebRTCDTLSTransport *rtcp_transport; + + gpointer _padding[GST_PADDING]; +}; + +struct _GstWebRTCRTPReceiverClass +{ + GstObjectClass parent_class; + + gpointer _padding[GST_PADDING]; +}; + +GST_EXPORT +GstWebRTCRTPReceiver * gst_webrtc_rtp_receiver_new (void); +GST_EXPORT +GstStructure * gst_webrtc_rtp_receiver_get_parameters (GstWebRTCRTPReceiver * receiver, gchar * kind); +/* FIXME: promise? */ +GST_EXPORT +gboolean gst_webrtc_rtp_receiver_set_parameters (GstWebRTCRTPReceiver * receiver, + GstStructure * parameters); +GST_EXPORT +void gst_webrtc_rtp_receiver_set_transport (GstWebRTCRTPReceiver * receiver, + GstWebRTCDTLSTransport * transport); +GST_EXPORT +void gst_webrtc_rtp_receiver_set_rtcp_transport (GstWebRTCRTPReceiver * receiver, + GstWebRTCDTLSTransport * transport); + +G_END_DECLS + +#endif /* __GST_WEBRTC_RTP_RECEIVER_H__ */ diff --git a/gst-libs/gst/webrtc/rtpsender.c b/gst-libs/gst/webrtc/rtpsender.c new file mode 100644 index 0000000000..b4dfe6ed88 --- /dev/null +++ b/gst-libs/gst/webrtc/rtpsender.c @@ -0,0 +1,141 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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:gstwebrtc-sender + * @short_description: RTCRtpSender object + * @title: GstWebRTCRTPSender + * @see_also: #GstWebRTCRTPReceiver, #GstWebRTCRTPTransceiver + * + * https://www.w3.org/TR/webrtc/#rtcrtpsender-interface + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "rtpsender.h" +#include "rtptransceiver.h" + +#define GST_CAT_DEFAULT gst_webrtc_rtp_sender_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +#define gst_webrtc_rtp_sender_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstWebRTCRTPSender, gst_webrtc_rtp_sender, + GST_TYPE_OBJECT, GST_DEBUG_CATEGORY_INIT (gst_webrtc_rtp_sender_debug, + "webrtcsender", 0, "webrtcsender"); + ); + +enum +{ + SIGNAL_0, + LAST_SIGNAL, +}; + +enum +{ + PROP_0, + PROP_MID, + PROP_SENDER, + PROP_STOPPED, + PROP_DIRECTION, +}; + +//static guint gst_webrtc_rtp_sender_signals[LAST_SIGNAL] = { 0 }; + +void +gst_webrtc_rtp_sender_set_transport (GstWebRTCRTPSender * sender, + GstWebRTCDTLSTransport * transport) +{ + g_return_if_fail (GST_IS_WEBRTC_RTP_SENDER (sender)); + g_return_if_fail (GST_IS_WEBRTC_DTLS_TRANSPORT (transport)); + + gst_object_replace ((GstObject **) & sender->transport, + GST_OBJECT (transport)); +} + +void +gst_webrtc_rtp_sender_set_rtcp_transport (GstWebRTCRTPSender * sender, + GstWebRTCDTLSTransport * transport) +{ + g_return_if_fail (GST_IS_WEBRTC_RTP_SENDER (sender)); + g_return_if_fail (GST_IS_WEBRTC_DTLS_TRANSPORT (transport)); + + gst_object_replace ((GstObject **) & sender->rtcp_transport, + GST_OBJECT (transport)); +} + +static void +gst_webrtc_rtp_sender_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_webrtc_rtp_sender_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_webrtc_rtp_sender_finalize (GObject * object) +{ + GstWebRTCRTPSender *webrtc = GST_WEBRTC_RTP_SENDER (object); + + if (webrtc->transport) + gst_object_unref (webrtc->transport); + webrtc->transport = NULL; + + if (webrtc->rtcp_transport) + gst_object_unref (webrtc->rtcp_transport); + webrtc->rtcp_transport = NULL; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_webrtc_rtp_sender_class_init (GstWebRTCRTPSenderClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->get_property = gst_webrtc_rtp_sender_get_property; + gobject_class->set_property = gst_webrtc_rtp_sender_set_property; + gobject_class->finalize = gst_webrtc_rtp_sender_finalize; +} + +static void +gst_webrtc_rtp_sender_init (GstWebRTCRTPSender * webrtc) +{ +} + +GstWebRTCRTPSender * +gst_webrtc_rtp_sender_new (GArray * send_encodings /* FIXME */ ) +{ + return g_object_new (GST_TYPE_WEBRTC_RTP_SENDER, NULL); +} diff --git a/gst-libs/gst/webrtc/rtpsender.h b/gst-libs/gst/webrtc/rtpsender.h new file mode 100644 index 0000000000..8308a0b44e --- /dev/null +++ b/gst-libs/gst/webrtc/rtpsender.h @@ -0,0 +1,77 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifndef __GST_WEBRTC_RTP_SENDER_H__ +#define __GST_WEBRTC_RTP_SENDER_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +GST_EXPORT +GType gst_webrtc_rtp_sender_get_type(void); +#define GST_TYPE_WEBRTC_RTP_SENDER (gst_webrtc_rtp_sender_get_type()) +#define GST_WEBRTC_RTP_SENDER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_WEBRTC_RTP_SENDER,GstWebRTCRTPSender)) +#define GST_IS_WEBRTC_RTP_SENDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_WEBRTC_RTP_SENDER)) +#define GST_WEBRTC_RTP_SENDER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_WEBRTC_RTP_SENDER,GstWebRTCRTPSenderClass)) +#define GST_IS_WEBRTC_RTP_SENDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_WEBRTC_RTP_SENDER)) +#define GST_WEBRTC_RTP_SENDER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_WEBRTC_RTP_SENDER,GstWebRTCRTPSenderClass)) + +struct _GstWebRTCRTPSender +{ + GstObject parent; + + /* The MediStreamTrack is represented by the stream and is output into @transport/@rtcp_transport as necessary */ + GstWebRTCDTLSTransport *transport; + GstWebRTCDTLSTransport *rtcp_transport; + + GArray *send_encodings; + + gpointer _padding[GST_PADDING]; +}; + +struct _GstWebRTCRTPSenderClass +{ + GstObjectClass parent_class; + + gpointer _padding[GST_PADDING]; +}; + +GST_EXPORT +GstWebRTCRTPSender * gst_webrtc_rtp_sender_new (GArray * send_encodings); +GST_EXPORT +GstStructure * gst_webrtc_rtp_sender_get_parameters (GstWebRTCRTPSender * sender, gchar * kind); +/* FIXME: promise? */ +GST_EXPORT +gboolean gst_webrtc_rtp_sender_set_parameters (GstWebRTCRTPSender * sender, + GstStructure * parameters); + +GST_EXPORT +void gst_webrtc_rtp_sender_set_transport (GstWebRTCRTPSender * sender, + GstWebRTCDTLSTransport * transport); +GST_EXPORT +void gst_webrtc_rtp_sender_set_rtcp_transport (GstWebRTCRTPSender * sender, + GstWebRTCDTLSTransport * transport); + + +G_END_DECLS + +#endif /* __GST_WEBRTC_RTP_SENDER_H__ */ diff --git a/gst-libs/gst/webrtc/rtptransceiver.c b/gst-libs/gst/webrtc/rtptransceiver.c new file mode 100644 index 0000000000..d0d9628d0f --- /dev/null +++ b/gst-libs/gst/webrtc/rtptransceiver.c @@ -0,0 +1,186 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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:gstwebrtc-transceiver + * @short_description: RTCRtpTransceiver object + * @title: GstWebRTCRTPTransceiver + * @see_also: #GstWebRTCRTPSender, #GstWebRTCRTPReceiver + * + * https://www.w3.org/TR/webrtc/#rtcrtptransceiver-interface + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "rtptransceiver.h" + +#define GST_CAT_DEFAULT gst_webrtc_rtp_transceiver_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +#define gst_webrtc_rtp_transceiver_parent_class parent_class +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstWebRTCRTPTransceiver, + gst_webrtc_rtp_transceiver, GST_TYPE_OBJECT, + GST_DEBUG_CATEGORY_INIT (gst_webrtc_rtp_transceiver_debug, + "webrtctransceiver", 0, "webrtctransceiver"); + ); + +enum +{ + SIGNAL_0, + LAST_SIGNAL, +}; + +enum +{ + PROP_0, + PROP_MID, + PROP_SENDER, + PROP_RECEIVER, + PROP_STOPPED, // FIXME + PROP_DIRECTION, // FIXME + PROP_MLINE, +}; + +//static guint gst_webrtc_rtp_transceiver_signals[LAST_SIGNAL] = { 0 }; + +static void +gst_webrtc_rtp_transceiver_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstWebRTCRTPTransceiver *webrtc = GST_WEBRTC_RTP_TRANSCEIVER (object); + + switch (prop_id) { + case PROP_SENDER: + webrtc->sender = g_value_dup_object (value); + break; + case PROP_RECEIVER: + webrtc->receiver = g_value_dup_object (value); + break; + case PROP_MLINE: + webrtc->mline = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_webrtc_rtp_transceiver_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstWebRTCRTPTransceiver *webrtc = GST_WEBRTC_RTP_TRANSCEIVER (object); + + switch (prop_id) { + case PROP_SENDER: + g_value_set_object (value, webrtc->sender); + break; + case PROP_RECEIVER: + g_value_set_object (value, webrtc->receiver); + break; + case PROP_MLINE: + g_value_set_uint (value, webrtc->mline); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_webrtc_rtp_transceiver_constructed (GObject * object) +{ + GstWebRTCRTPTransceiver *webrtc = GST_WEBRTC_RTP_TRANSCEIVER (object); + + gst_object_set_parent (GST_OBJECT (webrtc->sender), GST_OBJECT (webrtc)); + gst_object_set_parent (GST_OBJECT (webrtc->receiver), GST_OBJECT (webrtc)); + + G_OBJECT_CLASS (parent_class)->constructed (object); +} + +static void +gst_webrtc_rtp_transceiver_dispose (GObject * object) +{ + GstWebRTCRTPTransceiver *webrtc = GST_WEBRTC_RTP_TRANSCEIVER (object); + + if (webrtc->sender) { + GST_OBJECT_PARENT (webrtc->sender) = NULL; + gst_object_unref (webrtc->sender); + } + webrtc->sender = NULL; + if (webrtc->receiver) { + GST_OBJECT_PARENT (webrtc->receiver) = NULL; + gst_object_unref (webrtc->receiver); + } + webrtc->receiver = NULL; + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_webrtc_rtp_transceiver_finalize (GObject * object) +{ + GstWebRTCRTPTransceiver *webrtc = GST_WEBRTC_RTP_TRANSCEIVER (object); + + g_free (webrtc->mid); + if (webrtc->codec_preferences) + gst_caps_unref (webrtc->codec_preferences); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_webrtc_rtp_transceiver_class_init (GstWebRTCRTPTransceiverClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->get_property = gst_webrtc_rtp_transceiver_get_property; + gobject_class->set_property = gst_webrtc_rtp_transceiver_set_property; + gobject_class->constructed = gst_webrtc_rtp_transceiver_constructed; + gobject_class->dispose = gst_webrtc_rtp_transceiver_dispose; + gobject_class->finalize = gst_webrtc_rtp_transceiver_finalize; + + g_object_class_install_property (gobject_class, + PROP_SENDER, + g_param_spec_object ("sender", "Sender", + "The RTP sender for this transceiver", + GST_TYPE_WEBRTC_RTP_SENDER, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_RECEIVER, + g_param_spec_object ("receiver", "Receiver", + "The RTP receiver for this transceiver", + GST_TYPE_WEBRTC_RTP_RECEIVER, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_MLINE, + g_param_spec_uint ("mlineindex", "Media Line Index", + "Index in the SDP of the Media", + 0, G_MAXUINT, 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); +} + +static void +gst_webrtc_rtp_transceiver_init (GstWebRTCRTPTransceiver * webrtc) +{ +} diff --git a/gst-libs/gst/webrtc/rtptransceiver.h b/gst-libs/gst/webrtc/rtptransceiver.h new file mode 100644 index 0000000000..1bb819752a --- /dev/null +++ b/gst-libs/gst/webrtc/rtptransceiver.h @@ -0,0 +1,69 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifndef __GST_WEBRTC_RTP_TRANSCEIVER_H__ +#define __GST_WEBRTC_RTP_TRANSCEIVER_H__ + +#include +#include +#include +#include + +G_BEGIN_DECLS + +GST_EXPORT +GType gst_webrtc_rtp_transceiver_get_type(void); +#define GST_TYPE_WEBRTC_RTP_TRANSCEIVER (gst_webrtc_rtp_transceiver_get_type()) +#define GST_WEBRTC_RTP_TRANSCEIVER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_WEBRTC_RTP_TRANSCEIVER,GstWebRTCRTPTransceiver)) +#define GST_IS_WEBRTC_RTP_TRANSCEIVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_WEBRTC_RTP_TRANSCEIVER)) +#define GST_WEBRTC_RTP_TRANSCEIVER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_WEBRTC_RTP_TRANSCEIVER,GstWebRTCRTPTransceiverClass)) +#define GST_IS_WEBRTC_RTP_TRANSCEIVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_WEBRTC_RTP_TRANSCEIVER)) +#define GST_WEBRTC_RTP_TRANSCEIVER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_WEBRTC_RTP_TRANSCEIVER,GstWebRTCRTPTransceiverClass)) + +struct _GstWebRTCRTPTransceiver +{ + GstObject parent; + guint mline; + gchar *mid; + gboolean stopped; + + GstWebRTCRTPSender *sender; + GstWebRTCRTPReceiver *receiver; + + GstWebRTCRTPTransceiverDirection direction; + GstWebRTCRTPTransceiverDirection current_direction; + + GstCaps *codec_preferences; + + gpointer _padding[GST_PADDING]; +}; + +struct _GstWebRTCRTPTransceiverClass +{ + GstObjectClass parent_class; + + gpointer _padding[GST_PADDING]; +}; + +GST_EXPORT +void gst_webrtc_rtp_transceiver_stop (GstWebRTCRTPTransceiver * transceiver); + +G_END_DECLS + +#endif /* __GST_WEBRTC_RTP_TRANSCEIVER_H__ */ diff --git a/gst-libs/gst/webrtc/webrtc.h b/gst-libs/gst/webrtc/webrtc.h new file mode 100644 index 0000000000..354c15c196 --- /dev/null +++ b/gst-libs/gst/webrtc/webrtc.h @@ -0,0 +1,33 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifndef __GST_WEBRTC_WEBRTC_H__ +#define __GST_WEBRTC_WEBRTC_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif /* __GST_WEBRTC_WEBRTC_H__ */ diff --git a/gst-libs/gst/webrtc/webrtc_fwd.h b/gst-libs/gst/webrtc/webrtc_fwd.h new file mode 100644 index 0000000000..48c9bdab1a --- /dev/null +++ b/gst-libs/gst/webrtc/webrtc_fwd.h @@ -0,0 +1,251 @@ +/* GStreamer + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + +#ifndef __GST_WEBRTC_FWD_H__ +#define __GST_WEBRTC_FWD_H__ + +#ifndef GST_USE_UNSTABLE_API +#warning "The WebRTC library from gst-plugins-bad is unstable API and may change in future." +#warning "You can define GST_USE_UNSTABLE_API to avoid this warning." +#endif + +#include +#include + +typedef struct _GstWebRTCDTLSTransport GstWebRTCDTLSTransport; +typedef struct _GstWebRTCDTLSTransportClass GstWebRTCDTLSTransportClass; + +typedef struct _GstWebRTCICETransport GstWebRTCICETransport; +typedef struct _GstWebRTCICETransportClass GstWebRTCICETransportClass; + +typedef struct _GstWebRTCRTPReceiver GstWebRTCRTPReceiver; +typedef struct _GstWebRTCRTPReceiverClass GstWebRTCRTPReceiverClass; + +typedef struct _GstWebRTCRTPSender GstWebRTCRTPSender; +typedef struct _GstWebRTCRTPSenderClass GstWebRTCRTPSenderClass; + +typedef struct _GstWebRTCSessionDescription GstWebRTCSessionDescription; + +typedef struct _GstWebRTCRTPTransceiver GstWebRTCRTPTransceiver; +typedef struct _GstWebRTCRTPTransceiverClass GstWebRTCRTPTransceiverClass; + +/** + * GstWebRTCDTLSTransportState: + * GST_WEBRTC_DTLS_TRANSPORT_STATE_NEW: new + * GST_WEBRTC_DTLS_TRANSPORT_STATE_CLOSED: closed + * GST_WEBRTC_DTLS_TRANSPORT_STATE_FAILED: failed + * GST_WEBRTC_DTLS_TRANSPORT_STATE_CONNECTING: connecting + * GST_WEBRTC_DTLS_TRANSPORT_STATE_CONNECTED: connected + */ +typedef enum /*< underscore_name=gst_webrtc_dtls_transport_state >*/ +{ + GST_WEBRTC_DTLS_TRANSPORT_STATE_NEW, + GST_WEBRTC_DTLS_TRANSPORT_STATE_CLOSED, + GST_WEBRTC_DTLS_TRANSPORT_STATE_FAILED, + GST_WEBRTC_DTLS_TRANSPORT_STATE_CONNECTING, + GST_WEBRTC_DTLS_TRANSPORT_STATE_CONNECTED, +} GstWebRTCDTLSTransportState; + +/** + * GstWebRTCICEGatheringState: + * GST_WEBRTC_ICE_GATHERING_STATE_NEW: new + * GST_WEBRTC_ICE_GATHERING_STATE_GATHERING: gathering + * GST_WEBRTC_ICE_GATHERING_STATE_COMPLETE: complete + * + * See http://w3c.github.io/webrtc-pc/#dom-rtcicegatheringstate + */ +typedef enum /*< underscore_name=gst_webrtc_ice_gathering_state >*/ +{ + GST_WEBRTC_ICE_GATHERING_STATE_NEW, + GST_WEBRTC_ICE_GATHERING_STATE_GATHERING, + GST_WEBRTC_ICE_GATHERING_STATE_COMPLETE, +} GstWebRTCICEGatheringState; /*< underscore_name=gst_webrtc_ice_gathering_state >*/ + +/** + * GstWebRTCICEConnectionState: + * GST_WEBRTC_ICE_CONNECTION_STATE_NEW: new + * GST_WEBRTC_ICE_CONNECTION_STATE_CHECKING: checking + * GST_WEBRTC_ICE_CONNECTION_STATE_CONNECTED: connected + * GST_WEBRTC_ICE_CONNECTION_STATE_COMPLETED: completed + * GST_WEBRTC_ICE_CONNECTION_STATE_FAILED: failed + * GST_WEBRTC_ICE_CONNECTION_STATE_DISCONNECTED: disconnected + * GST_WEBRTC_ICE_CONNECTION_STATE_CLOSED: closed + * + * See http://w3c.github.io/webrtc-pc/#dom-rtciceconnectionstate + */ +typedef enum /*< underscore_name=gst_webrtc_ice_connection_state >*/ +{ + GST_WEBRTC_ICE_CONNECTION_STATE_NEW, + GST_WEBRTC_ICE_CONNECTION_STATE_CHECKING, + GST_WEBRTC_ICE_CONNECTION_STATE_CONNECTED, + GST_WEBRTC_ICE_CONNECTION_STATE_COMPLETED, + GST_WEBRTC_ICE_CONNECTION_STATE_FAILED, + GST_WEBRTC_ICE_CONNECTION_STATE_DISCONNECTED, + GST_WEBRTC_ICE_CONNECTION_STATE_CLOSED, +} GstWebRTCICEConnectionState; + +/** + * GstWebRTCSignalingState: + * GST_WEBRTC_SIGNALING_STATE_STABLE: stable + * GST_WEBRTC_SIGNALING_STATE_CLOSED: closed + * GST_WEBRTC_SIGNALING_STATE_HAVE_LOCAL_OFFER: have-local-offer + * GST_WEBRTC_SIGNALING_STATE_HAVE_REMOTE_OFFER: have-remote-offer + * GST_WEBRTC_SIGNALING_STATE_HAVE_LOCAL_PRANSWER: have-local-pranswer + * GST_WEBRTC_SIGNALING_STATE_HAVE_REMOTE_PRANSWER: have-remote-pranswer + * + * See http://w3c.github.io/webrtc-pc/#dom-rtcsignalingstate + */ +typedef enum /*< underscore_name=gst_webrtc_signaling_state >*/ +{ + GST_WEBRTC_SIGNALING_STATE_STABLE, + GST_WEBRTC_SIGNALING_STATE_CLOSED, + GST_WEBRTC_SIGNALING_STATE_HAVE_LOCAL_OFFER, + GST_WEBRTC_SIGNALING_STATE_HAVE_REMOTE_OFFER, + GST_WEBRTC_SIGNALING_STATE_HAVE_LOCAL_PRANSWER, + GST_WEBRTC_SIGNALING_STATE_HAVE_REMOTE_PRANSWER, +} GstWebRTCSignalingState; + +/** + * GstWebRTCPeerConnectionState: + * GST_WEBRTC_PEER_CONNECTION_STATE_NEW: new + * GST_WEBRTC_PEER_CONNECTION_STATE_CONNECTING: connecting + * GST_WEBRTC_PEER_CONNECTION_STATE_CONNECTED: connected + * GST_WEBRTC_PEER_CONNECTION_STATE_DISCONNECTED: disconnected + * GST_WEBRTC_PEER_CONNECTION_STATE_FAILED: failed + * GST_WEBRTC_PEER_CONNECTION_STATE_CLOSED: closed + * + * See http://w3c.github.io/webrtc-pc/#dom-rtcpeerconnectionstate + */ +typedef enum /*< underscore_name=gst_webrtc_peer_connection_state >*/ +{ + GST_WEBRTC_PEER_CONNECTION_STATE_NEW, + GST_WEBRTC_PEER_CONNECTION_STATE_CONNECTING, + GST_WEBRTC_PEER_CONNECTION_STATE_CONNECTED, + GST_WEBRTC_PEER_CONNECTION_STATE_DISCONNECTED, + GST_WEBRTC_PEER_CONNECTION_STATE_FAILED, + GST_WEBRTC_PEER_CONNECTION_STATE_CLOSED, +} GstWebRTCPeerConnectionState; + +/** + * GstWebRTCIceRole: + * GST_WEBRTC_ICE_ROLE_CONTROLLED: controlled + * GST_WEBRTC_ICE_ROLE_CONTROLLING: controlling + */ +typedef enum /*< underscore_name=gst_webrtc_ice_role >*/ +{ + GST_WEBRTC_ICE_ROLE_CONTROLLED, + GST_WEBRTC_ICE_ROLE_CONTROLLING, +} GstWebRTCIceRole; + +/** + * GstWebRTCIceComponent: + * GST_WEBRTC_ICE_COMPONENT_RTP, + * GST_WEBRTC_ICE_COMPONENT_RTCP, + */ +typedef enum /*< underscore_name=gst_webrtc_ice_component >*/ +{ + GST_WEBRTC_ICE_COMPONENT_RTP, + GST_WEBRTC_ICE_COMPONENT_RTCP, +} GstWebRTCICEComponent; + +/** + * GstWebRTCSDPType: + * GST_WEBRTC_SDP_TYPE_OFFER: offer + * GST_WEBRTC_SDP_TYPE_PRANSWER: pranswer + * GST_WEBRTC_SDP_TYPE_ANSWER: answer + * GST_WEBRTC_SDP_TYPE_ROLLBACK: rollback + * + * See http://w3c.github.io/webrtc-pc/#rtcsdptype + */ +typedef enum /*< underscore_name=gst_webrtc_sdp_type >*/ +{ + GST_WEBRTC_SDP_TYPE_OFFER = 1, + GST_WEBRTC_SDP_TYPE_PRANSWER, + GST_WEBRTC_SDP_TYPE_ANSWER, + GST_WEBRTC_SDP_TYPE_ROLLBACK, +} GstWebRTCSDPType; + +/** + * GstWebRTCRtpTransceiverDirection: + * GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE: none + * GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE: inactive + * GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY: sendonly + * GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY: recvonly + * GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV: sendrecv + */ +typedef enum /*< underscore_name=gst_webrtc_rtp_transceiver_direction >*/ +{ + GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE, + GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE, + GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY, + GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY, + GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV, +} GstWebRTCRTPTransceiverDirection; + +/** + * GstWebRTCDTLSSetup: + * GST_WEBRTC_DTLS_SETUP_NONE: none + * GST_WEBRTC_DTLS_SETUP_ACTPASS: actpass + * GST_WEBRTC_DTLS_SETUP_ACTIVE: sendonly + * GST_WEBRTC_DTLS_SETUP_PASSIVE: recvonly + */ +typedef enum /*< underscore_name=gst_webrtc_dtls_setup >*/ +{ + GST_WEBRTC_DTLS_SETUP_NONE, + GST_WEBRTC_DTLS_SETUP_ACTPASS, + GST_WEBRTC_DTLS_SETUP_ACTIVE, + GST_WEBRTC_DTLS_SETUP_PASSIVE, +} GstWebRTCDTLSSetup; + +/** + * GstWebRTCStatsType: + * GST_WEBRTC_STATS_CODEC: codec + * GST_WEBRTC_STATS_INBOUND_RTP: inbound-rtp + * GST_WEBRTC_STATS_OUTBOUND_RTP: outbound-rtp + * GST_WEBRTC_STATS_REMOTE_INBOUND_RTP: remote-inbound-rtp + * GST_WEBRTC_STATS_REMOTE_OUTBOUND_RTP: remote-outbound-rtp + * GST_WEBRTC_STATS_CSRC: csrc + * GST_WEBRTC_STATS_PEER_CONNECTION: peer-connectiion + * GST_WEBRTC_STATS_DATA_CHANNEL: data-channel + * GST_WEBRTC_STATS_STREAM: stream + * GST_WEBRTC_STATS_TRANSPORT: transport + * GST_WEBRTC_STATS_CANDIDATE_PAIR: candidate-pair + * GST_WEBRTC_STATS_LOCAL_CANDIDATE: local-candidate + * GST_WEBRTC_STATS_REMOTE_CANDIDATE: remote-candidate + * GST_WEBRTC_STATS_CERTIFICATE: certificate + */ +typedef enum /*< underscore_name=gst_webrtc_stats_type >*/ +{ + GST_WEBRTC_STATS_CODEC = 1, + GST_WEBRTC_STATS_INBOUND_RTP, + GST_WEBRTC_STATS_OUTBOUND_RTP, + GST_WEBRTC_STATS_REMOTE_INBOUND_RTP, + GST_WEBRTC_STATS_REMOTE_OUTBOUND_RTP, + GST_WEBRTC_STATS_CSRC, + GST_WEBRTC_STATS_PEER_CONNECTION, + GST_WEBRTC_STATS_DATA_CHANNEL, + GST_WEBRTC_STATS_STREAM, + GST_WEBRTC_STATS_TRANSPORT, + GST_WEBRTC_STATS_CANDIDATE_PAIR, + GST_WEBRTC_STATS_LOCAL_CANDIDATE, + GST_WEBRTC_STATS_REMOTE_CANDIDATE, + GST_WEBRTC_STATS_CERTIFICATE, +} GstWebRTCStatsType; + +#endif /* __GST_WEBRTC_FWD_H__ */ diff --git a/gst-libs/gst/webrtc/webrtc_mkenum.py b/gst-libs/gst/webrtc/webrtc_mkenum.py new file mode 100755 index 0000000000..fb6c2cba60 --- /dev/null +++ b/gst-libs/gst/webrtc/webrtc_mkenum.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 + +# This is in its own file rather than inside meson.build +# because a) mixing the two is ugly and b) trying to +# make special characters such as \n go through all +# backends is a fool's errand. + +import sys, os, shutil, subprocess + +h_array = ['--fhead', + "#ifndef __GST_WEBRTC_ENUM_TYPES_H__\n#define __GST_WEBRTC_ENUM_TYPES_H__\n\n#include \n\nG_BEGIN_DECLS\n", + '--fprod', + "\n/* enumerations from \"@filename@\" */\n", + '--vhead', + "GST_EXPORT GType @enum_name@_get_type (void);\n#define GST_TYPE_@ENUMSHORT@ (@enum_name@_get_type())\n", + '--ftail', + "G_END_DECLS\n\n#endif /* __GST_WEBRTC_ENUM_TYPES_H__ */" +] + +c_array = ['--fhead', + "#include \"webrtc-enumtypes.h\"\n\n#include \"webrtc.h\"", + '--fprod', + "\n/* enumerations from \"@filename@\" */", + '--vhead', + "GType\n@enum_name@_get_type (void)\n{\n static volatile gsize g_define_type_id__volatile = 0;\n if (g_once_init_enter (&g_define_type_id__volatile)) {\n static const G@Type@Value values[] = {", + '--vprod', + " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" },", + '--vtail', + " { 0, NULL, NULL }\n };\n GType g_define_type_id = g_@type@_register_static (\"@EnumName@\", values);\n g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);\n }\n return g_define_type_id__volatile;\n}\n" +] + +cmd = [] +argn = 1 +# Find the full command needed to run glib-mkenums +# On UNIX-like, this is just the full path to glib-mkenums +# On Windows, this is the full path to interpreter + full path to glib-mkenums +for arg in sys.argv[1:]: + cmd.append(arg) + argn += 1 + if arg.endswith('glib-mkenums'): + break +ofilename = sys.argv[argn] +headers = sys.argv[argn + 1:] + +if ofilename.endswith('.h'): + arg_array = h_array +else: + arg_array = c_array + +cmd_array = cmd + arg_array + headers +pc = subprocess.Popen(cmd_array, stdout=subprocess.PIPE) +(stdo, _) = pc.communicate() +if pc.returncode != 0: + sys.exit(pc.returncode) +open(ofilename, 'wb').write(stdo) diff --git a/pkgconfig/Makefile.am b/pkgconfig/Makefile.am index 6598baf148..0e69cd1f86 100644 --- a/pkgconfig/Makefile.am +++ b/pkgconfig/Makefile.am @@ -6,6 +6,7 @@ pcverfiles = \ gstreamer-insertbin-@GST_API_VERSION@.pc \ gstreamer-mpegts-@GST_API_VERSION@.pc \ gstreamer-player-@GST_API_VERSION@.pc \ + gstreamer-webrtc-@GST_API_VERSION@.pc \ gstreamer-bad-audio-@GST_API_VERSION@.pc \ gstreamer-bad-video-@GST_API_VERSION@.pc @@ -15,6 +16,7 @@ pcverfiles_uninstalled = \ gstreamer-insertbin-@GST_API_VERSION@-uninstalled.pc \ gstreamer-mpegts-@GST_API_VERSION@-uninstalled.pc \ gstreamer-player-@GST_API_VERSION@-uninstalled.pc \ + gstreamer-webrtc-@GST_API_VERSION@-uninstalled.pc \ gstreamer-bad-audio-@GST_API_VERSION@-uninstalled.pc \ gstreamer-bad-video-@GST_API_VERSION@-uninstalled.pc @@ -39,6 +41,7 @@ cp_verbose_0 = @echo " CP $@"; -e "s|[@]mpegtslibdir[@]|$(abs_top_builddir)/gst-libs/gst/mpegts/.libs|" \ -e "s|[@]playerlibdir[@]|$(abs_top_builddir)/gst-libs/gst/player/.libs|" \ -e "s|[@]waylandlibdir[@]|$(abs_top_builddir)/gst-libs/gst/wayland/.libs|" \ + -e "s|[@]webrtclibdir[@]|$(abs_top_builddir)/gst-libs/gst/webrtc/.libs|" \ -e "s|[@]basecamerabinsrclibdir[@]|$(abs_top_builddir)/gst-libs/gst/basecamerabinsrc/.libs|" \ -e "s|[@]photographylibdir[@]|$(abs_top_builddir)/gst-libs/gst/interfaces/.libs|" \ $< > $@.tmp && mv $@.tmp $@ @@ -53,6 +56,7 @@ pcinfiles = \ gstreamer-insertbin.pc.in gstreamer-insertbin-uninstalled.pc.in \ gstreamer-mpegts.pc.in gstreamer-mpegts-uninstalled.pc.in \ gstreamer-player.pc.in gstreamer-player-uninstalled.pc.in \ + gstreamer-webrtc.pc.in gstreamer-webrtc-uninstalled.pc.in \ gstreamer-bad-audio.pc.in gstreamer-bad-audio-uninstalled.pc.in \ gstreamer-bad-video.pc.in gstreamer-bad-video-uninstalled.pc.in diff --git a/pkgconfig/gstreamer-plugins-bad-uninstalled.pc.in b/pkgconfig/gstreamer-plugins-bad-uninstalled.pc.in index efd014e97f..a639d10661 100644 --- a/pkgconfig/gstreamer-plugins-bad-uninstalled.pc.in +++ b/pkgconfig/gstreamer-plugins-bad-uninstalled.pc.in @@ -10,5 +10,5 @@ Name: GStreamer Bad Plugin libraries, Uninstalled Description: Streaming media framework, bad plugins libraries, uninstalled Version: @VERSION@ Requires: gstreamer-@GST_API_VERSION@ -Libs: -L@audiolibdir@ -L@basecamerabinsrclibdir@ -L@codecparserslibdir@ -L@insertbinlibdir@ -L@photographylibdir@ -L@mpegtslibdir@ -L@playerlibdir@ -L@videolibdir@ -L@waylandlibdir@ +Libs: -L@audiolibdir@ -L@basecamerabinsrclibdir@ -L@codecparserslibdir@ -L@insertbinlibdir@ -L@photographylibdir@ -L@mpegtslibdir@ -L@playerlibdir@ -L@videolibdir@ -L@waylandlibdir@ -L@webrtclibdir@ Cflags: -I@abs_top_srcdir@/gst-libs -I@abs_top_builddir@/gst-libs diff --git a/pkgconfig/gstreamer-webrtc-uninstalled.pc.in b/pkgconfig/gstreamer-webrtc-uninstalled.pc.in new file mode 100644 index 0000000000..3eec1e15f0 --- /dev/null +++ b/pkgconfig/gstreamer-webrtc-uninstalled.pc.in @@ -0,0 +1,12 @@ +prefix= +exec_prefix= +libdir=@webrtclibdir@ +includedir=@abs_top_srcdir@/gst-libs + +Name: GStreamer WebRTC, Uninstalled +Description: GStreamer WebRTC support, uninstalled +Requires: gstreamer-@GST_API_VERSION@ gstreamer-base-@GST_API_VERSION@ +Version: @VERSION@ +Libs: -L${libdir} -lgstwebrtc-@GST_API_VERSION@ +Cflags: -I@abs_top_srcdir@/gst-libs -I@abs_top_builddir@/gst-libs + diff --git a/pkgconfig/gstreamer-webrtc.pc.in b/pkgconfig/gstreamer-webrtc.pc.in new file mode 100644 index 0000000000..7371ad4b4b --- /dev/null +++ b/pkgconfig/gstreamer-webrtc.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@/gstreamer-@GST_API_VERSION@ + +Name: GStreamer WebRTC +Description: GStreamer WebRTC support +Requires: gstreamer-@GST_API_VERSION@ gstreamer-base-@GST_API_VERSION@ +Version: @VERSION@ +Libs: -L${libdir} -lgstwebrtc-@GST_API_VERSION@ +Cflags: -I${includedir} + diff --git a/pkgconfig/meson.build b/pkgconfig/meson.build index 01317cb2e2..271f327f3d 100644 --- a/pkgconfig/meson.build +++ b/pkgconfig/meson.build @@ -18,6 +18,7 @@ pkgconf.set('mpegtslibdir', join_paths(meson.build_root(), gstmpegts.outdir())) pkgconf.set('playerlibdir', join_paths(meson.build_root(), gstplayer.outdir())) pkgconf.set('basecamerabinsrclibdir', join_paths(meson.build_root(), gstbasecamerabin.outdir())) pkgconf.set('photographylibdir', join_paths(meson.build_root(), gstphotography.outdir())) +pkgconf.set('webrtclibdir', join_paths(meson.build_root(), gstwebrtc.outdir())) pkg_install_dir = '@0@/pkgconfig'.format(get_option('libdir')) @@ -29,6 +30,7 @@ pkg_libs = [ 'mpegts', 'player', 'plugins-bad', + 'webrtc', ] #if use_wayland diff --git a/tests/check/Makefile.am b/tests/check/Makefile.am index 4969c4ad0f..290ce08e5a 100644 --- a/tests/check/Makefile.am +++ b/tests/check/Makefile.am @@ -204,6 +204,12 @@ else check_ipcpipeline= endif +if USE_WEBRTC +check_webrtc = elements/webrtcbin +else +check_webrtc= +endif + VALGRIND_TO_FIX = \ elements/mpeg2enc \ elements/mplex \ @@ -284,6 +290,7 @@ check_PROGRAMS = \ $(check_hlsdemux) \ $(check_srtp) \ $(check_player) \ + $(check_webrtc) \ $(EXPERIMENTAL_CHECKS) noinst_HEADERS = elements/mxfdemux.h libs/isoff.h @@ -562,6 +569,13 @@ orc/compositor.c: $(top_srcdir)/gst/compositor/compositororc.orc $(MKDIR_P) orc/ $(ORCC) --test -o $@ $< +elements_webrtcbin_LDADD = \ + $(top_builddir)/gst-libs/gst/webrtc/libgstwebrtc-@GST_API_VERSION@.la \ + $(GST_PLUGINS_BASE_LIBS) $(GST_BASE_LIBS) $(GST_SDP_LIBS) $(LDADD) +elements_webrtcbin_CFLAGS = \ + $(GST_PLUGINS_BASE_CLAGS) $(GST_PLUGINS_BAD_CFLAGS) $(GST_SDP_CFLAGS) \ + $(GST_BASE_CFLAGS) $(CFLAGS) $(AM_CFLAGS) + distclean-local-orc: rm -rf orc diff --git a/tests/check/elements/.gitignore b/tests/check/elements/.gitignore index 1b1d44cc8d..ef89c1455a 100644 --- a/tests/check/elements/.gitignore +++ b/tests/check/elements/.gitignore @@ -65,5 +65,6 @@ videorecordingbin viewfinderbin voaacenc voamrwbenc +webrtcbin x265enc zbar diff --git a/tests/check/elements/webrtcbin.c b/tests/check/elements/webrtcbin.c new file mode 100644 index 0000000000..b7f42e0f36 --- /dev/null +++ b/tests/check/elements/webrtcbin.c @@ -0,0 +1,1382 @@ +/* GStreamer + * + * Unit tests for webrtcbin + * + * Copyright (C) 2017 Matthew Waters + * + * 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. + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#define OPUS_RTP_CAPS(pt) "application/x-rtp,payload=" G_STRINGIFY(pt) ",encoding-name=OPUS,media=audio,clock-rate=48000" +#define VP8_RTP_CAPS(pt) "application/x-rtp,payload=" G_STRINGIFY(pt) ",encoding-name=VP8,media=video,clock-rate=90000" + +typedef enum +{ + STATE_NEW, + STATE_NEGOTATION_NEEDED, + STATE_OFFER_CREATED, + STATE_ANSWER_CREATED, + STATE_EOS, + STATE_ERROR, + STATE_CUSTOM, +} TestState; + +/* basic premise of this is that webrtc1 and webrtc2 are attempting to connect + * to each other in various configurations */ +struct test_webrtc; +struct test_webrtc +{ + GList *harnesses; + GThread *thread; + GMainLoop *loop; + GstBus *bus1; + GstBus *bus2; + GstElement *webrtc1; + GstElement *webrtc2; + GMutex lock; + GCond cond; + TestState state; + guint offerror; + gpointer user_data; + GDestroyNotify data_notify; +/* *INDENT-OFF* */ + void (*on_negotiation_needed) (struct test_webrtc * t, + GstElement * element, + gpointer user_data); + gpointer negotiation_data; + GDestroyNotify negotiation_notify; + void (*on_ice_candidate) (struct test_webrtc * t, + GstElement * element, + guint mlineindex, + gchar * candidate, + GstElement * other, + gpointer user_data); + gpointer ice_candidate_data; + GDestroyNotify ice_candidate_notify; + GstWebRTCSessionDescription * (*on_offer_created) (struct test_webrtc * t, + GstElement * element, + GstPromise * promise, + gpointer user_data); + gpointer offer_data; + GDestroyNotify offer_notify; + GstWebRTCSessionDescription * (*on_answer_created) (struct test_webrtc * t, + GstElement * element, + GstPromise * promise, + gpointer user_data); + gpointer answer_data; + GDestroyNotify answer_notify; + void (*on_pad_added) (struct test_webrtc * t, + GstElement * element, + GstPad * pad, + gpointer user_data); + gpointer pad_added_data; + GDestroyNotify pad_added_notify; + void (*bus_message) (struct test_webrtc * t, + GstBus * bus, + GstMessage * msg, + gpointer user_data); + gpointer bus_data; + GDestroyNotify bus_notify; +/* *INDENT-ON* */ +}; + +static void +_on_answer_received (GstPromise * promise, gpointer user_data) +{ + struct test_webrtc *t = user_data; + GstElement *offeror = t->offerror == 1 ? t->webrtc1 : t->webrtc2; + GstElement *answerer = t->offerror == 2 ? t->webrtc1 : t->webrtc2; + const GstStructure *reply; + GstWebRTCSessionDescription *answer = NULL; + gchar *desc; + + reply = gst_promise_get_reply (promise); + gst_structure_get (reply, "answer", + GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &answer, NULL); + desc = gst_sdp_message_as_text (answer->sdp); + GST_INFO ("Created Answer: %s", desc); + g_free (desc); + + g_mutex_lock (&t->lock); + if (t->on_answer_created) { + gst_webrtc_session_description_free (answer); + answer = t->on_answer_created (t, answerer, promise, t->answer_data); + } + gst_promise_unref (promise); + + g_signal_emit_by_name (answerer, "set-local-description", answer, NULL); + g_signal_emit_by_name (offeror, "set-remote-description", answer, NULL); + + t->state = STATE_ANSWER_CREATED; + g_cond_broadcast (&t->cond); + g_mutex_unlock (&t->lock); + + gst_webrtc_session_description_free (answer); +} + +static void +_on_offer_received (GstPromise * promise, gpointer user_data) +{ + struct test_webrtc *t = user_data; + GstElement *offeror = t->offerror == 1 ? t->webrtc1 : t->webrtc2; + GstElement *answerer = t->offerror == 2 ? t->webrtc1 : t->webrtc2; + const GstStructure *reply; + GstWebRTCSessionDescription *offer = NULL; + gchar *desc; + + reply = gst_promise_get_reply (promise); + gst_structure_get (reply, "offer", + GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL); + desc = gst_sdp_message_as_text (offer->sdp); + GST_INFO ("Created offer: %s", desc); + g_free (desc); + + g_mutex_lock (&t->lock); + if (t->on_offer_created) { + gst_webrtc_session_description_free (offer); + offer = t->on_offer_created (t, offeror, promise, t->offer_data); + } + gst_promise_unref (promise); + + g_signal_emit_by_name (offeror, "set-local-description", offer, NULL); + g_signal_emit_by_name (answerer, "set-remote-description", offer, NULL); + + promise = gst_promise_new_with_change_func (_on_answer_received, t, NULL); + g_signal_emit_by_name (answerer, "create-answer", NULL, promise); + + t->state = STATE_OFFER_CREATED; + g_cond_broadcast (&t->cond); + g_mutex_unlock (&t->lock); + + gst_webrtc_session_description_free (offer); +} + +static gboolean +_bus_watch (GstBus * bus, GstMessage * msg, struct test_webrtc *t) +{ + g_mutex_lock (&t->lock); + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_STATE_CHANGED: + if (GST_ELEMENT (msg->src) == t->webrtc1 + || GST_ELEMENT (msg->src) == t->webrtc2) { + GstState old, new, pending; + + gst_message_parse_state_changed (msg, &old, &new, &pending); + + { + gchar *dump_name = g_strconcat ("%s-state_changed-", + GST_OBJECT_NAME (msg->src), gst_element_state_get_name (old), "_", + gst_element_state_get_name (new), NULL); + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (msg->src), + GST_DEBUG_GRAPH_SHOW_ALL, dump_name); + g_free (dump_name); + } + } + break; + case GST_MESSAGE_ERROR:{ + GError *err = NULL; + gchar *dbg_info = NULL; + + { + gchar *dump_name; + dump_name = + g_strconcat ("%s-error", GST_OBJECT_NAME (t->webrtc1), NULL); + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (t->webrtc1), + GST_DEBUG_GRAPH_SHOW_ALL, dump_name); + g_free (dump_name); + dump_name = + g_strconcat ("%s-error", GST_OBJECT_NAME (t->webrtc2), NULL); + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (t->webrtc2), + GST_DEBUG_GRAPH_SHOW_ALL, dump_name); + g_free (dump_name); + } + + gst_message_parse_error (msg, &err, &dbg_info); + GST_WARNING ("ERROR from element %s: %s\n", + GST_OBJECT_NAME (msg->src), err->message); + GST_WARNING ("Debugging info: %s\n", (dbg_info) ? dbg_info : "none"); + g_error_free (err); + g_free (dbg_info); + t->state = STATE_ERROR; + g_cond_broadcast (&t->cond); + break; + } + case GST_MESSAGE_EOS:{ + { + gchar *dump_name; + dump_name = g_strconcat ("%s-eos", GST_OBJECT_NAME (t->webrtc1), NULL); + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (t->webrtc1), + GST_DEBUG_GRAPH_SHOW_ALL, dump_name); + g_free (dump_name); + dump_name = g_strconcat ("%s-eos", GST_OBJECT_NAME (t->webrtc2), NULL); + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (t->webrtc2), + GST_DEBUG_GRAPH_SHOW_ALL, dump_name); + g_free (dump_name); + } + GST_INFO ("EOS received\n"); + t->state = STATE_EOS; + g_cond_broadcast (&t->cond); + break; + } + default: + break; + } + + if (t->bus_message) + t->bus_message (t, bus, msg, t->bus_data); + g_mutex_unlock (&t->lock); + + return TRUE; +} + +static void +_on_negotiation_needed (GstElement * webrtc, struct test_webrtc *t) +{ + g_mutex_lock (&t->lock); + if (t->on_negotiation_needed) + t->on_negotiation_needed (t, webrtc, t->negotiation_data); + if (t->state == STATE_NEW) + t->state = STATE_NEGOTATION_NEEDED; + g_cond_broadcast (&t->cond); + g_mutex_unlock (&t->lock); +} + +static void +_on_ice_candidate (GstElement * webrtc, guint mlineindex, gchar * candidate, + struct test_webrtc *t) +{ + GstElement *other; + + g_mutex_lock (&t->lock); + other = webrtc == t->webrtc1 ? t->webrtc2 : t->webrtc1; + + if (t->on_ice_candidate) + t->on_ice_candidate (t, webrtc, mlineindex, candidate, other, + t->ice_candidate_data); + + g_signal_emit_by_name (other, "add-ice-candidate", mlineindex, candidate); + g_mutex_unlock (&t->lock); +} + +static void +_on_pad_added (GstElement * webrtc, GstPad * new_pad, struct test_webrtc *t) +{ + g_mutex_lock (&t->lock); + if (t->on_pad_added) + t->on_pad_added (t, webrtc, new_pad, t->pad_added_data); + g_mutex_unlock (&t->lock); +} + +static void +_pad_added_not_reached (struct test_webrtc *t, GstElement * element, + GstPad * pad, gpointer user_data) +{ + g_assert_not_reached (); +} + +static void +_ice_candidate_not_reached (struct test_webrtc *t, GstElement * element, + guint mlineindex, gchar * candidate, GstElement * other, gpointer user_data) +{ + g_assert_not_reached (); +} + +static void +_negotiation_not_reached (struct test_webrtc *t, GstElement * element, + gpointer user_data) +{ + g_assert_not_reached (); +} + +static void +_bus_no_errors (struct test_webrtc *t, GstBus * bus, GstMessage * msg, + gpointer user_data) +{ + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_ERROR:{ + g_assert_not_reached (); + break; + } + default: + break; + } +} + +static GstWebRTCSessionDescription * +_offer_answer_not_reached (struct test_webrtc *t, GstElement * element, + GstPromise * promise, gpointer user_data) +{ + g_assert_not_reached (); +} + +static void +_broadcast (struct test_webrtc *t) +{ + g_mutex_lock (&t->lock); + g_cond_broadcast (&t->cond); + g_mutex_unlock (&t->lock); +} + +static gboolean +_unlock_create_thread (GMutex * lock) +{ + g_mutex_unlock (lock); + return G_SOURCE_REMOVE; +} + +static gpointer +_bus_thread (struct test_webrtc *t) +{ + g_mutex_lock (&t->lock); + t->loop = g_main_loop_new (NULL, FALSE); + g_idle_add ((GSourceFunc) _unlock_create_thread, &t->lock); + g_cond_broadcast (&t->cond); + + g_main_loop_run (t->loop); + + g_mutex_lock (&t->lock); + g_main_loop_unref (t->loop); + t->loop = NULL; + g_cond_broadcast (&t->cond); + g_mutex_unlock (&t->lock); + + return NULL; +} + +static void +element_added_disable_sync (GstBin * bin, GstBin * sub_bin, + GstElement * element, gpointer user_data) +{ + GObjectClass *class = G_OBJECT_GET_CLASS (element); + if (g_object_class_find_property (class, "async")) + g_object_set (element, "async", FALSE, NULL); + if (g_object_class_find_property (class, "sync")) + g_object_set (element, "sync", FALSE, NULL); +} + +static struct test_webrtc * +test_webrtc_new (void) +{ + struct test_webrtc *ret = g_new0 (struct test_webrtc, 1); + + ret->on_negotiation_needed = _negotiation_not_reached; + ret->on_ice_candidate = _ice_candidate_not_reached; + ret->on_pad_added = _pad_added_not_reached; + ret->on_offer_created = _offer_answer_not_reached; + ret->on_answer_created = _offer_answer_not_reached; + ret->bus_message = _bus_no_errors; + + g_mutex_init (&ret->lock); + g_cond_init (&ret->cond); + + ret->bus1 = gst_bus_new (); + ret->bus2 = gst_bus_new (); + gst_bus_add_watch (ret->bus1, (GstBusFunc) _bus_watch, ret); + gst_bus_add_watch (ret->bus2, (GstBusFunc) _bus_watch, ret); + ret->webrtc1 = gst_element_factory_make ("webrtcbin", NULL); + ret->webrtc2 = gst_element_factory_make ("webrtcbin", NULL); + fail_unless (ret->webrtc1 != NULL && ret->webrtc2 != NULL); + + gst_element_set_bus (ret->webrtc1, ret->bus1); + gst_element_set_bus (ret->webrtc2, ret->bus2); + + g_signal_connect (ret->webrtc1, "deep-element-added", + G_CALLBACK (element_added_disable_sync), NULL); + g_signal_connect (ret->webrtc2, "deep-element-added", + G_CALLBACK (element_added_disable_sync), NULL); + g_signal_connect (ret->webrtc1, "on-negotiation-needed", + G_CALLBACK (_on_negotiation_needed), ret); + g_signal_connect (ret->webrtc2, "on-negotiation-needed", + G_CALLBACK (_on_negotiation_needed), ret); + g_signal_connect (ret->webrtc1, "on-ice-candidate", + G_CALLBACK (_on_ice_candidate), ret); + g_signal_connect (ret->webrtc2, "on-ice-candidate", + G_CALLBACK (_on_ice_candidate), ret); + g_signal_connect (ret->webrtc1, "pad-added", G_CALLBACK (_on_pad_added), ret); + g_signal_connect (ret->webrtc2, "pad-added", G_CALLBACK (_on_pad_added), ret); + g_signal_connect_swapped (ret->webrtc1, "notify::ice-gathering-state", + G_CALLBACK (_broadcast), ret); + g_signal_connect_swapped (ret->webrtc2, "notify::ice-gathering-state", + G_CALLBACK (_broadcast), ret); + g_signal_connect_swapped (ret->webrtc1, "notify::ice-connection-state", + G_CALLBACK (_broadcast), ret); + g_signal_connect_swapped (ret->webrtc2, "notify::ice-connection-state", + G_CALLBACK (_broadcast), ret); + + ret->thread = g_thread_new ("test-webrtc", (GThreadFunc) _bus_thread, ret); + + g_mutex_lock (&ret->lock); + while (!ret->loop) + g_cond_wait (&ret->cond, &ret->lock); + g_mutex_unlock (&ret->lock); + + return ret; +} + +static void +test_webrtc_free (struct test_webrtc *t) +{ + /* Otherwise while one webrtcbin is being destroyed, the other could + * generate a signal that calls into the destroyed webrtcbin */ + g_signal_handlers_disconnect_by_data (t->webrtc1, t); + g_signal_handlers_disconnect_by_data (t->webrtc2, t); + + g_main_loop_quit (t->loop); + g_mutex_lock (&t->lock); + while (t->loop) + g_cond_wait (&t->cond, &t->lock); + g_mutex_unlock (&t->lock); + + g_thread_join (t->thread); + + gst_bus_remove_watch (t->bus1); + gst_bus_remove_watch (t->bus2); + + gst_bus_set_flushing (t->bus1, TRUE); + gst_bus_set_flushing (t->bus2, TRUE); + + gst_object_unref (t->bus1); + gst_object_unref (t->bus2); + + g_list_free_full (t->harnesses, (GDestroyNotify) gst_harness_teardown); + + if (t->data_notify) + t->data_notify (t->user_data); + if (t->negotiation_notify) + t->negotiation_notify (t->negotiation_data); + if (t->ice_candidate_notify) + t->ice_candidate_notify (t->ice_candidate_data); + if (t->offer_notify) + t->offer_notify (t->offer_data); + if (t->answer_notify) + t->answer_notify (t->answer_data); + if (t->pad_added_notify) + t->pad_added_notify (t->pad_added_data); + + fail_unless_equals_int (GST_STATE_CHANGE_SUCCESS, + gst_element_set_state (t->webrtc1, GST_STATE_NULL)); + fail_unless_equals_int (GST_STATE_CHANGE_SUCCESS, + gst_element_set_state (t->webrtc2, GST_STATE_NULL)); + + gst_object_unref (t->webrtc1); + gst_object_unref (t->webrtc2); + + g_mutex_clear (&t->lock); + g_cond_clear (&t->cond); + + g_free (t); +} + +static void +test_webrtc_create_offer (struct test_webrtc *t, GstElement * webrtc) +{ + GstPromise *promise; + + t->offerror = webrtc == t->webrtc1 ? 1 : 2; + promise = gst_promise_new_with_change_func (_on_offer_received, t, NULL); + g_signal_emit_by_name (webrtc, "create-offer", NULL, promise); +} + +static void +test_webrtc_wait_for_state_mask (struct test_webrtc *t, TestState state) +{ + g_mutex_lock (&t->lock); + while (((1 << t->state) & state) == 0) { + GST_INFO ("test state 0x%x, current 0x%x", state, (1 << t->state)); + g_cond_wait (&t->cond, &t->lock); + } + GST_INFO ("have test state 0x%x, current 0x%x", state, 1 << t->state); + g_mutex_unlock (&t->lock); +} + +static void +test_webrtc_wait_for_answer_error_eos (struct test_webrtc *t) +{ + TestState states = 0; + states |= (1 << STATE_ANSWER_CREATED); + states |= (1 << STATE_EOS); + states |= (1 << STATE_ERROR); + test_webrtc_wait_for_state_mask (t, states); +} + +static void +test_webrtc_signal_state (struct test_webrtc *t, TestState state) +{ + g_mutex_lock (&t->lock); + t->state = state; + g_cond_broadcast (&t->cond); + g_mutex_unlock (&t->lock); +} + +#if 0 +static void +test_webrtc_wait_for_ice_gathering_complete (struct test_webrtc *t) +{ + GstWebRTCICEGatheringState ice_state1, ice_state2; + g_mutex_lock (&t->lock); + g_object_get (t->webrtc1, "ice-gathering-state", &ice_state1, NULL); + g_object_get (t->webrtc2, "ice-gathering-state", &ice_state2, NULL); + while (ice_state1 != GST_WEBRTC_ICE_GATHERING_STATE_COMPLETE && + ice_state2 != GST_WEBRTC_ICE_GATHERING_STATE_COMPLETE) { + g_cond_wait (&t->cond, &t->lock); + g_object_get (t->webrtc1, "ice-gathering-state", &ice_state1, NULL); + g_object_get (t->webrtc2, "ice-gathering-state", &ice_state2, NULL); + } + g_mutex_unlock (&t->lock); +} + +static void +test_webrtc_wait_for_ice_connection (struct test_webrtc *t, + GstWebRTCICEConnectionState states) +{ + GstWebRTCICEConnectionState ice_state1, ice_state2, current; + g_mutex_lock (&t->lock); + g_object_get (t->webrtc1, "ice-connection-state", &ice_state1, NULL); + g_object_get (t->webrtc2, "ice-connection-state", &ice_state2, NULL); + current = (1 << ice_state1) | (1 << ice_state2); + while ((current & states) == 0 || (current & ~states)) { + g_cond_wait (&t->cond, &t->lock); + g_object_get (t->webrtc1, "ice-connection-state", &ice_state1, NULL); + g_object_get (t->webrtc2, "ice-connection-state", &ice_state2, NULL); + current = (1 << ice_state1) | (1 << ice_state2); + } + g_mutex_unlock (&t->lock); +} +#endif +static void +_pad_added_fakesink (struct test_webrtc *t, GstElement * element, + GstPad * pad, gpointer user_data) +{ + GstHarness *h; + + if (GST_PAD_DIRECTION (pad) != GST_PAD_SRC) + return; + + h = gst_harness_new_with_element (element, NULL, "src_%u"); + gst_harness_add_sink_parse (h, "fakesink async=false sync=false"); + + t->harnesses = g_list_prepend (t->harnesses, h); +} + +static GstWebRTCSessionDescription * +_count_num_sdp_media (struct test_webrtc *t, GstElement * element, + GstPromise * promise, gpointer user_data) +{ + GstWebRTCSessionDescription *offer = NULL; + guint expected = GPOINTER_TO_UINT (user_data); + const GstStructure *reply; + const gchar *field; + + field = t->offerror == 1 && t->webrtc1 == element ? "offer" : "answer"; + + reply = gst_promise_get_reply (promise); + gst_structure_get (reply, field, + GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL); + + fail_unless_equals_int (gst_sdp_message_medias_len (offer->sdp), expected); + + return offer; +} + +GST_START_TEST (test_sdp_no_media) +{ + struct test_webrtc *t = test_webrtc_new (); + + /* check that a no stream connection creates 0 media sections */ + + t->offer_data = GUINT_TO_POINTER (0); + t->on_offer_created = _count_num_sdp_media; + t->answer_data = GUINT_TO_POINTER (0); + t->on_answer_created = _count_num_sdp_media; + + test_webrtc_create_offer (t, t->webrtc1); + + test_webrtc_wait_for_answer_error_eos (t); + fail_unless (t->state == STATE_ANSWER_CREATED); + test_webrtc_free (t); +} + +GST_END_TEST; + +static void +add_fake_audio_src_harness (GstHarness * h, gint pt) +{ + GstCaps *caps = gst_caps_from_string (OPUS_RTP_CAPS (pt)); + GstStructure *s = gst_caps_get_structure (caps, 0); + gst_structure_set (s, "payload", G_TYPE_INT, pt, NULL); + gst_harness_set_src_caps (h, caps); + gst_harness_add_src_parse (h, "fakesrc is-live=true", TRUE); +} + +static void +add_fake_video_src_harness (GstHarness * h, gint pt) +{ + GstCaps *caps = gst_caps_from_string (VP8_RTP_CAPS (pt)); + GstStructure *s = gst_caps_get_structure (caps, 0); + gst_structure_set (s, "payload", G_TYPE_INT, pt, NULL); + gst_harness_set_src_caps (h, caps); + gst_harness_add_src_parse (h, "fakesrc is-live=true", TRUE); +} + +static struct test_webrtc * +create_audio_test (void) +{ + struct test_webrtc *t = test_webrtc_new (); + GstHarness *h; + + t->on_negotiation_needed = NULL; + t->on_pad_added = _pad_added_fakesink; + + h = gst_harness_new_with_element (t->webrtc1, "sink_0", NULL); + add_fake_audio_src_harness (h, 96); + t->harnesses = g_list_prepend (t->harnesses, h); + + return t; +} + +GST_START_TEST (test_audio) +{ + struct test_webrtc *t = create_audio_test (); + + /* check that a single stream connection creates the associated number + * of media sections */ + + t->offer_data = GUINT_TO_POINTER (1); + t->on_offer_created = _count_num_sdp_media; + t->answer_data = GUINT_TO_POINTER (1); + t->on_answer_created = _count_num_sdp_media; + t->on_ice_candidate = NULL; + + test_webrtc_create_offer (t, t->webrtc1); + + test_webrtc_wait_for_answer_error_eos (t); + fail_unless_equals_int (STATE_ANSWER_CREATED, t->state); + test_webrtc_free (t); +} + +GST_END_TEST; + +static struct test_webrtc * +create_audio_video_test (void) +{ + struct test_webrtc *t = test_webrtc_new (); + GstHarness *h; + + t->on_negotiation_needed = NULL; + t->on_pad_added = _pad_added_fakesink; + + h = gst_harness_new_with_element (t->webrtc1, "sink_0", NULL); + add_fake_audio_src_harness (h, 96); + t->harnesses = g_list_prepend (t->harnesses, h); + + h = gst_harness_new_with_element (t->webrtc1, "sink_1", NULL); + add_fake_video_src_harness (h, 97); + t->harnesses = g_list_prepend (t->harnesses, h); + + return t; +} + +GST_START_TEST (test_audio_video) +{ + struct test_webrtc *t = create_audio_video_test (); + + /* check that a dual stream connection creates the associated number + * of media sections */ + + t->offer_data = GUINT_TO_POINTER (2); + t->on_offer_created = _count_num_sdp_media; + t->answer_data = GUINT_TO_POINTER (2); + t->on_answer_created = _count_num_sdp_media; + t->on_ice_candidate = NULL; + + test_webrtc_create_offer (t, t->webrtc1); + + test_webrtc_wait_for_answer_error_eos (t); + fail_unless_equals_int (STATE_ANSWER_CREATED, t->state); + test_webrtc_free (t); +} + +GST_END_TEST; + +typedef void (*ValidateSDPFunc) (struct test_webrtc * t, GstElement * element, + GstWebRTCSessionDescription * desc, gpointer user_data); + +struct validate_sdp +{ + ValidateSDPFunc validate; + gpointer user_data; +}; + +static GstWebRTCSessionDescription * +validate_sdp (struct test_webrtc *t, GstElement * element, + GstPromise * promise, gpointer user_data) +{ + struct validate_sdp *validate = user_data; + GstWebRTCSessionDescription *offer = NULL; + const GstStructure *reply; + const gchar *field; + + field = t->offerror == 1 && t->webrtc1 == element ? "offer" : "answer"; + + reply = gst_promise_get_reply (promise); + gst_structure_get (reply, field, + GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL); + + validate->validate (t, element, offer, validate->user_data); + + return offer; +} + +static void +on_sdp_media_direction (struct test_webrtc *t, GstElement * element, + GstWebRTCSessionDescription * desc, gpointer user_data) +{ + gchar **expected_directions = user_data; + int i; + + for (i = 0; i < gst_sdp_message_medias_len (desc->sdp); i++) { + const GstSDPMedia *media = gst_sdp_message_get_media (desc->sdp, i); + gboolean have_direction = FALSE; + int j; + + for (j = 0; j < gst_sdp_media_attributes_len (media); j++) { + const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, j); + + if (g_strcmp0 (attr->key, "inactive") == 0) { + fail_unless (have_direction == FALSE, + "duplicate/multiple directions for media %u", j); + have_direction = TRUE; + fail_unless (g_strcmp0 (attr->key, expected_directions[i]) == 0); + } else if (g_strcmp0 (attr->key, "sendonly") == 0) { + fail_unless (have_direction == FALSE, + "duplicate/multiple directions for media %u", j); + have_direction = TRUE; + fail_unless (g_strcmp0 (attr->key, expected_directions[i]) == 0); + } else if (g_strcmp0 (attr->key, "recvonly") == 0) { + fail_unless (have_direction == FALSE, + "duplicate/multiple directions for media %u", j); + have_direction = TRUE; + fail_unless (g_strcmp0 (attr->key, expected_directions[i]) == 0); + } else if (g_strcmp0 (attr->key, "sendrecv") == 0) { + fail_unless (have_direction == FALSE, + "duplicate/multiple directions for media %u", j); + have_direction = TRUE; + fail_unless (g_strcmp0 (attr->key, expected_directions[i]) == 0); + } + } + fail_unless (have_direction, "no direction attribute in media %u", j); + } +} + +GST_START_TEST (test_media_direction) +{ + struct test_webrtc *t = create_audio_video_test (); + const gchar *expected_offer[] = { "sendrecv", "sendrecv" }; + const gchar *expected_answer[] = { "sendrecv", "recvonly" }; + struct validate_sdp offer = { on_sdp_media_direction, expected_offer }; + struct validate_sdp answer = { on_sdp_media_direction, expected_answer }; + GstHarness *h; + + /* check the default media directions for transceivers */ + + h = gst_harness_new_with_element (t->webrtc2, "sink_0", NULL); + add_fake_audio_src_harness (h, 96); + t->harnesses = g_list_prepend (t->harnesses, h); + + t->offer_data = &offer; + t->on_offer_created = validate_sdp; + t->answer_data = &answer; + t->on_answer_created = validate_sdp; + t->on_ice_candidate = NULL; + + test_webrtc_create_offer (t, t->webrtc1); + + test_webrtc_wait_for_answer_error_eos (t); + fail_unless_equals_int (STATE_ANSWER_CREATED, t->state); + test_webrtc_free (t); +} + +GST_END_TEST; + +static void +on_sdp_media_setup (struct test_webrtc *t, GstElement * element, + GstWebRTCSessionDescription * desc, gpointer user_data) +{ + gchar **expected_setup = user_data; + int i; + + for (i = 0; i < gst_sdp_message_medias_len (desc->sdp); i++) { + const GstSDPMedia *media = gst_sdp_message_get_media (desc->sdp, i); + gboolean have_setup = FALSE; + int j; + + for (j = 0; j < gst_sdp_media_attributes_len (media); j++) { + const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, j); + + if (g_strcmp0 (attr->key, "setup") == 0) { + fail_unless (have_setup == FALSE, + "duplicate/multiple setup for media %u", j); + have_setup = TRUE; + fail_unless (g_strcmp0 (attr->value, expected_setup[i]) == 0); + } + } + fail_unless (have_setup, "no setup attribute in media %u", j); + } +} + +GST_START_TEST (test_media_setup) +{ + struct test_webrtc *t = create_audio_test (); + const gchar *expected_offer[] = { "actpass" }; + const gchar *expected_answer[] = { "active" }; + struct validate_sdp offer = { on_sdp_media_setup, expected_offer }; + struct validate_sdp answer = { on_sdp_media_setup, expected_answer }; + + /* check the default dtls setup negotiation values */ + + t->offer_data = &offer; + t->on_offer_created = validate_sdp; + t->answer_data = &answer; + t->on_answer_created = validate_sdp; + t->on_ice_candidate = NULL; + + test_webrtc_create_offer (t, t->webrtc1); + + test_webrtc_wait_for_answer_error_eos (t); + fail_unless_equals_int (STATE_ANSWER_CREATED, t->state); + test_webrtc_free (t); +} + +GST_END_TEST; + +GST_START_TEST (test_no_nice_elements_request_pad) +{ + struct test_webrtc *t = test_webrtc_new (); + GstPluginFeature *nicesrc, *nicesink; + GstRegistry *registry; + GstPad *pad; + + /* check that the absence of libnice elements posts an error on the bus + * when requesting a pad */ + + registry = gst_registry_get (); + nicesrc = gst_registry_lookup_feature (registry, "nicesrc"); + nicesink = gst_registry_lookup_feature (registry, "nicesink"); + + if (nicesrc) + gst_registry_remove_feature (registry, nicesrc); + if (nicesink) + gst_registry_remove_feature (registry, nicesink); + + t->bus_message = NULL; + + pad = gst_element_get_request_pad (t->webrtc1, "sink_0"); + fail_unless (pad == NULL); + + test_webrtc_wait_for_answer_error_eos (t); + fail_unless_equals_int (STATE_ERROR, t->state); + test_webrtc_free (t); + + if (nicesrc) + gst_registry_add_feature (registry, nicesrc); + if (nicesink) + gst_registry_add_feature (registry, nicesink); +} + +GST_END_TEST; + +GST_START_TEST (test_no_nice_elements_state_change) +{ + struct test_webrtc *t = test_webrtc_new (); + GstPluginFeature *nicesrc, *nicesink; + GstRegistry *registry; + + /* check that the absence of libnice elements posts an error on the bus */ + + registry = gst_registry_get (); + nicesrc = gst_registry_lookup_feature (registry, "nicesrc"); + nicesink = gst_registry_lookup_feature (registry, "nicesink"); + + if (nicesrc) + gst_registry_remove_feature (registry, nicesrc); + if (nicesink) + gst_registry_remove_feature (registry, nicesink); + + t->bus_message = NULL; + gst_element_set_state (t->webrtc1, GST_STATE_READY); + + test_webrtc_wait_for_answer_error_eos (t); + fail_unless_equals_int (STATE_ERROR, t->state); + test_webrtc_free (t); + + if (nicesrc) + gst_registry_add_feature (registry, nicesrc); + if (nicesink) + gst_registry_add_feature (registry, nicesink); +} + +GST_END_TEST; + +static void +validate_rtc_stats (const GstStructure * s) +{ + GstWebRTCStatsType type = 0; + double ts = 0.; + gchar *id = NULL; + + fail_unless (gst_structure_get (s, "type", GST_TYPE_WEBRTC_STATS_TYPE, &type, + NULL)); + fail_unless (gst_structure_get (s, "id", G_TYPE_STRING, &id, NULL)); + fail_unless (gst_structure_get (s, "timestamp", G_TYPE_DOUBLE, &ts, NULL)); + fail_unless (type != 0); + fail_unless (ts != 0.); + fail_unless (id != NULL); + + g_free (id); +} + +static void +validate_codec_stats (const GstStructure * s) +{ + guint pt = 0, clock_rate = 0; + + fail_unless (gst_structure_get (s, "payload-type", G_TYPE_UINT, &pt, NULL)); + fail_unless (gst_structure_get (s, "clock-rate", G_TYPE_UINT, &clock_rate, + NULL)); + fail_unless (pt >= 0 && pt <= 127); + fail_unless (clock_rate >= 0); +} + +static void +validate_rtc_stream_stats (const GstStructure * s, const GstStructure * stats) +{ + gchar *codec_id, *transport_id; + GstStructure *codec, *transport; + + fail_unless (gst_structure_get (s, "codec-id", G_TYPE_STRING, &codec_id, + NULL)); + fail_unless (gst_structure_get (s, "transport-id", G_TYPE_STRING, + &transport_id, NULL)); + + fail_unless (gst_structure_get (stats, codec_id, GST_TYPE_STRUCTURE, &codec, + NULL)); + fail_unless (gst_structure_get (stats, transport_id, GST_TYPE_STRUCTURE, + &transport, NULL)); + + fail_unless (codec != NULL); + fail_unless (transport != NULL); + + gst_structure_free (transport); + gst_structure_free (codec); + + g_free (codec_id); + g_free (transport_id); +} + +static void +validate_inbound_rtp_stats (const GstStructure * s, const GstStructure * stats) +{ + guint ssrc, fir, pli, nack; + gint packets_lost; + guint64 packets_received, bytes_received; + double jitter; + gchar *remote_id; + GstStructure *remote; + + validate_rtc_stream_stats (s, stats); + + fail_unless (gst_structure_get (s, "ssrc", G_TYPE_UINT, &ssrc, NULL)); + fail_unless (gst_structure_get (s, "fir-count", G_TYPE_UINT, &fir, NULL)); + fail_unless (gst_structure_get (s, "pli-count", G_TYPE_UINT, &pli, NULL)); + fail_unless (gst_structure_get (s, "nack-count", G_TYPE_UINT, &nack, NULL)); + fail_unless (gst_structure_get (s, "packets-received", G_TYPE_UINT64, + &packets_received, NULL)); + fail_unless (gst_structure_get (s, "bytes-received", G_TYPE_UINT64, + &bytes_received, NULL)); + fail_unless (gst_structure_get (s, "jitter", G_TYPE_DOUBLE, &jitter, NULL)); + fail_unless (gst_structure_get (s, "packets-lost", G_TYPE_INT, &packets_lost, + NULL)); + fail_unless (gst_structure_get (s, "remote-id", G_TYPE_STRING, &remote_id, + NULL)); + fail_unless (gst_structure_get (stats, remote_id, GST_TYPE_STRUCTURE, &remote, + NULL)); + fail_unless (remote != NULL); + + gst_structure_free (remote); + g_free (remote_id); +} + +static void +validate_remote_inbound_rtp_stats (const GstStructure * s, + const GstStructure * stats) +{ + guint ssrc; + gint packets_lost; + double jitter, rtt; + gchar *local_id; + GstStructure *local; + + validate_rtc_stream_stats (s, stats); + + fail_unless (gst_structure_get (s, "ssrc", G_TYPE_UINT, &ssrc, NULL)); + fail_unless (gst_structure_get (s, "jitter", G_TYPE_DOUBLE, &jitter, NULL)); + fail_unless (gst_structure_get (s, "packets-lost", G_TYPE_INT, &packets_lost, + NULL)); + fail_unless (gst_structure_get (s, "round-trip-time", G_TYPE_DOUBLE, &rtt, + NULL)); + fail_unless (gst_structure_get (s, "local-id", G_TYPE_STRING, &local_id, + NULL)); + fail_unless (gst_structure_get (stats, local_id, GST_TYPE_STRUCTURE, &local, + NULL)); + fail_unless (local != NULL); + + gst_structure_free (local); + g_free (local_id); +} + +static void +validate_outbound_rtp_stats (const GstStructure * s, const GstStructure * stats) +{ + guint ssrc, fir, pli, nack; + guint64 packets_sent, bytes_sent; + gchar *remote_id; + GstStructure *remote; + + validate_rtc_stream_stats (s, stats); + + fail_unless (gst_structure_get (s, "ssrc", G_TYPE_UINT, &ssrc, NULL)); + fail_unless (gst_structure_get (s, "fir-count", G_TYPE_UINT, &fir, NULL)); + fail_unless (gst_structure_get (s, "pli-count", G_TYPE_UINT, &pli, NULL)); + fail_unless (gst_structure_get (s, "nack-count", G_TYPE_UINT, &nack, NULL)); + fail_unless (gst_structure_get (s, "packets-sent", G_TYPE_UINT64, + &packets_sent, NULL)); + fail_unless (gst_structure_get (s, "bytes-sent", G_TYPE_UINT64, &bytes_sent, + NULL)); + fail_unless (gst_structure_get (s, "remote-id", G_TYPE_STRING, &remote_id, + NULL)); + fail_unless (gst_structure_get (stats, remote_id, GST_TYPE_STRUCTURE, &remote, + NULL)); + fail_unless (remote != NULL); + + gst_structure_free (remote); + g_free (remote_id); +} + +static void +validate_remote_outbound_rtp_stats (const GstStructure * s, + const GstStructure * stats) +{ + guint ssrc; + gchar *local_id; + GstStructure *local; + + validate_rtc_stream_stats (s, stats); + + fail_unless (gst_structure_get (s, "ssrc", G_TYPE_UINT, &ssrc, NULL)); + fail_unless (gst_structure_get (s, "local-id", G_TYPE_STRING, &local_id, + NULL)); + fail_unless (gst_structure_get (stats, local_id, GST_TYPE_STRUCTURE, &local, + NULL)); + fail_unless (local != NULL); + + gst_structure_free (local); + g_free (local_id); +} + +static gboolean +validate_stats_foreach (GQuark field_id, const GValue * value, + const GstStructure * stats) +{ + const gchar *field = g_quark_to_string (field_id); + GstWebRTCStatsType type; + const GstStructure *s; + + fail_unless (GST_VALUE_HOLDS_STRUCTURE (value)); + + s = gst_value_get_structure (value); + + GST_INFO ("validating field %s %" GST_PTR_FORMAT, field, s); + + validate_rtc_stats (s); + gst_structure_get (s, "type", GST_TYPE_WEBRTC_STATS_TYPE, &type, NULL); + if (type == GST_WEBRTC_STATS_CODEC) { + validate_codec_stats (s); + } else if (type == GST_WEBRTC_STATS_INBOUND_RTP) { + validate_inbound_rtp_stats (s, stats); + } else if (type == GST_WEBRTC_STATS_OUTBOUND_RTP) { + validate_outbound_rtp_stats (s, stats); + } else if (type == GST_WEBRTC_STATS_REMOTE_INBOUND_RTP) { + validate_remote_inbound_rtp_stats (s, stats); + } else if (type == GST_WEBRTC_STATS_REMOTE_OUTBOUND_RTP) { + validate_remote_outbound_rtp_stats (s, stats); + } else if (type == GST_WEBRTC_STATS_CSRC) { + } else if (type == GST_WEBRTC_STATS_PEER_CONNECTION) { + } else if (type == GST_WEBRTC_STATS_DATA_CHANNEL) { + } else if (type == GST_WEBRTC_STATS_STREAM) { + } else if (type == GST_WEBRTC_STATS_TRANSPORT) { + } else if (type == GST_WEBRTC_STATS_CANDIDATE_PAIR) { + } else if (type == GST_WEBRTC_STATS_LOCAL_CANDIDATE) { + } else if (type == GST_WEBRTC_STATS_REMOTE_CANDIDATE) { + } else if (type == GST_WEBRTC_STATS_CERTIFICATE) { + } else { + g_assert_not_reached (); + } + + return TRUE; +} + +static void +validate_stats (const GstStructure * stats) +{ + gst_structure_foreach (stats, + (GstStructureForeachFunc) validate_stats_foreach, (gpointer) stats); +} + +static void +_on_stats (GstPromise * promise, gpointer user_data) +{ + struct test_webrtc *t = user_data; + const GstStructure *reply = gst_promise_get_reply (promise); + int i; + + validate_stats (reply); + i = GPOINTER_TO_INT (t->user_data); + i++; + t->user_data = GINT_TO_POINTER (i); + if (i >= 2) + test_webrtc_signal_state (t, STATE_CUSTOM); + + gst_promise_unref (promise); +} + +GST_START_TEST (test_session_stats) +{ + struct test_webrtc *t = test_webrtc_new (); + GstPromise *p; + + /* test that the stats generated without any streams are sane */ + + t->on_offer_created = NULL; + t->on_answer_created = NULL; + + test_webrtc_create_offer (t, t->webrtc1); + + test_webrtc_wait_for_answer_error_eos (t); + fail_unless_equals_int (STATE_ANSWER_CREATED, t->state); + + p = gst_promise_new_with_change_func (_on_stats, t, NULL); + g_signal_emit_by_name (t->webrtc1, "get-stats", NULL, p); + p = gst_promise_new_with_change_func (_on_stats, t, NULL); + g_signal_emit_by_name (t->webrtc2, "get-stats", NULL, p); + + test_webrtc_wait_for_state_mask (t, 1 << STATE_CUSTOM); + + test_webrtc_free (t); +} + +GST_END_TEST; + +GST_START_TEST (test_add_transceiver) +{ + struct test_webrtc *t = test_webrtc_new (); + GstWebRTCRTPTransceiverDirection direction; + GstWebRTCRTPTransceiver *trans; + + direction = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV; + g_signal_emit_by_name (t->webrtc1, "add-transceiver", direction, NULL, + &trans); + fail_unless (trans != NULL); + fail_unless_equals_int (direction, trans->direction); + + gst_object_unref (trans); + + test_webrtc_free (t); +} + +GST_END_TEST; + +GST_START_TEST (test_get_transceivers) +{ + struct test_webrtc *t = create_audio_test (); + GstWebRTCRTPTransceiver *trans; + GArray *transceivers; + + g_signal_emit_by_name (t->webrtc1, "get-transceivers", &transceivers); + fail_unless (transceivers != NULL); + fail_unless_equals_int (1, transceivers->len); + + trans = g_array_index (transceivers, GstWebRTCRTPTransceiver *, 0); + fail_unless (trans != NULL); + + g_array_unref (transceivers); + + test_webrtc_free (t); +} + +GST_END_TEST; + +GST_START_TEST (test_add_recvonly_transceiver) +{ + struct test_webrtc *t = test_webrtc_new (); + GstWebRTCRTPTransceiverDirection direction; + GstWebRTCRTPTransceiver *trans; + const gchar *expected_offer[] = { "recvonly" }; + const gchar *expected_answer[] = { "sendonly" }; + struct validate_sdp offer = { on_sdp_media_direction, expected_offer }; + struct validate_sdp answer = { on_sdp_media_direction, expected_answer }; + GstCaps *caps; + GstHarness *h; + + /* add a transceiver that will only receive an opus stream and check that + * the created offer is marked as recvonly */ + + t->on_pad_added = _pad_added_fakesink; + t->on_negotiation_needed = NULL; + t->offer_data = &offer; + t->on_offer_created = validate_sdp; + t->answer_data = &answer; + t->on_answer_created = validate_sdp; + t->on_ice_candidate = NULL; + + /* setup recvonly transceiver */ + caps = gst_caps_from_string (OPUS_RTP_CAPS (96)); + direction = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY; + g_signal_emit_by_name (t->webrtc1, "add-transceiver", direction, caps, + &trans); + gst_caps_unref (caps); + fail_unless (trans != NULL); + gst_object_unref (trans); + + /* setup sendonly peer */ + h = gst_harness_new_with_element (t->webrtc2, "sink_0", NULL); + add_fake_audio_src_harness (h, 96); + t->harnesses = g_list_prepend (t->harnesses, h); + + test_webrtc_create_offer (t, t->webrtc1); + + test_webrtc_wait_for_answer_error_eos (t); + fail_unless_equals_int (STATE_ANSWER_CREATED, t->state); + test_webrtc_free (t); +} + +GST_END_TEST; + +GST_START_TEST (test_recvonly_sendonly) +{ + struct test_webrtc *t = test_webrtc_new (); + GstWebRTCRTPTransceiverDirection direction; + GstWebRTCRTPTransceiver *trans; + const gchar *expected_offer[] = { "recvonly", "sendonly" }; + const gchar *expected_answer[] = { "sendonly", "recvonly" }; + struct validate_sdp offer = { on_sdp_media_direction, expected_offer }; + struct validate_sdp answer = { on_sdp_media_direction, expected_answer }; + GstCaps *caps; + GstHarness *h; + GArray *transceivers; + + /* add a transceiver that will only receive an opus stream and check that + * the created offer is marked as recvonly */ + + t->on_pad_added = _pad_added_fakesink; + t->on_negotiation_needed = NULL; + t->offer_data = &offer; + t->on_offer_created = validate_sdp; + t->answer_data = &answer; + t->on_answer_created = validate_sdp; + t->on_ice_candidate = NULL; + + /* setup recvonly transceiver */ + caps = gst_caps_from_string (OPUS_RTP_CAPS (96)); + direction = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY; + g_signal_emit_by_name (t->webrtc1, "add-transceiver", direction, caps, + &trans); + gst_caps_unref (caps); + fail_unless (trans != NULL); + gst_object_unref (trans); + + /* setup sendonly stream */ + h = gst_harness_new_with_element (t->webrtc1, "sink_1", NULL); + add_fake_audio_src_harness (h, 96); + t->harnesses = g_list_prepend (t->harnesses, h); + g_signal_emit_by_name (t->webrtc1, "get-transceivers", &transceivers); + fail_unless (transceivers != NULL); + trans = g_array_index (transceivers, GstWebRTCRTPTransceiver *, 1); + trans->direction = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY; + + g_array_unref (transceivers); + + /* setup sendonly peer */ + h = gst_harness_new_with_element (t->webrtc2, "sink_0", NULL); + add_fake_audio_src_harness (h, 96); + t->harnesses = g_list_prepend (t->harnesses, h); + + test_webrtc_create_offer (t, t->webrtc1); + + test_webrtc_wait_for_answer_error_eos (t); + fail_unless_equals_int (STATE_ANSWER_CREATED, t->state); + test_webrtc_free (t); +} + +GST_END_TEST; + +static Suite * +webrtcbin_suite (void) +{ + Suite *s = suite_create ("webrtcbin"); + TCase *tc = tcase_create ("general"); + GstPluginFeature *nicesrc, *nicesink; + GstRegistry *registry; + + registry = gst_registry_get (); + nicesrc = gst_registry_lookup_feature (registry, "nicesrc"); + nicesink = gst_registry_lookup_feature (registry, "nicesink"); + + tcase_add_test (tc, test_sdp_no_media); + tcase_add_test (tc, test_no_nice_elements_request_pad); + tcase_add_test (tc, test_no_nice_elements_state_change); + tcase_add_test (tc, test_session_stats); + if (nicesrc && nicesink) { + tcase_add_test (tc, test_audio); + tcase_add_test (tc, test_audio_video); + tcase_add_test (tc, test_media_direction); + tcase_add_test (tc, test_media_setup); + tcase_add_test (tc, test_add_transceiver); + tcase_add_test (tc, test_get_transceivers); + tcase_add_test (tc, test_add_recvonly_transceiver); + tcase_add_test (tc, test_recvonly_sendonly); + } + + if (nicesrc) + gst_object_unref (nicesrc); + if (nicesink) + gst_object_unref (nicesink); + + suite_add_tcase (s, tc); + + return s; +} + +GST_CHECK_MAIN (webrtcbin); diff --git a/tests/check/meson.build b/tests/check/meson.build index 68a7705045..3bcb0dd51c 100644 --- a/tests/check/meson.build +++ b/tests/check/meson.build @@ -55,6 +55,7 @@ base_tests = [ [['elements/videoframe-audiolevel.c']], [['elements/viewfinderbin.c']], [['elements/voaacenc.c'], not voaac_dep.found(), [voaac_dep]], + [['elements/webrtcbin.c'], not libnice_dep.found(), [gstwebrtc_dep]], [['elements/x265enc.c'], not x265_dep.found(), [x265_dep]], [['elements/zbar.c'], not zbar_dep.found(), [zbar_dep]], [['libs/h264parser.c'], false, [gstcodecparsers_dep]], diff --git a/tests/examples/Makefile.am b/tests/examples/Makefile.am index 220254c6c0..b14b0232f7 100644 --- a/tests/examples/Makefile.am +++ b/tests/examples/Makefile.am @@ -52,6 +52,12 @@ else IPCPIPELINE_DIR= endif +if USE_WEBRTC +WEBRTC_DIR=webrtc +else +WEBRTC_DIR= +endif + noinst_PROGRAMS = playout playout_SOURCES = playout.c @@ -60,8 +66,8 @@ playout_LDADD = $(GST_PLUGINS_BASE_LIBS) -lgstvideo-$(GST_API_VERSION) $(GST_LIB SUBDIRS= codecparsers mpegts $(DIRECTFB_DIR) $(GTK_EXAMPLES) $(OPENCV_EXAMPLES) \ $(GTK3_DIR) $(AVSAMPLE_DIR) $(WAYLAND_DIR) $(MATRIXMIX_DIR) \ - $(IPCPIPELINE_DIR) + $(IPCPIPELINE_DIR) $(WEBRTC_DIR) DIST_SUBDIRS= codecparsers mpegts camerabin2 directfb mxf opencv uvch264 gtk \ - avsamplesink waylandsink audiomixmatrix ipcpipeline + avsamplesink waylandsink audiomixmatrix ipcpipeline webrtc include $(top_srcdir)/common/parallel-subdirs.mak diff --git a/tests/examples/meson.build b/tests/examples/meson.build index 6c3c4682e2..e646d4b89b 100644 --- a/tests/examples/meson.build +++ b/tests/examples/meson.build @@ -13,6 +13,7 @@ subdir('mpegts') #subdir('qt') #subdir('uvch264') #subdir('waylandsink') +subdir('webrtc') executable('playout', 'playout.c', diff --git a/tests/examples/webrtc/Makefile.am b/tests/examples/webrtc/Makefile.am new file mode 100644 index 0000000000..520942d7fe --- /dev/null +++ b/tests/examples/webrtc/Makefile.am @@ -0,0 +1,41 @@ + +noinst_PROGRAMS = webrtc webrtcbidirectional webrtcswap + +webrtc_SOURCES = webrtc.c +webrtc_CFLAGS=\ + -I$(top_srcdir)/gst-libs \ + -I$(top_builddir)/gst-libs \ + $(GST_PLUGINS_BASE_CFLAGS) \ + $(GST_CFLAGS) \ + $(GST_SDP_CFLAGS) +webrtc_LDADD=\ + $(GST_PLUGINS_BASE_LIBS) \ + $(GST_LIBS) \ + $(GST_SDP_LIBS) \ + $(top_builddir)/gst-libs/gst/webrtc/libgstwebrtc-@GST_API_VERSION@.la + +webrtcbidirectional_SOURCES = webrtcbidirectional.c +webrtcbidirectional_CFLAGS=\ + -I$(top_srcdir)/gst-libs \ + -I$(top_builddir)/gst-libs \ + $(GST_PLUGINS_BASE_CFLAGS) \ + $(GST_CFLAGS) \ + $(GST_SDP_CFLAGS) +webrtcbidirectional_LDADD=\ + $(GST_PLUGINS_BASE_LIBS) \ + $(GST_LIBS) \ + $(GST_SDP_LIBS) \ + $(top_builddir)/gst-libs/gst/webrtc/libgstwebrtc-@GST_API_VERSION@.la + +webrtcswap_SOURCES = webrtcswap.c +webrtcswap_CFLAGS=\ + -I$(top_srcdir)/gst-libs \ + -I$(top_builddir)/gst-libs \ + $(GST_PLUGINS_BASE_CFLAGS) \ + $(GST_CFLAGS) \ + $(GST_SDP_CFLAGS) +webrtcswap_LDADD=\ + $(GST_PLUGINS_BASE_LIBS) \ + $(GST_LIBS) \ + $(GST_SDP_LIBS) \ + $(top_builddir)/gst-libs/gst/webrtc/libgstwebrtc-@GST_API_VERSION@.la diff --git a/tests/examples/webrtc/meson.build b/tests/examples/webrtc/meson.build new file mode 100644 index 0000000000..7c2aab72eb --- /dev/null +++ b/tests/examples/webrtc/meson.build @@ -0,0 +1,15 @@ +examples = ['webrtc', 'webrtcbidirectional', 'webrtcswap'] + +foreach example : examples + exe_name = example + src_file = '@0@.c'.format(example) + + executable(exe_name, + src_file, + install: true, + include_directories : [configinc], + dependencies : [glib_dep, gst_dep, gstwebrtc_dep], + c_args : ['-DHAVE_CONFIG_H=1', '-DGST_USE_UNSTABLE_API'], + ) +endforeach + diff --git a/tests/examples/webrtc/webrtc.c b/tests/examples/webrtc/webrtc.c new file mode 100644 index 0000000000..1e378ae4f7 --- /dev/null +++ b/tests/examples/webrtc/webrtc.c @@ -0,0 +1,187 @@ +#include +#include +#include + +#include + +static GMainLoop *loop; +static GstElement *pipe1, *webrtc1, *webrtc2; +static GstBus *bus1; + +static gboolean +_bus_watch (GstBus * bus, GstMessage * msg, GstElement * pipe) +{ + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_STATE_CHANGED: + if (GST_ELEMENT (msg->src) == pipe) { + GstState old, new, pending; + + gst_message_parse_state_changed (msg, &old, &new, &pending); + + { + gchar *dump_name = g_strconcat ("state_changed-", + gst_element_state_get_name (old), "_", + gst_element_state_get_name (new), NULL); + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (msg->src), + GST_DEBUG_GRAPH_SHOW_ALL, dump_name); + g_free (dump_name); + } + } + break; + case GST_MESSAGE_ERROR:{ + GError *err = NULL; + gchar *dbg_info = NULL; + + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipe), + GST_DEBUG_GRAPH_SHOW_ALL, "error"); + + gst_message_parse_error (msg, &err, &dbg_info); + g_printerr ("ERROR from element %s: %s\n", + GST_OBJECT_NAME (msg->src), err->message); + g_printerr ("Debugging info: %s\n", (dbg_info) ? dbg_info : "none"); + g_error_free (err); + g_free (dbg_info); + g_main_loop_quit (loop); + break; + } + case GST_MESSAGE_EOS:{ + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipe), + GST_DEBUG_GRAPH_SHOW_ALL, "eos"); + g_print ("EOS received\n"); + g_main_loop_quit (loop); + break; + } + default: + break; + } + + return TRUE; +} + +static void +_webrtc_pad_added (GstElement * webrtc, GstPad * new_pad, GstElement * pipe) +{ + GstElement *out; + GstPad *sink; + + if (GST_PAD_DIRECTION (new_pad) != GST_PAD_SRC) + return; + + out = gst_parse_bin_from_description ("rtpvp8depay ! vp8dec ! " + "videoconvert ! queue ! xvimagesink sync=false", TRUE, NULL); + gst_bin_add (GST_BIN (pipe), out); + gst_element_sync_state_with_parent (out); + + sink = out->sinkpads->data; + + gst_pad_link (new_pad, sink); +} + +static void +_on_answer_received (GstPromise * promise, gpointer user_data) +{ + GstWebRTCSessionDescription *answer = NULL; + const GstStructure *reply; + gchar *desc; + + g_assert (gst_promise_wait (promise) == GST_PROMISE_RESULT_REPLIED); + reply = gst_promise_get_reply (promise); + gst_structure_get (reply, "answer", + GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &answer, NULL); + gst_promise_unref (promise); + desc = gst_sdp_message_as_text (answer->sdp); + g_print ("Created answer:\n%s\n", desc); + g_free (desc); + + g_signal_emit_by_name (webrtc1, "set-remote-description", answer, NULL); + g_signal_emit_by_name (webrtc2, "set-local-description", answer, NULL); + + gst_webrtc_session_description_free (answer); +} + +static void +_on_offer_received (GstPromise * promise, gpointer user_data) +{ + GstWebRTCSessionDescription *offer = NULL; + const GstStructure *reply; + gchar *desc; + + g_assert (gst_promise_wait (promise) == GST_PROMISE_RESULT_REPLIED); + reply = gst_promise_get_reply (promise); + gst_structure_get (reply, "offer", + GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL); + gst_promise_unref (promise); + desc = gst_sdp_message_as_text (offer->sdp); + g_print ("Created offer:\n%s\n", desc); + g_free (desc); + + g_signal_emit_by_name (webrtc1, "set-local-description", offer, NULL); + g_signal_emit_by_name (webrtc2, "set-remote-description", offer, NULL); + + promise = gst_promise_new_with_change_func (_on_answer_received, user_data, + NULL); + g_signal_emit_by_name (webrtc2, "create-answer", NULL, promise); + + gst_webrtc_session_description_free (offer); +} + +static void +_on_negotiation_needed (GstElement * element, gpointer user_data) +{ + GstPromise *promise; + + promise = gst_promise_new_with_change_func (_on_offer_received, user_data, + NULL); + g_signal_emit_by_name (webrtc1, "create-offer", NULL, promise); +} + +static void +_on_ice_candidate (GstElement * webrtc, guint mlineindex, gchar * candidate, + GstElement * other) +{ + g_signal_emit_by_name (other, "add-ice-candidate", mlineindex, candidate); +} + +int +main (int argc, char *argv[]) +{ + gst_init (&argc, &argv); + + loop = g_main_loop_new (NULL, FALSE); + pipe1 = + gst_parse_launch + ("videotestsrc ! video/x-raw,framerate=1/1 ! queue ! vp8enc ! rtpvp8pay ! queue ! " + "application/x-rtp,media=video,payload=96,encoding-name=VP8 ! " + "webrtcbin name=send webrtcbin name=recv", NULL); + bus1 = gst_pipeline_get_bus (GST_PIPELINE (pipe1)); + gst_bus_add_watch (bus1, (GstBusFunc) _bus_watch, pipe1); + + webrtc1 = gst_bin_get_by_name (GST_BIN (pipe1), "send"); + g_signal_connect (webrtc1, "on-negotiation-needed", + G_CALLBACK (_on_negotiation_needed), NULL); + webrtc2 = gst_bin_get_by_name (GST_BIN (pipe1), "recv"); + g_signal_connect (webrtc2, "pad-added", G_CALLBACK (_webrtc_pad_added), + pipe1); + g_signal_connect (webrtc1, "on-ice-candidate", + G_CALLBACK (_on_ice_candidate), webrtc2); + g_signal_connect (webrtc2, "on-ice-candidate", + G_CALLBACK (_on_ice_candidate), webrtc1); + + g_print ("Starting pipeline\n"); + gst_element_set_state (GST_ELEMENT (pipe1), GST_STATE_PLAYING); + + g_main_loop_run (loop); + + gst_element_set_state (GST_ELEMENT (pipe1), GST_STATE_NULL); + g_print ("Pipeline stopped\n"); + + gst_object_unref (webrtc1); + gst_object_unref (webrtc2); + gst_bus_remove_watch (bus1); + gst_object_unref (bus1); + gst_object_unref (pipe1); + + gst_deinit (); + + return 0; +} diff --git a/tests/examples/webrtc/webrtcbidirectional.c b/tests/examples/webrtc/webrtcbidirectional.c new file mode 100644 index 0000000000..2b8bf113f7 --- /dev/null +++ b/tests/examples/webrtc/webrtcbidirectional.c @@ -0,0 +1,197 @@ +#include +#include +#include + +#include + +static GMainLoop *loop; +static GstElement *pipe1, *webrtc1, *webrtc2; +static GstBus *bus1; + +static gboolean +_bus_watch (GstBus * bus, GstMessage * msg, GstElement * pipe) +{ + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_STATE_CHANGED: + if (GST_ELEMENT (msg->src) == pipe) { + GstState old, new, pending; + + gst_message_parse_state_changed (msg, &old, &new, &pending); + + { + gchar *dump_name = g_strconcat ("state_changed-", + gst_element_state_get_name (old), "_", + gst_element_state_get_name (new), NULL); + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (msg->src), + GST_DEBUG_GRAPH_SHOW_ALL, dump_name); + g_free (dump_name); + } + } + break; + case GST_MESSAGE_ERROR:{ + GError *err = NULL; + gchar *dbg_info = NULL; + + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipe), + GST_DEBUG_GRAPH_SHOW_ALL, "error"); + + gst_message_parse_error (msg, &err, &dbg_info); + g_printerr ("ERROR from element %s: %s\n", + GST_OBJECT_NAME (msg->src), err->message); + g_printerr ("Debugging info: %s\n", (dbg_info) ? dbg_info : "none"); + g_error_free (err); + g_free (dbg_info); + g_main_loop_quit (loop); + break; + } + case GST_MESSAGE_EOS:{ + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipe), + GST_DEBUG_GRAPH_SHOW_ALL, "eos"); + g_print ("EOS received\n"); + g_main_loop_quit (loop); + break; + } + default: + break; + } + + return TRUE; +} + +static void +_webrtc_pad_added (GstElement * webrtc, GstPad * new_pad, GstElement * pipe) +{ + GstElement *out; + GstPad *sink; + + if (GST_PAD_DIRECTION (new_pad) != GST_PAD_SRC) + return; + + out = gst_parse_bin_from_description ("rtpvp8depay ! vp8dec ! " + "videoconvert ! queue ! xvimagesink", TRUE, NULL); + gst_bin_add (GST_BIN (pipe), out); + gst_element_sync_state_with_parent (out); + + sink = out->sinkpads->data; + + gst_pad_link (new_pad, sink); +} + +static void +_on_answer_received (GstPromise * promise, gpointer user_data) +{ + GstWebRTCSessionDescription *answer = NULL; + const GstStructure *reply; + gchar *desc; + + g_assert (gst_promise_wait (promise) == GST_PROMISE_RESULT_REPLIED); + reply = gst_promise_get_reply (promise); + gst_structure_get (reply, "answer", + GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &answer, NULL); + gst_promise_unref (promise); + desc = gst_sdp_message_as_text (answer->sdp); + g_print ("Created answer:\n%s\n", desc); + g_free (desc); + + /* this is one way to tell webrtcbin that we don't want to be notified when + * this task is complete: set a NULL promise */ + g_signal_emit_by_name (webrtc1, "set-remote-description", answer, NULL); + /* this is another way to tell webrtcbin that we don't want to be notified + * when this task is complete: interrupt the promise */ + promise = gst_promise_new (); + g_signal_emit_by_name (webrtc2, "set-local-description", answer, NULL); + gst_promise_interrupt (promise); + gst_promise_unref (promise); + + gst_webrtc_session_description_free (answer); +} + +static void +_on_offer_received (GstPromise * promise, gpointer user_data) +{ + GstWebRTCSessionDescription *offer = NULL; + const GstStructure *reply; + gchar *desc; + + g_assert (gst_promise_wait (promise) == GST_PROMISE_RESULT_REPLIED); + reply = gst_promise_get_reply (promise); + gst_structure_get (reply, "offer", + GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL); + gst_promise_unref (promise); + desc = gst_sdp_message_as_text (offer->sdp); + g_print ("Created offer:\n%s\n", desc); + g_free (desc); + + g_signal_emit_by_name (webrtc1, "set-local-description", offer, NULL); + g_signal_emit_by_name (webrtc2, "set-remote-description", offer, NULL); + + promise = gst_promise_new_with_change_func (_on_answer_received, user_data, + NULL); + g_signal_emit_by_name (webrtc2, "create-answer", NULL, promise); + + gst_webrtc_session_description_free (offer); +} + +static void +_on_negotiation_needed (GstElement * element, gpointer user_data) +{ + GstPromise *promise; + + promise = gst_promise_new_with_change_func (_on_offer_received, user_data, + NULL); + g_signal_emit_by_name (webrtc1, "create-offer", NULL, promise); +} + +static void +_on_ice_candidate (GstElement * webrtc, guint mlineindex, gchar * candidate, + GstElement * other) +{ + g_signal_emit_by_name (other, "add-ice-candidate", mlineindex, candidate); +} + +int +main (int argc, char *argv[]) +{ + gst_init (&argc, &argv); + + loop = g_main_loop_new (NULL, FALSE); + pipe1 = + gst_parse_launch ("videotestsrc ! queue ! vp8enc ! rtpvp8pay ! queue ! " + "application/x-rtp,media=video,payload=96,encoding-name=VP8 ! " + "webrtcbin name=smpte videotestsrc pattern=ball ! queue ! vp8enc ! rtpvp8pay ! queue ! " + "application/x-rtp,media=video,payload=96,encoding-name=VP8 ! webrtcbin name=ball", + NULL); + bus1 = gst_pipeline_get_bus (GST_PIPELINE (pipe1)); + gst_bus_add_watch (bus1, (GstBusFunc) _bus_watch, pipe1); + + webrtc1 = gst_bin_get_by_name (GST_BIN (pipe1), "smpte"); + g_signal_connect (webrtc1, "on-negotiation-needed", + G_CALLBACK (_on_negotiation_needed), NULL); + g_signal_connect (webrtc1, "pad-added", G_CALLBACK (_webrtc_pad_added), + pipe1); + webrtc2 = gst_bin_get_by_name (GST_BIN (pipe1), "ball"); + g_signal_connect (webrtc2, "pad-added", G_CALLBACK (_webrtc_pad_added), + pipe1); + g_signal_connect (webrtc1, "on-ice-candidate", + G_CALLBACK (_on_ice_candidate), webrtc2); + g_signal_connect (webrtc2, "on-ice-candidate", + G_CALLBACK (_on_ice_candidate), webrtc1); + + g_print ("Starting pipeline\n"); + gst_element_set_state (GST_ELEMENT (pipe1), GST_STATE_PLAYING); + + g_main_loop_run (loop); + + gst_element_set_state (GST_ELEMENT (pipe1), GST_STATE_NULL); + g_print ("Pipeline stopped\n"); + + gst_object_unref (webrtc1); + gst_object_unref (webrtc2); + gst_bus_remove_watch (bus1); + gst_object_unref (bus1); + gst_object_unref (pipe1); + + gst_deinit (); + + return 0; +} diff --git a/tests/examples/webrtc/webrtcswap.c b/tests/examples/webrtc/webrtcswap.c new file mode 100644 index 0000000000..02c507dc88 --- /dev/null +++ b/tests/examples/webrtc/webrtcswap.c @@ -0,0 +1,215 @@ +#include +#include +#include + +#include + +static GMainLoop *loop; +static GstElement *pipe1, *webrtc1, *webrtc2; +static GstBus *bus1; + +static gboolean +_bus_watch (GstBus * bus, GstMessage * msg, GstElement * pipe) +{ + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_STATE_CHANGED: + if (GST_ELEMENT (msg->src) == pipe) { + GstState old, new, pending; + + gst_message_parse_state_changed (msg, &old, &new, &pending); + + { + gchar *dump_name = g_strconcat ("state_changed-", + gst_element_state_get_name (old), "_", + gst_element_state_get_name (new), NULL); + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (msg->src), + GST_DEBUG_GRAPH_SHOW_ALL, dump_name); + g_free (dump_name); + } + } + break; + case GST_MESSAGE_ERROR:{ + GError *err = NULL; + gchar *dbg_info = NULL; + + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipe), + GST_DEBUG_GRAPH_SHOW_ALL, "error"); + + gst_message_parse_error (msg, &err, &dbg_info); + g_printerr ("ERROR from element %s: %s\n", + GST_OBJECT_NAME (msg->src), err->message); + g_printerr ("Debugging info: %s\n", (dbg_info) ? dbg_info : "none"); + g_error_free (err); + g_free (dbg_info); + g_main_loop_quit (loop); + break; + } + case GST_MESSAGE_EOS:{ + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipe), + GST_DEBUG_GRAPH_SHOW_ALL, "eos"); + g_print ("EOS received\n"); + g_main_loop_quit (loop); + break; + } + default: + break; + } + + return TRUE; +} + +static void +_webrtc_pad_added (GstElement * webrtc, GstPad * new_pad, GstElement * pipe) +{ + GstElement *out = NULL; + GstPad *sink = NULL; + GstCaps *caps; + GstStructure *s; + const gchar *encoding_name; + + if (GST_PAD_DIRECTION (new_pad) != GST_PAD_SRC) + return; + + caps = gst_pad_get_current_caps (new_pad); + if (!caps) + caps = gst_pad_query_caps (new_pad, NULL); + GST_ERROR_OBJECT (new_pad, "caps %" GST_PTR_FORMAT, caps); + g_assert (gst_caps_is_fixed (caps)); + s = gst_caps_get_structure (caps, 0); + encoding_name = gst_structure_get_string (s, "encoding-name"); + if (g_strcmp0 (encoding_name, "VP8") == 0) { + out = gst_parse_bin_from_description ("rtpvp8depay ! vp8dec ! " + "videoconvert ! queue ! xvimagesink sync=false", TRUE, NULL); + } else if (g_strcmp0 (encoding_name, "OPUS") == 0) { + out = gst_parse_bin_from_description ("rtpopusdepay ! opusdec ! " + "audioconvert ! audioresample ! audiorate ! queue ! autoaudiosink", + TRUE, NULL); + } else { + g_critical ("Unknown encoding name %s", encoding_name); + g_assert_not_reached (); + } + gst_bin_add (GST_BIN (pipe), out); + gst_element_sync_state_with_parent (out); + sink = out->sinkpads->data; + + gst_pad_link (new_pad, sink); + + gst_caps_unref (caps); +} + +static void +_on_answer_received (GstPromise * promise, gpointer user_data) +{ + GstWebRTCSessionDescription *answer = NULL; + const GstStructure *reply; + gchar *desc; + + g_assert (gst_promise_wait (promise) == GST_PROMISE_RESULT_REPLIED); + reply = gst_promise_get_reply (promise); + gst_structure_get (reply, "answer", + GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &answer, NULL); + gst_promise_unref (promise); + desc = gst_sdp_message_as_text (answer->sdp); + g_print ("Created answer:\n%s\n", desc); + g_free (desc); + + g_signal_emit_by_name (webrtc1, "set-remote-description", answer, NULL); + g_signal_emit_by_name (webrtc2, "set-local-description", answer, NULL); + + gst_webrtc_session_description_free (answer); +} + +static void +_on_offer_received (GstPromise * promise, gpointer user_data) +{ + GstWebRTCSessionDescription *offer = NULL; + const GstStructure *reply; + gchar *desc; + + g_assert (gst_promise_wait (promise) == GST_PROMISE_RESULT_REPLIED); + reply = gst_promise_get_reply (promise); + gst_structure_get (reply, "offer", + GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL); + gst_promise_unref (promise); + desc = gst_sdp_message_as_text (offer->sdp); + g_print ("Created offer:\n%s\n", desc); + g_free (desc); + + g_signal_emit_by_name (webrtc1, "set-local-description", offer, NULL); + g_signal_emit_by_name (webrtc2, "set-remote-description", offer, NULL); + + promise = gst_promise_new_with_change_func (_on_answer_received, user_data, + NULL); + g_signal_emit_by_name (webrtc2, "create-answer", NULL, promise); + + gst_webrtc_session_description_free (offer); +} + +static void +_on_negotiation_needed (GstElement * element, gpointer user_data) +{ + GstPromise *promise; + + promise = gst_promise_new_with_change_func (_on_offer_received, user_data, + NULL); + g_signal_emit_by_name (webrtc1, "create-offer", NULL, promise); +} + +static void +_on_ice_candidate (GstElement * webrtc, guint mlineindex, gchar * candidate, + GstElement * other) +{ + g_signal_emit_by_name (other, "add-ice-candidate", mlineindex, candidate); +} + +int +main (int argc, char *argv[]) +{ + gst_init (&argc, &argv); + + loop = g_main_loop_new (NULL, FALSE); + pipe1 = + gst_parse_launch ("webrtcbin name=smpte webrtcbin name=ball " + "videotestsrc pattern=smpte ! queue ! vp8enc ! rtpvp8pay ! queue ! " + "application/x-rtp,media=video,payload=96,encoding-name=VP8 ! smpte.sink_0 " + "audiotestsrc ! opusenc ! rtpopuspay ! queue ! " + "application/x-rtp,media=audio,payload=97,encoding-name=OPUS ! smpte.sink_1 " + "videotestsrc pattern=ball ! queue ! vp8enc ! rtpvp8pay ! queue ! " + "application/x-rtp,media=video,payload=96,encoding-name=VP8 ! ball.sink_1 " + "audiotestsrc wave=saw ! opusenc ! rtpopuspay ! queue ! " + "application/x-rtp,media=audio,payload=97,encoding-name=OPUS ! ball.sink_0 ", + NULL); + bus1 = gst_pipeline_get_bus (GST_PIPELINE (pipe1)); + gst_bus_add_watch (bus1, (GstBusFunc) _bus_watch, pipe1); + + webrtc1 = gst_bin_get_by_name (GST_BIN (pipe1), "smpte"); + g_signal_connect (webrtc1, "on-negotiation-needed", + G_CALLBACK (_on_negotiation_needed), NULL); + g_signal_connect (webrtc1, "pad-added", G_CALLBACK (_webrtc_pad_added), + pipe1); + webrtc2 = gst_bin_get_by_name (GST_BIN (pipe1), "ball"); + g_signal_connect (webrtc2, "pad-added", G_CALLBACK (_webrtc_pad_added), + pipe1); + g_signal_connect (webrtc1, "on-ice-candidate", + G_CALLBACK (_on_ice_candidate), webrtc2); + g_signal_connect (webrtc2, "on-ice-candidate", + G_CALLBACK (_on_ice_candidate), webrtc1); + + g_print ("Starting pipeline\n"); + gst_element_set_state (GST_ELEMENT (pipe1), GST_STATE_PLAYING); + + g_main_loop_run (loop); + + gst_element_set_state (GST_ELEMENT (pipe1), GST_STATE_NULL); + g_print ("Pipeline stopped\n"); + + gst_object_unref (webrtc1); + gst_object_unref (webrtc2); + gst_bus_remove_watch (bus1); + gst_object_unref (bus1); + gst_object_unref (pipe1); + + gst_deinit (); + + return 0; +}