5、视频约束:视频约束详解(width、height、frameRate、facingMode)、前后摄像头切换
各位同学,今天我们来聊聊视频约束。这玩意儿,说白了就是告诉浏览器:“嘿,我想要什么样的摄像头画面”。
我个人习惯把视频约束比作“点菜”。你走进一家餐厅(浏览器),跟服务员(getUserMedia)说:“我要一份牛排,七分熟,配黑胡椒汁”。视频约束就是这份“订单”,你指定分辨率、帧率、用前置还是后置摄像头。
好,我们直接进入正题。
5.1 视频约束的核心参数
在 WebRTC 中,视频约束通过 MediaTrackConstraints 对象来定义。常用的四个参数是:
- width:视频宽度,单位像素。
- height:视频高度,单位像素。
- frameRate:帧率,每秒多少帧。
- facingMode:摄像头朝向,比如前置还是后置。
每个参数都可以指定三种模式:exact(精确值)、ideal(理想值)、min/max(范围)。
核心要点:exact 是强制要求,如果设备不支持,就会报错。ideal 是“尽量满足”,浏览器会选最接近的值。
举个例子:
const constraints = {
video: {
width: { ideal: 1280 },
height: { ideal: 720 },
frameRate: { ideal: 30 },
facingMode: { ideal: 'user' }
}
};
这段代码的意思是:我希望拿到 1280x720、30 帧、前置摄像头的画面。但如果设备不支持,浏览器会自动降级,比如降到 640x480、25 帧。不会直接报错。
我在项目中遇到过一种情况:用户用一台老旧的笔记本,摄像头最高只支持 640x480。我用了 exact: 1280,结果直接抛了个 OverconstrainedError。嗯,从那以后,我基本只用 ideal 或范围约束,除非业务强制要求高清。
5.2 分辨率约束详解
分辨率是视频最直观的参数。常见的分辨率有:
| 名称 | 宽 (px) | 高 (px) | 比例 |
|---|---|---|---|
| QVGA | 320 | 240 | 4:3 |
| VGA | 640 | 480 | 4:3 |
| HD | 1280 | 720 | 16:9 |
| Full HD | 1920 | 1080 | 16:9 |
| 4K | 3840 | 2160 | 16:9 |
你想想看,如果直接写死 width: 1920, height: 1080,很多手机前置摄像头根本达不到。所以更稳妥的做法是:
const constraints = {
video: {
width: { min: 640, ideal: 1280, max: 1920 },
height: { min: 480, ideal: 720, max: 1080 }
}
};
这样浏览器会在支持的范围内,尽量靠近 1280x720。如果设备只能到 640x480,也不会崩溃。
小技巧:如果你做的是视频通话,建议用 640x480 或 1280x720。4K 虽然清晰,但带宽消耗巨大,而且很多摄像头在 4K 下帧率会掉到 15fps 以下。
5.3 frameRate 帧率约束
帧率决定了视频的流畅度。常见的帧率有 15fps、24fps、30fps、60fps。
我个人习惯:
- 普通视频通话:30fps 足够。
- 直播或游戏:60fps 更好。
- 低带宽场景:15fps 也能接受。
帧率约束的写法:
const constraints = {
video: {
frameRate: { ideal: 30, max: 60 }
}
};
这里 ideal: 30 表示首选 30fps,但如果摄像头支持 60fps 且系统资源充足,也可能拿到 60fps。加上 max: 60 是防止某些设备自动跳到更高的帧率,导致发热或功耗问题。
我曾经做过一个远程医疗项目,要求摄像头必须输出 30fps 以上。我用了 frameRate: { exact: 30 },结果有一批设备只支持 25fps,直接报错。后来改成 min: 25, ideal: 30,问题就解决了。所以,除非万不得已,别用 exact。
5.4 facingMode 摄像头朝向
facingMode 用来指定用前置还是后置摄像头。可选值:
user:前置摄像头(自拍)。environment:后置摄像头(拍环境)。left/right:前置左右视角(很少用)。
写法:
const constraints = {
video: {
facingMode: { ideal: 'user' }
}
};
注意:facingMode 在桌面端通常只有一个摄像头,所以这个参数主要针对移动端。
警告:不要用 facingMode: { exact: 'user' }。如果设备没有前置摄像头,会直接报错。用 ideal 更安全。
5.5 前后摄像头切换实战
切换摄像头,本质上就是重新调用 getUserMedia,改变 facingMode 的值。
核心逻辑:
- 先获取当前视频流,停止所有轨道。
- 切换
facingMode的值(user↔environment)。 - 重新调用
getUserMedia,拿到新流。 - 把新流赋值给
<video>元素。
代码示例:
let currentFacingMode = 'user';
let stream = null;
async function switchCamera() {
// 停止旧流
if (stream) {
stream.getTracks().forEach(track => track.stop());
}
// 切换朝向
currentFacingMode = (currentFacingMode === 'user') ? 'environment' : 'user';
const constraints = {
video: {
facingMode: { ideal: currentFacingMode },
width: { ideal: 1280 },
height: { ideal: 720 }
}
};
try {
stream = await navigator.mediaDevices.getUserMedia(constraints);
document.getElementById('videoElement').srcObject = stream;
} catch (err) {
console.error('切换摄像头失败:', err);
}
}
这里有个细节:停止旧流很重要。如果不停止,摄像头灯可能一直亮着,而且会占用资源。
我在项目中遇到过一个问题:切换摄像头后,画面卡住了。后来发现是旧流没有完全释放,导致新流无法正常渲染。所以,记得在切换前调用 stream.getTracks().forEach(track => track.stop())。
5.6 知识体系总览
下面这张图,帮你把本章的知识点串起来:
这张图展示了视频约束的四个核心参数,以及它们之间的关系。记住,ideal 是你的好朋友,exact 是最后的手段。
5.7 避坑指南
- 不要同时约束 width 和 height 为 exact:如果设备不支持该比例,会报错。建议只约束一个维度,另一个用理想值。
- frameRate 不要设太高:60fps 在移动端容易导致发热和耗电。除非必要,30fps 是黄金平衡点。
- 切换摄像头前一定要停止旧流:否则可能造成资源泄漏,甚至导致新流无法获取。
- 测试不同设备:同一款手机,不同系统版本对约束的支持可能不同。我曾在某款 Android 手机上遇到
facingMode: 'user'不生效的情况,最后发现是系统 bug,只能通过枚举设备来解决。
我的经验:如果你需要精确控制分辨率,建议先调用 enumerateDevices() 获取设备列表,再结合 getUserMedia 的约束进行测试。这样可以提前知道设备支持哪些分辨率,避免运行时出错。
好了,视频约束的内容就讲到这里。记住,约束是“协商”而不是“命令”。用 ideal 和范围约束,你的代码会更健壮。