339 lines
13 KiB
HTML
339 lines
13 KiB
HTML
<!doctype html>
|
|
<meta charset=utf-8>
|
|
<title>RTCRtpTransceiver.prototype.setCodecPreferences</title>
|
|
<script src="/resources/testharness.js"></script>
|
|
<script src="/resources/testharnessreport.js"></script>
|
|
<script src="./third_party/sdp/sdp.js"></script>
|
|
<script>
|
|
'use strict';
|
|
|
|
test((t) => {
|
|
const pc = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc.close());
|
|
const transceiver = pc.addTransceiver('audio');
|
|
const capabilities = RTCRtpReceiver.getCapabilities('audio');
|
|
transceiver.setCodecPreferences(capabilities.codecs);
|
|
}, `setCodecPreferences() on audio transceiver with codecs returned from RTCRtpReceiver.getCapabilities('audio') should succeed`);
|
|
|
|
test((t) => {
|
|
const pc = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc.close());
|
|
const transceiver = pc.addTransceiver('video');
|
|
const capabilities = RTCRtpReceiver.getCapabilities('video');
|
|
transceiver.setCodecPreferences(capabilities.codecs);
|
|
}, `setCodecPreferences() on video transceiver with codecs returned from RTCRtpReceiver.getCapabilities('video') should succeed`);
|
|
|
|
test((t) => {
|
|
const pc = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc.close());
|
|
const transceiver = pc.addTransceiver('audio');
|
|
transceiver.setCodecPreferences([]);
|
|
}, `setCodecPreferences([]) should succeed`);
|
|
|
|
test((t) => {
|
|
const pc = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc.close());
|
|
const transceiver = pc.addTransceiver('audio');
|
|
const capabilities = RTCRtpReceiver.getCapabilities('audio');
|
|
const { codecs } = capabilities;
|
|
|
|
if(codecs.length >= 2) {
|
|
const tmp = codecs[0];
|
|
codecs[0] = codecs[1];
|
|
codecs[1] = tmp;
|
|
}
|
|
|
|
transceiver.setCodecPreferences(codecs);
|
|
}, `setCodecPreferences() with reordered codecs should succeed`);
|
|
|
|
test((t) => {
|
|
const pc = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc.close());
|
|
const transceiver = pc.addTransceiver('video');
|
|
const capabilities = RTCRtpReceiver.getCapabilities('video');
|
|
const { codecs } = capabilities;
|
|
// This test verifies that the mandatory VP8 codec is present
|
|
// and can be preferred.
|
|
const codec = codecs.find(c => c.mimeType === 'video/VP8');
|
|
assert_true(!!codec, 'VP8 video codec was found');
|
|
transceiver.setCodecPreferences([codec]);
|
|
}, `setCodecPreferences() with only VP8 should succeed`);
|
|
|
|
test(() => {
|
|
const pc = new RTCPeerConnection();
|
|
const transceiver = pc.addTransceiver('video');
|
|
const capabilities = RTCRtpReceiver.getCapabilities('video');
|
|
const { codecs } = capabilities;
|
|
// This test verifies that the mandatory H264 codec is present
|
|
// and can be preferred.
|
|
const codec = codecs.find(c => c.mimeType === 'video/H264');
|
|
assert_true(!!codec, 'H264 video codec was found');
|
|
transceiver.setCodecPreferences([codec]);
|
|
}, `setCodecPreferences() with only H264 should succeed`);
|
|
|
|
async function getRTPMapLinesWithCodecAsFirst(firstCodec)
|
|
{
|
|
const codecs = RTCRtpReceiver.getCapabilities('video').codecs;
|
|
codecs.forEach((codec, idx) => {
|
|
if (codec.mimeType === firstCodec) {
|
|
codecs.splice(idx, 1);
|
|
codecs.unshift(codec);
|
|
}
|
|
});
|
|
|
|
const pc = new RTCPeerConnection();
|
|
const transceiver = pc.addTransceiver('video');
|
|
transceiver.setCodecPreferences(codecs);
|
|
const offer = await pc.createOffer();
|
|
|
|
return offer.sdp.split('\r\n').filter(line => line.startsWith('a=rtpmap:'));
|
|
}
|
|
|
|
promise_test(async () => {
|
|
const lines = await getRTPMapLinesWithCodecAsFirst('video/H264');
|
|
|
|
assert_greater_than(lines.length, 1);
|
|
assert_true(lines[0].indexOf('H264') !== -1, 'H264 should be the first codec');
|
|
}, `setCodecPreferences() should allow setting H264 as first codec`);
|
|
|
|
promise_test(async () => {
|
|
const lines = await getRTPMapLinesWithCodecAsFirst('video/VP8');
|
|
|
|
assert_greater_than(lines.length, 1);
|
|
assert_true(lines[0].indexOf('VP8') !== -1, 'VP8 should be the first codec');
|
|
}, `setCodecPreferences() should allow setting VP8 as first codec`);
|
|
|
|
test((t) => {
|
|
const pc = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc.close());
|
|
const transceiver = pc.addTransceiver('audio');
|
|
const capabilities = RTCRtpReceiver.getCapabilities('video');
|
|
assert_throws_dom('InvalidModificationError', () => transceiver.setCodecPreferences(capabilities.codecs));
|
|
}, `setCodecPreferences() on audio transceiver with codecs returned from getCapabilities('video') should throw InvalidModificationError`);
|
|
|
|
test((t) => {
|
|
const pc = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc.close());
|
|
const transceiver = pc.addTransceiver('audio');
|
|
const codecs = [{
|
|
mimeType: 'data',
|
|
clockRate: 2000,
|
|
channels: 2,
|
|
sdpFmtpLine: '0-15'
|
|
}];
|
|
|
|
assert_throws_dom('InvalidModificationError', () => transceiver.setCodecPreferences(codecs));
|
|
}, `setCodecPreferences() with user defined codec with invalid mimeType should throw InvalidModificationError`);
|
|
|
|
test((t) => {
|
|
const pc = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc.close());
|
|
const transceiver = pc.addTransceiver('audio');
|
|
const codecs = [{
|
|
mimeType: 'audio/piepiper',
|
|
clockRate: 2000,
|
|
channels: 2,
|
|
sdpFmtpLine: '0-15'
|
|
}];
|
|
|
|
assert_throws_dom('InvalidModificationError', () => transceiver.setCodecPreferences(codecs));
|
|
}, `setCodecPreferences() with user defined codec should throw InvalidModificationError`);
|
|
|
|
test((t) => {
|
|
const pc = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc.close());
|
|
const transceiver = pc.addTransceiver('audio');
|
|
const capabilities = RTCRtpReceiver.getCapabilities('audio');
|
|
const codecs = [
|
|
...capabilities.codecs,
|
|
{
|
|
mimeType: 'audio/piepiper',
|
|
clockRate: 2000,
|
|
channels: 2,
|
|
sdpFmtpLine: '0-15'
|
|
}];
|
|
|
|
assert_throws_dom('InvalidModificationError', () => transceiver.setCodecPreferences(codecs));
|
|
}, `setCodecPreferences() with user defined codec together with codecs returned from getCapabilities() should throw InvalidModificationError`);
|
|
|
|
test((t) => {
|
|
const pc = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc.close());
|
|
const transceiver = pc.addTransceiver('audio');
|
|
const capabilities = RTCRtpReceiver.getCapabilities('audio');
|
|
const codecs = [capabilities.codecs[0]];
|
|
codecs[0].clockRate = codecs[0].clockRate / 2;
|
|
|
|
assert_throws_dom('InvalidModificationError', () => transceiver.setCodecPreferences(codecs));
|
|
}, `setCodecPreferences() with modified codec clock rate should throw InvalidModificationError`);
|
|
|
|
test((t) => {
|
|
const pc = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc.close());
|
|
const transceiver = pc.addTransceiver('audio');
|
|
const capabilities = RTCRtpReceiver.getCapabilities('audio');
|
|
const codecs = [capabilities.codecs[0]];
|
|
codecs[0].channels = codecs[0].channels + 11;
|
|
|
|
assert_throws_dom('InvalidModificationError', () => transceiver.setCodecPreferences(codecs));
|
|
}, `setCodecPreferences() with modified codec channel count should throw InvalidModificationError`);
|
|
|
|
test((t) => {
|
|
const pc = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc.close());
|
|
const transceiver = pc.addTransceiver('audio');
|
|
const capabilities = RTCRtpReceiver.getCapabilities('audio');
|
|
const codecs = [capabilities.codecs[0]];
|
|
codecs[0].sdpFmtpLine = "modifiedparameter=1";
|
|
|
|
assert_throws_dom('InvalidModificationError', () => transceiver.setCodecPreferences(codecs));
|
|
}, `setCodecPreferences() with modified codec parameters should throw InvalidModificationError`);
|
|
|
|
test((t) => {
|
|
const pc = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc.close());
|
|
const transceiver = pc.addTransceiver('audio');
|
|
const capabilities = RTCRtpReceiver.getCapabilities('audio');
|
|
|
|
const { codecs } = capabilities;
|
|
assert_greater_than(codecs.length, 0,
|
|
'Expect at least one codec available');
|
|
|
|
const [ codec ] = codecs;
|
|
const { channels=2 } = codec;
|
|
codec.channels = channels+1;
|
|
|
|
assert_throws_dom('InvalidModificationError', () => transceiver.setCodecPreferences(codecs));
|
|
}, `setCodecPreferences() with modified codecs returned from getCapabilities() should throw InvalidModificationError`);
|
|
|
|
promise_test(async (t) => {
|
|
const pc = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc.close());
|
|
const transceiver = pc.addTransceiver('audio');
|
|
const {codecs} = RTCRtpReceiver.getCapabilities('audio');
|
|
// Reorder codecs, put PCMU/PCMA first.
|
|
let firstCodec;
|
|
let i;
|
|
for (i = 0; i < codecs.length; i++) {
|
|
const codec = codecs[i];
|
|
if (codec.mimeType === 'audio/PCMU' || codec.mimeType === 'audio/PCMA') {
|
|
codecs.splice(i, 1);
|
|
codecs.unshift(codec);
|
|
firstCodec = codec.mimeType.substr(6);
|
|
break;
|
|
}
|
|
}
|
|
assert_not_equals(firstCodec, undefined, 'PCMU or PCMA codec not found');
|
|
transceiver.setCodecPreferences(codecs);
|
|
|
|
const offer = await pc.createOffer();
|
|
const mediaSection = SDPUtils.getMediaSections(offer.sdp)[0];
|
|
const rtpParameters = SDPUtils.parseRtpParameters(mediaSection);
|
|
assert_equals(rtpParameters.codecs[0].name, firstCodec);
|
|
}, `setCodecPreferences() modifies the order of audio codecs in createOffer`);
|
|
|
|
promise_test(async (t) => {
|
|
const pc = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc.close());
|
|
const transceiver = pc.addTransceiver('video');
|
|
const {codecs} = RTCRtpReceiver.getCapabilities('video');
|
|
// Reorder codecs, swap H264 and VP8.
|
|
let vp8 = -1;
|
|
let h264 = -1;
|
|
let firstCodec;
|
|
let i;
|
|
for (i = 0; i < codecs.length; i++) {
|
|
const codec = codecs[i];
|
|
if (codec.mimeType === 'video/VP8' && vp8 === -1) {
|
|
vp8 = i;
|
|
if (h264 !== -1) {
|
|
codecs[vp8] = codecs[h264];
|
|
codecs[h264] = codec;
|
|
firstCodec = 'VP8';
|
|
break;
|
|
}
|
|
}
|
|
if (codec.mimeType === 'video/H264' && h264 === -1) {
|
|
h264 = i;
|
|
if (vp8 !== -1) {
|
|
codecs[h264] = codecs[vp8];
|
|
codecs[vp8] = codec;
|
|
firstCodec = 'H264';
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
assert_not_equals(firstCodec, undefined, 'VP8 and H264 codecs not found');
|
|
transceiver.setCodecPreferences(codecs);
|
|
|
|
const offer = await pc.createOffer();
|
|
const mediaSection = SDPUtils.getMediaSections(offer.sdp)[0];
|
|
const rtpParameters = SDPUtils.parseRtpParameters(mediaSection);
|
|
assert_equals(rtpParameters.codecs[0].name, firstCodec);
|
|
}, `setCodecPreferences() modifies the order of video codecs in createOffer`);
|
|
|
|
['rtx', 'red', 'ulpfec'].forEach(resiliencyMechanism => {
|
|
promise_test(async (t) => {
|
|
const pc = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc.close());
|
|
const transceiver = pc.addTransceiver('video');
|
|
const {codecs} = RTCRtpReceiver.getCapabilities('video');
|
|
const filteredCodecs = codecs.
|
|
filter(codec => codec.mimeType !== 'video/' + resiliencyMechanism);
|
|
assert_not_equals(codecs.length, filteredCodecs.length);
|
|
transceiver.setCodecPreferences(filteredCodecs);
|
|
|
|
const offer = await pc.createOffer();
|
|
const mediaSection = SDPUtils.getMediaSections(offer.sdp)[0];
|
|
const rtpParameters = SDPUtils.parseRtpParameters(mediaSection);
|
|
assert_equals(rtpParameters.codecs.find(codec => codec.name === resiliencyMechanism),
|
|
undefined);
|
|
}, `setCodecPreferences() can remove ${resiliencyMechanism}`);
|
|
});
|
|
|
|
// Tests the note removed as result of discussion in
|
|
// https://github.com/w3c/webrtc-pc/issues/2933
|
|
promise_test(async (t) => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
|
|
const transceiver = pc1.addTransceiver('video');
|
|
const {codecs} = RTCRtpReceiver.getCapabilities('video');
|
|
const vp8 = codecs.find(codec => codec.mimeType === 'video/VP8');
|
|
const h264 = codecs.find(codec => codec.mimeType === 'video/H264');
|
|
const thirdCodec = codecs.find(codec => ['video/VP9', 'video/AV1'].includes(codec.mimeType));
|
|
assert_true(!!vp8);
|
|
assert_true(!!h264);
|
|
assert_true(!!thirdCodec);
|
|
|
|
transceiver.setCodecPreferences([vp8, thirdCodec]);
|
|
await pc1.setLocalDescription();
|
|
await pc2.setRemoteDescription(pc1.localDescription);
|
|
const transceiver2 = pc2.getTransceivers()[0];
|
|
transceiver2.setCodecPreferences([h264, thirdCodec, vp8]);
|
|
await pc2.setLocalDescription();
|
|
await pc1.setRemoteDescription(pc2.localDescription);
|
|
const mediaSection = SDPUtils.getMediaSections(pc2.localDescription.sdp)[0];
|
|
const rtpParameters = SDPUtils.parseRtpParameters(mediaSection);
|
|
// Order is determined by pc2 but H264 is not present.
|
|
assert_equals(rtpParameters.codecs.length, 2);
|
|
assert_equals(rtpParameters.codecs[0].name, thirdCodec.mimeType.substring(6));
|
|
assert_equals(rtpParameters.codecs[1].name, 'VP8');
|
|
|
|
}, `setCodecPreferences() filters on receiver and prefers receiver order`);
|
|
|
|
["audio", "video"].forEach(kind => promise_test(async (t) => {
|
|
const pc = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc.close());
|
|
const [codec] = RTCRtpReceiver.getCapabilities(kind).codecs;
|
|
codec.mimeType = codec.mimeType.toUpperCase();
|
|
const transceiver = pc.addTransceiver(kind);
|
|
transceiver.setCodecPreferences([codec]);
|
|
|
|
codec.mimeType = codec.mimeType.toLowerCase();
|
|
transceiver.setCodecPreferences([codec]);
|
|
}, `setCodecPreferences should accept ${kind} codecs regardless of mimeType case`));
|
|
|
|
</script>
|