webcodecs-utils 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +142 -0
- package/dist/audio/extract-channels.d.ts +45 -0
- package/dist/audio/extract-channels.d.ts.map +1 -0
- package/dist/audio/extract-channels.js +25 -0
- package/dist/audio/get-sample-rate.d.ts +19 -0
- package/dist/audio/get-sample-rate.d.ts.map +1 -0
- package/dist/audio/get-sample-rate.js +14 -0
- package/dist/audio/mp3.d.ts +162 -0
- package/dist/audio/mp3.d.ts.map +1 -0
- package/dist/audio/mp3.js +173 -0
- package/dist/demux/example-muxer.d.ts +74 -0
- package/dist/demux/example-muxer.d.ts.map +1 -0
- package/dist/demux/example-muxer.js +40 -0
- package/dist/demux/get-chunks.d.ts +81 -0
- package/dist/demux/get-chunks.d.ts.map +1 -0
- package/dist/demux/get-chunks.js +88 -0
- package/dist/demux/mp4-demuxer.d.ts +84 -0
- package/dist/demux/mp4-demuxer.d.ts.map +1 -0
- package/dist/demux/mp4-demuxer.js +215 -0
- package/dist/demux/simple-demuxer.d.ts +49 -0
- package/dist/demux/simple-demuxer.d.ts.map +1 -0
- package/dist/demux/simple-demuxer.js +87 -0
- package/dist/in-memory-storage.d.ts +30 -0
- package/dist/in-memory-storage.d.ts.map +1 -0
- package/dist/in-memory-storage.js +54 -0
- package/dist/index.cjs +5 -298
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -13873
- package/dist/mux/simple-muxer.d.ts +47 -0
- package/dist/mux/simple-muxer.d.ts.map +1 -0
- package/dist/mux/simple-muxer.js +83 -0
- package/dist/polyfills/media-stream-track-processor.d.ts +5 -0
- package/dist/polyfills/media-stream-track-processor.d.ts.map +1 -0
- package/dist/polyfills/media-stream-track-processor.js +109 -0
- package/dist/streams/video-decode-stream.d.ts +11 -0
- package/dist/streams/video-decode-stream.d.ts.map +1 -0
- package/dist/streams/video-decode-stream.js +39 -0
- package/dist/streams/video-encode-stream.d.ts +15 -0
- package/dist/streams/video-encode-stream.d.ts.map +1 -0
- package/dist/streams/video-encode-stream.js +40 -0
- package/dist/streams/video-process-stream.d.ts +22 -0
- package/dist/streams/video-process-stream.d.ts.map +1 -0
- package/dist/streams/video-process-stream.js +21 -0
- package/dist/video/get-bitrate.d.ts +35 -0
- package/dist/video/get-bitrate.d.ts.map +1 -0
- package/dist/video/get-bitrate.js +12 -0
- package/dist/video/get-codec-string.d.ts +46 -0
- package/dist/video/get-codec-string.d.ts.map +1 -0
- package/dist/video/get-codec-string.js +195 -0
- package/dist/video/gpu-renderer.d.ts +108 -0
- package/dist/video/gpu-renderer.d.ts.map +1 -0
- package/dist/video/gpu-renderer.js +266 -0
- package/package.json +1 -1
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
function s(a, m, c, o) {
|
|
2
|
+
const S = [
|
|
3
|
+
{ maxMacroblocks: 99, maxBitrate: 64e3, level: 10 },
|
|
4
|
+
// Level 1
|
|
5
|
+
{ maxMacroblocks: 396, maxBitrate: 192e3, level: 11 },
|
|
6
|
+
// Level 1.1
|
|
7
|
+
{ maxMacroblocks: 396, maxBitrate: 384e3, level: 12 },
|
|
8
|
+
// Level 1.2
|
|
9
|
+
{ maxMacroblocks: 396, maxBitrate: 768e3, level: 13 },
|
|
10
|
+
// Level 1.3
|
|
11
|
+
{ maxMacroblocks: 396, maxBitrate: 2e6, level: 20 },
|
|
12
|
+
// Level 2
|
|
13
|
+
{ maxMacroblocks: 792, maxBitrate: 4e6, level: 21 },
|
|
14
|
+
// Level 2.1
|
|
15
|
+
{ maxMacroblocks: 1620, maxBitrate: 4e6, level: 22 },
|
|
16
|
+
// Level 2.2
|
|
17
|
+
{ maxMacroblocks: 1620, maxBitrate: 1e7, level: 30 },
|
|
18
|
+
// Level 3
|
|
19
|
+
{ maxMacroblocks: 3600, maxBitrate: 14e6, level: 31 },
|
|
20
|
+
// Level 3.1
|
|
21
|
+
{ maxMacroblocks: 5120, maxBitrate: 2e7, level: 32 },
|
|
22
|
+
// Level 3.2
|
|
23
|
+
{ maxMacroblocks: 8192, maxBitrate: 2e7, level: 40 },
|
|
24
|
+
// Level 4
|
|
25
|
+
{ maxMacroblocks: 8192, maxBitrate: 5e7, level: 41 },
|
|
26
|
+
// Level 4.1
|
|
27
|
+
{ maxMacroblocks: 8704, maxBitrate: 5e7, level: 42 },
|
|
28
|
+
// Level 4.2
|
|
29
|
+
{ maxMacroblocks: 22080, maxBitrate: 135e6, level: 50 },
|
|
30
|
+
// Level 5
|
|
31
|
+
{ maxMacroblocks: 36864, maxBitrate: 24e7, level: 51 },
|
|
32
|
+
// Level 5.1
|
|
33
|
+
{ maxMacroblocks: 36864, maxBitrate: 24e7, level: 52 },
|
|
34
|
+
// Level 5.2
|
|
35
|
+
{ maxMacroblocks: 139264, maxBitrate: 24e7, level: 60 },
|
|
36
|
+
// Level 6
|
|
37
|
+
{ maxMacroblocks: 139264, maxBitrate: 48e7, level: 61 },
|
|
38
|
+
// Level 6.1
|
|
39
|
+
{ maxMacroblocks: 139264, maxBitrate: 8e8, level: 62 }
|
|
40
|
+
// Level 6.2
|
|
41
|
+
], u = [
|
|
42
|
+
{ maxPictureSize: 36864, maxBitrate: 128e3, tier: "L", level: 30 },
|
|
43
|
+
// Level 1 (Low Tier)
|
|
44
|
+
{ maxPictureSize: 122880, maxBitrate: 15e5, tier: "L", level: 60 },
|
|
45
|
+
// Level 2 (Low Tier)
|
|
46
|
+
{ maxPictureSize: 245760, maxBitrate: 3e6, tier: "L", level: 63 },
|
|
47
|
+
// Level 2.1 (Low Tier)
|
|
48
|
+
{ maxPictureSize: 552960, maxBitrate: 6e6, tier: "L", level: 90 },
|
|
49
|
+
// Level 3 (Low Tier)
|
|
50
|
+
{ maxPictureSize: 983040, maxBitrate: 1e7, tier: "L", level: 93 },
|
|
51
|
+
// Level 3.1 (Low Tier)
|
|
52
|
+
{ maxPictureSize: 2228224, maxBitrate: 12e6, tier: "L", level: 120 },
|
|
53
|
+
// Level 4 (Low Tier)
|
|
54
|
+
{ maxPictureSize: 2228224, maxBitrate: 3e7, tier: "H", level: 120 },
|
|
55
|
+
// Level 4 (High Tier)
|
|
56
|
+
{ maxPictureSize: 2228224, maxBitrate: 2e7, tier: "L", level: 123 },
|
|
57
|
+
// Level 4.1 (Low Tier)
|
|
58
|
+
{ maxPictureSize: 2228224, maxBitrate: 5e7, tier: "H", level: 123 },
|
|
59
|
+
// Level 4.1 (High Tier)
|
|
60
|
+
{ maxPictureSize: 8912896, maxBitrate: 25e6, tier: "L", level: 150 },
|
|
61
|
+
// Level 5 (Low Tier)
|
|
62
|
+
{ maxPictureSize: 8912896, maxBitrate: 1e8, tier: "H", level: 150 },
|
|
63
|
+
// Level 5 (High Tier)
|
|
64
|
+
{ maxPictureSize: 8912896, maxBitrate: 4e7, tier: "L", level: 153 },
|
|
65
|
+
// Level 5.1 (Low Tier)
|
|
66
|
+
{ maxPictureSize: 8912896, maxBitrate: 16e7, tier: "H", level: 153 },
|
|
67
|
+
// Level 5.1 (High Tier)
|
|
68
|
+
{ maxPictureSize: 8912896, maxBitrate: 6e7, tier: "L", level: 156 },
|
|
69
|
+
// Level 5.2 (Low Tier)
|
|
70
|
+
{ maxPictureSize: 8912896, maxBitrate: 24e7, tier: "H", level: 156 },
|
|
71
|
+
// Level 5.2 (High Tier)
|
|
72
|
+
{ maxPictureSize: 35651584, maxBitrate: 6e7, tier: "L", level: 180 },
|
|
73
|
+
// Level 6 (Low Tier)
|
|
74
|
+
{ maxPictureSize: 35651584, maxBitrate: 24e7, tier: "H", level: 180 },
|
|
75
|
+
// Level 6 (High Tier)
|
|
76
|
+
{ maxPictureSize: 35651584, maxBitrate: 12e7, tier: "L", level: 183 },
|
|
77
|
+
// Level 6.1 (Low Tier)
|
|
78
|
+
{ maxPictureSize: 35651584, maxBitrate: 48e7, tier: "H", level: 183 },
|
|
79
|
+
// Level 6.1 (High Tier)
|
|
80
|
+
{ maxPictureSize: 35651584, maxBitrate: 24e7, tier: "L", level: 186 },
|
|
81
|
+
// Level 6.2 (Low Tier)
|
|
82
|
+
{ maxPictureSize: 35651584, maxBitrate: 8e8, tier: "H", level: 186 }
|
|
83
|
+
// Level 6.2 (High Tier)
|
|
84
|
+
], n = [
|
|
85
|
+
{ maxPictureSize: 36864, maxBitrate: 2e5, level: 10 },
|
|
86
|
+
// Level 1
|
|
87
|
+
{ maxPictureSize: 73728, maxBitrate: 8e5, level: 11 },
|
|
88
|
+
// Level 1.1
|
|
89
|
+
{ maxPictureSize: 122880, maxBitrate: 18e5, level: 20 },
|
|
90
|
+
// Level 2
|
|
91
|
+
{ maxPictureSize: 245760, maxBitrate: 36e5, level: 21 },
|
|
92
|
+
// Level 2.1
|
|
93
|
+
{ maxPictureSize: 552960, maxBitrate: 72e5, level: 30 },
|
|
94
|
+
// Level 3
|
|
95
|
+
{ maxPictureSize: 983040, maxBitrate: 12e6, level: 31 },
|
|
96
|
+
// Level 3.1
|
|
97
|
+
{ maxPictureSize: 2228224, maxBitrate: 18e6, level: 40 },
|
|
98
|
+
// Level 4
|
|
99
|
+
{ maxPictureSize: 2228224, maxBitrate: 3e7, level: 41 },
|
|
100
|
+
// Level 4.1
|
|
101
|
+
{ maxPictureSize: 8912896, maxBitrate: 6e7, level: 50 },
|
|
102
|
+
// Level 5
|
|
103
|
+
{ maxPictureSize: 8912896, maxBitrate: 12e7, level: 51 },
|
|
104
|
+
// Level 5.1
|
|
105
|
+
{ maxPictureSize: 8912896, maxBitrate: 18e7, level: 52 },
|
|
106
|
+
// Level 5.2
|
|
107
|
+
{ maxPictureSize: 35651584, maxBitrate: 18e7, level: 60 },
|
|
108
|
+
// Level 6
|
|
109
|
+
{ maxPictureSize: 35651584, maxBitrate: 24e7, level: 61 },
|
|
110
|
+
// Level 6.1
|
|
111
|
+
{ maxPictureSize: 35651584, maxBitrate: 48e7, level: 62 }
|
|
112
|
+
// Level 6.2
|
|
113
|
+
], P = [
|
|
114
|
+
{ maxPictureSize: 147456, maxBitrate: 15e5, tier: "M", level: 0 },
|
|
115
|
+
// Level 2.0 (Main Tier)
|
|
116
|
+
{ maxPictureSize: 278784, maxBitrate: 3e6, tier: "M", level: 1 },
|
|
117
|
+
// Level 2.1 (Main Tier)
|
|
118
|
+
{ maxPictureSize: 665856, maxBitrate: 6e6, tier: "M", level: 4 },
|
|
119
|
+
// Level 3.0 (Main Tier)
|
|
120
|
+
{ maxPictureSize: 1065024, maxBitrate: 1e7, tier: "M", level: 5 },
|
|
121
|
+
// Level 3.1 (Main Tier)
|
|
122
|
+
{ maxPictureSize: 2359296, maxBitrate: 12e6, tier: "M", level: 8 },
|
|
123
|
+
// Level 4.0 (Main Tier)
|
|
124
|
+
{ maxPictureSize: 2359296, maxBitrate: 3e7, tier: "H", level: 8 },
|
|
125
|
+
// Level 4.0 (High Tier)
|
|
126
|
+
{ maxPictureSize: 2359296, maxBitrate: 2e7, tier: "M", level: 9 },
|
|
127
|
+
// Level 4.1 (Main Tier)
|
|
128
|
+
{ maxPictureSize: 2359296, maxBitrate: 5e7, tier: "H", level: 9 },
|
|
129
|
+
// Level 4.1 (High Tier)
|
|
130
|
+
{ maxPictureSize: 8912896, maxBitrate: 3e7, tier: "M", level: 12 },
|
|
131
|
+
// Level 5.0 (Main Tier)
|
|
132
|
+
{ maxPictureSize: 8912896, maxBitrate: 1e8, tier: "H", level: 12 },
|
|
133
|
+
// Level 5.0 (High Tier)
|
|
134
|
+
{ maxPictureSize: 8912896, maxBitrate: 4e7, tier: "M", level: 13 },
|
|
135
|
+
// Level 5.1 (Main Tier)
|
|
136
|
+
{ maxPictureSize: 8912896, maxBitrate: 16e7, tier: "H", level: 13 },
|
|
137
|
+
// Level 5.1 (High Tier)
|
|
138
|
+
{ maxPictureSize: 8912896, maxBitrate: 6e7, tier: "M", level: 14 },
|
|
139
|
+
// Level 5.2 (Main Tier)
|
|
140
|
+
{ maxPictureSize: 8912896, maxBitrate: 24e7, tier: "H", level: 14 },
|
|
141
|
+
// Level 5.2 (High Tier)
|
|
142
|
+
{ maxPictureSize: 35651584, maxBitrate: 6e7, tier: "M", level: 15 },
|
|
143
|
+
// Level 5.3 (Main Tier)
|
|
144
|
+
{ maxPictureSize: 35651584, maxBitrate: 24e7, tier: "H", level: 15 },
|
|
145
|
+
// Level 5.3 (High Tier)
|
|
146
|
+
{ maxPictureSize: 35651584, maxBitrate: 6e7, tier: "M", level: 16 },
|
|
147
|
+
// Level 6.0 (Main Tier)
|
|
148
|
+
{ maxPictureSize: 35651584, maxBitrate: 24e7, tier: "H", level: 16 },
|
|
149
|
+
// Level 6.0 (High Tier)
|
|
150
|
+
{ maxPictureSize: 35651584, maxBitrate: 1e8, tier: "M", level: 17 },
|
|
151
|
+
// Level 6.1 (Main Tier)
|
|
152
|
+
{ maxPictureSize: 35651584, maxBitrate: 48e7, tier: "H", level: 17 },
|
|
153
|
+
// Level 6.1 (High Tier)
|
|
154
|
+
{ maxPictureSize: 35651584, maxBitrate: 16e7, tier: "M", level: 18 },
|
|
155
|
+
// Level 6.2 (Main Tier)
|
|
156
|
+
{ maxPictureSize: 35651584, maxBitrate: 8e8, tier: "H", level: 18 },
|
|
157
|
+
// Level 6.2 (High Tier)
|
|
158
|
+
{ maxPictureSize: 35651584, maxBitrate: 16e7, tier: "M", level: 19 },
|
|
159
|
+
// Level 6.3 (Main Tier)
|
|
160
|
+
{ maxPictureSize: 35651584, maxBitrate: 8e8, tier: "H", level: 19 }
|
|
161
|
+
// Level 6.3 (High Tier)
|
|
162
|
+
];
|
|
163
|
+
function v(e) {
|
|
164
|
+
return e ? e[e.length - 1] : void 0;
|
|
165
|
+
}
|
|
166
|
+
if (a === "avc") {
|
|
167
|
+
const r = Math.ceil(m / 16) * Math.ceil(c / 16), t = S.find(
|
|
168
|
+
(z) => r <= z.maxMacroblocks && o <= z.maxBitrate
|
|
169
|
+
) ?? v(S), l = t ? t.level : 0, i = "64".padStart(2, "0"), x = "00", B = l.toString(16).padStart(2, "0");
|
|
170
|
+
return `avc1.${i}${x}${B}`;
|
|
171
|
+
} else if (a === "hevc") {
|
|
172
|
+
const e = "", t = "6", l = m * c, i = u.find(
|
|
173
|
+
(B) => l <= B.maxPictureSize && o <= B.maxBitrate
|
|
174
|
+
) ?? v(u);
|
|
175
|
+
return `hev1.${e}1.${t}.${i.tier}${i.level}.B0`;
|
|
176
|
+
} else {
|
|
177
|
+
if (a === "vp8")
|
|
178
|
+
return "vp8";
|
|
179
|
+
if (a === "vp9") {
|
|
180
|
+
const e = "00", r = m * c, t = n.find(
|
|
181
|
+
(i) => r <= i.maxPictureSize && o <= i.maxBitrate
|
|
182
|
+
) ?? v(n);
|
|
183
|
+
return `vp09.${e}.${t.level.toString().padStart(2, "0")}.08`;
|
|
184
|
+
} else if (a === "av1") {
|
|
185
|
+
const r = m * c, t = P.find(
|
|
186
|
+
(x) => r <= x.maxPictureSize && o <= x.maxBitrate
|
|
187
|
+
) ?? v(P);
|
|
188
|
+
return `av01.0.${t.level.toString().padStart(2, "0")}${t.tier}.08`;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
throw new TypeError(`Unhandled codec '${a}'.`);
|
|
192
|
+
}
|
|
193
|
+
export {
|
|
194
|
+
s as getCodecString
|
|
195
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-copy VideoFrame rendering using WebGPU with ImageBitmap fallback.
|
|
3
|
+
*
|
|
4
|
+
* This class provides high-performance video rendering by using WebGPU's
|
|
5
|
+
* `importExternalTexture()` for zero-copy rendering when available, or falling
|
|
6
|
+
* back to ImageBitmapRenderer for compatibility.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - **Zero-copy rendering** via WebGPU (no pixel copying between GPU and CPU)
|
|
10
|
+
* - **Two filter modes**: Linear (hardware accelerated) and Bicubic (high quality)
|
|
11
|
+
* - **Automatic fallback** to ImageBitmapRenderer if WebGPU is unavailable
|
|
12
|
+
* - **Works with VideoFrame** objects from VideoDecoder
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const canvas = document.getElementById('canvas');
|
|
17
|
+
* const renderer = new GPUFrameRenderer(canvas, { filterMode: 'linear' });
|
|
18
|
+
* await renderer.init();
|
|
19
|
+
*
|
|
20
|
+
* // In VideoDecoder output callback:
|
|
21
|
+
* decoder.configure({
|
|
22
|
+
* output: (frame) => {
|
|
23
|
+
* renderer.drawImage(frame);
|
|
24
|
+
* frame.close();
|
|
25
|
+
* },
|
|
26
|
+
* error: (e) => console.error(e)
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* // Switch to high-quality bicubic filtering
|
|
33
|
+
* renderer.setFilterMode('bicubic');
|
|
34
|
+
*
|
|
35
|
+
* // Check current mode
|
|
36
|
+
* if (renderer.getMode() === 'webgpu') {
|
|
37
|
+
* console.log('Using WebGPU zero-copy rendering');
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export declare class GPUFrameRenderer {
|
|
42
|
+
canvas: HTMLCanvasElement | OffscreenCanvas;
|
|
43
|
+
mode: 'webgpu' | 'bitmap' | null;
|
|
44
|
+
filterMode: 'linear' | 'bicubic';
|
|
45
|
+
device: GPUDevice | null;
|
|
46
|
+
context: GPUCanvasContext | null;
|
|
47
|
+
linearPipeline: GPURenderPipeline | null;
|
|
48
|
+
bicubicPipeline: GPURenderPipeline | null;
|
|
49
|
+
sampler: GPUSampler | null;
|
|
50
|
+
uniformBuffer: GPUBuffer | null;
|
|
51
|
+
bitmapCtx: ImageBitmapRenderingContext | null;
|
|
52
|
+
/**
|
|
53
|
+
* Create a new GPUFrameRenderer.
|
|
54
|
+
*
|
|
55
|
+
* @param canvas - The canvas element to render to (HTMLCanvasElement or OffscreenCanvas)
|
|
56
|
+
* @param options - Configuration options
|
|
57
|
+
* @param options.filterMode - Scaling filter: 'linear' (default) or 'bicubic' (higher quality)
|
|
58
|
+
*/
|
|
59
|
+
constructor(canvas: HTMLCanvasElement | OffscreenCanvas, options?: {
|
|
60
|
+
filterMode?: 'linear' | 'bicubic';
|
|
61
|
+
});
|
|
62
|
+
/**
|
|
63
|
+
* Initialize the renderer. Must be called before drawImage().
|
|
64
|
+
*
|
|
65
|
+
* Attempts to initialize WebGPU first for zero-copy rendering. If WebGPU
|
|
66
|
+
* is unavailable or initialization fails, automatically falls back to
|
|
67
|
+
* ImageBitmapRenderer.
|
|
68
|
+
*
|
|
69
|
+
* @returns Promise that resolves when initialization is complete
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* const renderer = new GPUFrameRenderer(canvas);
|
|
74
|
+
* await renderer.init();
|
|
75
|
+
* // Ready to render
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
init(): Promise<void>;
|
|
79
|
+
initWebGPU(): Promise<boolean>;
|
|
80
|
+
initBitmapRenderer(): void;
|
|
81
|
+
/**
|
|
82
|
+
* Draw a VideoFrame to the canvas
|
|
83
|
+
* @param {VideoFrame} source - The VideoFrame to draw
|
|
84
|
+
*/
|
|
85
|
+
drawImage(source: VideoFrame): void;
|
|
86
|
+
drawImageWebGPU(videoFrame: VideoFrame): void;
|
|
87
|
+
drawImageBitmap(videoFrame: VideoFrame): Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* Get the current rendering mode
|
|
90
|
+
* @returns {'webgpu'|'bitmap'|null}
|
|
91
|
+
*/
|
|
92
|
+
getMode(): "webgpu" | "bitmap" | null;
|
|
93
|
+
/**
|
|
94
|
+
* Get the current filter mode
|
|
95
|
+
* @returns {'linear'|'bicubic'}
|
|
96
|
+
*/
|
|
97
|
+
getFilterMode(): "linear" | "bicubic";
|
|
98
|
+
/**
|
|
99
|
+
* Set the filter mode (only applies to WebGPU mode)
|
|
100
|
+
* @param {'linear'|'bicubic'} mode
|
|
101
|
+
*/
|
|
102
|
+
setFilterMode(mode: 'linear' | 'bicubic'): void;
|
|
103
|
+
/**
|
|
104
|
+
* Clean up resources
|
|
105
|
+
*/
|
|
106
|
+
destroy(): void;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=gpu-renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gpu-renderer.d.ts","sourceRoot":"","sources":["../../src/video/gpu-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,qBAAa,gBAAgB;IAEzB,MAAM,EAAE,iBAAiB,GAAG,eAAe,CAAC;IAC5C,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,IAAI,CAAC;IACjC,UAAU,EAAE,QAAQ,GAAG,SAAS,CAAC;IACjC,MAAM,EAAE,SAAS,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACjC,cAAc,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACzC,eAAe,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAC1C,OAAO,EAAE,UAAU,GAAG,IAAI,CAAC;IAC3B,aAAa,EAAE,SAAS,GAAG,IAAI,CAAC;IAChC,SAAS,EAAE,2BAA2B,GAAG,IAAI,CAAC;IAE9C;;;;;;OAMG;gBACS,MAAM,EAAE,iBAAiB,GAAG,eAAe,EAAE,OAAO,GAAE;QAAE,UAAU,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;KAAO;IAiB5G;;;;;;;;;;;;;;;OAeG;IACG,IAAI;IA8BJ,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC;IAmKpC,kBAAkB;IAIlB;;;OAGG;IACH,SAAS,CAAC,MAAM,EAAE,UAAU;IAU5B,eAAe,CAAC,UAAU,EAAE,UAAU;IA2DhC,eAAe,CAAC,UAAU,EAAE,UAAU;IAM5C;;;OAGG;IACH,OAAO;IAIP;;;OAGG;IACH,aAAa;IAIb;;;OAGG;IACH,aAAa,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS;IAOxC;;OAEG;IACH,OAAO;CAKR"}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
class l {
|
|
2
|
+
/**
|
|
3
|
+
* Create a new GPUFrameRenderer.
|
|
4
|
+
*
|
|
5
|
+
* @param canvas - The canvas element to render to (HTMLCanvasElement or OffscreenCanvas)
|
|
6
|
+
* @param options - Configuration options
|
|
7
|
+
* @param options.filterMode - Scaling filter: 'linear' (default) or 'bicubic' (higher quality)
|
|
8
|
+
*/
|
|
9
|
+
constructor(e, t = {}) {
|
|
10
|
+
this.canvas = e, this.mode = "webgpu", this.filterMode = t.filterMode || "linear", this.device = null, this.context = null, this.linearPipeline = null, this.bicubicPipeline = null, this.sampler = null, this.uniformBuffer = null, this.bitmapCtx = null;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Initialize the renderer. Must be called before drawImage().
|
|
14
|
+
*
|
|
15
|
+
* Attempts to initialize WebGPU first for zero-copy rendering. If WebGPU
|
|
16
|
+
* is unavailable or initialization fails, automatically falls back to
|
|
17
|
+
* ImageBitmapRenderer.
|
|
18
|
+
*
|
|
19
|
+
* @returns Promise that resolves when initialization is complete
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* const renderer = new GPUFrameRenderer(canvas);
|
|
24
|
+
* await renderer.init();
|
|
25
|
+
* // Ready to render
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
async init() {
|
|
29
|
+
const e = navigator.platform.toLowerCase().includes("linux"), t = navigator.userAgent.toLowerCase().includes("firefox");
|
|
30
|
+
if (e && t) {
|
|
31
|
+
console.log("GPUDrawImage: Linux + Firefox detected, using ImageBitmapRenderer"), this.initBitmapRenderer(), this.mode = "bitmap";
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (navigator.gpu)
|
|
35
|
+
try {
|
|
36
|
+
await this.initWebGPU(), this.mode = "webgpu", console.log("GPUDrawImage: Using WebGPU (zero-copy)");
|
|
37
|
+
return;
|
|
38
|
+
} catch (i) {
|
|
39
|
+
console.warn("GPUDrawImage: WebGPU initialization failed, falling back to ImageBitmap", i);
|
|
40
|
+
}
|
|
41
|
+
this.initBitmapRenderer(), this.mode = "bitmap", console.log("GPUDrawImage: Using ImageBitmapRenderer (fallback)");
|
|
42
|
+
}
|
|
43
|
+
async initWebGPU() {
|
|
44
|
+
const e = await navigator.gpu.requestAdapter();
|
|
45
|
+
if (!e || (this.device = await e.requestDevice(), !this.device) || (this.context = this.canvas.getContext("webgpu"), !this.context)) return !1;
|
|
46
|
+
const t = navigator.gpu.getPreferredCanvasFormat();
|
|
47
|
+
this.context.configure({
|
|
48
|
+
device: this.device,
|
|
49
|
+
format: t,
|
|
50
|
+
alphaMode: "opaque"
|
|
51
|
+
}), this.sampler = this.device.createSampler({
|
|
52
|
+
magFilter: "linear",
|
|
53
|
+
minFilter: "linear"
|
|
54
|
+
}), this.uniformBuffer = this.device.createBuffer({
|
|
55
|
+
size: 8,
|
|
56
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
57
|
+
});
|
|
58
|
+
const i = `
|
|
59
|
+
struct VertexOutput {
|
|
60
|
+
@builtin(position) position: vec4f,
|
|
61
|
+
@location(0) texCoord: vec2f,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@vertex
|
|
65
|
+
fn vertexMain(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
|
66
|
+
var pos = array<vec2f, 6>(
|
|
67
|
+
vec2f(-1.0, -1.0),
|
|
68
|
+
vec2f(1.0, -1.0),
|
|
69
|
+
vec2f(-1.0, 1.0),
|
|
70
|
+
vec2f(-1.0, 1.0),
|
|
71
|
+
vec2f(1.0, -1.0),
|
|
72
|
+
vec2f(1.0, 1.0)
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
var texCoord = array<vec2f, 6>(
|
|
76
|
+
vec2f(0.0, 1.0),
|
|
77
|
+
vec2f(1.0, 1.0),
|
|
78
|
+
vec2f(0.0, 0.0),
|
|
79
|
+
vec2f(0.0, 0.0),
|
|
80
|
+
vec2f(1.0, 1.0),
|
|
81
|
+
vec2f(1.0, 0.0)
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
var output: VertexOutput;
|
|
85
|
+
output.position = vec4f(pos[vertexIndex], 0.0, 1.0);
|
|
86
|
+
output.texCoord = texCoord[vertexIndex];
|
|
87
|
+
return output;
|
|
88
|
+
}
|
|
89
|
+
`, r = this.device.createShaderModule({
|
|
90
|
+
code: i + `
|
|
91
|
+
@group(0) @binding(0) var videoTexture: texture_external;
|
|
92
|
+
@group(0) @binding(1) var texSampler: sampler;
|
|
93
|
+
|
|
94
|
+
@fragment
|
|
95
|
+
fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
|
|
96
|
+
return textureSampleBaseClampToEdge(videoTexture, texSampler, input.texCoord);
|
|
97
|
+
}
|
|
98
|
+
`
|
|
99
|
+
}), a = this.device.createShaderModule({
|
|
100
|
+
code: i + `
|
|
101
|
+
@group(0) @binding(0) var videoTexture: texture_external;
|
|
102
|
+
@group(0) @binding(1) var<uniform> texSize: vec2f;
|
|
103
|
+
|
|
104
|
+
// Bicubic weight function (Catmull-Rom)
|
|
105
|
+
fn cubic(x: f32) -> f32 {
|
|
106
|
+
let x_abs = abs(x);
|
|
107
|
+
if (x_abs <= 1.0) {
|
|
108
|
+
return 1.5 * x_abs * x_abs * x_abs - 2.5 * x_abs * x_abs + 1.0;
|
|
109
|
+
} else if (x_abs < 2.0) {
|
|
110
|
+
return -0.5 * x_abs * x_abs * x_abs + 2.5 * x_abs * x_abs - 4.0 * x_abs + 2.0;
|
|
111
|
+
}
|
|
112
|
+
return 0.0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@fragment
|
|
116
|
+
fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
|
|
117
|
+
let texCoord = input.texCoord;
|
|
118
|
+
|
|
119
|
+
let coord = texCoord * texSize;
|
|
120
|
+
let coordFloor = floor(coord);
|
|
121
|
+
let f = coord - coordFloor;
|
|
122
|
+
|
|
123
|
+
var result = vec4f(0.0, 0.0, 0.0, 0.0);
|
|
124
|
+
var weightSum = 0.0;
|
|
125
|
+
|
|
126
|
+
// Read exact pixel values from 4x4 neighborhood using textureLoad
|
|
127
|
+
for (var y = -1; y <= 2; y++) {
|
|
128
|
+
for (var x = -1; x <= 2; x++) {
|
|
129
|
+
let pixelCoord = vec2i(i32(coordFloor.x) + x, i32(coordFloor.y) + y);
|
|
130
|
+
|
|
131
|
+
// Clamp to valid texture coordinates
|
|
132
|
+
let clampedCoord = clamp(pixelCoord, vec2i(0, 0), vec2i(i32(texSize.x) - 1, i32(texSize.y) - 1));
|
|
133
|
+
|
|
134
|
+
let weight = cubic(f.x - f32(x)) * cubic(f.y - f32(y));
|
|
135
|
+
result += textureLoad(videoTexture, clampedCoord) * weight;
|
|
136
|
+
weightSum += weight;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return result / weightSum;
|
|
141
|
+
}
|
|
142
|
+
`
|
|
143
|
+
});
|
|
144
|
+
return this.linearPipeline = this.device.createRenderPipeline({
|
|
145
|
+
layout: "auto",
|
|
146
|
+
vertex: {
|
|
147
|
+
module: r,
|
|
148
|
+
entryPoint: "vertexMain"
|
|
149
|
+
},
|
|
150
|
+
fragment: {
|
|
151
|
+
module: r,
|
|
152
|
+
entryPoint: "fragmentMain",
|
|
153
|
+
targets: [{
|
|
154
|
+
format: t
|
|
155
|
+
}]
|
|
156
|
+
},
|
|
157
|
+
primitive: {
|
|
158
|
+
topology: "triangle-list"
|
|
159
|
+
}
|
|
160
|
+
}), this.bicubicPipeline = this.device.createRenderPipeline({
|
|
161
|
+
layout: "auto",
|
|
162
|
+
vertex: {
|
|
163
|
+
module: a,
|
|
164
|
+
entryPoint: "vertexMain"
|
|
165
|
+
},
|
|
166
|
+
fragment: {
|
|
167
|
+
module: a,
|
|
168
|
+
entryPoint: "fragmentMain",
|
|
169
|
+
targets: [{
|
|
170
|
+
format: t
|
|
171
|
+
}]
|
|
172
|
+
},
|
|
173
|
+
primitive: {
|
|
174
|
+
topology: "triangle-list"
|
|
175
|
+
}
|
|
176
|
+
}), !0;
|
|
177
|
+
}
|
|
178
|
+
initBitmapRenderer() {
|
|
179
|
+
this.bitmapCtx = this.canvas.getContext("bitmaprenderer");
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Draw a VideoFrame to the canvas
|
|
183
|
+
* @param {VideoFrame} source - The VideoFrame to draw
|
|
184
|
+
*/
|
|
185
|
+
drawImage(e) {
|
|
186
|
+
if (this.mode === "webgpu")
|
|
187
|
+
this.drawImageWebGPU(e);
|
|
188
|
+
else if (this.mode === "bitmap")
|
|
189
|
+
this.drawImageBitmap(e);
|
|
190
|
+
else
|
|
191
|
+
throw new Error("GPUDrawImage not initialized. Call init() first.");
|
|
192
|
+
}
|
|
193
|
+
drawImageWebGPU(e) {
|
|
194
|
+
const t = this.filterMode === "bicubic" ? this.bicubicPipeline : this.linearPipeline, i = this.filterMode === "bicubic", r = [
|
|
195
|
+
{
|
|
196
|
+
binding: 0,
|
|
197
|
+
resource: this.device.importExternalTexture({
|
|
198
|
+
source: e
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
];
|
|
202
|
+
if (i) {
|
|
203
|
+
const u = new Float32Array([e.displayWidth, e.displayHeight]);
|
|
204
|
+
this.device.queue.writeBuffer(this.uniformBuffer, 0, u), r.push({
|
|
205
|
+
binding: 1,
|
|
206
|
+
resource: {
|
|
207
|
+
//@ts-expect-error
|
|
208
|
+
buffer: this.uniformBuffer
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
} else
|
|
212
|
+
r.push({
|
|
213
|
+
binding: 1,
|
|
214
|
+
//@ts-expect-error
|
|
215
|
+
resource: this.sampler
|
|
216
|
+
});
|
|
217
|
+
const a = this.device.createBindGroup({
|
|
218
|
+
layout: t.getBindGroupLayout(0),
|
|
219
|
+
entries: r
|
|
220
|
+
}), n = this.device.createCommandEncoder(), s = this.context.getCurrentTexture().createView(), o = n.beginRenderPass({
|
|
221
|
+
colorAttachments: [{
|
|
222
|
+
view: s,
|
|
223
|
+
clearValue: { r: 0, g: 0, b: 0, a: 1 },
|
|
224
|
+
loadOp: "clear",
|
|
225
|
+
storeOp: "store"
|
|
226
|
+
}]
|
|
227
|
+
});
|
|
228
|
+
o.setPipeline(t), o.setBindGroup(0, a), o.draw(6), o.end(), this.device.queue.submit([n.finish()]);
|
|
229
|
+
}
|
|
230
|
+
async drawImageBitmap(e) {
|
|
231
|
+
const t = await createImageBitmap(e);
|
|
232
|
+
this.bitmapCtx.transferFromImageBitmap(t);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Get the current rendering mode
|
|
236
|
+
* @returns {'webgpu'|'bitmap'|null}
|
|
237
|
+
*/
|
|
238
|
+
getMode() {
|
|
239
|
+
return this.mode;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Get the current filter mode
|
|
243
|
+
* @returns {'linear'|'bicubic'}
|
|
244
|
+
*/
|
|
245
|
+
getFilterMode() {
|
|
246
|
+
return this.filterMode;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Set the filter mode (only applies to WebGPU mode)
|
|
250
|
+
* @param {'linear'|'bicubic'} mode
|
|
251
|
+
*/
|
|
252
|
+
setFilterMode(e) {
|
|
253
|
+
if (e !== "linear" && e !== "bicubic")
|
|
254
|
+
throw new Error('Filter mode must be "linear" or "bicubic"');
|
|
255
|
+
this.filterMode = e;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Clean up resources
|
|
259
|
+
*/
|
|
260
|
+
destroy() {
|
|
261
|
+
this.device && this.device.destroy();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
export {
|
|
265
|
+
l as GPUFrameRenderer
|
|
266
|
+
};
|