| Index: tools/perf/page_sets/webrtc_cases/audio.js
|
| diff --git a/tools/perf/page_sets/webrtc_cases/audio.js b/tools/perf/page_sets/webrtc_cases/audio.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..2485837ac9d2b2ee5e42173de19702f215687ef0
|
| --- /dev/null
|
| +++ b/tools/perf/page_sets/webrtc_cases/audio.js
|
| @@ -0,0 +1,302 @@
|
| +/*
|
| + * Copyright 2017 The Chromium Authors. All rights reserved.
|
| + * Use of this source code is governed by a BSD-style license that can be
|
| + * found in the LICENSE file.
|
| + */
|
| +
|
| +'use strict';
|
| +
|
| +var audio2 = document.querySelector('audio#audio2');
|
| +var callButton = document.querySelector('button#callButton');
|
| +var hangupButton = document.querySelector('button#hangupButton');
|
| +var codecSelector = document.querySelector('select#codec');
|
| +hangupButton.disabled = true;
|
| +callButton.onclick = call;
|
| +hangupButton.onclick = hangup;
|
| +
|
| +var pc1;
|
| +var pc2;
|
| +var localStream;
|
| +
|
| +var bitrateGraph;
|
| +var bitrateSeries;
|
| +
|
| +var packetGraph;
|
| +var packetSeries;
|
| +
|
| +var lastResult;
|
| +
|
| +var offerOptions = {
|
| + offerToReceiveAudio: 1,
|
| + offerToReceiveVideo: 0,
|
| + voiceActivityDetection: false
|
| +};
|
| +
|
| +function gotStream(stream) {
|
| + hangupButton.disabled = false;
|
| + trace('Received local stream');
|
| + localStream = stream;
|
| + var audioTracks = localStream.getAudioTracks();
|
| + if (audioTracks.length > 0) {
|
| + trace('Using Audio device: ' + audioTracks[0].label);
|
| + }
|
| + pc1.addStream(localStream);
|
| + trace('Adding Local Stream to peer connection');
|
| +
|
| + pc1.createOffer(
|
| + offerOptions
|
| + ).then(
|
| + gotDescription1,
|
| + onCreateSessionDescriptionError
|
| + );
|
| +
|
| + bitrateSeries = new TimelineDataSeries();
|
| + bitrateGraph = new TimelineGraphView('bitrateGraph', 'bitrateCanvas');
|
| + bitrateGraph.updateEndDate();
|
| +
|
| + packetSeries = new TimelineDataSeries();
|
| + packetGraph = new TimelineGraphView('packetGraph', 'packetCanvas');
|
| + packetGraph.updateEndDate();
|
| +}
|
| +
|
| +function onCreateSessionDescriptionError(error) {
|
| + trace('Failed to create session description: ' + error.toString());
|
| +}
|
| +
|
| +function call() {
|
| + callButton.disabled = true;
|
| + codecSelector.disabled = true;
|
| + trace('Starting call');
|
| + var servers = null;
|
| + var pcConstraints = {
|
| + 'optional': []
|
| + };
|
| + pc1 = new RTCPeerConnection(servers, pcConstraints);
|
| + trace('Created local peer connection object pc1');
|
| + pc1.onicecandidate = function(e) {
|
| + onIceCandidate(pc1, e);
|
| + };
|
| + pc2 = new RTCPeerConnection(servers, pcConstraints);
|
| + trace('Created remote peer connection object pc2');
|
| + pc2.onicecandidate = function(e) {
|
| + onIceCandidate(pc2, e);
|
| + };
|
| + pc2.onaddstream = gotRemoteStream;
|
| + trace('Requesting local stream');
|
| + navigator.mediaDevices.getUserMedia({
|
| + audio: true,
|
| + video: false
|
| + })
|
| + .then(gotStream)
|
| + .catch(function(e) {
|
| + alert('getUserMedia() error: ' + e.name);
|
| + });
|
| +}
|
| +
|
| +function gotDescription1(desc) {
|
| + trace('Offer from pc1 \n' + desc.sdp);
|
| + pc1.setLocalDescription(desc).then(
|
| + function() {
|
| + desc.sdp = forceChosenAudioCodec(desc.sdp);
|
| + pc2.setRemoteDescription(desc).then(
|
| + function() {
|
| + pc2.createAnswer().then(
|
| + gotDescription2,
|
| + onCreateSessionDescriptionError
|
| + );
|
| + },
|
| + onSetSessionDescriptionError
|
| + );
|
| + },
|
| + onSetSessionDescriptionError
|
| + );
|
| +}
|
| +
|
| +function gotDescription2(desc) {
|
| + trace('Answer from pc2 \n' + desc.sdp);
|
| + pc2.setLocalDescription(desc).then(
|
| + function() {
|
| + desc.sdp = forceChosenAudioCodec(desc.sdp);
|
| + pc1.setRemoteDescription(desc).then(
|
| + function() {
|
| + },
|
| + onSetSessionDescriptionError
|
| + );
|
| + },
|
| + onSetSessionDescriptionError
|
| + );
|
| +}
|
| +
|
| +function hangup() {
|
| + trace('Ending call');
|
| + localStream.getTracks().forEach(function(track) {
|
| + track.stop();
|
| + });
|
| + pc1.close();
|
| + pc2.close();
|
| + pc1 = null;
|
| + pc2 = null;
|
| + hangupButton.disabled = true;
|
| + callButton.disabled = false;
|
| + codecSelector.disabled = false;
|
| +}
|
| +
|
| +function gotRemoteStream(e) {
|
| + audio2.srcObject = e.stream;
|
| + trace('Received remote stream');
|
| +}
|
| +
|
| +function getOtherPc(pc) {
|
| + return (pc === pc1) ? pc2 : pc1;
|
| +}
|
| +
|
| +function getName(pc) {
|
| + return (pc === pc1) ? 'pc1' : 'pc2';
|
| +}
|
| +
|
| +function onIceCandidate(pc, event) {
|
| + getOtherPc(pc).addIceCandidate(event.candidate)
|
| + .then(
|
| + function() {
|
| + onAddIceCandidateSuccess(pc);
|
| + },
|
| + function(err) {
|
| + onAddIceCandidateError(pc, err);
|
| + }
|
| + );
|
| + trace(getName(pc) + ' ICE candidate: \n' + (event.candidate ?
|
| + event.candidate.candidate : '(null)'));
|
| +}
|
| +
|
| +function onAddIceCandidateSuccess() {
|
| + trace('AddIceCandidate success.');
|
| +}
|
| +
|
| +function onAddIceCandidateError(error) {
|
| + trace('Failed to add ICE Candidate: ' + error.toString());
|
| +}
|
| +
|
| +function onSetSessionDescriptionError(error) {
|
| + trace('Failed to set session description: ' + error.toString());
|
| +}
|
| +
|
| +function forceChosenAudioCodec(sdp) {
|
| + return maybePreferCodec(sdp, 'audio', 'send', codecSelector.value);
|
| +}
|
| +
|
| +// Copied from AppRTC's sdputils.js:
|
| +
|
| +// Sets |codec| as the default |type| codec if it's present.
|
| +// The format of |codec| is 'NAME/RATE', e.g. 'opus/48000'.
|
| +function maybePreferCodec(sdp, type, dir, codec) {
|
| + var str = type + ' ' + dir + ' codec';
|
| + if (codec === '') {
|
| + trace('No preference on ' + str + '.');
|
| + return sdp;
|
| + }
|
| +
|
| + trace('Prefer ' + str + ': ' + codec);
|
| +
|
| + var sdpLines = sdp.split('\r\n');
|
| +
|
| + // Search for m line.
|
| + var mLineIndex = findLine(sdpLines, 'm=', type);
|
| + if (mLineIndex === null) {
|
| + return sdp;
|
| + }
|
| +
|
| + // If the codec is available, set it as the default in m line.
|
| + var codecIndex = findLine(sdpLines, 'a=rtpmap', codec);
|
| + console.log('codecIndex', codecIndex);
|
| + if (codecIndex) {
|
| + var payload = getCodecPayloadType(sdpLines[codecIndex]);
|
| + if (payload) {
|
| + sdpLines[mLineIndex] = setDefaultCodec(sdpLines[mLineIndex], payload);
|
| + }
|
| + }
|
| +
|
| + sdp = sdpLines.join('\r\n');
|
| + return sdp;
|
| +}
|
| +
|
| +// Find the line in sdpLines that starts with |prefix|, and, if specified,
|
| +// contains |substr| (case-insensitive search).
|
| +function findLine(sdpLines, prefix, substr) {
|
| + return findLineInRange(sdpLines, 0, -1, prefix, substr);
|
| +}
|
| +
|
| +// Find the line in sdpLines[startLine...endLine - 1] that starts with |prefix|
|
| +// and, if specified, contains |substr| (case-insensitive search).
|
| +function findLineInRange(sdpLines, startLine, endLine, prefix, substr) {
|
| + var realEndLine = endLine !== -1 ? endLine : sdpLines.length;
|
| + for (var i = startLine; i < realEndLine; ++i) {
|
| + if (sdpLines[i].indexOf(prefix) === 0) {
|
| + if (!substr ||
|
| + sdpLines[i].toLowerCase().indexOf(substr.toLowerCase()) !== -1) {
|
| + return i;
|
| + }
|
| + }
|
| + }
|
| + return null;
|
| +}
|
| +
|
| +// Gets the codec payload type from an a=rtpmap:X line.
|
| +function getCodecPayloadType(sdpLine) {
|
| + var pattern = new RegExp('a=rtpmap:(\\d+) \\w+\\/\\d+');
|
| + var result = sdpLine.match(pattern);
|
| + return (result && result.length === 2) ? result[1] : null;
|
| +}
|
| +
|
| +// Returns a new m= line with the specified codec as the first one.
|
| +function setDefaultCodec(mLine, payload) {
|
| + var elements = mLine.split(' ');
|
| +
|
| + // Just copy the first three parameters; codec order starts on fourth.
|
| + var newLine = elements.slice(0, 3);
|
| +
|
| + // Put target payload first and copy in the rest.
|
| + newLine.push(payload);
|
| + for (var i = 3; i < elements.length; i++) {
|
| + if (elements[i] !== payload) {
|
| + newLine.push(elements[i]);
|
| + }
|
| + }
|
| + return newLine.join(' ');
|
| +}
|
| +
|
| +// query getStats every second
|
| +window.setInterval(function() {
|
| + if (!window.pc1) {
|
| + return;
|
| + }
|
| + window.pc1.getStats(null).then(function(res) {
|
| + res.forEach(function(report) {
|
| + var bytes;
|
| + var packets;
|
| + var now = report.timestamp;
|
| + if ((report.type === 'outboundrtp') ||
|
| + (report.type === 'outbound-rtp') ||
|
| + (report.type === 'ssrc' && report.bytesSent)) {
|
| + bytes = report.bytesSent;
|
| + packets = report.packetsSent;
|
| + if (lastResult && lastResult.get(report.id)) {
|
| + // calculate bitrate
|
| + var bitrate = 8 * (bytes - lastResult.get(report.id).bytesSent) /
|
| + (now - lastResult.get(report.id).timestamp);
|
| +
|
| + // append to chart
|
| + bitrateSeries.addPoint(now, bitrate);
|
| + bitrateGraph.setDataSeries([bitrateSeries]);
|
| + bitrateGraph.updateEndDate();
|
| +
|
| + // calculate number of packets and append to chart
|
| + packetSeries.addPoint(now, packets -
|
| + lastResult.get(report.id).packetsSent);
|
| + packetGraph.setDataSeries([packetSeries]);
|
| + packetGraph.updateEndDate();
|
| + }
|
| + }
|
| + });
|
| + lastResult = res;
|
| + });
|
| +}, 1000);
|
|
|