diff options
Diffstat (limited to 'testing/web-platform/tests/webrtc-extensions/RTCRtpParameters-codec.html')
-rw-r--r-- | testing/web-platform/tests/webrtc-extensions/RTCRtpParameters-codec.html | 611 |
1 files changed, 611 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webrtc-extensions/RTCRtpParameters-codec.html b/testing/web-platform/tests/webrtc-extensions/RTCRtpParameters-codec.html new file mode 100644 index 0000000000..5fc1401bad --- /dev/null +++ b/testing/web-platform/tests/webrtc-extensions/RTCRtpParameters-codec.html @@ -0,0 +1,611 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>RTCRtpEncodingParameters codec property</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../webrtc/RTCPeerConnection-helper.js"></script> +<script src="../webrtc/third_party/sdp/sdp.js"></script> +<script src="../webrtc/simulcast/simulcast.js"></script> +<script> + 'use strict'; + + function arrayEquals(a, b) { + return Array.isArray(a) && Array.isArray(b) && + a.length === b.length && + a.every((val, i) => val === b[i]); + } + + async function sleep(timeout) { + return new Promise(resolve => { + step_timeout(() => { + resolve(); + }, timeout); + }); + } + + function findFirstCodec(name) { + return RTCRtpReceiver.getCapabilities(name.split('/')[0]).codecs.filter(c => c.mimeType.localeCompare(name, undefined, { sensitivity: 'base' }) === 0)[0]; + } + + function codecsNotMatching(mimeType) { + return RTCRtpReceiver.getCapabilities(mimeType.split('/')[0]).codecs.filter(c => c.mimeType.localeCompare(mimeType, undefined, {sensitivity: 'base'}) !== 0); + } + + function assertCodecEquals(a, b) { + assert_equals(a.mimeType, b.mimeType); + assert_equals(a.clockRate, b.clockRate); + assert_equals(a.channels, b.channels); + assert_equals(a.sdpFmtpLine, b.sdpFmtpLine); + } + + async function codecsForSender(sender) { + const rids = sender.getParameters().encodings.map(e => e.rid); + const stats = await sender.getStats(); + const codecs = [...stats] + .filter(([k, v]) => v.type === 'outbound-rtp') + .sort(([k, v], [k2, v2]) => rids.indexOf(v.rid) - rids.indexOf(v2.rid)) + .map(([k, v]) => stats.get(v.codecId).mimeType); + return codecs; + } + + async function waitForAllLayers(t, sender) { + const encodings_count = sender.getParameters().encodings.length; + return step_wait_async(t, async () => { + const stats = await sender.getStats(); + return [...stats] + .filter(([k, v]) => v.type === 'outbound-rtp').length == encodings_count; + }, `Wait for ${encodings_count} layers to start`); + } + + function step_wait_async(t, cond, description, timeout=3000, interval=100) { + return new Promise(resolve => { + var timeout_full = timeout * t.timeout_multiplier; + var remaining = Math.ceil(timeout_full / interval); + + var wait_for_inner = t.step_func(async () => { + if (await cond()) { + resolve(); + } else { + if(remaining === 0) { + assert(false, "step_wait_async", description, + "Timed out waiting on condition"); + } + remaining--; + await sleep(interval); + wait_for_inner(); + } + }); + + wait_for_inner(); + }); + } + + promise_test(async t => { + const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); + + const { sender } = pc.addTransceiver('audio'); + + let param = sender.getParameters(); + let encoding = param.encodings[0]; + + assert_equals(encoding.codec, undefined); + }, `Codec should be undefined by default on audio encodings`); + + promise_test(async t => { + const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); + + const { sender } = pc.addTransceiver('video'); + + let param = sender.getParameters(); + let encoding = param.encodings[0]; + + assert_equals(encoding.codec, undefined); + }, `Codec should be undefined by default on video encodings`); + + promise_test(async t => { + const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); + + const opus = findFirstCodec('audio/opus'); + + const { sender } = pc.addTransceiver('audio', { + sendEncodings: [{codec: opus}], + }); + + let param = sender.getParameters(); + let encoding = param.encodings[0]; + + assertCodecEquals(opus, encoding.codec); + }, `Creating an audio sender with addTransceiver and codec should work`); + + promise_test(async t => { + const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); + + const vp8 = findFirstCodec('video/VP8'); + + const { sender } = pc.addTransceiver('video', { + sendEncodings: [{codec: vp8}], + }); + + let param = sender.getParameters(); + let encoding = param.encodings[0]; + + assertCodecEquals(vp8, encoding.codec); + }, `Creating a video sender with addTransceiver and codec should work`); + + promise_test(async t => { + const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); + + const opus = findFirstCodec('audio/opus'); + + const { sender } = pc.addTransceiver('audio'); + + let param = sender.getParameters(); + let encoding = param.encodings[0]; + + encoding.codec = opus; + await sender.setParameters(param); + param = sender.getParameters(); + encoding = param.encodings[0]; + + assertCodecEquals(opus, encoding.codec); + + delete encoding.codec; + await sender.setParameters(param); + param = sender.getParameters(); + encoding = param.encodings[0]; + + assert_equals(encoding.codec, undefined); + }, `Setting codec on an audio sender with setParameters should work`); + + promise_test(async t => { + const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); + + const vp8 = findFirstCodec('video/VP8'); + + const { sender } = pc.addTransceiver('video'); + + let param = sender.getParameters(); + let encoding = param.encodings[0]; + + encoding.codec = vp8; + await sender.setParameters(param); + param = sender.getParameters(); + encoding = param.encodings[0]; + + assertCodecEquals(vp8, encoding.codec); + + delete encoding.codec; + await sender.setParameters(param); + param = sender.getParameters(); + encoding = param.encodings[0]; + + assert_equals(encoding.codec, undefined); + }, `Setting codec on a video sender with setParameters should work`); + + promise_test(async t => { + const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); + + const newCodec = { + mimeType: "audio/newCodec", + clockRate: 90000, + channel: 2, + }; + + assert_throws_dom('OperationError', () => pc.addTransceiver('audio', { + sendEncodings: [{codec: newCodec}], + })); + }, `Creating an audio sender with addTransceiver and non-existing codec should throw OperationError`); + + promise_test(async t => { + const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); + + const newCodec = { + mimeType: "dummy/newCodec", + clockRate: 90000, + channel: 2, + }; + + assert_throws_dom('OperationError', () => pc.addTransceiver('audio', { + sendEncodings: [{codec: newCodec}], + })); + }, `Creating an audio sender with addTransceiver and non-existing codec type should throw OperationError`); + + promise_test(async t => { + const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); + + const newCodec = { + mimeType: "video/newCodec", + clockRate: 90000, + }; + + assert_throws_dom('OperationError', () => pc.addTransceiver('video', { + sendEncodings: [{codec: newCodec}], + })); + }, `Creating a video sender with addTransceiver and non-existing codec should throw OperationError`); + + promise_test(async t => { + const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); + + const newCodec = { + mimeType: "dummy/newCodec", + clockRate: 90000, + }; + + assert_throws_dom('OperationError', () => pc.addTransceiver('video', { + sendEncodings: [{codec: newCodec}], + })); + }, `Creating a video sender with addTransceiver and non-existing codec type should throw OperationError`); + + promise_test(async t => { + const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); + + const newCodec = { + mimeType: "audio/newCodec", + clockRate: 90000, + channel: 2, + }; + + const { sender } = pc.addTransceiver('audio'); + + let param = sender.getParameters(); + let encoding = param.encodings[0]; + + encoding.codec = newCodec; + await promise_rejects_dom(t, "InvalidModificationError", sender.setParameters(param)); + }, `Setting a non-existing codec on an audio sender with setParameters should throw InvalidModificationError`); + + promise_test(async t => { + const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); + + const newCodec = { + mimeType: "video/newCodec", + clockRate: 90000, + }; + + const { sender } = pc.addTransceiver('video'); + + let param = sender.getParameters(); + let encoding = param.encodings[0]; + + encoding.codec = newCodec; + await promise_rejects_dom(t, "InvalidModificationError", sender.setParameters(param)); + }, `Setting a non-existing codec on a video sender with setParameters should throw InvalidModificationError`); + + promise_test(async t => { + const pc1 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + const pc2 = new RTCPeerConnection(); + t.add_cleanup(() => pc2.close()); + + const opus = findFirstCodec('audio/opus'); + const nonOpus = codecsNotMatching(opus.mimeType); + pc2.ontrack = e => { + e.transceiver.setCodecPreferences(nonOpus); + }; + + const transceiver = pc1.addTransceiver('audio'); + exchangeIceCandidates(pc1, pc2); + await exchangeOfferAnswer(pc1, pc2); + + const sender = transceiver.sender; + let param = sender.getParameters(); + let encoding = param.encodings[0]; + + encoding.codec = opus; + await promise_rejects_dom(t, "InvalidModificationError", sender.setParameters(param)); + }, `Setting a non-preferred codec on an audio sender with setParameters should throw InvalidModificationError`); + + promise_test(async t => { + const pc1 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + const pc2 = new RTCPeerConnection(); + t.add_cleanup(() => pc2.close()); + + const vp8 = findFirstCodec('video/VP8'); + const nonVP8 = codecsNotMatching(vp8.mimeType); + pc2.ontrack = e => { + e.transceiver.setCodecPreferences(nonVP8); + }; + + const transceiver = pc1.addTransceiver('video'); + exchangeIceCandidates(pc1, pc2); + await exchangeOfferAnswer(pc1, pc2); + + const sender = transceiver.sender; + let param = sender.getParameters(); + let encoding = param.encodings[0]; + + encoding.codec = vp8; + await promise_rejects_dom(t, "InvalidModificationError", sender.setParameters(param)); + }, `Setting a non-preferred codec on a video sender with setParameters should throw InvalidModificationError`); + + promise_test(async (t) => { + const pc1 = new RTCPeerConnection(); + const pc2 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + t.add_cleanup(() => pc2.close()); + + const opus = findFirstCodec('audio/opus'); + const nonOpus = codecsNotMatching(opus.mimeType); + pc2.ontrack = e => { + e.transceiver.setCodecPreferences(nonOpus); + }; + + const transceiver = pc1.addTransceiver('audio'); + + exchangeIceCandidates(pc1, pc2); + await exchangeOfferAnswer(pc1, pc2); + + const sender = transceiver.sender; + let param = sender.getParameters(); + let encoding = param.encodings[0]; + + encoding.codec = opus; + await promise_rejects_dom(t, "InvalidModificationError", sender.setParameters(param)); + }, `Setting a non-negotiated codec on an audio sender with setParameters should throw InvalidModificationError`); + + promise_test(async (t) => { + const pc1 = new RTCPeerConnection(); + const pc2 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + t.add_cleanup(() => pc2.close()); + + const vp8 = findFirstCodec('video/VP8'); + const nonVP8 = codecsNotMatching(vp8.mimeType); + pc2.ontrack = e => { + e.transceiver.setCodecPreferences(nonVP8); + }; + + const transceiver = pc1.addTransceiver('video'); + exchangeIceCandidates(pc1, pc2); + await exchangeOfferAnswer(pc1, pc2); + + const sender = transceiver.sender; + let param = sender.getParameters(); + let encoding = param.encodings[0]; + + encoding.codec = vp8; + await promise_rejects_dom(t, "InvalidModificationError", sender.setParameters(param)); + }, `Setting a non-negotiated codec on a video sender with setParameters should throw InvalidModificationError`); + + promise_test(async (t) => { + const pc1 = new RTCPeerConnection(); + const pc2 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + t.add_cleanup(() => pc2.close()); + + const opus = findFirstCodec('audio/opus'); + const nonOpus = codecsNotMatching(opus.mimeType); + + const transceiver = pc1.addTransceiver('audio', { + sendEncodings: [{codec: opus}], + }); + const sender = transceiver.sender; + + exchangeIceCandidates(pc1, pc2); + await exchangeOfferAnswer(pc1, pc2); + + let param = sender.getParameters(); + let encoding = param.encodings[0]; + + assertCodecEquals(opus, encoding.codec); + + pc2.getTransceivers()[0].setCodecPreferences(nonOpus); + await exchangeOfferAnswer(pc1, pc2); + + param = sender.getParameters(); + encoding = param.encodings[0]; + + assert_equals(encoding.codec, undefined); + }, `Codec should be undefined after negotiating away the currently set codec on an audio sender`); + promise_test(async (t) => { + const pc1 = new RTCPeerConnection(); + const pc2 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + t.add_cleanup(() => pc2.close()); + + const vp8 = findFirstCodec('video/VP8'); + const nonVP8 = codecsNotMatching(vp8.mimeType); + + const transceiver = pc1.addTransceiver('video', { + sendEncodings: [{codec: vp8}], + }); + const sender = transceiver.sender; + + exchangeIceCandidates(pc1, pc2); + await exchangeOfferAnswer(pc1, pc2); + + let param = sender.getParameters(); + let encoding = param.encodings[0]; + + assertCodecEquals(vp8, encoding.codec); + + pc2.getTransceivers()[0].setCodecPreferences(nonVP8); + await exchangeOfferAnswer(pc1, pc2); + + param = sender.getParameters(); + encoding = param.encodings[0]; + + assert_equals(encoding.codec, undefined); + }, `Codec should be undefined after negotiating away the currently set codec on a video sender`); + + promise_test(async (t) => { + const pc1 = new RTCPeerConnection(); + const pc2 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + t.add_cleanup(() => pc2.close()); + const stream = await getNoiseStream({audio:true}); + t.add_cleanup(() => stream.getTracks().forEach(track => track.stop())); + + const opus = findFirstCodec('audio/opus'); + const nonOpus = codecsNotMatching(opus.mimeType); + pc2.ontrack = e => { + e.transceiver.setCodecPreferences(nonOpus.concat([opus])); + }; + + const transceiver = pc1.addTransceiver(stream.getTracks()[0]); + const sender = transceiver.sender; + + exchangeIceCandidates(pc1, pc2); + await exchangeOfferAnswer(pc1, pc2); + + let codecs = await codecsForSender(sender); + assert_not_equals(codecs[0], opus.mimeType); + + let param = sender.getParameters(); + let encoding = param.encodings[0]; + encoding.codec = opus; + + await sender.setParameters(param); + + await step_wait_async(t, async () => { + let old_codecs = codecs; + codecs = await codecsForSender(sender); + return !arrayEquals(codecs, old_codecs); + }, 'Waiting for current codecs to change', 5000, 200); + + assert_array_equals(codecs, [opus.mimeType]); + }, `Stats output-rtp should match the selected codec in non-simulcast usecase on an audio sender`); + + promise_test(async (t) => { + const pc1 = new RTCPeerConnection(); + const pc2 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + t.add_cleanup(() => pc2.close()); + const stream = await getNoiseStream({video:true}); + t.add_cleanup(() => stream.getTracks().forEach(track => track.stop())); + + const vp8 = findFirstCodec('video/VP8'); + const nonVP8 = codecsNotMatching(vp8.mimeType); + pc2.ontrack = e => { + e.transceiver.setCodecPreferences(nonVP8.concat([vp8])); + }; + + const transceiver = pc1.addTransceiver(stream.getTracks()[0]); + const sender = transceiver.sender; + + exchangeIceCandidates(pc1, pc2); + await exchangeOfferAnswer(pc1, pc2); + + let codecs = await codecsForSender(sender); + assert_not_equals(codecs[0], vp8.mimeType); + + let param = sender.getParameters(); + let encoding = param.encodings[0]; + encoding.codec = vp8; + + await sender.setParameters(param); + + await step_wait_async(t, async () => { + let old_codecs = codecs; + codecs = await codecsForSender(sender); + return !arrayEquals(codecs, old_codecs); + }, 'Waiting for current codecs to change', 5000, 200); + + assert_array_equals(codecs, [vp8.mimeType]); + }, `Stats output-rtp should match the selected codec in non-simulcast usecase on a video sender`); + + promise_test(async (t) => { + const pc1 = new RTCPeerConnection(); + const pc2 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + t.add_cleanup(() => pc2.close()); + const stream = await getNoiseStream({video:true}); + t.add_cleanup(() => stream.getTracks().forEach(track => track.stop())); + + const vp8 = findFirstCodec('video/VP8'); + const h264 = findFirstCodec('video/H264'); + pc2.ontrack = e => { + e.transceiver.setCodecPreferences([h264, vp8]); + }; + const transceiver = pc1.addTransceiver(stream.getTracks()[0], { + sendEncodings: [{rid: '0'}, {rid: '1'}, {rid: '2'}], + }); + const sender = transceiver.sender; + + exchangeIceCandidates(pc1, pc2); + await doOfferToSendSimulcastAndAnswer(pc1, pc2, ['0', '1', '2']); + + await waitForAllLayers(t, sender); + + let codecs = await codecsForSender(sender); + assert_not_equals(codecs[0], vp8.mimeType); + assert_not_equals(codecs[1], vp8.mimeType); + assert_not_equals(codecs[2], vp8.mimeType); + + let param = sender.getParameters(); + param.encodings[0].codec = vp8; + param.encodings[1].codec = vp8; + param.encodings[2].codec = vp8; + + await sender.setParameters(param); + + // Waiting for 10s as ramp-up time can be slow in the runners. + await step_wait_async(t, async () => { + let old_codecs = codecs; + codecs = await codecsForSender(sender); + return !arrayEquals(codecs, old_codecs); + }, 'Waiting for current codecs to change', 10000, 200); + + assert_array_equals(codecs, [vp8.mimeType, vp8.mimeType, vp8.mimeType]); + }, `Stats output-rtp should match the selected codec in simulcast usecase on a video sender`); + + promise_test(async (t) => { + const pc1 = new RTCPeerConnection(); + const pc2 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + t.add_cleanup(() => pc2.close()); + const stream = await getNoiseStream({video:true}); + t.add_cleanup(() => stream.getTracks().forEach(track => track.stop())); + + const vp8 = findFirstCodec('video/VP8'); + const h264 = findFirstCodec('video/H264'); + pc2.ontrack = e => { + e.transceiver.setCodecPreferences([h264, vp8]); + }; + + const transceiver = pc1.addTransceiver(stream.getTracks()[0], { + sendEncodings: [{rid: '0'}, {rid: '1'}, {rid: '2'}], + }); + const sender = transceiver.sender; + + exchangeIceCandidates(pc1, pc2); + await doOfferToSendSimulcastAndAnswer(pc1, pc2, ['0', '1', '2']); + + await waitForAllLayers(t, sender); + + let codecs = await codecsForSender(sender); + assert_not_equals(codecs[0], vp8.mimeType); + assert_not_equals(codecs[1], vp8.mimeType); + assert_not_equals(codecs[2], vp8.mimeType); + + let param = sender.getParameters(); + param.encodings[1].codec = vp8; + + await sender.setParameters(param); + + await step_wait_async(t, async () => { + let old_codecs = codecs; + codecs = await codecsForSender(sender); + return !arrayEquals(codecs, old_codecs); + }, 'Waiting for current codecs to change', 5000, 200); + + assert_not_equals(codecs[0], vp8.mimeType); + assert_equals(codecs[1], vp8.mimeType); + assert_not_equals(codecs[2], vp8.mimeType); + }, `Stats output-rtp should match the selected mixed codecs in simulcast usecase on a video sender`); + +</script> |