Skip to content

Commit 2a0d426

Browse files
authored
Merge pull request #718 from aboba/main
Add encode/decode time graphs to encode-decode sample
2 parents 3856bbf + 9aaf899 commit 2a0d426

File tree

3 files changed

+162
-30
lines changed

3 files changed

+162
-30
lines changed

samples/encode-decode-worker/index.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@ <h2>WebCodecs in Worker + RVFC</h2>
7575
value=3000>
7676
</div>
7777

78+
<div id="frameInput">
79+
<label for="framer">framerate: </label>
80+
<input type="text" name="framer" id="framer" minlength=2 maxlength=3 size=3
81+
value=30>
82+
</div>
83+
7884
<div id="codecButtons">
7985
<p>Codec:</p>
8086
<input type="radio" id="H264" name="codec" value="H264" onchange="getCodecValue(this)">
@@ -155,6 +161,10 @@ <h2>WebCodecs in Worker + RVFC</h2>
155161

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

164+
<div id="chart3_div" style="width: 900px; height: 500px;"></div>
165+
166+
<div id="chart4_div" style="width: 900px; height: 500px;"></div>
167+
158168
<div class="select">
159169
<label for="videoSource">Video source: </label><select id="videoSource"></select>
160170
</div>

samples/encode-decode-worker/js/main.js

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ let display_metrics = {
2020
};
2121

2222
const rate = document.querySelector('#rate');
23+
const framer = document.querySelector('#framer');
2324
const connectButton = document.querySelector('#connect');
2425
const stopButton = document.querySelector('#stop');
2526
const codecButtons = document.querySelector('#codecButtons');
@@ -28,9 +29,13 @@ const modeButtons = document.querySelector('#modeButtons');
2829
const decHwButtons = document.querySelector('#decHwButtons');
2930
const encHwButtons = document.querySelector('#encHwButtons');
3031
const chart2_div = document.getElementById('chart2_div');
32+
const chart3_div = document.getElementById('chart3_div');
33+
const chart4_div = document.getElementById('chart4_div');
3134
const videoSelect = document.querySelector('select#videoSource');
3235
const selectors = [videoSelect];
3336
chart2_div.style.display = "none";
37+
chart3_div.style.display = "none";
38+
chart4_div.style.display = "none";
3439
connectButton.disabled = false;
3540
stopButton.disabled = true;
3641

@@ -189,6 +194,8 @@ function stop() {
189194
stopButton.disabled = true;
190195
connectButton.disabled = true;
191196
chart2_div.style.display = "initial";
197+
chart3_div.style.display = "initial";
198+
chart4_div.style.display = "initial";
192199
streamWorker.postMessage({ type: "stop" });
193200
try {
194201
inputStream.cancel();
@@ -231,35 +238,42 @@ document.addEventListener('DOMContentLoaded', async function(event) {
231238
addToEventLog('Worker created.');
232239

233240
streamWorker.addEventListener('message', function(e) {
241+
let labels = '';
234242
if (e.data.severity != 'chart'){
235243
addToEventLog('Worker msg: ' + e.data.text, e.data.severity);
236244
} else {
237-
// draw the glass-glass latency chart
238-
metrics_report();
239-
const e2eX = e2e.all.map(item => item[0]);
240-
const e2eY = e2e.all.map(item => item[1]);
241-
const labels = e2e.all.map((item, index) => {
242-
return Object.keys(display_metrics.all[index]).map(key => {
243-
return `${key}: ${display_metrics.all[index][key]}`;
244-
}).join('<br>');
245+
if (e.data.text == '') {
246+
metrics_report(); // sets e2e.all and display_metrics
247+
e.data.text = JSON.stringify(e2e.all);
248+
labels = e2e.all.map((item, index) => {
249+
return Object.keys(display_metrics.all[index]).map(key => {
250+
return `${key}: ${display_metrics.all[index][key]}`;
251+
}).join('<br>');
252+
});
253+
}
254+
const parsed = JSON.parse(e.data.text);
255+
const x = parsed.map(item => item[0]);
256+
const y = parsed.map(item => item[1]);
257+
// TODO: more options needed from https://plotly.com/javascript/line-and-scatter
258+
Plotly.newPlot(e.data.div, [{
259+
x,
260+
y,
261+
text: labels,
262+
mode: 'markers',
263+
type: 'scatter',
264+
}], {
265+
xaxis: {
266+
title: e.data.x,
267+
autorange: true,
268+
range: [0, Math.max.apply(null, x) + 100 /* + a bit, 10%-ish to make it look good */],
269+
},
270+
yaxis: {
271+
title: e.data.y,
272+
autorange: true,
273+
//range: [0, Math.max.apply(null, y) /* + a bit, 10%-ish to make it look good */],
274+
},
275+
title: e.data.label,
245276
});
246-
Plotly.newPlot(chart2_div, [{
247-
x: e2eX,
248-
y: e2eY,
249-
text: labels,
250-
mode: 'markers',
251-
type: 'scatter',
252-
}], {
253-
xaxis: {
254-
title: 'Frame number',
255-
autorange: true,
256-
},
257-
yaxis: {
258-
title: 'Glass-Glass-Latency (ms)',
259-
autorange: true,
260-
},
261-
title: 'Glass-Glass Latency (ms) versus Frame Number',
262-
});
263277
}
264278
}, false);
265279

@@ -279,6 +293,7 @@ document.addEventListener('DOMContentLoaded', async function(event) {
279293
resButtons.style.display = "none";
280294
modeButtons.style.display = "none";
281295
rateInput.style.display = "none";
296+
frameInput.style.display = "none";
282297
keyInput.style.display = "none";
283298
startMedia();
284299
}
@@ -290,6 +305,9 @@ document.addEventListener('DOMContentLoaded', async function(event) {
290305
// Collect the bitrate
291306
const rate = document.getElementById('rate').value;
292307

308+
// Collect the framerate
309+
const framer = document.getElementById('framer').value;
310+
293311
// Collect the keyframe gap
294312
const keygap = document.getElementById('keygap').value;
295313

@@ -346,6 +364,7 @@ document.addEventListener('DOMContentLoaded', async function(event) {
346364
let ssrcArr = new Uint32Array(1);
347365
window.crypto.getRandomValues(ssrcArr);
348366
const ssrc = ssrcArr[0];
367+
const framerat = Math.min(framer, ts.frameRate/vConfig.framerateScale) ;
349368

350369
const config = {
351370
alpha: "discard",
@@ -357,7 +376,7 @@ document.addEventListener('DOMContentLoaded', async function(event) {
357376
hardwareAcceleration: encHw,
358377
decHwAcceleration: decHw,
359378
bitrate: rate,
360-
framerate: ts.frameRate/vConfig.framerateScale,
379+
framerate: framerat,
361380
keyInterval: vConfig.keyInterval,
362381
ssrc: ssrc
363382
};
@@ -368,7 +387,8 @@ document.addEventListener('DOMContentLoaded', async function(event) {
368387

369388
switch(preferredCodec){
370389
case "H264":
371-
config.codec = "avc1.42002A"; // baseline profile, level 4.2
390+
config.codec = "avc1.42002A"; // baseline profile, level 4.2
391+
/* config.codec = "avc1.640028"; */
372392
config.avc = { format: "annexb" };
373393
config.pt = 1;
374394
break;

samples/encode-decode-worker/js/stream_worker.js

Lines changed: 105 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,28 @@
22

33
let encoder, decoder, pl, started = false, stopped = false;
44

5+
let enc_aggregate = {
6+
all: [],
7+
};
8+
9+
let enc_time = {
10+
all: [],
11+
min: Number.MAX_VALUE,
12+
max: 0,
13+
sum: 0
14+
};
15+
16+
let dec_aggregate = {
17+
all: [],
18+
};
19+
20+
let dec_time = {
21+
all: [],
22+
min: Number.MAX_VALUE,
23+
max: 0,
24+
sum: 0
25+
};
26+
527
let encqueue_aggregate = {
628
all: [],
729
min: Number.MAX_VALUE,
@@ -18,13 +40,44 @@ let decqueue_aggregate = {
1840
sum: 0,
1941
};
2042

43+
function enc_update(data) {
44+
enc_aggregate.all.push(data);
45+
}
46+
2147
function encqueue_update(duration) {
2248
encqueue_aggregate.all.push(duration);
2349
encqueue_aggregate.min = Math.min(encqueue_aggregate.min, duration);
2450
encqueue_aggregate.max = Math.max(encqueue_aggregate.max, duration);
2551
encqueue_aggregate.sum += duration;
2652
}
2753

54+
function enc_report() {
55+
enc_aggregate.all.sort((a, b) => {
56+
return (100000 * (a.timestamp - b.timestamp) + a.output - b.output);
57+
});
58+
const len = enc_aggregate.all.length;
59+
if (len < 2) return;
60+
for (let i = 1; i < len ; i++ ) {
61+
if ((enc_aggregate.all[i].output == 1) && (enc_aggregate.all[i-1].output == 0) && (enc_aggregate.all[i].timestamp == enc_aggregate.all[i-1].timestamp)) {
62+
const timestamp = enc_aggregate.all[i].timestamp;
63+
const enc_delay = enc_aggregate.all[i].time - enc_aggregate.all[i-1].time;
64+
const data = [timestamp, enc_delay];
65+
enc_time.all.push(data);
66+
enc_time.min = Math.min(enc_time.min, enc_delay);
67+
enc_time.max = Math.max(enc_time.max, enc_delay);
68+
enc_time.sum += enc_delay;
69+
}
70+
}
71+
const avg = enc_time.sum / enc_time.all.length;
72+
//self.postMessage({text: 'Encode Time Data dump: ' + JSON.stringify(enc_time.all)});
73+
return {
74+
count: enc_time.all.length,
75+
min: enc_time.min,
76+
avg: avg,
77+
max: enc_time.max
78+
};
79+
}
80+
2881
function encqueue_report() {
2982
encqueue_aggregate.all.sort();
3083
const len = encqueue_aggregate.all.length;
@@ -47,6 +100,37 @@ function encqueue_report() {
47100
};
48101
}
49102

103+
function dec_update(data) {
104+
dec_aggregate.all.push(data);
105+
}
106+
107+
function dec_report() {
108+
dec_aggregate.all.sort((a, b) => {
109+
return (100000 * (a.timestamp - b.timestamp) + a.output - b.output);
110+
});
111+
const len = dec_aggregate.all.length;
112+
if (len < 2) return;
113+
for (let i = 1; i < len ; i++ ) {
114+
if ((dec_aggregate.all[i].output == 1) && (dec_aggregate.all[i-1].output == 0) && (dec_aggregate.all[i].timestamp == dec_aggregate.all[i-1].timestamp)) {
115+
const timestamp = dec_aggregate.all[i].timestamp;
116+
const dec_delay = dec_aggregate.all[i].time - dec_aggregate.all[i-1].time;
117+
const data = [timestamp, dec_delay];
118+
dec_time.all.push(data);
119+
dec_time.min = Math.min(dec_time.min, dec_delay);
120+
dec_time.max = Math.max(dec_time.max, dec_delay);
121+
dec_time.sum += dec_delay;
122+
}
123+
}
124+
const avg = dec_time.sum / dec_time.all.length;
125+
//self.postMessage({text: 'Decode Time Data dump: ' + JSON.stringify(dec_time.all)});
126+
return {
127+
count: dec_time.all.length,
128+
min: dec_time.min,
129+
avg: avg,
130+
max: dec_time.max
131+
};
132+
}
133+
50134
function decqueue_update(duration) {
51135
decqueue_aggregate.all.push(duration);
52136
decqueue_aggregate.min = Math.min(decqueue_aggregate.min, duration);
@@ -114,9 +198,13 @@ class pipeline {
114198
return new TransformStream({
115199
start(controller) {
116200
this.decoder = decoder = new VideoDecoder({
117-
output: frame => controller.enqueue(frame),
201+
output: (frame) => {
202+
const after = performance.now();
203+
dec_update({output: 1, timestamp: frame.timestamp, time: after});
204+
controller.enqueue(frame);
205+
},
118206
error: (e) => {
119-
self.postMessage({severity: 'fatal', text: `Init Decoder error: ${e.message}`});
207+
self.postMessage({severity: 'fatal', text: `Decoder error: ${e.message}`});
120208
}
121209
});
122210
},
@@ -139,6 +227,8 @@ class pipeline {
139227
try {
140228
const queue = this.decoder.decodeQueueSize;
141229
decqueue_update(queue);
230+
const before = performance.now();
231+
dec_update({output: 0, timestamp: chunk.timestamp, time: before});
142232
this.decoder.decode(chunk);
143233
} catch (e) {
144234
self.postMessage({severity: 'fatal', text: 'Derror size: ' + chunk.byteLength + ' seq: ' + chunk.seqNo + ' kf: ' + chunk.keyframeIndex + ' delta: ' + chunk.deltaframeIndex + ' dur: ' + chunk.duration + ' ts: ' + chunk.timestamp + ' ssrc: ' + chunk.ssrc + ' pt: ' + chunk.pt + ' tid: ' + chunk.temporalLayerId + ' type: ' + chunk.type});
@@ -175,6 +265,10 @@ class pipeline {
175265
config: decoderConfig
176266
};
177267
controller.enqueue(configChunk);
268+
}
269+
if (chunk.type != 'config'){
270+
const after = performance.now();
271+
enc_update({output: 1, timestamp: chunk.timestamp, time: after});
178272
}
179273
chunk.temporalLayerId = 0;
180274
if (cfg.svc) {
@@ -218,6 +312,8 @@ class pipeline {
218312
if (this.encoder.state != "closed") {
219313
const queue = this.encoder.encodeQueueSize;
220314
encqueue_update(queue);
315+
const before = performance.now();
316+
enc_update({output: 0, timestamp: frame.timestamp, time: before});
221317
this.encoder.encode(frame, { keyFrame: insert_keyframe });
222318
}
223319
} catch(e) {
@@ -236,10 +332,16 @@ class pipeline {
236332
this.stopped = true;
237333
const len = encqueue_aggregate.all.length;
238334
if (len > 1) {
335+
const enc_stats = enc_report();
239336
const encqueue_stats = encqueue_report();
337+
const dec_stats = dec_report();
240338
const decqueue_stats = decqueue_report();
241-
self.postMessage({severity: 'chart'});
339+
self.postMessage({severity: 'chart', x: 'Frame Number', y: 'Glass-Glass Latency', label: 'Glass-Glass Latency (ms) by Frame Number', div: 'chart2_div', text: ''});
340+
self.postMessage({severity: 'chart', x: 'Timestamp', y: 'Encoding Time', label: 'Encoding Time (ms) by Timestamp', div: 'chart3_div', text: JSON.stringify(enc_time.all)});
341+
self.postMessage({severity: 'chart', x: 'Timestamp', y: 'Decoding Time', label: 'Decoding Time (ms) by Timestamp', div: 'chart4_div', text: JSON.stringify(dec_time.all)});
342+
self.postMessage({text: 'Encoder Time report: ' + JSON.stringify(enc_stats)});
242343
self.postMessage({text: 'Encoder Queue report: ' + JSON.stringify(encqueue_stats)});
344+
self.postMessage({text: 'Decoder Time report: ' + JSON.stringify(dec_stats)});
243345
self.postMessage({text: 'Decoder Queue report: ' + JSON.stringify(decqueue_stats)});
244346
}
245347
self.postMessage({text: 'stop(): frame, encoder and decoder closed'});

0 commit comments

Comments
 (0)