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