193 lines
7.1 KiB
HTML
193 lines
7.1 KiB
HTML
<!doctype html>
|
|
<meta charset=utf-8>
|
|
<title>RTX codec integrity checks</title>
|
|
<script src="/resources/testharness.js"></script>
|
|
<script src="/resources/testharnessreport.js"></script>
|
|
<script src="../RTCPeerConnection-helper.js"></script>
|
|
<script>
|
|
'use strict';
|
|
|
|
const kProfileIdKey = 'profile-id';
|
|
const kLevelIdKey = 'level-id';
|
|
|
|
// The level-id value for Level X.Y is calculated as (X * 10 + Y) * 3.
|
|
// The lowest Level, 1.0, is thus (1 * 10 + 0) * 3 = 30.
|
|
const kH265Level1dot0 = '30';
|
|
|
|
function parseFmtpMap(sdpFmtpLine) {
|
|
const map = new Map();
|
|
// For each entry (semi-colon separated key=value).
|
|
for (let i = 0; i < sdpFmtpLine.length; ++i) {
|
|
let entryEnd = sdpFmtpLine.indexOf(';', i);
|
|
if (entryEnd == -1) {
|
|
entryEnd = sdpFmtpLine.length;
|
|
}
|
|
const entryStr = sdpFmtpLine.substring(i, entryEnd);
|
|
const keyValue = entryStr.split('=');
|
|
if (keyValue.length != 2) {
|
|
throw 'Failed to parse sdpFmtpLine';
|
|
}
|
|
map.set(keyValue[0], keyValue[1]);
|
|
i = entryEnd;
|
|
}
|
|
return map;
|
|
}
|
|
|
|
function findCodecWithProfileId(codecs, mimeType, profileId) {
|
|
return codecs.find(codec => {
|
|
if (codec.mimeType != mimeType) {
|
|
return false;
|
|
}
|
|
return parseFmtpMap(codec.sdpFmtpLine).get(kProfileIdKey) == profileId;
|
|
});
|
|
}
|
|
|
|
// Returns `[h265SendCodec, h265RecvCodec]` or aborts the calling test with
|
|
// [PRECONDITION_FAILED].
|
|
function getH265CodecsOrFailPrecondition() {
|
|
const h265SendCodec = RTCRtpSender.getCapabilities('video').codecs.find(
|
|
c => c.mimeType == 'video/H265');
|
|
assert_implements_optional(
|
|
h265SendCodec !== undefined,
|
|
`H265 is not available for sending.`);
|
|
|
|
const h265SendCodecFmtpMap = parseFmtpMap(h265SendCodec.sdpFmtpLine);
|
|
const profileId = h265SendCodecFmtpMap.get(kProfileIdKey);
|
|
assert_not_equals(profileId, undefined,
|
|
`profile-id is missing from sdpFmtpLine`);
|
|
|
|
const h265RecvCodec = findCodecWithProfileId(
|
|
RTCRtpReceiver.getCapabilities('video').codecs, 'video/H265', profileId);
|
|
assert_implements_optional(
|
|
h265RecvCodec !== undefined,
|
|
`H265 profile-id=${profileId} is not available for receiving.`);
|
|
|
|
return [h265SendCodec, h265RecvCodec];
|
|
}
|
|
|
|
function sdpModifyFmtpLevelId(sdp, newLevelId) {
|
|
const lines = sdp.split('\r\n');
|
|
for (let i = 0; i < lines.length; ++i) {
|
|
if (!lines[i].startsWith('a=fmtp:')) {
|
|
continue;
|
|
}
|
|
const spaceIndex = lines[i].indexOf(' ');
|
|
if (spaceIndex == -1) {
|
|
continue;
|
|
}
|
|
const fmtpMap = parseFmtpMap(lines[i].substring(spaceIndex + 1));
|
|
if (!fmtpMap.has(kLevelIdKey)) {
|
|
continue;
|
|
}
|
|
fmtpMap.set(kLevelIdKey, newLevelId);
|
|
const sdpFmtpLine =
|
|
Array.from(fmtpMap, ([key,value]) => `${key}=${value}`).join(';');
|
|
lines[i] = lines[i].substring(0, spaceIndex) + ' ' + sdpFmtpLine;
|
|
}
|
|
return lines.join('\r\n');
|
|
}
|
|
|
|
promise_test(async t => {
|
|
const [h265SendCodec, h265RecvCodec] = getH265CodecsOrFailPrecondition();
|
|
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
|
|
const pc1Transceiver = pc1.addTransceiver('video', {direction: 'sendonly'});
|
|
pc1Transceiver.setCodecPreferences([h265SendCodec]);
|
|
|
|
await pc1.setLocalDescription();
|
|
await pc2.setRemoteDescription(pc1.localDescription);
|
|
await pc2.setLocalDescription();
|
|
// Modify SDP to tell `pc1` that `pc2` can only receive level-id=30.
|
|
await pc1.setRemoteDescription({
|
|
type: 'answer',
|
|
sdp: sdpModifyFmtpLevelId(pc2.localDescription.sdp, kH265Level1dot0)
|
|
});
|
|
|
|
// Confirm level-id=30 was negotiated regardless of sender capabilities.
|
|
const sender = pc1Transceiver.sender;
|
|
const params = sender.getParameters();
|
|
assert_equals(params.codecs.length, 1);
|
|
const negotiatedFmtpMap = parseFmtpMap(params.codecs[0].sdpFmtpLine);
|
|
assert_equals(negotiatedFmtpMap.get(kLevelIdKey), kH265Level1dot0);
|
|
}, `Offer to send H265, answer to receive level-id=30 results in level-id=30`);
|
|
|
|
promise_test(async t => {
|
|
const [h265SendCodec, h265RecvCodec] = getH265CodecsOrFailPrecondition();
|
|
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
|
|
const pc1Transceiver = pc1.addTransceiver('video', {direction: 'recvonly'});
|
|
pc1Transceiver.setCodecPreferences([h265RecvCodec]);
|
|
|
|
await pc1.setLocalDescription();
|
|
// Modify SDP to tell `pc2` that `pc1` can only receive level-id=30.
|
|
await pc2.setRemoteDescription({
|
|
type: 'offer',
|
|
sdp: sdpModifyFmtpLevelId(pc1.localDescription.sdp, kH265Level1dot0)
|
|
});
|
|
const [pc2Transceiver] = pc2.getTransceivers();
|
|
pc2Transceiver.direction = 'sendonly';
|
|
await pc2.setLocalDescription();
|
|
await pc1.setRemoteDescription(pc2.localDescription);
|
|
|
|
// Confirm level-id=30 was negotiated regardless of sender capabilities.
|
|
const sender = pc2Transceiver.sender;
|
|
const params = sender.getParameters();
|
|
assert_equals(params.codecs.length, 1);
|
|
const negotiatedFmtpMap = parseFmtpMap(params.codecs[0].sdpFmtpLine);
|
|
assert_equals(negotiatedFmtpMap.get(kLevelIdKey), kH265Level1dot0);
|
|
// Setting a codec that was negotiated should always work, regardless of the
|
|
// level-id in sender capabilities.
|
|
params.encodings[0].codec = params.codecs[0];
|
|
await sender.setParameters(params);
|
|
assert_equals(sender.getParameters().encodings[0].codec.mimeType,
|
|
params.codecs[0].mimeType);
|
|
assert_equals(sender.getParameters().encodings[0].codec.sdpFmtpLine,
|
|
params.codecs[0].sdpFmtpLine);
|
|
}, `Offer to receive level-id=30 and set codec from getParameters`);
|
|
|
|
promise_test(async t => {
|
|
const [h265SendCodec, h265RecvCodec] = getH265CodecsOrFailPrecondition();
|
|
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
|
|
const pc1Transceiver = pc1.addTransceiver('video', {direction: 'recvonly'});
|
|
pc1Transceiver.setCodecPreferences([h265RecvCodec]);
|
|
|
|
await pc1.setLocalDescription();
|
|
// Modify SDP to tell `pc2` that `pc1` can only receive level-id=30.
|
|
await pc2.setRemoteDescription({
|
|
type: 'offer',
|
|
sdp: sdpModifyFmtpLevelId(pc1.localDescription.sdp, kH265Level1dot0)
|
|
});
|
|
const [pc2Transceiver] = pc2.getTransceivers();
|
|
pc2Transceiver.direction = 'sendonly';
|
|
await pc2.setLocalDescription();
|
|
await pc1.setRemoteDescription(pc2.localDescription);
|
|
|
|
// Confirm level-id=30 was negotiated regardless of sender capabilities.
|
|
const sender = pc2Transceiver.sender;
|
|
const params = sender.getParameters();
|
|
assert_equals(params.codecs.length, 1);
|
|
const negotiatedFmtpMap = parseFmtpMap(params.codecs[0].sdpFmtpLine);
|
|
assert_equals(negotiatedFmtpMap.get(kLevelIdKey), kH265Level1dot0);
|
|
// Setting a codec from getCapabilities should work, even if a lower level-id
|
|
// was negotiated.
|
|
params.encodings[0].codec = h265SendCodec;
|
|
await sender.setParameters(params);
|
|
assert_equals(sender.getParameters().encodings[0].codec.mimeType,
|
|
h265SendCodec.mimeType);
|
|
assert_equals(sender.getParameters().encodings[0].codec.sdpFmtpLine,
|
|
h265SendCodec.sdpFmtpLine);
|
|
}, `Offer to receive level-id=30 and set codec from getCapabilities`);
|
|
</script>
|