|
1 | 1 | <!DOCTYPE html> |
2 | 2 | <html> |
3 | 3 | <head> |
4 | | - <title>WebCodecs VP9 HDR decoding</title> |
| 4 | + <title>WebCodecs VP9 HDR decoding with colorSpace overrides</title> |
5 | 5 | <script src="../../resources/testharness.js"></script> |
6 | 6 | <script src="../../resources/testharnessreport.js"></script> |
7 | 7 | </head> |
8 | 8 | <body> |
9 | 9 | <script> |
10 | 10 | const HDR_CODEC = "vp09.02.10.10.01.09.16.09.00"; |
11 | | -promise_test(async () => { |
12 | | - assert_implements(window.VideoDecoder, "VideoDecoder is supported"); |
13 | | - const config = { codec: HDR_CODEC, codedWidth: 320, codedHeight: 240 }; |
14 | | - const support = await VideoDecoder.isConfigSupported(config); |
15 | | - assert_true(support.supported, "VP9 profile 2 (10-bit) is supported"); |
16 | | -}, "VP9 profile 2 isConfigSupported"); |
| 11 | +let vp9Data; |
17 | 12 |
|
18 | 13 | promise_test(async () => { |
| 14 | + // Profile 2, level 1.0, 10-bit, 4:2:0, BT.2020 video range. |
| 15 | + const response = await fetch("vp9-hdr.bin"); |
| 16 | + const buffer = await response.arrayBuffer(); |
| 17 | + // Strip 32-byte IVF file header + 12-byte frame header. |
| 18 | + vp9Data = new Uint8Array(buffer).slice(44); |
| 19 | +}, "Setup"); |
| 20 | + |
| 21 | +async function decodeWithOverride(colorSpace, hardwareAcceleration) { |
19 | 22 | const frames = []; |
20 | 23 | const decoder = new VideoDecoder({ |
21 | | - output: frame => frames.push(frame), |
| 24 | + output: f => frames.push(f), |
22 | 25 | error: e => assert_unreached(`decode error: ${e.message}`), |
23 | 26 | }); |
24 | | - decoder.configure({ |
25 | | - codec: HDR_CODEC, |
26 | | - codedWidth: 320, |
27 | | - codedHeight: 240, |
28 | | - }); |
29 | | - |
30 | | - // Content generated by ffmpeg, we need to strip the IVF header (32 bytes) and frame header (12 bytes). |
31 | | - const response = await fetch("vp9-hdr.bin"); |
32 | | - const buffer = await response.arrayBuffer(); |
33 | | - const vp9Data = new Uint8Array(buffer).slice(44); |
34 | | - decoder.decode(new EncodedVideoChunk({ |
35 | | - type: "key", |
36 | | - timestamp: 0, |
37 | | - data: vp9Data, |
38 | | - })); |
| 27 | + const config = { codec: HDR_CODEC, codedWidth: 320, codedHeight: 240, hardwareAcceleration }; |
| 28 | + if (colorSpace) |
| 29 | + config.colorSpace = colorSpace; |
| 30 | + decoder.configure(config); |
| 31 | + decoder.decode(new EncodedVideoChunk({ type: "key", timestamp: 0, data: vp9Data })); |
39 | 32 | await decoder.flush(); |
40 | | - |
41 | 33 | assert_equals(frames.length, 1, "one frame decoded"); |
42 | | - const frame = frames[0]; |
| 34 | + return frames[0]; |
| 35 | +} |
| 36 | + |
| 37 | +for (const hardwareAcceleration of ["prefer-hardware", "prefer-software"]) { |
| 38 | + const tag = `[${hardwareAcceleration}]`; |
| 39 | + |
| 40 | + promise_test(async () => { |
| 41 | + assert_implements(window.VideoDecoder, "VideoDecoder is supported"); |
| 42 | + const config = { codec: HDR_CODEC, codedWidth: 320, codedHeight: 240, hardwareAcceleration }; |
| 43 | + const support = await VideoDecoder.isConfigSupported(config); |
| 44 | + assert_true(support.supported, "VP9 profile 2 (10-bit) is supported"); |
| 45 | + }, `VP9 profile 2 isConfigSupported ${tag}`); |
| 46 | + |
| 47 | + promise_test(async (t) => { |
| 48 | + const f = await decodeWithOverride(undefined, hardwareAcceleration); |
| 49 | + t.add_cleanup(() => f.close()); |
| 50 | + assert_equals(f.colorSpace.primaries, "bt2020", "primaries from bitstream"); |
| 51 | + assert_equals(f.colorSpace.matrix, "bt2020-ncl", "matrix from bitstream"); |
| 52 | + assert_false(f.colorSpace.fullRange, "video range from bitstream"); |
| 53 | + |
| 54 | + if (f.format !== "I420P10") { |
| 55 | + assert_equals(f.format, null); |
| 56 | + assert_true(!window.internals?.is10bitsVideoFrame || internals.is10bitsVideoFrame(f), "10 bits"); |
| 57 | + } |
| 58 | + }, `VP9 (no override) ${tag}: bitstream colorSpace surfaces`); |
| 59 | + |
| 60 | + promise_test(async (t) => { |
| 61 | + const f = await decodeWithOverride({ primaries: "bt709", matrix: "bt2020-ncl", transfer: "bt709" }, hardwareAcceleration); |
| 62 | + t.add_cleanup(() => f.close()); |
| 63 | + assert_equals(f.colorSpace.primaries, "bt709", "primaries overridden"); |
| 64 | + assert_equals(f.colorSpace.matrix, "bt2020-ncl", "matrix preserved from bitstream"); |
| 65 | + }, `VP9 colorSpace override ${tag}: primaries`); |
| 66 | + |
| 67 | + promise_test(async (t) => { |
| 68 | + const f = await decodeWithOverride({ matrix: "bt709" }, hardwareAcceleration); |
| 69 | + t.add_cleanup(() => f.close()); |
| 70 | + assert_equals(f.colorSpace.primaries, "bt2020", "primaries preserved from bitstream"); |
| 71 | + assert_equals(f.colorSpace.matrix, "bt709", "matrix overridden"); |
| 72 | + }, `VP9 colorSpace override ${tag}: matrix`); |
43 | 73 |
|
44 | | - assert_equals(frame.colorSpace.primaries, "bt2020", "primaries"); |
45 | | - assert_equals(frame.colorSpace.matrix, "bt2020-ncl", "matrix"); |
46 | | - assert_false(frame.colorSpace.fullRange, "video range"); |
| 74 | + promise_test(async (t) => { |
| 75 | + const f = await decodeWithOverride({ transfer: "pq" }, hardwareAcceleration); |
| 76 | + t.add_cleanup(() => f.close()); |
| 77 | + assert_equals(f.colorSpace.primaries, "bt2020", "primaries preserved from bitstream"); |
| 78 | + assert_equals(f.colorSpace.matrix, "bt2020-ncl", "matrix preserved from bitstream"); |
| 79 | + assert_equals(f.colorSpace.transfer, "pq", "transfer overridden to PQ"); |
| 80 | + }, `VP9 colorSpace override ${tag}: transfer (pq)`); |
47 | 81 |
|
48 | | - if (frame.format !== "I420P10") { |
49 | | - assert_equals(frame.format, null); |
50 | | - assert_true(!window.internals?.is10bitsVideoFrame || internals.is10bitsVideoFrame(frame), "10 bits"); |
51 | | - } |
| 82 | + promise_test(async (t) => { |
| 83 | + const f = await decodeWithOverride({ fullRange: true }, hardwareAcceleration); |
| 84 | + t.add_cleanup(() => f.close()); |
| 85 | + assert_equals(f.colorSpace.primaries, "bt2020", "primaries preserved from bitstream"); |
| 86 | + assert_equals(f.colorSpace.matrix, "bt2020-ncl", "matrix preserved from bitstream"); |
| 87 | + assert_true(f.colorSpace.fullRange, "range overridden to full"); |
| 88 | + }, `VP9 colorSpace override ${tag}: fullRange (true)`); |
52 | 89 |
|
53 | | - frame.close(); |
54 | | -}, "VP9 profile 2 decoded with WebCodecs has BT.2020 and is 10 bits"); |
| 90 | + promise_test(async (t) => { |
| 91 | + const f = await decodeWithOverride({ fullRange: false }, hardwareAcceleration); |
| 92 | + t.add_cleanup(() => f.close()); |
| 93 | + assert_equals(f.colorSpace.primaries, "bt2020", "primaries preserved from bitstream"); |
| 94 | + assert_equals(f.colorSpace.matrix, "bt2020-ncl", "matrix preserved from bitstream"); |
| 95 | + assert_false(f.colorSpace.fullRange, "range overridden to video"); |
| 96 | + }, `VP9 colorSpace override ${tag}: fullRange (false)`); |
| 97 | +} |
55 | 98 | </script> |
56 | 99 | </body> |
57 | 100 | </html> |
0 commit comments