| <!DOCTYPE html> | |
| <html> | |
| <title>Test the VideoTrackReader API.</title> | |
| <body> | |
| <img id='frame_image' src="pattern.png"> | |
| </body> | |
| <script src="/resources/testharness.js"></script> | |
| <script src="/resources/testharnessreport.js"></script> | |
| <script src="/common/media.js"></script> | |
| <script src="/webcodecs/utils.js"></script> | |
| <script> | |
| const defaultConfig = { | |
| codec: 'vp8', | |
| framerate: 25, | |
| width: 640, | |
| height: 480 | |
| }; | |
| async function generateBitmap(width, height) { | |
| return createImageBitmap(document.getElementById('frame_image'), | |
| { resizeWidth: width, | |
| resizeHeight: height }); | |
| } | |
| async function createVideoFrame(width, height, timestamp) { | |
| let bitmap = await generateBitmap(width, height); | |
| return new VideoFrame(bitmap, { timestamp: timestamp }); | |
| } | |
| promise_test(t => { | |
| // VideoEncoderInit lacks required fields. | |
| assert_throws_js(TypeError, () => { new VideoEncoder({}); }); | |
| // VideoEncoderInit has required fields. | |
| let encoder = new VideoEncoder(getDefaultCodecInit(t)); | |
| assert_equals(encoder.state, "unconfigured"); | |
| encoder.close(); | |
| return endAfterEventLoopTurn(); | |
| }, 'Test VideoEncoder construction'); | |
| promise_test(t => { | |
| let encoder = new VideoEncoder(getDefaultCodecInit(t)); | |
| let badCodecsList = [ | |
| '', // Empty codec | |
| 'bogus', // Non exsitent codec | |
| 'vorbis', // Audio codec | |
| 'vp9', // Ambiguous codec | |
| 'video/webm; codecs="vp9"' // Codec with mime type | |
| ] | |
| testConfigurations(encoder, defaultConfig, badCodecsList); | |
| return endAfterEventLoopTurn(); | |
| }, 'Test VideoEncoder.configure()'); | |
| promise_test(async t => { | |
| let output_chunks = []; | |
| let codecInit = getDefaultCodecInit(t); | |
| codecInit.output = chunk => output_chunks.push(chunk); | |
| let encoder = new VideoEncoder(codecInit); | |
| // No encodes yet. | |
| assert_equals(encoder.encodeQueueSize, 0); | |
| encoder.configure(defaultConfig); | |
| // Still no encodes. | |
| assert_equals(encoder.encodeQueueSize, 0); | |
| let frame1 = await createVideoFrame(640, 480, 0); | |
| let frame2 = await createVideoFrame(640, 480, 33333); | |
| encoder.encode(frame1.clone()); | |
| encoder.encode(frame2.clone()); | |
| // Could be 0, 1, or 2. We can't guarantee this check runs before the UA has | |
| // processed the encodes. | |
| assert_true(encoder.encodeQueueSize >= 0 && encoder.encodeQueueSize <= 2) | |
| await encoder.flush(); | |
| // We can guarantee that all encodes are processed after a flush. | |
| assert_equals(encoder.encodeQueueSize, 0); | |
| assert_equals(output_chunks.length, 2); | |
| assert_equals(output_chunks[0].timestamp, frame1.timestamp); | |
| assert_equals(output_chunks[1].timestamp, frame2.timestamp); | |
| }, 'Test successful configure(), encode(), and flush()'); | |
| promise_test(async t => { | |
| let output_chunks = []; | |
| let codecInit = getDefaultCodecInit(t); | |
| codecInit.output = chunk => output_chunks.push(chunk); | |
| let encoder = new VideoEncoder(codecInit); | |
| // No encodes yet. | |
| assert_equals(encoder.encodeQueueSize, 0); | |
| let config = defaultConfig; | |
| encoder.configure(config); | |
| let frame1 = await createVideoFrame(640, 480, 0); | |
| let frame2 = await createVideoFrame(640, 480, 33333); | |
| encoder.encode(frame1.clone()); | |
| encoder.configure(config); | |
| encoder.encode(frame2.clone()); | |
| await encoder.flush(); | |
| // We can guarantee that all encodes are processed after a flush. | |
| assert_equals(encoder.encodeQueueSize, 0); | |
| // The first frame may have been dropped when reconfiguring. | |
| // This shouldn't happen, and should be fixed/called out in the spec, but | |
| // this is preptively added to prevent flakiness. | |
| // TODO: Remove these checks when implementations handle this correctly. | |
| assert_true(output_chunks.length == 1 || output_chunks.length == 2); | |
| if (output_chunks.length == 1) { | |
| // If we only have one chunk frame, make sure we droped the frame that was | |
| // in flight when we reconfigured. | |
| assert_equals(output_chunks[0].timestamp, frame2.timestamp); | |
| } else { | |
| assert_equals(output_chunks[0].timestamp, frame1.timestamp); | |
| assert_equals(output_chunks[1].timestamp, frame2.timestamp); | |
| } | |
| output_chunks = []; | |
| let frame3 = await createVideoFrame(640, 480, 66666); | |
| let frame4 = await createVideoFrame(640, 480, 100000); | |
| encoder.encode(frame3.clone()); | |
| // Verify that a failed call to configure does not change the encoder's state. | |
| let badConfig = {...defaultConfig}; | |
| badConfig.codec = 'bogus'; | |
| assert_throws_js(TypeError, () => encoder.configure(badConfig)); | |
| encoder.encode(frame4.clone()); | |
| await encoder.flush(); | |
| assert_equals(output_chunks[0].timestamp, frame3.timestamp); | |
| assert_equals(output_chunks[1].timestamp, frame4.timestamp); | |
| }, 'Test successful encode() after re-configure().'); | |
| promise_test(async t => { | |
| let output_chunks = []; | |
| let codecInit = getDefaultCodecInit(t); | |
| codecInit.output = chunk => output_chunks.push(chunk); | |
| let encoder = new VideoEncoder(codecInit); | |
| let timestamp = 33333; | |
| let frame = await createVideoFrame(640, 480, timestamp); | |
| encoder.configure(defaultConfig); | |
| assert_equals(encoder.state, "configured"); | |
| encoder.encode(frame); | |
| // |frame| is not longer valid since it has been destroyed. | |
| assert_not_equals(frame.timestamp, timestamp); | |
| assert_throws_dom("InvalidStateError", () => frame.clone()); | |
| encoder.close(); | |
| return endAfterEventLoopTurn(); | |
| }, 'Test encoder consumes (destroys) frames.'); | |
| promise_test(async t => { | |
| let encoder = new VideoEncoder(getDefaultCodecInit(t)); | |
| let frame = await createVideoFrame(640, 480, 0); | |
| return testClosedCodec(t, encoder, defaultConfig, frame); | |
| }, 'Verify closed VideoEncoder operations'); | |
| promise_test(async t => { | |
| let encoder = new VideoEncoder(getDefaultCodecInit(t)); | |
| let frame = await createVideoFrame(640, 480, 0); | |
| return testUnconfiguredCodec(t, encoder, frame); | |
| }, 'Verify unconfigured VideoEncoder operations'); | |
| promise_test(async t => { | |
| let encoder = new VideoEncoder(getDefaultCodecInit(t)); | |
| let frame = await createVideoFrame(640, 480, 0); | |
| frame.destroy(); | |
| encoder.configure(defaultConfig); | |
| frame.destroy(); | |
| assert_throws_dom("OperationError", () => { | |
| encoder.encode(frame) | |
| }); | |
| }, 'Verify encoding destroyed frames throws.'); | |
| </script> | |
| </html> |