summaryrefslogtreecommitdiffstats
path: root/dom/media/webrtc/tests/mochitests/test_setSinkId-echoCancellation.html
blob: fa13b72bfb4609c60a7314370af7d0aca3292e2f (plain)
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
<!DOCTYPE HTML>
<html>
<head>
<title></title>
<script src="mediaStreamPlayback.js"></script>
</head>
<script>
"use strict";

createHTML({
  title: "Test echoCancellation with setSinkId()",
  bug: "1849108",
  visible: true,
});
/**
This test captures audio from a loopback device to test echo cancellation.
 **/
runTest(async () => {
  await SpecialPowers.pushPrefEnv({set: [
    // skip selectAudioOutput/getUserMedia permission prompt
    ["media.navigator.permission.disabled", true],
    // enumerateDevices() without focus
    ["media.devices.unfocused.enabled", true],
  ]});

  const ac = new AudioContext();
  const dest = new MediaStreamAudioDestinationNode(ac);
  const gain = new GainNode(ac, {gain: 0.5});
  gain.connect(dest);
  // Use a couple of triangle waves for audio with some bandwidth.
  // Pick incommensurable frequencies so that the audio is aperiodic.
  // Perhaps that might help the AEC determine the delay.
  const osc1 =
        new OscillatorNode(ac, {type: "triangle", frequency: 200});
  const osc2 =
        new OscillatorNode(ac, {type: "triangle", frequency: Math.PI * 100});
  osc1.connect(gain);
  osc2.connect(gain);
  osc1.start();
  osc2.start();
  const audio = new Audio();
  audio.srcObject = dest.stream;
  audio.controls = true;
  document.body.appendChild(audio);

  // The loopback device is currenly only available on Linux.
  let loopbackInputLabel =
      SpecialPowers.getCharPref("media.audio_loopback_dev", "");
  if (!navigator.userAgent.includes("Linux")) {
    todo_isnot(loopbackInputLabel, "", "audio_loopback_dev");
    return;
  }
  isnot(loopbackInputLabel, "",
        "audio_loopback_dev. Use --use-test-media-devices.");

  const loopbackStream = await navigator.mediaDevices.getUserMedia({ audio: {
    echoCancellation: false,
    autoGainControl: false,
    noiseSuppression: false,
  }});
  is(loopbackStream.getTracks()[0].label, loopbackInputLabel,
     "loopback track label");

  // Check that the loopback stream contains silence now.
  const loopbackNode = ac.createMediaStreamSource(loopbackStream);
  const processor1 = ac.createScriptProcessor(4096, 1, 0);
  loopbackNode.connect(processor1);
  const {inputBuffer} = await new Promise(r => processor1.onaudioprocess = r);
  loopbackNode.disconnect();
  is(inputBuffer.getChannelData(0).find(value => value != 0.0), undefined,
     "should have silence in loopback input");

  // Find the loopback output device
  const devices = await navigator.mediaDevices.enumerateDevices();
  let loopbackOutputLabel =
      SpecialPowers.getCharPref("media.cubeb.output_device", "");
  const outputDeviceInfo = devices.find(
    ({kind, label}) => kind == "audiooutput" && label == loopbackOutputLabel
  );
  ok(outputDeviceInfo, `found "${loopbackOutputLabel}"`);

  await audio.setSinkId(outputDeviceInfo.deviceId);
  await audio.play();

  const analyser = new AudioStreamAnalyser(ac, loopbackStream);
  const bin1 = analyser.binIndexForFrequency(osc1.frequency.value);
  const bin2 = analyser.binIndexForFrequency(osc2.frequency.value);
  try {
    analyser.enableDebugCanvas();
    // Check for audio with AEC.
    await analyser.waitForAnalysisSuccess(array => {
      return array[bin1] > 200 && array[bin2] > 200;
    });

    // Check echo cancellation.
    await loopbackStream.getTracks()[0].applyConstraints({
      echoCancellation: true,
      autoGainControl: false,
      noiseSuppression: false,
    });
    await analyser.waitForAnalysisSuccess(array => {
      return !array.find(bin => bin > 50);
    });
  } finally {
    await ac.close();
    loopbackStream.getTracks()[0].stop();
  }
});
</script>
</html>