Web Audio API的 navigator.getUserMedia会返回来自错误设备的音频流
我正在尝试使用Web Audio API将一个正弦音播放到一个特定的音频输出设备。
(如果你感兴趣,使用场景是对连接到业余无线电的音频接口播放一个短促的 1750 Hz 音调,这个音频接口会“打开”中继器。)
为此,我首先使用:
navigator.mediaDevices.enumerateDevices()
来获取音频输出接口的列表并将其填充到一个下拉菜单中。
然后,在绑定到“运行”按钮的函数中,我将当前选中的 deviceId 作为约束应用于:
navigator.mediaDevices.getUserMedia()
以获取该特定音频设备的 MediaStream。
之后,我:
- 创建一个
AudioContext实例。 - 在该音频上下文中基于该流创建一个
MediaStreamDestinationNode。 - 创建一个
OscillatorNode,并将其输出连接到目标设备。
然而,在第二步就已经遇到了问题。
据观察(在Chrome 145.0.7632.117、macOS Tahoe 26.3 上测试),在查看轨道标签时,MediaStream 的第一条也是唯一的一条轨道总是来自我MacBook的麦克风,而不是我所选择的扬声器设备。因此,我也听不到任何音频在播放。
我创建了一个最小可复现的片段来展示这个问题。
你需要打开控制台才能看到异常的轨道标签。你也可能需要手动授予
stacksnippets.net麦克风和扬声器权限,因为Chrome并不总是会自动提示这些权限。
$(document).ready(function() {
let enumeratorPromise = navigator.mediaDevices.enumerateDevices();
enumeratorPromise.then((devices) => {
console.log(devices);
devices.forEach((device) => {
console.log("Got device from enumerator", device);
if (device.kind !== "audiooutput") return;
let $deviceOption = $("<option>")
.text(device.label)
.val(device.deviceId)
.appendTo($("#audioout"));
});
});
$("#run").click(function() {
let id = $("#audioout").val();
navigator.mediaDevices
.getUserMedia({
audio: {
deviceId: id
}
})
.then(function(stream) {
console.log(
"got MediaStream for",
id,
stream,
stream.getTracks(),
stream.getTracks()[0].label
);
let audioContext = new AudioContext();
let mediaStreamDestination = audioContext.createMediaStreamDestination(
stream
);
audioContext.createOscillator();
oscillatorNode.type = "sine";
oscillatorNode.frequency.setValueAtTime(1750, audioContext.currentTime);
console.log(mediaStreamDestination);
oscillatorNode.connect(mediaStreamDestination);
//oscillatorNode.connect(audioContext.destination);
oscillatorNode.start();
});
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
Select audio device: <select id="audioout"></select><br />
<button id="run">Play 1750 Hz</button>
解决方案
getUserMedia() 仅适用于输入设备(audioinput / videoinput)。它不能捕获一个 audiooutput 设备。
尽管 enumerateDevices() 会列出 audiooutput 设备,但那些是回放目标,而不是媒体源,因此将它们的 deviceId 传递给 getUserMedia() 并不会从该设备生成流。getUserMedia() 能返回的唯一音频轨道来自麦克风。
如果目标是通过特定输出设备播放音频,通常的做法是使用一个 <audio>/<video> 元素,并通过 HTMLMediaElement.setSinkId() 选择设备。
在实际操作中,常见的模式是:
- 用Web Audio生成音频。
- 将其路由到一个
MediaStreamDestination。 - 将得到的流附加到一个
<audio>元素(srcObject)。 - 使用
setSinkId()选择输出设备。