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是理想值,浏览器会尽量满足;minmax是硬约束,如果设备不支持,Promise会reject。我曾经在低端笔记本上设了width: { exact: 1920 },结果直接报错——摄像头根本不支持这个分辨率。

避坑指南: 不要用exact除非你100%确定设备支持。用ideal配合min/max更安全。获取流之后,可以通过stream.getVideoTracks()[0].getSettings()查看实际生效的分辨率和帧率。

4.3 设备枚举:找到所有可用的摄像头和麦克风

做多设备切换时,必须先枚举设备。API是navigator.mediaDevices.enumerateDevices()。它返回一个设备列表,每个设备有deviceIdkindlabel等属性。

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和注意事项。

音视频采集知识体系 enumerateDevices() 获取所有摄像头/麦克风列表 getUserMedia({ video, audio }) 传入分辨率、帧率、设备ID等约束 MediaStream → 绑定到 video/audio 标签 替换 track:stop → remove → add 第一步 第二步 第三步 第四步 ⚠️ 注意 • 需 HTTPS 或 localhost • 先授权再枚举 • 用 ideal 不用 exact 💡 技巧 • 监听 devicechange • getSettings() 查实际值

4.6 常见问题与避坑

  • 权限被拒: 用户点了"禁止",浏览器不会弹窗第二次。需要引导用户去浏览器设置里手动开启。我曾在项目中加了一个"摄像头被禁用"的提示,点击后跳转到浏览器设置页。
  • 设备被占用: 另一个应用(如Zoom)正在使用摄像头,getUserMedia会失败。建议捕获NotReadableError,提示用户关闭其他应用。
  • 分辨率不匹配: 设了width: 1920但摄像头只支持1280,浏览器会自动降级。用getSettings()确认实际值。
  • 切换后黑屏: 多半是旧track没stop,或者新track没正确add到stream。按我上面给的替换流程走,基本不会出问题。

总结一下: 音视频采集看似简单,但细节很多。设备枚举、约束设置、track替换,每一步都有坑。我个人建议,先把enumerateDevices和getUserMedia的约束玩透,再去做设备切换。基础打牢了,后面编解码和传输才能稳。


公众号:蓝海资料掘金营,微信deep3321