[Pkg-voip-commits] [janus] 21/163: Added simulcasting support to VideoCall plugin
Jonas Smedegaard
dr at jones.dk
Sat Oct 28 01:22:04 UTC 2017
This is an automated email from the git hooks/post-receive script.
js pushed a commit to annotated tag debian/0.2.5-1
in repository janus.
commit 5709ba670969b771e9e6232b9df492a09e387c85
Author: Lorenzo Miniero <lminiero at gmail.com>
Date: Mon Jul 10 19:02:06 2017 +0200
Added simulcasting support to VideoCall plugin
---
html/videocalltest.html | 14 +++
html/videocalltest.js | 152 ++++++++++++++++++++++++++++
plugins/janus_videocall.c | 245 ++++++++++++++++++++++++++++++++++++++++++++--
3 files changed, 404 insertions(+), 7 deletions(-)
diff --git a/html/videocalltest.html b/html/videocalltest.html
index 26f890c..feec836 100644
--- a/html/videocalltest.html
+++ b/html/videocalltest.html
@@ -11,6 +11,7 @@
<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="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.3/toastr.min.js"></script>
<script type="text/javascript" src="janus.js" ></script>
<script type="text/javascript" src="videocalltest.js"></script>
<script>
@@ -25,6 +26,7 @@
<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"/>
+<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.3/toastr.css"/>
</head>
<body>
@@ -58,6 +60,18 @@
your audio and video, and a knob to try and limit your bandwidth. If
the browser supports it, you'll also get a view of the bandwidth
currently used by your peer for the video stream.</p>
+ <p>If you're interested in testing how simulcasting can be used within
+ the context of this sample videocall application, just pass the
+ <code>?simulcast=true</code> query string to the url of this page and
+ reload it. If you're using a browser that does support simulcasting
+ (Chrome or Firefox) and the call will end up using VP8, you'll
+ send multiple qualities of the video you're capturing. Notice that
+ simulcasting will only occur if the browser thinks there is enough
+ bandwidth, so you'll have to play with the Bandwidth selector to
+ increase it. New buttons to play with the feature will automatically
+ appear for your peer; at the same time, if your peer enabled simulcasting
+ new buttons will appear for you when watching the remote stream. Notice that
+ no simulcast support is needed for watching, only for publishing.</p>
<p>A very simple chat based on Data Channels is available as well:
just use the text area under your local video to send messages
to your peer. Incoming messages will be displayed below the
diff --git a/html/videocalltest.js b/html/videocalltest.js
index c993fbd..e8da66d 100644
--- a/html/videocalltest.js
+++ b/html/videocalltest.js
@@ -62,6 +62,9 @@ var videoenabled = false;
var myusername = null;
var yourusername = null;
+var doSimulcast = (getQueryStringValue("simulcast") === "yes" || getQueryStringValue("simulcast") === "true");
+var simulcastStarted = false;
+
$(document).ready(function() {
// Initialize the library (console debug enabled)
Janus.init({debug: true, callback: function() {
@@ -181,6 +184,10 @@ $(document).ready(function() {
jsep: jsep,
// No media provided: by default, it's sendrecv for audio and video
media: { data: true }, // Let's negotiate data channels as well
+ // If you want to test simulcasting (Chrome and Firefox only), then
+ // pass a ?simulcast=true when opening this demo page: it will turn
+ // the following 'simulcast' property to pass to janus.js to true
+ simulcast: doSimulcast,
success: function(jsep) {
Janus.debug("Got SDP!");
Janus.debug(jsep);
@@ -240,6 +247,18 @@ $(document).ready(function() {
$('#bitrate').attr('disabled', true);
$('#curbitrate').hide();
$('#curres').hide();
+ } else if(event === "simulcast") {
+ // Is simulcast in place?
+ var substream = result["substream"];
+ var temporal = result["temporal"];
+ if((substream !== null && substream !== undefined) || (temporal !== null && temporal !== undefined)) {
+ if(!simulcastStarted) {
+ simulcastStarted = true;
+ addSimulcastButtons();
+ }
+ // We just received notice that there's been a switch, update the buttons
+ updateSimulcastButtons(substream, temporal);
+ }
}
}
} else {
@@ -415,6 +434,8 @@ $(document).ready(function() {
bitrateTimer = null;
$('#waitingvideo').remove();
$('#videos').hide();
+ simulcastStarted = false;
+ $('#simulcast').remove();
$('#peer').removeAttr('disabled').val('');
$('#call').removeAttr('disabled').html('Call')
.removeClass("btn-danger").addClass("btn-success")
@@ -494,6 +515,10 @@ function doCall() {
{
// By default, it's sendrecv for audio and video...
media: { data: true }, // ... let's negotiate data channels as well
+ // If you want to test simulcasting (Chrome and Firefox only), then
+ // pass a ?simulcast=true when opening this demo page: it will turn
+ // the following 'simulcast' property to pass to janus.js to true
+ simulcast: doSimulcast,
success: function(jsep) {
Janus.debug("Got SDP!");
Janus.debug(jsep);
@@ -528,3 +553,130 @@ function sendData() {
success: function() { $('#datasend').val(''); },
});
}
+
+// Helper to parse query string
+function getQueryStringValue(name) {
+ name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
+ var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
+ results = regex.exec(location.search);
+ return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
+}
+
+// Helpers to create Simulcast-related UI, if enabled
+function addSimulcastButtons() {
+ $('#curres').parent().append(
+ '<div id="simulcast" class="btn-group-vertical btn-group-vertical-xs pull-right">' +
+ ' <div class"row">' +
+ ' <div class="btn-group btn-group-xs" style="width: 100%">' +
+ ' <button id="sl-2" type="button" class="btn btn-primary" data-toggle="tooltip" title="Switch to higher quality" style="width: 33%">SL 2</button>' +
+ ' <button id="sl-1" type="button" class="btn btn-primary" data-toggle="tooltip" title="Switch to normal quality" style="width: 33%">SL 1</button>' +
+ ' <button id="sl-0" type="button" class="btn btn-primary" data-toggle="tooltip" title="Switch to lower quality" style="width: 34%">SL 0</button>' +
+ ' </div>' +
+ ' </div>' +
+ ' <div class"row">' +
+ ' <div class="btn-group btn-group-xs" style="width: 100%">' +
+ ' <button id="tl-2" type="button" class="btn btn-primary" data-toggle="tooltip" title="Cap to temporal layer 2" style="width: 34%">TL 2</button>' +
+ ' <button id="tl-1" type="button" class="btn btn-primary" data-toggle="tooltip" title="Cap to temporal layer 1" style="width: 33%">TL 1</button>' +
+ ' <button id="tl-0" type="button" class="btn btn-primary" data-toggle="tooltip" title="Cap to temporal layer 0" style="width: 33%">TL 0</button>' +
+ ' </div>' +
+ ' </div>' +
+ '</div>');
+ // Enable the VP8 simulcast selection buttons
+ $('#sl-0').removeClass('btn-primary btn-success').addClass('btn-primary')
+ .unbind('click').click(function() {
+ toastr.info("Switching simulcast substream, wait for it... (lower quality)", null, {timeOut: 2000});
+ if(!$('#sl-2').hasClass('btn-success'))
+ $('#sl-2').removeClass('btn-primary btn-info').addClass('btn-primary');
+ if(!$('#sl-1').hasClass('btn-success'))
+ $('#sl-1').removeClass('btn-primary btn-info').addClass('btn-primary');
+ $('#sl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
+ videocall.send({message: { request: "set", substream: 0 }});
+ });
+ $('#sl-1').removeClass('btn-primary btn-success').addClass('btn-primary')
+ .unbind('click').click(function() {
+ toastr.info("Switching simulcast substream, wait for it... (normal quality)", null, {timeOut: 2000});
+ if(!$('#sl-2').hasClass('btn-success'))
+ $('#sl-2').removeClass('btn-primary btn-info').addClass('btn-primary');
+ $('#sl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
+ if(!$('#sl-0').hasClass('btn-success'))
+ $('#sl-0').removeClass('btn-primary btn-info').addClass('btn-primary');
+ videocall.send({message: { request: "set", substream: 1 }});
+ });
+ $('#sl-2').removeClass('btn-primary btn-success').addClass('btn-primary')
+ .unbind('click').click(function() {
+ toastr.info("Switching simulcast substream, wait for it... (higher quality)", null, {timeOut: 2000});
+ $('#sl-2').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
+ if(!$('#sl-1').hasClass('btn-success'))
+ $('#sl-1').removeClass('btn-primary btn-info').addClass('btn-primary');
+ if(!$('#sl-0').hasClass('btn-success'))
+ $('#sl-0').removeClass('btn-primary btn-info').addClass('btn-primary');
+ videocall.send({message: { request: "set", substream: 2 }});
+ });
+ $('#tl-0').removeClass('btn-primary btn-success').addClass('btn-primary')
+ .unbind('click').click(function() {
+ toastr.info("Capping simulcast temporal layer, wait for it... (lowest FPS)", null, {timeOut: 2000});
+ if(!$('#tl-2').hasClass('btn-success'))
+ $('#tl-2').removeClass('btn-primary btn-info').addClass('btn-primary');
+ if(!$('#tl-1').hasClass('btn-success'))
+ $('#tl-1').removeClass('btn-primary btn-info').addClass('btn-primary');
+ $('#tl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
+ videocall.send({message: { request: "set", temporal: 0 }});
+ });
+ $('#tl-1').removeClass('btn-primary btn-success').addClass('btn-primary')
+ .unbind('click').click(function() {
+ toastr.info("Capping simulcast temporal layer, wait for it... (medium FPS)", null, {timeOut: 2000});
+ if(!$('#tl-2').hasClass('btn-success'))
+ $('#tl-2').removeClass('btn-primary btn-info').addClass('btn-primary');
+ $('#tl-1').removeClass('btn-primary btn-info').addClass('btn-info');
+ if(!$('#tl-0').hasClass('btn-success'))
+ $('#tl-0').removeClass('btn-primary btn-info').addClass('btn-primary');
+ videocall.send({message: { request: "set", temporal: 1 }});
+ });
+ $('#tl-2').removeClass('btn-primary btn-success').addClass('btn-primary')
+ .unbind('click').click(function() {
+ toastr.info("Capping simulcast temporal layer, wait for it... (highest FPS)", null, {timeOut: 2000});
+ $('#tl-2').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
+ if(!$('#tl-1').hasClass('btn-success'))
+ $('#tl-1').removeClass('btn-primary btn-info').addClass('btn-primary');
+ if(!$('#tl-0').hasClass('btn-success'))
+ $('#tl-0').removeClass('btn-primary btn-info').addClass('btn-primary');
+ videocall.send({message: { request: "set", temporal: 2 }});
+ });
+}
+
+function updateSimulcastButtons(substream, temporal) {
+ // Check the substream
+ if(substream === 0) {
+ toastr.success("Switched simulcast substream! (lower quality)", null, {timeOut: 2000});
+ $('#sl-2').removeClass('btn-primary btn-success').addClass('btn-primary');
+ $('#sl-1').removeClass('btn-primary btn-success').addClass('btn-primary');
+ $('#sl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
+ } else if(substream === 1) {
+ toastr.success("Switched simulcast substream! (normal quality)", null, {timeOut: 2000});
+ $('#sl-2').removeClass('btn-primary btn-success').addClass('btn-primary');
+ $('#sl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
+ $('#sl-0').removeClass('btn-primary btn-success').addClass('btn-primary');
+ } else if(substream === 2) {
+ toastr.success("Switched simulcast substream! (higher quality)", null, {timeOut: 2000});
+ $('#sl-2').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
+ $('#sl-1').removeClass('btn-primary btn-success').addClass('btn-primary');
+ $('#sl-0').removeClass('btn-primary btn-success').addClass('btn-primary');
+ }
+ // Check the temporal layer
+ if(temporal === 0) {
+ toastr.success("Capped simulcast temporal layer! (lowest FPS)", null, {timeOut: 2000});
+ $('#tl-2').removeClass('btn-primary btn-success').addClass('btn-primary');
+ $('#tl-1').removeClass('btn-primary btn-success').addClass('btn-primary');
+ $('#tl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
+ } else if(temporal === 1) {
+ toastr.success("Capped simulcast temporal layer! (medium FPS)", null, {timeOut: 2000});
+ $('#tl-2').removeClass('btn-primary btn-success').addClass('btn-primary');
+ $('#tl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
+ $('#tl-0').removeClass('btn-primary btn-success').addClass('btn-primary');
+ } else if(temporal === 2) {
+ toastr.success("Capped simulcast temporal layer! (highest FPS)", null, {timeOut: 2000});
+ $('#tl-2').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
+ $('#tl-1').removeClass('btn-primary btn-success').addClass('btn-primary');
+ $('#tl-0').removeClass('btn-primary btn-success').addClass('btn-primary');
+ }
+}
diff --git a/plugins/janus_videocall.c b/plugins/janus_videocall.c
index f713cec..aac5279 100644
--- a/plugins/janus_videocall.c
+++ b/plugins/janus_videocall.c
@@ -236,6 +236,7 @@
#include "../config.h"
#include "../mutex.h"
#include "../record.h"
+#include "../rtp.h"
#include "../rtcp.h"
#include "../utils.h"
@@ -357,13 +358,25 @@ typedef struct janus_videocall_session {
gchar *username;
gboolean has_audio;
gboolean has_video;
+ gboolean has_data;
gboolean audio_active;
gboolean video_active;
uint64_t bitrate;
guint16 slowlink_count;
struct janus_videocall_session *peer;
+ janus_rtp_switching_context context;
+ uint32_t ssrc[3]; /* Only needed in case VP8 simulcasting is involved */
+ int rtpmapid_extmap_id; /* Only needed in case Firefox's RID-based simulcasting is involved */
+ char *rid[3]; /* Only needed in case Firefox's RID-based simulcasting is involved */
+ int substream; /* Which simulcast substream we should forward back */
+ int substream_target; /* As above, but to handle transitions (e.g., wait for keyframe) */
+ int templayer; /* Which simulcast temporal layer we should forward back */
+ int templayer_target; /* As above, but to handle transitions (e.g., wait for keyframe) */
+ gint64 last_relayed; /* When we relayed the last packet (used to detect when substreams become unavailable) */
+ janus_vp8_simulcast_context simulcast_context;
janus_recorder *arc; /* The Janus recorder instance for this user's audio, if enabled */
janus_recorder *vrc; /* The Janus recorder instance for this user's video, if enabled */
+ janus_recorder *drc; /* The Janus recorder instance for this user's data, 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 */
@@ -548,11 +561,22 @@ void janus_videocall_create_session(janus_plugin_session *handle, int *error) {
session->handle = handle;
session->has_audio = FALSE;
session->has_video = FALSE;
+ session->has_data = FALSE;
session->audio_active = TRUE;
session->video_active = TRUE;
session->bitrate = 0; /* No limit */
session->peer = NULL;
session->username = NULL;
+ janus_rtp_switching_context_reset(&session->context);
+ session->ssrc[0] = 0;
+ session->ssrc[1] = 0;
+ session->ssrc[2] = 0;
+ session->substream = -1;
+ session->substream_target = 0;
+ session->templayer = -1;
+ session->templayer_target = 0;
+ session->last_relayed = 0;
+ janus_vp8_simulcast_context_reset(&session->simulcast_context);
janus_mutex_init(&session->rec_mutex);
session->destroyed = 0;
g_atomic_int_set(&session->hangingup, 0);
@@ -608,12 +632,24 @@ json_t *janus_videocall_query_session(janus_plugin_session *handle) {
json_object_set_new(info, "bitrate", json_integer(session->bitrate));
json_object_set_new(info, "slowlink_count", json_integer(session->slowlink_count));
}
- if(session->arc || session->vrc) {
+ if(session->ssrc[0] != 0) {
+ json_object_set_new(info, "simulcast", json_true());
+ }
+ if(session->peer->ssrc[0] != 0) {
+ json_object_set_new(info, "simulcast-peer", json_true());
+ json_object_set_new(info, "substream", json_integer(session->substream));
+ json_object_set_new(info, "substream-target", json_integer(session->substream_target));
+ json_object_set_new(info, "temporal-layer", json_integer(session->templayer));
+ json_object_set_new(info, "temporal-layer-target", json_integer(session->templayer_target));
+ }
+ if(session->arc || session->vrc || session->drc) {
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->drc && session->drc->filename)
+ json_object_set_new(recording, "data", json_string(session->drc->filename));
json_object_set_new(info, "recording", recording);
}
json_object_set_new(info, "destroyed", json_integer(session->destroyed));
@@ -665,11 +701,142 @@ void janus_videocall_incoming_rtp(janus_plugin_session *handle, int video, char
}
if(session->destroyed || session->peer->destroyed)
return;
- if((!video && session->audio_active) || (video && session->video_active)) {
+ if(video && session->video_active && session->rtpmapid_extmap_id != -1) {
+ /* FIXME Just a way to debug Firefox simulcasting */
+ rtp_header *header = (rtp_header *)buf;
+ uint32_t seq_number = ntohs(header->seq_number);
+ uint32_t timestamp = ntohl(header->timestamp);
+ uint32_t ssrc = ntohl(header->ssrc);
+ char sdes_item[16];
+ if(janus_rtp_header_extension_parse_rtp_stream_id(buf, len, session->rtpmapid_extmap_id, sdes_item, sizeof(sdes_item)) == 0) {
+ JANUS_LOG(LOG_DBG, "%"SCNu32"/%"SCNu16"/%"SCNu32"/%d: RTP stream ID extension: %s\n",
+ ssrc, seq_number, timestamp, header->padding, sdes_item);
+ }
+ }
+ if(video && session->video_active && session->ssrc[0] != 0) {
+ /* Handle simulcast: don't relay if it's not the SSRC we wanted to handle */
+ rtp_header *header = (rtp_header *)buf;
+ uint32_t seq_number = ntohs(header->seq_number);
+ uint32_t timestamp = ntohl(header->timestamp);
+ uint32_t ssrc = ntohl(header->ssrc);
+ /* Access the packet payload */
+ int plen = 0;
+ char *payload = janus_rtp_payload(buf, len, &plen);
+ if(payload == NULL)
+ return;
+ gboolean switched = FALSE;
+ if(session->peer->substream != session->peer->substream_target) {
+ /* There has been a change: let's wait for a keyframe on the target */
+ int step = (session->peer->substream < 1 && session->peer->substream_target == 2);
+ if((ssrc == session->ssrc[session->peer->substream_target]) || (step && ssrc == session->ssrc[step])) {
+ //~ if(janus_vp8_is_keyframe(payload, plen)) {
+ uint32_t ssrc_old = 0;
+ if(session->peer->substream != -1)
+ ssrc_old = session->ssrc[session->peer->substream];
+ JANUS_LOG(LOG_WARN, "Received keyframe on SSRC %"SCNu32", switching (was %"SCNu32")\n", ssrc, ssrc_old);
+ session->peer->substream = (ssrc == session->ssrc[session->peer->substream_target] ? session->peer->substream_target : step);
+ switched = TRUE;
+ /* Notify the peer */
+ json_t *event = json_object();
+ json_object_set_new(event, "videocall", json_string("event"));
+ json_t *result = json_object();
+ json_object_set_new(result, "event", json_string("simulcast"));
+ json_object_set_new(result, "substream", json_integer(session->peer->substream));
+ json_object_set_new(event, "result", result);
+ gateway->push_event(session->peer->handle, &janus_videocall_plugin, NULL, event, NULL);
+ json_decref(event);
+ //~ } else {
+ //~ JANUS_LOG(LOG_WARN, "Not a keyframe on SSRC %"SCNu32" yet, waiting before switching\n", ssrc);
+ //~ }
+ }
+ }
+ /* If we haven't received our desired substream yet, let's drop temporarily */
+ if(session->last_relayed == 0) {
+ /* Let's start slow */
+ session->last_relayed = janus_get_monotonic_time();
+ } else {
+ /* Check if 250ms went by with no packet relayed */
+ gint64 now = janus_get_monotonic_time();
+ if(now-session->last_relayed >= 250000) {
+ session->last_relayed = now;
+ int substream = session->peer->substream-1;
+ if(substream < 0)
+ substream = 0;
+ if(session->peer->substream != substream) {
+ JANUS_LOG(LOG_WARN, "No packet received on substream %d for a while, falling back to %d\n",
+ session->peer->substream, substream);
+ session->peer->substream = substream;
+ /* Send a PLI to the user */
+ JANUS_LOG(LOG_VERB, "Just (re-)enabled video, sending a PLI to recover it\n");
+ char rtcpbuf[12];
+ memset(rtcpbuf, 0, 12);
+ janus_rtcp_pli((char *)&rtcpbuf, 12);
+ gateway->relay_rtcp(handle, 1, rtcpbuf, 12);
+ /* Notify the peer */
+ json_t *event = json_object();
+ json_object_set_new(event, "videocall", json_string("event"));
+ json_t *result = json_object();
+ json_object_set_new(result, "event", json_string("simulcast"));
+ json_object_set_new(result, "substream", json_integer(session->peer->substream));
+ json_object_set_new(event, "result", result);
+ gateway->push_event(session->peer->handle, &janus_videocall_plugin, NULL, event, NULL);
+ json_decref(event);
+ }
+ }
+ }
+ /* Do we need to drop this? */
+ if(ssrc != session->ssrc[session->peer->substream]) {
+ JANUS_LOG(LOG_HUGE, "Dropping packet (it's from SSRC %"SCNu32", but we're only relaying to the peer the SSRC %"SCNu32" now\n",
+ ssrc, session->ssrc[session->peer->substream]);
+ return;
+ }
+ session->last_relayed = janus_get_monotonic_time();
+ /* Check if there's any temporal scalability to take into account */
+ uint16_t picid = 0;
+ uint8_t tlzi = 0;
+ uint8_t tid = 0;
+ uint8_t ybit = 0;
+ uint8_t keyidx = 0;
+ if(janus_vp8_parse_descriptor(payload, plen, &picid, &tlzi, &tid, &ybit, &keyidx) == 0) {
+ //~ JANUS_LOG(LOG_WARN, "%"SCNu16", %u, %u, %u, %u\n", picid, tlzi, tid, ybit, keyidx);
+ if(session->peer->templayer != session->peer->templayer_target) {
+ /* FIXME We should be smarter in deciding when to switch */
+ session->peer->templayer = session->peer->templayer_target;
+ /* Notify the peer */
+ json_t *event = json_object();
+ json_object_set_new(event, "videocall", json_string("event"));
+ json_t *result = json_object();
+ json_object_set_new(result, "event", json_string("simulcast"));
+ json_object_set_new(result, "temporal", json_integer(session->peer->templayer));
+ json_object_set_new(event, "result", result);
+ gateway->push_event(session->peer->handle, &janus_videocall_plugin, NULL, event, NULL);
+ json_decref(event);
+ }
+ if(tid > session->peer->templayer) {
+ JANUS_LOG(LOG_HUGE, "Dropping packet (it's temporal layer %d, but we're capping at %d)\n",
+ tid, session->peer->templayer);
+ /* We increase the base sequence number, or there will be gaps when delivering later */
+ session->peer->context.v_base_seq++;
+ return;
+ }
+ }
+ /* If we got here, update the RTP header and send the packet */
+ janus_rtp_header_update(header, &session->peer->context, TRUE, 4500);
+ janus_vp8_simulcast_descriptor_update(payload, plen, &session->peer->simulcast_context, switched);
/* Save the frame if we're recording */
- janus_recorder_save_frame(video ? session->vrc : session->arc, buf, len);
- /* Forward the packet to the peer */
+ janus_recorder_save_frame(session->vrc, buf, len);
+ /* Send the frame back */
gateway->relay_rtp(session->peer->handle, video, buf, len);
+ /* Restore header or core statistics will be messed up */
+ header->timestamp = htonl(timestamp);
+ header->seq_number = htons(seq_number);
+ } else {
+ if((!video && session->audio_active) || (video && session->video_active)) {
+ /* Save the frame if we're recording */
+ janus_recorder_save_frame(video ? session->vrc : session->arc, buf, len);
+ /* Forward the packet to the peer */
+ gateway->relay_rtp(session->peer->handle, video, buf, len);
+ }
}
}
}
@@ -689,8 +856,14 @@ void janus_videocall_incoming_rtcp(janus_plugin_session *handle, int video, char
}
if(session->destroyed || session->peer->destroyed)
return;
- if(session->bitrate > 0)
- janus_rtcp_cap_remb(buf, len, session->bitrate);
+ guint64 bitrate = janus_rtcp_get_remb(buf, len);
+ if(bitrate > 0) {
+ /* If a REMB arrived, make sure we cap it to our configuration, and send it as a video RTCP */
+ if(session->bitrate > 0)
+ janus_rtcp_cap_remb(buf, len, session->bitrate);
+ gateway->relay_rtcp(session->peer->handle, 1, buf, len);
+ return;
+ }
gateway->relay_rtcp(session->peer->handle, video, buf, len);
}
}
@@ -804,6 +977,12 @@ void janus_videocall_hangup_media(janus_plugin_session *handle) {
janus_recorder_free(session->vrc);
}
session->vrc = NULL;
+ if(session->drc) {
+ janus_recorder_close(session->drc);
+ JANUS_LOG(LOG_INFO, "Closed data recording %s\n", session->drc->filename ? session->drc->filename : "??");
+ janus_recorder_free(session->drc);
+ }
+ session->drc = NULL;
janus_mutex_unlock(&session->rec_mutex);
if(session->peer) {
/* Send event to our peer too */
@@ -830,9 +1009,11 @@ void janus_videocall_hangup_media(janus_plugin_session *handle) {
/* Reset controls */
session->has_audio = FALSE;
session->has_video = FALSE;
+ session->has_data = FALSE;
session->audio_active = TRUE;
session->video_active = TRUE;
session->bitrate = 0;
+ janus_rtp_switching_context_reset(&session->context);
}
/* Thread to handle incoming messages */
@@ -884,6 +1065,13 @@ static void *janus_videocall_handler(void *data) {
goto error;
const char *msg_sdp_type = json_string_value(json_object_get(msg->jsep, "type"));
const char *msg_sdp = json_string_value(json_object_get(msg->jsep, "sdp"));
+ json_t *msg_simulcast = json_object_get(msg->jsep, "simulcast");
+ if(msg_simulcast) {
+ JANUS_LOG(LOG_WARN, "VideoCall client (%s) is going to do simulcasting\n", session->username);
+ session->ssrc[0] = json_integer_value(json_object_get(msg_simulcast, "ssrc-0"));
+ session->ssrc[1] = json_integer_value(json_object_get(msg_simulcast, "ssrc-1"));
+ session->ssrc[2] = json_integer_value(json_object_get(msg_simulcast, "ssrc-2"));
+ }
json_t *request = json_object_get(root, "request");
const char *request_text = json_string_value(request);
json_t *result = NULL;
@@ -1007,6 +1195,7 @@ static void *janus_videocall_handler(void *data) {
peer->peer = session;
session->has_audio = (strstr(msg_sdp, "m=audio") != NULL);
session->has_video = (strstr(msg_sdp, "m=video") != NULL);
+ session->has_data = (strstr(msg_sdp, "DTLS/SCTP") != NULL);
janus_mutex_unlock(&sessions_mutex);
JANUS_LOG(LOG_VERB, "%s is calling %s\n", session->username, session->peer->username);
JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg_sdp_type, msg_sdp);
@@ -1053,6 +1242,7 @@ static void *janus_videocall_handler(void *data) {
JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg_sdp_type, msg_sdp);
session->has_audio = (strstr(msg_sdp, "m=audio") != NULL);
session->has_video = (strstr(msg_sdp, "m=video") != NULL);
+ session->has_data = (strstr(msg_sdp, "DTLS/SCTP") != NULL);
/* Send SDP to our peer */
json_t *jsep = json_pack("{ssss}", "type", msg_sdp_type, "sdp", msg_sdp);
json_t *call = json_object();
@@ -1075,6 +1265,15 @@ static void *janus_videocall_handler(void *data) {
json_object_set_new(info, "event", json_string("accepted"));
gateway->notify_event(&janus_videocall_plugin, session->handle, info);
}
+ /* Is simulcasting involved on either side? */
+ if(session->ssrc[0] && session->ssrc[1]) {
+ session->peer->substream_target = 2; /* Let's aim for the highest quality */
+ session->peer->templayer_target = 2; /* Let's aim for all temporal layers */
+ }
+ if(session->peer->ssrc[0] && session->peer->ssrc[1]) {
+ session->substream_target = 2; /* Let's aim for the highest quality */
+ session->templayer_target = 2; /* Let's aim for all temporal layers */
+ }
} else if(!strcasecmp(request_text, "set")) {
/* Update the local configuration (audio/video mute/unmute, bitrate cap or recording) */
JANUS_VALIDATE_JSON_OBJECT(root, set_parameters,
@@ -1118,6 +1317,7 @@ static void *janus_videocall_handler(void *data) {
if(msg_sdp) {
session->has_audio = (strstr(msg_sdp, "m=audio") != NULL);
session->has_video = (strstr(msg_sdp, "m=video") != NULL);
+ session->has_data = (strstr(msg_sdp, "DTLS/SCTP") != NULL);
}
gboolean recording = json_is_true(record);
const char *recording_base = json_string_value(recfile);
@@ -1137,6 +1337,12 @@ static void *janus_videocall_handler(void *data) {
janus_recorder_free(session->vrc);
}
session->vrc = NULL;
+ if(session->drc) {
+ janus_recorder_close(session->drc);
+ JANUS_LOG(LOG_INFO, "Closed data recording %s\n", session->drc->filename ? session->drc->filename : "??");
+ janus_recorder_free(session->drc);
+ }
+ session->drc = NULL;
} else {
/* We've started recording, send a PLI and go on */
char filename[255];
@@ -1194,6 +1400,29 @@ static void *janus_videocall_handler(void *data) {
janus_rtcp_pli((char *)&buf, 12);
gateway->relay_rtcp(session->handle, 1, buf, 12);
}
+ if(session->has_data) {
+ memset(filename, 0, 255);
+ if(recording_base) {
+ /* Use the filename and path we have been provided */
+ g_snprintf(filename, 255, "%s-data", recording_base);
+ session->drc = janus_recorder_create(NULL, "text", filename);
+ if(session->drc == NULL) {
+ /* FIXME We should notify the fact the recorder could not be created */
+ JANUS_LOG(LOG_ERR, "Couldn't open a data recording file for this VideoCall user!\n");
+ }
+ } else {
+ /* Build a filename */
+ g_snprintf(filename, 255, "videocall-%s-%s-%"SCNi64"-data",
+ session->username ? session->username : "unknown",
+ (session->peer && session->peer->username) ? session->peer->username : "unknown",
+ now);
+ session->drc = janus_recorder_create(NULL, "text", filename);
+ if(session->drc == NULL) {
+ /* FIXME We should notify the fact the recorder could not be created */
+ JANUS_LOG(LOG_ERR, "Couldn't open a data recording file for this VideoCall user!\n");
+ }
+ }
+ }
}
janus_mutex_unlock(&session->rec_mutex);
}
@@ -1204,12 +1433,14 @@ static void *janus_videocall_handler(void *data) {
json_object_set_new(info, "audio_active", session->audio_active ? json_true() : json_false());
json_object_set_new(info, "video_active", session->video_active ? json_true() : json_false());
json_object_set_new(info, "bitrate", json_integer(session->bitrate));
- if(session->arc || session->vrc) {
+ if(session->arc || session->vrc || session->drc) {
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->drc && session->drc->filename)
+ json_object_set_new(recording, "data", json_string(session->drc->filename));
json_object_set_new(info, "recording", recording);
}
gateway->notify_event(&janus_videocall_plugin, session->handle, info);
--
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