4、音视频采集:摄像头与麦克风控制、分辨率帧率设置、设备枚举与切换
音视频采集,是整个WebRTC应用的起点。说白了,就是让浏览器拿到摄像头和麦克风的数据。这一步如果没做好,后面编解码、传输全是白搭。我刚开始做WebRTC时,就踩过不少采集的坑——比如设备枚举不全、分辨率设置无效、切换摄像头时黑屏。今天咱们就把这些事彻底聊透。
核心知识点: getUserMedia API、设备枚举、分辨率/帧率控制、设备热插拔与切换
4.1 摄像头与麦克风控制:getUserMedia 实战
获取音视频流,核心就一个API:navigator.mediaDevices.getUserMedia()。它返回一个Promise,resolve后给你一个MediaStream对象。
// 最简单的用法:同时获取音视频
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
// 把流绑定到 video 标签
const video = document.getElementById('localVideo');
video.srcObject = stream;
嗯,这里要注意:getUserMedia必须在HTTPS或localhost下才能调用。我在项目中遇到过,部署到测试服务器后,页面死活拿不到摄像头权限,查了半天才发现是HTTP的问题。
个人习惯: 调用getUserMedia之前,先检查一下浏览器是否支持:
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
console.error('浏览器不支持 getUserMedia');
return;
}
4.2 分辨率与帧率设置:别让摄像头"偷懒"
你想想看,如果做视频会议,720p就够了;但做远程医疗或高清录制,可能需要1080p甚至4K。分辨率设置不对,要么画面模糊,要么性能爆炸。
getUserMedia的video参数可以传一个对象,指定分辨率、帧率等约束:
const constraints = {
video: {
width: { ideal: 1280, min: 640, max: 1920 },
height: { ideal: 720, min: 480, max: 1080 },
frameRate: { ideal: 30, min: 15, max: 60 }
},
audio: {
sampleRate: { ideal: 48000 },
channelCount: { ideal: 2 }
}
};
const stream = await navigator.mediaDevices.getUserMedia(constraints);
这里有个坑:ideal是理想值,浏览器会尽量满足;min和max是硬约束,如果设备不支持,Promise会reject。我曾经在低端笔记本上设了width: { exact: 1920 },结果直接报错——摄像头根本不支持这个分辨率。
避坑指南: 不要用exact除非你100%确定设备支持。用ideal配合min/max更安全。获取流之后,可以通过stream.getVideoTracks()[0].getSettings()查看实际生效的分辨率和帧率。
4.3 设备枚举:找到所有可用的摄像头和麦克风
做多设备切换时,必须先枚举设备。API是navigator.mediaDevices.enumerateDevices()。它返回一个设备列表,每个设备有deviceId、kind、label等属性。
const devices = await navigator.mediaDevices.enumerateDevices();
const videoInputs = devices.filter(d => d.kind === 'videoinput');
const audioInputs = devices.filter(d => d.kind === 'audioinput');
const audioOutputs = devices.filter(d => d.kind === 'audiooutput');
console.log('摄像头数量:', videoInputs.length);
console.log('麦克风数量:', audioInputs.length);
我个人习惯在页面加载时就枚举一次,然后缓存起来。但要注意:label属性在用户未授权摄像头权限时是空字符串。所以最好先调一次getUserMedia获取权限,再枚举。
| 设备类型 | kind 值 | 典型用途 |
|---|---|---|
| 摄像头 | videoinput | 视频采集 |
| 麦克风 | audioinput | 音频采集 |
| 扬声器 | audiooutput | 音频播放 |
4.4 设备切换:不停流,直接换
很多新手做设备切换时,会把整个流停掉再重新获取。其实有更优雅的方式——替换track。
// 假设已有 stream,想切换到另一个摄像头
async function switchCamera(newDeviceId) {
const newStream = await navigator.mediaDevices.getUserMedia({
video: { deviceId: { exact: newDeviceId } },
audio: false
});
const [oldTrack] = stream.getVideoTracks();
const [newTrack] = newStream.getVideoTracks();
// 替换 track
oldTrack.stop();
stream.removeTrack(oldTrack);
stream.addTrack(newTrack);
// 更新 video 标签
video.srcObject = stream;
}
这样做的好处是:音频流不会中断,视频切换也平滑。我在做在线教育项目时,学生切换摄像头,老师端完全感觉不到卡顿,就是这个原理。
小技巧: 切换前先调用enumerateDevices()刷新设备列表,因为用户可能插拔了USB摄像头。监听devicechange事件可以自动感知设备变化:
navigator.mediaDevices.addEventListener('devicechange', () => {
console.log('设备列表变了,重新枚举...');
enumerateDevices();
});
4.5 知识体系总览
下面这张图,把本章的核心逻辑串起来了。从设备枚举到流获取,再到分辨率控制和设备切换,每一步都有对应的API和注意事项。
4.6 常见问题与避坑
- 权限被拒: 用户点了"禁止",浏览器不会弹窗第二次。需要引导用户去浏览器设置里手动开启。我曾在项目中加了一个"摄像头被禁用"的提示,点击后跳转到浏览器设置页。
- 设备被占用: 另一个应用(如Zoom)正在使用摄像头,getUserMedia会失败。建议捕获
NotReadableError,提示用户关闭其他应用。 - 分辨率不匹配: 设了
width: 1920但摄像头只支持1280,浏览器会自动降级。用getSettings()确认实际值。 - 切换后黑屏: 多半是旧track没stop,或者新track没正确add到stream。按我上面给的替换流程走,基本不会出问题。
总结一下: 音视频采集看似简单,但细节很多。设备枚举、约束设置、track替换,每一步都有坑。我个人建议,先把enumerateDevices和getUserMedia的约束玩透,再去做设备切换。基础打牢了,后面编解码和传输才能稳。
公众号:蓝海资料掘金营,微信deep3321