跳到主要内容

本篇是 WebRTC 101 系列 第 1 篇。原理/协议为主,代码点到为止——只给能验证结论的最小片段。

媒体采集:getUserMedia、轨道、约束与坑

进入连接之前,先搞清"采集"这件事本身。WebRTC 里采集与通话是分离的两个概念getUserMedia 只是从本机设备拿到本地轨道,它还什么都没"传"出去;真正的传输发生在第 2 篇开始讲的 RTCPeerConnection 上。先把这个心智模型钉住:

MediaStream = 一组轨道的容器(一个视频 track + N 个音频 track)。 MediaStreamTrack = 一条真实数据流(一路视频/一路音频)。轨道才是"有数据的东西"。

1. 三件套 API 的分工回顾

API作用域一句话
getUserMedia本机采集产出一条 MediaStream
RTCPeerConnection.addTrack加入会话把本地轨道放进点对点连接去编码传输
RTCDataChannel数据传输不走媒体轨道的任意数据(第 5 篇)

所以"摄像头画面能进通话"要两步:先 getUserMedia 采集,再 pc.addTrack(track) 把轨道交给连接——第 2 篇会看到这一步其实体现在 SDP 的 m= 段里。

2. getUserMedia:最小可用片段

const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
const video = document.querySelector("video");
video.srcObject = stream; // 注意是 srcObject,不是 src

几个立刻要知道的硬规则:

  • 返回 Promise,必须处理拒绝:用户拒绝授权 / 无设备 / 非安全上下文都会 reject;
  • 必须安全上下文httpslocalhost 才可用(file:// 不行);
  • video.srcObject 而不是 video.src:后者给 URL 字符串,前者接受 MediaStream
  • 预览/挂断后要主动停轨道track.stop(),否则摄像头灯常亮。

3. 约束(constraints):别用魔法数字

采集参数的写法分三层:总开关 → 每个轨道的理想值 → 尽力而为。WebRTC 的约束不是"必须满足",而是理想区间,浏览器在设备能力范围内尽量靠近:

const stream = await navigator.mediaDevices.getUserMedia({
video: {
width: { ideal: 1280 }, // 理想宽度
height: { ideal: 720 },
frameRate: { ideal: 30, max: 60 },
facingMode: "user", // 前置摄像头;environment = 后置
deviceId: { exact: selectedId }, // exact = 必须精确匹配
},
audio: {
echoCancellation: true, // 回声消除(AEC)
noiseSuppression: true, // 降噪
autoGainControl: true, // 自动增益
},
});
  • ideal vs exactideal 是"尽量",exact 是"没有就报错"。用 deviceId: { exact } 前先 enumerateDevices() 拿到真实 id。
  • 视频编码分辨率在编码端由浏览器决定,约束只是输入设备侧的期望——别指望约束 4K 就真给你传 4K。

4. 设备枚举与热插拔

const devices = await navigator.mediaDevices.enumerateDevices();
// [{ kind: "videoinput", deviceId, label, groupId }, ...]
navigator.mediaDevices.addEventListener("devicechange", () => {
// 拔插摄像头/耳机时重新枚举
});

label 在未授权前是空串——先 getUserMedia 授权,才能拿到可读的设备名(隐私设计)。

5. 回声、降噪与"处理管线"(原理)

很多人忽略:getUserMedia 出来的音频不是纯设备声音。浏览器(通过底层,如 Chromium 的 audio processing 与回声消除模块)在把它交给你之前,默认已经跑了 AEC(回声消除)、NS(降噪)、AGC(自动增益)。这是 VoIP 通话质量的基石——没有 AEC,扬声器出来的对方声音会再进麦克风,形成回声/啸叫。

采集 →(AEC/NS/AGC)→ MediaStreamTrack → 编码。WebRTC 标准强制实现最小音频能力(Opus 等,见 RFC 7478/6716),保证端到端可互通;处理管线的具体品质则依赖浏览器实现。

想拿到"干声"(如做混音/音效)用 audio: { echoCancellation: false } 等关掉——但要明白代价。

6. 关键坑位清单(按踩中频率排序)

  1. 非安全上下文被拒:本地记得开 localhost,线上必须 https
  2. 用户拒绝/无设备时没处理 reject:空实现会静默失败,加错误提示;
  3. 预览用 srcObject,有人写成 video.src = stream 导致黑屏;
  4. 摄像头灯不灭:没有 track.stop()
  5. iframe 内嵌被权限策略挡住:父页面需给 iframe 加 allow="camera; microphone",否则 getUserMedia 直接 reject;
  6. 约束被当"必须":用了 { width: 1920 } 以为必得 1080,其实浏览器可能给你别的——想锁定用 exact 并做好拿不到的准备。

7. 无设备环境怎么办(CI / 无头)

本地调试 / CI 想"假装有摄像头",浏览器启动参数给假设备即可,代码不用改:

# Chromium 系:自动应答授权 + 注入假摄像头/麦克风(生成彩色测试画面/蜂鸣声)
google-chrome --use-fake-device-for-media-stream \
--use-fake-ui-for-media-stream \
--allow-http ... # 配合 headless

WebDriver/Puppeteer 可在 launch() 时带这两个 flag。这是把"采集"环节做成可自动化测试的前提——采集本不该依赖真实硬件

参考与来源