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 的值。

核心逻辑:

  1. 先获取当前视频流,停止所有轨道。
  2. 切换 facingMode 的值(userenvironment)。
  3. 重新调用 getUserMedia,拿到新流。
  4. 把新流赋值给 <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 知识体系总览

下面这张图,帮你把本章的知识点串起来:

视频约束 width min / ideal / max height min / ideal / max frameRate ideal / max facingMode user / environment 切换摄像头 重新调用 getUserMedia 核心原则:用 ideal,少用 exact

这张图展示了视频约束的四个核心参数,以及它们之间的关系。记住,ideal 是你的好朋友,exact 是最后的手段。

5.7 避坑指南

  • 不要同时约束 width 和 height 为 exact:如果设备不支持该比例,会报错。建议只约束一个维度,另一个用理想值。
  • frameRate 不要设太高:60fps 在移动端容易导致发热和耗电。除非必要,30fps 是黄金平衡点。
  • 切换摄像头前一定要停止旧流:否则可能造成资源泄漏,甚至导致新流无法获取。
  • 测试不同设备:同一款手机,不同系统版本对约束的支持可能不同。我曾在某款 Android 手机上遇到 facingMode: 'user' 不生效的情况,最后发现是系统 bug,只能通过枚举设备来解决。

我的经验:如果你需要精确控制分辨率,建议先调用 enumerateDevices() 获取设备列表,再结合 getUserMedia 的约束进行测试。这样可以提前知道设备支持哪些分辨率,避免运行时出错。

好了,视频约束的内容就讲到这里。记住,约束是“协商”而不是“命令”。用 ideal 和范围约束,你的代码会更健壮。


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