tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_setSinkId-echoCancellation.html (3643B)


      1 <!DOCTYPE HTML>
      2 <html>
      3 <head>
      4 <title></title>
      5 <script src="mediaStreamPlayback.js"></script>
      6 </head>
      7 <script>
      8 "use strict";
      9 
     10 createHTML({
     11  title: "Test echoCancellation with setSinkId()",
     12  bug: "1849108",
     13  visible: true,
     14 });
     15 /**
     16 This test captures audio from a loopback device to test echo cancellation.
     17 */
     18 runTest(async () => {
     19  await SpecialPowers.pushPrefEnv({set: [
     20    // skip selectAudioOutput/getUserMedia permission prompt
     21    ["media.navigator.permission.disabled", true],
     22    // enumerateDevices() without focus
     23    ["media.devices.unfocused.enabled", true],
     24  ]});
     25 
     26  const ac = new AudioContext();
     27  const dest = new MediaStreamAudioDestinationNode(ac);
     28  const gain = new GainNode(ac, {gain: 0.5});
     29  gain.connect(dest);
     30  // Use a couple of triangle waves for audio with some bandwidth.
     31  // Pick incommensurable frequencies so that the audio is aperiodic.
     32  // Perhaps that might help the AEC determine the delay.
     33  const osc1 =
     34        new OscillatorNode(ac, {type: "triangle", frequency: 200});
     35  const osc2 =
     36        new OscillatorNode(ac, {type: "triangle", frequency: Math.PI * 100});
     37  osc1.connect(gain);
     38  osc2.connect(gain);
     39  osc1.start();
     40  osc2.start();
     41  const audio = new Audio();
     42  audio.srcObject = dest.stream;
     43  audio.controls = true;
     44  document.body.appendChild(audio);
     45 
     46  // The loopback device is currenly only available on Linux.
     47  let loopbackInputLabel =
     48      SpecialPowers.getCharPref("media.audio_loopback_dev", "");
     49  if (!navigator.userAgent.includes("Linux")) {
     50    todo_isnot(loopbackInputLabel, "", "audio_loopback_dev");
     51    return;
     52  }
     53  isnot(loopbackInputLabel, "",
     54        "audio_loopback_dev. Use --use-test-media-devices.");
     55 
     56  const loopbackStream = await navigator.mediaDevices.getUserMedia({ audio: {
     57    echoCancellation: false,
     58    autoGainControl: false,
     59    noiseSuppression: false,
     60  }});
     61  is(loopbackStream.getTracks()[0].label, loopbackInputLabel,
     62     "loopback track label");
     63 
     64  // Check that the loopback stream contains silence now.
     65  const loopbackNode = ac.createMediaStreamSource(loopbackStream);
     66  const processor1 = ac.createScriptProcessor(4096, 1, 0);
     67  loopbackNode.connect(processor1);
     68  const {inputBuffer} = await new Promise(r => processor1.onaudioprocess = r);
     69  loopbackNode.disconnect();
     70  is(inputBuffer.getChannelData(0).find(value => value != 0.0), undefined,
     71     "should have silence in loopback input");
     72 
     73  // Find the loopback output device
     74  const devices = await navigator.mediaDevices.enumerateDevices();
     75  let loopbackOutputLabel =
     76      SpecialPowers.getCharPref("media.cubeb.output_device", "");
     77  const outputDeviceInfo = devices.find(
     78    ({kind, label}) => kind == "audiooutput" && label == loopbackOutputLabel
     79  );
     80  ok(outputDeviceInfo, `found "${loopbackOutputLabel}"`);
     81 
     82  await audio.setSinkId(outputDeviceInfo.deviceId);
     83  await audio.play();
     84 
     85  const analyser = new AudioStreamAnalyser(ac, loopbackStream);
     86  const bin1 = analyser.binIndexForFrequency(osc1.frequency.value);
     87  const bin2 = analyser.binIndexForFrequency(osc2.frequency.value);
     88  try {
     89    analyser.enableDebugCanvas();
     90    // Check for audio with AEC.
     91    await analyser.waitForAnalysisSuccess(array => {
     92      return array[bin1] > 200 && array[bin2] > 200;
     93    });
     94 
     95    // Check echo cancellation.
     96    await loopbackStream.getTracks()[0].applyConstraints({
     97      echoCancellation: true,
     98      autoGainControl: false,
     99      noiseSuppression: false,
    100    });
    101    await analyser.waitForAnalysisSuccess(array => {
    102      return !array.find(bin => bin > 50);
    103    });
    104  } finally {
    105    await ac.close();
    106    loopbackStream.getTracks()[0].stop();
    107  }
    108 });
    109 </script>
    110 </html>