summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webrtc-extensions/RTCRtpParameters-codec.html
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/webrtc-extensions/RTCRtpParameters-codec.html')
-rw-r--r--testing/web-platform/tests/webrtc-extensions/RTCRtpParameters-codec.html611
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>