视频设备管理:摄像头枚举、分辨率切换、帧率控制与旋转
各位同学,今天我们来聊聊视频设备管理。说实话,这块内容在 WebRTC 开发中经常被忽视,但恰恰是它决定了用户体验的起点。你想想看,用户打开网页第一件事是什么?肯定是选摄像头、调分辨率。如果这一步做不好,后面编解码再牛也没用。
摄像头枚举:找到你的设备
我们先从最基础的开始——怎么拿到用户电脑上的摄像头列表。WebRTC 提供了 navigator.mediaDevices.enumerateDevices() 这个 API。嗯,这里要注意,它返回的不只是摄像头,还有麦克风和扬声器。
async function getCameras() {
const devices = await navigator.mediaDevices.enumerateDevices();
const cameras = devices.filter(d => d.kind === 'videoinput');
return cameras;
}
我个人习惯在页面加载时就调用一次,把设备信息缓存起来。为什么?因为有些浏览器在用户授权前,设备标签是空的。你拿到的可能是一堆 "Unknown" 设备。我曾经踩过这个坑——用户选了 "Camera 1",结果实际用的是 "Camera 2",画面全黑。
每个摄像头对象包含这些关键字段:
| 字段 | 说明 | 我的经验 |
|---|---|---|
| deviceId | 设备唯一标识 | 持久化存储时用这个,别用 label |
| groupId | 同物理设备的组 ID | 可以用来关联摄像头和麦克风 |
| label | 设备名称 | 用户友好显示用这个 |
分辨率切换:不是越高越好
拿到设备列表后,下一步就是让用户选择分辨率。很多人以为 4K 最好,其实不然。分辨率越高,带宽占用越大,处理延迟也越高。我在做远程医疗项目时,医生要求 720p 就够了,因为 1080p 反而让网络抖动更明显。
WebRTC 的分辨率控制是通过 getUserMedia 的 constraints 参数实现的:
const constraints = {
video: {
deviceId: selectedDeviceId,
width: { ideal: 1280 },
height: { ideal: 720 },
frameRate: { ideal: 30 }
}
};
const stream = await navigator.mediaDevices.getUserMedia(constraints);
这里有个关键点:ideal 不是强制值。浏览器会尽量接近,但不保证完全匹配。如果你非要精确控制,用 exact:
width: { exact: 1920 }
但说实话,我不建议用 exact。为什么?因为很多摄像头不支持所有分辨率。你指定 exact 1920,摄像头不支持就直接报错。更好的做法是先枚举设备支持的分辨率,再让用户选。
MediaStreamTrack.getCapabilities() 获取设备支持的分辨率范围。这样你就能动态生成下拉菜单了。
帧率控制:流畅与性能的平衡
帧率这块,我见过太多人直接设 60fps。嗯,除非你做的是游戏直播,否则 30fps 完全够用。帧率越高,CPU 编码压力越大。我记得有一次,用户用老款笔记本开 60fps,结果 CPU 直接飙到 90%,画面卡成 PPT。
帧率控制同样在 constraints 里设置:
frameRate: { ideal: 30, min: 15, max: 30 }
这样浏览器会在 15-30 之间动态调整。如果网络不好,自动降帧。我个人习惯给一个范围,而不是固定值。这样 WebRTC 的拥塞控制才能发挥作用。
实际项目中,我通常这样设计帧率策略:
- 视频会议: 24-30fps,优先保证流畅
- 屏幕共享: 5-15fps,因为内容变化少
- 远程医疗: 30fps 固定,不能丢帧
旋转:别让用户歪着头看
最后说说旋转。移动端摄像头经常是横着的,但用户拿手机是竖着的。如果不处理,画面就是歪的。WebRTC 提供了 MediaStreamTrack.applyConstraints() 来动态调整。
但更常见的是用 CSS 旋转:
video.style.transform = `rotate(${orientation}deg)`;
这里 orientation 来自设备 orientation 事件。我建议监听 deviceorientation 事件,实时调整。不过要注意,有些浏览器不支持这个事件。我曾经在 iPad 上调试了半天,发现 orientation 值一直是 0,最后改用 screen.orientation 才搞定。
还有一种情况是摄像头本身是倒装的。比如一些外接 USB 摄像头,物理上就是倒的。这时候可以用 facingMode 来指定:
facingMode: { exact: 'environment' } // 后置摄像头
facingMode: { exact: 'user' } // 前置摄像头
但 facingMode 在桌面端支持不好。我建议还是用 deviceId 精确选择。
知识体系总览
下面这张图总结了视频设备管理的核心流程。从设备枚举到最终渲染,每一步都有坑,但掌握了就能游刃有余。
好了,视频设备管理这块就讲到这里。记住三个核心:枚举要提前、分辨率要适配、帧率要动态。下次遇到用户说「画面卡」「选不了摄像头」,你就知道问题出在哪了。