Skip to content

Commit 501f971

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 301a09c commit 501f971

File tree

3 files changed

+191
-3
lines changed

3 files changed

+191
-3
lines changed

index.html

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ <h2 id="getusermedia"><a href="https://developer.mozilla.org/en-US/docs/Web/API/
100100
<li><a href="src/content/getusermedia/getdisplaymedia/">Screensharing with getDisplayMedia</a></li>
101101

102102
<li><a href="src/content/getusermedia/pan-tilt-zoom/">Control camera pan, tilt, and zoom</a></li>
103-
103+
104104
<li><a href="src/content/getusermedia/exposure/">Control exposure</a></li>
105105
</ul>
106106
<h2 id="devices">Devices:</h2>
@@ -210,8 +210,9 @@ <h2 id="capture">Insertable Streams:</h2>
210210
<li><a href="src/content/insertable-streams/video-processing">Video processing using MediaStream Insertable Streams</a></li> (Experimental)
211211
<li><a href="src/content/insertable-streams/audio-processing">Audio processing using MediaStream Insertable Streams</a></li> (Experimental)
212212
<li><a href="src/content/insertable-streams/video-crop">Video cropping using MediaStream Insertable Streams in a Worker</a></li> (Experimental)
213-
<li><a href="src/content/insertable-streams/webgpu">Integrations with WebGPU for custom video rendering:</a></li> (Experimental)
214-
</ul>
213+
<li><a href="src/content/insertable-streams/webgpu">Integrations with WebGPU for custom video rendering</a></li> (Experimental)
214+
<li><a href="src/content/insertable-streams/video-replay">Play a IVF file generated by video_replay in the browser</a></li> (Experimental)
215+
</ul>
215216

216217
</section>
217218

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 - video_replay in the browser using WebCodecs</title>
24+
25+
<link rel="icon" sizes="192x192" href="../../../images/webrtc-icon-192x192.png">
26+
<link href="https://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: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
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 MediaStreamTrackGenerator, EncodedVideoChunk */
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 generator = new MediaStreamTrackGenerator('video');
82+
const writer = generator.writable.getWriter();
83+
localVideo.srcObject = new MediaStream([generator]);
84+
85+
const header = await ivf.readHeader();
86+
if (header) {
87+
metadata.innerText = 'File metadata: ' + JSON.stringify(header, null, ' ');
88+
} else {
89+
metadata.innerText = 'Failed to load IVF file.';
90+
return;
91+
}
92+
93+
const decoder = new VideoDecoder({
94+
output: async (frame) => {
95+
await writer.write(frame);
96+
frame.close();
97+
const nextFrame = await ivf.readFrame();
98+
if (nextFrame) {
99+
decoder.decode(new EncodedVideoChunk({
100+
timestamp: Number(nextFrame.timestamp - firstFrame.timestamp) * 1000,
101+
type: 'delta',
102+
data: nextFrame.data,
103+
}));
104+
} else {
105+
decoder.flush();
106+
}
107+
},
108+
error: e => console.error(e.message, e),
109+
});
110+
decoder.configure({
111+
codec: IVF2WebCodecs[header.codec],
112+
codedWidth: header.width,
113+
codedHeight: header.height,
114+
});
115+
const firstFrame = await ivf.readFrame();
116+
decoder.decode(new EncodedVideoChunk({
117+
timestamp: 0,
118+
type: 'key',
119+
data: firstFrame.data,
120+
}));
121+
};

0 commit comments

Comments
 (0)