[Pkg-voip-commits] [janus] 03/282: New Janus plugin, NoSIP, for legacy interop without touching signalling
Jonas Smedegaard
dr at jones.dk
Wed Dec 20 21:53:21 UTC 2017
This is an automated email from the git hooks/post-receive script.
js pushed a commit to annotated tag debian/0.2.6-1
in repository janus.
commit 84c93711a6407c1a3488530796d23165a6c0d268
Author: Lorenzo Miniero <lminiero at gmail.com>
Date: Tue Feb 28 18:18:30 2017 +0100
New Janus plugin, NoSIP, for legacy interop without touching signalling
---
Makefile.am | 10 +
conf/janus.plugin.nosip.cfg.sample | 7 +
configure.ac | 10 +
html/demos.html | 20 +-
html/navbar.html | 3 +-
html/nosiptest.html | 102 ++
html/nosiptest.js | 399 +++++++
plugins/janus_nosip.c | 2213 ++++++++++++++++++++++++++++++++++++
8 files changed, 2755 insertions(+), 9 deletions(-)
diff --git a/Makefile.am b/Makefile.am
index ce581e6..0572de5 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -297,6 +297,16 @@ conf_DATA += conf/janus.plugin.sip.cfg.sample
EXTRA_DIST += conf/janus.plugin.sip.cfg.sample
endif
+if ENABLE_PLUGIN_NOSIP
+plugin_LTLIBRARIES += plugins/libjanus_nosip.la
+plugins_libjanus_nosip_la_SOURCES = plugins/janus_nosip.c
+plugins_libjanus_nosip_la_CFLAGS = $(plugins_cflags)
+plugins_libjanus_nosip_la_LDFLAGS = $(plugins_ldflags)
+plugins_libjanus_nosip_la_LIBADD = $(plugins_libadd)
+conf_DATA += conf/janus.plugin.nosip.cfg.sample
+EXTRA_DIST += conf/janus.plugin.nosip.cfg.sample
+endif
+
if ENABLE_PLUGIN_STREAMING
plugin_LTLIBRARIES += plugins/libjanus_streaming.la
plugins_libjanus_streaming_la_SOURCES = plugins/janus_streaming.c
diff --git a/conf/janus.plugin.nosip.cfg.sample b/conf/janus.plugin.nosip.cfg.sample
new file mode 100644
index 0000000..c4a85c7
--- /dev/null
+++ b/conf/janus.plugin.nosip.cfg.sample
@@ -0,0 +1,7 @@
+[general]
+; Specify which local IP address to use. If not set it will be automatically
+; guessed from the system
+;local_ip = 1.2.3.4
+
+; Whether events should be sent to event handlers (default is yes)
+;events = no
diff --git a/configure.ac b/configure.ac
index 4b89ca6..f1d1f2e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -403,6 +403,12 @@ AC_ARG_ENABLE([plugin-sip],
[],
[enable_plugin_sip=maybe])
+AC_ARG_ENABLE([plugin-nosip],
+ [AS_HELP_STRING([--disable-plugin-nosip],
+ [Disable nosip plugin])],
+ [],
+ [enable_plugin_nosip=yes])
+
AC_ARG_ENABLE([plugin-streaming],
[AS_HELP_STRING([--disable-plugin-streaming],
[Disable streaming plugin])],
@@ -476,6 +482,7 @@ AM_CONDITIONAL([ENABLE_PLUGIN_AUDIOBRIDGE], [test "x$enable_plugin_audiobridge"
AM_CONDITIONAL([ENABLE_PLUGIN_ECHOTEST], [test "x$enable_plugin_echotest" = "xyes"])
AM_CONDITIONAL([ENABLE_PLUGIN_RECORDPLAY], [test "x$enable_plugin_recordplay" = "xyes"])
AM_CONDITIONAL([ENABLE_PLUGIN_SIP], [test "x$enable_plugin_sip" = "xyes"])
+AM_CONDITIONAL([ENABLE_PLUGIN_NOSIP], [test "x$enable_plugin_nosip" = "xyes"])
AM_CONDITIONAL([ENABLE_PLUGIN_STREAMING], [test "x$enable_plugin_streaming" = "xyes"])
AM_CONDITIONAL([ENABLE_PLUGIN_VIDEOCALL], [test "x$enable_plugin_videocall" = "xyes"])
AM_CONDITIONAL([ENABLE_PLUGIN_VIDEOROOM], [test "x$enable_plugin_videoroom" = "xyes"])
@@ -584,6 +591,9 @@ AM_COND_IF([ENABLE_PLUGIN_VIDEOCALL],
AM_COND_IF([ENABLE_PLUGIN_SIP],
[echo " SIP Gateway: yes"],
[echo " SIP Gateway: no"])
+AM_COND_IF([ENABLE_PLUGIN_NOSIP],
+ [echo " NoSIP (RTP Bridge): yes"],
+ [echo " NoSIP (RTP Bridge): no"])
AM_COND_IF([ENABLE_PLUGIN_AUDIOBRIDGE],
[echo " Audio Bridge: yes"],
[echo " Audio Bridge: no"])
diff --git a/html/demos.html b/html/demos.html
index 1b9214e..6410022 100644
--- a/html/demos.html
+++ b/html/demos.html
@@ -32,11 +32,11 @@
<div class="page-header">
<h1>Janus WebRTC Gateway: Demo Tests</h1>
</div>
- <table class="table">
+ <table class="table table-striped">
<tr>
<td colspan=2><h3>Plugin demos</h3></td>
</tr>
- <tr class="active">
+ <tr>
<td><a href="echotest.html">Echo Test</a></td>
<td>A simple Echo Test demo, with knobs to control the bitrate.</td>
</tr>
@@ -44,7 +44,7 @@
<td><a href="streamingtest.html">Streaming</a></td>
<td>A media Streaming demo, with sample live and on-demand streams.</td>
</tr>
- <tr class="active">
+ <tr>
<td><a href="videocalltest.html">Video Call</a></td>
<td>A Video Call demo, a bit like AppRTC but with media passing through the gateway.</td>
</tr>
@@ -52,7 +52,11 @@
<td><a href="siptest.html">SIP Gateway</a></td>
<td>A SIP Gateway demo, allowing you to register at a SIP server and start/receive calls.</td>
</tr>
- <tr class="active">
+ <tr>
+ <td><a href="nosiptest.html">NoSIP (SDP/RTP)</a></td>
+ <td>A legacy interop demo (e.g., with a SIP peer) where signalling is up to the application.</td>
+ </tr>
+ <tr>
<td><a href="videoroomtest.html">Video Room</a></td>
<td>A videoconferencing demo, allowing you to join a video room with up to six users.</td>
</tr>
@@ -60,7 +64,7 @@
<td><a href="audiobridgetest.html">Audio Room</a></td>
<td>An audio mixing/bridge demo, allowing you join an Audio Room room.</td>
</tr>
- <tr class="active">
+ <tr>
<td><a href="textroomtest.html">Text Room</a></td>
<td>A text room demo, using DataChannels only.</td>
</tr>
@@ -68,7 +72,7 @@
<td><a href="voicemailtest.html">Voice Mail</a></td>
<td>A simple audio recorder demo, returning an .opus file after 10 seconds.</td>
</tr>
- <tr class="active">
+ <tr>
<td><a href="recordplaytest.html">Recorder/Playout</a></td>
<td>A demo to record audio/video messages, and subsequently replay them through WebRTC.</td>
</tr>
@@ -77,11 +81,11 @@
<td>A webinar-like screen sharing session, based on the Video Room plugin.</td>
</tr>
</table>
- <table class="table">
+ <table class="table table-striped">
<tr>
<td colspan=2><h3>Other demos</h3></td>
</tr>
- <tr class="active">
+ <tr>
<td><a href="devicetest.html">Device Selection</a></td>
<td>A variant of the Echo Test demo, that allows you to choose a specific capture device.</td>
</tr>
diff --git a/html/navbar.html b/html/navbar.html
index ee8a005..5c74003 100644
--- a/html/navbar.html
+++ b/html/navbar.html
@@ -18,8 +18,9 @@
<li><a href="streamingtest.html">Streaming</a></li>
<li><a href="videocalltest.html">Video Call</a></li>
<li><a href="siptest.html">SIP Gateway</a></li>
+ <li><a href="nosiptest.html">NoSIP (SDP/RTP)</a></li>
<li><a href="videoroomtest.html">Video Room</a></li>
- <li><a href="audiobridgetest.html">Audio Room</a></li>
+ <li><a href="audiobridgetest.html">Audio Bridge</a></li>
<li><a href="textroomtest.html">Text Room</a></li>
<li><a href="voicemailtest.html">Voice Mail</a></li>
<li><a href="recordplaytest.html">Recorder/Playout</a></li>
diff --git a/html/nosiptest.html b/html/nosiptest.html
new file mode 100644
index 0000000..cc2ecf3
--- /dev/null
+++ b/html/nosiptest.html
@@ -0,0 +1,102 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<title>Janus WebRTC Gateway: NoSIP (SDP/RTP)</title>
+<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/webrtc-adapter/3.1.5/adapter.min.js" ></script>
+<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.7.2/jquery.min.js" ></script>
+<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery.blockUI/2.70/jquery.blockUI.min.js" ></script>
+<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.0.2/js/bootstrap.min.js"></script>
+<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/4.1.0/bootbox.min.js"></script>
+<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/spin.js/2.3.2/spin.min.js"></script>
+<script type="text/javascript" src="janus.js" ></script>
+<script type="text/javascript" src="nosiptest.js"></script>
+<script>
+ $(function() {
+ $(".navbar-static-top").load("navbar.html", function() {
+ $(".navbar-static-top li.dropdown").addClass("active");
+ $(".navbar-static-top a[href='nosiptest.html']").parent().addClass("active");
+ });
+ $(".footer").load("footer.html");
+ });
+</script>
+<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootswatch/3.3.7/cerulean/bootstrap.min.css" type="text/css"/>
+<link rel="stylesheet" href="css/demo.css" type="text/css"/>
+<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.2/css/font-awesome.min.css" type="text/css"/>
+</head>
+<body>
+
+<a href="https://github.com/meetecho/janus-gateway"><img style="position: absolute; top: 0; left: 0; border: 0; z-index: 1001;" src="https://s3.amazonaws.com/github/ribbons/forkme_left_darkblue_121621.png" alt="Fork me on GitHub"></a>
+
+<nav class="navbar navbar-default navbar-static-top">
+</nav>
+
+<div class="container">
+ <div class="row">
+ <div class="col-md-12">
+ <div class="page-header">
+ <h1>Plugin Demo: NoSIP (SDP/RTP)
+ <button class="btn btn-default" autocomplete="off" id="start">Start</button>
+ </h1>
+ </div>
+ <div class="container" id="details">
+ <div class="row">
+ <div class="col-md-12">
+ <h3>Demo details</h3>
+ <p>This is a demo that complements the one <a href="siptest.html">showcasing the SIP plugin</a>. In
+ fact, while the SIP plugin allows you to not worry about SIP details, which are implemented within
+ the plugin itself, the NoSIP plugin doesn't mess with signalling itself, leaving it up to the
+ application. As such, it provided an alternative to those that still want to interact with a legacy
+ infrastructure (e.g., a pre-existing SIP-based one), but still want to be able to have control
+ on the signalling themselves, rather than completely delegating it to the SIP plugin.</p>
+ <p>All this plugin does, as a consequence, is taking care of the translation between WebRTC
+ empowered SDPs, and barebone SDPs that can be used with legacy peers. The barebone SDPs the
+ plugin generates are crafted so that media is handled by the plugin itself, thus implementing
+ the same RTP/RTCP gateway functionality the SIP plugin provides, but without the constraint
+ of the signalling. It is up to the appplication to transport a generated offer in whatever
+ signalling they want to use (e.g., SIP, IAX, XMPP, etc.) and make sure the offer/answer from
+ the peer is passed to the plugin, so that the session can be completed.</p>
+ <p>Considering this plugin is very much generic and signalling-agnostic, this demo does NOT
+ involve any signalling at all. On the contrary, it will show how a WebRTC peer can establish
+ a session with another WebRTC peer (for the sake of simplicity located in the same page)
+ by passing through the RTP/RTCP gatewaying functionality. This should as a result make it easier
+ for you to understand how a NoSIP caller and a NoSIP callee would need to be implemented. The
+ barebone SDPs generated/processed as a consequence will be displayed as a proof of concept.</p>
+ <p>Press the <code>Start</code> button above to launch the demo.</p>
+ </div>
+ </div>
+ </div>
+ <div class="container hide" id="videos">
+ <div class="row">
+ <div class="col-md-6">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title">Caller</h3>
+ </div>
+ <div class="panel-body" id="videoleft"></div>
+ </div>
+ <pre id="localsdp"></pre>
+ </div>
+ <div class="col-md-6">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title">Callee</h3>
+ </div>
+ <div class="panel-body" id="videoright"></div>
+ </div>
+ <pre id="remotesdp"></pre>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <hr>
+ <div class="footer">
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/html/nosiptest.js b/html/nosiptest.js
new file mode 100644
index 0000000..dcedd16
--- /dev/null
+++ b/html/nosiptest.js
@@ -0,0 +1,399 @@
+// We make use of this 'server' variable to provide the address of the
+// REST Janus API. By default, in this example we assume that Janus is
+// co-located with the web server hosting the HTML pages but listening
+// on a different port (8088, the default for HTTP in Janus), which is
+// why we make use of the 'window.location.hostname' base address. Since
+// Janus can also do HTTPS, and considering we don't really want to make
+// use of HTTP for Janus if your demos are served on HTTPS, we also rely
+// on the 'window.location.protocol' prefix to build the variable, in
+// particular to also change the port used to contact Janus (8088 for
+// HTTP and 8089 for HTTPS, if enabled).
+// In case you place Janus behind an Apache frontend (as we did on the
+// online demos at http://janus.conf.meetecho.com) you can just use a
+// relative path for the variable, e.g.:
+//
+// var server = "/janus";
+//
+// which will take care of this on its own.
+//
+//
+// If you want to use the WebSockets frontend to Janus, instead, you'll
+// have to pass a different kind of address, e.g.:
+//
+// var server = "ws://" + window.location.hostname + ":8188";
+//
+// Of course this assumes that support for WebSockets has been built in
+// when compiling the gateway. WebSockets support has not been tested
+// as much as the REST API, so handle with care!
+//
+//
+// If you have multiple options available, and want to let the library
+// autodetect the best way to contact your gateway (or pool of gateways),
+// you can also pass an array of servers, e.g., to provide alternative
+// means of access (e.g., try WebSockets first and, if that fails, fall
+// back to plain HTTP) or just have failover servers:
+//
+// var server = [
+// "ws://" + window.location.hostname + ":8188",
+// "/janus"
+// ];
+//
+// This will tell the library to try connecting to each of the servers
+// in the presented order. The first working server will be used for
+// the whole session.
+//
+var server = null;
+if(window.location.protocol === 'http:')
+ server = "http://" + window.location.hostname + ":8088/janus";
+else
+ server = "https://" + window.location.hostname + ":8089/janus";
+
+var janus = null;
+
+// We'll need two handles for this demo: a caller and a callee
+var caller = null, callee = null;
+var opaqueId = Janus.randomString(12);
+
+var started = false;
+var spinner = null;
+
+$(document).ready(function() {
+ // Initialize the library (all console debuggers enabled)
+ Janus.init({debug: "all", callback: function() {
+ // Use a button to start the demo
+ $('#start').click(function() {
+ if(started)
+ return;
+ started = true;
+ $(this).attr('disabled', true).unbind('click');
+ // Make sure the browser supports WebRTC
+ if(!Janus.isWebrtcSupported()) {
+ bootbox.alert("No WebRTC support... ");
+ return;
+ }
+ // Create session
+ janus = new Janus(
+ {
+ server: server,
+ success: function() {
+ // Attach to nosip plugin as a caller
+ janus.attach(
+ {
+ plugin: "janus.plugin.nosip",
+ opaqueId: "nosiptest-caller-"+opaqueId,
+ success: function(pluginHandle) {
+ $('#details').remove();
+ caller = pluginHandle;
+ Janus.log("[caller] Plugin attached! (" + caller.getPlugin() + ", id=" + caller.getId() + ")");
+ $('#start').removeAttr('disabled').html("Stop")
+ .click(function() {
+ $(this).attr('disabled', true);
+ janus.destroy();
+ });
+ // Negotiate WebRTC in a second (just to make sure both caller and callee handles exist)
+ setTimeout(function() {
+ Janus.debug("[caller] Trying a createOffer too (audio/video sendrecv)");
+ caller.createOffer(
+ {
+ // No media provided: by default, it's sendrecv for audio and video
+ success: function(jsep) {
+ Janus.debug("[caller] Got SDP!");
+ Janus.debug(jsep);
+ // We now have a WebRTC SDP: to get a barebone SDP legacy
+ // peers can digest, we ask the NoSIP plugin to generate
+ // an offer for us. For the sake of simplicity, no SRTP:
+ // if you need SRTP support, you can use the same syntax
+ // the SIP plugin uses (mandatory vs. optional). We'll
+ // get the result in an event called "generated" here.
+ var body = {
+ request: "generate",
+ info: "something-meaningful-about-the-caller"
+ };
+ caller.send({message: body, jsep: jsep});
+ },
+ error: function(error) {
+ Janus.error("WebRTC error:", error);
+ bootbox.alert("WebRTC error... " + JSON.stringify(error));
+ }
+ });
+ }, 1000);
+ },
+ error: function(error) {
+ console.error("[caller] -- Error attaching plugin...", error);
+ bootbox.alert("[caller] Error attaching plugin... " + error);
+ },
+ consentDialog: function(on) {
+ Janus.debug("[caller] Consent dialog should be " + (on ? "on" : "off") + " now");
+ if(on) {
+ // Darken screen and show hint
+ $.blockUI({
+ message: '<div><img src="up_arrow.png"/></div>',
+ css: {
+ border: 'none',
+ padding: '15px',
+ backgroundColor: 'transparent',
+ color: '#aaa',
+ top: '10px',
+ left: (navigator.mozGetUserMedia ? '-100px' : '300px')
+ } });
+ } else {
+ // Restore screen
+ $.unblockUI();
+ }
+ },
+ iceState: function(state) {
+ Janus.log("[caller] ICE state changed to " + state);
+ },
+ mediaState: function(medium, on) {
+ Janus.log("[caller] Janus " + (on ? "started" : "stopped") + " receiving our " + medium);
+ },
+ webrtcState: function(on) {
+ Janus.log("[caller] Janus says our WebRTC PeerConnection is " + (on ? "up" : "down") + " now");
+ $("#videoleft").parent().unblock();
+ },
+ slowLink: function(uplink, nacks) {
+ Janus.warn("[caller] Janus reports problems " + (uplink ? "sending" : "receiving") +
+ " packets on this PeerConnection (" + nacks + " NACKs/s " + (uplink ? "received" : "sent") + ")");
+ },
+ onmessage: function(msg, jsep) {
+ Janus.debug("[caller] ::: Got a message :::");
+ Janus.debug(JSON.stringify(msg));
+ // Any error?
+ var error = msg["error"];
+ if(error) {
+ bootbox.alert(error);
+ caller.hangup();
+ return;
+ }
+ var result = msg["result"];
+ if(result) {
+ var event = result["event"];
+ if(event === "generated") {
+ // We got the barebone SDP offer we wanted, let's have
+ // the callee handle it as if it arrived via signalling
+ var sdp = result["sdp"];
+ $('#localsdp').text(
+ "[" + result["type"] + "][" + result["info"] + "]\n" + sdp);
+ // This will result in a "processed" event on the callee handle
+ var processOffer = {
+ request: "process",
+ info: "something-meaningful-about-the-callee",
+ type: result["type"],
+ sdp: result["sdp"]
+ }
+ callee.send({message: processOffer});
+ } else if(event === "processed") {
+ // As a caller, this means the remote, barebone SDP answer
+ // we got from the legacy peer has been turned into a full
+ // WebRTC SDP answer we can consume here, let's do that
+ if(jsep) {
+ Janus.debug("[caller] Handling SDP as well...");
+ Janus.debug(jsep);
+ caller.handleRemoteJsep({jsep: jsep});
+ }
+ }
+ }
+ },
+ onlocalstream: function(stream) {
+ Janus.debug("[caller] ::: Got a local stream :::");
+ Janus.debug(JSON.stringify(stream));
+ if($('#myvideo').length === 0) {
+ $('#videos').removeClass('hide').show();
+ $('#videoleft').append('<video class="rounded centered" id="myvideo" width=320 height=240 autoplay muted="muted"/>');
+ }
+ Janus.attachMediaStream($('#myvideo').get(0), stream);
+ $("#myvideo").get(0).muted = "muted";
+ $("#videoleft").parent().block({
+ message: '<b>Publishing...</b>',
+ css: {
+ border: 'none',
+ backgroundColor: 'transparent',
+ color: 'white'
+ }
+ });
+ // No remote video yet
+ $('#videoright').append('<video class="rounded centered" id="waitingvideo" width=320 height=240 />');
+ if(spinner == null) {
+ var target = document.getElementById('videoright');
+ spinner = new Spinner({top:100}).spin(target);
+ } else {
+ spinner.spin();
+ }
+ var videoTracks = stream.getVideoTracks();
+ if(videoTracks === null || videoTracks === undefined || videoTracks.length === 0) {
+ // No webcam
+ $('#myvideo').hide();
+ $('#videoleft').append(
+ '<div class="no-video-container">' +
+ '<i class="fa fa-video-camera fa-5 no-video-icon"></i>' +
+ '<span class="no-video-text">No webcam available</span>' +
+ '</div>');
+ }
+ },
+ onremotestream: function(stream) {
+ Janus.debug("[caller] ::: Got a remote stream :::");
+ Janus.debug(JSON.stringify(stream));
+ if($('#peervideo').length === 0) {
+ $('#videos').removeClass('hide').show();
+ $('#videoright').append('<video class="rounded centered hide" id="peervideo" width=320 height=240 autoplay/>');
+ // Show the video and hide the spinner
+ $("#peervideo").bind("playing", function () {
+ $('#waitingvideo').remove();
+ $('#peervideo').removeClass('hide');
+ if(spinner !== null && spinner !== undefined)
+ spinner.stop();
+ spinner = null;
+ });
+ }
+ Janus.attachMediaStream($('#peervideo').get(0), stream);
+ var videoTracks = stream.getVideoTracks();
+ if(videoTracks === null || videoTracks === undefined || videoTracks.length === 0 || videoTracks[0].muted) {
+ // No remote video
+ $('#peervideo').hide();
+ $('#videoright').append(
+ '<div class="no-video-container">' +
+ '<i class="fa fa-video-camera fa-5 no-video-icon"></i>' +
+ '<span class="no-video-text">No remote video available</span>' +
+ '</div>');
+ }
+ },
+ oncleanup: function() {
+ Janus.log("[caller] ::: Got a cleanup notification :::");
+ if(spinner !== null && spinner !== undefined)
+ spinner.stop();
+ spinner = null;
+ $('#myvideo').remove();
+ $('#waitingvideo').remove();
+ $("#videoleft").parent().unblock();
+ $('#peervideo').remove();
+ }
+ });
+ // Attach to nosip plugin as a callee
+ janus.attach(
+ {
+ plugin: "janus.plugin.nosip",
+ opaqueId: "nosiptest-callee-"+opaqueId,
+ success: function(pluginHandle) {
+ callee = pluginHandle;
+ Janus.log("[callee] Plugin attached! (" + callee.getPlugin() + ", id=" + callee.getId() + ")");
+ },
+ error: function(error) {
+ console.error("[callee] -- Error attaching plugin...", error);
+ bootbox.alert("[callee] Error attaching plugin... " + error);
+ },
+ consentDialog: function(on) {
+ Janus.debug("[callee] Consent dialog should be " + (on ? "on" : "off") + " now");
+ if(on) {
+ // Darken screen and show hint
+ $.blockUI({
+ message: '<div><img src="up_arrow.png"/></div>',
+ css: {
+ border: 'none',
+ padding: '15px',
+ backgroundColor: 'transparent',
+ color: '#aaa',
+ top: '10px',
+ left: (navigator.mozGetUserMedia ? '-100px' : '300px')
+ } });
+ } else {
+ // Restore screen
+ $.unblockUI();
+ }
+ },
+ iceState: function(state) {
+ Janus.log("[callee] ICE state changed to " + state);
+ },
+ mediaState: function(medium, on) {
+ Janus.log("[callee] Janus " + (on ? "started" : "stopped") + " receiving our " + medium);
+ },
+ webrtcState: function(on) {
+ Janus.log("[callee] Janus says our WebRTC PeerConnection is " + (on ? "up" : "down") + " now");
+ $("#videoleft").parent().unblock();
+ },
+ slowLink: function(uplink, nacks) {
+ Janus.warn("[callee] Janus reports problems " + (uplink ? "sending" : "receiving") +
+ " packets on this PeerConnection (" + nacks + " NACKs/s " + (uplink ? "received" : "sent") + ")");
+ },
+ onmessage: function(msg, jsep) {
+ Janus.debug("[callee] ::: Got a message :::");
+ Janus.debug(JSON.stringify(msg));
+ // Any error?
+ var error = msg["error"];
+ if(error) {
+ bootbox.alert(error);
+ callee.hangup();
+ return;
+ }
+ var result = msg["result"];
+ if(result) {
+ var event = result["event"];
+ if(event === "processed") {
+ // Since we're a callee, this means that the barebone SDP offer
+ // the caller gave us (and that we assumed had been sent via
+ // signalling)has been processed, and we got a JSEP SDP to process:
+ // we need to come up with our own answer now, so let's do that
+ Janus.debug("[callee] Trying a createAnswer too (audio/video sendrecv)");
+ callee.createAnswer(
+ {
+ // This is the WebRTC enriched offer the plugin gave us
+ jsep: jsep,
+ // No media provided: by default, it's sendrecv for audio and video
+ success: function(jsep) {
+ Janus.debug("[callee] Got SDP!");
+ Janus.debug(jsep);
+ // We now have a WebRTC SDP: to get a barebone SDP legacy
+ // peers can digest, we ask the NoSIP plugin to generate
+ // an answer for us, just as we did for the caller's offer.
+ // We'll get the result in an event called "generated" here.
+ var body = {
+ request: "generate"
+ };
+ callee.send({message: body, jsep: jsep});
+ },
+ error: function(error) {
+ Janus.error("WebRTC error:", error);
+ bootbox.alert("WebRTC error... " + JSON.stringify(error));
+ }
+ });
+
+ } else if(event === "generated") {
+ // As a callee, we get this when our barebone answer has been
+ // generated from the original JSEP answer. Let's have
+ // the caller handle it as if it arrived via signalling
+ var sdp = result["sdp"];
+ $('#remotesdp').text(
+ "[" + result["type"] + "][" + result["info"] + "]\n" + sdp);
+ // This will result in a "processed" event on the caller handle
+ var processAnswer = {
+ request: "process",
+ type: result["type"],
+ sdp: result["sdp"]
+ }
+ caller.send({message: processAnswer});
+ }
+ }
+ },
+ onlocalstream: function(stream) {
+ // The callee is our fake peer, we don't display anything
+ },
+ onremotestream: function(stream) {
+ // The callee is our fake peer, we don't display anything
+ },
+ oncleanup: function() {
+ Janus.log("[callee] ::: Got a cleanup notification :::");
+ }
+ });
+ },
+ error: function(error) {
+ Janus.error(error);
+ bootbox.alert(error, function() {
+ window.location.reload();
+ });
+ },
+ destroyed: function() {
+ window.location.reload();
+ }
+ });
+ });
+ }});
+});
diff --git a/plugins/janus_nosip.c b/plugins/janus_nosip.c
new file mode 100644
index 0000000..634aaea
--- /dev/null
+++ b/plugins/janus_nosip.c
@@ -0,0 +1,2213 @@
+/*! \file janus_nosip.c
+ * \author Lorenzo Miniero <lorenzo at meetecho.com>
+ * \copyright GNU General Public License v3
+ * \brief Janus NoSIP plugin
+ * \details
+ *
+ * This is quite a basic plugin, as it only takes care of acting as an
+ * RTP bridge. It is named "NoSIP" since, as the name suggests, signalling
+ * takes no place here, and is entirely up to the application. The typical
+ * usage of this application is something like this:
+ *
+ * 1. a WebRTC application handles signalling on its own (e.g., SIP), but
+ * needs to interact with a peer that doesn't support WebRTC (DTLS/ICE);
+ * 2. it creates a handle with the NoSIP plugin, creates a JSEP SDP offer,
+ * and passes it to the plugin;
+ * 3. the plugin creates a barebone SDP that can be used to communicate
+ * with the legacy peer, binds to the ports for RTP/RTCP, and sends this
+ * plain SDP back to the application;
+ * 4. the application uses this barebone SDP in its signalling, and expects
+ * an answer from the peer;
+ * 5. the SDP answer from the peer will be barebone as well, and so unfit
+ * for WebRTC usage; as such, the application passes it to the plugin as
+ * the answer to match the offer created before;
+ * 6. the plugin matches the answer to the offer, and starts exchanging
+ * RTP/RTCP with the legacy peer: media coming from the peer is relayed
+ * via WebRTC to the application, and WebRTC stuff coming from the application
+ * is relayed via plain RTP/RTCP to the legacy peer.
+ *
+ * The same behaviour can be followed if the application is the callee
+ * instead, with the only difference being that the barebone offer will
+ * come from the peer in this case, and the application will ask the
+ * NoSIP plugin for a barebone answer instead.
+ *
+ * As you can see, the behaviour is pretty much the same as the SIP plugin,
+ * with the key difference being that in this case there's no SIP stack in
+ * the plugin itself. All signalling is left to the application, and Janus
+ * (via the NoSIP plugin) is only responsible for bridging the media. This
+ * might be more appropriate than the SIP plugin in cases where developers
+ * want to keep control on the signalling layer, while still involving a
+ * gateway of sorts. Of course, SIP is just an example here: other signalling
+ * protocols may be involved as well (e.g., IAX, XMPP, others). The NoSIP
+ * plugin, though, will generate and expect plain SDP, so you'll need to
+ * take care of any adaptation that may be needed to make this work with
+ * the signalling protocol of your choice.
+ *
+ * Actual API docs: TBD.
+ *
+ * \ingroup plugins
+ * \ref plugins
+ */
+
+#include "plugin.h"
+
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <poll.h>
+
+#include <jansson.h>
+
+#include "../debug.h"
+#include "../apierror.h"
+#include "../config.h"
+#include "../mutex.h"
+#include "../record.h"
+#include "../rtp.h"
+#include "../rtcp.h"
+#include "../sdp-utils.h"
+#include "../utils.h"
+
+#ifdef HAVE_SRTP_2
+#include <srtp2/srtp.h>
+#include <openssl/rand.h>
+#include <openssl/err.h>
+static int srtp_crypto_get_random(uint8_t *key, int len) {
+ /* libsrtp 2.0 doesn't have crypto_get_random, we use OpenSSL's RAND_* to replace it:
+ * https://wiki.openssl.org/index.php/Random_Numbers */
+ int rc = RAND_bytes(key, len);
+ if(rc != 1) {
+ /* Error generating */
+ JANUS_LOG(LOG_ERR, "RAND_bytes failes: %s\n", ERR_reason_error_string(ERR_get_error()));
+ return -1;
+ }
+ return 0;
+}
+#else
+#include <srtp/srtp.h>
+#include <srtp/crypto_kernel.h>
+#define srtp_err_status_t err_status_t
+#define srtp_err_status_ok err_status_ok
+#define srtp_err_status_replay_fail err_status_replay_fail
+#define srtp_err_status_replay_old err_status_replay_old
+#define srtp_crypto_policy_set_rtp_default crypto_policy_set_rtp_default
+#define srtp_crypto_policy_set_rtcp_default crypto_policy_set_rtcp_default
+#define srtp_crypto_policy_set_aes_cm_128_hmac_sha1_32 crypto_policy_set_aes_cm_128_hmac_sha1_32
+#define srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80 crypto_policy_set_aes_cm_128_hmac_sha1_80
+#define srtp_crypto_get_random crypto_get_random
+#endif
+
+
+/* Plugin information */
+#define JANUS_NOSIP_VERSION 1
+#define JANUS_NOSIP_VERSION_STRING "0.0.1"
+#define JANUS_NOSIP_DESCRIPTION "This is a simple RTP bridging plugin that leaves signalling details (e.g., SIP) up to the application."
+#define JANUS_NOSIP_NAME "JANUS NoSIP plugin"
+#define JANUS_NOSIP_AUTHOR "Meetecho s.r.l."
+#define JANUS_NOSIP_PACKAGE "janus.plugin.nosip"
+
+/* Plugin methods */
+janus_plugin *create(void);
+int janus_nosip_init(janus_callbacks *callback, const char *config_path);
+void janus_nosip_destroy(void);
+int janus_nosip_get_api_compatibility(void);
+int janus_nosip_get_version(void);
+const char *janus_nosip_get_version_string(void);
+const char *janus_nosip_get_description(void);
+const char *janus_nosip_get_name(void);
+const char *janus_nosip_get_author(void);
+const char *janus_nosip_get_package(void);
+void janus_nosip_create_session(janus_plugin_session *handle, int *error);
+struct janus_plugin_result *janus_nosip_handle_message(janus_plugin_session *handle, char *transaction, json_t *message, json_t *jsep);
+void janus_nosip_setup_media(janus_plugin_session *handle);
+void janus_nosip_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len);
+void janus_nosip_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len);
+void janus_nosip_hangup_media(janus_plugin_session *handle);
+void janus_nosip_destroy_session(janus_plugin_session *handle, int *error);
+json_t *janus_nosip_query_session(janus_plugin_session *handle);
+
+/* Plugin setup */
+static janus_plugin janus_nosip_plugin =
+ JANUS_PLUGIN_INIT (
+ .init = janus_nosip_init,
+ .destroy = janus_nosip_destroy,
+
+ .get_api_compatibility = janus_nosip_get_api_compatibility,
+ .get_version = janus_nosip_get_version,
+ .get_version_string = janus_nosip_get_version_string,
+ .get_description = janus_nosip_get_description,
+ .get_name = janus_nosip_get_name,
+ .get_author = janus_nosip_get_author,
+ .get_package = janus_nosip_get_package,
+
+ .create_session = janus_nosip_create_session,
+ .handle_message = janus_nosip_handle_message,
+ .setup_media = janus_nosip_setup_media,
+ .incoming_rtp = janus_nosip_incoming_rtp,
+ .incoming_rtcp = janus_nosip_incoming_rtcp,
+ .hangup_media = janus_nosip_hangup_media,
+ .destroy_session = janus_nosip_destroy_session,
+ .query_session = janus_nosip_query_session,
+ );
+
+/* Plugin creator */
+janus_plugin *create(void) {
+ JANUS_LOG(LOG_VERB, "%s created!\n", JANUS_NOSIP_NAME);
+ return &janus_nosip_plugin;
+}
+
+/* Parameter validation */
+static struct janus_json_parameter request_parameters[] = {
+ {"request", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
+};
+static struct janus_json_parameter generate_parameters[] = {
+ {"info", JSON_STRING, 0},
+ {"srtp", JSON_STRING, 0}
+};
+static struct janus_json_parameter process_parameters[] = {
+ {"type", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
+ {"sdp", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
+ {"info", JSON_STRING, 0},
+ {"srtp", JSON_STRING, 0}
+};
+static struct janus_json_parameter recording_parameters[] = {
+ {"action", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
+ {"audio", JANUS_JSON_BOOL, 0},
+ {"video", JANUS_JSON_BOOL, 0},
+ {"peer_audio", JANUS_JSON_BOOL, 0},
+ {"peer_video", JANUS_JSON_BOOL, 0},
+ {"filename", JSON_STRING, 0}
+};
+
+/* Useful stuff */
+static volatile gint initialized = 0, stopping = 0;
+static gboolean notify_events = TRUE;
+static janus_callbacks *gateway = NULL;
+
+static char local_ip[INET6_ADDRSTRLEN];
+
+static GThread *handler_thread;
+static GThread *watchdog;
+static void *janus_nosip_handler(void *data);
+
+typedef struct janus_nosip_message {
+ janus_plugin_session *handle;
+ char *transaction;
+ json_t *message;
+ json_t *jsep;
+} janus_nosip_message;
+static GAsyncQueue *messages = NULL;
+static janus_nosip_message exit_message;
+
+static void janus_nosip_message_free(janus_nosip_message *msg) {
+ if(!msg || msg == &exit_message)
+ return;
+
+ msg->handle = NULL;
+
+ g_free(msg->transaction);
+ msg->transaction = NULL;
+ if(msg->message)
+ json_decref(msg->message);
+ msg->message = NULL;
+ if(msg->jsep)
+ json_decref(msg->jsep);
+ msg->jsep = NULL;
+
+ g_free(msg);
+}
+
+
+typedef struct janus_nosip_rtp_context {
+ /* Needed to fix seq and ts in case RTP sources change */
+ uint32_t a_last_ssrc, a_last_ts, a_base_ts, a_base_ts_prev,
+ v_last_ssrc, v_last_ts, v_base_ts, v_base_ts_prev;
+ uint16_t a_last_seq, a_base_seq, a_base_seq_prev,
+ v_last_seq, v_base_seq, v_base_seq_prev;
+} janus_nosip_rtp_context;
+
+typedef struct janus_nosip_media {
+ char *remote_ip;
+ int ready:1;
+ gboolean autoack;
+ gboolean require_srtp, has_srtp_local, has_srtp_remote;
+ int has_audio:1;
+ int audio_rtp_fd, audio_rtcp_fd;
+ int local_audio_rtp_port, remote_audio_rtp_port;
+ int local_audio_rtcp_port, remote_audio_rtcp_port;
+ guint32 audio_ssrc, audio_ssrc_peer;
+ int audio_pt;
+ const char *audio_pt_name;
+ srtp_t audio_srtp_in, audio_srtp_out;
+ srtp_policy_t audio_remote_policy, audio_local_policy;
+ int audio_srtp_suite_in, audio_srtp_suite_out;
+ gboolean audio_send;
+ int has_video:1;
+ int video_rtp_fd, video_rtcp_fd;
+ int local_video_rtp_port, remote_video_rtp_port;
+ int local_video_rtcp_port, remote_video_rtcp_port;
+ guint32 video_ssrc, video_ssrc_peer;
+ int video_pt;
+ const char *video_pt_name;
+ srtp_t video_srtp_in, video_srtp_out;
+ srtp_policy_t video_remote_policy, video_local_policy;
+ int video_srtp_suite_in, video_srtp_suite_out;
+ gboolean video_send;
+ janus_nosip_rtp_context context;
+ int pipefd[2];
+ gboolean updated;
+} janus_nosip_media;
+
+typedef struct janus_nosip_session {
+ janus_plugin_session *handle;
+ char *info; /* Opaque identifier that the application can set */
+ janus_nosip_media media; /* Media gatewaying stuff (same stuff as the SIP plugin) */
+ janus_sdp *sdp; /* The SDP this user sent */
+ janus_recorder *arc; /* The Janus recorder instance for this user's audio, if enabled */
+ janus_recorder *arc_peer; /* The Janus recorder instance for the peer's audio, if enabled */
+ janus_recorder *vrc; /* The Janus recorder instance for this user's video, if enabled */
+ janus_recorder *vrc_peer; /* The Janus recorder instance for the peer's video, if enabled */
+ janus_mutex rec_mutex; /* Mutex to protect the recorders from race conditions */
+ volatile gint hangingup;
+ gint64 destroyed; /* Time at which this session was marked as destroyed */
+ janus_mutex mutex;
+} janus_nosip_session;
+static GHashTable *sessions;
+static GList *old_sessions;
+static janus_mutex sessions_mutex;
+
+
+/* SRTP stuff (in case we need SDES) */
+#define SRTP_MASTER_KEY_LENGTH 16
+#define SRTP_MASTER_SALT_LENGTH 14
+#define SRTP_MASTER_LENGTH (SRTP_MASTER_KEY_LENGTH + SRTP_MASTER_SALT_LENGTH)
+static const char *janus_nosip_srtp_error[] =
+{
+ "srtp_err_status_ok",
+ "srtp_err_status_fail",
+ "srtp_err_status_bad_param",
+ "srtp_err_status_alloc_fail",
+ "srtp_err_status_dealloc_fail",
+ "srtp_err_status_init_fail",
+ "srtp_err_status_terminus",
+ "srtp_err_status_auth_fail",
+ "srtp_err_status_cipher_fail",
+ "srtp_err_status_replay_fail",
+ "srtp_err_status_replay_old",
+ "srtp_err_status_algo_fail",
+ "srtp_err_status_no_such_op",
+ "srtp_err_status_no_ctx",
+ "srtp_err_status_cant_check",
+ "srtp_err_status_key_expired",
+ "srtp_err_status_socket_err",
+ "srtp_err_status_signal_err",
+ "srtp_err_status_nonce_bad",
+ "srtp_err_status_read_fail",
+ "srtp_err_status_write_fail",
+ "srtp_err_status_parse_err",
+ "srtp_err_status_encode_err",
+ "srtp_err_status_semaphore_err",
+ "srtp_err_status_pfkey_err",
+};
+static const gchar *janus_nosip_get_srtp_error(int error) {
+ if(error < 0 || error > 24)
+ return NULL;
+ return janus_nosip_srtp_error[error];
+}
+static int janus_nosip_srtp_set_local(janus_nosip_session *session, gboolean video, char **crypto) {
+ if(session == NULL)
+ return -1;
+ /* Generate key/salt */
+ uint8_t *key = g_malloc0(SRTP_MASTER_LENGTH);
+ srtp_crypto_get_random(key, SRTP_MASTER_LENGTH);
+ /* Set SRTP policies */
+ srtp_policy_t *policy = video ? &session->media.video_local_policy : &session->media.audio_local_policy;
+ srtp_crypto_policy_set_rtp_default(&(policy->rtp));
+ srtp_crypto_policy_set_rtcp_default(&(policy->rtcp));
+ policy->ssrc.type = ssrc_any_inbound;
+ policy->key = key;
+ policy->next = NULL;
+ /* Create SRTP context */
+ srtp_err_status_t res = srtp_create(video ? &session->media.video_srtp_out : &session->media.audio_srtp_out, policy);
+ if(res != srtp_err_status_ok) {
+ /* Something went wrong... */
+ JANUS_LOG(LOG_ERR, "Oops, error creating outbound SRTP session: %d (%s)\n", res, janus_nosip_get_srtp_error(res));
+ g_free(key);
+ policy->key = NULL;
+ return -2;
+ }
+ /* Base64 encode the salt */
+ *crypto = g_base64_encode(key, SRTP_MASTER_LENGTH);
+ if((video && session->media.video_srtp_out) || (!video && session->media.audio_srtp_out)) {
+ JANUS_LOG(LOG_VERB, "%s outbound SRTP session created\n", video ? "Video" : "Audio");
+ }
+ return 0;
+}
+static int janus_nosip_srtp_set_remote(janus_nosip_session *session, gboolean video, const char *crypto, int suite) {
+ if(session == NULL || crypto == NULL)
+ return -1;
+ /* Base64 decode the crypto string and set it as the remote SRTP context */
+ gsize len = 0;
+ guchar *decoded = g_base64_decode(crypto, &len);
+ if(len < SRTP_MASTER_LENGTH) {
+ /* FIXME Can this happen? */
+ g_free(decoded);
+ return -2;
+ }
+ /* Set SRTP policies */
+ srtp_policy_t *policy = video ? &session->media.video_remote_policy : &session->media.audio_remote_policy;
+ srtp_crypto_policy_set_rtp_default(&(policy->rtp));
+ srtp_crypto_policy_set_rtcp_default(&(policy->rtcp));
+ if(suite == 32) {
+ srtp_crypto_policy_set_aes_cm_128_hmac_sha1_32(&(policy->rtp));
+ srtp_crypto_policy_set_aes_cm_128_hmac_sha1_32(&(policy->rtcp));
+ } else if(suite == 80) {
+ srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&(policy->rtp));
+ srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&(policy->rtcp));
+ }
+ policy->ssrc.type = ssrc_any_inbound;
+ policy->key = decoded;
+ policy->next = NULL;
+ /* Create SRTP context */
+ srtp_err_status_t res = srtp_create(video ? &session->media.video_srtp_in : &session->media.audio_srtp_in, policy);
+ if(res != srtp_err_status_ok) {
+ /* Something went wrong... */
+ JANUS_LOG(LOG_ERR, "Oops, error creating inbound SRTP session: %d (%s)\n", res, janus_nosip_get_srtp_error(res));
+ g_free(decoded);
+ policy->key = NULL;
+ return -2;
+ }
+ if((video && session->media.video_srtp_in) || (!video && session->media.audio_srtp_in)) {
+ JANUS_LOG(LOG_VERB, "%s inbound SRTP session created\n", video ? "Video" : "Audio");
+ }
+ return 0;
+}
+static void janus_nosip_srtp_cleanup(janus_nosip_session *session) {
+ if(session == NULL)
+ return;
+ session->media.autoack = TRUE;
+ session->media.require_srtp = FALSE;
+ session->media.has_srtp_local = FALSE;
+ session->media.has_srtp_remote = FALSE;
+ /* Audio */
+ if(session->media.audio_srtp_out)
+ srtp_dealloc(session->media.audio_srtp_out);
+ session->media.audio_srtp_out = NULL;
+ g_free(session->media.audio_local_policy.key);
+ session->media.audio_local_policy.key = NULL;
+ session->media.audio_srtp_suite_out = 0;
+ if(session->media.audio_srtp_in)
+ srtp_dealloc(session->media.audio_srtp_in);
+ session->media.audio_srtp_in = NULL;
+ g_free(session->media.audio_remote_policy.key);
+ session->media.audio_remote_policy.key = NULL;
+ session->media.audio_srtp_suite_in = 0;
+ /* Video */
+ if(session->media.video_srtp_out)
+ srtp_dealloc(session->media.video_srtp_out);
+ session->media.video_srtp_out = NULL;
+ g_free(session->media.video_local_policy.key);
+ session->media.video_local_policy.key = NULL;
+ session->media.video_srtp_suite_out = 0;
+ if(session->media.video_srtp_in)
+ srtp_dealloc(session->media.video_srtp_in);
+ session->media.video_srtp_in = NULL;
+ g_free(session->media.video_remote_policy.key);
+ session->media.video_remote_policy.key = NULL;
+ session->media.video_srtp_suite_in = 0;
+}
+
+
+/* SDP parsing and manipulation */
+void janus_nosip_sdp_process(janus_nosip_session *session, janus_sdp *sdp, gboolean answer, gboolean update, gboolean *changed);
+char *janus_nosip_sdp_manipulate(janus_nosip_session *session, janus_sdp *sdp, gboolean answer);
+/* Media */
+static int janus_nosip_allocate_local_ports(janus_nosip_session *session);
+static void *janus_nosip_relay_thread(void *data);
+
+
+/* Random string helper (in case user doesn't provide opaque info) */
+static char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+static void janus_nosip_random_string(int length, char *buffer) {
+ if(length > 0 && buffer) {
+ int l = (int)(sizeof(charset)-1);
+ int i=0;
+ for(i=0; i<length; i++) {
+ int key = rand() % l;
+ buffer[i] = charset[key];
+ }
+ buffer[length-1] = '\0';
+ }
+}
+
+
+/* Error codes */
+#define JANUS_NOSIP_ERROR_UNKNOWN_ERROR 499
+#define JANUS_NOSIP_ERROR_NO_MESSAGE 440
+#define JANUS_NOSIP_ERROR_INVALID_JSON 441
+#define JANUS_NOSIP_ERROR_INVALID_REQUEST 442
+#define JANUS_NOSIP_ERROR_MISSING_ELEMENT 443
+#define JANUS_NOSIP_ERROR_INVALID_ELEMENT 444
+#define JANUS_NOSIP_ERROR_WRONG_STATE 445
+#define JANUS_NOSIP_ERROR_MISSING_SDP 446
+#define JANUS_NOSIP_ERROR_INVALID_SDP 447
+#define JANUS_NOSIP_ERROR_IO_ERROR 448
+#define JANUS_NOSIP_ERROR_RECORDING_ERROR 449
+#define JANUS_NOSIP_ERROR_TOO_STRICT 450
+
+
+/* NoSIP watchdog/garbage collector (sort of) */
+static void *janus_nosip_watchdog(void *data) {
+ JANUS_LOG(LOG_INFO, "NoSIP watchdog started\n");
+ gint64 now = 0;
+ while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
+ janus_mutex_lock(&sessions_mutex);
+ /* Iterate on all the sessions */
+ now = janus_get_monotonic_time();
+ if(old_sessions != NULL) {
+ GList *sl = old_sessions;
+ JANUS_LOG(LOG_HUGE, "Checking %d old NoSIP sessions...\n", g_list_length(old_sessions));
+ while(sl) {
+ janus_nosip_session *session = (janus_nosip_session *)sl->data;
+ if(!session) {
+ sl = sl->next;
+ continue;
+ }
+ if (now-session->destroyed >= 5*G_USEC_PER_SEC) {
+ /* We're lazy and actually get rid of the stuff only after a few seconds */
+ JANUS_LOG(LOG_VERB, "Freeing old NoSIP session\n");
+ GList *rm = sl->next;
+ old_sessions = g_list_delete_link(old_sessions, sl);
+ sl = rm;
+ g_free(session->info);
+ session->info = NULL;
+ janus_sdp_free(session->sdp);
+ session->sdp = NULL;
+ g_free(session->media.remote_ip);
+ session->media.remote_ip = NULL;
+ janus_nosip_srtp_cleanup(session);
+ session->handle = NULL;
+ g_free(session);
+ session = NULL;
+ continue;
+ }
+ sl = sl->next;
+ }
+ }
+ janus_mutex_unlock(&sessions_mutex);
+ g_usleep(500000);
+ }
+ JANUS_LOG(LOG_INFO, "NoSIP watchdog stopped\n");
+ return NULL;
+}
+
+
+static void janus_nosip_detect_local_ip(char *buf, size_t buflen) {
+ JANUS_LOG(LOG_VERB, "Autodetecting local IP...\n");
+
+ struct sockaddr_in addr;
+ socklen_t len;
+ int fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd == -1)
+ goto error;
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(1);
+ inet_pton(AF_INET, "1.2.3.4", &addr.sin_addr.s_addr);
+ if (connect(fd, (const struct sockaddr*) &addr, sizeof(addr)) < 0)
+ goto error;
+ len = sizeof(addr);
+ if (getsockname(fd, (struct sockaddr*) &addr, &len) < 0)
+ goto error;
+ if (getnameinfo((const struct sockaddr*) &addr, sizeof(addr),
+ buf, buflen,
+ NULL, 0, NI_NUMERICHOST) != 0)
+ goto error;
+ close(fd);
+ return;
+
+error:
+ if (fd != -1)
+ close(fd);
+ JANUS_LOG(LOG_VERB, "Couldn't find any address! using 127.0.0.1 as the local IP... (which is NOT going to work out of your machine)\n");
+ g_strlcpy(buf, "127.0.0.1", buflen);
+}
+
+
+/* Plugin implementation */
+int janus_nosip_init(janus_callbacks *callback, const char *config_path) {
+ if(g_atomic_int_get(&stopping)) {
+ /* Still stopping from before */
+ return -1;
+ }
+ if(callback == NULL || config_path == NULL) {
+ /* Invalid arguments */
+ return -1;
+ }
+
+ /* Read configuration */
+ char filename[255];
+ g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_NOSIP_PACKAGE);
+ JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename);
+ janus_config *config = janus_config_parse(filename);
+ if(config != NULL) {
+ janus_config_print(config);
+
+ gboolean local_ip_set = FALSE;
+ janus_config_item *item = janus_config_get_item_drilldown(config, "general", "local_ip");
+ if(item && item->value) {
+ int family;
+ if (!janus_is_ip_valid(item->value, &family)) {
+ JANUS_LOG(LOG_WARN, "Invalid local IP specified: %s, guessing the default...\n", item->value);
+ } else {
+ /* Verify that we can actually bind to that address */
+ int fd = socket(family, SOCK_DGRAM, 0);
+ if (fd == -1) {
+ JANUS_LOG(LOG_WARN, "Error creating test socket, falling back to detecting IP address...\n");
+ } else {
+ int r;
+ struct sockaddr_storage ss;
+ socklen_t addrlen;
+ memset(&ss, 0, sizeof(ss));
+ if (family == AF_INET) {
+ struct sockaddr_in *addr4 = (struct sockaddr_in*)&ss;
+ addr4->sin_family = AF_INET;
+ addr4->sin_port = 0;
+ inet_pton(AF_INET, item->value, &(addr4->sin_addr.s_addr));
+ addrlen = sizeof(struct sockaddr_in);
+ } else {
+ struct sockaddr_in6 *addr6 = (struct sockaddr_in6*)&ss;
+ addr6->sin6_family = AF_INET6;
+ addr6->sin6_port = 0;
+ inet_pton(AF_INET6, item->value, &(addr6->sin6_addr.s6_addr));
+ addrlen = sizeof(struct sockaddr_in6);
+ }
+ r = bind(fd, (const struct sockaddr*)&ss, addrlen);
+ close(fd);
+ if (r < 0) {
+ JANUS_LOG(LOG_WARN, "Error setting local IP address to %s, falling back to detecting IP address...\n", item->value);
+ } else {
+ g_strlcpy(local_ip, item->value, sizeof(local_ip));
+ local_ip_set = TRUE;
+ }
+ }
+ }
+ }
+ if (!local_ip_set)
+ janus_nosip_detect_local_ip(local_ip, sizeof(local_ip));
+ JANUS_LOG(LOG_WARN, "Local IP set to %s\n", local_ip);
+
+ item = janus_config_get_item_drilldown(config, "general", "events");
+ if(item != NULL && item->value != NULL)
+ notify_events = janus_is_true(item->value);
+ if(!notify_events && callback->events_is_enabled()) {
+ JANUS_LOG(LOG_WARN, "Notification of events to handlers disabled for %s\n", JANUS_NOSIP_NAME);
+ }
+
+ janus_config_destroy(config);
+ }
+ config = NULL;
+
+#ifdef HAVE_SRTP_2
+ /* Init randomizer (for randum numbers in SRTP) */
+ RAND_poll();
+#endif
+
+ sessions = g_hash_table_new(NULL, NULL);
+ janus_mutex_init(&sessions_mutex);
+ messages = g_async_queue_new_full((GDestroyNotify) janus_nosip_message_free);
+ /* This is the callback we'll need to invoke to contact the gateway */
+ gateway = callback;
+
+ g_atomic_int_set(&initialized, 1);
+
+ GError *error = NULL;
+ /* Start the sessions watchdog */
+ watchdog = g_thread_try_new("nosip watchdog", &janus_nosip_watchdog, NULL, &error);
+ if(error != NULL) {
+ g_atomic_int_set(&initialized, 0);
+ JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the NoSIP watchdog thread...\n", error->code, error->message ? error->message : "??");
+ return -1;
+ }
+ /* Launch the thread that will handle incoming messages */
+ handler_thread = g_thread_try_new("nosip handler", janus_nosip_handler, NULL, &error);
+ if(error != NULL) {
+ g_atomic_int_set(&initialized, 0);
+ JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the NoSIP handler thread...\n", error->code, error->message ? error->message : "??");
+ return -1;
+ }
+ JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_NOSIP_NAME);
+ return 0;
+}
+
+void janus_nosip_destroy(void) {
+ if(!g_atomic_int_get(&initialized))
+ return;
+ g_atomic_int_set(&stopping, 1);
+
+ g_async_queue_push(messages, &exit_message);
+ if(handler_thread != NULL) {
+ g_thread_join(handler_thread);
+ handler_thread = NULL;
+ }
+ if(watchdog != NULL) {
+ g_thread_join(watchdog);
+ watchdog = NULL;
+ }
+ /* FIXME We should destroy the sessions cleanly */
+ janus_mutex_lock(&sessions_mutex);
+ g_hash_table_destroy(sessions);
+ sessions = NULL;
+ janus_mutex_unlock(&sessions_mutex);
+ g_async_queue_unref(messages);
+ messages = NULL;
+ g_atomic_int_set(&initialized, 0);
+ g_atomic_int_set(&stopping, 0);
+
+ JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_NOSIP_NAME);
+}
+
+int janus_nosip_get_api_compatibility(void) {
+ /* Important! This is what your plugin MUST always return: don't lie here or bad things will happen */
+ return JANUS_PLUGIN_API_VERSION;
+}
+
+int janus_nosip_get_version(void) {
+ return JANUS_NOSIP_VERSION;
+}
+
+const char *janus_nosip_get_version_string(void) {
+ return JANUS_NOSIP_VERSION_STRING;
+}
+
+const char *janus_nosip_get_description(void) {
+ return JANUS_NOSIP_DESCRIPTION;
+}
+
+const char *janus_nosip_get_name(void) {
+ return JANUS_NOSIP_NAME;
+}
+
+const char *janus_nosip_get_author(void) {
+ return JANUS_NOSIP_AUTHOR;
+}
+
+const char *janus_nosip_get_package(void) {
+ return JANUS_NOSIP_PACKAGE;
+}
+
+void janus_nosip_create_session(janus_plugin_session *handle, int *error) {
+ if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
+ *error = -1;
+ return;
+ }
+ janus_nosip_session *session = g_malloc0(sizeof(janus_nosip_session));
+ session->handle = handle;
+ session->info = NULL;
+ session->sdp = NULL;
+ session->media.remote_ip = NULL;
+ session->media.ready = 0;
+ session->media.autoack = TRUE;
+ session->media.require_srtp = FALSE;
+ session->media.has_srtp_local = FALSE;
+ session->media.has_srtp_remote = FALSE;
+ session->media.has_audio = 0;
+ session->media.audio_rtp_fd = -1;
+ session->media.audio_rtcp_fd = -1;
+ session->media.local_audio_rtp_port = 0;
+ session->media.remote_audio_rtp_port = 0;
+ session->media.local_audio_rtcp_port = 0;
+ session->media.remote_audio_rtcp_port = 0;
+ session->media.audio_ssrc = 0;
+ session->media.audio_ssrc_peer = 0;
+ session->media.audio_pt = -1;
+ session->media.audio_pt_name = NULL;
+ session->media.audio_srtp_suite_in = 0;
+ session->media.audio_srtp_suite_out = 0;
+ session->media.audio_send = TRUE;
+ session->media.has_video = 0;
+ session->media.video_rtp_fd = -1;
+ session->media.video_rtcp_fd = -1;
+ session->media.local_video_rtp_port = 0;
+ session->media.remote_video_rtp_port = 0;
+ session->media.local_video_rtcp_port = 0;
+ session->media.remote_video_rtcp_port = 0;
+ session->media.video_ssrc = 0;
+ session->media.video_ssrc_peer = 0;
+ session->media.video_pt = -1;
+ session->media.video_pt_name = NULL;
+ session->media.video_srtp_suite_in = 0;
+ session->media.video_srtp_suite_out = 0;
+ session->media.video_send = TRUE;
+ /* Initialize the RTP context */
+ session->media.context.a_last_ssrc = 0;
+ session->media.context.a_last_ssrc = 0;
+ session->media.context.a_last_ts = 0;
+ session->media.context.a_base_ts = 0;
+ session->media.context.a_base_ts_prev = 0;
+ session->media.context.v_last_ssrc = 0;
+ session->media.context.v_last_ts = 0;
+ session->media.context.v_base_ts = 0;
+ session->media.context.v_base_ts_prev = 0;
+ session->media.context.a_last_seq = 0;
+ session->media.context.a_base_seq = 0;
+ session->media.context.a_base_seq_prev = 0;
+ session->media.context.v_last_seq = 0;
+ session->media.context.v_base_seq = 0;
+ session->media.context.v_base_seq_prev = 0;
+ session->media.pipefd[0] = -1;
+ session->media.pipefd[1] = -1;
+ session->media.updated = FALSE;
+ janus_mutex_init(&session->rec_mutex);
+ session->destroyed = 0;
+ g_atomic_int_set(&session->hangingup, 0);
+ janus_mutex_init(&session->mutex);
+ handle->plugin_handle = session;
+
+ janus_mutex_lock(&sessions_mutex);
+ g_hash_table_insert(sessions, handle, session);
+ janus_mutex_unlock(&sessions_mutex);
+
+ return;
+}
+
+void janus_nosip_destroy_session(janus_plugin_session *handle, int *error) {
+ if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
+ *error = -1;
+ return;
+ }
+ janus_nosip_session *session = (janus_nosip_session *)handle->plugin_handle;
+ if(!session) {
+ JANUS_LOG(LOG_ERR, "No NoSIP session associated with this handle...\n");
+ *error = -2;
+ return;
+ }
+ janus_mutex_lock(&sessions_mutex);
+ if(!session->destroyed) {
+ g_hash_table_remove(sessions, handle);
+ janus_nosip_hangup_media(handle);
+ session->destroyed = janus_get_monotonic_time();
+ JANUS_LOG(LOG_VERB, "Destroying NoSIP session (%s)...\n", session->info);
+ /* Cleaning up and removing the session is done in a lazy way */
+ old_sessions = g_list_append(old_sessions, session);
+ }
+ janus_mutex_unlock(&sessions_mutex);
+ return;
+}
+
+json_t *janus_nosip_query_session(janus_plugin_session *handle) {
+ if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
+ return NULL;
+ }
+ janus_nosip_session *session = (janus_nosip_session *)handle->plugin_handle;
+ if(!session) {
+ JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
+ return NULL;
+ }
+ /* Provide some generic info, e.g., if we're in a call and with whom */
+ json_t *info = json_object();
+ json_object_set_new(info, "info", session->info ? json_string(session->info) : NULL);
+ if(session->sdp) {
+ json_object_set_new(info, "srtp-required", json_string(session->media.require_srtp ? "yes" : "no"));
+ json_object_set_new(info, "sdes-local", json_string(session->media.has_srtp_local ? "yes" : "no"));
+ json_object_set_new(info, "sdes-remote", json_string(session->media.has_srtp_remote ? "yes" : "no"));
+ }
+ if(session->arc || session->vrc || session->arc_peer || session->vrc_peer) {
+ json_t *recording = json_object();
+ if(session->arc && session->arc->filename)
+ json_object_set_new(recording, "audio", json_string(session->arc->filename));
+ if(session->vrc && session->vrc->filename)
+ json_object_set_new(recording, "video", json_string(session->vrc->filename));
+ if(session->arc_peer && session->arc_peer->filename)
+ json_object_set_new(recording, "audio-peer", json_string(session->arc_peer->filename));
+ if(session->vrc_peer && session->vrc_peer->filename)
+ json_object_set_new(recording, "video-peer", json_string(session->vrc_peer->filename));
+ json_object_set_new(info, "recording", recording);
+ }
+ json_object_set_new(info, "destroyed", json_integer(session->destroyed));
+ return info;
+}
+
+struct janus_plugin_result *janus_nosip_handle_message(janus_plugin_session *handle, char *transaction, json_t *message, json_t *jsep) {
+ if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
+ return janus_plugin_result_new(JANUS_PLUGIN_ERROR, g_atomic_int_get(&stopping) ? "Shutting down" : "Plugin not initialized", NULL);
+ janus_nosip_message *msg = g_malloc0(sizeof(janus_nosip_message));
+ msg->handle = handle;
+ msg->transaction = transaction;
+ msg->message = message;
+ msg->jsep = jsep;
+ g_async_queue_push(messages, msg);
+
+ /* All the requests to this plugin are handled asynchronously */
+ return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL, NULL);
+}
+
+void janus_nosip_setup_media(janus_plugin_session *handle) {
+ JANUS_LOG(LOG_INFO, "WebRTC media is now available\n");
+ if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
+ return;
+ janus_nosip_session *session = (janus_nosip_session *)handle->plugin_handle;
+ if(!session) {
+ JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
+ return;
+ }
+ if(session->destroyed)
+ return;
+ g_atomic_int_set(&session->hangingup, 0);
+}
+
+void janus_nosip_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len) {
+ if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
+ return;
+ if(gateway) {
+ /* Honour the audio/video active flags */
+ janus_nosip_session *session = (janus_nosip_session *)handle->plugin_handle;
+ if(!session || session->destroyed) {
+ JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
+ return;
+ }
+ /* Forward to our NoSIP peer */
+ if(video) {
+ if(!session->media.video_send) {
+ /* Dropping video packet, peer doesn't want to receive it */
+ return;
+ }
+ if(session->media.video_ssrc == 0) {
+ rtp_header *header = (rtp_header *)buf;
+ session->media.video_ssrc = ntohl(header->ssrc);
+ JANUS_LOG(LOG_VERB, "Got NoSIP video SSRC: %"SCNu32"\n", session->media.video_ssrc);
+ }
+ if(session->media.has_video && session->media.video_rtp_fd) {
+ /* Save the frame if we're recording */
+ janus_recorder_save_frame(session->vrc, buf, len);
+ /* Is SRTP involved? */
+ if(session->media.has_srtp_local) {
+ char sbuf[2048];
+ memcpy(&sbuf, buf, len);
+ int protected = len;
+ int res = srtp_protect(session->media.video_srtp_out, &sbuf, &protected);
+ if(res != srtp_err_status_ok) {
+ rtp_header *header = (rtp_header *)&sbuf;
+ guint32 timestamp = ntohl(header->timestamp);
+ guint16 seq = ntohs(header->seq_number);
+ JANUS_LOG(LOG_ERR, "[NoSIP-%s] Video SRTP protect error... %s (len=%d-->%d, ts=%"SCNu32", seq=%"SCNu16")...\n",
+ session->info, janus_nosip_get_srtp_error(res), len, protected, timestamp, seq);
+ } else {
+ /* Forward the frame to the peer */
+ send(session->media.video_rtp_fd, sbuf, protected, 0);
+ }
+ } else {
+ /* Forward the frame to the peer */
+ send(session->media.video_rtp_fd, buf, len, 0);
+ }
+ }
+ } else {
+ if(!session->media.audio_send) {
+ /* Dropping audio packet, peer doesn't want to receive it */
+ return;
+ }
+ if(session->media.audio_ssrc == 0) {
+ rtp_header *header = (rtp_header *)buf;
+ session->media.audio_ssrc = ntohl(header->ssrc);
+ JANUS_LOG(LOG_VERB, "Got NoSIP audio SSRC: %"SCNu32"\n", session->media.audio_ssrc);
+ }
+ if(session->media.has_audio && session->media.audio_rtp_fd) {
+ /* Save the frame if we're recording */
+ janus_recorder_save_frame(session->arc, buf, len);
+ /* Is SRTP involved? */
+ if(session->media.has_srtp_local) {
+ char sbuf[2048];
+ memcpy(&sbuf, buf, len);
+ int protected = len;
+ int res = srtp_protect(session->media.audio_srtp_out, &sbuf, &protected);
+ if(res != srtp_err_status_ok) {
+ rtp_header *header = (rtp_header *)&sbuf;
+ guint32 timestamp = ntohl(header->timestamp);
+ guint16 seq = ntohs(header->seq_number);
+ JANUS_LOG(LOG_ERR, "[NoSIP-%s] Audio SRTP protect error... %s (len=%d-->%d, ts=%"SCNu32", seq=%"SCNu16")...\n",
+ session->info, janus_nosip_get_srtp_error(res), len, protected, timestamp, seq);
+ } else {
+ /* Forward the frame to the peer */
+ send(session->media.audio_rtp_fd, sbuf, protected, 0);
+ }
+ } else {
+ /* Forward the frame to the peer */
+ send(session->media.audio_rtp_fd, buf, len, 0);
+ }
+ }
+ }
+ }
+}
+
+void janus_nosip_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len) {
+ if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
+ return;
+ if(gateway) {
+ janus_nosip_session *session = (janus_nosip_session *)handle->plugin_handle;
+ if(!session || session->destroyed) {
+ JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
+ return;
+ }
+ /* Forward to our NoSIP peer */
+ if(video) {
+ if(session->media.has_video && session->media.video_rtcp_fd) {
+ /* Fix SSRCs as the gateway does */
+ JANUS_LOG(LOG_HUGE, "[NoSIP] Fixing SSRCs (local %u, peer %u)\n",
+ session->media.video_ssrc, session->media.video_ssrc_peer);
+ janus_rtcp_fix_ssrc(NULL, (char *)buf, len, 1, session->media.video_ssrc, session->media.video_ssrc_peer);
+ /* Is SRTP involved? */
+ if(session->media.has_srtp_local) {
+ char sbuf[2048];
+ memcpy(&sbuf, buf, len);
+ int protected = len;
+ int res = srtp_protect_rtcp(session->media.video_srtp_out, &sbuf, &protected);
+ if(res != srtp_err_status_ok) {
+ JANUS_LOG(LOG_ERR, "[NoSIP-%s] Video SRTCP protect error... %s (len=%d-->%d)...\n",
+ session->info, janus_nosip_get_srtp_error(res), len, protected);
+ } else {
+ /* Forward the message to the peer */
+ send(session->media.video_rtcp_fd, sbuf, protected, 0);
+ }
+ } else {
+ /* Forward the message to the peer */
+ send(session->media.video_rtcp_fd, buf, len, 0);
+ }
+ }
+ } else {
+ if(session->media.has_audio && session->media.audio_rtcp_fd) {
+ /* Fix SSRCs as the gateway does */
+ JANUS_LOG(LOG_HUGE, "[NoSIP] Fixing SSRCs (local %u, peer %u)\n",
+ session->media.audio_ssrc, session->media.audio_ssrc_peer);
+ janus_rtcp_fix_ssrc(NULL, (char *)buf, len, 1, session->media.audio_ssrc, session->media.audio_ssrc_peer);
+ /* Is SRTP involved? */
+ if(session->media.has_srtp_local) {
+ char sbuf[2048];
+ memcpy(&sbuf, buf, len);
+ int protected = len;
+ int res = srtp_protect_rtcp(session->media.audio_srtp_out, &sbuf, &protected);
+ if(res != srtp_err_status_ok) {
+ JANUS_LOG(LOG_ERR, "[NoSIP-%s] Audio SRTCP protect error... %s (len=%d-->%d)...\n",
+ session->info, janus_nosip_get_srtp_error(res), len, protected);
+ } else {
+ /* Forward the message to the peer */
+ send(session->media.audio_rtcp_fd, sbuf, protected, 0);
+ }
+ } else {
+ /* Forward the message to the peer */
+ send(session->media.audio_rtcp_fd, buf, len, 0);
+ }
+ }
+ }
+ }
+}
+
+void janus_nosip_hangup_media(janus_plugin_session *handle) {
+ JANUS_LOG(LOG_INFO, "No WebRTC media anymore\n");
+ if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
+ return;
+ janus_nosip_session *session = (janus_nosip_session *)handle->plugin_handle;
+ if(!session) {
+ JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
+ return;
+ }
+ if(session->destroyed)
+ return;
+ if(g_atomic_int_add(&session->hangingup, 1))
+ return;
+ /* Get rid of the recorders, if available */
+ janus_mutex_lock(&session->rec_mutex);
+ if(session->arc) {
+ janus_recorder_close(session->arc);
+ JANUS_LOG(LOG_INFO, "Closed user's audio recording %s\n", session->arc->filename ? session->arc->filename : "??");
+ janus_recorder_free(session->arc);
+ }
+ session->arc = NULL;
+ if(session->arc_peer) {
+ janus_recorder_close(session->arc_peer);
+ JANUS_LOG(LOG_INFO, "Closed peer's audio recording %s\n", session->arc_peer->filename ? session->arc_peer->filename : "??");
+ janus_recorder_free(session->arc_peer);
+ }
+ session->arc_peer = NULL;
+ if(session->vrc) {
+ janus_recorder_close(session->vrc);
+ JANUS_LOG(LOG_INFO, "Closed user's video recording %s\n", session->vrc->filename ? session->vrc->filename : "??");
+ janus_recorder_free(session->vrc);
+ }
+ session->vrc = NULL;
+ if(session->vrc_peer) {
+ janus_recorder_close(session->vrc_peer);
+ JANUS_LOG(LOG_INFO, "Closed peer's video recording %s\n", session->vrc_peer->filename ? session->vrc_peer->filename : "??");
+ janus_recorder_free(session->vrc_peer);
+ }
+ session->vrc_peer = NULL;
+ janus_mutex_unlock(&session->rec_mutex);
+ /* FIXME Simulate a "hangup" coming from the browser */
+ janus_nosip_message *msg = g_malloc0(sizeof(janus_nosip_message));
+ msg->handle = handle;
+ msg->message = json_pack("{ss}", "request", "hangup");
+ msg->transaction = NULL;
+ msg->jsep = NULL;
+ g_async_queue_push(messages, msg);
+}
+
+/* Thread to handle incoming messages */
+static void *janus_nosip_handler(void *data) {
+ JANUS_LOG(LOG_VERB, "Joining NoSIP handler thread\n");
+ janus_nosip_message *msg = NULL;
+ int error_code = 0;
+ char error_cause[512];
+ json_t *root = NULL;
+ while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
+ msg = g_async_queue_pop(messages);
+ if(msg == NULL)
+ continue;
+ if(msg == &exit_message)
+ break;
+ if(msg->handle == NULL) {
+ janus_nosip_message_free(msg);
+ continue;
+ }
+ janus_nosip_session *session = NULL;
+ janus_mutex_lock(&sessions_mutex);
+ if(g_hash_table_lookup(sessions, msg->handle) != NULL ) {
+ session = (janus_nosip_session *)msg->handle->plugin_handle;
+ }
+ janus_mutex_unlock(&sessions_mutex);
+ if(!session) {
+ JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
+ janus_nosip_message_free(msg);
+ continue;
+ }
+ if(session->destroyed) {
+ janus_nosip_message_free(msg);
+ continue;
+ }
+ /* Handle request */
+ error_code = 0;
+ root = msg->message;
+ if(msg->message == NULL) {
+ JANUS_LOG(LOG_ERR, "No message??\n");
+ error_code = JANUS_NOSIP_ERROR_NO_MESSAGE;
+ g_snprintf(error_cause, 512, "%s", "No message??");
+ goto error;
+ }
+ if(!json_is_object(root)) {
+ JANUS_LOG(LOG_ERR, "JSON error: not an object\n");
+ error_code = JANUS_NOSIP_ERROR_INVALID_JSON;
+ g_snprintf(error_cause, 512, "JSON error: not an object");
+ goto error;
+ }
+ JANUS_VALIDATE_JSON_OBJECT(root, request_parameters,
+ error_code, error_cause, TRUE,
+ JANUS_NOSIP_ERROR_MISSING_ELEMENT, JANUS_NOSIP_ERROR_INVALID_ELEMENT);
+ if(error_code != 0)
+ goto error;
+ json_t *request = json_object_get(root, "request");
+ const char *request_text = json_string_value(request);
+ json_t *result = NULL, *localjsep = NULL;
+
+ if(!strcasecmp(request_text, "generate") || !strcasecmp(request_text, "process")) {
+ /* Shared code for two different requests:
+ * generate: Take a JSEP offer or answer and generate a barebone SDP the application can use
+ * process: Process a remote barebone SDP, and match it to the one we may have generated before */
+ gboolean generate = !strcasecmp(request_text, "generate") ? TRUE : FALSE;
+ if(generate) {
+ JANUS_VALIDATE_JSON_OBJECT(root, generate_parameters,
+ error_code, error_cause, TRUE,
+ JANUS_NOSIP_ERROR_MISSING_ELEMENT, JANUS_NOSIP_ERROR_INVALID_ELEMENT);
+ } else {
+ JANUS_VALIDATE_JSON_OBJECT(root, process_parameters,
+ error_code, error_cause, TRUE,
+ JANUS_NOSIP_ERROR_MISSING_ELEMENT, JANUS_NOSIP_ERROR_INVALID_ELEMENT);
+ }
+ if(error_code != 0)
+ goto error;
+ /* Any SDP to handle? if not, something's wrong */
+ const char *msg_sdp_type = json_string_value(json_object_get(generate ? msg->jsep : root, "type"));
+ const char *msg_sdp = json_string_value(json_object_get(generate ? msg->jsep : root, "sdp"));
+ if(!msg_sdp) {
+ JANUS_LOG(LOG_ERR, "Missing SDP\n");
+ error_code = JANUS_NOSIP_ERROR_MISSING_SDP;
+ g_snprintf(error_cause, 512, "Missing SDP");
+ goto error;
+ }
+ if(!msg_sdp_type || (strcasecmp(msg_sdp_type, "offer") && strcasecmp(msg_sdp_type, "answer"))) {
+ JANUS_LOG(LOG_ERR, "Missing or invalid SDP type\n");
+ error_code = JANUS_NOSIP_ERROR_MISSING_SDP;
+ g_snprintf(error_cause, 512, "Missing or invalid SDP type");
+ goto error;
+ }
+ gboolean offer = !strcasecmp(msg_sdp_type, "offer");
+ if(strstr(msg_sdp, "m=application")) {
+ JANUS_LOG(LOG_ERR, "The NoSIP plugin does not support DataChannels\n");
+ error_code = JANUS_NOSIP_ERROR_MISSING_SDP;
+ g_snprintf(error_cause, 512, "The NoSIP plugin does not support DataChannels");
+ goto error;
+ }
+ /* Check if the user provided an info string to provide context */
+ const char *info = json_string_value(json_object_get(root, "info"));
+ /* SDES-SRTP is disabled by default, let's see if we need to enable it */
+ gboolean do_srtp = FALSE, require_srtp = FALSE;
+ if(generate) {
+ json_t *srtp = json_object_get(root, "srtp");
+ if(srtp) {
+ const char *srtp_text = json_string_value(srtp);
+ if(!strcasecmp(srtp_text, "sdes_optional")) {
+ /* Negotiate SDES, but make it optional */
+ do_srtp = TRUE;
+ } else if(!strcasecmp(srtp_text, "sdes_mandatory")) {
+ /* Negotiate SDES, and require it */
+ do_srtp = TRUE;
+ require_srtp = TRUE;
+ } else {
+ JANUS_LOG(LOG_ERR, "Invalid element (srtp can only be sdes_optional or sdes_mandatory)\n");
+ error_code = JANUS_NOSIP_ERROR_INVALID_ELEMENT;
+ g_snprintf(error_cause, 512, "Invalid element (srtp can only be sdes_optional or sdes_mandatory)");
+ goto error;
+ }
+ }
+ if(offer) {
+ /* Clean up SRTP stuff from before first, in case it's still needed */
+ janus_nosip_srtp_cleanup(session);
+ session->media.require_srtp = require_srtp;
+ session->media.has_srtp_local = do_srtp;
+ if(do_srtp) {
+ JANUS_LOG(LOG_VERB, "Going to negotiate SDES-SRTP (%s)...\n", require_srtp ? "mandatory" : "optional");
+ }
+ } else {
+ /* Make sure the request is consistent with the state (original offer) */
+ if(session->media.require_srtp && !session->media.has_srtp_remote) {
+ JANUS_LOG(LOG_ERR, "Can't generate answer: SDES-SRTP required, but caller didn't offer it\n");
+ error_code = JANUS_NOSIP_ERROR_TOO_STRICT;
+ g_snprintf(error_cause, 512, "Can't generate answer: SDES-SRTP required, but caller didn't offer it");
+ goto error;
+ }
+ do_srtp = do_srtp || session->media.has_srtp_remote;
+ }
+ }
+ /* Parse the SDP we got, manipulate some things, and generate a new one */
+ char sdperror[100];
+ janus_sdp *parsed_sdp = janus_sdp_parse(msg_sdp, sdperror, sizeof(sdperror));
+ if(!parsed_sdp) {
+ JANUS_LOG(LOG_ERR, "Error parsing SDP: %s\n", sdperror);
+ error_code = JANUS_NOSIP_ERROR_MISSING_SDP;
+ g_snprintf(error_cause, 512, "Error parsing SDP: %s", sdperror);
+ goto error;
+ }
+ if(generate) {
+ /* Allocate RTP ports and merge them with the anonymized SDP */
+ if(strstr(msg_sdp, "m=audio") && !strstr(msg_sdp, "m=audio 0")) {
+ JANUS_LOG(LOG_VERB, "Going to negotiate audio...\n");
+ session->media.has_audio = 1; /* FIXME Maybe we need a better way to signal this */
+ }
+ if(strstr(msg_sdp, "m=video") && !strstr(msg_sdp, "m=video 0")) {
+ JANUS_LOG(LOG_VERB, "Going to negotiate video...\n");
+ session->media.has_video = 1; /* FIXME Maybe we need a better way to signal this */
+ }
+ if(janus_nosip_allocate_local_ports(session) < 0) {
+ JANUS_LOG(LOG_ERR, "Could not allocate RTP/RTCP ports\n");
+ janus_sdp_free(parsed_sdp);
+ error_code = JANUS_NOSIP_ERROR_IO_ERROR;
+ g_snprintf(error_cause, 512, "Could not allocate RTP/RTCP ports");
+ goto error;
+ }
+ char *sdp = janus_nosip_sdp_manipulate(session, parsed_sdp, FALSE);
+ if(sdp == NULL) {
+ JANUS_LOG(LOG_ERR, "Could not allocate RTP/RTCP ports\n");
+ janus_sdp_free(parsed_sdp);
+ error_code = JANUS_NOSIP_ERROR_IO_ERROR;
+ g_snprintf(error_cause, 512, "Could not allocate RTP/RTCP ports");
+ goto error;
+ }
+ /* Take note of the SDP (may be useful for UPDATEs or re-INVITEs) */
+ janus_sdp_free(session->sdp);
+ session->sdp = parsed_sdp;
+ if(session->info == NULL) {
+ char tempinfo[16];
+ janus_nosip_random_string(sizeof(tempinfo), tempinfo);
+ session->info = g_strdup(info ? info : tempinfo);
+ }
+ JANUS_LOG(LOG_VERB, "Prepared SDP %s for (%s)\n%s", msg_sdp_type, info, sdp);
+ g_atomic_int_set(&session->hangingup, 0);
+ /* Also notify event handlers */
+ if(notify_events && gateway->events_is_enabled()) {
+ json_t *info = json_object();
+ json_object_set_new(info, "event", json_string("generated"));
+ json_object_set_new(info, "info", json_string(session->info));
+ json_object_set_new(info, "type", json_string(offer ? "offer" : "answer"));
+ json_object_set_new(info, "sdp", json_string(sdp));
+ gateway->notify_event(&janus_nosip_plugin, session->handle, info);
+ }
+ /* Send the barebone SDP back */
+ result = json_object();
+ json_object_set_new(result, "event", json_string("generated"));
+ json_object_set_new(result, "info", json_string(session->info));
+ json_object_set_new(result, "type", json_string(offer ? "offer" : "answer"));
+ json_object_set_new(result, "sdp", json_string(sdp));
+ g_free(sdp);
+ } else {
+ /* We got a barebone offer or answer from our peer: process it accordingly */
+ gboolean changed = FALSE;
+ if(offer) {
+ /* Clean up SRTP stuff from before first, in case it's still needed */
+ janus_nosip_srtp_cleanup(session);
+ }
+ janus_nosip_sdp_process(session, parsed_sdp, !offer, FALSE, &changed);
+ /* Check if offer has neither audio nor video, fail */
+ if (!session->media.has_audio && !session->media.has_video) {
+ JANUS_LOG(LOG_ERR, "No audio and no video being negotiated\n");
+ janus_sdp_free(parsed_sdp);
+ error_code = JANUS_NOSIP_ERROR_INVALID_SDP;
+ g_snprintf(error_cause, 512, "No audio and no video being negotiated");
+ goto error;
+ }
+ /* Also fail if there's no remote IP address that can be used for RTP */
+ if (!session->media.remote_ip) {
+ JANUS_LOG(LOG_ERR, "No remote IP address\n");
+ janus_sdp_free(parsed_sdp);
+ error_code = JANUS_NOSIP_ERROR_INVALID_SDP;
+ g_snprintf(error_cause, 512, "No remote IP address");
+ goto error;
+ }
+ /* Take note of the SDP (may be useful for UPDATEs or re-INVITEs) */
+ janus_sdp_free(session->sdp);
+ session->sdp = parsed_sdp;
+ if(session->info == NULL) {
+ char tempinfo[16];
+ janus_nosip_random_string(sizeof(tempinfo), tempinfo);
+ session->info = g_strdup(info ? info : tempinfo);
+ }
+ /* Also notify event handlers */
+ if(notify_events && gateway->events_is_enabled()) {
+ json_t *info = json_object();
+ json_object_set_new(info, "event", json_string("processed"));
+ json_object_set_new(info, "info", json_string(session->info));
+ json_object_set_new(info, "type", json_string(offer ? "offer" : "answer"));
+ json_object_set_new(info, "sdp", json_string(msg_sdp));
+ gateway->notify_event(&janus_nosip_plugin, session->handle, info);
+ }
+ /* Send SDP to the browser */
+ result = json_object();
+ json_object_set_new(result, "event", json_string("processed"));
+ json_object_set_new(result, "info", json_string(session->info));
+ if(session->media.has_srtp_remote) {
+ json_object_set_new(result, "srtp",
+ json_string(session->media.require_srtp ? "sdes_mandatory" : "sdes_optional"));
+ }
+ localjsep = json_pack("{ssss}", "type", msg_sdp_type, "sdp", msg_sdp);
+ }
+ /* If this is an answer, start the media */
+ if(!offer) {
+ /* Start the media */
+ session->media.ready = 1; /* FIXME Maybe we need a better way to signal this */
+ GError *error = NULL;
+ char tname[16];
+ g_snprintf(tname, sizeof(tname), "nosiprtp %s", session->info);
+ g_thread_try_new(tname, janus_nosip_relay_thread, session, &error);
+ if(error != NULL) {
+ JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the RTP/RTCP thread...\n", error->code, error->message ? error->message : "??");
+ }
+ }
+ } else if(!strcasecmp(request_text, "hangup")) {
+ /* Get rid of an ongoing session */
+ if(session->info == NULL) {
+ JANUS_LOG(LOG_ERR, "Wrong state (no info?)\n");
+ error_code = JANUS_NOSIP_ERROR_WRONG_STATE;
+ g_snprintf(error_cause, 512, "Wrong state (no info?)");
+ goto error;
+ }
+ g_free(session->info);
+ session->info = NULL;
+ /* Notify the operation */
+ result = json_object();
+ json_object_set_new(result, "event", json_string("hangingup"));
+ } else if(!strcasecmp(request_text, "recording")) {
+ /* Start or stop recording */
+ JANUS_VALIDATE_JSON_OBJECT(root, recording_parameters,
+ error_code, error_cause, TRUE,
+ JANUS_NOSIP_ERROR_MISSING_ELEMENT, JANUS_NOSIP_ERROR_INVALID_ELEMENT);
+ if(error_code != 0)
+ goto error;
+ if(session->info == NULL) {
+ JANUS_LOG(LOG_ERR, "Wrong state (no info?)\n");
+ error_code = JANUS_NOSIP_ERROR_WRONG_STATE;
+ g_snprintf(error_cause, 512, "Wrong state (no info?)");
+ goto error;
+ }
+ json_t *action = json_object_get(root, "action");
+ const char *action_text = json_string_value(action);
+ if(strcasecmp(action_text, "start") && strcasecmp(action_text, "stop")) {
+ JANUS_LOG(LOG_ERR, "Invalid action (should be start|stop)\n");
+ error_code = JANUS_NOSIP_ERROR_INVALID_ELEMENT;
+ g_snprintf(error_cause, 512, "Invalid action (should be start|stop)");
+ goto error;
+ }
+ gboolean record_audio = FALSE, record_video = FALSE, /* No media is recorded by default */
+ record_peer_audio = FALSE, record_peer_video = FALSE;
+ json_t *audio = json_object_get(root, "audio");
+ record_audio = audio ? json_is_true(audio) : FALSE;
+ json_t *video = json_object_get(root, "video");
+ record_video = video ? json_is_true(video) : FALSE;
+ json_t *peer_audio = json_object_get(root, "peer_audio");
+ record_peer_audio = peer_audio ? json_is_true(peer_audio) : FALSE;
+ json_t *peer_video = json_object_get(root, "peer_video");
+ record_peer_video = peer_video ? json_is_true(peer_video) : FALSE;
+ if(!record_audio && !record_video && !record_peer_audio && !record_peer_video) {
+ JANUS_LOG(LOG_ERR, "Invalid request (at least one of audio, video, peer_audio and peer_video should be true)\n");
+ error_code = JANUS_NOSIP_ERROR_RECORDING_ERROR;
+ g_snprintf(error_cause, 512, "Invalid request (at least one of audio, video, peer_audio and peer_video should be true)");
+ goto error;
+ }
+ json_t *recfile = json_object_get(root, "filename");
+ const char *recording_base = json_string_value(recfile);
+ janus_mutex_lock(&session->rec_mutex);
+ if(!strcasecmp(action_text, "start")) {
+ /* Start recording something */
+ char filename[255];
+ gint64 now = janus_get_real_time();
+ if(record_peer_audio || record_peer_video) {
+ JANUS_LOG(LOG_INFO, "Starting recording of peer's %s\n",
+ (record_peer_audio && record_peer_video ? "audio and video" : (record_peer_audio ? "audio" : "video")));
+ /* Start recording this peer's audio and/or video */
+ if(record_peer_audio) {
+ memset(filename, 0, 255);
+ if(recording_base) {
+ /* Use the filename and path we have been provided */
+ g_snprintf(filename, 255, "%s-peer-audio", recording_base);
+ /* FIXME This only works if offer/answer happened */
+ session->arc_peer = janus_recorder_create(NULL, session->media.audio_pt_name, filename);
+ if(session->arc_peer == NULL) {
+ /* FIXME We should notify the fact the recorder could not be created */
+ JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this peer!\n");
+ }
+ } else {
+ /* Build a filename */
+ g_snprintf(filename, 255, "nosip-%p-%"SCNi64"-peer-audio", session, now);
+ /* FIXME This only works if offer/answer happened */
+ session->arc_peer = janus_recorder_create(NULL, session->media.audio_pt_name, filename);
+ if(session->arc_peer == NULL) {
+ /* FIXME We should notify the fact the recorder could not be created */
+ JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this peer!\n");
+ }
+ }
+ }
+ if(record_peer_video) {
+ memset(filename, 0, 255);
+ if(recording_base) {
+ /* Use the filename and path we have been provided */
+ g_snprintf(filename, 255, "%s-peer-video", recording_base);
+ /* FIXME This only works if offer/answer happened */
+ session->vrc_peer = janus_recorder_create(NULL, session->media.video_pt_name, filename);
+ if(session->vrc_peer == NULL) {
+ /* FIXME We should notify the fact the recorder could not be created */
+ JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this peer!\n");
+ }
+ } else {
+ /* Build a filename */
+ g_snprintf(filename, 255, "nosip-%p-%"SCNi64"-peer-video", session, now);
+ /* FIXME This only works if offer/answer happened */
+ session->vrc_peer = janus_recorder_create(NULL, session->media.video_pt_name, filename);
+ if(session->vrc_peer == NULL) {
+ /* FIXME We should notify the fact the recorder could not be created */
+ JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this peer!\n");
+ }
+ }
+ /* TODO We should send a FIR/PLI to this peer... */
+ }
+ }
+ if(record_audio || record_video) {
+ /* Start recording the user's audio and/or video */
+ JANUS_LOG(LOG_INFO, "Starting recording of user's %s (%s)\n",
+ (record_audio && record_video ? "audio and video" : (record_audio ? "audio" : "video")), session->info);
+ if(record_audio) {
+ memset(filename, 0, 255);
+ if(recording_base) {
+ /* Use the filename and path we have been provided */
+ g_snprintf(filename, 255, "%s-user-audio", recording_base);
+ /* FIXME This only works if offer/answer happened */
+ session->arc = janus_recorder_create(NULL, session->media.audio_pt_name, filename);
+ if(session->arc == NULL) {
+ /* FIXME We should notify the fact the recorder could not be created */
+ JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this peer!\n");
+ }
+ } else {
+ /* Build a filename */
+ g_snprintf(filename, 255, "nosip-%p-%"SCNi64"-own-audio", session, now);
+ /* FIXME This only works if offer/answer happened */
+ session->arc = janus_recorder_create(NULL, session->media.audio_pt_name, filename);
+ if(session->arc == NULL) {
+ /* FIXME We should notify the fact the recorder could not be created */
+ JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this peer!\n");
+ }
+ }
+ }
+ if(record_video) {
+ memset(filename, 0, 255);
+ if(recording_base) {
+ /* Use the filename and path we have been provided */
+ g_snprintf(filename, 255, "%s-user-video", recording_base);
+ /* FIXME This only works if offer/answer happened */
+ session->vrc = janus_recorder_create(NULL, session->media.video_pt_name, filename);
+ if(session->vrc == NULL) {
+ /* FIXME We should notify the fact the recorder could not be created */
+ JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this user!\n");
+ }
+ } else {
+ /* Build a filename */
+ g_snprintf(filename, 255, "nosip-%p-%"SCNi64"-own-video", session, now);
+ /* FIXME This only works if offer/answer happened */
+ session->vrc = janus_recorder_create(NULL, session->media.video_pt_name, filename);
+ if(session->vrc == NULL) {
+ /* FIXME We should notify the fact the recorder could not be created */
+ JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this user!\n");
+ }
+ }
+ /* Send a PLI */
+ JANUS_LOG(LOG_VERB, "Recording video, sending a PLI to kickstart it\n");
+ char buf[12];
+ memset(buf, 0, 12);
+ janus_rtcp_pli((char *)&buf, 12);
+ gateway->relay_rtcp(session->handle, 1, buf, 12);
+ }
+ }
+ } else {
+ /* Stop recording something: notice that this never returns an error, even when we were not recording anything */
+ if(record_audio) {
+ if(session->arc) {
+ janus_recorder_close(session->arc);
+ JANUS_LOG(LOG_INFO, "Closed user's audio recording %s\n", session->arc->filename ? session->arc->filename : "??");
+ janus_recorder_free(session->arc);
+ }
+ session->arc = NULL;
+ }
+ if(record_video) {
+ if(session->vrc) {
+ janus_recorder_close(session->vrc);
+ JANUS_LOG(LOG_INFO, "Closed user's video recording %s\n", session->vrc->filename ? session->vrc->filename : "??");
+ janus_recorder_free(session->vrc);
+ }
+ session->vrc = NULL;
+ }
+ if(record_peer_audio) {
+ if(session->arc_peer) {
+ janus_recorder_close(session->arc_peer);
+ JANUS_LOG(LOG_INFO, "Closed peer's audio recording %s\n", session->arc_peer->filename ? session->arc_peer->filename : "??");
+ janus_recorder_free(session->arc_peer);
+ }
+ session->arc_peer = NULL;
+ }
+ if(record_peer_video) {
+ if(session->vrc_peer) {
+ janus_recorder_close(session->vrc_peer);
+ JANUS_LOG(LOG_INFO, "Closed peer's video recording %s\n", session->vrc_peer->filename ? session->vrc_peer->filename : "??");
+ janus_recorder_free(session->vrc_peer);
+ }
+ session->vrc_peer = NULL;
+ }
+ }
+ janus_mutex_unlock(&session->rec_mutex);
+ /* Notify the result */
+ result = json_object();
+ json_object_set_new(result, "event", json_string("recordingupdated"));
+ } else {
+ JANUS_LOG(LOG_ERR, "Unknown request (%s)\n", request_text);
+ error_code = JANUS_NOSIP_ERROR_INVALID_REQUEST;
+ g_snprintf(error_cause, 512, "Unknown request (%s)", request_text);
+ goto error;
+ }
+
+ /* Prepare JSON event */
+ json_t *event = json_object();
+ json_object_set_new(event, "nosip", json_string("event"));
+ if(result != NULL)
+ json_object_set_new(event, "result", result);
+ int ret = gateway->push_event(msg->handle, &janus_nosip_plugin, msg->transaction, event, localjsep);
+ JANUS_LOG(LOG_VERB, " >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret));
+ json_decref(event);
+ janus_nosip_message_free(msg);
+ continue;
+
+error:
+ {
+ /* Prepare JSON error event */
+ json_t *event = json_object();
+ json_object_set_new(event, "nosip", json_string("event"));
+ json_object_set_new(event, "error_code", json_integer(error_code));
+ json_object_set_new(event, "error", json_string(error_cause));
+ int ret = gateway->push_event(msg->handle, &janus_nosip_plugin, msg->transaction, event, NULL);
+ JANUS_LOG(LOG_VERB, " >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret));
+ json_decref(event);
+ janus_nosip_message_free(msg);
+ }
+ }
+ JANUS_LOG(LOG_VERB, "Leaving NoSIP handler thread\n");
+ return NULL;
+}
+
+
+void janus_nosip_sdp_process(janus_nosip_session *session, janus_sdp *sdp, gboolean answer, gboolean update, gboolean *changed) {
+ if(!session || !sdp)
+ return;
+ /* c= */
+ if(sdp->c_addr) {
+ if(update && strcmp(sdp->c_addr, session->media.remote_ip)) {
+ /* This is an update and an address changed */
+ if(changed)
+ *changed = TRUE;
+ }
+ g_free(session->media.remote_ip);
+ session->media.remote_ip = g_strdup(sdp->c_addr);
+ }
+ GList *temp = sdp->m_lines;
+ while(temp) {
+ janus_sdp_mline *m = (janus_sdp_mline *)temp->data;
+ session->media.require_srtp = session->media.require_srtp || (m->proto && !strcasecmp(m->proto, "RTP/SAVP"));
+ if(m->type == JANUS_SDP_AUDIO) {
+ if(m->port) {
+ if(m->port != session->media.remote_audio_rtp_port) {
+ /* This is an update and an address changed */
+ if(changed)
+ *changed = TRUE;
+ }
+ session->media.has_audio = 1;
+ session->media.remote_audio_rtp_port = m->port;
+ session->media.remote_audio_rtcp_port = m->port+1; /* FIXME We're assuming RTCP is on the next port */
+ if(m->direction == JANUS_SDP_SENDONLY || m->direction == JANUS_SDP_INACTIVE)
+ session->media.audio_send = FALSE;
+ else
+ session->media.audio_send = TRUE;
+ } else {
+ session->media.audio_send = FALSE;
+ }
+ } else if(m->type == JANUS_SDP_VIDEO) {
+ if(m->port) {
+ if(m->port != session->media.remote_video_rtp_port) {
+ /* This is an update and an address changed */
+ if(changed)
+ *changed = TRUE;
+ }
+ session->media.has_video = 1;
+ session->media.remote_video_rtp_port = m->port;
+ session->media.remote_video_rtcp_port = m->port+1; /* FIXME We're assuming RTCP is on the next port */
+ if(m->direction == JANUS_SDP_SENDONLY || m->direction == JANUS_SDP_INACTIVE)
+ session->media.video_send = FALSE;
+ else
+ session->media.video_send = TRUE;
+ } else {
+ session->media.video_send = FALSE;
+ }
+ } else {
+ JANUS_LOG(LOG_WARN, "Unsupported media line (not audio/video)\n");
+ temp = temp->next;
+ continue;
+ }
+ if(m->c_addr) {
+ if(update && strcmp(m->c_addr, session->media.remote_ip)) {
+ /* This is an update and an address changed */
+ if(changed)
+ *changed = TRUE;
+ }
+ g_free(session->media.remote_ip);
+ session->media.remote_ip = g_strdup(m->c_addr);
+ }
+ if(update) {
+ /* FIXME This is a session update, we only accept changes in IP/ports */
+ temp = temp->next;
+ continue;
+ }
+ GList *tempA = m->attributes;
+ while(tempA) {
+ janus_sdp_attribute *a = (janus_sdp_attribute *)tempA->data;
+ if(a->name) {
+ if(!strcasecmp(a->name, "crypto")) {
+ if(m->type == JANUS_SDP_AUDIO || m->type == JANUS_SDP_VIDEO) {
+ gint32 tag = 0;
+ int suite;
+ char crypto[81];
+ /* FIXME inline can be more complex than that, and we're currently only offering SHA1_80 */
+ int res = sscanf(a->value, "%"SCNi32" AES_CM_128_HMAC_SHA1_%2d inline:%80s",
+ &tag, &suite, crypto);
+ if(res != 3) {
+ JANUS_LOG(LOG_WARN, "Failed to parse crypto line, ignoring... %s\n", a->value);
+ } else {
+ gboolean video = (m->type == JANUS_SDP_VIDEO);
+ int current_suite = video ? session->media.video_srtp_suite_in : session->media.audio_srtp_suite_in;
+ if(current_suite == 0) {
+ if(video)
+ session->media.video_srtp_suite_in = suite;
+ else
+ session->media.audio_srtp_suite_in = suite;
+ janus_nosip_srtp_set_remote(session, video, crypto, suite);
+ session->media.has_srtp_remote = TRUE;
+ } else {
+ JANUS_LOG(LOG_WARN, "We already configured a %s crypto context (AES_CM_128_HMAC_SHA1_%d), skipping additional crypto line\n",
+ video ? "video" : "audio", current_suite);
+ }
+ }
+ }
+ }
+ }
+ tempA = tempA->next;
+ }
+ if(answer && (m->type == JANUS_SDP_AUDIO || m->type == JANUS_SDP_VIDEO)) {
+ /* Check which codec was negotiated eventually */
+ int pt = -1;
+ if(m->ptypes)
+ pt = GPOINTER_TO_INT(m->ptypes->data);
+ if(pt > -1) {
+ if(m->type == JANUS_SDP_AUDIO) {
+ session->media.audio_pt = pt;
+ } else {
+ session->media.video_pt = pt;
+ }
+ }
+ }
+ temp = temp->next;
+ }
+ if(changed && *changed) {
+ /* Something changed: mark this on the session, so that the thread can update the sockets */
+ session->media.updated = TRUE;
+ if(session->media.pipefd[1] > 0) {
+ int code = 1;
+ ssize_t res = 0;
+ do {
+ res = write(session->media.pipefd[1], &code, sizeof(int));
+ } while(res == -1 && errno == EINTR);
+ }
+ }
+}
+
+char *janus_nosip_sdp_manipulate(janus_nosip_session *session, janus_sdp *sdp, gboolean answer) {
+ if(!session || !sdp)
+ return NULL;
+ /* Start replacing stuff */
+ JANUS_LOG(LOG_VERB, "Setting protocol to %s\n", session->media.require_srtp ? "RTP/SAVP" : "RTP/AVP");
+ GList *temp = sdp->m_lines;
+ while(temp) {
+ janus_sdp_mline *m = (janus_sdp_mline *)temp->data;
+ g_free(m->proto);
+ m->proto = g_strdup(session->media.require_srtp ? "RTP/SAVP" : "RTP/AVP");
+ if(m->type == JANUS_SDP_AUDIO) {
+ m->port = session->media.local_audio_rtp_port;
+ if(session->media.has_srtp_local) {
+ char *crypto = NULL;
+ session->media.audio_srtp_suite_out = 80;
+ janus_nosip_srtp_set_local(session, FALSE, &crypto);
+ /* FIXME 32? 80? Both? */
+ janus_sdp_attribute *a = janus_sdp_attribute_create("crypto", "1 AES_CM_128_HMAC_SHA1_80 inline:%s", crypto);
+ g_free(crypto);
+ m->attributes = g_list_append(m->attributes, a);
+ }
+ } else if(m->type == JANUS_SDP_VIDEO) {
+ m->port = session->media.local_video_rtp_port;
+ if(session->media.has_srtp_local) {
+ char *crypto = NULL;
+ session->media.audio_srtp_suite_out = 80;
+ janus_nosip_srtp_set_local(session, TRUE, &crypto);
+ /* FIXME 32? 80? Both? */
+ janus_sdp_attribute *a = janus_sdp_attribute_create("crypto", "1 AES_CM_128_HMAC_SHA1_80 inline:%s", crypto);
+ g_free(crypto);
+ m->attributes = g_list_append(m->attributes, a);
+ }
+ }
+ g_free(m->c_addr);
+ m->c_addr = g_strdup(local_ip);
+ if(answer && (m->type == JANUS_SDP_AUDIO || m->type == JANUS_SDP_VIDEO)) {
+ /* Check which codec was negotiated eventually */
+ int pt = -1;
+ if(m->ptypes)
+ pt = GPOINTER_TO_INT(m->ptypes->data);
+ if(pt > -1) {
+ if(m->type == JANUS_SDP_AUDIO) {
+ session->media.audio_pt = pt;
+ } else {
+ session->media.video_pt = pt;
+ }
+ }
+ }
+ temp = temp->next;
+ }
+ /* Generate a SDP string out of our changes */
+ return janus_sdp_write(sdp);
+}
+
+/* Bind local RTP/RTCP sockets */
+static int janus_nosip_allocate_local_ports(janus_nosip_session *session) {
+ if(session == NULL) {
+ JANUS_LOG(LOG_ERR, "Invalid session\n");
+ return -1;
+ }
+ /* Reset status */
+ if(session->media.audio_rtp_fd != -1) {
+ close(session->media.audio_rtp_fd);
+ session->media.audio_rtp_fd = -1;
+ }
+ if(session->media.audio_rtcp_fd != -1) {
+ close(session->media.audio_rtcp_fd);
+ session->media.audio_rtcp_fd = -1;
+ }
+ session->media.local_audio_rtp_port = 0;
+ session->media.local_audio_rtcp_port = 0;
+ session->media.audio_ssrc = 0;
+ if(session->media.video_rtp_fd != -1) {
+ close(session->media.video_rtp_fd);
+ session->media.video_rtp_fd = -1;
+ }
+ if(session->media.video_rtcp_fd != -1) {
+ close(session->media.video_rtcp_fd);
+ session->media.video_rtcp_fd = -1;
+ }
+ session->media.local_video_rtp_port = 0;
+ session->media.local_video_rtcp_port = 0;
+ session->media.video_ssrc = 0;
+ if(session->media.pipefd[0] > 0) {
+ close(session->media.pipefd[0]);
+ session->media.pipefd[0] = -1;
+ }
+ if(session->media.pipefd[1] > 0) {
+ close(session->media.pipefd[1]);
+ session->media.pipefd[1] = -1;
+ }
+ /* Start */
+ int attempts = 100; /* FIXME Don't retry forever */
+ if(session->media.has_audio) {
+ JANUS_LOG(LOG_VERB, "Allocating audio ports:\n");
+ struct sockaddr_in audio_rtp_address, audio_rtcp_address;
+ while(session->media.local_audio_rtp_port == 0 || session->media.local_audio_rtcp_port == 0) {
+ if(attempts == 0) /* Too many failures */
+ return -1;
+ if(session->media.audio_rtp_fd == -1) {
+ session->media.audio_rtp_fd = socket(AF_INET, SOCK_DGRAM, 0);
+ }
+ if(session->media.audio_rtcp_fd == -1) {
+ session->media.audio_rtcp_fd = socket(AF_INET, SOCK_DGRAM, 0);
+ }
+ int rtp_port = g_random_int_range(10000, 60000); /* FIXME Should this be configurable? */
+ if(rtp_port % 2)
+ rtp_port++; /* Pick an even port for RTP */
+ audio_rtp_address.sin_family = AF_INET;
+ audio_rtp_address.sin_port = htons(rtp_port);
+ inet_pton(AF_INET, local_ip, &audio_rtp_address.sin_addr.s_addr);
+ if(bind(session->media.audio_rtp_fd, (struct sockaddr *)(&audio_rtp_address), sizeof(struct sockaddr)) < 0) {
+ JANUS_LOG(LOG_ERR, "Bind failed for audio RTP (port %d), trying a different one...\n", rtp_port);
+ attempts--;
+ continue;
+ }
+ JANUS_LOG(LOG_VERB, "Audio RTP listener bound to port %d\n", rtp_port);
+ int rtcp_port = rtp_port+1;
+ audio_rtcp_address.sin_family = AF_INET;
+ audio_rtcp_address.sin_port = htons(rtcp_port);
+ inet_pton(AF_INET, local_ip, &audio_rtcp_address.sin_addr.s_addr);
+ if(bind(session->media.audio_rtcp_fd, (struct sockaddr *)(&audio_rtcp_address), sizeof(struct sockaddr)) < 0) {
+ JANUS_LOG(LOG_ERR, "Bind failed for audio RTCP (port %d), trying a different one...\n", rtcp_port);
+ /* RTP socket is not valid anymore, reset it */
+ close(session->media.audio_rtp_fd);
+ session->media.audio_rtp_fd = -1;
+ attempts--;
+ continue;
+ }
+ JANUS_LOG(LOG_VERB, "Audio RTCP listener bound to port %d\n", rtcp_port);
+ session->media.local_audio_rtp_port = rtp_port;
+ session->media.local_audio_rtcp_port = rtcp_port;
+ }
+ }
+ if(session->media.has_video) {
+ JANUS_LOG(LOG_VERB, "Allocating video ports:\n");
+ struct sockaddr_in video_rtp_address, video_rtcp_address;
+ while(session->media.local_video_rtp_port == 0 || session->media.local_video_rtcp_port == 0) {
+ if(attempts == 0) /* Too many failures */
+ return -1;
+ if(session->media.video_rtp_fd == -1) {
+ session->media.video_rtp_fd = socket(AF_INET, SOCK_DGRAM, 0);
+ }
+ if(session->media.video_rtcp_fd == -1) {
+ session->media.video_rtcp_fd = socket(AF_INET, SOCK_DGRAM, 0);
+ }
+ int rtp_port = g_random_int_range(10000, 60000); /* FIXME Should this be configurable? */
+ if(rtp_port % 2)
+ rtp_port++; /* Pick an even port for RTP */
+ video_rtp_address.sin_family = AF_INET;
+ video_rtp_address.sin_port = htons(rtp_port);
+ inet_pton(AF_INET, local_ip, &video_rtp_address.sin_addr.s_addr);
+ if(bind(session->media.video_rtp_fd, (struct sockaddr *)(&video_rtp_address), sizeof(struct sockaddr)) < 0) {
+ JANUS_LOG(LOG_ERR, "Bind failed for video RTP (port %d), trying a different one...\n", rtp_port);
+ attempts--;
+ continue;
+ }
+ JANUS_LOG(LOG_VERB, "Video RTP listener bound to port %d\n", rtp_port);
+ int rtcp_port = rtp_port+1;
+ video_rtcp_address.sin_family = AF_INET;
+ video_rtcp_address.sin_port = htons(rtcp_port);
+ inet_pton(AF_INET, local_ip, &video_rtcp_address.sin_addr.s_addr);
+ if(bind(session->media.video_rtcp_fd, (struct sockaddr *)(&video_rtcp_address), sizeof(struct sockaddr)) < 0) {
+ JANUS_LOG(LOG_ERR, "Bind failed for video RTCP (port %d), trying a different one...\n", rtcp_port);
+ /* RTP socket is not valid anymore, reset it */
+ close(session->media.video_rtp_fd);
+ session->media.video_rtp_fd = -1;
+ attempts--;
+ continue;
+ }
+ JANUS_LOG(LOG_VERB, "Video RTCP listener bound to port %d\n", rtcp_port);
+ session->media.local_video_rtp_port = rtp_port;
+ session->media.local_video_rtcp_port = rtcp_port;
+ }
+ }
+ /* We need this to quickly interrupt the poll when it's time to update a session or wrap up */
+ pipe(session->media.pipefd);
+ return 0;
+}
+
+/* Helper method to (re)connect RTP/RTCP sockets */
+static void janus_nosip_connect_sockets(janus_nosip_session *session, struct sockaddr_in *server_addr) {
+ if(!session || !server_addr)
+ return;
+
+ if(session->media.updated) {
+ JANUS_LOG(LOG_VERB, "Updating session sockets\n");
+ }
+
+ /* Connect peers (FIXME This pretty much sucks right now) */
+ if(session->media.remote_audio_rtp_port) {
+ server_addr->sin_port = htons(session->media.remote_audio_rtp_port);
+ if(connect(session->media.audio_rtp_fd, (struct sockaddr *)server_addr, sizeof(struct sockaddr)) == -1) {
+ JANUS_LOG(LOG_ERR, "[NoSIP-%s] Couldn't connect audio RTP? (%s:%d)\n", session->info, session->media.remote_ip, session->media.remote_audio_rtp_port);
+ JANUS_LOG(LOG_ERR, "[NoSIP-%s] -- %d (%s)\n", session->info, errno, strerror(errno));
+ }
+ }
+ if(session->media.remote_audio_rtcp_port) {
+ server_addr->sin_port = htons(session->media.remote_audio_rtcp_port);
+ if(connect(session->media.audio_rtcp_fd, (struct sockaddr *)server_addr, sizeof(struct sockaddr)) == -1) {
+ JANUS_LOG(LOG_ERR, "[NoSIP-%s] Couldn't connect audio RTCP? (%s:%d)\n", session->info, session->media.remote_ip, session->media.remote_audio_rtcp_port);
+ JANUS_LOG(LOG_ERR, "[NoSIP-%s] -- %d (%s)\n", session->info, errno, strerror(errno));
+ }
+ }
+ if(session->media.remote_video_rtp_port) {
+ server_addr->sin_port = htons(session->media.remote_video_rtp_port);
+ if(connect(session->media.video_rtp_fd, (struct sockaddr *)server_addr, sizeof(struct sockaddr)) == -1) {
+ JANUS_LOG(LOG_ERR, "[NoSIP-%s] Couldn't connect video RTP? (%s:%d)\n", session->info, session->media.remote_ip, session->media.remote_video_rtp_port);
+ JANUS_LOG(LOG_ERR, "[NoSIP-%s] -- %d (%s)\n", session->info, errno, strerror(errno));
+ }
+ }
+ if(session->media.remote_video_rtcp_port) {
+ server_addr->sin_port = htons(session->media.remote_video_rtcp_port);
+ if(connect(session->media.video_rtcp_fd, (struct sockaddr *)server_addr, sizeof(struct sockaddr)) == -1) {
+ JANUS_LOG(LOG_ERR, "[NoSIP-%s] Couldn't connect video RTCP? (%s:%d)\n", session->info, session->media.remote_ip, session->media.remote_video_rtcp_port);
+ JANUS_LOG(LOG_ERR, "[NoSIP-%s] -- %d (%s)\n", session->info, errno, strerror(errno));
+ }
+ }
+
+}
+
+/* Thread to relay RTP/RTCP frames coming from the peer */
+static void *janus_nosip_relay_thread(void *data) {
+ janus_nosip_session *session = (janus_nosip_session *)data;
+ if(!session || !session->info) {
+ g_thread_unref(g_thread_self());
+ return NULL;
+ }
+ JANUS_LOG(LOG_INFO, "[NoSIP-%s] Starting relay thread\n", session->info);
+
+ gboolean have_server_ip = TRUE;
+ struct sockaddr_in server_addr;
+ memset(&server_addr, 0, sizeof(server_addr));
+ server_addr.sin_family = AF_INET;
+ if(session->media.remote_ip == NULL) {
+ JANUS_LOG(LOG_WARN, "[NoSIP-%s] No remote IP?\n", session->info);
+ } else {
+ if((inet_aton(session->media.remote_ip, &server_addr.sin_addr)) <= 0) { /* Not a numeric IP... */
+ struct hostent *host = gethostbyname(session->media.remote_ip); /* ...resolve name */
+ if(!host) {
+ JANUS_LOG(LOG_ERR, "[NoSIP-%s] Couldn't get host (%s)\n", session->info, session->media.remote_ip);
+ have_server_ip = FALSE;
+ } else {
+ server_addr.sin_addr = *(struct in_addr *)host->h_addr_list;
+ }
+ }
+ }
+ if(have_server_ip)
+ janus_nosip_connect_sockets(session, &server_addr);
+
+ /* File descriptors */
+ socklen_t addrlen;
+ struct sockaddr_in remote;
+ int resfd = 0, bytes = 0;
+ struct pollfd fds[5];
+ int pipe_fd = session->media.pipefd[0];
+ char buffer[1500];
+ memset(buffer, 0, 1500);
+ /* Loop */
+ int num = 0;
+ gboolean goon = TRUE;
+ int astep = 0, vstep = 0;
+ guint32 ats = 0, vts = 0;
+ while(goon && session != NULL && !session->destroyed) { /* FIXME We need a per-call watchdog as well */
+ if(session->media.updated) {
+ /* Apparently there was a session update */
+ if(have_server_ip && (inet_aton(session->media.remote_ip, &server_addr.sin_addr)) <= 0) {
+ janus_nosip_connect_sockets(session, &server_addr);
+ } else {
+ JANUS_LOG(LOG_ERR, "[NoSIP-%s] Couldn't update session details (missing or invalid remote IP address)\n", session->info);
+ }
+ session->media.updated = FALSE;
+ }
+
+ /* Prepare poll */
+ num = 0;
+ if(session->media.audio_rtp_fd != -1) {
+ fds[num].fd = session->media.audio_rtp_fd;
+ fds[num].events = POLLIN;
+ fds[num].revents = 0;
+ num++;
+ }
+ if(session->media.audio_rtcp_fd != -1) {
+ fds[num].fd = session->media.audio_rtcp_fd;
+ fds[num].events = POLLIN;
+ fds[num].revents = 0;
+ num++;
+ }
+ if(session->media.video_rtp_fd != -1) {
+ fds[num].fd = session->media.video_rtp_fd;
+ fds[num].events = POLLIN;
+ fds[num].revents = 0;
+ num++;
+ }
+ if(session->media.video_rtcp_fd != -1) {
+ fds[num].fd = session->media.video_rtcp_fd;
+ fds[num].events = POLLIN;
+ fds[num].revents = 0;
+ num++;
+ }
+ if(pipe_fd != -1) {
+ fds[num].fd = pipe_fd;
+ fds[num].events = POLLIN;
+ fds[num].revents = 0;
+ num++;
+ }
+ /* Wait for some data */
+ resfd = poll(fds, num, 1000);
+ if(resfd < 0) {
+ JANUS_LOG(LOG_ERR, "[NoSIP-%s] Error polling...\n", session->info);
+ JANUS_LOG(LOG_ERR, "[NoSIP-%s] -- %d (%s)\n", session->info, errno, strerror(errno));
+ break;
+ } else if(resfd == 0) {
+ /* No data, keep going */
+ continue;
+ }
+ if(session == NULL || session->destroyed)
+ break;
+ int i = 0;
+ for(i=0; i<num; i++) {
+ if(fds[i].revents & (POLLERR | POLLHUP)) {
+ /* Socket error? */
+ JANUS_LOG(LOG_ERR, "[NoSIP-%s] Error polling: %s...\n", session->info,
+ fds[i].revents & POLLERR ? "POLLERR" : "POLLHUP");
+ JANUS_LOG(LOG_ERR, "[NoSIP-%s] -- %d (%s)\n", session->info, errno, strerror(errno));
+ if(session->media.updated)
+ break;
+ goon = FALSE; /* Can we assume it's pretty much over, after a POLLERR? */
+ /* FIXME Simulate a "hangup" coming from the browser */
+ janus_nosip_message *msg = g_malloc0(sizeof(janus_nosip_message));
+ msg->handle = session->handle;
+ msg->message = json_pack("{ss}", "request", "hangup");
+ msg->transaction = NULL;
+ msg->jsep = NULL;
+ g_async_queue_push(messages, msg);
+ break;
+ } else if(fds[i].revents & POLLIN) {
+ if(pipe_fd != -1 && fds[i].fd == pipe_fd) {
+ /* Poll interrupted for a reason, go on */
+ int code = 0;
+ bytes = read(pipe_fd, &code, sizeof(int));
+ break;
+ }
+ /* Got an RTP/RTCP packet */
+ if(session->media.audio_rtp_fd != -1 && fds[i].fd == session->media.audio_rtp_fd) {
+ /* Got something audio (RTP) */
+ addrlen = sizeof(remote);
+ bytes = recvfrom(session->media.audio_rtp_fd, buffer, 1500, 0, (struct sockaddr*)&remote, &addrlen);
+ rtp_header *header = (rtp_header *)buffer;
+ if(session->media.audio_ssrc_peer != ntohl(header->ssrc)) {
+ session->media.audio_ssrc_peer = ntohl(header->ssrc);
+ JANUS_LOG(LOG_VERB, "Got SIP peer audio SSRC: %"SCNu32"\n", session->media.audio_ssrc_peer);
+ }
+ /* Is this SRTP? */
+ if(session->media.has_srtp_remote) {
+ int buflen = bytes;
+ srtp_err_status_t res = srtp_unprotect(session->media.audio_srtp_in, buffer, &buflen);
+ if(res != srtp_err_status_ok && res != srtp_err_status_replay_fail && res != srtp_err_status_replay_old) {
+ guint32 timestamp = ntohl(header->timestamp);
+ guint16 seq = ntohs(header->seq_number);
+ JANUS_LOG(LOG_ERR, "[NoSIP-%s] Audio SRTP unprotect error: %s (len=%d-->%d, ts=%"SCNu32", seq=%"SCNu16")\n",
+ session->info, janus_nosip_get_srtp_error(res), bytes, buflen, timestamp, seq);
+ continue;
+ }
+ bytes = buflen;
+ }
+ /* Check if the SSRC changed (e.g., after a re-INVITE or UPDATE) */
+ guint32 ssrc = ntohl(header->ssrc);
+ guint32 timestamp = ntohl(header->timestamp);
+ guint16 seq = ntohs(header->seq_number);
+ if(ssrc != session->media.context.a_last_ssrc) {
+ JANUS_LOG(LOG_VERB, "Audio SSRC changed (re-INVITE?), %"SCNu32" --> %"SCNu32"\n",
+ session->media.context.a_last_ssrc, ssrc);
+ session->media.context.a_last_ssrc = ssrc;
+ session->media.context.a_base_ts_prev = session->media.context.a_last_ts;
+ session->media.context.a_base_ts = timestamp;
+ session->media.context.a_base_seq_prev = session->media.context.a_last_seq;
+ session->media.context.a_base_seq = seq;
+ }
+ /* Compute a coherent timestamp and sequence number */
+ session->media.context.a_last_ts = (timestamp-session->media.context.a_base_ts)
+ + session->media.context.a_base_ts_prev+(astep ? astep : 960); /* FIXME */
+ session->media.context.a_last_seq = (seq-session->media.context.a_base_seq)+session->media.context.a_base_seq_prev+1;
+ /* Update the timestamp and sequence number in the RTP packet, and send it */
+ header->timestamp = htonl(session->media.context.a_last_ts);
+ header->seq_number = htons(session->media.context.a_last_seq);
+ if(ats == 0) {
+ ats = timestamp;
+ } else if(astep == 0) {
+ astep = timestamp-ats;
+ if(astep < 0)
+ astep = 0;
+ }
+ /* Save the frame if we're recording */
+ janus_recorder_save_frame(session->arc_peer, buffer, bytes);
+ /* Relay to browser */
+ gateway->relay_rtp(session->handle, 0, buffer, bytes);
+ continue;
+ } else if(session->media.audio_rtcp_fd != -1 && fds[i].fd == session->media.audio_rtcp_fd) {
+ /* Got something audio (RTCP) */
+ addrlen = sizeof(remote);
+ bytes = recvfrom(session->media.audio_rtcp_fd, buffer, 1500, 0, (struct sockaddr*)&remote, &addrlen);
+ //~ JANUS_LOG(LOG_VERB, "************************\nGot %d bytes on the audio RTCP channel...\n", bytes);
+ /* Is this SRTCP? */
+ if(session->media.has_srtp_remote) {
+ int buflen = bytes;
+ srtp_err_status_t res = srtp_unprotect_rtcp(session->media.audio_srtp_in, buffer, &buflen);
+ if(res != srtp_err_status_ok && res != srtp_err_status_replay_fail && res != srtp_err_status_replay_old) {
+ JANUS_LOG(LOG_ERR, "[NoSIP-%s] Audio SRTCP unprotect error: %s (len=%d-->%d)\n",
+ session->info, janus_nosip_get_srtp_error(res), bytes, buflen);
+ continue;
+ }
+ bytes = buflen;
+ }
+ /* Relay to browser */
+ gateway->relay_rtcp(session->handle, 0, buffer, bytes);
+ continue;
+ } else if(session->media.video_rtp_fd != -1 && fds[i].fd == session->media.video_rtp_fd) {
+ /* Got something video (RTP) */
+ addrlen = sizeof(remote);
+ bytes = recvfrom(session->media.video_rtp_fd, buffer, 1500, 0, (struct sockaddr*)&remote, &addrlen);
+ //~ JANUS_LOG(LOG_VERB, "************************\nGot %d bytes on the video RTP channel...\n", bytes);
+ //~ rtp_header_t *rtp = (rtp_header_t *)buffer;
+ //~ JANUS_LOG(LOG_VERB, " ... parsed RTP packet (ssrc=%u, pt=%u, seq=%u, ts=%u)...\n",
+ //~ ntohl(rtp->ssrc), rtp->type, ntohs(rtp->seq_number), ntohl(rtp->timestamp));
+ rtp_header *header = (rtp_header *)buffer;
+ if(session->media.video_ssrc_peer != ntohl(header->ssrc)) {
+ session->media.video_ssrc_peer = ntohl(header->ssrc);
+ JANUS_LOG(LOG_VERB, "Got SIP peer video SSRC: %"SCNu32"\n", session->media.video_ssrc_peer);
+ }
+ /* Is this SRTP? */
+ if(session->media.has_srtp_remote) {
+ int buflen = bytes;
+ srtp_err_status_t res = srtp_unprotect(session->media.video_srtp_in, buffer, &buflen);
+ if(res != srtp_err_status_ok && res != srtp_err_status_replay_fail && res != srtp_err_status_replay_old) {
+ guint32 timestamp = ntohl(header->timestamp);
+ guint16 seq = ntohs(header->seq_number);
+ JANUS_LOG(LOG_ERR, "[NoSIP-%s] Video SRTP unprotect error: %s (len=%d-->%d, ts=%"SCNu32", seq=%"SCNu16")\n",
+ session->info, janus_nosip_get_srtp_error(res), bytes, buflen, timestamp, seq);
+ continue;
+ }
+ bytes = buflen;
+ }
+ /* Check if the SSRC changed (e.g., after a re-INVITE or UPDATE) */
+ guint32 ssrc = ntohl(header->ssrc);
+ guint32 timestamp = ntohl(header->timestamp);
+ guint16 seq = ntohs(header->seq_number);
+ if(ssrc != session->media.context.v_last_ssrc) {
+ JANUS_LOG(LOG_VERB, "Video SSRC changed (re-INVITE?)\n");
+ session->media.context.v_last_ssrc = ssrc;
+ session->media.context.v_base_ts_prev = session->media.context.v_last_ts;
+ session->media.context.v_base_ts = timestamp;
+ session->media.context.v_base_seq_prev = session->media.context.v_last_seq;
+ session->media.context.v_base_seq = seq;
+ }
+ /* Compute a coherent timestamp and sequence number */
+ session->media.context.v_last_ts = (timestamp-session->media.context.v_base_ts)
+ + session->media.context.v_base_ts_prev+(vstep ? vstep : 4500); /* FIXME */
+ session->media.context.v_last_seq = (seq-session->media.context.v_base_seq)+session->media.context.v_base_seq_prev+1;
+ /* Update the timestamp and sequence number in the RTP packet, and send it */
+ header->timestamp = htonl(session->media.context.v_last_ts);
+ header->seq_number = htons(session->media.context.v_last_seq);
+ if(vts == 0) {
+ vts = timestamp;
+ } else if(vstep == 0) {
+ vstep = timestamp-vts;
+ if(vstep < 0)
+ vstep = 0;
+ }
+ /* Save the frame if we're recording */
+ janus_recorder_save_frame(session->vrc_peer, buffer, bytes);
+ /* Relay to browser */
+ gateway->relay_rtp(session->handle, 1, buffer, bytes);
+ continue;
+ } else if(session->media.video_rtcp_fd != -1 && fds[i].fd == session->media.video_rtcp_fd) {
+ /* Got something video (RTCP) */
+ addrlen = sizeof(remote);
+ bytes = recvfrom(session->media.video_rtcp_fd, buffer, 1500, 0, (struct sockaddr*)&remote, &addrlen);
+ //~ JANUS_LOG(LOG_VERB, "************************\nGot %d bytes on the video RTCP channel...\n", bytes);
+ /* Is this SRTCP? */
+ if(session->media.has_srtp_remote) {
+ int buflen = bytes;
+ srtp_err_status_t res = srtp_unprotect_rtcp(session->media.video_srtp_in, buffer, &buflen);
+ if(res != srtp_err_status_ok && res != srtp_err_status_replay_fail && res != srtp_err_status_replay_old) {
+ JANUS_LOG(LOG_ERR, "[NoSIP-%s] Video SRTP unprotect error: %s (len=%d-->%d)\n",
+ session->info, janus_nosip_get_srtp_error(res), bytes, buflen);
+ continue;
+ }
+ bytes = buflen;
+ }
+ /* Relay to browser */
+ gateway->relay_rtcp(session->handle, 1, buffer, bytes);
+ continue;
+ }
+ }
+ }
+ }
+ if(session->media.audio_rtp_fd != -1) {
+ close(session->media.audio_rtp_fd);
+ session->media.audio_rtp_fd = -1;
+ }
+ if(session->media.audio_rtcp_fd != -1) {
+ close(session->media.audio_rtcp_fd);
+ session->media.audio_rtcp_fd = -1;
+ }
+ session->media.local_audio_rtp_port = 0;
+ session->media.local_audio_rtcp_port = 0;
+ session->media.audio_ssrc = 0;
+ if(session->media.video_rtp_fd != -1) {
+ close(session->media.video_rtp_fd);
+ session->media.video_rtp_fd = -1;
+ }
+ if(session->media.video_rtcp_fd != -1) {
+ close(session->media.video_rtcp_fd);
+ session->media.video_rtcp_fd = -1;
+ }
+ session->media.local_video_rtp_port = 0;
+ session->media.local_video_rtcp_port = 0;
+ session->media.video_ssrc = 0;
+ if(session->media.pipefd[0] > 0) {
+ close(session->media.pipefd[0]);
+ session->media.pipefd[0] = -1;
+ }
+ if(session->media.pipefd[1] > 0) {
+ close(session->media.pipefd[1]);
+ session->media.pipefd[1] = -1;
+ }
+ /* Clean up SRTP stuff, if needed */
+ janus_nosip_srtp_cleanup(session);
+ /* Done */
+ JANUS_LOG(LOG_INFO, "Leaving NoSIP relay thread\n");
+ g_thread_unref(g_thread_self());
+ return NULL;
+}
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-voip/janus.git
More information about the Pkg-voip-commits
mailing list