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