blob: 6eaf2bac585dc954b31871a99d9dab99d1f149b5 [file]
<!DOCTYPE html>
<html>
<head>
<link rel="icon" href="data:;base64,="><!-- prevent request for favicon -->
<script src="webrtc_common.js"></script>
<script type="text/javascript">
'use strict';
// Using canvas-based tracks over getUserMedia() is preferred due to the
// capturer crashing on some bots.
function createCanvasTrack(width = 640, height = 360) {
// Draws the color red at 30 fps.
const canvas = Object.assign(
document.createElement('canvas'), { width, height });
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgb(255,0,0)';
const interval = setInterval(() => {
ctx.fillRect(0, 0, canvas.width, canvas.height);
}, 1000 / 30);
const stream = canvas.captureStream();
const [track] = stream.getTracks();
// Clear the interval on stop().
const nativeStop = track.stop;
track.stop = () => {
clearInterval(interval);
nativeStop.apply(track);
};
return track;
}
async function pollGetStatsUntil(pc, condition, pollingMs = 100) {
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => { reject(new Error('Timeout after 30s')) }, 30000);
const pollGetStats = async () => {
const report = await pc.getStats();
for (const stats of report.values()) {
if (condition(report, stats)) {
clearTimeout(timeoutId);
resolve({ report, stats });
return;
}
}
// No match, then set timeout and try again.
setTimeout(pollGetStats, pollingMs);
};
pollGetStats();
});
}
function isRtpWithCodec(report, stats, mimeType) {
if (stats.type !== 'outbound-rtp' && stats.type !== 'inbound-rtp') {
return false; // Not an RTP stats object.
}
const codec = report.get(stats.codecId);
return codec && codec.mimeType == mimeType;
}
async function main(arg) {
const { codec: preferredCodec, requireHwEncoder, requireHwDecoder } = arg;
if (!requireHwDecoder && !requireHwEncoder) {
TEST.reportFailure('No hw encoder or decoder required. Check test configuration.');
return;
}
// MediaCapabilities check.
const config = {
type: 'webrtc',
video: {
contentType: preferredCodec,
width: 640,
height: 360,
bitrate: 2000000,
framerate: 30
}
};
// Get user media permission for audio to allow exposing hardware capabilities.
// This triggers IsCapturing() in Blink which is the condition for exposing these stats.
let stream = null;
try {
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
TEST.log('Acquired audio stream for hardware capability stats exposure.');
TEST.addCleanup(() => {
if (stream) {
stream.getTracks().forEach(track => track.stop());
}
});
} catch (e) {
TEST.log('Failed to acquire audio stream: ' + e);
// We might continue but stats might be missing.
}
const encodingInfo = await navigator.mediaCapabilities.encodingInfo(config);
TEST.log('Encoding info: ' + JSON.stringify(encodingInfo));
if (requireHwEncoder && !encodingInfo.powerEfficient) {
TEST.skip('No power efficient encoder for ' + preferredCodec);
return;
}
const decodingInfo = await navigator.mediaCapabilities.decodingInfo(config);
TEST.log('Decoding info: ' + JSON.stringify(decodingInfo));
if (requireHwDecoder && !decodingInfo.powerEfficient) {
TEST.skip('No power efficient decoder for ' + preferredCodec);
return;
}
const sendCodec =
RTCRtpSender.getCapabilities('video').codecs.find(
codec => codec.mimeType == preferredCodec);
const canRecv =
RTCRtpReceiver.getCapabilities('video').codecs.find(
codec => codec.mimeType == preferredCodec) != undefined;
const canSend = sendCodec !== undefined;
if (!canSend || !canRecv) {
TEST.skip(`${preferredCodec} either does not support send(${canSend}) or receive (${canRecv})`);
return;
}
const pc1 = new RTCPeerConnection();
TEST.addCleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
TEST.addCleanup(() => pc2.close());
pc1.onicecandidate = (e) => pc2.addIceCandidate(e.candidate);
pc2.onicecandidate = (e) => pc1.addIceCandidate(e.candidate);
const track = createCanvasTrack();
TEST.addCleanup(() => track.stop());
const transceiver = pc1.addTransceiver(track);
transceiver.setCodecPreferences([sendCodec]);
await pc1.setLocalDescription();
await pc2.setRemoteDescription(pc1.localDescription);
await pc2.setLocalDescription();
await pc1.setRemoteDescription(pc2.localDescription);
// Wait for frames to be encoded and sent.
const { report: encoderReport, stats: encoderStats } =
await pollGetStatsUntil(pc1, (report, stats) => {
if (!isRtpWithCodec(report, stats, preferredCodec)) {
return false;
}
TEST.assert_equals(stats.type, 'outbound-rtp');
return stats.framesEncoded > 0 && stats.framesSent > 0;
});
if (requireHwEncoder) {
TEST.assert(encoderStats.powerEfficientEncoder,
`Expected encoder to be power efficient for ${preferredCodec}. ` +
`Got powerEfficient=${encoderStats.powerEfficientEncoder}. ` +
`Codec implementation ${encoderStats.encoderImplementation}. ` +
`${JSON.stringify(encoderStats, undefined, 2)}`);
}
// Wait for frames to be received and decoded.
const { report: decoderReport, stats: decoderStats } =
await pollGetStatsUntil(pc2, (report, stats) => {
if (!isRtpWithCodec(report, stats, preferredCodec)) {
return false;
}
TEST.assert_equals(stats.type, 'inbound-rtp');
return stats.framesReceived > 0 && stats.framesDecoded > 0;
});
if (requireHwDecoder) {
TEST.assert(decoderStats.powerEfficientDecoder,
`Expected decoder to be power efficient for ${preferredCodec}. ` +
`Got powerEfficient=${decoderStats.powerEfficientDecoder}. ` +
`Codec implementation ${decoderStats.decoderImplementation}. ` +
`${JSON.stringify(decoderStats, undefined, 2)}`);
}
}
</script>
</head>
<body>
<p id="consoleId"></p>
</body>
</html>