1 // META: global=window,dedicatedworker
2 // META: script=/common/media.js
3 // META: script=/webcodecs/utils.js
5 const defaultConfig = {
12 async function generateBitmap(width, height) {
13 const src = "pattern.png";
21 .then(response => response.blob())
22 .then(blob => createImageBitmap(blob, size));
25 async function createVideoFrame(width, height, timestamp) {
26 let bitmap = await generateBitmap(width, height);
27 return new VideoFrame(bitmap, { timestamp: timestamp });
31 // VideoEncoderInit lacks required fields.
32 assert_throws_js(TypeError, () => { new VideoEncoder({}); });
34 // VideoEncoderInit has required fields.
35 let encoder = new VideoEncoder(getDefaultCodecInit(t));
37 assert_equals(encoder.state, "unconfigured");
41 return endAfterEventLoopTurn();
42 }, 'Test VideoEncoder construction');
45 let encoder = new VideoEncoder(getDefaultCodecInit(t));
49 'bogus', // Non exsitent codec
50 'vorbis', // Audio codec
51 'vp9', // Ambiguous codec
52 'video/webm; codecs="vp9"' // Codec with mime type
55 testConfigurations(encoder, defaultConfig, badCodecsList);
57 return endAfterEventLoopTurn();
58 }, 'Test VideoEncoder.configure()');
60 promise_test(async t => {
61 let output_chunks = [];
62 let codecInit = getDefaultCodecInit(t);
63 codecInit.output = chunk => output_chunks.push(chunk);
65 let encoder = new VideoEncoder(codecInit);
68 assert_equals(encoder.encodeQueueSize, 0);
70 encoder.configure(defaultConfig);
73 assert_equals(encoder.encodeQueueSize, 0);
75 let frame1 = await createVideoFrame(640, 480, 0);
76 let frame2 = await createVideoFrame(640, 480, 33333);
78 encoder.encode(frame1.clone());
79 encoder.encode(frame2.clone());
81 // Could be 0, 1, or 2. We can't guarantee this check runs before the UA has
82 // processed the encodes.
83 assert_true(encoder.encodeQueueSize >= 0 && encoder.encodeQueueSize <= 2)
85 await encoder.flush();
87 // We can guarantee that all encodes are processed after a flush.
88 assert_equals(encoder.encodeQueueSize, 0);
90 assert_equals(output_chunks.length, 2);
91 assert_equals(output_chunks[0].timestamp, frame1.timestamp);
92 assert_equals(output_chunks[1].timestamp, frame2.timestamp);
93 }, 'Test successful configure(), encode(), and flush()');
95 promise_test(async t => {
96 let callbacks_before_reset = 0;
97 let callbacks_after_reset = 0;
98 let codecInit = getDefaultCodecInit(t);
99 codecInit.output = chunk => {
100 if (chunk.timestamp % 2 == 0) {
101 // pre-reset frames have even timestamp
102 callbacks_before_reset++;
104 // after-reset frames have odd timestamp
105 callbacks_after_reset++;
109 let encoder = new VideoEncoder(codecInit);
110 encoder.configure(defaultConfig);
111 await encoder.flush();
114 for (let i = 0; i < 200; i++) {
115 let frame = await createVideoFrame(640, 480, i * 40_000);
119 for (frame of frames)
120 encoder.encode(frame);
122 // Wait for the first frame to be encoded
123 await t.step_wait(() => callbacks_before_reset > 0,
124 "Encoded outputs started coming", 10000, 1);
126 let saved_callbacks_before_reset = callbacks_before_reset;
127 assert_greater_than(callbacks_before_reset, 0);
128 assert_less_than_equal(callbacks_before_reset, frames.length);
131 assert_equals(encoder.encodeQueueSize, 0);
133 let newConfig = { ...defaultConfig };
134 newConfig.width = 800;
135 newConfig.height = 600;
136 encoder.configure(newConfig);
138 for (let i = frames.length; i < frames.length + 5; i++) {
139 let frame = await createVideoFrame(800, 600, i * 40_000 + 1);
140 encoder.encode(frame);
142 await encoder.flush();
143 assert_equals(callbacks_after_reset, 5);
144 assert_equals(saved_callbacks_before_reset, callbacks_before_reset);
145 assert_equals(encoder.encodeQueueSize, 0);
146 }, 'Test successful reset() and re-confiugre()');
148 promise_test(async t => {
149 let output_chunks = [];
150 let codecInit = getDefaultCodecInit(t);
151 codecInit.output = chunk => output_chunks.push(chunk);
153 let encoder = new VideoEncoder(codecInit);
156 assert_equals(encoder.encodeQueueSize, 0);
158 let config = defaultConfig;
160 encoder.configure(config);
162 let frame1 = await createVideoFrame(640, 480, 0);
163 let frame2 = await createVideoFrame(640, 480, 33333);
165 encoder.encode(frame1.clone());
166 encoder.configure(config);
168 encoder.encode(frame2.clone());
170 await encoder.flush();
172 // We can guarantee that all encodes are processed after a flush.
173 assert_equals(encoder.encodeQueueSize, 0);
175 // The first frame may have been dropped when reconfiguring.
176 // This shouldn't happen, and should be fixed/called out in the spec, but
177 // this is preptively added to prevent flakiness.
178 // TODO: Remove these checks when implementations handle this correctly.
179 assert_true(output_chunks.length == 1 || output_chunks.length == 2);
181 if (output_chunks.length == 1) {
182 // If we only have one chunk frame, make sure we droped the frame that was
183 // in flight when we reconfigured.
184 assert_equals(output_chunks[0].timestamp, frame2.timestamp);
186 assert_equals(output_chunks[0].timestamp, frame1.timestamp);
187 assert_equals(output_chunks[1].timestamp, frame2.timestamp);
192 let frame3 = await createVideoFrame(640, 480, 66666);
193 let frame4 = await createVideoFrame(640, 480, 100000);
195 encoder.encode(frame3.clone());
197 // Verify that a failed call to configure does not change the encoder's state.
198 let badConfig = { ...defaultConfig };
199 badConfig.codec = 'bogus';
200 assert_throws_js(TypeError, () => encoder.configure(badConfig));
202 encoder.encode(frame4.clone());
204 await encoder.flush();
206 assert_equals(output_chunks[0].timestamp, frame3.timestamp);
207 assert_equals(output_chunks[1].timestamp, frame4.timestamp);
208 }, 'Test successful encode() after re-configure().');
210 promise_test(async t => {
211 let output_chunks = [];
212 let codecInit = getDefaultCodecInit(t);
213 codecInit.output = chunk => output_chunks.push(chunk);
215 let encoder = new VideoEncoder(codecInit);
217 let timestamp = 33333;
218 let frame = await createVideoFrame(640, 480, timestamp);
220 encoder.configure(defaultConfig);
221 assert_equals(encoder.state, "configured");
223 encoder.encode(frame);
225 // |frame| is not longer valid since it has been closed.
226 assert_not_equals(frame.timestamp, timestamp);
227 assert_throws_dom("InvalidStateError", () => frame.clone());
231 return endAfterEventLoopTurn();
232 }, 'Test encoder consumes (closes) frames.');
234 promise_test(async t => {
235 let encoder = new VideoEncoder(getDefaultCodecInit(t));
237 let frame = await createVideoFrame(640, 480, 0);
239 return testClosedCodec(t, encoder, defaultConfig, frame);
240 }, 'Verify closed VideoEncoder operations');
242 promise_test(async t => {
243 let encoder = new VideoEncoder(getDefaultCodecInit(t));
245 let frame = await createVideoFrame(640, 480, 0);
247 return testUnconfiguredCodec(t, encoder, frame);
248 }, 'Verify unconfigured VideoEncoder operations');
250 promise_test(async t => {
251 let encoder = new VideoEncoder(getDefaultCodecInit(t));
253 let frame = await createVideoFrame(640, 480, 0);
256 encoder.configure(defaultConfig);
258 assert_throws_dom("OperationError", () => {
259 encoder.encode(frame)
261 }, 'Verify encoding closed frames throws.');