webcodecs-node 0.5.0 → 0.5.1
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/package.json +1 -1
- package/dist/AudioData.d.ts +0 -6
- package/dist/AudioData.d.ts.map +0 -1
- package/dist/AudioData.js +0 -6
- package/dist/AudioData.js.map +0 -1
- package/dist/AudioDecoder.d.ts +0 -6
- package/dist/AudioDecoder.d.ts.map +0 -1
- package/dist/AudioDecoder.js +0 -6
- package/dist/AudioDecoder.js.map +0 -1
- package/dist/AudioEncoder.d.ts +0 -6
- package/dist/AudioEncoder.d.ts.map +0 -1
- package/dist/AudioEncoder.js +0 -6
- package/dist/AudioEncoder.js.map +0 -1
- package/dist/EncodedAudioChunk.d.ts +0 -6
- package/dist/EncodedAudioChunk.d.ts.map +0 -1
- package/dist/EncodedAudioChunk.js +0 -6
- package/dist/EncodedAudioChunk.js.map +0 -1
- package/dist/EncodedVideoChunk.d.ts +0 -6
- package/dist/EncodedVideoChunk.d.ts.map +0 -1
- package/dist/EncodedVideoChunk.js +0 -6
- package/dist/EncodedVideoChunk.js.map +0 -1
- package/dist/FFmpegProcess.d.ts +0 -6
- package/dist/FFmpegProcess.d.ts.map +0 -1
- package/dist/FFmpegProcess.js +0 -6
- package/dist/FFmpegProcess.js.map +0 -1
- package/dist/HardwareAcceleration.d.ts +0 -6
- package/dist/HardwareAcceleration.d.ts.map +0 -1
- package/dist/HardwareAcceleration.js +0 -6
- package/dist/HardwareAcceleration.js.map +0 -1
- package/dist/ImageDecoder.d.ts +0 -6
- package/dist/ImageDecoder.d.ts.map +0 -1
- package/dist/ImageDecoder.js +0 -6
- package/dist/ImageDecoder.js.map +0 -1
- package/dist/Logger.d.ts +0 -6
- package/dist/Logger.d.ts.map +0 -1
- package/dist/Logger.js +0 -6
- package/dist/Logger.js.map +0 -1
- package/dist/MediaCapabilities.d.ts +0 -9
- package/dist/MediaCapabilities.d.ts.map +0 -1
- package/dist/MediaCapabilities.js +0 -8
- package/dist/MediaCapabilities.js.map +0 -1
- package/dist/VideoDecoder.d.ts +0 -6
- package/dist/VideoDecoder.d.ts.map +0 -1
- package/dist/VideoDecoder.js +0 -6
- package/dist/VideoDecoder.js.map +0 -1
- package/dist/VideoEncoder.d.ts +0 -6
- package/dist/VideoEncoder.d.ts.map +0 -1
- package/dist/VideoEncoder.js +0 -6
- package/dist/VideoEncoder.js.map +0 -1
- package/dist/VideoFrame.d.ts +0 -10
- package/dist/VideoFrame.d.ts.map +0 -1
- package/dist/VideoFrame.js +0 -9
- package/dist/VideoFrame.js.map +0 -1
- package/dist/__tests__/AudioData.test.d.ts +0 -5
- package/dist/__tests__/AudioData.test.d.ts.map +0 -1
- package/dist/__tests__/AudioData.test.js +0 -180
- package/dist/__tests__/AudioData.test.js.map +0 -1
- package/dist/__tests__/AudioDecoder.test.d.ts +0 -5
- package/dist/__tests__/AudioDecoder.test.d.ts.map +0 -1
- package/dist/__tests__/AudioDecoder.test.js +0 -733
- package/dist/__tests__/AudioDecoder.test.js.map +0 -1
- package/dist/__tests__/AudioEncoder.test.d.ts +0 -5
- package/dist/__tests__/AudioEncoder.test.d.ts.map +0 -1
- package/dist/__tests__/AudioEncoder.test.js +0 -247
- package/dist/__tests__/AudioEncoder.test.js.map +0 -1
- package/dist/__tests__/EncodedChunks.test.d.ts +0 -5
- package/dist/__tests__/EncodedChunks.test.d.ts.map +0 -1
- package/dist/__tests__/EncodedChunks.test.js +0 -100
- package/dist/__tests__/EncodedChunks.test.js.map +0 -1
- package/dist/__tests__/HardwareAcceleration.test.d.ts +0 -5
- package/dist/__tests__/HardwareAcceleration.test.d.ts.map +0 -1
- package/dist/__tests__/HardwareAcceleration.test.js +0 -85
- package/dist/__tests__/HardwareAcceleration.test.js.map +0 -1
- package/dist/__tests__/ImageDecoder.test.d.ts +0 -5
- package/dist/__tests__/ImageDecoder.test.d.ts.map +0 -1
- package/dist/__tests__/ImageDecoder.test.js +0 -558
- package/dist/__tests__/ImageDecoder.test.js.map +0 -1
- package/dist/__tests__/ImageDecoder.wpt.test.d.ts +0 -8
- package/dist/__tests__/ImageDecoder.wpt.test.d.ts.map +0 -1
- package/dist/__tests__/ImageDecoder.wpt.test.js +0 -135
- package/dist/__tests__/ImageDecoder.wpt.test.js.map +0 -1
- package/dist/__tests__/MediaCapabilities.test.d.ts +0 -5
- package/dist/__tests__/MediaCapabilities.test.d.ts.map +0 -1
- package/dist/__tests__/MediaCapabilities.test.js +0 -346
- package/dist/__tests__/MediaCapabilities.test.js.map +0 -1
- package/dist/__tests__/NodeAvDecoder.test.d.ts +0 -2
- package/dist/__tests__/NodeAvDecoder.test.d.ts.map +0 -1
- package/dist/__tests__/NodeAvDecoder.test.js +0 -206
- package/dist/__tests__/NodeAvDecoder.test.js.map +0 -1
- package/dist/__tests__/NodeAvEncoder.test.d.ts +0 -2
- package/dist/__tests__/NodeAvEncoder.test.d.ts.map +0 -1
- package/dist/__tests__/NodeAvEncoder.test.js +0 -176
- package/dist/__tests__/NodeAvEncoder.test.js.map +0 -1
- package/dist/__tests__/VideoDecoder.test.d.ts +0 -5
- package/dist/__tests__/VideoDecoder.test.d.ts.map +0 -1
- package/dist/__tests__/VideoDecoder.test.js +0 -675
- package/dist/__tests__/VideoDecoder.test.js.map +0 -1
- package/dist/__tests__/VideoEncoder.test.d.ts +0 -5
- package/dist/__tests__/VideoEncoder.test.d.ts.map +0 -1
- package/dist/__tests__/VideoEncoder.test.js +0 -468
- package/dist/__tests__/VideoEncoder.test.js.map +0 -1
- package/dist/__tests__/VideoFrame.test.d.ts +0 -5
- package/dist/__tests__/VideoFrame.test.d.ts.map +0 -1
- package/dist/__tests__/VideoFrame.test.js +0 -246
- package/dist/__tests__/VideoFrame.test.js.map +0 -1
- package/dist/__tests__/aac-utils.test.d.ts +0 -2
- package/dist/__tests__/aac-utils.test.d.ts.map +0 -1
- package/dist/__tests__/aac-utils.test.js +0 -37
- package/dist/__tests__/aac-utils.test.js.map +0 -1
- package/dist/__tests__/avc-utils.test.d.ts +0 -2
- package/dist/__tests__/avc-utils.test.d.ts.map +0 -1
- package/dist/__tests__/avc-utils.test.js +0 -63
- package/dist/__tests__/avc-utils.test.js.map +0 -1
- package/dist/__tests__/hevc-utils.test.d.ts +0 -2
- package/dist/__tests__/hevc-utils.test.d.ts.map +0 -1
- package/dist/__tests__/hevc-utils.test.js +0 -78
- package/dist/__tests__/hevc-utils.test.js.map +0 -1
- package/dist/demos/demo-conversion.d.ts +0 -13
- package/dist/demos/demo-conversion.d.ts.map +0 -1
- package/dist/demos/demo-conversion.js +0 -301
- package/dist/demos/demo-conversion.js.map +0 -1
- package/dist/demos/demo-hwaccel-conversion.d.ts +0 -8
- package/dist/demos/demo-hwaccel-conversion.d.ts.map +0 -1
- package/dist/demos/demo-hwaccel-conversion.js +0 -161
- package/dist/demos/demo-hwaccel-conversion.js.map +0 -1
- package/dist/demos/demo-mediabunny.d.ts +0 -7
- package/dist/demos/demo-mediabunny.d.ts.map +0 -1
- package/dist/demos/demo-mediabunny.js +0 -78
- package/dist/demos/demo-mediabunny.js.map +0 -1
- package/dist/ffmpeg/FFmpegProcess.d.ts +0 -111
- package/dist/ffmpeg/FFmpegProcess.d.ts.map +0 -1
- package/dist/ffmpeg/FFmpegProcess.js +0 -543
- package/dist/ffmpeg/FFmpegProcess.js.map +0 -1
- package/dist/ffmpeg/audio-codecs.d.ts +0 -59
- package/dist/ffmpeg/audio-codecs.d.ts.map +0 -1
- package/dist/ffmpeg/audio-codecs.js +0 -99
- package/dist/ffmpeg/audio-codecs.js.map +0 -1
- package/dist/ffmpeg/formats.d.ts +0 -42
- package/dist/ffmpeg/formats.d.ts.map +0 -1
- package/dist/ffmpeg/formats.js +0 -147
- package/dist/ffmpeg/formats.js.map +0 -1
- package/dist/ffmpeg/index.d.ts +0 -10
- package/dist/ffmpeg/index.d.ts.map +0 -1
- package/dist/ffmpeg/index.js +0 -13
- package/dist/ffmpeg/index.js.map +0 -1
- package/dist/ffmpeg/parsers/annexb.d.ts +0 -88
- package/dist/ffmpeg/parsers/annexb.d.ts.map +0 -1
- package/dist/ffmpeg/parsers/annexb.js +0 -201
- package/dist/ffmpeg/parsers/annexb.js.map +0 -1
- package/dist/ffmpeg/parsers/index.d.ts +0 -6
- package/dist/ffmpeg/parsers/index.d.ts.map +0 -1
- package/dist/ffmpeg/parsers/index.js +0 -8
- package/dist/ffmpeg/parsers/index.js.map +0 -1
- package/dist/ffmpeg/parsers/ivf.d.ts +0 -54
- package/dist/ffmpeg/parsers/ivf.d.ts.map +0 -1
- package/dist/ffmpeg/parsers/ivf.js +0 -109
- package/dist/ffmpeg/parsers/ivf.js.map +0 -1
- package/dist/ffmpeg/types.d.ts +0 -85
- package/dist/ffmpeg/types.d.ts.map +0 -1
- package/dist/ffmpeg/types.js +0 -8
- package/dist/ffmpeg/types.js.map +0 -1
- package/dist/mediabunny/FFmpegAudioDecoder.d.ts +0 -24
- package/dist/mediabunny/FFmpegAudioDecoder.d.ts.map +0 -1
- package/dist/mediabunny/FFmpegAudioDecoder.js +0 -126
- package/dist/mediabunny/FFmpegAudioDecoder.js.map +0 -1
- package/dist/mediabunny/FFmpegAudioEncoder.d.ts +0 -44
- package/dist/mediabunny/FFmpegAudioEncoder.d.ts.map +0 -1
- package/dist/mediabunny/FFmpegAudioEncoder.js +0 -221
- package/dist/mediabunny/FFmpegAudioEncoder.js.map +0 -1
- package/dist/mediabunny/FFmpegVideoDecoder.d.ts +0 -25
- package/dist/mediabunny/FFmpegVideoDecoder.d.ts.map +0 -1
- package/dist/mediabunny/FFmpegVideoDecoder.js +0 -122
- package/dist/mediabunny/FFmpegVideoDecoder.js.map +0 -1
- package/dist/mediabunny/FFmpegVideoEncoder.d.ts +0 -49
- package/dist/mediabunny/FFmpegVideoEncoder.d.ts.map +0 -1
- package/dist/mediabunny/FFmpegVideoEncoder.js +0 -264
- package/dist/mediabunny/FFmpegVideoEncoder.js.map +0 -1
- package/dist/mediabunny/index.d.ts +0 -21
- package/dist/mediabunny/index.d.ts.map +0 -1
- package/dist/mediabunny/index.js +0 -33
- package/dist/mediabunny/index.js.map +0 -1
- package/dist/types.d.ts +0 -6
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -6
- package/dist/types.js.map +0 -1
|
@@ -1,675 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for VideoDecoder class
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it, expect } from 'vitest';
|
|
5
|
-
import { VideoDecoder } from '../decoders/VideoDecoder.js';
|
|
6
|
-
import { VideoEncoder } from '../encoders/VideoEncoder.js';
|
|
7
|
-
import { VideoFrame } from '../core/VideoFrame.js';
|
|
8
|
-
import { EncodedVideoChunk } from '../core/EncodedVideoChunk.js';
|
|
9
|
-
describe('VideoDecoder', () => {
|
|
10
|
-
describe('isConfigSupported', () => {
|
|
11
|
-
it('should support H.264', async () => {
|
|
12
|
-
const support = await VideoDecoder.isConfigSupported({
|
|
13
|
-
codec: 'avc1.42001E',
|
|
14
|
-
codedWidth: 640,
|
|
15
|
-
codedHeight: 480,
|
|
16
|
-
});
|
|
17
|
-
expect(support.supported).toBe(true);
|
|
18
|
-
});
|
|
19
|
-
it('should support VP8', async () => {
|
|
20
|
-
const support = await VideoDecoder.isConfigSupported({
|
|
21
|
-
codec: 'vp8',
|
|
22
|
-
codedWidth: 640,
|
|
23
|
-
codedHeight: 480,
|
|
24
|
-
});
|
|
25
|
-
expect(support.supported).toBe(true);
|
|
26
|
-
});
|
|
27
|
-
it('should support VP9', async () => {
|
|
28
|
-
const support = await VideoDecoder.isConfigSupported({
|
|
29
|
-
codec: 'vp09.00.10.08',
|
|
30
|
-
codedWidth: 1920,
|
|
31
|
-
codedHeight: 1080,
|
|
32
|
-
});
|
|
33
|
-
expect(support.supported).toBe(true);
|
|
34
|
-
});
|
|
35
|
-
it('should support HEVC', async () => {
|
|
36
|
-
const support = await VideoDecoder.isConfigSupported({
|
|
37
|
-
codec: 'hev1.1.6.L93.B0',
|
|
38
|
-
codedWidth: 1920,
|
|
39
|
-
codedHeight: 1080,
|
|
40
|
-
});
|
|
41
|
-
expect(support.supported).toBe(true);
|
|
42
|
-
});
|
|
43
|
-
it('should support AV1', async () => {
|
|
44
|
-
const support = await VideoDecoder.isConfigSupported({
|
|
45
|
-
codec: 'av01.0.01M.08',
|
|
46
|
-
codedWidth: 1920,
|
|
47
|
-
codedHeight: 1080,
|
|
48
|
-
});
|
|
49
|
-
expect(support.supported).toBe(true);
|
|
50
|
-
});
|
|
51
|
-
it('should not support unknown codec', async () => {
|
|
52
|
-
const support = await VideoDecoder.isConfigSupported({
|
|
53
|
-
codec: 'unknown-codec',
|
|
54
|
-
codedWidth: 640,
|
|
55
|
-
codedHeight: 480,
|
|
56
|
-
});
|
|
57
|
-
expect(support.supported).toBe(false);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
describe('constructor', () => {
|
|
61
|
-
it('should create decoder with callbacks', () => {
|
|
62
|
-
const output = jest.fn();
|
|
63
|
-
const error = jest.fn();
|
|
64
|
-
const decoder = new VideoDecoder({ output, error });
|
|
65
|
-
expect(decoder.state).toBe('unconfigured');
|
|
66
|
-
decoder.close();
|
|
67
|
-
});
|
|
68
|
-
it('should throw without output callback', () => {
|
|
69
|
-
expect(() => new VideoDecoder({ output: null, error: () => { } })).toThrow();
|
|
70
|
-
});
|
|
71
|
-
it('should throw without error callback', () => {
|
|
72
|
-
expect(() => new VideoDecoder({ output: () => { }, error: null })).toThrow();
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
describe('configure', () => {
|
|
76
|
-
it('should configure decoder', () => {
|
|
77
|
-
const decoder = new VideoDecoder({
|
|
78
|
-
output: () => { },
|
|
79
|
-
error: () => { },
|
|
80
|
-
});
|
|
81
|
-
decoder.configure({
|
|
82
|
-
codec: 'avc1.42001E',
|
|
83
|
-
codedWidth: 320,
|
|
84
|
-
codedHeight: 240,
|
|
85
|
-
});
|
|
86
|
-
expect(decoder.state).toBe('configured');
|
|
87
|
-
decoder.close();
|
|
88
|
-
});
|
|
89
|
-
it('should throw without codec', () => {
|
|
90
|
-
const decoder = new VideoDecoder({
|
|
91
|
-
output: () => { },
|
|
92
|
-
error: () => { },
|
|
93
|
-
});
|
|
94
|
-
expect(() => decoder.configure({
|
|
95
|
-
codec: '',
|
|
96
|
-
codedWidth: 320,
|
|
97
|
-
codedHeight: 240,
|
|
98
|
-
})).toThrow();
|
|
99
|
-
decoder.close();
|
|
100
|
-
});
|
|
101
|
-
it('should throw on closed decoder', () => {
|
|
102
|
-
const decoder = new VideoDecoder({
|
|
103
|
-
output: () => { },
|
|
104
|
-
error: () => { },
|
|
105
|
-
});
|
|
106
|
-
decoder.close();
|
|
107
|
-
expect(() => decoder.configure({
|
|
108
|
-
codec: 'avc1.42001E',
|
|
109
|
-
codedWidth: 320,
|
|
110
|
-
codedHeight: 240,
|
|
111
|
-
})).toThrow();
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
describe('decode', () => {
|
|
115
|
-
it('should throw when not configured', () => {
|
|
116
|
-
const decoder = new VideoDecoder({
|
|
117
|
-
output: () => { },
|
|
118
|
-
error: () => { },
|
|
119
|
-
});
|
|
120
|
-
const chunk = new EncodedVideoChunk({
|
|
121
|
-
type: 'key',
|
|
122
|
-
timestamp: 0,
|
|
123
|
-
data: new Uint8Array(100),
|
|
124
|
-
});
|
|
125
|
-
expect(() => decoder.decode(chunk)).toThrow();
|
|
126
|
-
decoder.close();
|
|
127
|
-
});
|
|
128
|
-
it('should throw with invalid chunk', () => {
|
|
129
|
-
const decoder = new VideoDecoder({
|
|
130
|
-
output: () => { },
|
|
131
|
-
error: () => { },
|
|
132
|
-
});
|
|
133
|
-
decoder.configure({
|
|
134
|
-
codec: 'avc1.42001E',
|
|
135
|
-
codedWidth: 64,
|
|
136
|
-
codedHeight: 64,
|
|
137
|
-
});
|
|
138
|
-
expect(() => decoder.decode('invalid')).toThrow();
|
|
139
|
-
decoder.close();
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
describe('reset', () => {
|
|
143
|
-
it('should reset decoder to unconfigured state', () => {
|
|
144
|
-
const decoder = new VideoDecoder({
|
|
145
|
-
output: () => { },
|
|
146
|
-
error: () => { },
|
|
147
|
-
});
|
|
148
|
-
decoder.configure({
|
|
149
|
-
codec: 'vp8',
|
|
150
|
-
codedWidth: 320,
|
|
151
|
-
codedHeight: 240,
|
|
152
|
-
});
|
|
153
|
-
expect(decoder.state).toBe('configured');
|
|
154
|
-
decoder.reset();
|
|
155
|
-
expect(decoder.state).toBe('unconfigured');
|
|
156
|
-
decoder.close();
|
|
157
|
-
});
|
|
158
|
-
it('should throw on closed decoder', () => {
|
|
159
|
-
const decoder = new VideoDecoder({
|
|
160
|
-
output: () => { },
|
|
161
|
-
error: () => { },
|
|
162
|
-
});
|
|
163
|
-
decoder.close();
|
|
164
|
-
expect(() => decoder.reset()).toThrow();
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
describe('close', () => {
|
|
168
|
-
it('should close decoder', () => {
|
|
169
|
-
const decoder = new VideoDecoder({
|
|
170
|
-
output: () => { },
|
|
171
|
-
error: () => { },
|
|
172
|
-
});
|
|
173
|
-
decoder.close();
|
|
174
|
-
expect(decoder.state).toBe('closed');
|
|
175
|
-
});
|
|
176
|
-
it('should be idempotent', () => {
|
|
177
|
-
const decoder = new VideoDecoder({
|
|
178
|
-
output: () => { },
|
|
179
|
-
error: () => { },
|
|
180
|
-
});
|
|
181
|
-
decoder.close();
|
|
182
|
-
decoder.close(); // Should not throw
|
|
183
|
-
expect(decoder.state).toBe('closed');
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
describe('flush', () => {
|
|
187
|
-
it('should throw when not configured', async () => {
|
|
188
|
-
const decoder = new VideoDecoder({
|
|
189
|
-
output: () => { },
|
|
190
|
-
error: () => { },
|
|
191
|
-
});
|
|
192
|
-
await expect(decoder.flush()).rejects.toThrow();
|
|
193
|
-
decoder.close();
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
describe('decodeQueueSize', () => {
|
|
197
|
-
it('should start at 0', () => {
|
|
198
|
-
const decoder = new VideoDecoder({
|
|
199
|
-
output: () => { },
|
|
200
|
-
error: () => { },
|
|
201
|
-
});
|
|
202
|
-
expect(decoder.decodeQueueSize).toBe(0);
|
|
203
|
-
decoder.close();
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
});
|
|
207
|
-
describe('VideoDecoder encode-decode roundtrip', () => {
|
|
208
|
-
it('should decode H.264 encoded frames', async () => {
|
|
209
|
-
const width = 64;
|
|
210
|
-
const height = 64;
|
|
211
|
-
const frameCount = 3;
|
|
212
|
-
// Step 1: Encode frames
|
|
213
|
-
const encodedChunks = [];
|
|
214
|
-
const encoder = new VideoEncoder({
|
|
215
|
-
output: (chunk) => encodedChunks.push(chunk),
|
|
216
|
-
error: (err) => { throw err; },
|
|
217
|
-
});
|
|
218
|
-
encoder.configure({
|
|
219
|
-
codec: 'avc1.42001E',
|
|
220
|
-
width,
|
|
221
|
-
height,
|
|
222
|
-
bitrate: 500_000,
|
|
223
|
-
framerate: 30,
|
|
224
|
-
});
|
|
225
|
-
// Create and encode test frames (red, green, blue)
|
|
226
|
-
const colors = [
|
|
227
|
-
[255, 0, 0, 255], // Red
|
|
228
|
-
[0, 255, 0, 255], // Green
|
|
229
|
-
[0, 0, 255, 255], // Blue
|
|
230
|
-
];
|
|
231
|
-
for (let i = 0; i < frameCount; i++) {
|
|
232
|
-
const data = new Uint8Array(width * height * 4);
|
|
233
|
-
const [r, g, b, a] = colors[i];
|
|
234
|
-
for (let p = 0; p < width * height; p++) {
|
|
235
|
-
data[p * 4] = r;
|
|
236
|
-
data[p * 4 + 1] = g;
|
|
237
|
-
data[p * 4 + 2] = b;
|
|
238
|
-
data[p * 4 + 3] = a;
|
|
239
|
-
}
|
|
240
|
-
const frame = new VideoFrame(data, {
|
|
241
|
-
format: 'RGBA',
|
|
242
|
-
codedWidth: width,
|
|
243
|
-
codedHeight: height,
|
|
244
|
-
timestamp: i * 33333,
|
|
245
|
-
});
|
|
246
|
-
encoder.encode(frame, { keyFrame: i === 0 });
|
|
247
|
-
frame.close();
|
|
248
|
-
}
|
|
249
|
-
await encoder.flush();
|
|
250
|
-
encoder.close();
|
|
251
|
-
expect(encodedChunks.length).toBeGreaterThan(0);
|
|
252
|
-
// Step 2: Decode frames
|
|
253
|
-
const decodedFrames = [];
|
|
254
|
-
const decoder = new VideoDecoder({
|
|
255
|
-
output: (frame) => decodedFrames.push(frame),
|
|
256
|
-
error: (err) => { throw err; },
|
|
257
|
-
});
|
|
258
|
-
decoder.configure({
|
|
259
|
-
codec: 'avc1.42001E',
|
|
260
|
-
codedWidth: width,
|
|
261
|
-
codedHeight: height,
|
|
262
|
-
outputFormat: 'RGBA',
|
|
263
|
-
});
|
|
264
|
-
for (const chunk of encodedChunks) {
|
|
265
|
-
decoder.decode(chunk);
|
|
266
|
-
}
|
|
267
|
-
await decoder.flush();
|
|
268
|
-
decoder.close();
|
|
269
|
-
// Verify decoded frames
|
|
270
|
-
expect(decodedFrames.length).toBe(frameCount);
|
|
271
|
-
for (const frame of decodedFrames) {
|
|
272
|
-
expect(frame.codedWidth).toBe(width);
|
|
273
|
-
expect(frame.codedHeight).toBe(height);
|
|
274
|
-
expect(frame.format).toBe('RGBA');
|
|
275
|
-
frame.close();
|
|
276
|
-
}
|
|
277
|
-
}, 30000);
|
|
278
|
-
it('should decode VP9 encoded frames', async () => {
|
|
279
|
-
const width = 64;
|
|
280
|
-
const height = 64;
|
|
281
|
-
const frameCount = 3;
|
|
282
|
-
// Step 1: Encode frames
|
|
283
|
-
const encodedChunks = [];
|
|
284
|
-
const encoder = new VideoEncoder({
|
|
285
|
-
output: (chunk) => encodedChunks.push(chunk),
|
|
286
|
-
error: (err) => { throw err; },
|
|
287
|
-
});
|
|
288
|
-
encoder.configure({
|
|
289
|
-
codec: 'vp09.00.10.08',
|
|
290
|
-
width,
|
|
291
|
-
height,
|
|
292
|
-
bitrate: 500_000,
|
|
293
|
-
framerate: 30,
|
|
294
|
-
});
|
|
295
|
-
for (let i = 0; i < frameCount; i++) {
|
|
296
|
-
const data = new Uint8Array(width * height * 4);
|
|
297
|
-
// Fill with gradient (no alpha transparency issues with VP9)
|
|
298
|
-
for (let p = 0; p < width * height; p++) {
|
|
299
|
-
data[p * 4] = (i * 80) % 256;
|
|
300
|
-
data[p * 4 + 1] = (p % width) * 4;
|
|
301
|
-
data[p * 4 + 2] = Math.floor(p / width) * 4;
|
|
302
|
-
data[p * 4 + 3] = 255;
|
|
303
|
-
}
|
|
304
|
-
const frame = new VideoFrame(data, {
|
|
305
|
-
format: 'RGBA',
|
|
306
|
-
codedWidth: width,
|
|
307
|
-
codedHeight: height,
|
|
308
|
-
timestamp: i * 33333,
|
|
309
|
-
});
|
|
310
|
-
encoder.encode(frame, { keyFrame: i === 0 });
|
|
311
|
-
frame.close();
|
|
312
|
-
}
|
|
313
|
-
await encoder.flush();
|
|
314
|
-
encoder.close();
|
|
315
|
-
expect(encodedChunks.length).toBeGreaterThan(0);
|
|
316
|
-
// Step 2: Decode frames
|
|
317
|
-
const decodedFrames = [];
|
|
318
|
-
const decoder = new VideoDecoder({
|
|
319
|
-
output: (frame) => decodedFrames.push(frame),
|
|
320
|
-
error: (err) => { throw err; },
|
|
321
|
-
});
|
|
322
|
-
decoder.configure({
|
|
323
|
-
codec: 'vp09.00.10.08',
|
|
324
|
-
codedWidth: width,
|
|
325
|
-
codedHeight: height,
|
|
326
|
-
});
|
|
327
|
-
for (const chunk of encodedChunks) {
|
|
328
|
-
decoder.decode(chunk);
|
|
329
|
-
}
|
|
330
|
-
await decoder.flush();
|
|
331
|
-
decoder.close();
|
|
332
|
-
// Verify decoded frames
|
|
333
|
-
expect(decodedFrames.length).toBe(frameCount);
|
|
334
|
-
for (const frame of decodedFrames) {
|
|
335
|
-
expect(frame.codedWidth).toBe(width);
|
|
336
|
-
expect(frame.codedHeight).toBe(height);
|
|
337
|
-
frame.close();
|
|
338
|
-
}
|
|
339
|
-
}, 30000);
|
|
340
|
-
it('should pass chunk timestamps to decoded frames', async () => {
|
|
341
|
-
const width = 64;
|
|
342
|
-
const height = 64;
|
|
343
|
-
const frameCount = 3;
|
|
344
|
-
// Encode frames first
|
|
345
|
-
const encodedChunks = [];
|
|
346
|
-
const encoder = new VideoEncoder({
|
|
347
|
-
output: (chunk) => encodedChunks.push(chunk),
|
|
348
|
-
error: (err) => { throw err; },
|
|
349
|
-
});
|
|
350
|
-
encoder.configure({
|
|
351
|
-
codec: 'avc1.42001E',
|
|
352
|
-
width,
|
|
353
|
-
height,
|
|
354
|
-
bitrate: 500_000,
|
|
355
|
-
framerate: 30,
|
|
356
|
-
});
|
|
357
|
-
for (let i = 0; i < frameCount; i++) {
|
|
358
|
-
const data = new Uint8Array(width * height * 4);
|
|
359
|
-
const frame = new VideoFrame(data, {
|
|
360
|
-
format: 'RGBA',
|
|
361
|
-
codedWidth: width,
|
|
362
|
-
codedHeight: height,
|
|
363
|
-
timestamp: i * 33333,
|
|
364
|
-
});
|
|
365
|
-
encoder.encode(frame, { keyFrame: i === 0 });
|
|
366
|
-
frame.close();
|
|
367
|
-
}
|
|
368
|
-
await encoder.flush();
|
|
369
|
-
encoder.close();
|
|
370
|
-
expect(encodedChunks.length).toBe(frameCount);
|
|
371
|
-
// Now decode - the decoder should pass timestamps from chunks to output frames
|
|
372
|
-
const decodedFrames = [];
|
|
373
|
-
const decoder = new VideoDecoder({
|
|
374
|
-
output: (frame) => decodedFrames.push(frame),
|
|
375
|
-
error: (err) => { throw err; },
|
|
376
|
-
});
|
|
377
|
-
decoder.configure({
|
|
378
|
-
codec: 'avc1.42001E',
|
|
379
|
-
codedWidth: width,
|
|
380
|
-
codedHeight: height,
|
|
381
|
-
});
|
|
382
|
-
for (const chunk of encodedChunks) {
|
|
383
|
-
decoder.decode(chunk);
|
|
384
|
-
}
|
|
385
|
-
await decoder.flush();
|
|
386
|
-
decoder.close();
|
|
387
|
-
// The decoder should output same number of frames as input chunks
|
|
388
|
-
expect(decodedFrames.length).toBe(encodedChunks.length);
|
|
389
|
-
// Each decoded frame should have the timestamp from the corresponding chunk
|
|
390
|
-
for (let i = 0; i < decodedFrames.length; i++) {
|
|
391
|
-
expect(decodedFrames[i].timestamp).toBe(encodedChunks[i].timestamp);
|
|
392
|
-
decodedFrames[i].close();
|
|
393
|
-
}
|
|
394
|
-
}, 30000);
|
|
395
|
-
});
|
|
396
|
-
describe('VideoDecoder output formats', () => {
|
|
397
|
-
// Helper to create encoded chunks for testing
|
|
398
|
-
async function createEncodedChunks(format) {
|
|
399
|
-
const width = 64;
|
|
400
|
-
const height = 64;
|
|
401
|
-
const encodedChunks = [];
|
|
402
|
-
const encoder = new VideoEncoder({
|
|
403
|
-
output: (chunk) => encodedChunks.push(chunk),
|
|
404
|
-
error: (err) => { throw err; },
|
|
405
|
-
});
|
|
406
|
-
encoder.configure({
|
|
407
|
-
codec: 'vp09.00.10.08',
|
|
408
|
-
width,
|
|
409
|
-
height,
|
|
410
|
-
bitrate: 500_000,
|
|
411
|
-
framerate: 30,
|
|
412
|
-
});
|
|
413
|
-
// Create test frame in the specified format
|
|
414
|
-
let data;
|
|
415
|
-
if (format === 'RGBA') {
|
|
416
|
-
data = new Uint8Array(width * height * 4);
|
|
417
|
-
for (let p = 0; p < width * height; p++) {
|
|
418
|
-
data[p * 4] = 128; // R
|
|
419
|
-
data[p * 4 + 1] = 64; // G
|
|
420
|
-
data[p * 4 + 2] = 192; // B
|
|
421
|
-
data[p * 4 + 3] = 255; // A
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
else {
|
|
425
|
-
// I420: Y + U + V planes
|
|
426
|
-
const ySize = width * height;
|
|
427
|
-
const uvSize = (width / 2) * (height / 2);
|
|
428
|
-
data = new Uint8Array(ySize + 2 * uvSize);
|
|
429
|
-
// Y plane (gray)
|
|
430
|
-
for (let i = 0; i < ySize; i++)
|
|
431
|
-
data[i] = 128;
|
|
432
|
-
// U plane
|
|
433
|
-
for (let i = 0; i < uvSize; i++)
|
|
434
|
-
data[ySize + i] = 128;
|
|
435
|
-
// V plane
|
|
436
|
-
for (let i = 0; i < uvSize; i++)
|
|
437
|
-
data[ySize + uvSize + i] = 128;
|
|
438
|
-
}
|
|
439
|
-
const frame = new VideoFrame(data, {
|
|
440
|
-
format,
|
|
441
|
-
codedWidth: width,
|
|
442
|
-
codedHeight: height,
|
|
443
|
-
timestamp: 0,
|
|
444
|
-
});
|
|
445
|
-
encoder.encode(frame, { keyFrame: true });
|
|
446
|
-
frame.close();
|
|
447
|
-
await encoder.flush();
|
|
448
|
-
encoder.close();
|
|
449
|
-
return encodedChunks;
|
|
450
|
-
}
|
|
451
|
-
it('should decode to I420 format (default)', async () => {
|
|
452
|
-
const encodedChunks = await createEncodedChunks('RGBA');
|
|
453
|
-
expect(encodedChunks.length).toBeGreaterThan(0);
|
|
454
|
-
const decodedFrames = [];
|
|
455
|
-
const decoder = new VideoDecoder({
|
|
456
|
-
output: (frame) => decodedFrames.push(frame),
|
|
457
|
-
error: (err) => { throw err; },
|
|
458
|
-
});
|
|
459
|
-
decoder.configure({
|
|
460
|
-
codec: 'vp09.00.10.08',
|
|
461
|
-
codedWidth: 64,
|
|
462
|
-
codedHeight: 64,
|
|
463
|
-
// outputFormat defaults to I420
|
|
464
|
-
});
|
|
465
|
-
for (const chunk of encodedChunks) {
|
|
466
|
-
decoder.decode(chunk);
|
|
467
|
-
}
|
|
468
|
-
await decoder.flush();
|
|
469
|
-
decoder.close();
|
|
470
|
-
expect(decodedFrames.length).toBeGreaterThan(0);
|
|
471
|
-
for (const frame of decodedFrames) {
|
|
472
|
-
expect(frame.format).toBe('I420');
|
|
473
|
-
expect(frame.numberOfPlanes).toBe(3);
|
|
474
|
-
// I420 frame size = Y + U + V = 64*64 + 32*32 + 32*32 = 4096 + 1024 + 1024 = 6144
|
|
475
|
-
expect(frame.allocationSize()).toBe(6144);
|
|
476
|
-
frame.close();
|
|
477
|
-
}
|
|
478
|
-
}, 30000);
|
|
479
|
-
it('should decode to NV12 format', async () => {
|
|
480
|
-
const encodedChunks = await createEncodedChunks('RGBA');
|
|
481
|
-
expect(encodedChunks.length).toBeGreaterThan(0);
|
|
482
|
-
const decodedFrames = [];
|
|
483
|
-
const decoder = new VideoDecoder({
|
|
484
|
-
output: (frame) => decodedFrames.push(frame),
|
|
485
|
-
error: (err) => { throw err; },
|
|
486
|
-
});
|
|
487
|
-
decoder.configure({
|
|
488
|
-
codec: 'vp09.00.10.08',
|
|
489
|
-
codedWidth: 64,
|
|
490
|
-
codedHeight: 64,
|
|
491
|
-
outputFormat: 'NV12',
|
|
492
|
-
});
|
|
493
|
-
for (const chunk of encodedChunks) {
|
|
494
|
-
decoder.decode(chunk);
|
|
495
|
-
}
|
|
496
|
-
await decoder.flush();
|
|
497
|
-
decoder.close();
|
|
498
|
-
expect(decodedFrames.length).toBeGreaterThan(0);
|
|
499
|
-
for (const frame of decodedFrames) {
|
|
500
|
-
expect(frame.format).toBe('NV12');
|
|
501
|
-
expect(frame.numberOfPlanes).toBe(2);
|
|
502
|
-
// NV12 frame size = Y + UV = 64*64 + 64*32 = 4096 + 2048 = 6144
|
|
503
|
-
expect(frame.allocationSize()).toBe(6144);
|
|
504
|
-
frame.close();
|
|
505
|
-
}
|
|
506
|
-
}, 30000);
|
|
507
|
-
it('should decode to BGRA format', async () => {
|
|
508
|
-
const encodedChunks = await createEncodedChunks('RGBA');
|
|
509
|
-
expect(encodedChunks.length).toBeGreaterThan(0);
|
|
510
|
-
const decodedFrames = [];
|
|
511
|
-
const decoder = new VideoDecoder({
|
|
512
|
-
output: (frame) => decodedFrames.push(frame),
|
|
513
|
-
error: (err) => { throw err; },
|
|
514
|
-
});
|
|
515
|
-
decoder.configure({
|
|
516
|
-
codec: 'vp09.00.10.08',
|
|
517
|
-
codedWidth: 64,
|
|
518
|
-
codedHeight: 64,
|
|
519
|
-
outputFormat: 'BGRA',
|
|
520
|
-
});
|
|
521
|
-
for (const chunk of encodedChunks) {
|
|
522
|
-
decoder.decode(chunk);
|
|
523
|
-
}
|
|
524
|
-
await decoder.flush();
|
|
525
|
-
decoder.close();
|
|
526
|
-
expect(decodedFrames.length).toBeGreaterThan(0);
|
|
527
|
-
for (const frame of decodedFrames) {
|
|
528
|
-
expect(frame.format).toBe('BGRA');
|
|
529
|
-
expect(frame.numberOfPlanes).toBe(1);
|
|
530
|
-
// BGRA frame size = 64*64*4 = 16384
|
|
531
|
-
expect(frame.allocationSize()).toBe(16384);
|
|
532
|
-
frame.close();
|
|
533
|
-
}
|
|
534
|
-
}, 30000);
|
|
535
|
-
it('should throw with invalid outputFormat', () => {
|
|
536
|
-
const decoder = new VideoDecoder({
|
|
537
|
-
output: () => { },
|
|
538
|
-
error: () => { },
|
|
539
|
-
});
|
|
540
|
-
expect(() => {
|
|
541
|
-
decoder.configure({
|
|
542
|
-
codec: 'vp09.00.10.08',
|
|
543
|
-
codedWidth: 64,
|
|
544
|
-
codedHeight: 64,
|
|
545
|
-
outputFormat: 'INVALID',
|
|
546
|
-
});
|
|
547
|
-
}).toThrow(TypeError);
|
|
548
|
-
decoder.close();
|
|
549
|
-
});
|
|
550
|
-
});
|
|
551
|
-
describe('VideoEncoder input formats', () => {
|
|
552
|
-
it('should encode I420 input format', async () => {
|
|
553
|
-
const width = 64;
|
|
554
|
-
const height = 64;
|
|
555
|
-
const encodedChunks = [];
|
|
556
|
-
const encoder = new VideoEncoder({
|
|
557
|
-
output: (chunk) => encodedChunks.push(chunk),
|
|
558
|
-
error: (err) => { throw err; },
|
|
559
|
-
});
|
|
560
|
-
encoder.configure({
|
|
561
|
-
codec: 'vp09.00.10.08',
|
|
562
|
-
width,
|
|
563
|
-
height,
|
|
564
|
-
bitrate: 500_000,
|
|
565
|
-
framerate: 30,
|
|
566
|
-
});
|
|
567
|
-
// Create I420 frame
|
|
568
|
-
const ySize = width * height;
|
|
569
|
-
const uvSize = (width / 2) * (height / 2);
|
|
570
|
-
const data = new Uint8Array(ySize + 2 * uvSize);
|
|
571
|
-
// Y plane
|
|
572
|
-
for (let i = 0; i < ySize; i++)
|
|
573
|
-
data[i] = 128;
|
|
574
|
-
// U plane
|
|
575
|
-
for (let i = 0; i < uvSize; i++)
|
|
576
|
-
data[ySize + i] = 128;
|
|
577
|
-
// V plane
|
|
578
|
-
for (let i = 0; i < uvSize; i++)
|
|
579
|
-
data[ySize + uvSize + i] = 128;
|
|
580
|
-
const frame = new VideoFrame(data, {
|
|
581
|
-
format: 'I420',
|
|
582
|
-
codedWidth: width,
|
|
583
|
-
codedHeight: height,
|
|
584
|
-
timestamp: 0,
|
|
585
|
-
});
|
|
586
|
-
encoder.encode(frame, { keyFrame: true });
|
|
587
|
-
frame.close();
|
|
588
|
-
await encoder.flush();
|
|
589
|
-
encoder.close();
|
|
590
|
-
expect(encodedChunks.length).toBeGreaterThan(0);
|
|
591
|
-
// Verify encoding succeeded - chunk type depends on FFmpeg keyframe detection
|
|
592
|
-
expect(['key', 'delta']).toContain(encodedChunks[0].type);
|
|
593
|
-
}, 30000);
|
|
594
|
-
it('should encode NV12 input format', async () => {
|
|
595
|
-
const width = 64;
|
|
596
|
-
const height = 64;
|
|
597
|
-
const encodedChunks = [];
|
|
598
|
-
const encoder = new VideoEncoder({
|
|
599
|
-
output: (chunk) => encodedChunks.push(chunk),
|
|
600
|
-
error: (err) => { throw err; },
|
|
601
|
-
});
|
|
602
|
-
encoder.configure({
|
|
603
|
-
codec: 'vp09.00.10.08',
|
|
604
|
-
width,
|
|
605
|
-
height,
|
|
606
|
-
bitrate: 500_000,
|
|
607
|
-
framerate: 30,
|
|
608
|
-
});
|
|
609
|
-
// Create NV12 frame (Y + interleaved UV)
|
|
610
|
-
const ySize = width * height;
|
|
611
|
-
const uvSize = width * (height / 2);
|
|
612
|
-
const data = new Uint8Array(ySize + uvSize);
|
|
613
|
-
// Y plane
|
|
614
|
-
for (let i = 0; i < ySize; i++)
|
|
615
|
-
data[i] = 128;
|
|
616
|
-
// UV interleaved
|
|
617
|
-
for (let i = 0; i < uvSize; i++)
|
|
618
|
-
data[ySize + i] = 128;
|
|
619
|
-
const frame = new VideoFrame(data, {
|
|
620
|
-
format: 'NV12',
|
|
621
|
-
codedWidth: width,
|
|
622
|
-
codedHeight: height,
|
|
623
|
-
timestamp: 0,
|
|
624
|
-
});
|
|
625
|
-
encoder.encode(frame, { keyFrame: true });
|
|
626
|
-
frame.close();
|
|
627
|
-
await encoder.flush();
|
|
628
|
-
encoder.close();
|
|
629
|
-
expect(encodedChunks.length).toBeGreaterThan(0);
|
|
630
|
-
// Verify encoding succeeded - chunk type depends on FFmpeg keyframe detection
|
|
631
|
-
expect(['key', 'delta']).toContain(encodedChunks[0].type);
|
|
632
|
-
}, 30000);
|
|
633
|
-
it('should reject mixed input formats', async () => {
|
|
634
|
-
const width = 64;
|
|
635
|
-
const height = 64;
|
|
636
|
-
let errorCalled = false;
|
|
637
|
-
const encoder = new VideoEncoder({
|
|
638
|
-
output: () => { },
|
|
639
|
-
error: () => { errorCalled = true; },
|
|
640
|
-
});
|
|
641
|
-
encoder.configure({
|
|
642
|
-
codec: 'vp09.00.10.08',
|
|
643
|
-
width,
|
|
644
|
-
height,
|
|
645
|
-
bitrate: 500_000,
|
|
646
|
-
framerate: 30,
|
|
647
|
-
});
|
|
648
|
-
// First frame in RGBA
|
|
649
|
-
const rgbaData = new Uint8Array(width * height * 4);
|
|
650
|
-
const frame1 = new VideoFrame(rgbaData, {
|
|
651
|
-
format: 'RGBA',
|
|
652
|
-
codedWidth: width,
|
|
653
|
-
codedHeight: height,
|
|
654
|
-
timestamp: 0,
|
|
655
|
-
});
|
|
656
|
-
encoder.encode(frame1, { keyFrame: true });
|
|
657
|
-
frame1.close();
|
|
658
|
-
// Second frame in I420 (different format)
|
|
659
|
-
const ySize = width * height;
|
|
660
|
-
const uvSize = (width / 2) * (height / 2);
|
|
661
|
-
const i420Data = new Uint8Array(ySize + 2 * uvSize);
|
|
662
|
-
const frame2 = new VideoFrame(i420Data, {
|
|
663
|
-
format: 'I420',
|
|
664
|
-
codedWidth: width,
|
|
665
|
-
codedHeight: height,
|
|
666
|
-
timestamp: 33333,
|
|
667
|
-
});
|
|
668
|
-
encoder.encode(frame2);
|
|
669
|
-
frame2.close();
|
|
670
|
-
// Error callback should have been called due to format mismatch
|
|
671
|
-
expect(errorCalled).toBe(true);
|
|
672
|
-
encoder.close();
|
|
673
|
-
}, 30000);
|
|
674
|
-
});
|
|
675
|
-
//# sourceMappingURL=VideoDecoder.test.js.map
|