Skip to content

Commit d3cd2d2

Browse files
committed
add video_replay sample
which allows replaying IVF files generated by video_replay in the browser. This is useful for debugging issues like https://bugs.chromium.org/p/chromium/issues/detail?id=1418596 where the native video_replay is using software decoder which do not show the issue.
1 parent 379a6ab commit d3cd2d2

File tree

2 files changed

+188
-0
lines changed

2 files changed

+188
-0
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<!DOCTYPE html>
2+
<!--
3+
* Copyright (c) 2023 The WebRTC project authors. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by a BSD-style license
6+
* that can be found in the LICENSE file in the root of the source
7+
* tree.
8+
-->
9+
<html>
10+
<head>
11+
12+
<meta charset="utf-8">
13+
<meta name="description" content="WebRTC code samples">
14+
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1">
15+
<meta itemprop="description" content="Client-side WebRTC code samples">
16+
<meta itemprop="image" content="../../../images/webrtc-icon-192x192.png">
17+
<meta itemprop="name" content="WebRTC code samples">
18+
<meta name="mobile-web-app-capable" content="yes">
19+
<meta id="theme-color" name="theme-color" content="#ffffff">
20+
21+
<base target="_blank">
22+
23+
<title>Insertable Streams - Crop in a worker</title>
24+
25+
<link rel="icon" sizes="192x192" href="../../../images/webrtc-icon-192x192.png">
26+
<link href="//fonts.googleapis.com/css?family=Roboto:300,400,500,700" rel="stylesheet" type="text/css">
27+
<link rel="stylesheet" href="../../../css/main.css"/>
28+
</head>
29+
30+
<body>
31+
32+
<div id="container">
33+
<h1><a href="//webrtc.github.io/samples/" title="WebRTC samples homepage">WebRTC samples</a>
34+
<span>video_replay for Chrome</span></h1>
35+
36+
<p>
37+
This sample shows how how load an IVF file generated by libWebRTC's
38+
<a href="https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/rtc_tools/video_replay.cc">
39+
video_replay tool</a> in the browser using WebCodecs and MediaStreamTrackGenerator.
40+
This is useful since the browser uses different decoders for video than the native libWebRTC ones.
41+
</p>
42+
43+
<video id="localVideo" playsinline autoplay muted></video>
44+
45+
<div class="box">
46+
<label for="input">IVF file to load:</label>
47+
<input id="input" type="file">
48+
</div>
49+
<div id="metadata">
50+
</div>
51+
52+
<p>
53+
<b>Note</b>: This sample is using an experimental API that has not yet been standardized. As
54+
of 2023-02-27, this API is available in the latest version of Chrome based browsers.
55+
</p>
56+
<a href="https://github.com/webrtc/samples/tree/gh-pages/src/content/insertable-streams/video-replay"
57+
title="View source for this page on GitHub" id="viewSource">View source on GitHub</a>
58+
59+
</div>
60+
61+
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
62+
<script src="js/main.js" async></script>
63+
64+
<script src="../../../js/lib/ga.js"></script>
65+
</body>
66+
</html>
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* Copyright (c) 2023 The WebRTC project authors. All Rights Reserved.
3+
*
4+
* Use of this source code is governed by a BSD-style license
5+
* that can be found in the LICENSE file in the root of the source
6+
* tree.
7+
*/
8+
9+
'use strict';
10+
11+
/* global MediaStreamTrackProcessor, MediaStreamTrackGenerator */
12+
if (typeof MediaStreamTrackGenerator === 'undefined') {
13+
alert(
14+
'Your browser does not support the experimental MediaStreamTrack API ' +
15+
'for Insertable Streams of Media. See the note at the bottom of the ' +
16+
'page.');
17+
}
18+
19+
// Reader for the IVF file format as described by
20+
// https://wiki.multimedia.cx/index.php/Duck_IVF
21+
class IVF {
22+
constructor(file) {
23+
this.blob = file;
24+
this.offset = 0;
25+
}
26+
27+
async readHeader() {
28+
if (this.offset !== 0) {
29+
console.error('readHeader called not at start of file.');
30+
return;
31+
}
32+
this.offset = 32;
33+
34+
const header = await this.blob.slice(0, 32).arrayBuffer();
35+
const view = new DataView(header);
36+
const decoder = new TextDecoder('ascii');
37+
return {
38+
codec: decoder.decode(header.slice(8, 12)),
39+
width: view.getUint16(12, true),
40+
height: view.getUint16(14, true),
41+
fpsDenominator: view.getUint32(16, true),
42+
fpsNumerator: view.getUint32(20, true),
43+
};
44+
}
45+
46+
async readFrame() {
47+
if (this.offset == this.blob.size) {
48+
return; // done.
49+
} else if (this.offset === 0) {
50+
console.error('readFrame called without reading header.');
51+
return;
52+
}
53+
const header = await this.blob.slice(this.offset, this.offset + 12).arrayBuffer();
54+
const view = new DataView(header);
55+
const frameLength = view.getUint32(0, true);
56+
const timestamp = view.getBigUint64(4, true);
57+
const currentOffset = this.offset;
58+
this.offset += 12 + frameLength;
59+
return {
60+
timestamp,
61+
data: new Uint8Array(await this.blob.slice(currentOffset + 12, currentOffset + 12 + frameLength).arrayBuffer()),
62+
};
63+
}
64+
}
65+
66+
// Translate between IVF fourcc codec names and WebCodec named.
67+
const IVF2WebCodecs = {
68+
VP80: 'vp8',
69+
VP90: 'vp09.00.10.08',
70+
H264: 'avc1.42E01F',
71+
};
72+
73+
const input = document.getElementById('input');
74+
const localVideo = document.getElementById('localVideo');
75+
const metadata = document.getElementById('metadata');
76+
77+
input.onchange = async (event) => {
78+
event.target.disabled = true;
79+
const file = event.target.files[0];
80+
const ivf = new IVF(file);
81+
const canvas = document.getElementById('canvas');
82+
const generator = new MediaStreamTrackGenerator('video');
83+
const writer = generator.writable.getWriter();
84+
localVideo.srcObject = new MediaStream([generator]);
85+
86+
const header = await ivf.readHeader();
87+
if (header) {
88+
metadata.innerText = 'File metadata: ' + JSON.stringify(header, null, ' ');
89+
} else {
90+
metadata.innerText = 'Failed to load IVF file.';
91+
return;
92+
}
93+
94+
const decoder = new VideoDecoder({
95+
output: async (frame) => {
96+
await writer.write(frame);
97+
frame.close();
98+
const nextFrame = await ivf.readFrame()
99+
if (nextFrame) {
100+
decoder.decode(new EncodedVideoChunk({
101+
timestamp: Number(nextFrame.timestamp - firstFrame.timestamp) * 1000,
102+
type: 'delta',
103+
data: nextFrame.data,
104+
}));
105+
}else {
106+
decoder.flush();
107+
}
108+
},
109+
error: e => console.error(e.message, e),
110+
});
111+
decoder.configure({
112+
codec: IVF2WebCodecs[header.codec],
113+
codedWidth: header.width,
114+
codedHeight: header.height,
115+
})
116+
const firstFrame = await ivf.readFrame();
117+
decoder.decode(new EncodedVideoChunk({
118+
timestamp: 0,
119+
type: 'key',
120+
data: firstFrame.data,
121+
}));
122+
}

0 commit comments

Comments
 (0)