5、音视频采集:摄像头调用、麦克风控制、分辨率与帧率设置
好,咱们进入移动端开发最基础也最核心的一环——音视频采集。说白了,就是怎么把摄像头和麦克风的数据拿到手,然后交给WebRTC去处理。这部分我踩过的坑不少,今天一次性给你讲透。
核心要点:在移动端,采集不是简单的打开设备就完事。分辨率、帧率、前后摄像头切换、音频回声消除……每一个细节都可能让你的应用卡死或者音画不同步。
5.1 摄像头调用:从 getUserMedia 说起
WebRTC 采集视频的标准接口是 navigator.mediaDevices.getUserMedia()。这个接口在桌面端很成熟,但在移动端,尤其是 Android 和 iOS 的 WebView 里,有很多隐藏的坑。
先看一个最基础的调用示例:
// 基础视频采集
async function startVideo() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: false
});
// 将 stream 绑定到 video 标签
const video = document.getElementById('localVideo');
video.srcObject = stream;
} catch (err) {
console.error('摄像头打开失败:', err);
}
}
嗯,这里要注意:video: true 会让浏览器自己选择默认摄像头。但在移动端,默认通常是后置摄像头。如果你需要前置摄像头(比如视频通话),必须显式指定:
// 强制使用前置摄像头
const stream = await navigator.mediaDevices.getUserMedia({
video: {
facingMode: 'user' // 前置
// facingMode: 'environment' // 后置
},
audio: false
});
我个人习惯在项目里先检测设备支持的摄像头列表,再让用户选择。用 enumerateDevices() 可以拿到所有设备信息:
async function getCameraList() {
const devices = await navigator.mediaDevices.enumerateDevices();
const cameras = devices.filter(d => d.kind === 'videoinput');
console.log('可用摄像头:', cameras);
// 每个 camera 有 deviceId,可以精确指定
}
避坑指南:我曾经在 Android 的 Chrome 上遇到过一个问题——调用 getUserMedia 后,页面直接白屏。后来发现是 WebView 没有授予摄像头权限。移动端必须处理权限请求的 Promise 拒绝情况,否则用户拒绝授权后,整个应用会卡死。
警告:iOS 上的 Safari 要求页面必须处于用户手势触发的上下文中才能调用 getUserMedia。也就是说,你不能在页面加载时自动打开摄像头,必须绑定一个按钮点击事件。否则接口会静默失败。
5.2 麦克风控制:不只是打开和关闭
音频采集看起来简单,但实际项目中,回声消除、降噪、自动增益控制这些参数才是关键。WebRTC 的 getUserMedia 允许你传递一组音频约束:
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true, // 回声消除
noiseSuppression: true, // 降噪
autoGainControl: true, // 自动增益
sampleRate: 48000, // 采样率
channelCount: 1 // 单声道
},
video: false
});
你想想看,如果不开回声消除,对方的声音从扬声器出来,又被你的麦克风收进去,再传回对方耳朵里——那就是无限循环的啸叫。我在做第一个视频通话 Demo 时就吃过这个亏,测试时两个人面对面坐着,声音越来越大,最后直接炸麦。
另外,移动端的麦克风权限比摄像头更敏感。iOS 上如果用户拒绝了麦克风权限,getUserMedia 会抛出一个 NotAllowedError。建议在 UI 层提前检测权限状态:
// 检测麦克风权限状态
navigator.permissions.query({ name: 'microphone' }).then(result => {
if (result.state === 'denied') {
// 引导用户去设置中开启
showPermissionGuide();
}
});
小技巧:在 Android 上,如果同时采集音频和视频,建议先请求音频权限再请求视频权限。因为有些国产 ROM 会把两个权限合并弹窗,用户一旦拒绝一个,另一个也会失败。
5.3 分辨率与帧率设置:平衡画质与性能
这是移动端最让人头疼的部分。分辨率越高,画面越清晰,但编码压力、网络带宽消耗也越大。帧率同理,30fps 流畅但耗电,15fps 省电但画面卡顿。
WebRTC 允许你通过 video 约束来指定理想的分辨率和帧率:
const stream = await navigator.mediaDevices.getUserMedia({
video: {
width: { ideal: 1280, min: 640, max: 1920 },
height: { ideal: 720, min: 480, max: 1080 },
frameRate: { ideal: 30, min: 15, max: 60 }
},
audio: true
});
这里有个关键点:ideal 是理想值,浏览器会尽量满足,但不保证。移动端设备能力参差不齐,比如一些低端 Android 机,你设 1080p 60fps,它可能直接降级到 480p 15fps,甚至采集失败。
我建议的做法是:先检测设备能力,再动态调整。用 MediaStreamTrack.getCapabilities() 可以拿到设备支持的范围:
const track = stream.getVideoTracks()[0];
const capabilities = track.getCapabilities();
console.log('支持的分辨率范围:', capabilities.width, capabilities.height);
console.log('支持的帧率范围:', capabilities.frameRate);
然后根据网络状况动态切换。比如弱网时降到 480p 15fps,强网时升到 720p 30fps。这个我在项目中用 RTCRtpSender.setParameters() 实现过,效果不错。
经验之谈:移动端视频通话,720p 30fps 是一个黄金平衡点。低于这个,画面模糊;高于这个,发热和耗电会明显增加。除非你做的是直播推流,否则不建议上 1080p。
5.4 前后摄像头切换
视频通话中,用户经常需要切换前后摄像头。WebRTC 没有直接的切换 API,但可以通过重新调用 getUserMedia 来实现。不过要注意,重新采集会中断当前的视频流。
更优雅的做法是:先停止当前视频轨道,再重新采集:
async function switchCamera() {
const stream = localVideo.srcObject;
const videoTrack = stream.getVideoTracks()[0];
videoTrack.stop(); // 停止当前轨道
const facingMode = currentFacing === 'user' ? 'environment' : 'user';
const newStream = await navigator.mediaDevices.getUserMedia({
video: { facingMode },
audio: false
});
// 替换视频轨道
const newVideoTrack = newStream.getVideoTracks()[0];
stream.removeTrack(videoTrack);
stream.addTrack(newVideoTrack);
localVideo.srcObject = stream;
currentFacing = facingMode;
}
嗯,这里要注意:stop() 之后,原来的摄像头指示灯会熄灭,然后重新亮起。如果切换太快,用户可能会看到黑屏闪烁。我一般会在切换前先显示一个 loading 动画,等新轨道就绪后再隐藏。
警告:iOS 上切换摄像头时,如果前后摄像头的分辨率能力不同(比如后置支持 4K,前置只支持 1080p),新采集的流可能会自动降级。建议在切换前先获取新摄像头的 capabilities,然后重新设置约束。
5.5 知识体系总览
下面这张图总结了本章的核心知识点和它们之间的关系。你可以把它当作一个快速索引,遇到问题时回来对照一下。
5.6 常见问题与调试技巧
最后分享几个我在项目中反复遇到的坑,你遇到了可以直接对照排查:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 摄像头黑屏 | 权限未授予 / 设备被占用 | 检查权限状态,确保没有其他应用占用摄像头 |
| 音频有回声 | echoCancellation 未开启 | 在约束中显式设置 echoCancellation: true |
| 切换摄像头后画面卡住 | 旧轨道未正确停止 | 调用 videoTrack.stop() 后再采集新流 |
| 低端机采集 1080p 失败 | 设备不支持高分辨率 | 使用 getCapabilities() 检测后降级 |
| iOS 上 getUserMedia 不执行 | 不在用户手势上下文中 | 将调用绑定到按钮点击事件 |
调试建议:在 Chrome 的开发者工具中,chrome://webrtc-internals 页面可以查看所有采集参数和统计信息。移动端可以通过 USB 调试连接后访问这个页面,非常实用。
好了,音视频采集这部分就讲到这里。记住一个原则:移动端永远不要假设设备能力,先检测,再适配。下一章我们会聊到编码与传输,到时候这些采集到的数据就要真正上路了。
公众号:蓝海资料掘金营,微信deep3321