Skip to content

Commit

Permalink
Merge pull request #611 from aboba/main
Browse files Browse the repository at this point in the history
Update WebTransport Echo to support HEVC, display glass to glass delay
  • Loading branch information
jan-ivar authored Jul 30, 2024
2 parents d5781c2 + 1b857f6 commit eb01239
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 113 deletions.
40 changes: 25 additions & 15 deletions samples/webcodecs-echo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,13 @@ <h2>WebTransport Echo with WebCodecs in Worker + RVFC</h2>
<div id="rateInput">
<label for="rate">bitrate: </label>
<input type="text" name="rate" id="rate" minlength=2 maxlength=8 size=8
value=100000>
value=300000>
</div>

<div id="frameInput">
<label for="framer">framerate: </label>
<input type="text" name="framer" id="framer" minlength=2 maxlength=3 size=3
value=30>
</div>

<div id="keyInput">
Expand All @@ -83,9 +89,9 @@ <h2>WebTransport Echo with WebCodecs in Worker + RVFC</h2>
<div id="codecButtons">
<p>Codec:</p>
<input type="radio" id="H264" name="codec" value="H264" onchange="getCodecValue(this)">
<label for="H.264">H.264</label><br>
<label for="H264">H.264</label><br>
<input type="radio" id="H265" name="codec" value="H265" onchange="getCodecValue(this)">
<label for="H.265">H.265</label><br>
<label for="H265">H.265</label><br>
<input type="radio" id="VP8" name="codec" value="VP8" checked="checked" onchange="getCodecValue(this)">
<label for="VP8">VP8</label><br>
<input type="radio" id="VP9" name="codec" value="VP9" onchange="getCodecValue(this)">
Expand All @@ -96,22 +102,22 @@ <h2>WebTransport Echo with WebCodecs in Worker + RVFC</h2>

<div id="encHwButtons">
<p>Encoder Hardware Acceleration Preference:</p>
<input type="radio" id="hw" name="encHwAccel" value="prefer-hardware" onchange="getEncHwValue(this)">
<label for="hw">Prefer Hardware</label><br>
<input type="radio" id="sw" name="encHwAccel" value="prefer-software" onchange="getEncHwValue(this)">
<label for="sw">Prefer Software</label><br>
<input type="radio" id="no-pref" name="encHwAccel" value="no-preference" checked="checked" onchange="getEncHwValue(this)">
<label for="no-pref">No Preference</label><br>
<input type="radio" id="encHw" name="encHwAccel" value="prefer-hardware" onchange="getEncHwValue(this)">
<label for="encHw">Prefer Hardware</label><br>
<input type="radio" id="encSw" name="encHwAccel" value="prefer-software" onchange="getEncHwValue(this)">
<label for="encSw">Prefer Software</label><br>
<input type="radio" id="encNo-pref" name="encHwAccel" value="no-preference" checked="checked" onchange="getEncHwValue(this)">
<label for="encNo-pref">No Preference</label><br>
</div>

<div id="decHwButtons">
<p>Decoder Hardware Acceleration Preference:</p>
<input type="radio" id="hw" name="decHwAccel" value="prefer-hardware" onchange="getDecHwValue(this)">
<label for="hw">Prefer Hardware</label><br>
<input type="radio" id="sw" name="decHwAccel" value="prefer-software" onchange="getDecHwValue(this)">
<label for="sw">Prefer Software</label><br>
<input type="radio" id="no-pref" name="decHwAccel" value="no-preference" checked="checked" onchange="getDecHwValue(this)">
<label for="no-pref">No Preference</label><br>
<input type="radio" id="decHw" name="decHwAccel" value="prefer-hardware" onchange="getDecHwValue(this)">
<label for="decHw">Prefer Hardware</label><br>
<input type="radio" id="decSw" name="decHwAccel" value="prefer-software" onchange="getDecHwValue(this)">
<label for="decSw">Prefer Software</label><br>
<input type="radio" id="decNo-pref" name="decHwAccel" value="no-preference" checked="checked" onchange="getDecHwValue(this)">
<label for="decNo-pref">No Preference</label><br>
</div>

<div id="prefButtons">
Expand Down Expand Up @@ -162,6 +168,10 @@ <h2>WebTransport Echo with WebCodecs in Worker + RVFC</h2>

<div id="chart2_div" style="width: 900px; height: 500px;"></div>

<div id="chart3_div" style="width: 900px; height: 500px;"></div>

<div id="chart4_div" style="width: 900px; height: 500px;"></div>

<div class="select">
<label for="videoSource">Video source: </label><select id="videoSource"></select>
</div>
Expand Down
90 changes: 45 additions & 45 deletions samples/webcodecs-echo/js/main.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';

let preferredResolution;
let mediaStream, videoSource, bitrate = 100000;
let mediaStream, videoSource, bitrate = 300000;
let stopped = false;
let preferredCodec ="VP8";
let mode = "L1T3";
Expand All @@ -18,7 +18,9 @@ let e2e = {
let display_metrics = {
all: [],
};

const rate = document.querySelector('#rate');
const framer = document.querySelector('#framer');
const connectButton = document.querySelector('#connect');
const stopButton = document.querySelector('#stop');
const codecButtons = document.querySelector('#codecButtons');
Expand All @@ -28,12 +30,16 @@ const decHwButtons = document.querySelector('#decHwButtons');
const encHwButtons = document.querySelector('#encHwButtons');
const chart_div = document.getElementById('chart_div');
const chart2_div = document.getElementById('chart2_div');
const chart3_div = document.getElementById('chart3_div');
const chart4_div = document.getElementById('chart4_div');
const videoSelect = document.querySelector('select#videoSource');
const outputVideo = document.getElementById('outputVideo');
const inputVideo = document.getElementById('inputVideo');
const selectors = [videoSelect];
chart_div.style.display = "none";
chart2_div.style.display = "none";
chart3_div.style.display = "none";
chart4_div.style.display = "none";
connectButton.disabled = false;
stopButton.disabled = true;

Expand All @@ -56,26 +62,28 @@ function metrics_update(data) {

function metrics_report() {
metrics.all.sort((a, b) => {
return (100000 * (a.mediaTime - b.mediaTime) + a.output - b.output);
return (100000 * (b.mediaTime - a.mediaTime) + b.output - a.output);
});
const len = metrics.all.length;
if (len < 2) return;
for (let i = 0; i < len ; i++ ) {
if (metrics.all[i].output == 1) {
const frameno = metrics.all[i].presentedFrames;
const g2g = metrics.all[i].expectedDisplayTime - metrics.all[i-1].captureTime;
const fps = metrics.all[i].fps;
const time = metrics.all[i].elapsed;
const mediaTime = metrics.all[i].mediaTime;
const captureTime = metrics.all[i-1].captureTime;
const captureTime = metrics.all[i].captureTime;
const expectedDisplayTime = metrics.all[i].expectedDisplayTime;
const delay = metrics.all[i].expectedDisplayTime - metrics.all[i-1].expectedDisplayTime;
const g2g = Math.max(0, expectedDisplayTime - captureTime);
const data = [frameno, g2g];
const info = {frameno: frameno, g2g: g2g, mediaTime: mediaTime, captureTime: captureTime, expectedDisplayTime: expectedDisplayTime, delay: delay};
const info = {frameno: frameno, fps: fps, time: time, g2g: g2g, mediaTime: mediaTime, captureTime: captureTime, expectedDisplayTime: expectedDisplayTime};
e2e.all.push(data);
display_metrics.all.push(info);
}
}
// addToEventLog('Data dump: ' + JSON.stringify(e2e.all));
return {
count: e2e.all.length
count: metrics.all.length
};
}

Expand Down Expand Up @@ -196,6 +204,8 @@ function stop() {
connectButton.disabled = true;
chart_div.style.display = "initial";
chart2_div.style.display = "initial";
chart3_div.style.display = "initial";
chart4_div.style.display = "initial";
streamWorker.postMessage({ type: "stop" });
try {
inputStream.cancel();
Expand Down Expand Up @@ -244,58 +254,41 @@ document.addEventListener('DOMContentLoaded', async function(event) {
addToEventLog('Worker created.');
// Print messages from the worker in the text area.
streamWorker.addEventListener('message', function(e) {
let labels = '';
if (e.data.severity != 'chart'){
addToEventLog('Worker msg: ' + e.data.text, e.data.severity);
} else {
if (e.data.text == '') {
metrics_report(); // sets e2e.all and display_metrics
e.data.text = JSON.stringify(e2e.all);
labels = e2e.all.map((item, index) => {
return Object.keys(display_metrics.all[index]).map(key => {
return `${key}: ${display_metrics.all[index][key]}`;
}).join('<br>');
});
}
const parsed = JSON.parse(e.data.text);
const x = parsed.map(item => item[0]);
const y = parsed.map(item => item[1]);
// TODO: more options needed from https://plotly.com/javascript/line-and-scatter
Plotly.newPlot(chart_div, [{
Plotly.newPlot(e.data.div, [{
x,
y,
text: labels,
mode: 'markers',
type: 'scatter',
}], {
xaxis: {
title: 'Length', // Frame size (bytes)?
autorange: false,
title: e.data.x,
autorange: true,
range: [0, Math.max.apply(null, x) + 100 /* + a bit, 10%-ish to make it look good */],
},
yaxis: {
title: 'RTT',
//autorange: false,
title: e.data.y,
autorange: true,
//range: [0, Math.max.apply(null, y) /* + a bit, 10%-ish to make it look good */],
},
title: 'RTT (ms) versus Frame length',
});
// draw the glass-glass latency chart
metrics_report(); // sets e2e.all and display_metrics
const e2eX = e2e.all.map(item => item[0]);
const e2eY = e2e.all.map(item => item[1]);
const labels = e2e.all.map((item, index) => {
return Object.keys(display_metrics.all[index]).map(key => {
return `${key}: ${display_metrics.all[index][key]}`;
}).join('<br>');
});
Plotly.newPlot(chart2_div, [{
x: e2eX,
y: e2eY,
text: labels,
mode: 'markers',
type: 'scatter',
}], {
xaxis: {
title: 'Frame Number',
autorange: false,
range: [0, Math.max.apply(null, e2eX) + 100 /* + a bit, 10%-ish to make it look good */],
},
yaxis: {
title: 'Glass-Glass Latency',
//autorange: false,
//range: [0, Math.max.apply(null, e2eY) /* + a bit, 10%-ish to make it look good */],
},
title: 'Glass-Glass Latency (ms) versus Frame Number',
title: e.data.label,
});
}
}, false);
Expand All @@ -316,6 +309,7 @@ document.addEventListener('DOMContentLoaded', async function(event) {
resButtons.style.display = "none";
modeButtons.style.display = "none";
rateInput.style.display = "none";
frameInput.style.display = "none";
keyInput.style.display = "none";
startMedia();
}
Expand All @@ -326,6 +320,9 @@ document.addEventListener('DOMContentLoaded', async function(event) {
// Collect the bitrate
const rate = document.getElementById('rate').value;

// Collect the framerate
const framer = document.getElementById('framer').value;

// Collect the keyframe gap
const keygap = document.getElementById('keygap').value;

Expand Down Expand Up @@ -354,6 +351,7 @@ document.addEventListener('DOMContentLoaded', async function(event) {
let elapsed = (now - start_time)/1000.;
let fps = (++paint_count / elapsed).toFixed(3);
metadata.fps = fps;
metadata.elapsed = elapsed;
metrics_update(metadata);
outputVideo.requestVideoFrameCallback(recordOutputFrames);
};
Expand All @@ -367,6 +365,7 @@ document.addEventListener('DOMContentLoaded', async function(event) {
let elapsed = (now - start_time)/1000.;
let fps = (++paint_count / elapsed).toFixed(3);
metadata.fps = fps;
metadata.elapsed = elapsed;
metrics_update(metadata);
inputVideo.requestVideoFrameCallback(recordInputFrames);
};
Expand All @@ -383,6 +382,7 @@ document.addEventListener('DOMContentLoaded', async function(event) {
let ssrcArr = new Uint32Array(1);
window.crypto.getRandomValues(ssrcArr);
const ssrc = ssrcArr[0];
const framerat = Math.min(framer, ts.frameRate/vConfig.framerateScale) ;

const config = {
alpha: "discard",
Expand All @@ -394,7 +394,7 @@ document.addEventListener('DOMContentLoaded', async function(event) {
hardwareAcceleration: encHw,
decHwAcceleration: decHw,
bitrate: rate,
framerate: ts.frameRate/vConfig.framerateScale,
framerate: framerat,
keyInterval: vConfig.keyInterval,
ssrc: ssrc
};
Expand All @@ -410,10 +410,10 @@ document.addEventListener('DOMContentLoaded', async function(event) {
config.pt = 1;
break;
case "H265":
config.codec = "hev1.1.6.L120.B0"; // Main profile, level 4, up to 2048 x 1080@30
// config.codec = "hev1.1.6.L120.B0"; // Main profile, level 4, up to 2048 x 1080@30
// config.codec = "hev1.1.4.L93.B0" Main 10
// config.codec = "hev1.2.4.L120.B0" Main 10, Level 4.0
// config.codec = "hev1.1.6.L93.B0"; // Main profile, level 3.1, up to 1280 x [email protected]
config.codec = "hev1.1.6.L93.B0"; // Main profile, level 3.1, up to 1280 x [email protected]
config.hevc = { format: "annexb" };
config.pt = 2;
break;
Expand All @@ -426,7 +426,7 @@ document.addEventListener('DOMContentLoaded', async function(event) {
config.pt = 4;
break;
case "AV1":
config.codec = "av01.0.08M.10.0.110.09" // AV1 Main Profile, level 4.0, Main tier, 10-bit content, non-monochrome, with 4:2:0 chroma subsampling
config.codec = "av01.0.08M.08.0.110.09" // AV1 Main Profile, level 4.0, Main tier, 8-bit content, non-monochrome, with 4:2:0 chroma subsampling
config.pt = 5;
break;
}
Expand Down
Loading

0 comments on commit eb01239

Please sign in to comment.