1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
content / test / data / media / webrtc_test_audio.js [blame]
// Copyright 2013 The Chromium Authors
// 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];
}
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(report) {
audioOutputLevels = getAudioLevelFromStats_(report);
if (audioOutputLevels.length == 0) {
// The call probably isn't up yet.
peerConnection.getStats().then(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().then(gotStats);
}
peerConnection.getStats().then(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_(report) {
// var reports = response.result();
const audioOutputLevels = [];
for (const stats of report.values()) {
if (stats.type != 'inbound-rtp' || stats.kind != 'audio' ||
stats.audioLevel == undefined) {
continue;
}
// Convert from [0,1] range to [0, 32768].
audioOutputLevels.push(stats.audioLevel*32768);
}
return audioOutputLevels;
}