| // 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; |
| } |