Skip to content

Commit

Permalink
SVC related tweaks (#3174)
Browse files Browse the repository at this point in the history
  • Loading branch information
lminiero committed Mar 6, 2023
1 parent 730c91c commit ca36ad9
Show file tree
Hide file tree
Showing 16 changed files with 701 additions and 1,368 deletions.
18 changes: 1 addition & 17 deletions conf/janus.plugin.videoroom.jcfg.sample
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
# h264_profile = H.264-specific profile to prefer (e.g., "42e01f" for "profile-level-id=42e01f")
# opus_fec = true|false (whether inband FEC must be negotiated; only works for Opus, default=true)
# opus_dtx = true|false (whether DTX must be negotiated; only works for Opus, default=false)
# video_svc = true|false (whether SVC support must be enabled; only works for VP9, default=false)
# audiolevel_ext = true|false (whether the ssrc-audio-level RTP extension must
# be negotiated/used or not for new publishers, default=true)
# audiolevel_event = true|false (whether to emit event to other users or not, default=false)
Expand Down Expand Up @@ -71,25 +70,10 @@ room-1234: {
description = "Demo Room"
secret = "adminpwd"
publishers = 6
bitrate = 128000
bitrate = 128000 # This is a low cap, increase if you want to use simulcast or SVC
fir_freq = 10
#audiocodec = "opus"
#videocodec = "vp8"
record = false
#rec_dir = "/path/to/recordings-folder"
}

# This other demo room here is only there in case you want to play with
# the VP9 SVC support. Notice that you'll need a Chrome launched with
# the flag that enables that support, or otherwise you'll be using just
# plain VP9 (which is good if you want to test how this indeed affect
# what receivers will get, whether they're encoding SVC or not).
room-5678: {
description = "VP9-SVC Demo Room"
secret = "adminpwd"
publishers = 6
bitrate = 0 # No limit, to give room for all layers
fir_freq = 10
videocodec = "vp9"
video_svc = true
}
4 changes: 0 additions & 4 deletions html/demos.html
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,6 @@ <h1>Janus WebRTC Server: Demo Tests</h1>
<td><a href="virtualbg.html">Virtual Background</a></td>
<td>A variant of the Echo Test demo, that shows how to use something like MediaPipe to add a virtual background before sending video to Janus.</td>
</tr>
<tr>
<td><a href="vp9svctest.html">VP9-SVC Video Room</a></td>
<td>A variant of the Video Room demo, that allows you to test the VP9 SVC layer selection, if available.</td>
</tr>
<tr>
<td><a href="admin.html">Admin/Monitor</a></td>
<td>A simple page showcasing how you can use the Janus Admin/Monitor API.</td>
Expand Down
10 changes: 7 additions & 3 deletions html/echotest.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,16 @@ <h3>Demo details</h3>
back to you. You can also try and cap the bitrate: such control
will tell the server to manipulate the RTCP REMB packets passing
through, in order to simulate a bandwidth limitation. In case
you're interested in testing simulcasting, add the <code>?simulcast=true</code>
you're interested in testing simulcasting or SVC, add a
<code>?simulcast=true</code> (for simulcast) or <code>?svc=&lt;mode&gt;</code> (for SVC)
query string to the url of this page and reload it: buttons will appear
to allow you to try and switch between lower and higher quality
versions of the video you're capturing: notice that you may have
versions of the video you're capturing. Notice that you may have
to increase the bandwidth indicator to have the higher quality
versions appear, as the browser will not encode them otherwise.</p>
versions appear, as the browser will not encode them otherwise.
Besides, notice that simulcast will only work when using VP8 or H.264,
while SVC will only work if you're using VP9 on a browser that
supports setting the <code>scalabilityMode</code>.</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,
Expand Down
90 changes: 63 additions & 27 deletions html/echotest.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ if(getQueryStringValue("stereo") !== "")
stereo = (getQueryStringValue("stereo") === "true");
var doDtx = (getQueryStringValue("dtx") === "yes" || getQueryStringValue("dtx") === "true");
var doOpusred = (getQueryStringValue("opusred") === "yes" || getQueryStringValue("opusred") === "true");
var simulcastStarted = false;
var simulcastStarted = false, svcStarted = false;

// By default we talk to the "regular" EchoTest plugin
var echotestPluginBackend = "janus.plugin.echotest";
Expand Down Expand Up @@ -96,7 +96,10 @@ $(document).ready(function() {
{ type: 'audio', capture: true, recv: true },
{ type: 'video', capture: true, recv: true,
// We may need to enable simulcast or SVC on the video track
simulcast: doSimulcast, svc: (vcodec === 'av1' && doSvc) ? doSvc : null },
simulcast: doSimulcast,
// We only support SVC for VP9 and (still WIP) AV1
svc: ((vcodec === 'vp9' || vcodec === 'av1') && doSvc) ? doSvc : null
},
{ type: 'data' },
],
customizeSdp: function(jsep) {
Expand Down Expand Up @@ -200,10 +203,21 @@ $(document).ready(function() {
if((substream !== null && substream !== undefined) || (temporal !== null && temporal !== undefined)) {
if(!simulcastStarted) {
simulcastStarted = true;
addSimulcastButtons(msg["videocodec"] === "vp8");
addSimulcastSvcButtons(msg["videocodec"] === "vp8");
}
// We just received notice that there's been a switch, update the buttons
updateSimulcastButtons(substream, temporal);
updateSimulcastSvcButtons(substream, temporal);
}
// Or maybe SVC?
let spatial = msg["spatial_layer"];
temporal = msg["temporal_layer"];
if((spatial !== null && spatial !== undefined) || (temporal !== null && temporal !== undefined)) {
if(!svcStarted) {
svcStarted = true;
addSimulcastSvcButtons(true);
}
// We just received notice that there's been a switch, update the buttons
updateSimulcastSvcButtons(spatial, temporal);
}
},
onlocaltrack: function(track, on) {
Expand Down Expand Up @@ -480,8 +494,10 @@ function getQueryStringValue(name) {
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}

// Helpers to create Simulcast-related UI, if enabled
function addSimulcastButtons(temporal) {
// Helpers to create Simulcast- or SVC-related UI, if enabled
function addSimulcastSvcButtons(temporal) {
let what = (simulcastStarted ? 'simulcast' : 'SVC');
let layer = (simulcastStarted ? 'substream' : 'layer');
$('#curres').parent().append(
'<div id="simulcast" class="btn-group-vertical btn-group-vertical-xs pull-right">' +
' <div class"row">' +
Expand All @@ -499,109 +515,129 @@ function addSimulcastButtons(temporal) {
' </div>' +
' </div>' +
'</div>');
if(Janus.webRTCAdapter.browserDetails.browser !== "firefox") {
// Chromium-based browsers only have two temporal layers
if(simulcastStarted && Janus.webRTCAdapter.browserDetails.browser !== "firefox") {
// Chromium-based browsers only have two temporal layers, when doing simulcast
$('#tl-2').remove();
$('#tl-1').css('width', '50%');
$('#tl-0').css('width', '50%');
}
// Enable the simulcast selection buttons
$('#sl-0').removeClass('btn-primary btn-success').addClass('btn-primary')
.unbind('click').click(function() {
toastr.info("Switching simulcast substream, wait for it... (lower quality)", null, {timeOut: 2000});
toastr.info("Switching " + what + " " + layer + ", wait for it... (lower quality)", null, {timeOut: 2000});
if(!$('#sl-2').hasClass('btn-success'))
$('#sl-2').removeClass('btn-primary btn-info').addClass('btn-primary');
if(!$('#sl-1').hasClass('btn-success'))
$('#sl-1').removeClass('btn-primary btn-info').addClass('btn-primary');
$('#sl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
echotest.send({ message: { substream: 0 }});
if(simulcastStarted)
echotest.send({ message: { substream: 0 }});
else
echotest.send({ message: { spatial_layer: 0 }});
});
$('#sl-1').removeClass('btn-primary btn-success').addClass('btn-primary')
.unbind('click').click(function() {
toastr.info("Switching simulcast substream, wait for it... (normal quality)", null, {timeOut: 2000});
toastr.info("Switching " + what + " " + layer + ", wait for it... (normal quality)", null, {timeOut: 2000});
if(!$('#sl-2').hasClass('btn-success'))
$('#sl-2').removeClass('btn-primary btn-info').addClass('btn-primary');
$('#sl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
if(!$('#sl-0').hasClass('btn-success'))
$('#sl-0').removeClass('btn-primary btn-info').addClass('btn-primary');
echotest.send({ message: { substream: 1 }});
if(simulcastStarted)
echotest.send({ message: { substream: 1 }});
else
echotest.send({ message: { spatial_layer: 1 }});
});
$('#sl-2').removeClass('btn-primary btn-success').addClass('btn-primary')
.unbind('click').click(function() {
toastr.info("Switching simulcast substream, wait for it... (higher quality)", null, {timeOut: 2000});
toastr.info("Switching " + what + " " + layer + ", wait for it... (higher quality)", null, {timeOut: 2000});
$('#sl-2').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
if(!$('#sl-1').hasClass('btn-success'))
$('#sl-1').removeClass('btn-primary btn-info').addClass('btn-primary');
if(!$('#sl-0').hasClass('btn-success'))
$('#sl-0').removeClass('btn-primary btn-info').addClass('btn-primary');
echotest.send({ message: { substream: 2 }});
if(simulcastStarted)
echotest.send({ message: { substream: 2 }});
else
echotest.send({ message: { spatial_layer: 2 }});
});
if(!temporal) // No temporal layer support
return;
$('#tl-0').parent().removeClass('hide');
$('#tl-0').removeClass('btn-primary btn-success').addClass('btn-primary')
.unbind('click').click(function() {
toastr.info("Capping simulcast temporal layer, wait for it... (lowest FPS)", null, {timeOut: 2000});
toastr.info("Capping " + what + " temporal layer, wait for it... (lowest FPS)", null, {timeOut: 2000});
if(!$('#tl-2').hasClass('btn-success'))
$('#tl-2').removeClass('btn-primary btn-info').addClass('btn-primary');
if(!$('#tl-1').hasClass('btn-success'))
$('#tl-1').removeClass('btn-primary btn-info').addClass('btn-primary');
$('#tl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
echotest.send({ message: { temporal: 0 }});
if(simulcastStarted)
echotest.send({ message: { temporal: 0 }});
else
echotest.send({ message: { temporal_layer: 0 }});
});
$('#tl-1').removeClass('btn-primary btn-success').addClass('btn-primary')
.unbind('click').click(function() {
toastr.info("Capping simulcast temporal layer, wait for it... (medium FPS)", null, {timeOut: 2000});
toastr.info("Capping " + what + " temporal layer, wait for it... (medium FPS)", null, {timeOut: 2000});
if(!$('#tl-2').hasClass('btn-success'))
$('#tl-2').removeClass('btn-primary btn-info').addClass('btn-primary');
$('#tl-1').removeClass('btn-primary btn-info').addClass('btn-info');
if(!$('#tl-0').hasClass('btn-success'))
$('#tl-0').removeClass('btn-primary btn-info').addClass('btn-primary');
echotest.send({ message: { temporal: 1 }});
if(simulcastStarted)
echotest.send({ message: { temporal: 1 }});
else
echotest.send({ message: { temporal_layer: 1 }});
});
$('#tl-2').removeClass('btn-primary btn-success').addClass('btn-primary')
.unbind('click').click(function() {
toastr.info("Capping simulcast temporal layer, wait for it... (highest FPS)", null, {timeOut: 2000});
toastr.info("Capping " + what + " temporal layer, wait for it... (highest FPS)", null, {timeOut: 2000});
$('#tl-2').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
if(!$('#tl-1').hasClass('btn-success'))
$('#tl-1').removeClass('btn-primary btn-info').addClass('btn-primary');
if(!$('#tl-0').hasClass('btn-success'))
$('#tl-0').removeClass('btn-primary btn-info').addClass('btn-primary');
echotest.send({ message: { temporal: 2 }});
if(simulcastStarted)
echotest.send({ message: { temporal: 2 }});
else
echotest.send({ message: { temporal_layer: 2 }});
});
}

function updateSimulcastButtons(substream, temporal) {
function updateSimulcastSvcButtons(substream, temporal) {
// Check the substream
let what = (simulcastStarted ? 'simulcast' : 'SVC');
let layer = (simulcastStarted ? 'substream' : 'layer');
if(substream === 0) {
toastr.success("Switched simulcast substream! (lower quality)", null, {timeOut: 2000});
toastr.success("Switched " + what + " " + layer + "! (lower quality)", null, {timeOut: 2000});
$('#sl-2').removeClass('btn-primary btn-success').addClass('btn-primary');
$('#sl-1').removeClass('btn-primary btn-success').addClass('btn-primary');
$('#sl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
} else if(substream === 1) {
toastr.success("Switched simulcast substream! (normal quality)", null, {timeOut: 2000});
toastr.success("Switched " + what + " " + layer + "! (normal quality)", null, {timeOut: 2000});
$('#sl-2').removeClass('btn-primary btn-success').addClass('btn-primary');
$('#sl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
$('#sl-0').removeClass('btn-primary btn-success').addClass('btn-primary');
} else if(substream === 2) {
toastr.success("Switched simulcast substream! (higher quality)", null, {timeOut: 2000});
toastr.success("Switched " + what + " " + layer + "! (higher quality)", null, {timeOut: 2000});
$('#sl-2').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
$('#sl-1').removeClass('btn-primary btn-success').addClass('btn-primary');
$('#sl-0').removeClass('btn-primary btn-success').addClass('btn-primary');
}
// Check the temporal layer
if(temporal === 0) {
toastr.success("Capped simulcast temporal layer! (lowest FPS)", null, {timeOut: 2000});
toastr.success("Capped " + what + " temporal layer! (lowest FPS)", null, {timeOut: 2000});
$('#tl-2').removeClass('btn-primary btn-success').addClass('btn-primary');
$('#tl-1').removeClass('btn-primary btn-success').addClass('btn-primary');
$('#tl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
} else if(temporal === 1) {
toastr.success("Capped simulcast temporal layer! (medium FPS)", null, {timeOut: 2000});
toastr.success("Capped " + what + " temporal layer! (medium FPS)", null, {timeOut: 2000});
$('#tl-2').removeClass('btn-primary btn-success').addClass('btn-primary');
$('#tl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
$('#tl-0').removeClass('btn-primary btn-success').addClass('btn-primary');
} else if(temporal === 2) {
toastr.success("Capped simulcast temporal layer! (highest FPS)", null, {timeOut: 2000});
toastr.success("Capped " + what + " temporal layer! (highest FPS)", null, {timeOut: 2000});
$('#tl-2').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
$('#tl-1').removeClass('btn-primary btn-success').addClass('btn-primary');
$('#tl-0').removeClass('btn-primary btn-success').addClass('btn-primary');
Expand Down
27 changes: 27 additions & 0 deletions html/janus.js
Original file line number Diff line number Diff line change
Expand Up @@ -1494,6 +1494,33 @@ function Janus(gatewayCallbacks) {
request.jsep.rid_order = jsep.rid_order;
if(jsep.force_relay)
request.jsep.force_relay = true;
// Check if there's SVC video streams to tell Janus about
let svc = null;
let config = pluginHandle.webrtcStuff;
if(config.pc) {
let transceivers = config.pc.getTransceivers();
if(transceivers && transceivers.length > 0) {
for(let mindex in transceivers) {
let tr = transceivers[mindex];
if(tr && tr.sender && tr.sender.track && tr.sender.track.kind === 'video') {
let params = tr.sender.getParameters();
if(params && params.encodings && params.encodings[0] &&
params.encodings[0].scalabilityMode) {
// This video stream uses SVC
if(!svc)
svc = [];
svc.push({
mindex: parseInt(mindex),
mid: tr.mid,
svc: params.encodings[0].scalabilityMode
});
}
}
}
}
}
if(svc)
request.jsep.svc = svc;
}
Janus.debug("Sending message to plugin (handle=" + handleId + "):");
Janus.debug(request);
Expand Down
Loading

0 comments on commit ca36ad9

Please sign in to comment.