videoDecoder-codec-specific.https.any.js (15507B)
1 // META: global=window,dedicatedworker 2 // META: script=videoDecoder-codec-specific-setup.js 3 // META: variant=?av1 4 // META: variant=?vp8 5 // META: variant=?vp9 6 // META: variant=?h264_avc 7 // META: variant=?h264_annexb 8 // META: variant=?h265_hevc 9 // META: variant=?h265_annexb 10 11 promise_test(async t => { 12 await checkImplements(); 13 const support = await VideoDecoder.isConfigSupported(CONFIG); 14 assert_true(support.supported, 'supported'); 15 }, 'Test isConfigSupported()'); 16 17 promise_test(async t => { 18 await checkImplements(); 19 // TODO(sandersd): Create a 1080p `description` for H.264 in AVC format. 20 // This version is testing only the H.264 Annex B path. 21 const config = { 22 codec: CONFIG.codec, 23 codedWidth: 1920, 24 codedHeight: 1088, 25 displayAspectWidth: 1920, 26 displayAspectHeight: 1080, 27 }; 28 29 const support = await VideoDecoder.isConfigSupported(config); 30 assert_true(support.supported, 'supported'); 31 }, 'Test isConfigSupported() with 1080p crop'); 32 33 promise_test(async t => { 34 await checkImplements(); 35 // Define a valid config that includes a hypothetical `futureConfigFeature`, 36 // which is not yet recognized by the User Agent. 37 const config = { 38 ...CONFIG, 39 colorSpace: {primaries: 'bt709'}, 40 futureConfigFeature: 'foo', 41 }; 42 43 // The UA will evaluate validConfig as being "valid", ignoring the 44 // `futureConfigFeature` it doesn't recognize. 45 const support = await VideoDecoder.isConfigSupported(config); 46 assert_true(support.supported, 'supported'); 47 assert_equals(support.config.codec, config.codec, 'codec'); 48 assert_equals(support.config.codedWidth, config.codedWidth, 'codedWidth'); 49 assert_equals(support.config.codedHeight, config.codedHeight, 'codedHeight'); 50 assert_equals(support.config.displayAspectWidth, config.displayAspectWidth, 'displayAspectWidth'); 51 assert_equals(support.config.displayAspectHeight, config.displayAspectHeight, 'displayAspectHeight'); 52 assert_equals(support.config.colorSpace.primaries, config.colorSpace.primaries, 'color primaries'); 53 assert_equals(support.config.colorSpace.transfer, null, 'color transfer'); 54 assert_equals(support.config.colorSpace.matrix, null, 'color matrix'); 55 assert_equals(support.config.colorSpace.fullRange, null, 'color range'); 56 assert_false(support.config.hasOwnProperty('futureConfigFeature'), 'futureConfigFeature'); 57 58 if (config.description) { 59 // The description must be copied. 60 assert_false( 61 support.config.description === config.description, 62 'description is unique'); 63 assert_array_equals( 64 new Uint8Array(support.config.description, 0), 65 new Uint8Array(config.description, 0), 'description'); 66 } else { 67 assert_false(support.config.hasOwnProperty('description'), 'description'); 68 } 69 }, 'Test that isConfigSupported() returns a parsed configuration'); 70 71 promise_test(async t => { 72 await checkImplements(); 73 async function test(t, config, description) { 74 await promise_rejects_js( 75 t, TypeError, VideoDecoder.isConfigSupported(config), description); 76 77 const decoder = createVideoDecoder(t); 78 assert_throws_js(TypeError, () => decoder.configure(config), description); 79 assert_equals(decoder.state, 'unconfigured', 'state'); 80 } 81 82 await test(t, {...CONFIG, codedWidth: 0}, 'invalid codedWidth'); 83 await test(t, {...CONFIG, displayAspectWidth: 0}, 'invalid displayAspectWidth'); 84 }, 'Test invalid configs'); 85 86 promise_test(async t => { 87 await checkImplements(); 88 const decoder = createVideoDecoder(t); 89 decoder.configure(CONFIG); 90 assert_equals(decoder.state, 'configured', 'state'); 91 }, 'Test configure()'); 92 93 promise_test(async t => { 94 await checkImplements(); 95 const callbacks = {}; 96 const decoder = createVideoDecoder(t, callbacks); 97 decoder.configure(CONFIG); 98 decoder.decode(CHUNKS[0]); 99 100 let outputs = 0; 101 callbacks.output = frame => { 102 outputs++; 103 assert_equals(frame.timestamp, CHUNKS[0].timestamp, 'timestamp'); 104 assert_equals(frame.duration, CHUNKS[0].duration, 'duration'); 105 frame.close(); 106 }; 107 108 await decoder.flush(); 109 assert_equals(outputs, 1, 'outputs'); 110 }, 'Decode a key frame'); 111 112 promise_test(async t => { 113 await checkImplements(); 114 const callbacks = {}; 115 const decoder = createVideoDecoder(t, callbacks); 116 decoder.configure(CONFIG); 117 118 // Ensure type value is verified. 119 assert_equals(CHUNKS[1].type, 'delta'); 120 assert_throws_dom('DataError', () => decoder.decode(CHUNKS[1], 'decode')); 121 }, 'Decode a non key frame first fails'); 122 123 promise_test(async t => { 124 await checkImplements(); 125 const callbacks = {}; 126 const decoder = createVideoDecoder(t, callbacks); 127 decoder.configure(CONFIG); 128 129 // Mark a keyframe chunk as a delta chunk to ensure any packet analysis 130 // doesn't override the user provided type. 131 let mismarked_chunk = new EncodedVideoChunk( 132 {type: 'delta', timestamp: 0, duration: 1, data: CHUNK_DATA[0]}); 133 134 assert_throws_dom( 135 'DataError', () => decoder.decode(mismarked_chunk, 'decode')); 136 }, 'Decode a key frame marked as delta fails'); 137 138 promise_test(async t => { 139 await checkImplements(); 140 const callbacks = {}; 141 const decoder = createVideoDecoder(t, callbacks); 142 decoder.configure(CONFIG); 143 for (let i = 0; i < 16; i++) { 144 decoder.decode(new EncodedVideoChunk( 145 {type: 'key', timestamp: 0, data: CHUNK_DATA[0]})); 146 } 147 assert_greater_than(decoder.decodeQueueSize, 0); 148 149 // Wait for the first output, then reset the decoder. 150 let outputs = 0; 151 await new Promise(resolve => { 152 callbacks.output = frame => { 153 outputs++; 154 assert_equals(outputs, 1, 'outputs'); 155 assert_equals(frame.timestamp, 0, 'timestamp'); 156 frame.close(); 157 decoder.reset(); 158 assert_equals(decoder.decodeQueueSize, 0, 'decodeQueueSize'); 159 resolve(); 160 }; 161 }); 162 163 decoder.configure(CONFIG); 164 for (let i = 0; i < 4; i++) { 165 decoder.decode(new EncodedVideoChunk( 166 {type: 'key', timestamp: 1, data: CHUNK_DATA[0]})); 167 } 168 169 // Expect future outputs to come from after the reset. 170 callbacks.output = frame => { 171 outputs++; 172 assert_equals(frame.timestamp, 1, 'timestamp'); 173 frame.close(); 174 }; 175 176 await decoder.flush(); 177 assert_equals(outputs, 5); 178 assert_equals(decoder.decodeQueueSize, 0); 179 }, 'Verify reset() suppresses outputs'); 180 181 promise_test(async t => { 182 await checkImplements(); 183 const decoder = createVideoDecoder(t); 184 assert_equals(decoder.state, 'unconfigured'); 185 186 decoder.reset(); 187 assert_equals(decoder.state, 'unconfigured'); 188 assert_throws_dom( 189 'InvalidStateError', () => decoder.decode(CHUNKS[0]), 'decode'); 190 await promise_rejects_dom(t, 'InvalidStateError', decoder.flush(), 'flush'); 191 }, 'Test unconfigured VideoDecoder operations'); 192 193 promise_test(async t => { 194 await checkImplements(); 195 const decoder = createVideoDecoder(t); 196 decoder.close(); 197 assert_equals(decoder.state, 'closed'); 198 assert_throws_dom( 199 'InvalidStateError', () => decoder.configure(CONFIG), 'configure'); 200 assert_throws_dom('InvalidStateError', () => decoder.reset(), 'reset'); 201 assert_throws_dom('InvalidStateError', () => decoder.close(), 'close'); 202 assert_throws_dom( 203 'InvalidStateError', () => decoder.decode(CHUNKS[0]), 'decode'); 204 await promise_rejects_dom(t, 'InvalidStateError', decoder.flush(), 'flush'); 205 }, 'Test closed VideoDecoder operations'); 206 207 promise_test(async t => { 208 await checkImplements(); 209 const callbacks = {}; 210 211 let errors = 0; 212 let gotError = new Promise(resolve => callbacks.error = e => { 213 errors++; 214 resolve(e); 215 }); 216 callbacks.output = frame => { frame.close(); }; 217 218 const decoder = createVideoDecoder(t, callbacks); 219 decoder.configure(CONFIG); 220 decoder.decode(CHUNKS[0]); // Decode keyframe first. 221 decoder.decode(new EncodedVideoChunk( 222 {type: 'key', timestamp: 1, data: new ArrayBuffer(0)})); 223 224 await promise_rejects_dom(t, "EncodingError", 225 decoder.flush().catch((e) => { 226 assert_equals(errors, 1); 227 throw e; 228 }) 229 ); 230 231 let e = await gotError; 232 assert_true(e instanceof DOMException); 233 assert_equals(e.name, 'EncodingError'); 234 assert_equals(decoder.state, 'closed', 'state'); 235 }, 'Decode empty frame'); 236 237 238 promise_test(async t => { 239 await checkImplements(); 240 const callbacks = {}; 241 242 let errors = 0; 243 let gotError = new Promise(resolve => callbacks.error = e => { 244 errors++; 245 resolve(e); 246 }); 247 248 let outputs = 0; 249 callbacks.output = frame => { 250 outputs++; 251 frame.close(); 252 }; 253 254 const decoder = createVideoDecoder(t, callbacks); 255 decoder.configure(CONFIG); 256 decoder.decode(CHUNKS[0]); // Decode keyframe first. 257 decoder.decode(createCorruptChunk(2)); 258 259 await promise_rejects_dom(t, "EncodingError", 260 decoder.flush().catch((e) => { 261 assert_equals(errors, 1); 262 throw e; 263 }) 264 ); 265 266 assert_less_than_equal(outputs, 1); 267 let e = await gotError; 268 assert_true(e instanceof DOMException); 269 assert_equals(e.name, 'EncodingError'); 270 assert_equals(decoder.state, 'closed', 'state'); 271 }, 'Decode corrupt frame'); 272 273 promise_test(async t => { 274 await checkImplements(); 275 const decoder = createVideoDecoder(t); 276 277 decoder.configure(CONFIG); 278 decoder.decode(CHUNKS[0]); // Decode keyframe first. 279 decoder.decode(createCorruptChunk(1)); 280 281 let flushDone = decoder.flush(); 282 decoder.close(); 283 284 // Flush should have been synchronously rejected, with no output() or error() 285 // callbacks. 286 await promise_rejects_dom(t, 'AbortError', flushDone); 287 }, 'Close while decoding corrupt frame'); 288 289 promise_test(async t => { 290 await checkImplements(); 291 const callbacks = {}; 292 const decoder = createVideoDecoder(t, callbacks); 293 294 decoder.configure(CONFIG); 295 decoder.decode(CHUNKS[0]); 296 297 let outputs = 0; 298 callbacks.output = frame => { 299 outputs++; 300 frame.close(); 301 }; 302 303 await decoder.flush(); 304 assert_equals(outputs, 1, 'outputs'); 305 306 decoder.decode(CHUNKS[0]); 307 await decoder.flush(); 308 assert_equals(outputs, 2, 'outputs'); 309 }, 'Test decoding after flush'); 310 311 promise_test(async t => { 312 await checkImplements(); 313 const callbacks = {}; 314 const decoder = createVideoDecoder(t, callbacks); 315 316 decoder.configure(CONFIG); 317 decoder.decode(new EncodedVideoChunk( 318 {type: 'key', timestamp: -42, data: CHUNK_DATA[0]})); 319 320 let outputs = 0; 321 callbacks.output = frame => { 322 outputs++; 323 assert_equals(frame.timestamp, -42, 'timestamp'); 324 frame.close(); 325 }; 326 327 await decoder.flush(); 328 assert_equals(outputs, 1, 'outputs'); 329 }, 'Test decoding a with negative timestamp'); 330 331 promise_test(async t => { 332 await checkImplements(); 333 const callbacks = {}; 334 const decoder = createVideoDecoder(t, callbacks); 335 336 decoder.configure(CONFIG); 337 decoder.decode(CHUNKS[0]); 338 decoder.decode(CHUNKS[1]); 339 const flushDone = decoder.flush(); 340 341 // Wait for the first output, then reset. 342 let outputs = 0; 343 await new Promise(resolve => { 344 callbacks.output = frame => { 345 outputs++; 346 assert_equals(outputs, 1, 'outputs'); 347 decoder.reset(); 348 frame.close(); 349 resolve(); 350 }; 351 }); 352 353 // Flush should have been synchronously rejected. 354 await promise_rejects_dom(t, 'AbortError', flushDone); 355 356 assert_equals(outputs, 1, 'outputs'); 357 }, 'Test reset during flush'); 358 359 promise_test(async t => { 360 await checkImplements(); 361 const callbacks = {}; 362 const decoder = createVideoDecoder(t, callbacks); 363 364 decoder.configure(CONFIG); 365 decoder.decode(CHUNKS[0]); 366 const flushDone = decoder.flush(); 367 368 let flushDoneInCallback; 369 let outputs = 0; 370 await new Promise(resolve => { 371 callbacks.output = frame => { 372 decoder.reset(); 373 frame.close(); 374 375 callbacks.output = frame => { 376 outputs++; 377 frame.close(); 378 }; 379 callbacks.error = e => { 380 t.unreached_func('unexpected error()'); 381 }; 382 decoder.configure(CONFIG); 383 decoder.decode(CHUNKS[0]); 384 flushDoneInCallback = decoder.flush(); 385 386 resolve(); 387 }; 388 }); 389 390 // First flush should have been synchronously rejected. 391 await promise_rejects_dom(t, 'AbortError', flushDone); 392 // Wait for the second flush and check the output count. 393 await flushDoneInCallback; 394 assert_equals(outputs, 1, 'outputs'); 395 }, 'Test new flush after reset in a flush callback'); 396 397 promise_test(async t => { 398 await checkImplements(); 399 const callbacks = {}; 400 const decoder = createVideoDecoder(t, callbacks); 401 402 decoder.configure(CONFIG); 403 decoder.decode(CHUNKS[0]); 404 const flushDone = decoder.flush(); 405 let flushDoneInCallback; 406 407 await new Promise(resolve => { 408 callbacks.output = frame => { 409 decoder.reset(); 410 frame.close(); 411 412 callbacks.output = frame => { frame.close(); }; 413 decoder.configure(CONFIG); 414 decoder.decode(CHUNKS[0]); 415 decoder.decode(createCorruptChunk(1)); 416 flushDoneInCallback = decoder.flush(); 417 418 resolve(); 419 }; 420 }); 421 422 // First flush should have been synchronously rejected. 423 await promise_rejects_dom(t, 'AbortError', flushDone); 424 // Wait for the second flush and check the error in the rejected promise. 425 await promise_rejects_dom(t, 'EncodingError', flushDoneInCallback); 426 }, 'Test decoding a corrupt frame after reset in a flush callback'); 427 428 promise_test(async t => { 429 await checkImplements(); 430 const callbacks = {}; 431 const decoder = createVideoDecoder(t, callbacks); 432 433 decoder.configure({...CONFIG, optimizeForLatency: true}); 434 decoder.decode(CHUNKS[0]); 435 436 // The frame should be output without flushing. 437 await new Promise(resolve => { 438 callbacks.output = frame => { 439 frame.close(); 440 resolve(); 441 }; 442 }); 443 }, 'Test low-latency decoding'); 444 445 promise_test(async t => { 446 await checkImplements(); 447 const callbacks = {}; 448 callbacks.output = frame => { frame.close(); }; 449 const decoder = createVideoDecoder(t, callbacks); 450 451 // No decodes yet. 452 assert_equals(decoder.decodeQueueSize, 0); 453 454 decoder.configure(CONFIG); 455 456 // Still no decodes. 457 assert_equals(decoder.decodeQueueSize, 0); 458 459 let lastDequeueSize = Infinity; 460 decoder.ondequeue = () => { 461 assert_greater_than(lastDequeueSize, 0, "Dequeue event after queue empty"); 462 assert_greater_than(lastDequeueSize, decoder.decodeQueueSize, 463 "Dequeue event without decreased queue size"); 464 lastDequeueSize = decoder.decodeQueueSize; 465 }; 466 467 for (let chunk of CHUNKS) 468 decoder.decode(chunk); 469 470 assert_greater_than_equal(decoder.decodeQueueSize, 0); 471 assert_less_than_equal(decoder.decodeQueueSize, CHUNKS.length); 472 473 await decoder.flush(); 474 // We can guarantee that all decodes are processed after a flush. 475 assert_equals(decoder.decodeQueueSize, 0); 476 // Last dequeue event should fire when the queue is empty. 477 assert_equals(lastDequeueSize, 0); 478 479 // Reset this to Infinity to track the decline of queue size for this next 480 // batch of decodes. 481 lastDequeueSize = Infinity; 482 483 for (let chunk of CHUNKS) 484 decoder.decode(chunk); 485 486 assert_greater_than_equal(decoder.decodeQueueSize, 0); 487 decoder.reset(); 488 assert_equals(decoder.decodeQueueSize, 0); 489 }, 'VideoDecoder decodeQueueSize test'); 490 491 promise_test(async t => { 492 await checkImplements(); 493 const callbacks = {}; 494 const decoder = createVideoDecoder(t, callbacks); 495 decoder.configure(CONFIG); 496 decoder.reset(); 497 decoder.configure(CONFIG); 498 decoder.decode(CHUNKS[0]); 499 500 let outputs = 0; 501 callbacks.output = frame => { 502 outputs++; 503 assert_equals(frame.timestamp, CHUNKS[0].timestamp, 'timestamp'); 504 assert_equals(frame.duration, CHUNKS[0].duration, 'duration'); 505 frame.close(); 506 }; 507 508 await decoder.flush(); 509 assert_equals(outputs, 1, 'outputs'); 510 }, 'Test configure, reset, configure does not stall');