Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental Vp9 SVC support #1838

Draft
wants to merge 27 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions erizo_controller/erizoClient/lib/unifiedPlanUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.addLegacySimulcast = exports.getRtpEncodings = void 0;
function getRtpEncodings({ offerMediaObject }) {
const ssrcs = new Set();
for (const line of offerMediaObject.ssrcs || []) {
const ssrc = line.id;
ssrcs.add(ssrc);
}
if (ssrcs.size === 0)
throw new Error('no a=ssrc lines found');
const ssrcToRtxSsrc = new Map();
// First assume RTX is used.
for (const line of offerMediaObject.ssrcGroups || []) {
if (line.semantics !== 'FID')
continue;
let [ssrc, rtxSsrc] = line.ssrcs.split(/\s+/);
ssrc = Number(ssrc);
rtxSsrc = Number(rtxSsrc);
if (ssrcs.has(ssrc)) {
// Remove both the SSRC and RTX SSRC from the set so later we know that they
// are already handled.
ssrcs.delete(ssrc);
ssrcs.delete(rtxSsrc);
// Add to the map.
ssrcToRtxSsrc.set(ssrc, rtxSsrc);
}
}
// If the set of SSRCs is not empty it means that RTX is not being used, so take
// media SSRCs from there.
for (const ssrc of ssrcs) {
// Add to the map.
ssrcToRtxSsrc.set(ssrc, null);
}
const encodings = [];
for (const [ssrc, rtxSsrc] of ssrcToRtxSsrc) {
const encoding = { ssrc };
if (rtxSsrc)
encoding.rtx = { ssrc: rtxSsrc };
encodings.push(encoding);
}
return encodings;
}
exports.getRtpEncodings = getRtpEncodings;
/**
* Adds multi-ssrc based simulcast into the given SDP media section offer.
*/
function addLegacySimulcast({ offerMediaObject, numStreams }) {
if (numStreams <= 1)
console.log('numStreams must be greater than 1');
// Get the SSRC.
const ssrcMsidLine = (offerMediaObject.ssrcs || [])
.find((line) => line.attribute === 'msid');
if (!ssrcMsidLine)
console.log('a=ssrc line with msid information not found');
const [streamId, trackId] = ssrcMsidLine.value.split(' ');
const firstSsrc = ssrcMsidLine.id;
let firstRtxSsrc;
// Get the SSRC for RTX.
(offerMediaObject.ssrcGroups || [])
.some((line) => {
if (line.semantics !== 'FID')
return false;
const ssrcs = line.ssrcs.split(/\s+/);
if (Number(ssrcs[0]) === firstSsrc) {
firstRtxSsrc = Number(ssrcs[1]);
return true;
}
else {
return false;
}
});
const ssrcCnameLine = offerMediaObject.ssrcs
.find((line) => line.attribute === 'cname');
if (!ssrcCnameLine)
console.log('a=ssrc line with cname information not found');
const cname = ssrcCnameLine.value;
const ssrcs = [];
const rtxSsrcs = [];
for (let i = 0; i < numStreams; ++i) {
ssrcs.push(firstSsrc + i);
if (firstRtxSsrc)
rtxSsrcs.push(firstRtxSsrc + i);
}
offerMediaObject.ssrcGroups = [];
offerMediaObject.ssrcs = [];
offerMediaObject.ssrcGroups.push({
semantics: 'SIM',
ssrcs: ssrcs.join(' ')
});
for (let i = 0; i < ssrcs.length; ++i) {
const ssrc = ssrcs[i];
offerMediaObject.ssrcs.push({
id: ssrc,
attribute: 'cname',
value: cname
});
offerMediaObject.ssrcs.push({
id: ssrc,
attribute: 'msid',
value: `${streamId} ${trackId}`
});
}
for (let i = 0; i < rtxSsrcs.length; ++i) {
const ssrc = ssrcs[i];
const rtxSsrc = rtxSsrcs[i];
offerMediaObject.ssrcs.push({
id: rtxSsrc,
attribute: 'cname',
value: cname
});
offerMediaObject.ssrcs.push({
id: rtxSsrc,
attribute: 'msid',
value: `${streamId} ${trackId}`
});
offerMediaObject.ssrcGroups.push({
semantics: 'FID',
ssrcs: `${ssrc} ${rtxSsrc}`
});
}
}
exports.addLegacySimulcast = addLegacySimulcast;
3 changes: 3 additions & 0 deletions erizo_controller/erizoClient/src/Room.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ const Room = (altIo, altConnectionHelpers, altConnectionManager, specInput) => {
disableIceRestart: that.disableIceRestart,
forceTurn: stream.forceTurn,
p2p: false,
svc: options.svc,
streamRemovedListener: onRemoteStreamRemovedListener,
isRemote,
};
Expand Down Expand Up @@ -832,6 +833,8 @@ const Room = (altIo, altConnectionHelpers, altConnectionManager, specInput) => {
video: stream.videoMuted,
};

options.svc = options.svc || false;

// 1- If the stream is not local or it is a failed stream we do nothing.
if (stream && stream.local && !stream.failed && !localStreams.has(stream.getID())) {
// 2- Publish Media Stream to Erizo-Controller
Expand Down
3 changes: 2 additions & 1 deletion erizo_controller/erizoClient/src/Stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ const Stream = (altConnectionHelpers, specInput) => {
that.hasSimulcast = () => Object.keys(videoSenderLicodeParameters).length > 1;

that.generateEncoderParameters = () => {
const nativeSenderParameters = [];
let nativeSenderParameters = [];
const requestedLayers = Object.keys(videoSenderLicodeParameters).length ||
defaultSimulcastSpatialLayers;
const isScreenshare = that.hasScreen();
Expand All @@ -236,6 +236,7 @@ const Stream = (altConnectionHelpers, specInput) => {
layerConfig.scaleResolutionDownBy = base ** (requestedLayers - layer);
nativeSenderParameters.push(layerConfig);
}
nativeSenderParameters = [nativeSenderParameters];
return nativeSenderParameters;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import BaseStack from './BaseStack';
import SdpHelpers from './../utils/SdpHelpers';
import Logger from '../utils/Logger';
import { addLegacySimulcast } from '../../lib/unifiedPlanUtils';

const sdpTransform = require('sdp-transform');

const log = Logger.module('ChromeStableStack');

Expand Down Expand Up @@ -29,6 +32,29 @@ const ChromeStableStack = (specInput) => {
}
};

if (specInput.svc) {
that.peerConnection.onnegotiationneeded = async () => {
// This is for testing the negotiation step by step
if (specInput.managed) {
return;
}
try {
let offer = await that.peerConnection.createOffer();
const localOffer = sdpTransform.parse(offer.sdp);
const offerMediaObject = localOffer.media[localOffer.media.length - 1];
addLegacySimulcast({ offerMediaObject, numStreams: 3 });
offer = { type: 'offer', sdp: sdpTransform.write(localOffer) };
await that.peerConnection.setLocalDescription(offer);
spec.callback({
type: that.peerConnection.localDescription.type,
sdp: that.peerConnection.localDescription.sdp,
});
} catch (e) {
log.error('onnegotiationneeded - error', e.message);
}
};
}

return that;
};

Expand Down
1 change: 1 addition & 0 deletions extras/basic_example/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<button id="startButton" onclick="startBasicExample()" disabled>Start</button>
<button id="testConnection" onclick="testConnection()">Test Connection</button>
<button id="slideShowMode" onclick="toggleSlideShowMode()" disabled>Toggle Slide Show Mode</button>
<button id="layerControl" onclick="toggleLayerControls()" >Toggle Layer Controls</button>
</div>
<div style="margin-top: 5px;">
<label>Publish:</label>
Expand Down
48 changes: 46 additions & 2 deletions extras/basic_example/public/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ let localStream;
let room;
let localStreamIndex = 0;
const localStreams = new Map();
let layerControlsActive = false;
const configFlags = {
noStart: false, // disable start button when only subscribe
forceStart: false, // force start button in all cases
Expand All @@ -30,20 +31,48 @@ const createSubscriberContainer = (stream) => {
container.setAttribute('style', 'width: 320px; height: 280px;float:left;');
container.setAttribute('id', `container_${stream.getID()}`);

const layerControlDiv = document.createElement('div');
layerControlDiv.setAttribute('class', 'controlDiv');
layerControlDiv.hidden = !layerControlsActive;

const videoContainer = document.createElement('div');
videoContainer.setAttribute('style', 'width: 320px; height: 240px;');
videoContainer.setAttribute('id', `test${stream.getID()}`);
container.appendChild(videoContainer);

const unsubscribeButton = document.createElement('button');
unsubscribeButton.textContent = 'Unsubscribe';
unsubscribeButton.setAttribute('style', 'float:left;');

const slideshowButton = document.createElement('button');
slideshowButton.textContent = 'Toggle Slideshow';
slideshowButton.setAttribute('style', 'float:left;');
stream.slideshowMode = false;

container.appendChild(unsubscribeButton);
container.appendChild(slideshowButton);

const layerChangeButton = document.createElement('button');
layerChangeButton.textContent = 'Change Layer';
layerChangeButton.onclick = () => {
// eslint-disable-next-line no-use-before-define
changeLayer(stream, sLayer.value, tLayer.value);
};

const tLayer = document.createElement('input');
tLayer.setAttribute('type', 'number');
tLayer.setAttribute('id', `tLayer${stream.getID()}`);
tLayer.setAttribute('placeholder', 'Temporal Layer');

const sLayer = document.createElement('input');
sLayer.setAttribute('type', 'number');
sLayer.setAttribute('id', `sLayer${stream.getID()}`);
sLayer.setAttribute('placeholder', 'Spatial Layer');

layerControlDiv.appendChild(sLayer);
layerControlDiv.appendChild(tLayer);
layerControlDiv.appendChild(layerChangeButton);
container.appendChild(layerControlDiv);

unsubscribeButton.onclick = () => {
room.unsubscribe(stream);
document.getElementById('videoContainer').removeChild(container);
Expand Down Expand Up @@ -100,7 +129,6 @@ const createPublisherContainer = (stream, index) => {
stopRecordButton.hidden = true;
};


const div = document.createElement('div');
div.setAttribute('style', 'width: 320px; height: 240px; float:left');
div.setAttribute('id', `myVideo${index}`);
Expand Down Expand Up @@ -335,6 +363,22 @@ const startBasicExample = () => {
});
};

// eslint-disable-next-line no-unused-vars
const changeLayer = (stream, s, t) => {
stream._setStaticQualityLayer(s, t);
};

// eslint-disable-next-line no-unused-vars
const toggleLayerControls = () => {
layerControlsActive = !layerControlsActive;
// eslint-disable-next-line no-return-assign
const list = document.getElementsByClassName('controlDiv');
// eslint-disable-next-line no-restricted-syntax
for (const item of list) {
item.hidden = !layerControlsActive;
}
};

window.onload = () => {
fillInConfigFlagsFromParameters(configFlags);
window.configFlags = configFlags;
Expand Down