blob: 0ea6e91b397637ebf37d06ef4f7ba468eec7bcf4 [file] [log] [blame]
// Copyright 2013 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.
// Audio test utilities.
// GetStats reports audio output energy in the [0, 32768] range.
var MAX_AUDIO_OUTPUT_ENERGY = 32768;
// Queries WebRTC stats on |peerConnection| to find out whether audio is playing
// on the connection. Note this does not necessarily mean the audio is actually
// playing out (for instance if there's a bug in the WebRTC web media player).
function ensureAudioPlaying(peerConnection) {
return new Promise((resolve, reject) => {
var attempt = 1;
gatherAudioLevelSamples(peerConnection, function(samples) {
if (identifyFakeDeviceSignal_(samples)) {
resolve();
return true;
}
if (attempt++ % 5 == 0) {
console.log('Still waiting for the fake audio signal.');
console.log('Dumping samples so far for analysis: ' + samples);
}
return false;
});
});
}
// Queries WebRTC stats on |peerConnection| to find out whether audio is muted
// on the connection.
function ensureSilence(peerConnection) {
return new Promise((resolve, reject) => {
var attempt = 1;
gatherAudioLevelSamples(peerConnection, function(samples) {
if (identifySilence_(samples)) {
resolve();
return true;
}
if (attempt++ % 5 == 0) {
console.log('Still waiting for audio to go silent.');
console.log('Dumping samples so far for analysis: ' + samples);
}
return false;
});
});
}
// Not sure if this is a bug, but sometimes we get several audio ssrc's where
// just reports audio level zero. Think of the nonzero level as the more
// credible one here. http://crbug.com/479147.
function workAroundSeveralReportsIssue(audioOutputLevels) {
if (audioOutputLevels.length == 1) {
return audioOutputLevels[0];
}
console.log("Hit issue where one report batch returns two or more reports " +
"with audioReportLevel; got " + audioOutputLevels);
return Math.max(audioOutputLevels[0], audioOutputLevels[1]);
}
// Gathers samples from WebRTC stats as fast as possible for and calls back
// |callback| continuously with an array with numbers in the [0, 32768] range.
// The array will grow continuously over time as we gather more samples. The
// |callback| should return true when it is satisfied. It will be called about
// once a second and can contain expensive processing (but faster = better).
//
// There are no guarantees for how often we will be able to collect values,
// but this function deliberately avoids setTimeout calls in order be as
// insensitive as possible to starvation (particularly when this code runs in
// parallel with other tests on a heavily loaded bot).
function gatherAudioLevelSamples(peerConnection, callback) {
console.log('Gathering audio samples...');
var callbackIntervalMs = 1000;
var audioLevelSamples = []
// If this times out and never found any audio output levels, the call
// probably doesn't have an audio stream.
var lastRunAt = new Date();
var gotStats = function(response) {
audioOutputLevels = getAudioLevelFromStats_(response);
if (audioOutputLevels.length == 0) {
// The call probably isn't up yet.
peerConnection.getStats(gotStats);
return;
}
var outputLevel = workAroundSeveralReportsIssue(audioOutputLevels);
audioLevelSamples.push(outputLevel);
var elapsed = new Date() - lastRunAt;
if (elapsed > callbackIntervalMs) {
if (callback(audioLevelSamples)) {
console.log('Done gathering samples: we found what we looked for.');
return;
}
lastRunAt = new Date();
}
// Otherwise, continue as fast as we can.
peerConnection.getStats(gotStats);
}
peerConnection.getStats(gotStats);
}
/**
* Tries to identify the beep-every-half-second signal generated by the fake
* audio device in media/capture/video/fake_video_capture_device.cc. Fails the
* test if we can't see a signal. The samples should have been gathered over at
* least two seconds since we expect to see at least three "peaks" in there
* (we should see either 3 or 4 depending on how things line up).
*
* @private
*/
function identifyFakeDeviceSignal_(samples) {
var numPeaks = 0;
var threshold = MAX_AUDIO_OUTPUT_ENERGY * 0.7;
var currentlyOverThreshold = false;
// Detect when we have been been over the threshold and is going back again
// (i.e. count peaks). We should see about two peaks per second.
for (var i = 0; i < samples.length; ++i) {
if (currentlyOverThreshold && samples[i] < threshold)
numPeaks++;
currentlyOverThreshold = samples[i] >= threshold;
}
var expectedPeaks = 3;
console.log(numPeaks + '/' + expectedPeaks + ' signal peaks identified.');
return numPeaks >= expectedPeaks;
}
/**
* @private
*/
function identifySilence_(samples) {
// Look at the last 10K samples only to make detection a bit faster.
var window = samples.slice(-10000);
var average = 0;
for (var i = 0; i < window.length; ++i)
average += window[i] / window.length;
// If silent (like when muted), we should get very near zero audio level.
console.log('Average audio level (last 10k samples): ' + average);
return average < 0.01 * MAX_AUDIO_OUTPUT_ENERGY;
}
/**
* @private
*/
function getAudioLevelFromStats_(response) {
var reports = response.result();
var audioOutputLevels = [];
for (var i = 0; i < reports.length; ++i) {
var report = reports[i];
if (report.names().indexOf('audioOutputLevel') != -1) {
audioOutputLevels.push(report.stat('audioOutputLevel'));
}
}
return audioOutputLevels;
}