video-encoder.https.any.js (9187B)
1 // META: global=window,dedicatedworker 2 // META: script=/common/media.js 3 // META: script=/webcodecs/utils.js 4 // META: script=/webcodecs/video-encoder-utils.js 5 6 const defaultConfig = { 7 codec: 'vp8', 8 width: 640, 9 height: 480 10 }; 11 12 promise_test(t => { 13 // VideoEncoderInit lacks required fields. 14 assert_throws_js(TypeError, () => { new VideoEncoder({}); }); 15 16 // VideoEncoderInit has required fields. 17 let encoder = new VideoEncoder(getDefaultCodecInit(t)); 18 19 assert_equals(encoder.state, "unconfigured"); 20 21 encoder.close(); 22 23 return endAfterEventLoopTurn(); 24 }, 'Test VideoEncoder construction'); 25 26 promise_test(async t => { 27 let output_chunks = []; 28 let codecInit = getDefaultCodecInit(t); 29 let decoderConfig = null; 30 let encoderConfig = { 31 codec: 'vp8', 32 width: 640, 33 height: 480, 34 displayWidth: 800, 35 displayHeight: 600, 36 }; 37 38 codecInit.output = (chunk, metadata) => { 39 assert_not_equals(metadata, null); 40 if (metadata.decoderConfig) 41 decoderConfig = metadata.decoderConfig; 42 output_chunks.push(chunk); 43 } 44 45 let encoder = new VideoEncoder(codecInit); 46 encoder.configure(encoderConfig); 47 48 let frame1 = createFrame(640, 480, 0); 49 let frame2 = createFrame(640, 480, 33333); 50 t.add_cleanup(() => { 51 frame1.close(); 52 frame2.close(); 53 }); 54 55 encoder.encode(frame1); 56 encoder.encode(frame2); 57 58 await encoder.flush(); 59 60 // Decoder config should be given with the first chunk 61 assert_not_equals(decoderConfig, null); 62 assert_equals(decoderConfig.codec, encoderConfig.codec); 63 assert_greater_than_equal(decoderConfig.codedHeight, encoderConfig.height); 64 assert_greater_than_equal(decoderConfig.codedWidth, encoderConfig.width); 65 assert_equals(decoderConfig.displayAspectHeight, encoderConfig.displayHeight); 66 assert_equals(decoderConfig.displayAspectWidth, encoderConfig.displayWidth); 67 assert_not_equals(decoderConfig.colorSpace.primaries, null); 68 assert_not_equals(decoderConfig.colorSpace.transfer, null); 69 assert_not_equals(decoderConfig.colorSpace.matrix, null); 70 assert_not_equals(decoderConfig.colorSpace.fullRange, null); 71 72 assert_equals(output_chunks.length, 2); 73 assert_equals(output_chunks[0].timestamp, frame1.timestamp); 74 assert_equals(output_chunks[0].duration, frame1.duration); 75 assert_equals(output_chunks[1].timestamp, frame2.timestamp); 76 assert_equals(output_chunks[1].duration, frame2.duration); 77 }, 'Test successful configure(), encode(), and flush()'); 78 79 promise_test(async t => { 80 let codecInit = getDefaultCodecInit(t); 81 let encoderConfig = { 82 codec: 'vp8', 83 width: 320, 84 height: 200 85 }; 86 87 codecInit.output = (chunk, metadata) => {} 88 89 let encoder = new VideoEncoder(codecInit); 90 91 // No encodes yet. 92 assert_equals(encoder.encodeQueueSize, 0); 93 94 encoder.configure(encoderConfig); 95 96 // Still no encodes. 97 assert_equals(encoder.encodeQueueSize, 0); 98 99 const frames_count = 100; 100 let frames = []; 101 for (let i = 0; i < frames_count; i++) { 102 let frame = createFrame(320, 200, i * 16000); 103 frames.push(frame); 104 } 105 106 let lastDequeueSize = Infinity; 107 encoder.ondequeue = () => { 108 assert_greater_than(lastDequeueSize, 0, "Dequeue event after queue empty"); 109 assert_greater_than(lastDequeueSize, encoder.encodeQueueSize, 110 "Dequeue event without decreased queue size"); 111 lastDequeueSize = encoder.encodeQueueSize; 112 }; 113 114 for (let frame of frames) 115 encoder.encode(frame); 116 117 assert_greater_than_equal(encoder.encodeQueueSize, 0); 118 assert_less_than_equal(encoder.encodeQueueSize, frames_count); 119 120 await encoder.flush(); 121 // We can guarantee that all encodes are processed after a flush. 122 assert_equals(encoder.encodeQueueSize, 0); 123 // Last dequeue event should fire when the queue is empty. 124 assert_equals(lastDequeueSize, 0); 125 126 // Reset this to Infinity to track the decline of queue size for this next 127 // batch of encodes. 128 lastDequeueSize = Infinity; 129 130 for (let frame of frames) { 131 encoder.encode(frame); 132 frame.close(); 133 } 134 135 assert_greater_than_equal(encoder.encodeQueueSize, 0); 136 encoder.reset(); 137 assert_equals(encoder.encodeQueueSize, 0); 138 }, 'encodeQueueSize test'); 139 140 141 promise_test(async t => { 142 let timestamp = 0; 143 let callbacks_before_reset = 0; 144 let callbacks_after_reset = 0; 145 const timestamp_step = 40000; 146 const expected_callbacks_before_reset = 3; 147 let codecInit = getDefaultCodecInit(t); 148 let original = createFrame(320, 200, 0); 149 let encoder = null; 150 let reset_completed = false; 151 codecInit.output = (chunk, metadata) => { 152 if (chunk.timestamp % 2 == 0) { 153 // pre-reset frames have even timestamp 154 callbacks_before_reset++; 155 if (callbacks_before_reset == expected_callbacks_before_reset) { 156 encoder.reset(); 157 reset_completed = true; 158 } 159 } else { 160 // after-reset frames have odd timestamp 161 callbacks_after_reset++; 162 } 163 } 164 165 encoder = new VideoEncoder(codecInit); 166 encoder.configure(defaultConfig); 167 await encoder.flush(); 168 169 // Send 10x frames to the encoder, call reset() on it after x outputs, 170 // and make sure no more chunks are emitted afterwards. 171 let encodes_before_reset = expected_callbacks_before_reset * 10; 172 for (let i = 0; i < encodes_before_reset; i++) { 173 let frame = new VideoFrame(original, { timestamp: timestamp }); 174 timestamp += timestamp_step; 175 encoder.encode(frame); 176 frame.close(); 177 } 178 179 await t.step_wait(() => reset_completed, 180 "Reset() should be called by output callback", 10000, 1); 181 182 assert_equals(callbacks_before_reset, expected_callbacks_before_reset); 183 assert_true(reset_completed); 184 assert_equals(encoder.encodeQueueSize, 0); 185 186 let newConfig = { ...defaultConfig }; 187 newConfig.width = 800; 188 newConfig.height = 600; 189 encoder.configure(newConfig); 190 191 const frames_after_reset = 5; 192 for (let i = 0; i < frames_after_reset; i++) { 193 let frame = createFrame(800, 600, timestamp + 1); 194 timestamp += timestamp_step; 195 encoder.encode(frame); 196 frame.close(); 197 } 198 await encoder.flush(); 199 200 assert_equals(callbacks_after_reset, frames_after_reset, 201 "not all after-reset() outputs have been emitted"); 202 assert_equals(callbacks_before_reset, expected_callbacks_before_reset, 203 "pre-reset() outputs were emitter after reset() and flush()"); 204 assert_equals(encoder.encodeQueueSize, 0); 205 }, 'Test successful reset() and re-confiugre()'); 206 207 promise_test(async t => { 208 let output_chunks = []; 209 const codecInit = { 210 output: chunk => output_chunks.push(chunk), 211 }; 212 const error = new Promise(resolve => codecInit.error = e => { 213 resolve(e); 214 }); 215 216 let encoder = new VideoEncoder(codecInit); 217 218 // No encodes yet. 219 assert_equals(encoder.encodeQueueSize, 0); 220 221 let config = defaultConfig; 222 223 encoder.configure(config); 224 225 let frame1 = createFrame(640, 480, 0); 226 let frame2 = createFrame(640, 480, 33333); 227 228 encoder.encode(frame1); 229 encoder.configure(config); 230 231 encoder.encode(frame2); 232 233 await encoder.flush(); 234 235 // We can guarantee that all encodes are processed after a flush. 236 assert_equals(encoder.encodeQueueSize, 0, "queue size after encode"); 237 238 assert_equals(output_chunks.length, 2, "number of chunks"); 239 assert_equals(output_chunks[0].timestamp, frame1.timestamp); 240 assert_equals(output_chunks[1].timestamp, frame2.timestamp); 241 242 output_chunks = []; 243 244 let frame3 = createFrame(640, 480, 66666); 245 246 encoder.encode(frame3); 247 248 let badConfig = { ...defaultConfig }; 249 badConfig.codec = ''; 250 assert_throws_js(TypeError, () => encoder.configure(badConfig)); 251 252 badConfig.codec = 'bogus'; 253 encoder.configure(badConfig); 254 let e = await error; 255 assert_true(e instanceof DOMException); 256 assert_equals(e.name, 'NotSupportedError'); 257 assert_equals(encoder.state, 'closed', 'state'); 258 259 // We may or may not have received frame3 before closing. 260 }, 'Test successful encode() after re-configure().'); 261 262 promise_test(async t => { 263 let encoder = new VideoEncoder(getDefaultCodecInit(t)); 264 265 let frame = createFrame(640, 480, 0); 266 267 return testClosedCodec(t, encoder, defaultConfig, frame); 268 }, 'Verify closed VideoEncoder operations'); 269 270 promise_test(async t => { 271 let encoder = new VideoEncoder(getDefaultCodecInit(t)); 272 273 let frame = createFrame(640, 480, 0); 274 275 return testUnconfiguredCodec(t, encoder, frame); 276 }, 'Verify unconfigured VideoEncoder operations'); 277 278 promise_test(async t => { 279 let encoder = new VideoEncoder(getDefaultCodecInit(t)); 280 281 let frame = createFrame(640, 480, 0); 282 frame.close(); 283 284 encoder.configure(defaultConfig); 285 286 assert_throws_js(TypeError, () => { 287 encoder.encode(frame); 288 }); 289 }, 'Verify encoding closed frames throws.'); 290 291 promise_test(async t => { 292 let output_chunks = []; 293 let codecInit = getDefaultCodecInit(t); 294 codecInit.output = chunk => output_chunks.push(chunk); 295 296 let encoder = new VideoEncoder(codecInit); 297 let config = defaultConfig; 298 encoder.configure(config); 299 300 let frame = createFrame(640, 480, -10000); 301 encoder.encode(frame); 302 frame.close(); 303 await encoder.flush(); 304 encoder.close(); 305 assert_equals(output_chunks.length, 1); 306 assert_equals(output_chunks[0].timestamp, -10000, "first chunk timestamp"); 307 assert_greater_than(output_chunks[0].byteLength, 0); 308 }, 'Encode video with negative timestamp');