summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore5
-rw-r--r--configure.ac16
-rw-r--r--docs/libs/Makefile.am1
-rw-r--r--docs/libs/gst-plugins-bad-libs-docs.sgml10
-rw-r--r--docs/libs/gst-plugins-bad-libs-sections.txt101
-rw-r--r--docs/libs/gst-plugins-bad-libs.types20
-rw-r--r--ext/Makefile.am12
-rw-r--r--ext/meson.build1
-rw-r--r--ext/webrtc/Makefile.am53
-rw-r--r--ext/webrtc/fwd.h58
-rw-r--r--ext/webrtc/gstwebrtc.c39
-rw-r--r--ext/webrtc/gstwebrtcbin.c3530
-rw-r--r--ext/webrtc/gstwebrtcbin.h154
-rw-r--r--ext/webrtc/gstwebrtcice.c887
-rw-r--r--ext/webrtc/gstwebrtcice.h83
-rw-r--r--ext/webrtc/gstwebrtcstats.c549
-rw-r--r--ext/webrtc/gstwebrtcstats.h35
-rw-r--r--ext/webrtc/icestream.c239
-rw-r--r--ext/webrtc/icestream.h63
-rw-r--r--ext/webrtc/meson.build27
-rw-r--r--ext/webrtc/nicetransport.c268
-rw-r--r--ext/webrtc/nicetransport.h58
-rw-r--r--ext/webrtc/transportreceivebin.c376
-rw-r--r--ext/webrtc/transportreceivebin.h65
-rw-r--r--ext/webrtc/transportsendbin.c471
-rw-r--r--ext/webrtc/transportsendbin.h58
-rw-r--r--ext/webrtc/transportstream.c252
-rw-r--r--ext/webrtc/transportstream.h69
-rw-r--r--ext/webrtc/utils.c138
-rw-r--r--ext/webrtc/utils.h65
-rw-r--r--ext/webrtc/webrtcsdp.c716
-rw-r--r--ext/webrtc/webrtcsdp.h80
-rw-r--r--ext/webrtc/webrtctransceiver.c149
-rw-r--r--ext/webrtc/webrtctransceiver.h57
-rw-r--r--gst-libs/gst/Makefile.am4
-rw-r--r--gst-libs/gst/meson.build1
-rw-r--r--gst-libs/gst/webrtc/Makefile.am54
-rw-r--r--gst-libs/gst/webrtc/dtlstransport.c238
-rw-r--r--gst-libs/gst/webrtc/dtlstransport.h70
-rw-r--r--gst-libs/gst/webrtc/icetransport.c204
-rw-r--r--gst-libs/gst/webrtc/icetransport.h76
-rw-r--r--gst-libs/gst/webrtc/meson.build59
-rw-r--r--gst-libs/gst/webrtc/rtcsessiondescription.c123
-rw-r--r--gst-libs/gst/webrtc/rtcsessiondescription.h58
-rw-r--r--gst-libs/gst/webrtc/rtpreceiver.c135
-rw-r--r--gst-libs/gst/webrtc/rtpreceiver.h76
-rw-r--r--gst-libs/gst/webrtc/rtpsender.c141
-rw-r--r--gst-libs/gst/webrtc/rtpsender.h77
-rw-r--r--gst-libs/gst/webrtc/rtptransceiver.c186
-rw-r--r--gst-libs/gst/webrtc/rtptransceiver.h69
-rw-r--r--gst-libs/gst/webrtc/webrtc.h33
-rw-r--r--gst-libs/gst/webrtc/webrtc_fwd.h251
-rwxr-xr-xgst-libs/gst/webrtc/webrtc_mkenum.py55
-rw-r--r--pkgconfig/Makefile.am4
-rw-r--r--pkgconfig/gstreamer-plugins-bad-uninstalled.pc.in2
-rw-r--r--pkgconfig/gstreamer-webrtc-uninstalled.pc.in12
-rw-r--r--pkgconfig/gstreamer-webrtc.pc.in12
-rw-r--r--pkgconfig/meson.build2
-rw-r--r--tests/check/Makefile.am14
-rw-r--r--tests/check/elements/.gitignore1
-rw-r--r--tests/check/elements/webrtcbin.c1382
-rw-r--r--tests/check/meson.build1
-rw-r--r--tests/examples/Makefile.am10
-rw-r--r--tests/examples/meson.build1
-rw-r--r--tests/examples/webrtc/Makefile.am41
-rw-r--r--tests/examples/webrtc/meson.build15
-rw-r--r--tests/examples/webrtc/webrtc.c187
-rw-r--r--tests/examples/webrtc/webrtcbidirectional.c197
-rw-r--r--tests/examples/webrtc/webrtcswap.c215
69 files changed, 12704 insertions, 7 deletions
diff --git a/.gitignore b/.gitignore
index 5a64122c8..7a0aef604 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 b413720e5..197c4991b 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 dfc20ff53..ef2d37f8e 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 530af2b4d..6a237c8da 100644
--- a/docs/libs/gst-plugins-bad-libs-docs.sgml
+++ b/docs/libs/gst-plugins-bad-libs-docs.sgml
@@ -73,6 +73,16 @@
<xi:include href="xml/gstplayer-visualization.xml"/>
</chapter>
+ <chapter id="webrtc">
+ <title>WebRTC Library</title>
+ <xi:include href="xml/gstwebrtc-dtlstransport.xml"/>
+ <xi:include href="xml/gstwebrtc-icetransport.xml"/>
+ <xi:include href="xml/gstwebrtc-receiver.xml"/>
+ <xi:include href="xml/gstwebrtc-sender.xml"/>
+ <xi:include href="xml/gstwebrtc-sessiondescription.xml"/>
+ <xi:include href="xml/gstwebrtc-transceiver.xml"/>
+ </chapter>
+
<chapter>
<title>Interfaces</title>
<xi:include href="xml/gstphotography.xml" />
diff --git a/docs/libs/gst-plugins-bad-libs-sections.txt b/docs/libs/gst-plugins-bad-libs-sections.txt
index 7becdeb68..7231614cc 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
</SECTION>
+
+<SECTION>
+<FILE>gstwebrtc-dtlstransport</FILE>
+GstWebRTCDTLSTransportState
+
+gst_webrtc_dtls_transport_new
+
+<SUBSECTION Standard>
+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
+</SECTION>
+
+<SECTION>
+<FILE>gstwebrtc-icetransport</FILE>
+GstWebRTCIceRole
+GstWebRTCICEConnectionState
+GstWebRTCICEGatheringState
+
+
+
+<SUBSECTION Standard>
+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
+</SECTION>
+
+<SECTION>
+<FILE>gstwebrtc-receiver</FILE>
+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
+<SUBSECTION Standard>
+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
+</SECTION>
+
+<SECTION>
+<FILE>gstwebrtc-sender</FILE>
+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
+<SUBSECTION Standard>
+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
+</SECTION>
+
+<SECTION>
+<FILE>gstwebrtc-sessiondescription</FILE>
+GstWebRTCSessionDescription
+gst_webrtc_session_description_new
+gst_webrtc_session_description_copy
+gst_webrtc_session_description_free
+<SUBSECTION Standard>
+gst_webrtc_session_description_get_type
+GST_TYPE_WEBRTC_SESSION_DESCRIPTION
+</SECTION>
+
+<SECTION>
+<FILE>gstwebrtc-transceiver</FILE>
+<SUBSECTION Standard>
+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
+</SECTION>
diff --git a/docs/libs/gst-plugins-bad-libs.types b/docs/libs/gst-plugins-bad-libs.types
index 1b3a55534..7b4851c42 100644
--- a/docs/libs/gst-plugins-bad-libs.types
+++ b/docs/libs/gst-plugins-bad-libs.types
@@ -7,6 +7,7 @@
#include <gst/insertbin/gstinsertbin.h>
#include <gst/mpegts/mpegts.h>
#include <gst/player/player.h>
+#include <gst/webrtc/webrtc.h>
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 e4e0db7d6..cbea2e0bd 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 d4730b7ce..a2960b8ee 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 000000000..5f9a71488
--- /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 000000000..903145fbf
--- /dev/null
+++ b/ext/webrtc/fwd.h
@@ -0,0 +1,58 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __WEBRTC_FWD_H__
+#define __WEBRTC_FWD_H__
+
+#include <gst/gst.h>
+#include <gst/webrtc/webrtc.h>
+
+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 000000000..27dc642b4
--- /dev/null
+++ b/ext/webrtc/gstwebrtc.c
@@ -0,0 +1,39 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#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 000000000..a834a22c5
--- /dev/null
+++ b/ext/webrtc/gstwebrtcbin.c
@@ -0,0 +1,3530 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <matthew@centricular.com>");
+
+ 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 000000000..bbcc5f507
--- /dev/null
+++ b/ext/webrtc/gstwebrtcbin.h
@@ -0,0 +1,154 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_WEBRTC_BIN_H__
+#define __GST_WEBRTC_BIN_H__
+
+#include <gst/sdp/sdp.h>
+#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 000000000..ce30d3b44
--- /dev/null
+++ b/ext/webrtc/gstwebrtcice.c
@@ -0,0 +1,887 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "gstwebrtcice.h"
+/* libnice */
+#include <agent.h>
+#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://<host>:<port>";
+ 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 000000000..cacf497a8
--- /dev/null
+++ b/ext/webrtc/gstwebrtcice.h
@@ -0,0 +1,83 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_WEBRTC_ICE_H__
+#define __GST_WEBRTC_ICE_H__
+
+#include <gst/gst.h>
+#include <gst/sdp/sdp.h>
+#include <gst/webrtc/webrtc.h>
+#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 000000000..38a6a02a2
--- /dev/null
+++ b/ext/webrtc/gstwebrtcstats.c
@@ -0,0 +1,549 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#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 000000000..e67ba47d6
--- /dev/null
+++ b/ext/webrtc/gstwebrtcstats.h
@@ -0,0 +1,35 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_WEBRTC_STATS_H__
+#define __GST_WEBRTC_STATS_H__
+
+#include <gst/gst.h>
+#include <gst/sdp/sdp.h>
+#include <gst/webrtc/webrtc.h>
+#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 000000000..dd4852468
--- /dev/null
+++ b/ext/webrtc/icestream.c
@@ -0,0 +1,239 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#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 000000000..6bf67ea78
--- /dev/null
+++ b/ext/webrtc/icestream.h
@@ -0,0 +1,63 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_WEBRTC_ICE_STREAM_H__
+#define __GST_WEBRTC_ICE_STREAM_H__
+
+#include <gst/gst.h>
+/* libice */
+#include <agent.h>
+#include <gst/webrtc/webrtc.h>
+#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 000000000..c98bd0d89
--- /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 000000000..2365cfd52
--- /dev/null
+++ b/ext/webrtc/nicetransport.c
@@ -0,0 +1,268 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#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 000000000..f36e1ccb9
--- /dev/null
+++ b/ext/webrtc/nicetransport.h
@@ -0,0 +1,58 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_WEBRTC_NICE_TRANSPORT_H__
+#define __GST_WEBRTC_NICE_TRANSPORT_H__
+
+#include <gst/gst.h>
+/* libnice */
+#include <agent.h>
+#include <gst/webrtc/webrtc.h>
+#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 000000000..6730b1fb7
--- /dev/null
+++ b/ext/webrtc/transportreceivebin.c
@@ -0,0 +1,376 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#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 <matthew@centricular.com>");
+
+ 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 000000000..f26b4ad0f
--- /dev/null
+++ b/ext/webrtc/transportreceivebin.h
@@ -0,0 +1,65 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __TRANSPORT_RECEIVE_BIN_H__
+#define __TRANSPORT_RECEIVE_BIN_H__
+
+#include <gst/gst.h>
+#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 000000000..8acb74025
--- /dev/null
+++ b/ext/webrtc/transportsendbin.c
@@ -0,0 +1,471 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#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 <matthew@centricular.com>");
+
+ 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 000000000..fc5faf8da
--- /dev/null
+++ b/ext/webrtc/transportsendbin.h
@@ -0,0 +1,58 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __TRANSPORT_SEND_BIN_H__
+#define __TRANSPORT_SEND_BIN_H__
+
+#include <gst/gst.h>
+#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 000000000..894b3217a
--- /dev/null
+++ b/ext/webrtc/transportstream.c
@@ -0,0 +1,252 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#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 000000000..9c4e4a0de
--- /dev/null
+++ b/ext/webrtc/transportstream.h
@@ -0,0 +1,69 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __TRANSPORT_STREAM_H__
+#define __TRANSPORT_STREAM_H__
+
+#include "fwd.h"
+#include <gst/webrtc/rtptransceiver.h>
+
+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 000000000..2b99c7047
--- /dev/null
+++ b/ext/webrtc/utils.c
@@ -0,0 +1,138 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#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 000000000..f76f850d9
--- /dev/null
+++ b/ext/webrtc/utils.h
@@ -0,0 +1,65 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __WEBRTC_UTILS_H__
+#define __WEBRTC_UTILS_H__
+
+#include <gst/gst.h>
+#include <gst/webrtc/webrtc.h>
+#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 000000000..5584d9bd1
--- /dev/null
+++ b/ext/webrtc/webrtcsdp.c
@@ -0,0 +1,716 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "webrtcsdp.h"
+
+#include "utils.h"
+#include "gstwebrtcbin.h"
+
+#include <string.h>
+
+#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 000000000..779dcc276
--- /dev/null
+++ b/ext/webrtc/webrtcsdp.h
@@ -0,0 +1,80 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __WEBRTC_SDP_H__
+#define __WEBRTC_SDP_H__
+
+#include <gst/gst.h>
+#include <gst/webrtc/webrtc.h>
+#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 000000000..310956f2d
--- /dev/null
+++ b/ext/webrtc/webrtctransceiver.c
@@ -0,0 +1,149 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#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 000000000..b90fea043
--- /dev/null
+++ b/ext/webrtc/webrtctransceiver.h
@@ -0,0 +1,57 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __WEBRTC_TRANSCEIVER_H__
+#define __WEBRTC_TRANSCEIVER_H__
+
+#include "fwd.h"
+#include <gst/webrtc/rtptransceiver.h>
+#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 ae541aaf8..db67fc89f 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 aac5398af..2e579540e 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 000000000..49bb95a01
--- /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 000000000..31324c34d
--- /dev/null
+++ b/gst-libs/gst/webrtc/dtlstransport.c
@@ -0,0 +1,238 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:gstwebrtc-dtlstransport
+ * @short_description: RTCDtlsTransport object
+ * @title: GstWebRTCDTLSTransport
+ * @see_also: #GstWebRTCRTPSender, #GstWebRTCRTPReceiver, #GstWebRTCICETransport
+ *
+ * <ulink url="https://www.w3.org/TR/webrtc/#rtcdtlstransport">https://www.w3.org/TR/webrtc/#rtcdtlstransport</ulink>
+ */
+
+#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 000000000..366a602a2
--- /dev/null
+++ b/gst-libs/gst/webrtc/dtlstransport.h
@@ -0,0 +1,70 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_WEBRTC_DTLS_TRANSPORT_H__
+#define __GST_WEBRTC_DTLS_TRANSPORT_H__
+
+#include <gst/gst.h>
+#include <gst/webrtc/webrtc_fwd.h>
+#include <gst/webrtc/icetransport.h>
+
+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 000000000..d5ed0605e
--- /dev/null
+++ b/gst-libs/gst/webrtc/icetransport.c
@@ -0,0 +1,204 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:gstwebrtc-icetransport
+ * @short_description: RTCIceTransport object
+ * @title: GstWebRTCICETransport
+ * @see_also: #GstWebRTCRTPSender, #GstWebRTCRTPReceiver, #GstWebRTCDTLSTransport
+ *
+ * <ulink url="https://www.w3.org/TR/webrtc/#rtcicetransport">https://www.w3.org/TR/webrtc/#rtcicetransport</ulink>
+ */
+
+#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 000000000..30730fa9b
--- /dev/null
+++ b/gst-libs/gst/webrtc/icetransport.h
@@ -0,0 +1,76 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_WEBRTC_ICE_TRANSPORT_H__
+#define __GST_WEBRTC_ICE_TRANSPORT_H__
+
+#include <gst/gst.h>
+#include <gst/webrtc/webrtc_fwd.h>
+
+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 000000000..c670eadb5
--- /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 000000000..3987ab63f
--- /dev/null
+++ b/gst-libs/gst/webrtc/rtcsessiondescription.c
@@ -0,0 +1,123 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:gstwebrtc-sessiondescription
+ * @short_description: RTCSessionDescription object
+ * @title: GstWebRTCSessionDescription
+ *
+ * <ulink url="https://www.w3.org/TR/webrtc/#rtcsessiondescription-class">https://www.w3.org/TR/webrtc/#rtcsessiondescription-class</ulink>
+ */
+
+#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 000000000..080d21c7e
--- /dev/null
+++ b/gst-libs/gst/webrtc/rtcsessiondescription.h
@@ -0,0 +1,58 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_WEBRTC_SESSION_DESCRIPTION_H__
+#define __GST_WEBRTC_SESSION_DESCRIPTION_H__
+
+#include <gst/gst.h>
+#include <gst/sdp/sdp.h>
+#include <gst/webrtc/webrtc_fwd.h>
+
+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 <ulink url="https://www.w3.org/TR/webrtc/#rtcsessiondescription-class">https://www.w3.org/TR/webrtc/#rtcsessiondescription-class</ulink>
+ */
+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 000000000..edf6e201b
--- /dev/null
+++ b/gst-libs/gst/webrtc/rtpreceiver.c
@@ -0,0 +1,135 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:gstwebrtc-receiver
+ * @short_description: RTCRtpReceiver object
+ * @title: GstWebRTCRTPReceiver
+ * @see_also: #GstWebRTCRTPSender, #GstWebRTCRTPTransceiver
+ *
+ * <ulink url="https://www.w3.org/TR/webrtc/#rtcrtpreceiver-interface">https://www.w3.org/TR/webrtc/#rtcrtpreceiver-interface</ulink>
+ */
+
+#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 000000000..969c4de65
--- /dev/null
+++ b/gst-libs/gst/webrtc/rtpreceiver.h
@@ -0,0 +1,76 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_WEBRTC_RTP_RECEIVER_H__
+#define __GST_WEBRTC_RTP_RECEIVER_H__
+
+#include <gst/gst.h>
+#include <gst/webrtc/webrtc_fwd.h>
+#include <gst/webrtc/dtlstransport.h>
+
+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 000000000..b4dfe6ed8
--- /dev/null
+++ b/gst-libs/gst/webrtc/rtpsender.c
@@ -0,0 +1,141 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:gstwebrtc-sender
+ * @short_description: RTCRtpSender object
+ * @title: GstWebRTCRTPSender
+ * @see_also: #GstWebRTCRTPReceiver, #GstWebRTCRTPTransceiver
+ *
+ * <ulink url="https://www.w3.org/TR/webrtc/#rtcrtpsender-interface">https://www.w3.org/TR/webrtc/#rtcrtpsender-interface</ulink>
+ */
+
+#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 000000000..8308a0b44
--- /dev/null
+++ b/gst-libs/gst/webrtc/rtpsender.h
@@ -0,0 +1,77 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_WEBRTC_RTP_SENDER_H__
+#define __GST_WEBRTC_RTP_SENDER_H__
+
+#include <gst/gst.h>
+#include <gst/webrtc/webrtc_fwd.h>
+#include <gst/webrtc/dtlstransport.h>
+
+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 000000000..d0d9628d0
--- /dev/null
+++ b/gst-libs/gst/webrtc/rtptransceiver.c
@@ -0,0 +1,186 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:gstwebrtc-transceiver
+ * @short_description: RTCRtpTransceiver object
+ * @title: GstWebRTCRTPTransceiver
+ * @see_also: #GstWebRTCRTPSender, #GstWebRTCRTPReceiver
+ *
+ * <ulink url="https://www.w3.org/TR/webrtc/#rtcrtptransceiver-interface">https://www.w3.org/TR/webrtc/#rtcrtptransceiver-interface</ulink>
+ */
+
+#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 000000000..1bb819752
--- /dev/null
+++ b/gst-libs/gst/webrtc/rtptransceiver.h
@@ -0,0 +1,69 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_WEBRTC_RTP_TRANSCEIVER_H__
+#define __GST_WEBRTC_RTP_TRANSCEIVER_H__
+
+#include <gst/gst.h>
+#include <gst/webrtc/webrtc_fwd.h>
+#include <gst/webrtc/rtpsender.h>
+#include <gst/webrtc/rtpreceiver.h>
+
+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 000000000..354c15c19
--- /dev/null
+++ b/gst-libs/gst/webrtc/webrtc.h
@@ -0,0 +1,33 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_WEBRTC_WEBRTC_H__
+#define __GST_WEBRTC_WEBRTC_H__
+
+#include <gst/gst.h>
+#include <gst/webrtc/webrtc_fwd.h>
+#include <gst/webrtc/webrtc-enumtypes.h>
+#include <gst/webrtc/dtlstransport.h>
+#include <gst/webrtc/icetransport.h>
+#include <gst/webrtc/rtcsessiondescription.h>
+#include <gst/webrtc/rtpreceiver.h>
+#include <gst/webrtc/rtpsender.h>
+#include <gst/webrtc/rtptransceiver.h>
+
+#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 000000000..48c9bdab1
--- /dev/null
+++ b/gst-libs/gst/webrtc/webrtc_fwd.h
@@ -0,0 +1,251 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#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 <gst/gst.h>
+#include <gst/webrtc/webrtc-enumtypes.h>
+
+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 <ulink url="http://w3c.github.io/webrtc-pc/#dom-rtcicegatheringstate">http://w3c.github.io/webrtc-pc/#dom-rtcicegatheringstate</ulink>
+ */
+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 <ulink url="http://w3c.github.io/webrtc-pc/#dom-rtciceconnectionstate">http://w3c.github.io/webrtc-pc/#dom-rtciceconnectionstate</ulink>
+ */
+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 <ulink url="http://w3c.github.io/webrtc-pc/#dom-rtcsignalingstate">http://w3c.github.io/webrtc-pc/#dom-rtcsignalingstate</ulink>
+ */
+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 <ulink url="http://w3c.github.io/webrtc-pc/#dom-rtcpeerconnectionstate">http://w3c.github.io/webrtc-pc/#dom-rtcpeerconnectionstate</ulink>
+ */
+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 <ulink url="http://w3c.github.io/webrtc-pc/#rtcsdptype">http://w3c.github.io/webrtc-pc/#rtcsdptype</ulink>
+ */
+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 000000000..fb6c2cba6
--- /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 <gst/gst.h>\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 6598baf14..0e69cd1f8 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 efd014e97..a639d1066 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 000000000..3eec1e15f
--- /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 000000000..7371ad4b4
--- /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 01317cb2e..271f327f3 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 4969c4ad0..290ce08e5 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 1b1d44cc8..ef89c1455 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 000000000..b7f42e0f3
--- /dev/null
+++ b/tests/check/elements/webrtcbin.c
@@ -0,0 +1,1382 @@
+/* GStreamer
+ *
+ * Unit tests for webrtcbin
+ *
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gst.h>
+#include <gst/check/gstcheck.h>
+#include <gst/check/gstharness.h>
+#include <gst/webrtc/webrtc.h>
+
+#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 68a770504..3bcb0dd51 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 220254c6c..b14b0232f 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 6c3c4682e..e646d4b89 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 000000000..520942d7f
--- /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 000000000..7c2aab72e
--- /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 000000000..1e378ae4f
--- /dev/null
+++ b/tests/examples/webrtc/webrtc.c
@@ -0,0 +1,187 @@
+#include <gst/gst.h>
+#include <gst/sdp/sdp.h>
+#include <gst/webrtc/webrtc.h>
+
+#include <string.h>
+
+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 000000000..2b8bf113f
--- /dev/null
+++ b/tests/examples/webrtc/webrtcbidirectional.c
@@ -0,0 +1,197 @@
+#include <gst/gst.h>
+#include <gst/sdp/sdp.h>
+#include <gst/webrtc/webrtc.h>
+
+#include <string.h>
+
+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 000000000..02c507dc8
--- /dev/null
+++ b/tests/examples/webrtc/webrtcswap.c
@@ -0,0 +1,215 @@
+#include <gst/gst.h>
+#include <gst/sdp/sdp.h>
+#include <gst/webrtc/webrtc.h>
+
+#include <string.h>
+
+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;
+}