[Pkg-voip-commits] [janus] 07/163: First experiments with VP8 simulcasting (Chrome and Firefox, WIP)
Jonas Smedegaard
dr at jones.dk
Sat Oct 28 01:22:03 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 3e01d6ef6936510821bb699566be3b0cf8d3041a
Author: Lorenzo Miniero <lminiero at gmail.com>
Date: Wed Jun 28 10:53:38 2017 +0200
First experiments with VP8 simulcasting (Chrome and Firefox, WIP)
---
html/devicetest.html | 8 ++
html/devicetest.js | 45 ++++++-
html/echotest.html | 16 ++-
html/echotest.js | 37 ++++++
html/janus.js | 123 +++++++++++++++++-
html/videoroomtest.html | 66 +++++++++-
html/videoroomtest.js | 58 +++++++++
ice.c | 30 ++++-
ice.h | 6 +
janus.c | 42 ++++++-
plugins/janus_echotest.c | 277 +++++++++++++++++++++++++++++++++++++----
plugins/janus_streaming.c | 265 +++++++--------------------------------
plugins/janus_videoroom.c | 143 +++++++++++++++++----
postprocessing/pp-webm.c | 2 +-
rtp.c | 33 ++++-
rtp.h | 13 ++
sdp.c | 130 +++++++++++++++++--
sdp.h | 8 ++
utils.c | 308 ++++++++++++++++++++++++++++++++++++++++++++++
utils.h | 35 ++++++
20 files changed, 1339 insertions(+), 306 deletions(-)
diff --git a/html/devicetest.html b/html/devicetest.html
index 8872892..798a54c 100644
--- a/html/devicetest.html
+++ b/html/devicetest.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="devicetest.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>
@@ -118,6 +120,12 @@
<h3 class="panel-title">Remote Stream
<span class="label label-primary hide" id="curres"></span>
<span class="label label-info hide" id="curbitrate"></span>
+ <div id="simulcast" class="btn-group btn-group-xs pull-right hide">
+ <div class="btn-group btn-group-xs">
+ <button id="sl-1" type="button" class="btn btn-primary" data-toggle="tooltip" title="Switch to higher quality" style="width: 50%">SL 1</button>
+ <button id="sl-0" type="button" class="btn btn-primary" data-toggle="tooltip" title="Switch to lower quality" style="width: 50%">SL 0</button>
+ </div>
+ </div>
<div class="btn-group btn-group-xs pull-right hide" id="output-devices">
<div class="btn-group btn-group-xs">
<button id="outputdeviceset" autocomplete="off" class="btn btn-primary dropdown-toggle" data-toggle="dropdown">
diff --git a/html/devicetest.js b/html/devicetest.js
index 4b63a97..07f5f8c 100644
--- a/html/devicetest.js
+++ b/html/devicetest.js
@@ -58,6 +58,7 @@ var spinner = null;
var audioenabled = false;
var videoenabled = false;
+var simulcastStarted = false;
// Helper method to prepare a UI selection of the available devices
function initDevices(devices) {
@@ -112,12 +113,12 @@ function initDevices(devices) {
// A different device has been selected: hangup the session, and set it up again
$('#audio-device, #video-device').attr('disabled', true);
$('#change-devices').attr('disabled', true);
- echotest.hangup(true);
if(firstTime) {
firstTime = false;
restartCapture();
return;
}
+ echotest.hangup(true);
// Let's wait a couple of seconds before restarting
setTimeout(restartCapture, 2000);
});
@@ -133,7 +134,7 @@ function restartCapture() {
Janus.debug("Trying a createOffer too (audio/video sendrecv)");
echotest.createOffer(
{
- // No media provided: by default, it's sendrecv for audio and video
+ // We provide a specific device ID for both audio and video
media: {
audio: {
deviceId: {
@@ -147,6 +148,9 @@ function restartCapture() {
},
data: true // Let's negotiate data channels as well
},
+ // If you want to test simulcasting (Chrome and Firefox only), then
+ // uncomment the "simulcast:true," line: new buttons will appear
+ simulcast: true,
success: function(jsep) {
Janus.debug("Got SDP!");
Janus.debug(jsep);
@@ -263,6 +267,39 @@ $(document).ready(function() {
$('#outputdeviceset').html('Output device<span class="caret"></span>');
}
}
+ // Is simulcast in place?
+ var simulcast = msg["simulcast"];
+ if(simulcast !== null && simulcast !== undefined) {
+ if(!simulcastStarted) {
+ simulcastStarted = true;
+ $('#simulcast').removeClass('hide');
+ // Enable the VP8 simulcast selection buttons
+ $('#sl-0').removeClass('btn-primary btn-success').addClass('btn-primary')
+ .unbind('click').click(function() {
+ toastr.info("Switching simulcast video, wait for it... (lower quality)", null, {timeOut: 2000});
+ $('#sl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
+ $('#sl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
+ echotest.send({message: { simulcast: 0}});
+ });
+ $('#sl-1').removeClass('btn-primary btn-success').addClass('btn-success')
+ .unbind('click').click(function() {
+ toastr.info("Switching simulcast video, wait for it... (higher quality)", null, {timeOut: 2000});
+ $('#sl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
+ $('#sl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
+ echotest.send({message: { simulcast: 1}});
+ });
+ }
+ // We just received notice that there's been a switch, update the buttons
+ if(simulcast === 0) {
+ toastr.success("Switched simulcast video! (lower quality)", null, {timeOut: 2000});
+ $('#sl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
+ $('#sl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
+ } else if(simulcast === 1) {
+ toastr.success("Switched simulcast video! (higher quality)", null, {timeOut: 2000});
+ $('#sl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
+ $('#sl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
+ }
+ }
},
onlocalstream: function(stream) {
Janus.debug(" ::: Got a local stream :::");
@@ -412,6 +449,10 @@ $(document).ready(function() {
$('#datasend').val('').attr('disabled', true);
$('#datarecv').val('');
$('#outputdeviceset').html('Output device<span class="caret"></span>');
+ simulcastStarted = false;
+ $('#simulcast').addClass('hide');
+ $('#sl-0').unbind('click');
+ $('#sl-1').unbind('click');
}
});
},
diff --git a/html/echotest.html b/html/echotest.html
index 8308399..f9e576a 100644
--- a/html/echotest.html
+++ b/html/echotest.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="echotest.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>
@@ -54,7 +56,10 @@
which will tell the gateway to drop the frames and not echo them
back to you. You can also try and cap the bitrate: such control
will tell the gateway to manipulate the RTCP REMB packets passing
- through, in order to simulate a bandwidth limitation.</p>
+ through, in order to simulate a bandwidth limitation. In case
+ Simulcasting has been enabled, buttons will appear to allow you
+ to switch between lower and higher quality: notice that you may
+ have to increase the bandwidth to have the quality video appear.</p>
<p>Finally, this demo also includes Data Channels: whatever you
write in the text box under your local video, will be sent via
Data Channels to the plugins, modified by adding a fixed prefix,
@@ -104,7 +109,14 @@
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
- <h3 class="panel-title">Remote Stream <span class="label label-primary hide" id="curres"></span> <span class="label label-info hide" id="curbitrate"></span></h3>
+ <h3 class="panel-title">Remote Stream <span class="label label-primary hide" id="curres"></span> <span class="label label-info hide" id="curbitrate"></span>
+ <div id="simulcast" class="btn-group btn-group-xs pull-right hide">
+ <div class="btn-group btn-group-xs">
+ <button id="sl-1" type="button" class="btn btn-primary" data-toggle="tooltip" title="Switch to higher quality" style="width: 50%">SL 1</button>
+ <button id="sl-0" type="button" class="btn btn-primary" data-toggle="tooltip" title="Switch to lower quality" style="width: 50%">SL 0</button>
+ </div>
+ </div>
+ </h3>
</div>
<div class="panel-body" id="videoright"></div>
</div>
diff --git a/html/echotest.js b/html/echotest.js
index f720d0a..c98b866 100644
--- a/html/echotest.js
+++ b/html/echotest.js
@@ -58,6 +58,7 @@ var spinner = null;
var audioenabled = false;
var videoenabled = false;
+var simulcastStarted = false;
$(document).ready(function() {
// Initialize the library (all console debuggers enabled)
@@ -105,6 +106,9 @@ $(document).ready(function() {
{
// 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
+ // uncomment the "simulcast:true," line: new buttons will appear
+ simulcast: true,
success: function(jsep) {
Janus.debug("Got SDP!");
Janus.debug(jsep);
@@ -185,6 +189,39 @@ $(document).ready(function() {
$('#curres').hide();
}
}
+ // Is simulcast in place?
+ var simulcast = msg["simulcast"];
+ if(simulcast !== null && simulcast !== undefined) {
+ if(!simulcastStarted) {
+ simulcastStarted = true;
+ $('#simulcast').removeClass('hide');
+ // Enable the VP8 simulcast selection buttons
+ $('#sl-0').removeClass('btn-primary btn-success').addClass('btn-primary')
+ .unbind('click').click(function() {
+ toastr.info("Switching simulcast video, wait for it... (lower quality)", null, {timeOut: 2000});
+ $('#sl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
+ $('#sl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
+ echotest.send({message: { simulcast: 0}});
+ });
+ $('#sl-1').removeClass('btn-primary btn-success').addClass('btn-success')
+ .unbind('click').click(function() {
+ toastr.info("Switching simulcast video, wait for it... (higher quality)", null, {timeOut: 2000});
+ $('#sl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
+ $('#sl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
+ echotest.send({message: { simulcast: 1}});
+ });
+ }
+ // We just received notice that there's been a switch, update the buttons
+ if(simulcast === 0) {
+ toastr.success("Switched simulcast video! (lower quality)", null, {timeOut: 2000});
+ $('#sl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
+ $('#sl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
+ } else if(simulcast === 1) {
+ toastr.success("Switched simulcast video! (higher quality)", null, {timeOut: 2000});
+ $('#sl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
+ $('#sl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
+ }
+ }
},
onlocalstream: function(stream) {
Janus.debug(" ::: Got a local stream :::");
diff --git a/html/janus.js b/html/janus.js
index 7647dfb..46d1e54 100644
--- a/html/janus.js
+++ b/html/janus.js
@@ -1731,7 +1731,8 @@ function Janus(gatewayCallbacks) {
return;
}
var config = pluginHandle.webrtcStuff;
- Janus.log("Creating offer (iceDone=" + config.iceDone + ")");
+ var simulcast = callbacks.simulcast === true ? true : false;
+ Janus.log("Creating offer (iceDone=" + config.iceDone + ", simulcast=" + simulcast + ")");
// https://code.google.com/p/webrtc/issues/detail?id=3508
var mediaConstraints = null;
if(adapter.browserDetails.browser == "firefox" || adapter.browserDetails.browser == "edge") {
@@ -1748,11 +1749,129 @@ function Janus(gatewayCallbacks) {
};
}
Janus.debug(mediaConstraints);
+ // Check if this is Firefox and we've been asked to do simulcasting
+ if(simulcast && adapter.browserDetails.browser === "firefox") {
+ // FIXME Based on https://gist.github.com/voluntas/088bc3cc62094730647b
+ Janus.log("Enabling Simulcasting for Firefox (RID)");
+ var sender = config.pc.getSenders()[1];
+ Janus.log(sender);
+ var parameters = sender.getParameters();
+ Janus.log(parameters);
+ sender.setParameters({encodings: [
+ { rid: "high", active: true, priority: "high", maxBitrate: 2000000 },
+ { rid: "medium", active: true, priority: "medium", maxBitrate: 200000 }
+ ]});
+ }
config.pc.createOffer(
function(offer) {
Janus.debug(offer);
if(config.mySdp === null || config.mySdp === undefined) {
Janus.log("Setting local description");
+ if(simulcast) {
+ // This SDP munging only works with Chrome
+ if(adapter.browserDetails.browser === "chrome") {
+ Janus.log("Enabling Simulcasting for Chrome (SDP munging)");
+ // Let's munge the SDP to add the attributes for enabling simulcasting
+ // (based on https://gist.github.com/ggarber/a19b4c33510028b9c657)
+ var lines = offer.sdp.split("\r\n");
+ var video = false;
+ var ssrc = [ -1 ], ssrc_fid = -1;
+ var cname = null, msid = null, mslabel = null, label = null;
+ var insertAt = -1;
+ for(var i=0; i<lines.length; i++) {
+ var mline = lines[i].match(/m=(\w+) */);
+ if(mline) {
+ var medium = mline[1];
+ if(medium === "video") {
+ // New video m-line: make sure it's the first one
+ if(ssrc[0] < 0) {
+ video = true;
+ } else {
+ // We're done, let's add the new attributes here
+ insertAt = i;
+ break;
+ }
+ } else {
+ // New non-video m-line: do we have what we were looking for?
+ if(ssrc[0] > -1) {
+ // We're done, let's add the new attributes here
+ insertAt = i;
+ break;
+ }
+ }
+ continue;
+ }
+ var fid = lines[i].match(/a=ssrc-group:FID (\d+) (\d+)/);
+ if(fid) {
+ ssrc[0] = fid[1];
+ ssrc_fid = fid[2];
+ lines.splice(i, 1); i--;
+ continue;
+ }
+ if(ssrc[0]) {
+ var match = lines[i].match('a=ssrc:' + ssrc[0] + ' cname:(.+)')
+ if(match) {
+ cname = match[1];
+ }
+ match = lines[i].match('a=ssrc:' + ssrc[0] + ' msid:(.+)')
+ if(match) {
+ msid = match[1];
+ }
+ match = lines[i].match('a=ssrc:' + ssrc[0] + ' mslabel:(.+)')
+ if(match) {
+ mslabel = match[1];
+ }
+ match = lines[i].match('a=ssrc:' + ssrc + ' label:(.+)')
+ if(match) {
+ label = match[1];
+ }
+ if(lines[i].indexOf('a=ssrc:' + ssrc_fid) === 0) {
+ lines.splice(i, 1); i--;
+ continue;
+ }
+ if(lines[i].indexOf('a=ssrc:' + ssrc[0]) === 0) {
+ lines.splice(i, 1); i--;
+ continue;
+ }
+ }
+ if(lines[i].length == 0) {
+ lines.splice(i, 1); i--;
+ continue;
+ }
+ }
+ if(insertAt < 0) {
+ // Append at the end
+ insertAt = lines.length-1;
+ }
+ // Generate a couple of SSRCs
+ ssrc[1] = Math.floor(Math.random()*0xFFFFFFFF);
+ ssrc[2] = Math.floor(Math.random()*0xFFFFFFFF);
+ // Add attributes to the SDP
+ lines.splice(insertAt, 0, 'a=ssrc-group:SIM ' + ssrc[0] + ' ' + ssrc[1] + ' ' + ssrc[2]);
+ insertAt++;
+ for(var i=0; i<ssrc.length; i++) {
+ if(cname) {
+ lines.splice(insertAt, 0, 'a=ssrc:' + ssrc[i] + ' cname:' + cname);
+ insertAt++;
+ }
+ if(msid) {
+ lines.splice(insertAt, 0, 'a=ssrc:' + ssrc[i] + ' msid:' + msid);
+ insertAt++;
+ }
+ if(mslabel) {
+ lines.splice(insertAt, 0, 'a=ssrc:' + ssrc[i] + ' mslabel:' + msid);
+ insertAt++;
+ }
+ if(label) {
+ lines.splice(insertAt, 0, 'a=ssrc:' + ssrc[i] + ' label:' + msid);
+ insertAt++;
+ }
+ }
+ offer.sdp = lines.join("\r\n");
+ } else if(adapter.browserDetails.browser !== "firefox") {
+ Janus.warn("simulcast=true, but this is not Chrome nor Firefox, ignoring");
+ }
+ }
config.mySdp = offer.sdp;
config.pc.setLocalDescription(offer);
}
@@ -2027,7 +2146,7 @@ function Janus(gatewayCallbacks) {
return config.bitrate.value;
} else if(config.pc.getStats && adapter.browserDetails.browser == "firefox") {
// Do it the Firefox way
- if(config.remoteStream === null || config.remoteStream === undefined
+ if(config.remoteStream === null || config.remoteStream === undefined || config.remoteStream.remoteStreams === undefined
|| config.remoteStream.streams[0] === null || config.remoteStream.streams[0] === undefined) {
Janus.warn("Remote stream unavailable");
return "Remote stream unavailable";
diff --git a/html/videoroomtest.html b/html/videoroomtest.html
index f8a0f3e..4ec8abf 100644
--- a/html/videoroomtest.html
+++ b/html/videoroomtest.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="videoroomtest.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"/>
+<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.3/toastr.css"/>
</head>
<body>
@@ -81,7 +83,24 @@
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
- <h3 class="panel-title">Local Video <span class="label label-primary hide" id="publisher"></span></h3>
+ <h3 class="panel-title">Local Video <span class="label label-primary hide" id="publisher"></span>
+ <div class="btn-group btn-group-xs pull-right hide">
+ <div class="btn-group btn-group-xs">
+ <button id="bitrateset" autocomplete="off" class="btn btn-primary dropdown-toggle" data-toggle="dropdown">
+ Bandwidth<span class="caret"></span>
+ </button>
+ <ul id="bitrate" class="dropdown-menu" role="menu">
+ <li><a href="#" id="0">No limit</a></li>
+ <li><a href="#" id="128">Cap to 128kbit</a></li>
+ <li><a href="#" id="256">Cap to 256kbit</a></li>
+ <li><a href="#" id="512">Cap to 512kbit</a></li>
+ <li><a href="#" id="1024">Cap to 1mbit</a></li>
+ <li><a href="#" id="1500">Cap to 1.5mbit</a></li>
+ <li><a href="#" id="2000">Cap to 2mbit</a></li>
+ </ul>
+ </div>
+ </div>
+ </h3>
</div>
<div class="panel-body" id="videolocal"></div>
</div>
@@ -89,7 +108,14 @@
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
- <h3 class="panel-title">Remote Video #1 <span class="label label-info hide" id="remote1"></span></h3>
+ <h3 class="panel-title">Remote Video #1 <span class="label label-info hide" id="remote1"></span>
+ <div id="simulcast1" class="btn-group btn-group-xs pull-right hide">
+ <div class="btn-group btn-group-xs">
+ <button id="sl1-1" type="button" class="btn btn-primary" style="width: 50%">SL 1</button>
+ <button id="sl1-0" type="button" class="btn btn-primary" style="width: 50%">SL 0</button>
+ </div>
+ </div>
+ </h3>
</div>
<div class="panel-body relative" id="videoremote1"></div>
</div>
@@ -97,7 +123,14 @@
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
- <h3 class="panel-title">Remote Video #2 <span class="label label-info hide" id="remote2"></span></h3>
+ <h3 class="panel-title">Remote Video #2 <span class="label label-info hide" id="remote2"></span>
+ <div id="simulcast2" class="btn-group btn-group-xs pull-right hide">
+ <div class="btn-group btn-group-xs">
+ <button id="sl2-1" type="button" class="btn btn-primary" style="width: 50%">SL 1</button>
+ <button id="sl2-0" type="button" class="btn btn-primary" style="width: 50%">SL 0</button>
+ </div>
+ </div>
+ </h3>
</div>
<div class="panel-body relative" id="videoremote2"></div>
</div>
@@ -107,7 +140,14 @@
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
- <h3 class="panel-title">Remote Video #3 <span class="label label-info hide" id="remote3"></span></h3>
+ <h3 class="panel-title">Remote Video #3 <span class="label label-info hide" id="remote3"></span>
+ <div id="simulcast3" class="btn-group btn-group-xs pull-right hide">
+ <div class="btn-group btn-group-xs">
+ <button id="sl3-1" type="button" class="btn btn-primary" style="width: 50%">SL 1</button>
+ <button id="sl3-0" type="button" class="btn btn-primary" style="width: 50%">SL 0</button>
+ </div>
+ </div>
+ </h3>
</div>
<div class="panel-body relative" id="videoremote3"></div>
</div>
@@ -115,7 +155,14 @@
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
- <h3 class="panel-title">Remote Video #4 <span class="label label-info hide" id="remote4"></span></h3>
+ <h3 class="panel-title">Remote Video #4 <span class="label label-info hide" id="remote4"></span>
+ <div id="simulcast4" class="btn-group btn-group-xs pull-right hide">
+ <div class="btn-group btn-group-xs">
+ <button id="sl4-1" type="button" class="btn btn-primary" style="width: 50%">SL 1</button>
+ <button id="sl4-0" type="button" class="btn btn-primary" style="width: 50%">SL 0</button>
+ </div>
+ </div>
+ </h3>
</div>
<div class="panel-body relative" id="videoremote4"></div>
</div>
@@ -123,7 +170,14 @@
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
- <h3 class="panel-title">Remote Video #5 <span class="label label-info hide" id="remote5"></span></h3>
+ <h3 class="panel-title">Remote Video #5 <span class="label label-info hide" id="remote5"></span>
+ <div id="simulcast5" class="btn-group btn-group-xs pull-right hide">
+ <div class="btn-group btn-group-xs">
+ <button id="sl5-1" type="button" class="btn btn-primary" style="width: 50%">SL 1</button>
+ <button id="sl5-0" type="button" class="btn btn-primary" style="width: 50%">SL 0</button>
+ </div>
+ </div>
+ </h3>
</div>
<div class="panel-body relative" id="videoremote5"></div>
</div>
diff --git a/html/videoroomtest.js b/html/videoroomtest.js
index d139bf9..6903193 100644
--- a/html/videoroomtest.js
+++ b/html/videoroomtest.js
@@ -134,6 +134,20 @@ $(document).ready(function() {
webrtcState: function(on) {
Janus.log("Janus says our WebRTC PeerConnection is " + (on ? "up" : "down") + " now");
$("#videolocal").parent().parent().unblock();
+ // This controls allows us to override the global room bitrate cap
+ $('#bitrate').parent().parent().removeClass('hide').show();
+ $('#bitrate a').click(function() {
+ var id = $(this).attr("id");
+ var bitrate = parseInt(id)*1000;
+ if(bitrate === 0) {
+ Janus.log("Not limiting bandwidth via REMB");
+ } else {
+ Janus.log("Capping bandwidth to " + bitrate + " via REMB");
+ }
+ $('#bitrateset').html($(this).html() + '<span class="caret"></span>').parent().removeClass('open');
+ sfutest.send({"message": { "request": "configure", "bitrate": bitrate }});
+ return false;
+ });
},
onmessage: function(msg, jsep) {
Janus.debug(" ::: Got a message (publisher) :::");
@@ -286,6 +300,8 @@ $(document).ready(function() {
$('#videolocal').html('<button id="publish" class="btn btn-primary">Publish</button>');
$('#publish').click(function() { publishOwnFeed(true); });
$("#videolocal").parent().parent().unblock();
+ $('#bitrate').parent().parent().addClass('hide');
+ $('#bitrate a').unbind('click');
}
});
},
@@ -352,6 +368,10 @@ function publishOwnFeed(useAudio) {
{
// Add data:true here if you want to publish datachannels as well
media: { audioRecv: false, videoRecv: false, audioSend: useAudio, videoSend: true }, // Publishers are sendonly
+ // If you want to test simulcasting (Chrome and Firefox only), then
+ // uncomment the "simulcast:true," line: new buttons will appear
+ // for the viewers subscribing to this stream remotely
+ simulcast: true,
success: function(jsep) {
Janus.debug("Got publisher SDP!");
Janus.debug(jsep);
@@ -397,6 +417,7 @@ function newRemoteFeed(id, display) {
opaqueId: opaqueId,
success: function(pluginHandle) {
remoteFeed = pluginHandle;
+ remoteFeed.simulcastStarted = false;
Janus.log("Plugin attached! (" + remoteFeed.getPlugin() + ", id=" + remoteFeed.getId() + ")");
Janus.log(" -- This is a subscriber");
// We wait for the plugin to send us an offer
@@ -432,6 +453,40 @@ function newRemoteFeed(id, display) {
}
Janus.log("Successfully attached to feed " + remoteFeed.rfid + " (" + remoteFeed.rfdisplay + ") in room " + msg["room"]);
$('#remote'+remoteFeed.rfindex).removeClass('hide').html(remoteFeed.rfdisplay).show();
+ } else if(event === "simulcast") {
+ // We got an event on a simulcast switch from this publisher
+ var simulcast = msg["simulcast"];
+ if(simulcast !== null && simulcast !== undefined) {
+ if(!remoteFeed.simulcastStarted) {
+ remoteFeed.simulcastStarted = true;
+ $('#simulcast'+remoteFeed.rfindex).removeClass('hide');
+ // Enable the VP8 simulcast selection buttons
+ $('#sl'+remoteFeed.rfindex+'-0').removeClass('btn-primary btn-success').addClass('btn-primary')
+ .unbind('click').click(function() {
+ toastr.info("Switching " + remoteFeed.rfdisplay + "'s simulcast video, wait for it... (lower quality)", null, {timeOut: 2000});
+ $('#sl'+remoteFeed.rfindex+'-1').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
+ $('#sl'+remoteFeed.rfindex+'-0').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
+ remoteFeed.send({message: { request: "configure", simulcast: 0}});
+ });
+ $('#sl'+remoteFeed.rfindex+'-1').removeClass('btn-primary btn-success').addClass('btn-success')
+ .unbind('click').click(function() {
+ toastr.info("Switching " + remoteFeed.rfdisplay + "'s simulcast video, wait for it... (higher quality)", null, {timeOut: 2000});
+ $('#sl'+remoteFeed.rfindex+'-1').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
+ $('#sl'+remoteFeed.rfindex+'-0').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
+ remoteFeed.send({message: { request: "configure", simulcast: 1}});
+ });
+ }
+ // We just received notice that there's been a switch, update the buttons
+ if(simulcast === 0) {
+ toastr.success("Switched " + remoteFeed.rfdisplay + "'s simulcast video! (lower quality)", null, {timeOut: 2000});
+ $('#sl'+remoteFeed.rfindex+'-1').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
+ $('#sl'+remoteFeed.rfindex+'-0').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
+ } else if(simulcast === 1) {
+ toastr.success("Switched " + remoteFeed.rfdisplay + "'s simulcast video! (higher quality)", null, {timeOut: 2000});
+ $('#sl'+remoteFeed.rfindex+'-1').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
+ $('#sl'+remoteFeed.rfindex+'-0').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
+ }
+ }
} else if(msg["error"] !== undefined && msg["error"] !== null) {
bootbox.alert(msg["error"]);
} else {
@@ -532,6 +587,9 @@ function newRemoteFeed(id, display) {
if(bitrateTimer[remoteFeed.rfindex] !== null && bitrateTimer[remoteFeed.rfindex] !== null)
clearInterval(bitrateTimer[remoteFeed.rfindex]);
bitrateTimer[remoteFeed.rfindex] = null;
+ $('#sl'+remoteFeed.rfindex+'-1').unbind('click');
+ $('#sl'+remoteFeed.rfindex+'-0').unbind('click');
+ $('#simulcast'+remoteFeed.rfindex).addClass('hide');
}
});
}
diff --git a/ice.c b/ice.c
index 56e7ada..49afd90 100644
--- a/ice.c
+++ b/ice.c
@@ -1329,6 +1329,12 @@ void janus_ice_stream_free(GHashTable *streams, janus_ice_stream *stream) {
g_free(stream->rpass);
stream->rpass = NULL;
}
+ g_free(stream->rid[0]);
+ stream->rid[0] = NULL;
+ g_free(stream->rid[1]);
+ stream->rid[1] = NULL;
+ g_free(stream->rid[2]);
+ stream->rid[2] = NULL;
g_list_free(stream->audio_payload_types);
stream->audio_payload_types = NULL;
g_list_free(stream->video_payload_types);
@@ -1896,7 +1902,10 @@ void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint component_i
} else {
/* Bundled streams, check SSRC */
guint32 packet_ssrc = ntohl(header->ssrc);
- video = ((stream->video_ssrc_peer == packet_ssrc || stream->video_ssrc_peer_rtx == packet_ssrc) ? 1 : 0);
+ video = ((stream->video_ssrc_peer == packet_ssrc
+ || stream->video_ssrc_peer_rtx == packet_ssrc
+ || stream->video_ssrc_peer_sim_1 == packet_ssrc
+ || stream->video_ssrc_peer_sim_2 == packet_ssrc) ? 1 : 0);
if(!video && stream->audio_ssrc_peer != packet_ssrc) {
/* FIXME In case it happens, we should check what it is */
if(stream->audio_ssrc_peer == 0 || stream->video_ssrc_peer == 0) {
@@ -1941,6 +1950,12 @@ void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint component_i
/* FIXME This is a video retransmission: set the regular peer SSRC so
* that we avoid outgoing SRTP errors in case we got the packet already */
header->ssrc = htonl(stream->video_ssrc_peer);
+ } else if(stream->video_ssrc_peer_sim_1 == packet_ssrc) {
+ /* FIXME Simulcast (1) */
+ JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Simulcast #1 (SSRC %"SCNu32")...\n", handle->handle_id, packet_ssrc);
+ } else if(stream->video_ssrc_peer_sim_2 == packet_ssrc) {
+ /* FIXME Simulcast (2) */
+ JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Simulcast #2 (SSRC %"SCNu32")...\n", handle->handle_id, packet_ssrc);
}
//~ JANUS_LOG(LOG_VERB, "[RTP] Bundling: this is %s (video=%"SCNu64", audio=%"SCNu64", got %ld)\n",
//~ video ? "video" : "audio", stream->video_ssrc_peer, stream->audio_ssrc_peer, ntohl(header->ssrc));
@@ -2015,6 +2030,10 @@ void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint component_i
janus_mutex_unlock(&component->mutex);
}
+ /* FIXME Don't handle RTCP or stats for the simulcasted SSRCs, for now */
+ if(video && ntohl(header->ssrc) != stream->video_ssrc_peer)
+ return;
+
/* Update the RTCP context as well */
rtcp_context *rtcp_ctx = video ? stream->video_rtcp_ctx : stream->audio_rtcp_ctx;
janus_rtcp_process_incoming_rtp(rtcp_ctx, buf, buflen);
@@ -2180,6 +2199,7 @@ void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint component_i
}
}
}
+
/* Let's process this RTCP (compound?) packet, and update the RTCP context for this stream in case */
rtcp_context *rtcp_ctx = video ? stream->video_rtcp_ctx : stream->audio_rtcp_ctx;
janus_rtcp_parse(rtcp_ctx, buf, buflen);
@@ -2826,7 +2846,9 @@ int janus_ice_setup_local(janus_ice_handle *handle, int offer, int audio, int vi
audio_stream->video_ssrc = janus_random_uint32(); /* FIXME Should we look for conflicts? */
}
audio_stream->video_ssrc_peer = 0; /* FIXME Right now we don't know what this will be */
- audio_stream->video_ssrc_peer_rtx = 0; /* FIXME Right now we don't know what this will be */
+ audio_stream->video_ssrc_peer_rtx = 0; /* FIXME Right now we don't know if and what this will be */
+ audio_stream->video_ssrc_peer_sim_1 = 0; /* FIXME Right now we don't know if and what this will be */
+ audio_stream->video_ssrc_peer_sim_2 = 0; /* FIXME Right now we don't know if and what this will be */
audio_stream->audio_rtcp_ctx = g_malloc0(sizeof(rtcp_context));
audio_stream->audio_rtcp_ctx->tb = 48000; /* May change later */
audio_stream->video_rtcp_ctx = g_malloc0(sizeof(rtcp_context));
@@ -2979,7 +3001,9 @@ int janus_ice_setup_local(janus_ice_handle *handle, int offer, int audio, int vi
video_stream->dtls_role = offer ? JANUS_DTLS_ROLE_CLIENT : JANUS_DTLS_ROLE_ACTPASS;
video_stream->video_ssrc = janus_random_uint32(); /* FIXME Should we look for conflicts? */
video_stream->video_ssrc_peer = 0; /* FIXME Right now we don't know what this will be */
- video_stream->video_ssrc_peer_rtx = 0; /* FIXME Right now we don't know what this will be */
+ video_stream->video_ssrc_peer_rtx = 0; /* FIXME Right now we don't know if and what this will be */
+ video_stream->video_ssrc_peer_sim_1 = 0; /* FIXME Right now we don't know if and what this will be */
+ video_stream->video_ssrc_peer_sim_2 = 0; /* FIXME Right now we don't know if and what this will be */
video_stream->audio_ssrc = 0;
video_stream->audio_ssrc_peer = 0;
video_stream->video_rtcp_ctx = g_malloc0(sizeof(rtcp_context));
diff --git a/ice.h b/ice.h
index f1fb4f8..351b862 100644
--- a/ice.h
+++ b/ice.h
@@ -359,6 +359,12 @@ struct janus_ice_stream {
guint32 video_ssrc_peer;
/*! \brief Video retransmissions SSRC of the peer for this stream (may be bundled) */
guint32 video_ssrc_peer_rtx;
+ /*! \brief Video SSRC (simulcasted 1) of the peer for this stream (may be bundled) */
+ guint32 video_ssrc_peer_sim_1;
+ /*! \brief Video SSRC (simulcasted 2) of the peer for this stream (may be bundled) */
+ guint32 video_ssrc_peer_sim_2;
+ /*! \brief Array of RTP Stream IDs (for Firefox simulcasting, if enabled) */
+ char *rid[3];
/*! \brief List of payload types we can expect for audio */
GList *audio_payload_types;
/*! \brief List of payload types we can expect for video */
diff --git a/janus.c b/janus.c
index 8b24050..06b0447 100644
--- a/janus.c
+++ b/janus.c
@@ -1119,6 +1119,8 @@ int janus_process_incoming_request(janus_request *request) {
handle->audio_stream->video_ssrc = handle->video_stream->video_ssrc;
handle->audio_stream->video_ssrc_peer = handle->video_stream->video_ssrc_peer;
handle->audio_stream->video_ssrc_peer_rtx = handle->video_stream->video_ssrc_peer_rtx;
+ handle->audio_stream->video_ssrc_peer_sim_1 = handle->video_stream->video_ssrc_peer_sim_1;
+ handle->audio_stream->video_ssrc_peer_sim_2 = handle->video_stream->video_ssrc_peer_sim_2;
nice_agent_attach_recv(handle->agent, handle->video_stream->stream_id, 1, g_main_loop_get_context (handle->iceloop), NULL, NULL);
if(!handle->force_rtcp_mux && !janus_ice_is_rtcpmux_forced())
nice_agent_attach_recv(handle->agent, handle->video_stream->stream_id, 2, g_main_loop_get_context (handle->iceloop), NULL, NULL);
@@ -1345,9 +1347,30 @@ int janus_process_incoming_request(janus_request *request) {
/* Send the message to the plugin (which must eventually free transaction_text and unref the two objects, body and jsep) */
json_incref(body);
+ json_t *body_jsep = NULL;
+ if(jsep_sdp_stripped) {
+ body_jsep = json_pack("{ssss}", "type", jsep_type, "sdp", jsep_sdp_stripped);
+ /* Check if VP8 simulcasting is enabled */
+ if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_VIDEO)) {
+ if(handle->video_stream && handle->video_stream->video_ssrc_peer_sim_1) {
+ json_t *simulcast = json_object();
+ json_object_set(simulcast, "ssrc-0", json_integer(handle->video_stream->video_ssrc_peer));
+ json_object_set(simulcast, "ssrc-1", json_integer(handle->video_stream->video_ssrc_peer_sim_1));
+ if(handle->video_stream->video_ssrc_peer_sim_2)
+ json_object_set(simulcast, "ssrc-2", json_integer(handle->video_stream->video_ssrc_peer_sim_2));
+ json_object_set(body_jsep, "simulcast", simulcast);
+ } else if(handle->audio_stream && handle->audio_stream->video_ssrc_peer_sim_1) {
+ json_t *simulcast = json_object();
+ json_object_set(simulcast, "ssrc-0", json_integer(handle->audio_stream->video_ssrc_peer));
+ json_object_set(simulcast, "ssrc-1", json_integer(handle->audio_stream->video_ssrc_peer_sim_1));
+ if(handle->audio_stream->video_ssrc_peer_sim_2)
+ json_object_set(simulcast, "ssrc-2", json_integer(handle->audio_stream->video_ssrc_peer_sim_2));
+ json_object_set(body_jsep, "simulcast", simulcast);
+ }
+ }
+ };
janus_plugin_result *result = plugin_t->handle_message(handle->app_handle,
- g_strdup((char *)transaction_text), body,
- jsep_sdp_stripped ? json_pack("{ssss}", "type", jsep_type, "sdp", jsep_sdp_stripped) : NULL);
+ g_strdup((char *)transaction_text), body, body_jsep);
g_free(jsep_type);
g_free(jsep_sdp_stripped);
if(result == NULL) {
@@ -2346,6 +2369,19 @@ json_t *janus_admin_stream_summary(janus_ice_stream *stream) {
json_object_set_new(ss, "video-peer", json_integer(stream->video_ssrc_peer));
if(stream->video_ssrc_peer_rtx)
json_object_set_new(ss, "video-peer-rtx", json_integer(stream->video_ssrc_peer_rtx));
+ if(stream->video_ssrc_peer_sim_1)
+ json_object_set_new(ss, "video-peer-sim-1", json_integer(stream->video_ssrc_peer_sim_1));
+ if(stream->video_ssrc_peer_sim_2)
+ json_object_set_new(ss, "video-peer-sim-2", json_integer(stream->video_ssrc_peer_sim_2));
+ if(stream->rid[0]) {
+ json_t *rid = json_array();
+ json_array_append_new(rid, json_string(stream->rid[0]));
+ if(stream->rid[1])
+ json_array_append_new(rid, json_string(stream->rid[1]));
+ if(stream->rid[1])
+ json_array_append_new(rid, json_string(stream->rid[2]));
+ json_object_set_new(ss, "rid", rid);
+ }
json_object_set_new(s, "ssrc", ss);
json_t *components = json_array();
if(stream->rtp_component) {
@@ -2875,6 +2911,8 @@ json_t *janus_plugin_handle_sdp(janus_plugin_session *plugin_session, janus_plug
ice_handle->audio_stream->video_ssrc = ice_handle->video_stream->video_ssrc;
ice_handle->audio_stream->video_ssrc_peer = ice_handle->video_stream->video_ssrc_peer;
ice_handle->audio_stream->video_ssrc_peer_rtx = ice_handle->video_stream->video_ssrc_peer_rtx;
+ ice_handle->audio_stream->video_ssrc_peer_sim_1 = ice_handle->video_stream->video_ssrc_peer_sim_1;
+ ice_handle->audio_stream->video_ssrc_peer_sim_2 = ice_handle->video_stream->video_ssrc_peer_sim_2;
nice_agent_attach_recv(ice_handle->agent, ice_handle->video_stream->stream_id, 1, g_main_loop_get_context (ice_handle->iceloop), NULL, NULL);
if(!ice_handle->force_rtcp_mux && !janus_ice_is_rtcpmux_forced())
nice_agent_attach_recv(ice_handle->agent, ice_handle->video_stream->stream_id, 2, g_main_loop_get_context (ice_handle->iceloop), NULL, NULL);
diff --git a/plugins/janus_echotest.c b/plugins/janus_echotest.c
index 7fbb3d1..34ea725 100644
--- a/plugins/janus_echotest.c
+++ b/plugins/janus_echotest.c
@@ -92,6 +92,7 @@
#include "../config.h"
#include "../mutex.h"
#include "../record.h"
+#include "../rtp.h"
#include "../rtcp.h"
#include "../sdp-utils.h"
#include "../utils.h"
@@ -185,8 +186,15 @@ typedef struct janus_echotest_session {
gboolean audio_active;
gboolean video_active;
uint64_t bitrate;
+ 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 simulcast; /* Which simulcast "layer" we should forward back */
+ int simulcast_target; /* As above, but to handle transitions (e.g., wait for keyframe) */
+ 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 *vrc[3]; /* The Janus recorder instance for this user's video, if enabled (there may be more if we're simulcasting) */
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 */
guint16 slowlink_count;
@@ -387,6 +395,13 @@ void janus_echotest_create_session(janus_plugin_session *handle, int *error) {
session->video_active = TRUE;
janus_mutex_init(&session->rec_mutex);
session->bitrate = 0; /* No limit */
+ janus_rtp_switching_context_reset(&session->context);
+ session->ssrc[0] = 0;
+ session->ssrc[1] = 0;
+ session->ssrc[2] = 0;
+ session->simulcast = -1;
+ session->simulcast_target = 0;
+ janus_vp8_simulcast_context_reset(&session->simulcast_context);
session->destroyed = 0;
g_atomic_int_set(&session->hangingup, 0);
handle->plugin_handle = session;
@@ -434,12 +449,18 @@ json_t *janus_echotest_query_session(janus_plugin_session *handle) {
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 || session->drc) {
+ if(session->ssrc[0] != 0)
+ json_object_set_new(info, "simulcast", json_true());
+ if(session->arc || session->vrc[0] || 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->vrc[0] && session->vrc[0]->filename)
+ json_object_set_new(recording, "video", json_string(session->vrc[0]->filename));
+ if(session->vrc[1] && session->vrc[1]->filename)
+ json_object_set_new(recording, "video-sim1", json_string(session->vrc[1]->filename));
+ if(session->vrc[2] && session->vrc[2]->filename)
+ json_object_set_new(recording, "video-sim2", json_string(session->vrc[2]->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);
@@ -494,11 +515,74 @@ void janus_echotest_incoming_rtp(janus_plugin_session *handle, int video, char *
}
if(session->destroyed)
return;
- if((!video && session->audio_active) || (video && session->video_active)) {
+ if(video && session->video_active && session->rtpmapid_extmap_id != -1) {
+ /* FIXME Experiments with 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": RTP stream ID extension: %s\n", ssrc, seq_number, timestamp, 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);
/* Save the frame if we're recording */
- janus_recorder_save_frame(video ? session->vrc : session->arc, buf, len);
+ if(ssrc == session->ssrc[0])
+ janus_recorder_save_frame(session->vrc[0], buf, len);
+ else if(ssrc == session->ssrc[1])
+ janus_recorder_save_frame(session->vrc[1], buf, len);
+ else if(ssrc == session->ssrc[2])
+ janus_recorder_save_frame(session->vrc[2], buf, len);
+ /* Access the packet payload */
+ int plen = 0;
+ char *payload = janus_rtp_payload(buf, len, &plen);
+ if(payload == NULL)
+ return;
+ gboolean switched = FALSE;
+ if(session->simulcast != session->simulcast_target) {
+ /* There has been a change: let's wait for a keyframe on the target */
+ if(ssrc == session->ssrc[session->simulcast_target]) {
+ if(janus_vp8_is_keyframe(payload, plen)) {
+ JANUS_LOG(LOG_WARN, "Received keyframe on SSRC %"SCNu32", switching (was %"SCNu32")\n",
+ ssrc, session->ssrc[session->simulcast]);
+ session->simulcast = session->simulcast_target;
+ switched = TRUE;
+ /* Notify the user */
+ json_t *event = json_object();
+ json_object_set_new(event, "echotest", json_string("event"));
+ json_object_set_new(event, "simulcast", json_integer(session->simulcast));
+ gateway->push_event(session->handle, &janus_echotest_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(ssrc != session->ssrc[session->simulcast]) {
+ JANUS_LOG(LOG_HUGE, "Dropping packet (it's from SSRC %"SCNu32", but we're only relaying SSRC %"SCNu32" now\n",
+ ssrc, session->ssrc[session->simulcast]);
+ return;
+ }
+ janus_rtp_header_update(header, &session->context, TRUE, 4500);
+ janus_vp8_simulcast_descriptor_update(payload, plen, &session->simulcast_context, switched);
/* Send the frame back */
gateway->relay_rtp(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[0] : session->arc, buf, len);
+ /* Send the frame back */
+ gateway->relay_rtp(handle, video, buf, len);
+ }
}
}
}
@@ -625,12 +709,24 @@ void janus_echotest_hangup_media(janus_plugin_session *handle) {
janus_recorder_free(session->arc);
}
session->arc = NULL;
- if(session->vrc) {
- janus_recorder_close(session->vrc);
- JANUS_LOG(LOG_INFO, "Closed video recording %s\n", session->vrc->filename ? session->vrc->filename : "??");
- janus_recorder_free(session->vrc);
+ if(session->vrc[0]) {
+ janus_recorder_close(session->vrc[0]);
+ JANUS_LOG(LOG_INFO, "Closed video recording %s\n", session->vrc[0]->filename ? session->vrc[0]->filename : "??");
+ janus_recorder_free(session->vrc[0]);
+ }
+ session->vrc[0] = NULL;
+ if(session->vrc[1]) {
+ janus_recorder_close(session->vrc[1]);
+ JANUS_LOG(LOG_INFO, "Closed video recording %s (simulcasting #1)\n", session->vrc[1]->filename ? session->vrc[1]->filename : "??");
+ janus_recorder_free(session->vrc[1]);
+ }
+ session->vrc[1] = NULL;
+ if(session->vrc[2]) {
+ janus_recorder_close(session->vrc[2]);
+ JANUS_LOG(LOG_INFO, "Closed video recording %s (simulcasting #2)\n", session->vrc[2]->filename ? session->vrc[2]->filename : "??");
+ janus_recorder_free(session->vrc[2]);
}
- session->vrc = NULL;
+ session->vrc[2] = NULL;
if(session->drc) {
janus_recorder_close(session->drc);
JANUS_LOG(LOG_INFO, "Closed data recording %s\n", session->drc->filename ? session->drc->filename : "??");
@@ -645,6 +741,11 @@ void janus_echotest_hangup_media(janus_plugin_session *handle) {
session->audio_active = TRUE;
session->video_active = TRUE;
session->bitrate = 0;
+ session->ssrc[0] = 0;
+ session->ssrc[1] = 0;
+ session->ssrc[2] = 0;
+ session->simulcast = -1;
+ session->simulcast_target = 0;
}
/* Thread to handle incoming messages */
@@ -697,6 +798,14 @@ static void *janus_echotest_handler(void *data) {
/* Parse request */
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, "EchoTest client is going to do simulcasting\n");
+ 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"));
+ session->simulcast_target = 0; /* Let's start with low quality */
+ }
json_t *audio = json_object_get(root, "audio");
if(audio && !json_is_boolean(audio)) {
JANUS_LOG(LOG_ERR, "Invalid element (audio should be a boolean)\n");
@@ -718,6 +827,13 @@ static void *janus_echotest_handler(void *data) {
g_snprintf(error_cause, 512, "Invalid value (bitrate should be a positive integer)");
goto error;
}
+ json_t *simulcast = json_object_get(root, "simulcast");
+ if(simulcast && (!json_is_integer(simulcast) || (json_integer_value(simulcast) < 0 && json_integer_value(simulcast) > 2))) {
+ JANUS_LOG(LOG_ERR, "Invalid element (simulcast should be 0, 1 or 2)\n");
+ error_code = JANUS_ECHOTEST_ERROR_INVALID_ELEMENT;
+ g_snprintf(error_cause, 512, "Invalid value (simulcast should be 0, 1 or 2)");
+ goto error;
+ }
json_t *record = json_object_get(root, "record");
if(record && !json_is_boolean(record)) {
JANUS_LOG(LOG_ERR, "Invalid element (record should be a boolean)\n");
@@ -762,6 +878,17 @@ static void *janus_echotest_handler(void *data) {
/* FIXME How should we handle a subsequent "no limit" bitrate? */
}
}
+ if(simulcast) {
+ session->simulcast_target = json_integer_value(simulcast);
+ JANUS_LOG(LOG_VERB, "Setting video SSRC to let through (simulcast): %"SCNu32" (index %d, was %d)\n",
+ session->ssrc[session->simulcast], session->simulcast_target, session->simulcast);
+ /* Send a PLI */
+ JANUS_LOG(LOG_VERB, "Simulcasting change, 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);
+ }
if(record) {
if(msg_sdp) {
session->has_audio = (strstr(msg_sdp, "m=audio") != NULL);
@@ -780,12 +907,24 @@ static void *janus_echotest_handler(void *data) {
janus_recorder_free(session->arc);
}
session->arc = NULL;
- if(session->vrc) {
- janus_recorder_close(session->vrc);
- JANUS_LOG(LOG_INFO, "Closed video recording %s\n", session->vrc->filename ? session->vrc->filename : "??");
- janus_recorder_free(session->vrc);
+ if(session->vrc[0]) {
+ janus_recorder_close(session->vrc[0]);
+ JANUS_LOG(LOG_INFO, "Closed video recording %s\n", session->vrc[0]->filename ? session->vrc[0]->filename : "??");
+ janus_recorder_free(session->vrc[0]);
+ }
+ session->vrc[0] = NULL;
+ if(session->vrc[1]) {
+ janus_recorder_close(session->vrc[1]);
+ JANUS_LOG(LOG_INFO, "Closed video recording %s (simulcasting #1)\n", session->vrc[1]->filename ? session->vrc[1]->filename : "??");
+ janus_recorder_free(session->vrc[1]);
+ }
+ session->vrc[1] = NULL;
+ if(session->vrc[2]) {
+ janus_recorder_close(session->vrc[2]);
+ JANUS_LOG(LOG_INFO, "Closed video recording %s (simulcasting #2)\n", session->vrc[2]->filename ? session->vrc[2]->filename : "??");
+ janus_recorder_free(session->vrc[2]);
}
- session->vrc = NULL;
+ session->vrc[2] = NULL;
if(session->drc) {
janus_recorder_close(session->drc);
JANUS_LOG(LOG_INFO, "Closed data recording %s\n", session->drc->filename ? session->drc->filename : "??");
@@ -823,19 +962,49 @@ static void *janus_echotest_handler(void *data) {
if(recording_base) {
/* Use the filename and path we have been provided */
g_snprintf(filename, 255, "%s-video", recording_base);
- session->vrc = janus_recorder_create(NULL, "vp8", filename);
- if(session->vrc == NULL) {
+ session->vrc[0] = janus_recorder_create(NULL, "vp8", filename);
+ if(session->vrc[0] == 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 EchoTest user!\n");
}
+ if(session->ssrc[0] != 0) {
+ /* Create recordings for the other layers as well */
+ g_snprintf(filename, 255, "%s-video-sim1", recording_base);
+ session->vrc[1] = janus_recorder_create(NULL, "vp8", filename);
+ if(session->vrc[1] == NULL) {
+ /* FIXME We should notify the fact the recorder could not be created */
+ JANUS_LOG(LOG_ERR, "Couldn't open an video recording file (simulcasting #1) for this EchoTest user!\n");
+ }
+ g_snprintf(filename, 255, "%s-video-sim2", recording_base);
+ session->vrc[2] = janus_recorder_create(NULL, "vp8", filename);
+ if(session->vrc[2] == NULL) {
+ /* FIXME We should notify the fact the recorder could not be created */
+ JANUS_LOG(LOG_ERR, "Couldn't open an video recording file (simulcasting #2) for this EchoTest user!\n");
+ }
+ }
} else {
/* Build a filename */
g_snprintf(filename, 255, "echotest-%p-%"SCNi64"-video", session, now);
- session->vrc = janus_recorder_create(NULL, "vp8", filename);
- if(session->vrc == NULL) {
+ session->vrc[0] = janus_recorder_create(NULL, "vp8", filename);
+ if(session->vrc[0] == 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 EchoTest user!\n");
}
+ if(session->ssrc[0] != 0) {
+ /* Create recordings for the other layers as well */
+ g_snprintf(filename, 255, "echotest-%p-%"SCNi64"-video-sim1", session, now);
+ session->vrc[1] = janus_recorder_create(NULL, "vp8", filename);
+ if(session->vrc[1] == NULL) {
+ /* FIXME We should notify the fact the recorder could not be created */
+ JANUS_LOG(LOG_ERR, "Couldn't open an video recording file (simulcasting #1) for this EchoTest user!\n");
+ }
+ g_snprintf(filename, 255, "echotest-%p-%"SCNi64"-video-sim2", session, now);
+ session->vrc[2] = janus_recorder_create(NULL, "vp8", filename);
+ if(session->vrc[2] == NULL) {
+ /* FIXME We should notify the fact the recorder could not be created */
+ JANUS_LOG(LOG_ERR, "Couldn't open an video recording file (simulcasting #2) for this EchoTest user!\n");
+ }
+ }
}
/* Send a PLI */
JANUS_LOG(LOG_VERB, "Recording video, sending a PLI to kickstart it\n");
@@ -875,10 +1044,10 @@ static void *janus_echotest_handler(void *data) {
session->has_data = (strstr(msg_sdp, "DTLS/SCTP") != NULL);
}
- if(!audio && !video && !bitrate && !record && !msg_sdp) {
- JANUS_LOG(LOG_ERR, "No supported attributes (audio, video, bitrate, record, jsep) found\n");
+ if(!audio && !video && !bitrate && !simulcast && !record && !msg_sdp) {
+ JANUS_LOG(LOG_ERR, "No supported attributes (audio, video, bitrate, simulcast, record, jsep) found\n");
error_code = JANUS_ECHOTEST_ERROR_INVALID_ELEMENT;
- g_snprintf(error_cause, 512, "Message error: no supported attributes (audio, video, bitrate, record, jsep) found");
+ g_snprintf(error_cause, 512, "Message error: no supported attributes (audio, video, bitrate, simulcast, record, jsep) found");
goto error;
}
@@ -902,7 +1071,51 @@ static void *janus_echotest_handler(void *data) {
g_snprintf(error_cause, 512, "Error parsing offer: %s", error_str);
goto error;
}
+ /* Check if we need to negotiate the rtp-stream-id extension */
+ session->rtpmapid_extmap_id = -1;
+ janus_sdp_mdirection extmap_mdir = JANUS_SDP_SENDRECV;
+ GList *temp = offer->m_lines;
+ while(temp) {
+ /* Which media are available? */
+ janus_sdp_mline *m = (janus_sdp_mline *)temp->data;
+ if(m->type == JANUS_SDP_VIDEO && m->port > 0) {
+ /* Are the extmaps we care about there? */
+ GList *ma = m->attributes;
+ while(ma) {
+ janus_sdp_attribute *a = (janus_sdp_attribute *)ma->data;
+ if(a->value) {
+ if(strstr(a->value, JANUS_RTP_EXTMAP_RTP_STREAM_ID)) {
+ session->rtpmapid_extmap_id = atoi(a->value);
+ extmap_mdir = a->direction;
+ break;
+ }
+ }
+ ma = ma->next;
+ }
+ }
+ temp = temp->next;
+ }
janus_sdp *answer = janus_sdp_generate_answer(offer, JANUS_SDP_OA_DONE);
+ /* Add the extmap attribute, if needed */
+ if(session->rtpmapid_extmap_id > -1) {
+ /* First of all, let's check if the extmap attribute had a direction */
+ const char *direction = NULL;
+ switch(extmap_mdir) {
+ case JANUS_SDP_SENDONLY:
+ direction = "/recvonly";
+ break;
+ case JANUS_SDP_RECVONLY:
+ case JANUS_SDP_INACTIVE:
+ direction = "/inactive";
+ break;
+ default:
+ direction = "";
+ break;
+ }
+ janus_sdp_attribute *a = janus_sdp_attribute_create("extmap",
+ "%d%s %s\r\n", session->rtpmapid_extmap_id, direction, JANUS_RTP_EXTMAP_RTP_STREAM_ID);
+ janus_sdp_attribute_add_to_mline(janus_sdp_mline_find(answer, JANUS_SDP_VIDEO), a);
+ }
char *sdp = janus_sdp_write(answer);
janus_sdp_free(offer);
janus_sdp_free(answer);
@@ -926,12 +1139,24 @@ static void *janus_echotest_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->ssrc[0] && session->ssrc[1]) {
+ json_t *simulcast = json_object();
+ json_object_set_new(simulcast, "ssrc-0", json_integer(session->ssrc[0]));
+ json_object_set_new(simulcast, "ssrc-1", json_integer(session->ssrc[1]));
+ json_object_set_new(simulcast, "ssrc-2", json_integer(session->ssrc[2]));
+ json_object_set_new(simulcast, "simulcast", json_integer(session->simulcast));
+ json_object_set_new(info, "simulcast", simulcast);
+ }
+ if(session->arc || session->vrc[0]) {
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->vrc[0] && session->vrc[0]->filename)
+ json_object_set_new(recording, "video", json_string(session->vrc[0]->filename));
+ if(session->vrc[1] && session->vrc[1]->filename)
+ json_object_set_new(recording, "video-sim1", json_string(session->vrc[1]->filename));
+ if(session->vrc[2] && session->vrc[2]->filename)
+ json_object_set_new(recording, "video-sim2", json_string(session->vrc[2]->filename));
json_object_set_new(info, "recording", recording);
}
gateway->notify_event(&janus_echotest_plugin, session->handle, info);
diff --git a/plugins/janus_streaming.c b/plugins/janus_streaming.c
index 2ed2213..fafb578 100644
--- a/plugins/janus_streaming.c
+++ b/plugins/janus_streaming.c
@@ -327,7 +327,6 @@ static void *janus_streaming_ondemand_thread(void *data);
static void *janus_streaming_filesource_thread(void *data);
static void janus_streaming_relay_rtp_packet(gpointer data, gpointer user_data);
static void *janus_streaming_relay_thread(void *data);
-static gboolean janus_streaming_is_keyframe(gint codec, char* buffer, int len);
typedef enum janus_streaming_type {
janus_streaming_type_none = 0,
@@ -4067,24 +4066,50 @@ static void *janus_streaming_relay_thread(void *data) {
pkt->seq_number = ntohs(rtp->seq_number);
source->keyframe.temp_keyframe = g_list_append(source->keyframe.temp_keyframe, pkt);
janus_mutex_unlock(&source->keyframe.mutex);
- } else if(janus_streaming_is_keyframe(mountpoint->codecs.video_codec, buffer, bytes)) {
- /* New keyframe, start saving it */
- source->keyframe.temp_ts = ntohl(rtp->timestamp);
- JANUS_LOG(LOG_HUGE, "[%s] New keyframe received! ts=%"SCNu32"\n", name, source->keyframe.temp_ts);
- janus_mutex_lock(&source->keyframe.mutex);
- janus_streaming_rtp_relay_packet *pkt = g_malloc0(sizeof(janus_streaming_rtp_relay_packet));
- pkt->data = g_malloc0(bytes);
- memcpy(pkt->data, buffer, bytes);
- pkt->data->ssrc = htons(1);
- pkt->data->type = mountpoint->codecs.video_pt;
- packet.is_rtp = TRUE;
- packet.is_video = TRUE;
- packet.is_keyframe = TRUE;
- pkt->length = bytes;
- pkt->timestamp = source->keyframe.temp_ts;
- pkt->seq_number = ntohs(rtp->seq_number);
- source->keyframe.temp_keyframe = g_list_append(source->keyframe.temp_keyframe, pkt);
- janus_mutex_unlock(&source->keyframe.mutex);
+ } else {
+ gboolean kf = FALSE;
+ /* Parse RTP header first */
+ rtp_header *header = (rtp_header *)buffer;
+ guint32 timestamp = ntohl(header->timestamp);
+ guint16 seq = ntohs(header->seq_number);
+ JANUS_LOG(LOG_HUGE, "Checking if packet (size=%d, seq=%"SCNu16", ts=%"SCNu32") is a key frame...\n",
+ bytes, seq, timestamp);
+ int plen = 0;
+ char *payload = janus_rtp_payload(buffer, bytes, &plen);
+ if(payload) {
+ switch(mountpoint->codecs.video_codec) {
+ case JANUS_STREAMING_VP8:
+ kf = janus_vp8_is_keyframe(payload, plen);
+ break;
+ case JANUS_STREAMING_VP9:
+ kf = janus_vp9_is_keyframe(payload, plen);
+ break;
+ case JANUS_STREAMING_H264:
+ kf = janus_h264_is_keyframe(payload, plen);
+ break;
+ default:
+ break;
+ }
+ if(kf) {
+ /* New keyframe, start saving it */
+ source->keyframe.temp_ts = ntohl(rtp->timestamp);
+ JANUS_LOG(LOG_HUGE, "[%s] New keyframe received! ts=%"SCNu32"\n", name, source->keyframe.temp_ts);
+ janus_mutex_lock(&source->keyframe.mutex);
+ janus_streaming_rtp_relay_packet *pkt = g_malloc0(sizeof(janus_streaming_rtp_relay_packet));
+ pkt->data = g_malloc0(bytes);
+ memcpy(pkt->data, buffer, bytes);
+ pkt->data->ssrc = htons(1);
+ pkt->data->type = mountpoint->codecs.video_pt;
+ packet.is_rtp = TRUE;
+ packet.is_video = TRUE;
+ packet.is_keyframe = TRUE;
+ pkt->length = bytes;
+ pkt->timestamp = source->keyframe.temp_ts;
+ pkt->seq_number = ntohs(rtp->seq_number);
+ source->keyframe.temp_keyframe = g_list_append(source->keyframe.temp_keyframe, pkt);
+ janus_mutex_unlock(&source->keyframe.mutex);
+ }
+ }
}
}
/* If paused, ignore this packet */
@@ -4242,205 +4267,3 @@ static void janus_streaming_relay_rtp_packet(gpointer data, gpointer user_data)
return;
}
-
-/* Helpers to check if frame is a key frame (see post processor code) */
-#if defined(__ppc__) || defined(__ppc64__)
- # define swap2(d) \
- ((d&0x000000ff)<<8) | \
- ((d&0x0000ff00)>>8)
-#else
- # define swap2(d) d
-#endif
-
-static gboolean janus_streaming_is_keyframe(gint codec, char* buffer, int len) {
- if(codec == JANUS_STREAMING_VP8) {
- /* VP8 packet */
- if(!buffer || len < 28)
- return FALSE;
- /* Parse RTP header first */
- rtp_header *header = (rtp_header *)buffer;
- guint32 timestamp = ntohl(header->timestamp);
- guint16 seq = ntohs(header->seq_number);
- JANUS_LOG(LOG_HUGE, "Checking if VP8 packet (size=%d, seq=%"SCNu16", ts=%"SCNu32") is a key frame...\n",
- len, seq, timestamp);
- int plen = 0;
- buffer = janus_rtp_payload(buffer, len, &plen);
- if(!buffer) {
- JANUS_LOG(LOG_WARN, "Couldn't access RTP payload\n");
- return FALSE;
- }
- /* Parse VP8 header now */
- uint8_t vp8pd = *buffer;
- uint8_t xbit = (vp8pd & 0x80);
- uint8_t sbit = (vp8pd & 0x10);
- if(xbit) {
- JANUS_LOG(LOG_HUGE, " -- X bit is set!\n");
- /* Read the Extended control bits octet */
- buffer++;
- vp8pd = *buffer;
- uint8_t ibit = (vp8pd & 0x80);
- uint8_t lbit = (vp8pd & 0x40);
- uint8_t tbit = (vp8pd & 0x20);
- uint8_t kbit = (vp8pd & 0x10);
- if(ibit) {
- JANUS_LOG(LOG_HUGE, " -- I bit is set!\n");
- /* Read the PictureID octet */
- buffer++;
- vp8pd = *buffer;
- uint16_t picid = vp8pd, wholepicid = picid;
- uint8_t mbit = (vp8pd & 0x80);
- if(mbit) {
- JANUS_LOG(LOG_HUGE, " -- M bit is set!\n");
- memcpy(&picid, buffer, sizeof(uint16_t));
- wholepicid = ntohs(picid);
- picid = (wholepicid & 0x7FFF);
- buffer++;
- }
- JANUS_LOG(LOG_HUGE, " -- -- PictureID: %"SCNu16"\n", picid);
- }
- if(lbit) {
- JANUS_LOG(LOG_HUGE, " -- L bit is set!\n");
- /* Read the TL0PICIDX octet */
- buffer++;
- vp8pd = *buffer;
- }
- if(tbit || kbit) {
- JANUS_LOG(LOG_HUGE, " -- T/K bit is set!\n");
- /* Read the TID/KEYIDX octet */
- buffer++;
- vp8pd = *buffer;
- }
- buffer++; /* Now we're in the payload */
- if(sbit) {
- JANUS_LOG(LOG_HUGE, " -- S bit is set!\n");
- unsigned long int vp8ph = 0;
- memcpy(&vp8ph, buffer, 4);
- vp8ph = ntohl(vp8ph);
- uint8_t pbit = ((vp8ph & 0x01000000) >> 24);
- if(!pbit) {
- JANUS_LOG(LOG_HUGE, " -- P bit is NOT set!\n");
- /* It is a key frame! Get resolution for debugging */
- unsigned char *c = (unsigned char *)buffer+3;
- /* vet via sync code */
- if(c[0]!=0x9d||c[1]!=0x01||c[2]!=0x2a) {
- JANUS_LOG(LOG_WARN, "First 3-bytes after header not what they're supposed to be?\n");
- } else {
- int vp8w = swap2(*(unsigned short*)(c+3))&0x3fff;
- int vp8ws = swap2(*(unsigned short*)(c+3))>>14;
- int vp8h = swap2(*(unsigned short*)(c+5))&0x3fff;
- int vp8hs = swap2(*(unsigned short*)(c+5))>>14;
- JANUS_LOG(LOG_HUGE, "Got a VP8 key frame: %dx%d (scale=%dx%d)\n", vp8w, vp8h, vp8ws, vp8hs);
- return TRUE;
- }
- }
- }
- }
- /* If we got here it's not a key frame */
- return FALSE;
- } else if(codec == JANUS_STREAMING_VP9) {
- /* Parse RTP header first */
- rtp_header *header = (rtp_header *)buffer;
- guint32 timestamp = ntohl(header->timestamp);
- guint16 seq = ntohs(header->seq_number);
- JANUS_LOG(LOG_HUGE, "Checking if VP9 packet (size=%d, seq=%"SCNu16", ts=%"SCNu32") is a key frame...\n",
- len, seq, timestamp);
- int plen = 0;
- buffer = janus_rtp_payload(buffer, len, &plen);
- if(!buffer) {
- JANUS_LOG(LOG_WARN, "Couldn't access RTP payload\n");
- return FALSE;
- }
- /* Parse VP9 header now */
- uint8_t vp9pd = *buffer;
- uint8_t ibit = (vp9pd & 0x80);
- uint8_t pbit = (vp9pd & 0x40);
- uint8_t lbit = (vp9pd & 0x20);
- uint8_t fbit = (vp9pd & 0x10);
- uint8_t vbit = (vp9pd & 0x02);
- buffer++;
- if(ibit) {
- /* Read the PictureID octet */
- vp9pd = *buffer;
- uint16_t picid = vp9pd, wholepicid = picid;
- uint8_t mbit = (vp9pd & 0x80);
- if(!mbit) {
- buffer++;
- } else {
- memcpy(&picid, buffer, sizeof(uint16_t));
- wholepicid = ntohs(picid);
- picid = (wholepicid & 0x7FFF);
- buffer += 2;
- }
- }
- if(lbit) {
- buffer++;
- if(!fbit) {
- /* Non-flexible mode, skip TL0PICIDX */
- buffer++;
- }
- }
- if(fbit && pbit) {
- /* Skip reference indices */
- uint8_t nbit = 1;
- while(nbit) {
- vp9pd = *buffer;
- nbit = (vp9pd & 0x01);
- buffer++;
- }
- }
- if(vbit) {
- /* Parse and skip SS */
- vp9pd = *buffer;
- uint n_s = (vp9pd & 0xE0) >> 5;
- n_s++;
- uint8_t ybit = (vp9pd & 0x10);
- if(ybit) {
- /* Iterate on all spatial layers and get resolution */
- buffer++;
- uint i=0;
- for(i=0; i<n_s; i++) {
- /* Width */
- uint16_t *w = (uint16_t *)buffer;
- int vp9w = ntohs(*w);
- buffer += 2;
- /* Height */
- uint16_t *h = (uint16_t *)buffer;
- int vp9h = ntohs(*h);
- buffer += 2;
- JANUS_LOG(LOG_WARN, "Got a VP9 key frame: %dx%d\n", vp9w, vp9h);
- return TRUE;
- }
- }
- }
- /* If we got here it's not a key frame */
- return FALSE;
- } else if(codec == JANUS_STREAMING_H264) {
- /* Parse RTP header first */
- rtp_header *header = (rtp_header *)buffer;
- guint32 timestamp = ntohl(header->timestamp);
- guint16 seq = ntohs(header->seq_number);
- JANUS_LOG(LOG_HUGE, "Checking if H264 packet (size=%d, seq=%"SCNu16", ts=%"SCNu32") is a key frame...\n",
- len, seq, timestamp);
- int plen = 0;
- buffer = janus_rtp_payload(buffer, len, &plen);
- if(!buffer) {
- JANUS_LOG(LOG_WARN, "Couldn't access RTP payload\n");
- return FALSE;
- }
- /* Parse H264 header now */
- uint8_t fragment = *buffer & 0x1F;
- uint8_t nal = *(buffer+1) & 0x1F;
- uint8_t start_bit = *(buffer+1) & 0x80;
- JANUS_LOG(LOG_HUGE, "Fragment=%d, NAL=%d, Start=%d\n", fragment, nal, start_bit);
- if(fragment == 5 ||
- ((fragment == 28 || fragment == 29) && nal == 5 && start_bit == 128)) {
- JANUS_LOG(LOG_HUGE, "Got an H264 key frame\n");
- return TRUE;
- }
- /* If we got here it's not a key frame */
- return FALSE;
- } else {
- /* FIXME Not a clue */
- return FALSE;
- }
-}
diff --git a/plugins/janus_videoroom.c b/plugins/janus_videoroom.c
index 68d7066..7317208 100644
--- a/plugins/janus_videoroom.c
+++ b/plugins/janus_videoroom.c
@@ -462,8 +462,8 @@ typedef struct janus_videoroom {
janus_videoroom_videocodec vcodec; /* Video codec to force on publishers*/
gboolean audiolevel_ext; /* Whether the ssrc-audio-level extension must be negotiated or not for new publishers */
gboolean audiolevel_event; /* Whether to emit event to other users about audiolevel */
- int audio_active_packets; /* amount of packets with audio level for checkup */
- int audio_level_average; /* average audio level */
+ int audio_active_packets; /* Amount of packets with audio level for checkup */
+ int audio_level_average; /* Average audio level */
gboolean videoorient_ext; /* Whether the video-orientation extension must be negotiated or not for new publishers */
gboolean playoutdelay_ext; /* Whether the playout-delay extension must be negotiated or not for new publishers */
gboolean record; /* Whether the feeds from publishers in this room should be recorded */
@@ -515,6 +515,7 @@ typedef struct janus_videoroom_participant {
guint32 video_pt; /* Video payload type (depends on room configuration) */
guint32 audio_ssrc; /* Audio SSRC of this publisher */
guint32 video_ssrc; /* Video SSRC of this publisher */
+ uint32_t ssrc[3]; /* Only needed in case VP8 simulcasting is involved */
guint8 audio_level_extmap_id; /* Audio level extmap ID */
guint8 video_orient_extmap_id; /* Video orientation extmap ID */
guint8 playout_delay_extmap_id; /* Playout delay extmap ID */
@@ -534,7 +535,7 @@ typedef struct janus_videoroom_participant {
gboolean recording_active; /* Whether this publisher has to be recorded or not */
gchar *recording_base; /* Base name for the recording (e.g., /path/to/filename, will generate /path/to/filename-audio.mjr and/or /path/to/filename-video.mjr */
janus_recorder *arc; /* The Janus recorder instance for this publisher's audio, if enabled */
- janus_recorder *vrc; /* The Janus recorder instance for this publisher's video, if enabled */
+ janus_recorder *vrc[3]; /* The Janus recorder instance for this user's video, if enabled (there may be more if we're simulcasting) */
janus_recorder *drc; /* The Janus recorder instance for this publisher's data, if enabled */
janus_mutex rec_mutex; /* Mutex to protect the recorders from race conditions */
GSList *listeners; /* Subscriptions to this publisher (who's watching this publisher) */
@@ -556,6 +557,9 @@ typedef struct janus_videoroom_listener {
janus_videoroom_participant *feed; /* Participant this listener is subscribed to */
guint32 pvt_id; /* Private ID of the participant that is subscribing (if available/provided) */
janus_rtp_switching_context context; /* Needed in case there are publisher switches on this listener */
+ int simulcast; /* Which simulcast "layer" we should forward back, in case the publisher is simulcasting */
+ int simulcast_target; /* As above, but to handle transitions (e.g., wait for keyframe) */
+ janus_vp8_simulcast_context simulcast_context;
gboolean audio, video, data; /* Whether audio, video and/or data must be sent to this publisher */
gboolean paused;
gboolean kicked; /* Whether this subscription belongs to a participant that has been kicked */
@@ -1152,12 +1156,16 @@ json_t *janus_videoroom_query_session(janus_plugin_session *handle) {
json_object_set_new(media, "data", json_integer(participant->data));
json_object_set_new(info, "media", media);
json_object_set_new(info, "bitrate", json_integer(participant->bitrate));
- if(participant->arc || participant->vrc || participant->drc) {
+ if(participant->arc || participant->vrc[0] || participant->drc) {
json_t *recording = json_object();
if(participant->arc && participant->arc->filename)
json_object_set_new(recording, "audio", json_string(participant->arc->filename));
- if(participant->vrc && participant->vrc->filename)
- json_object_set_new(recording, "video", json_string(participant->vrc->filename));
+ if(participant->vrc[0] && participant->vrc[0]->filename)
+ json_object_set_new(recording, "video", json_string(participant->vrc[0]->filename));
+ if(participant->vrc[1] && participant->vrc[1]->filename)
+ json_object_set_new(recording, "video-sim1", json_string(participant->vrc[1]->filename));
+ if(participant->vrc[2] && participant->vrc[0]->filename)
+ json_object_set_new(recording, "video-sim2", json_string(participant->vrc[2]->filename));
if(participant->drc && participant->drc->filename)
json_object_set_new(recording, "data", json_string(participant->drc->filename));
json_object_set_new(info, "recording", recording);
@@ -2361,9 +2369,20 @@ void janus_videoroom_incoming_rtp(janus_plugin_session *handle, int video, char
}
if((!video && participant->audio_active) || (video && participant->video_active)) {
+ rtp_header *rtp = (rtp_header *)buf;
+ uint32_t ssrc = ntohl(rtp->ssrc);
+ int sc = -1;
+ /* Check if we're simulcasting, and if so, keep track of the "layer" */
+ if(video && participant->ssrc[0] != 0) {
+ if(ssrc == participant->ssrc[0])
+ sc = 0;
+ else if(ssrc == participant->ssrc[1])
+ sc = 1;
+ else if(ssrc == participant->ssrc[2])
+ sc = 2;
+ }
/* Update payload type and SSRC */
janus_mutex_lock(&participant->rtp_forwarders_mutex);
- rtp_header *rtp = (rtp_header *)buf;
rtp->type = video ? participant->video_pt : participant->audio_pt;
rtp->ssrc = htonl(video ? participant->video_ssrc : participant->audio_ssrc);
/* Forward RTP to the appropriate port for the rtp_forwarders associated with this publisher, if there are any */
@@ -2391,7 +2410,7 @@ void janus_videoroom_incoming_rtp(janus_plugin_session *handle, int video, char
}
janus_mutex_unlock(&participant->rtp_forwarders_mutex);
/* Save the frame if we're recording */
- janus_recorder_save_frame(video ? participant->vrc : participant->arc, buf, len);
+ janus_recorder_save_frame(video ? (sc < 0 ? participant->vrc[0] : participant->vrc[sc]) : participant->arc, buf, len);
/* Done, relay it */
janus_videoroom_rtp_relay_packet packet;
packet.data = rtp;
@@ -2602,20 +2621,52 @@ static void janus_videoroom_recorder_create(janus_videoroom_participant *partici
if(participant->recording_base) {
/* Use the filename and path we have been provided */
g_snprintf(filename, 255, "%s-video", participant->recording_base);
- participant->vrc = janus_recorder_create(participant->room->rec_dir,
+ participant->vrc[0] = janus_recorder_create(participant->room->rec_dir,
janus_videoroom_videocodec_name(participant->room->vcodec), filename);
- if(participant->vrc == NULL) {
+ if(participant->vrc[0] == NULL) {
JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this publisher!\n");
}
+ if(participant->ssrc[0] != 0) {
+ /* Create recordings for the other layers as well */
+ g_snprintf(filename, 255, "%s-video-sim1", participant->recording_base);
+ participant->vrc[1] = janus_recorder_create(participant->room->rec_dir,
+ janus_videoroom_videocodec_name(participant->room->vcodec), filename);
+ if(participant->vrc[1] == NULL) {
+ JANUS_LOG(LOG_ERR, "Couldn't open an video recording file (simulcasting #1) for this publisher!\n");
+ }
+ g_snprintf(filename, 255, "%s-video-sim2", participant->recording_base);
+ participant->vrc[2] = janus_recorder_create(participant->room->rec_dir,
+ janus_videoroom_videocodec_name(participant->room->vcodec), filename);
+ if(participant->vrc[2] == NULL) {
+ JANUS_LOG(LOG_ERR, "Couldn't open an video recording file (simulcasting #2) for this publisher!\n");
+ }
+ }
} else {
/* Build a filename */
g_snprintf(filename, 255, "videoroom-%"SCNu64"-user-%"SCNu64"-%"SCNi64"-video",
participant->room->room_id, participant->user_id, now);
- participant->vrc = janus_recorder_create(participant->room->rec_dir,
+ participant->vrc[0] = janus_recorder_create(participant->room->rec_dir,
janus_videoroom_videocodec_name(participant->room->vcodec), filename);
- if(participant->vrc == NULL) {
+ if(participant->vrc[0] == NULL) {
JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this publisher!\n");
}
+ if(participant->ssrc[0] != 0) {
+ /* Create recordings for the other layers as well */
+ g_snprintf(filename, 255, "videoroom-%"SCNu64"-user-%"SCNu64"-%"SCNi64"-video-sim1",
+ participant->room->room_id, participant->user_id, now);
+ participant->vrc[1] = janus_recorder_create(participant->room->rec_dir,
+ janus_videoroom_videocodec_name(participant->room->vcodec), filename);
+ if(participant->vrc[1] == NULL) {
+ JANUS_LOG(LOG_ERR, "Couldn't open an video recording file (simulcasting #1) for this publisher!\n");
+ }
+ g_snprintf(filename, 255, "videoroom-%"SCNu64"-user-%"SCNu64"-%"SCNi64"-video-sim2",
+ participant->room->room_id, participant->user_id, now);
+ participant->vrc[2] = janus_recorder_create(participant->room->rec_dir,
+ janus_videoroom_videocodec_name(participant->room->vcodec), filename);
+ if(participant->vrc[2] == NULL) {
+ JANUS_LOG(LOG_ERR, "Couldn't open an video recording file (simulcasting #2) for this publisher!\n");
+ }
+ }
}
}
if(data) {
@@ -2648,12 +2699,24 @@ static void janus_videoroom_recorder_close(janus_videoroom_participant *particip
janus_recorder_free(participant->arc);
}
participant->arc = NULL;
- if(participant->vrc) {
- janus_recorder_close(participant->vrc);
- JANUS_LOG(LOG_INFO, "Closed video recording %s\n", participant->vrc->filename ? participant->vrc->filename : "??");
- janus_recorder_free(participant->vrc);
- }
- participant->vrc = NULL;
+ if(participant->vrc[0]) {
+ janus_recorder_close(participant->vrc[0]);
+ JANUS_LOG(LOG_INFO, "Closed video recording %s\n", participant->vrc[0]->filename ? participant->vrc[0]->filename : "??");
+ janus_recorder_free(participant->vrc[0]);
+ }
+ participant->vrc[0] = NULL;
+ if(participant->vrc[1]) {
+ janus_recorder_close(participant->vrc[1]);
+ JANUS_LOG(LOG_INFO, "Closed video recording %s (simulcasting #1)\n", participant->vrc[1]->filename ? participant->vrc[1]->filename : "??");
+ janus_recorder_free(participant->vrc[1]);
+ }
+ participant->vrc[1] = NULL;
+ if(participant->vrc[2]) {
+ janus_recorder_close(participant->vrc[2]);
+ JANUS_LOG(LOG_INFO, "Closed video recording %s (simulcasting #2)\n", participant->vrc[2]->filename ? participant->vrc[2]->filename : "??");
+ janus_recorder_free(participant->vrc[2]);
+ }
+ participant->vrc[2] = NULL;
if(participant->drc) {
janus_recorder_close(participant->drc);
JANUS_LOG(LOG_INFO, "Closed data recording %s\n", participant->drc->filename ? participant->drc->filename : "??");
@@ -2897,7 +2960,9 @@ static void *janus_videoroom_handler(void *data) {
publisher->recording_active = FALSE;
publisher->recording_base = NULL;
publisher->arc = NULL;
- publisher->vrc = NULL;
+ publisher->vrc[0] = NULL;
+ publisher->vrc[1] = NULL;
+ publisher->vrc[2] = NULL;
publisher->drc = NULL;
janus_mutex_init(&publisher->rec_mutex);
publisher->firefox = FALSE;
@@ -3087,6 +3152,9 @@ static void *janus_videoroom_handler(void *data) {
if(!publisher->data)
listener->data = FALSE; /* ... unless the publisher isn't sending any data */
listener->paused = TRUE; /* We need an explicit start from the listener */
+ listener->simulcast = -1;
+ listener->simulcast_target = 0;
+ janus_vp8_simulcast_context_reset(&listener->simulcast_context);
session->participant = listener;
janus_mutex_lock(&publisher->listeners_mutex);
publisher->listeners = g_slist_append(publisher->listeners, listener);
@@ -3292,12 +3360,16 @@ static void *janus_videoroom_handler(void *data) {
json_object_set_new(info, "video_active", participant->video_active ? json_true() : json_false());
json_object_set_new(info, "data_active", participant->data_active ? json_true() : json_false());
json_object_set_new(info, "bitrate", json_integer(participant->bitrate));
- if(participant->arc || participant->vrc || participant->drc) {
+ if(participant->arc || participant->vrc[0] || participant->drc) {
json_t *recording = json_object();
if(participant->arc && participant->arc->filename)
json_object_set_new(recording, "audio", json_string(participant->arc->filename));
- if(participant->vrc && participant->vrc->filename)
- json_object_set_new(recording, "video", json_string(participant->vrc->filename));
+ if(participant->vrc[0] && participant->vrc[0]->filename)
+ json_object_set_new(recording, "video", json_string(participant->vrc[0]->filename));
+ if(participant->vrc[1] && participant->vrc[1]->filename)
+ json_object_set_new(recording, "video-sim1", json_string(participant->vrc[1]->filename));
+ if(participant->vrc[2] && participant->vrc[2]->filename)
+ json_object_set_new(recording, "video-sim2", json_string(participant->vrc[2]->filename));
if(participant->drc && participant->drc->filename)
json_object_set_new(recording, "data", json_string(participant->drc->filename));
json_object_set_new(info, "recording", recording);
@@ -3527,6 +3599,7 @@ static void *janus_videoroom_handler(void *data) {
/* Any SDP to handle? */
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_sdp) {
int ret = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event, NULL);
JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret));
@@ -3784,6 +3857,18 @@ static void *janus_videoroom_handler(void *data) {
if(videoroom->record || participant->recording_active) {
janus_videoroom_recorder_create(participant, participant->audio, participant->video, participant->data);
}
+ /* Is simulcasting involved */
+ if(msg_simulcast && videoroom->vcodec == JANUS_VIDEOROOM_VP8) {
+ JANUS_LOG(LOG_WARN, "Publisher is going to do simulcasting\n");
+ participant->ssrc[0] = json_integer_value(json_object_get(msg_simulcast, "ssrc-0"));
+ participant->ssrc[1] = json_integer_value(json_object_get(msg_simulcast, "ssrc-1"));
+ participant->ssrc[2] = json_integer_value(json_object_get(msg_simulcast, "ssrc-2"));
+ } else {
+ /* No simulcasting involved */
+ participant->ssrc[0] = 0;
+ participant->ssrc[1] = 0;
+ participant->ssrc[2] = 0;
+ }
janus_mutex_unlock(&participant->rec_mutex);
/* Send the answer back to the publisher */
JANUS_LOG(LOG_VERB, "Handling publisher: turned this into an '%s':\n%s\n", type, answer_sdp);
@@ -3939,9 +4024,17 @@ static void janus_videoroom_participant_free(janus_videoroom_participant *p) {
janus_recorder_free(p->arc);
p->arc = NULL;
}
- if(p->vrc) {
- janus_recorder_free(p->vrc);
- p->vrc = NULL;
+ if(p->vrc[0]) {
+ janus_recorder_free(p->vrc[0]);
+ p->vrc[0] = NULL;
+ }
+ if(p->vrc[1]) {
+ janus_recorder_free(p->vrc[1]);
+ p->vrc[1] = NULL;
+ }
+ if(p->vrc[2]) {
+ janus_recorder_free(p->vrc[2]);
+ p->vrc[2] = NULL;
}
if(p->drc) {
janus_recorder_free(p->drc);
diff --git a/postprocessing/pp-webm.c b/postprocessing/pp-webm.c
index 5cbfdb7..84f0856 100644
--- a/postprocessing/pp-webm.c
+++ b/postprocessing/pp-webm.c
@@ -167,7 +167,7 @@ int janus_pp_webm_preprocess(FILE *file, janus_pp_frame_packet *list, int vp8) {
uint8_t xbit = (vp8pd & 0x80);
uint8_t sbit = (vp8pd & 0x10);
/* Read the Extended control bits octet */
- if (xbit) {
+ if(xbit) {
buffer++;
vp8pd = *buffer;
uint8_t ibit = (vp8pd & 0x80);
diff --git a/rtp.c b/rtp.c
index 1e04481..ae5ddce 100644
--- a/rtp.c
+++ b/rtp.c
@@ -90,6 +90,8 @@ const char *janus_rtp_header_extension_get_from_id(const char *sdp, int id) {
return JANUS_RTP_EXTMAP_ABS_SEND_TIME;
if(strstr(extension, JANUS_RTP_EXTMAP_CC_EXTENSIONS))
return JANUS_RTP_EXTMAP_CC_EXTENSIONS;
+ if(strstr(extension, JANUS_RTP_EXTMAP_RTP_STREAM_ID))
+ return JANUS_RTP_EXTMAP_RTP_STREAM_ID;
JANUS_LOG(LOG_ERR, "Unsupported extension '%s'\n", extension);
return NULL;
}
@@ -102,7 +104,8 @@ const char *janus_rtp_header_extension_get_from_id(const char *sdp, int id) {
}
/* Static helper to quickly find the extension data */
-static int janus_rtp_header_extension_find(char *buf, int len, int id, uint8_t *byte, uint32_t *word) {
+static int janus_rtp_header_extension_find(char *buf, int len, int id,
+ uint8_t *byte, uint32_t *word, char **ref) {
if(!buf || len < 12)
return -1;
rtp_header *rtp = (rtp_header *)buf;
@@ -110,7 +113,7 @@ static int janus_rtp_header_extension_find(char *buf, int len, int id, uint8_t *
if(rtp->csrccount) /* Skip CSRC if needed */
hlen += rtp->csrccount*4;
if(rtp->extension) {
- janus_rtp_header_extension *ext = (janus_rtp_header_extension*)(buf+hlen);
+ janus_rtp_header_extension *ext = (janus_rtp_header_extension *)(buf+hlen);
int extlen = ntohs(ext->length)*4;
hlen += 4;
if(len > (hlen + extlen)) {
@@ -134,6 +137,8 @@ static int janus_rtp_header_extension_find(char *buf, int len, int id, uint8_t *
*byte = buf[hlen+i+1];
if(word)
*word = *(uint32_t *)(buf+hlen+i);
+ if(ref)
+ *ref = &buf[hlen];
return 0;
}
i += 1 + idlen;
@@ -147,7 +152,7 @@ static int janus_rtp_header_extension_find(char *buf, int len, int id, uint8_t *
int janus_rtp_header_extension_parse_audio_level(char *buf, int len, int id, int *level) {
uint8_t byte = 0;
- if(janus_rtp_header_extension_find(buf, len, id, &byte, NULL) < 0)
+ if(janus_rtp_header_extension_find(buf, len, id, &byte, NULL, NULL) < 0)
return -1;
/* a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level */
int v = (byte & 0x80) >> 7;
@@ -161,7 +166,7 @@ int janus_rtp_header_extension_parse_audio_level(char *buf, int len, int id, int
int janus_rtp_header_extension_parse_video_orientation(char *buf, int len, int id,
gboolean *c, gboolean *f, gboolean *r1, gboolean *r0) {
uint8_t byte = 0;
- if(janus_rtp_header_extension_find(buf, len, id, &byte, NULL) < 0)
+ if(janus_rtp_header_extension_find(buf, len, id, &byte, NULL, NULL) < 0)
return -1;
/* a=extmap:4 urn:3gpp:video-orientation */
gboolean cbit = (byte & 0x08) >> 3;
@@ -183,7 +188,7 @@ int janus_rtp_header_extension_parse_video_orientation(char *buf, int len, int i
int janus_rtp_header_extension_parse_playout_delay(char *buf, int len, int id,
uint16_t *min_delay, uint16_t *max_delay) {
uint32_t bytes = 0;
- if(janus_rtp_header_extension_find(buf, len, id, NULL, &bytes) < 0)
+ if(janus_rtp_header_extension_find(buf, len, id, NULL, &bytes, NULL) < 0)
return -1;
/* a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay */
uint16_t min = (bytes & 0x00FFF000) >> 12;
@@ -196,6 +201,24 @@ int janus_rtp_header_extension_parse_playout_delay(char *buf, int len, int id,
return 0;
}
+int janus_rtp_header_extension_parse_rtp_stream_id(char *buf, int len, int id,
+ char *sdes_item, int sdes_len) {
+ char *ext = NULL;
+ if(janus_rtp_header_extension_find(buf, len, id, NULL, NULL, &ext) < 0)
+ return -1;
+ /* a=extmap:3/sendonly urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id */
+ if(ext == NULL)
+ return -2;
+ int val_len = (*ext & 0x0F) + 1;
+ if(val_len > (sdes_len-1)) {
+ JANUS_LOG(LOG_WARN, "SDES buffer is too small (%d < %d), RTP stream ID will be cut\n", val_len, sdes_len);
+ val_len = sdes_len-1;
+ }
+ memcpy(sdes_item, ext+1, val_len);
+ *(sdes_item+val_len) = '\0';
+ return 0;
+}
+
/* RTP context related methods */
void janus_rtp_switching_context_reset(janus_rtp_switching_context *context) {
diff --git a/rtp.h b/rtp.h
index 9076982..3bcf4d1 100644
--- a/rtp.h
+++ b/rtp.h
@@ -102,6 +102,8 @@ typedef struct janus_rtp_header_extension {
#define JANUS_RTP_EXTMAP_CC_EXTENSIONS "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01"
/*! \brief a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay */
#define JANUS_RTP_EXTMAP_PLAYOUT_DELAY "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay"
+/*! \brief a=extmap:3/sendonly urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id */
+#define JANUS_RTP_EXTMAP_RTP_STREAM_ID "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id"
/*! \brief Helper to quickly access the RTP payload, skipping header and extensions
* @param[in] buf The packet data
@@ -153,6 +155,17 @@ int janus_rtp_header_extension_parse_video_orientation(char *buf, int len, int i
int janus_rtp_header_extension_parse_playout_delay(char *buf, int len, int id,
uint16_t *min_delay, uint16_t *max_delay);
+/*! \brief Helper to parse a rtp-stream-id RTP extension (https://tools.ietf.org/html/draft-ietf-avtext-rid-09)
+ * @param[in] buf The packet data
+ * @param[in] len The packet data length in bytes
+ * @param[in] id The extension ID to look for
+ * @param[out] sdes_item Buffer where the RTP stream ID will be written
+ * @param[in] sdes_len Size of the input/output buffer
+ * @returns 0 if found, -1 otherwise */
+int janus_rtp_header_extension_parse_rtp_stream_id(char *buf, int len, int id,
+ char *sdes_item, int sdes_len);
+
+
/*! \brief RTP context, in order to make sure SSRC changes result in coherent seq/ts increases */
typedef struct janus_rtp_switching_context {
uint32_t a_last_ssrc, a_last_ts, a_base_ts, a_base_ts_prev,
diff --git a/sdp.c b/sdp.c
index ce0697c..8fa0d3f 100644
--- a/sdp.c
+++ b/sdp.c
@@ -295,15 +295,37 @@ int janus_sdp_process(void *ice_handle, janus_sdp *remote_sdp) {
JANUS_LOG(LOG_ERR, "[%"SCNu64"] Failed to parse candidate... (%d)\n", handle->handle_id, res);
}
}
- }
- if(!strcasecmp(a->name, "ssrc")) {
+ } else if(!strcasecmp(a->name, "rid")) {
+ /* This attribute is used by Firefox for simulcasting */
+ char rid[16];
+ if(sscanf(a->value, "%15s send", rid) != 1) {
+ JANUS_LOG(LOG_ERR, "[%"SCNu64"] Failed to parse rid attribute...\n", handle->handle_id);
+ } else {
+ JANUS_LOG(LOG_VERB, "[%"SCNu64"] Parsed rid: %s\n", handle->handle_id, rid);
+ if(stream->rid[0] == NULL) {
+ stream->rid[0] = g_strdup(rid);
+ } else if(stream->rid[1] == NULL) {
+ stream->rid[1] = g_strdup(rid);
+ } else if(stream->rid[2] == NULL) {
+ stream->rid[2] = g_strdup(rid);
+ } else {
+ JANUS_LOG(LOG_WARN, "[%"SCNu64"] Too many RTP Stream IDs, ignoring '%s'...\n", handle->handle_id, rid);
+ }
+ }
+ } else if(!strcasecmp(a->name, "ssrc-group")) {
+ /* FIXME This can be either FID or SIM */
+ int res = janus_sdp_parse_ssrc_group(stream, (const char *)a->value, m->type == JANUS_SDP_VIDEO);
+ if(res != 0) {
+ JANUS_LOG(LOG_ERR, "[%"SCNu64"] Failed to parse SSRC group attribute... (%d)\n", handle->handle_id, res);
+ }
+ } else if(!strcasecmp(a->name, "ssrc")) {
int res = janus_sdp_parse_ssrc(stream, (const char *)a->value, m->type == JANUS_SDP_VIDEO);
if(res != 0) {
JANUS_LOG(LOG_ERR, "[%"SCNu64"] Failed to parse SSRC attribute... (%d)\n", handle->handle_id, res);
}
}
#ifdef HAVE_SCTP
- if(!strcasecmp(a->name, "sctpmap")) {
+ else if(!strcasecmp(a->name, "sctpmap")) {
/* TODO Parse sctpmap line to get the UDP-port value and the number of channels */
JANUS_LOG(LOG_VERB, "Got a sctpmap attribute: %s\n", a->value);
}
@@ -534,6 +556,64 @@ int janus_sdp_parse_candidate(void *ice_stream, const char *candidate, int trick
return 0;
}
+int janus_sdp_parse_ssrc_group(void *ice_stream, const char *group_attr, int video) {
+ if(ice_stream == NULL || group_attr == NULL)
+ return -1;
+ janus_ice_stream *stream = (janus_ice_stream *)ice_stream;
+ janus_ice_handle *handle = stream->handle;
+ if(handle == NULL)
+ return -2;
+ if(!video)
+ return -3;
+ gboolean fid = strstr(group_attr, "FID") != NULL;
+ gboolean sim = strstr(group_attr, "SIM") != NULL;
+ guint64 ssrc = 0;
+ gchar **list = g_strsplit(group_attr, " ", -1);
+ gchar *index = list[0];
+ if(index != NULL) {
+ int i=0;
+ while(index != NULL) {
+ if(i > 0 && strlen(index) > 0) {
+ ssrc = g_ascii_strtoull(index, NULL, 0);
+ switch(i) {
+ case 1:
+ stream->video_ssrc_peer = ssrc;
+ JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC: %"SCNu32"\n", handle->handle_id, stream->video_ssrc_peer);
+ break;
+ case 2:
+ if(fid) {
+ stream->video_ssrc_peer_rtx = ssrc;
+ JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC (rtx): %"SCNu32"\n", handle->handle_id, stream->video_ssrc_peer_rtx);
+ } else if(sim) {
+ stream->video_ssrc_peer_sim_1 = ssrc;
+ JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC (sim-1): %"SCNu32"\n", handle->handle_id, stream->video_ssrc_peer_sim_1);
+ } else {
+ JANUS_LOG(LOG_WARN, "[%"SCNu64"] Don't know what to do with SSRC: %"SCNu64"\n", handle->handle_id, ssrc);
+ }
+ break;
+ case 3:
+ if(fid) {
+ JANUS_LOG(LOG_WARN, "[%"SCNu64"] Found one too many retransmission SSRC (rtx): %"SCNu64"\n", handle->handle_id, ssrc);
+ } else if(sim) {
+ stream->video_ssrc_peer_sim_2 = ssrc;
+ JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC (sim-2): %"SCNu32"\n", handle->handle_id, stream->video_ssrc_peer_sim_2);
+ } else {
+ JANUS_LOG(LOG_WARN, "[%"SCNu64"] Don't know what to do with SSRC: %"SCNu64"\n", handle->handle_id, ssrc);
+ }
+ break;
+ default:
+ JANUS_LOG(LOG_WARN, "[%"SCNu64"] Don't know what to do with video SSRC: %"SCNu64"\n", handle->handle_id, ssrc);
+ break;
+ }
+ }
+ i++;
+ index = list[i];
+ }
+ }
+ g_clear_pointer(&list, g_strfreev);
+ return 0;
+}
+
int janus_sdp_parse_ssrc(void *ice_stream, const char *ssrc_attr, int video) {
if(ice_stream == NULL || ssrc_attr == NULL)
return -1;
@@ -547,18 +627,25 @@ int janus_sdp_parse_ssrc(void *ice_stream, const char *ssrc_attr, int video) {
if(video) {
if(stream->video_ssrc_peer == 0) {
stream->video_ssrc_peer = ssrc;
- JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC: %u\n", handle->handle_id, stream->video_ssrc_peer);
- } else if(stream->video_ssrc_peer != ssrc) {
- /* FIXME We assume the second SSRC we get is the one Chrome associates with retransmissions, e.g.
- * a=ssrc-group:FID 586466331 2053167359 (SSRC SSRC-rtx)
- * SSRC group FID: https://tools.ietf.org/html/rfc3388#section-7 */
- stream->video_ssrc_peer_rtx = ssrc;
- JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC (rtx): %u\n", handle->handle_id, stream->video_ssrc_peer_rtx);
+ JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC: %"SCNu32"\n", handle->handle_id, stream->video_ssrc_peer);
+ } else {
+ /* We already have a video SSRC: check if RID is involved, and we'll keep track of this for simulcasting */
+ if(stream->rid[0]) {
+ if(stream->video_ssrc_peer_sim_1 == 0) {
+ stream->video_ssrc_peer_sim_1 = ssrc;
+ JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC (sim-1): %"SCNu32"\n", handle->handle_id, stream->video_ssrc_peer_sim_1);
+ } else if(stream->video_ssrc_peer_sim_2 == 0) {
+ stream->video_ssrc_peer_sim_2 = ssrc;
+ JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC (sim-2): %"SCNu32"\n", handle->handle_id, stream->video_ssrc_peer_sim_2);
+ } else {
+ JANUS_LOG(LOG_WARN, "[%"SCNu64"] Don't know what to do with video SSRC: %"SCNu64"\n", handle->handle_id, ssrc);
+ }
+ }
}
} else {
if(stream->audio_ssrc_peer == 0) {
stream->audio_ssrc_peer = ssrc;
- JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer audio SSRC: %u\n", handle->handle_id, stream->audio_ssrc_peer);
+ JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer audio SSRC: %"SCNu32"\n", handle->handle_id, stream->audio_ssrc_peer);
}
}
return 0;
@@ -636,6 +723,7 @@ int janus_sdp_anonymize(janus_sdp *anon) {
|| !strcasecmp(a->name, "mid")
|| !strcasecmp(a->name, "msid")
|| !strcasecmp(a->name, "msid-semantic")
+ || !strcasecmp(a->name, "rid")
|| !strcasecmp(a->name, "rtcp")
|| !strcasecmp(a->name, "rtcp-mux")
|| !strcasecmp(a->name, "rtcp-rsize")
@@ -916,6 +1004,26 @@ char *janus_sdp_merge(void *ice_handle, janus_sdp *anon) {
a = janus_sdp_attribute_create("ssrc", "%"SCNu32" label:janusv0", stream->video_ssrc);
m->attributes = g_list_append(m->attributes, a);
}
+ /* FIXME If the peer is Firefox and is negotiating simulcasting, add the rid attributes */
+ if(m->type == JANUS_SDP_VIDEO && stream->rid[0] != NULL) {
+ char rids[50];
+ rids[0] = '\0';
+ int i=0;
+ for(i=0; i<3; i++) {
+ if(stream->rid[i] == NULL)
+ continue;
+ a = janus_sdp_attribute_create("rid", "%s recv", stream->rid[i]);
+ m->attributes = g_list_append(m->attributes, a);
+ if(strlen(rids) == 0) {
+ g_strlcat(rids, stream->rid[i], sizeof(rids));
+ } else {
+ g_strlcat(rids, ";", sizeof(rids));
+ g_strlcat(rids, stream->rid[i], sizeof(rids));
+ }
+ }
+ a = janus_sdp_attribute_create("simulcast", " recv rid=%s", rids);
+ m->attributes = g_list_append(m->attributes, a);
+ }
/* And now the candidates */
janus_ice_candidates_to_sdp(handle, m, stream->stream_id, 1);
if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RTCPMUX) &&
diff --git a/sdp.h b/sdp.h
index 9229bd6..96b1513 100644
--- a/sdp.h
+++ b/sdp.h
@@ -62,6 +62,14 @@ int janus_sdp_process(void *handle, janus_sdp *sdp);
* @returns 0 in case of success, a non-zero integer in case of an error */
int janus_sdp_parse_candidate(void *stream, const char *candidate, int trickle);
+/*! \brief Method to parse a SSRC group attribute
+ * \details This method will parse a SSRC group attribute, and set the parsed values for the peer
+ * @param[in] stream Opaque pointer to the ICE stream this candidate refers to
+ * @param[in] group_attr The SSRC group attribute value to parse
+ * @param[in] video Whether this is video-related or not
+ * @returns 0 in case of success, a non-zero integer in case of an error */
+int janus_sdp_parse_ssrc_group(void *stream, const char *group_attr, int video);
+
/*! \brief Method to parse a SSRC attribute
* \details This method will parse a SSRC attribute, and set it for the peer
* @param[in] stream Opaque pointer to the ICE stream this candidate refers to
diff --git a/utils.c b/utils.c
index edaac94..668cec1 100644
--- a/utils.c
+++ b/utils.c
@@ -17,6 +17,8 @@
#include <sys/file.h>
#include <sys/types.h>
#include <unistd.h>
+#include <arpa/inet.h>
+#include <inttypes.h>
#include "utils.h"
#include "debug.h"
@@ -494,3 +496,309 @@ gboolean janus_json_is_valid(json_t *val, json_type jtype, unsigned int flags) {
}
return is_valid;
}
+
+/* The following code is more related to codec specific helpers */
+#if defined(__ppc__) || defined(__ppc64__)
+ # define swap2(d) \
+ ((d&0x000000ff)<<8) | \
+ ((d&0x0000ff00)>>8)
+#else
+ # define swap2(d) d
+#endif
+
+gboolean janus_vp8_is_keyframe(char* buffer, int len) {
+ if(!buffer || len < 0)
+ return FALSE;
+ /* Parse VP8 header now */
+ uint8_t vp8pd = *buffer;
+ uint8_t xbit = (vp8pd & 0x80);
+ uint8_t sbit = (vp8pd & 0x10);
+ if(xbit) {
+ JANUS_LOG(LOG_HUGE, " -- X bit is set!\n");
+ /* Read the Extended control bits octet */
+ buffer++;
+ vp8pd = *buffer;
+ uint8_t ibit = (vp8pd & 0x80);
+ uint8_t lbit = (vp8pd & 0x40);
+ uint8_t tbit = (vp8pd & 0x20);
+ uint8_t kbit = (vp8pd & 0x10);
+ if(ibit) {
+ JANUS_LOG(LOG_HUGE, " -- I bit is set!\n");
+ /* Read the PictureID octet */
+ buffer++;
+ vp8pd = *buffer;
+ uint16_t picid = vp8pd, wholepicid = picid;
+ uint8_t mbit = (vp8pd & 0x80);
+ if(mbit) {
+ JANUS_LOG(LOG_HUGE, " -- M bit is set!\n");
+ memcpy(&picid, buffer, sizeof(uint16_t));
+ wholepicid = ntohs(picid);
+ picid = (wholepicid & 0x7FFF);
+ buffer++;
+ }
+ JANUS_LOG(LOG_HUGE, " -- -- PictureID: %"SCNu16"\n", picid);
+ }
+ if(lbit) {
+ JANUS_LOG(LOG_HUGE, " -- L bit is set!\n");
+ /* Read the TL0PICIDX octet */
+ buffer++;
+ vp8pd = *buffer;
+ }
+ if(tbit || kbit) {
+ JANUS_LOG(LOG_HUGE, " -- T/K bit is set!\n");
+ /* Read the TID/KEYIDX octet */
+ buffer++;
+ vp8pd = *buffer;
+ }
+ buffer++; /* Now we're in the payload */
+ if(sbit) {
+ JANUS_LOG(LOG_HUGE, " -- S bit is set!\n");
+ unsigned long int vp8ph = 0;
+ memcpy(&vp8ph, buffer, 4);
+ vp8ph = ntohl(vp8ph);
+ uint8_t pbit = ((vp8ph & 0x01000000) >> 24);
+ if(!pbit) {
+ JANUS_LOG(LOG_HUGE, " -- P bit is NOT set!\n");
+ /* It is a key frame! Get resolution for debugging */
+ unsigned char *c = (unsigned char *)buffer+3;
+ /* vet via sync code */
+ if(c[0]!=0x9d||c[1]!=0x01||c[2]!=0x2a) {
+ JANUS_LOG(LOG_WARN, "First 3-bytes after header not what they're supposed to be?\n");
+ } else {
+ int vp8w = swap2(*(unsigned short*)(c+3))&0x3fff;
+ int vp8ws = swap2(*(unsigned short*)(c+3))>>14;
+ int vp8h = swap2(*(unsigned short*)(c+5))&0x3fff;
+ int vp8hs = swap2(*(unsigned short*)(c+5))>>14;
+ JANUS_LOG(LOG_HUGE, "Got a VP8 key frame: %dx%d (scale=%dx%d)\n", vp8w, vp8h, vp8ws, vp8hs);
+ return TRUE;
+ }
+ }
+ }
+ }
+ /* If we got here it's not a key frame */
+ return FALSE;
+}
+
+gboolean janus_vp9_is_keyframe(char* buffer, int len) {
+ if(!buffer || len < 0)
+ return FALSE;
+ /* Parse VP9 header now */
+ uint8_t vp9pd = *buffer;
+ uint8_t ibit = (vp9pd & 0x80);
+ uint8_t pbit = (vp9pd & 0x40);
+ uint8_t lbit = (vp9pd & 0x20);
+ uint8_t fbit = (vp9pd & 0x10);
+ uint8_t vbit = (vp9pd & 0x02);
+ buffer++;
+ if(ibit) {
+ /* Read the PictureID octet */
+ vp9pd = *buffer;
+ uint16_t picid = vp9pd, wholepicid = picid;
+ uint8_t mbit = (vp9pd & 0x80);
+ if(!mbit) {
+ buffer++;
+ } else {
+ memcpy(&picid, buffer, sizeof(uint16_t));
+ wholepicid = ntohs(picid);
+ picid = (wholepicid & 0x7FFF);
+ buffer += 2;
+ }
+ }
+ if(lbit) {
+ buffer++;
+ if(!fbit) {
+ /* Non-flexible mode, skip TL0PICIDX */
+ buffer++;
+ }
+ }
+ if(fbit && pbit) {
+ /* Skip reference indices */
+ uint8_t nbit = 1;
+ while(nbit) {
+ vp9pd = *buffer;
+ nbit = (vp9pd & 0x01);
+ buffer++;
+ }
+ }
+ if(vbit) {
+ /* Parse and skip SS */
+ vp9pd = *buffer;
+ uint n_s = (vp9pd & 0xE0) >> 5;
+ n_s++;
+ uint8_t ybit = (vp9pd & 0x10);
+ if(ybit) {
+ /* Iterate on all spatial layers and get resolution */
+ buffer++;
+ uint i=0;
+ for(i=0; i<n_s; i++) {
+ /* Width */
+ uint16_t *w = (uint16_t *)buffer;
+ int vp9w = ntohs(*w);
+ buffer += 2;
+ /* Height */
+ uint16_t *h = (uint16_t *)buffer;
+ int vp9h = ntohs(*h);
+ buffer += 2;
+ JANUS_LOG(LOG_WARN, "Got a VP9 key frame: %dx%d\n", vp9w, vp9h);
+ return TRUE;
+ }
+ }
+ }
+ /* If we got here it's not a key frame */
+ return FALSE;
+}
+
+gboolean janus_h264_is_keyframe(char* buffer, int len) {
+ if(!buffer || len < 0)
+ return FALSE;
+ /* Parse H264 header now */
+ uint8_t fragment = *buffer & 0x1F;
+ uint8_t nal = *(buffer+1) & 0x1F;
+ uint8_t start_bit = *(buffer+1) & 0x80;
+ JANUS_LOG(LOG_HUGE, "Fragment=%d, NAL=%d, Start=%d\n", fragment, nal, start_bit);
+ if(fragment == 5 ||
+ ((fragment == 28 || fragment == 29) && nal == 5 && start_bit == 128)) {
+ JANUS_LOG(LOG_HUGE, "Got an H264 key frame\n");
+ return TRUE;
+ }
+ /* If we got here it's not a key frame */
+ return FALSE;
+}
+
+static int janus_vp8_parse_descriptor(char *buffer, int len,
+ uint16_t *picid, uint8_t *tl0picidx, uint8_t *tid, uint8_t *y, uint8_t *keyidx) {
+ if(!buffer || len < 0)
+ return -1;
+ if(picid)
+ *picid = 0;
+ if(tl0picidx)
+ *tl0picidx = 0;
+ if(tid)
+ *tid = 0;
+ if(y)
+ *y = 0;
+ if(keyidx)
+ *keyidx = 0;
+ uint8_t vp8pd = *buffer;
+ uint8_t xbit = (vp8pd & 0x80);
+ /* Read the Extended control bits octet */
+ if(xbit) {
+ buffer++;
+ vp8pd = *buffer;
+ uint8_t ibit = (vp8pd & 0x80);
+ uint8_t lbit = (vp8pd & 0x40);
+ uint8_t tbit = (vp8pd & 0x20);
+ uint8_t kbit = (vp8pd & 0x10);
+ if(ibit) {
+ /* Read the PictureID octet */
+ buffer++;
+ vp8pd = *buffer;
+ uint16_t partpicid = vp8pd, wholepicid = partpicid;
+ uint8_t mbit = (vp8pd & 0x80);
+ if(mbit) {
+ memcpy(&partpicid, buffer, sizeof(uint16_t));
+ wholepicid = ntohs(partpicid);
+ partpicid = (wholepicid & 0x7FFF);
+ if(picid)
+ *picid = partpicid;
+ buffer++;
+ }
+ }
+ if(lbit) {
+ /* Read the TL0PICIDX octet */
+ buffer++;
+ vp8pd = *buffer;
+ if(tl0picidx)
+ *tl0picidx = vp8pd;
+ }
+ if(tbit || kbit) {
+ /* Read the TID/Y/KEYIDX octet */
+ buffer++;
+ vp8pd = *buffer;
+ if(tid)
+ *tid = (vp8pd & 0xC0) >> 6;
+ if(y)
+ *y = (vp8pd & 0x20) >> 5;
+ if(keyidx)
+ *keyidx = (vp8pd & 0x1F) >> 4;
+ }
+ }
+ return 0;
+}
+
+static int janus_vp8_replace_descriptor(char *buffer, int len, uint16_t picid, uint8_t tl0picidx) {
+ if(!buffer || len < 0)
+ return -1;
+ uint8_t vp8pd = *buffer;
+ uint8_t xbit = (vp8pd & 0x80);
+ /* Read the Extended control bits octet */
+ if(xbit) {
+ buffer++;
+ vp8pd = *buffer;
+ uint8_t ibit = (vp8pd & 0x80);
+ uint8_t lbit = (vp8pd & 0x40);
+ uint8_t tbit = (vp8pd & 0x20);
+ uint8_t kbit = (vp8pd & 0x10);
+ if(ibit) {
+ /* Overwrite the PictureID octet */
+ buffer++;
+ vp8pd = *buffer;
+ uint8_t mbit = (vp8pd & 0x80);
+ if(!mbit) {
+ *buffer = picid;
+ } else {
+ uint16_t wholepicid = htons(picid);
+ memcpy(buffer, &wholepicid, 2);
+ *buffer |= 0x80;
+ buffer++;
+ }
+ }
+ if(lbit) {
+ /* Overwrite the TL0PICIDX octet */
+ buffer++;
+ *buffer = tl0picidx;
+ }
+ if(tbit || kbit) {
+ /* TODO Overwrite the TID/Y/KEYIDX octet */
+ buffer++;
+ }
+ }
+ return 0;
+}
+
+void janus_vp8_simulcast_context_reset(janus_vp8_simulcast_context *context) {
+ if(context == NULL)
+ return;
+ /* Reset the context values */
+ context->last_picid = 0;
+ context->base_picid = 0;
+ context->base_picid_prev = 0;
+ context->last_tlzi = 0;
+ context->base_tlzi = 0;
+ context->base_tlzi_prev = 0;
+}
+
+void janus_vp8_simulcast_descriptor_update(char *buffer, int len, janus_vp8_simulcast_context *context, gboolean switched) {
+ if(!buffer || len < 0)
+ return;
+ uint16_t picid = 0;
+ uint8_t tlzi = 0;
+ uint8_t tid = 0;
+ uint8_t ybit = 0;
+ uint8_t keyidx = 0;
+ /* Parse the identifiers in the VP8 payload descriptor */
+ if(janus_vp8_parse_descriptor(buffer, len, &picid, &tlzi, &tid, &ybit, &keyidx) < 0)
+ return;
+ //~ JANUS_LOG(LOG_WARN, "%"SCNu16", %"SCNu8", %"SCNu8", %"SCNu8", %"SCNu8"\n",
+ //~ picid, tlzi, tid, ybit, keyidx);
+ if(switched) {
+ context->base_picid_prev = context->last_picid;
+ context->base_picid = picid;
+ context->base_tlzi_prev = context->last_tlzi;
+ context->base_tlzi = tlzi;
+ }
+ context->last_picid = (picid-context->base_picid)+context->base_picid_prev+1;
+ context->last_tlzi = (tlzi-context->base_tlzi)+context->base_tlzi_prev+1;
+ /* Overwrite the values in the VP8 payload descriptors with the ones we have */
+ janus_vp8_replace_descriptor(buffer, len, context->last_picid, context->last_tlzi);
+}
diff --git a/utils.h b/utils.h
index 487092e..a04170c 100644
--- a/utils.h
+++ b/utils.h
@@ -220,4 +220,39 @@ gboolean janus_json_is_valid(json_t *val, json_type jtype, unsigned int flags);
} \
} while(0)
+/*! \brief Helper method to check if a VP8 frame is a keyframe or not
+ * @param[in] buffer The RTP payload to process
+ * @param[in] len The length of the RTP payload
+ * @returns TRUE if it's a keyframe, FALSE otherwise */
+gboolean janus_vp8_is_keyframe(char* buffer, int len);
+
+/*! \brief Helper method to check if a VP9 frame is a keyframe or not
+ * @param[in] buffer The RTP payload to process
+ * @param[in] len The length of the RTP payload
+ * @returns TRUE if it's a keyframe, FALSE otherwise */
+gboolean janus_vp9_is_keyframe(char* buffer, int len);
+
+/*! \brief Helper method to check if an H.264 frame is a keyframe or not
+ * @param[in] buffer The RTP payload to process
+ * @param[in] len The length of the RTP payload
+ * @returns TRUE if it's a keyframe, FALSE otherwise */
+gboolean janus_h264_is_keyframe(char* buffer, int len);
+
+/*! \brief VP8 simulcasting context, in order to make sure SSRC changes result in coherent picid/temporal level increases */
+typedef struct janus_vp8_simulcast_context {
+ uint16_t last_picid, base_picid, base_picid_prev;
+ uint16_t last_tlzi, base_tlzi, base_tlzi_prev;
+} janus_vp8_simulcast_context;
+
+/*! \brief Set (or reset) the context fields to their default values
+ * @param[in] context The context to (re)set */
+void janus_vp8_simulcast_context_reset(janus_vp8_simulcast_context *context);
+
+/*! \brief Use the context info to update the RTP header of a packet, if needed
+ * @param[in] buffer The RTP payload to process
+ * @param[in] len The length of the RTP payload
+ * @param[in] context The context to use as a reference
+ * @param[in] switched Whether there has been a source switch or not (important to compute offsets) */
+void janus_vp8_simulcast_descriptor_update(char *buffer, int len, janus_vp8_simulcast_context *context, gboolean switched);
+
#endif
--
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