音视频采集:getUserMedia 与设备管理

音视频采集: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 的详细用法,怎么添加移除轨道、怎么克隆流。

我是黒漂技术佬。

相关推荐
数据法师2 小时前
musicDownload :多平台音频资源聚合与本地化存储
音视频
小鹿软件办公3 小时前
通过调整编码参数使用 VLC 媒体播放器无损压缩视频方法
运维·网络·音视频
音乐宝贝家3 小时前
从合板到面单:麦杰克AC系列声学结构拆解与选购决策
新媒体运营·音视频·社交电子·业界资讯·媒体·用户运营·零售
AI服务老曹3 小时前
摄像机安装AI视频分析参数配置说明:POC现场交付全流程指南
人工智能·音视频
myenjoy_13 小时前
I2S 音频与数字音频接口详解
macos·音视频·xcode
cxr8281 天前
第14章 创意音乐与视频的多模态智能体创作
人工智能·音视频·智能体·hermes
梦帮科技1 天前
基于EVM架构的Web3音频确权与RNS Token智能合约设计深度解析
人工智能·python·架构·web3·区块链·音视频·智能合约
最爱老式锅包肉1 天前
《HarmonyOS技术精讲-Core Speech Kit(基础语音服务)》第1篇:音频管理基础与语音服务入门
华为·音视频·harmonyos
“码”力全开2 天前
AI视频分析飞书告警常见问题和排查清单
人工智能·音视频·飞书