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