音视频采集:getUserMedia 与设备管理
实时通信第一步是采集音视频。getUserMedia 怎么用?分辨率、帧率、码率怎么设?多个摄像头怎么切换?麦克风怎么选?这篇讲音视频采集的详细用法和常见坑。
大家好,我是黒漂技术佬。
WebRTC 的第一步就是采集音视频------打开摄像头、获取麦克风。看起来简单,就一个 getUserMedia,但里面细节不少:分辨率怎么设、帧率怎么控、前后摄像头怎么切、设备权限怎么处理。
这篇讲音视频采集的完整用法,从基础参数到设备管理,再到常见坑。
一、getUserMedia 基础
最简单的调用
javascript
const stream = await navigator.mediaDevices.getUserMedia({
video: true, // 要视频
audio: true // 要音频
});
返回一个 MediaStream 对象,里面包含视频轨和音频轨。
显示到页面
javascript
const video = document.querySelector('video');
video.srcObject = stream;
video 标签加上 autoplay playsinline 属性就能自动播放。
只开音频/只开视频
javascript
// 只要音频(语音通话)
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
// 只要视频
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
二、视频参数控制
分辨率设置
javascript
const stream = await navigator.mediaDevices.getUserMedia({
video: {
width: 1280,
height: 720
}
});
常用分辨率
| 分辨率 | 名称 | 适用场景 |
|---|---|---|
| 320×240 | QVGA | 低带宽、小窗口 |
| 640×480 | VGA | 普通视频通话 |
| 1280×720 | 720P | 高清通话 |
| 1920×1080 | 1080P | 高清会议、直播 |
| 3840×2160 | 4K | 超高画质(设备支持才行) |
理想值 vs 最小值/最大值
不一定能拿到你要的分辨率,摄像头不一定支持。可以设范围:
javascript
video: {
width: { min: 640, ideal: 1280, max: 1920 },
height: { min: 480, ideal: 720, max: 1080 }
}
ideal:理想值,尽量满足min/max:范围,超出就报错- 只写数字 = 理想值
帧率控制
javascript
video: {
width: 1280,
height: 720,
frameRate: 30 // 30帧
}
或者设范围:
javascript
frameRate: { min: 15, ideal: 30, max: 60 }
常见帧率:15、24、30、60。一般视频通话 30 帧足够,60 帧更流畅但码率高。
宽高比
javascript
video: {
aspectRatio: 1.777, // 16:9
width: 1280
}
前置/后置摄像头(移动端)
javascript
// 前置摄像头(默认)
video: { facingMode: "user" }
// 后置摄像头
video: { facingMode: "environment" }
移动端切换前后摄像头常用。
三、音频参数控制
基础设置
javascript
audio: true // 默认配置
详细参数
javascript
audio: {
echoCancellation: true, // 回声消除(开)
noiseSuppression: true, // 降噪(开)
autoGainControl: true, // 自动增益(开)
sampleRate: 48000, // 采样率
sampleSize: 16, // 采样位数
channelCount: 1 // 声道数(1=单声道,2=立体声)
}
三个重要的音频处理
回声消除(AEC)
- 作用:对方的声音从扬声器出来,不会又被麦克风录回去
- 视频通话必须开,不然有回声
降噪(NS)
- 作用:去掉背景噪音(键盘声、空调声)
- 一般都开,通话质量好很多
自动增益(AGC)
- 作用:声音小自动放大,声音大自动缩小
- 保持音量稳定
这三个默认都是开的,一般不用改。
四、设备管理
枚举所有设备
javascript
const devices = await navigator.mediaDevices.enumerateDevices();
devices.forEach(device => {
console.log(device.kind, device.label, device.deviceId);
// kind: audioinput / videoinput / audiooutput
});
按设备 ID 指定摄像头/麦克风
知道设备 ID 后,可以指定用哪个设备:
javascript
const stream = await navigator.mediaDevices.getUserMedia({
video: { deviceId: cameraDeviceId },
audio: { deviceId: micDeviceId }
});
切换摄像头
javascript
async function switchCamera(deviceId) {
// 关掉旧的
stream.getTracks().forEach(track => track.stop());
// 打开新的
const newStream = await navigator.mediaDevices.getUserMedia({
video: { deviceId: deviceId },
audio: true
});
// 更新显示
video.srcObject = newStream;
stream = newStream;
}
设备变化监听
插拔摄像头/麦克风时触发:
javascript
navigator.mediaDevices.ondevicechange = () => {
console.log('设备列表变化了');
// 重新枚举设备,更新UI
};
注意:没权限时 label 是空的
用户没授权摄像头/麦克风权限前,enumerateDevices 返回的设备 label 是空的,只能看到数量看不到名字。
授权后才能看到设备名称。
五、屏幕共享
采集屏幕
javascript
const stream = await navigator.mediaDevices.getDisplayMedia({
video: true,
audio: true // 可选,采集系统音频
});
会弹出系统的屏幕选择窗口,用户选共享整个屏幕、某个窗口、还是某个浏览器标签页。
屏幕共享参数
javascript
getDisplayMedia({
video: {
cursor: "always" // 显示鼠标:always / motion / never
},
audio: true // 共享系统音频(注意:不是所有平台都支持)
});
屏幕共享 + 摄像头
可以同时采集摄像头和屏幕,两个流:
javascript
const cameraStream = await getUserMedia({ video: true, audio: true });
const screenStream = await getDisplayMedia({ video: true });
然后可以把摄像头画到屏幕流上做成画中画,或者两路都推给对方。
注意
- getDisplayMedia 必须用户主动触发(点击按钮),不能自动弹
- 共享结束用户可以手动停止,页面也能监听停止事件
- 移动端支持有限
六、轨道操作
获取轨道
javascript
// 视频轨
const videoTracks = stream.getVideoTracks();
const videoTrack = videoTracks[0];
// 音频轨
const audioTracks = stream.getAudioTracks();
const audioTrack = audioTracks[0];
// 所有轨道
const tracks = stream.getTracks();
开关摄像头/麦克风
javascript
// 关闭摄像头(画面变黑)
videoTrack.enabled = false;
// 打开
videoTrack.enabled = true;
// 静音
audioTrack.enabled = false;
enabled = false 时还在采集,只是数据是黑的/静音的,带宽还占着。要完全停止用 stop()。
停止采集
javascript
// 停止某个轨道
videoTrack.stop();
// 停止所有轨道(挂断时用)
stream.getTracks().forEach(track => track.stop());
stop 之后轨道就废了,要重新采集得再调用 getUserMedia。
动态修改参数
采集过程中改分辨率、帧率:
javascript
await videoTrack.applyConstraints({
width: 640,
height: 480,
frameRate: 15
});
网络不好的时候降低画质,网络好的时候升回去。
七、权限处理
权限状态
javascript
const permissions = await navigator.permissions.query({ name: 'camera' });
console.log(permissions.state); // granted / denied / prompt
permissions.onchange = () => {
console.log('权限状态变了:', permissions.state);
};
常见错误
| 错误 | 原因 |
|---|---|
| NotAllowedError | 用户拒绝了权限 |
| NotFoundError | 没有摄像头/麦克风 |
| NotReadableError | 设备被其他程序占用了 |
| OverconstrainedError | 设置的参数设备不支持 |
| SecurityError | 非 HTTPS 环境 |
错误处理
javascript
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
} catch (err) {
switch (err.name) {
case 'NotAllowedError':
alert('请允许摄像头权限');
break;
case 'NotFoundError':
alert('没有找到摄像头');
break;
case 'NotReadableError':
alert('摄像头被占用了,请关闭其他使用摄像头的程序');
break;
default:
alert('打开摄像头失败:' + err.message);
}
}
八、兼容性处理
adapter.js
不同浏览器 API 有前缀差异,Google 官方的 adapter.js 可以抹平:
html
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
引入之后直接用标准 API 就行,不用管前缀。
功能检测
javascript
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
alert('您的浏览器不支持 WebRTC');
}
HTTPS 要求
正式环境必须 HTTPS,localhost 本地开发例外。
HTTP 环境下 getUserMedia 是 undefined。
九、实际场景:视频通话的采集配置
普通视频通话
javascript
const stream = await navigator.mediaDevices.getUserMedia({
video: {
width: { ideal: 1280 },
height: { ideal: 720 },
frameRate: 30
},
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true
}
});
720P 30帧,音频处理全开,平衡画质和带宽。
语音通话
javascript
const stream = await navigator.mediaDevices.getUserMedia({
video: false,
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
sampleRate: 48000
}
});
高清直播
javascript
const stream = await navigator.mediaDevices.getUserMedia({
video: {
width: 1920,
height: 1080,
frameRate: 30
},
audio: {
echoCancellation: false, // 直播不用回声消除
noiseSuppression: true,
sampleRate: 48000,
channelCount: 2 // 立体声
}
});
十、常见坑
坑 1:HTTP 环境用不了
必须 HTTPS,localhost 除外。本地开发用 localhost 没问题,部署上线要配 HTTPS。
坑 2:iOS Safari 限制多
- 必须用户点击才能播放(autoplay 没用)
- 第一次 getUserMedia 要用户手势触发
- playsinline 属性一定要加,不然会全屏播放
坑 3:分辨率设太高拿不到
写死 1080P,有些摄像头不支持就报错。用 ideal 不要写死,或者给个范围。
坑 4:设备被占用
摄像头被其他应用占了,打开失败。提示用户关闭其他程序。
坑 5:移动端前后摄像头切换
facingMode 不是所有浏览器都支持,最好用 deviceId 的方式切换更可靠。
坑 6:静音用 enabled = false 不省带宽
只是不发送数据内容,轨道还在,带宽和连接都保持着。要省带宽得移除轨道或者降低码率。
坑 7:屏幕共享不能自动弹
必须用户点击按钮触发,不能页面加载自动弹,浏览器会拦截。
十一、本篇小结
- getUserMedia 是音视频采集的入口,video 和 audio 两个参数控制
- 视频参数:分辨率、帧率、宽高比、前后摄像头
- 音频参数:回声消除、降噪、自动增益,默认都开
- enumerateDevices 枚举设备,deviceId 指定设备
- getDisplayMedia 采集屏幕,支持共享整个屏幕/窗口/标签页
- 轨道操作:enabled 开关、stop 停止、applyConstraints 动态改参数
- 注意权限处理和错误提示,用户体验很重要
- 必须 HTTPS 环境,注意移动端和 Safari 兼容性
下一篇讲媒体流与轨道:MediaStream 和 MediaTrack 的详细用法,怎么添加移除轨道、怎么克隆流。
我是黒漂技术佬。