采集:摄像头/麦克风与屏幕共享
采集是 RTC 流水线的起点,负责从硬件设备获取原始音视频数据。本章将从原理层面介绍采集机制、数据格式、内存模型和平台差异。
1. 采集概述
1.1 采集的本质
采集是将物理世界的模拟信号转换为数字信号的过程:
物理信号 → 传感器 → ADC(模数转换)→ 数字数据
↓
摄像头:光线 → CMOS/CCD → ADC → YUV/RGB
↓
麦克风:声波 → 振膜 → ADC → PCM
1.2 采集流水线
flowchart LR
subgraph 物理层
A[光线/声波]
end
subgraph 硬件层
B[传感器]
C[ADC]
end
subgraph 驱动层
D[设备驱动]
end
subgraph 系统层
E[系统采集API]
end
subgraph 应用层
F[MediaStream]
end
A --> B --> C --> D --> E --> F
2. 视频采集原理
2.1 图像传感器工作原理
| 传感器类型 |
工作原理 |
特点 |
| CMOS |
每个像素独立放大读出 |
功耗低、集成度高、主流选择 |
| CCD |
电荷逐行转移读出 |
画质好、功耗高、专业设备 |
2.2 拜耳阵列与色彩还原
传感器只能感知光强,需要拜耳滤镜获取色彩:
拜耳阵列排列:
R G R G R G
G B G B G B
R G R G R G
G B G B G B
R=红 G=绿 B=蓝
每个位置只能捕获一种颜色,需要去马赛克算法还原完整RGB
2.3 视频采集输出格式
原始格式(从传感器输出)
| 格式 |
位深 |
数据量(1080p) |
说明 |
| RAW |
10-14 bit |
极大 |
未经处理的传感器数据 |
| RGB24 |
24 bit |
6 MB/帧 |
红绿蓝各8位 |
| RGBA |
32 bit |
8 MB/帧 |
含透明通道 |
YUV 格式(视频处理标准)
| 格式 |
色度采样 |
数据量 |
适用场景 |
| I420 (YUV420P) |
4:2:0 |
1.5 MB/帧 |
WebRTC 标准格式 |
| NV12 (YUV420SP) |
4:2:0 |
1.5 MB/帧 |
GPU 高效处理 |
| I422 (YUV422P) |
4:2:2 |
2 MB/帧 |
色彩质量更高 |
| I444 (YUV444P) |
4:4:4 |
3 MB/帧 |
专业视频 |
YUV 格式内存布局
I420 (Planar) - Y/U/V 分开存储:
┌─────────────────────┐
│ Y 平面 (1920x1080) │ 亮度,完整分辨率
├──────────┬──────────┤
│ U 平面 │ V 平面 │ 色度,1/4 分辨率
│(960x540) │(960x540) │
└──────────┴──────────┘
NV12 (Semi-Planar) - Y 独立,UV 交错:
┌─────────────────────┐
│ Y 平面 (1920x1080) │
├─────────────────────┤
│ UV 交错 (960x540) │ U/V 交替存储
│ U V U V U V ... │
└─────────────────────┘
2.4 采集分辨率与帧率
常见分辨率
| 名称 |
分辨率 |
像素数 |
宽高比 |
| VGA |
640×480 |
307K |
4:3 |
| HD |
1280×720 |
922K |
16:9 |
| FHD |
1920×1080 |
2.07M |
16:9 |
| 2K |
2560×1440 |
3.69M |
16:9 |
| 4K |
3840×2160 |
8.29M |
16:9 |
帧率选择
| 帧率 |
适用场景 |
说明 |
| 15 fps |
静态内容、弱网 |
节省带宽 |
| 24 fps |
电影标准 |
有运动模糊 |
| 30 fps |
标准视频通话 |
平衡流畅度与带宽 |
| 60 fps |
游戏、运动 |
高流畅度 |
| 120+ fps |
慢动作、VR |
特殊需求 |
3. 音频采集原理
3.1 麦克风工作原理
flowchart LR
A[声波] --> B[振膜振动]
B --> C[电容/动圈转换]
C --> D[电信号]
D --> E[放大器]
E --> F[ADC]
F --> G[PCM数据]
3.2 音频采集参数
| 参数 |
说明 |
常见值 |
| 采样率 |
每秒采样次数 |
8000/16000/44100/48000 Hz |
| 位深 |
每个采样的位数 |
16/24/32 bit |
| 声道数 |
音频轨道数 |
1(单声道)/ 2(立体声) |
| 帧大小 |
每次采集的采样数 |
480/960/1024 |
3.3 音频数据量计算
数据量 = 采样率 × 位深 × 声道数
示例(48kHz/16bit/单声道):
48000 × 16 × 1 = 768 kbps = 96 KB/s
每 20ms 帧大小:
48000 × 0.02 × 2字节 = 1920 字节
3.4 音频采集格式
| 格式 |
说明 |
应用场景 |
| PCM(整数) |
原始采样值 |
最通用 |
| 浮点 PCM |
-1.0 ~ 1.0 范围 |
专业音频处理 |
| μ-law/A-law |
对数压缩 |
传统电话 |
4. 内存模型与数据流
4.1 采集缓冲区模型
flowchart LR
subgraph 硬件缓冲
A[传感器] --> B[环形缓冲]
end
subgraph 驱动缓冲
B --> C[驱动队列]
end
subgraph 应用缓冲
C --> D[应用缓冲区]
end
subgraph 处理
D --> E[编码/处理]
end
4.2 缓冲区大小权衡
| 缓冲区大小 |
延迟 |
稳定性 |
适用场景 |
| 小(10ms) |
低 |
易丢数据 |
实时通话 |
| 中(20-40ms) |
中等 |
平衡 |
通用场景 |
| 大(100ms+) |
高 |
稳定 |
录制 |
4.3 采集到内存 vs 采集到纹理
这是视频采集中最关键的性能优化点,决定了数据如何在系统中流转。
两种采集模式对比
采集到内存(CPU 可访问):
摄像头 → DMA → 系统内存 → CPU 处理 → 复制到显存 → GPU 渲染
↑
CPU 可以直接访问
但需要额外复制到 GPU
采集到纹理(GPU 直接访问):
摄像头 → DMA → 显存纹理 → GPU 渲染/编码
↑
GPU 直接访问
无需 CPU 参与
| 特性 |
采集到内存 |
采集到纹理 |
| 数据位置 |
系统内存(RAM) |
显存(VRAM) |
| CPU 访问 |
直接访问 |
需要映射或回读 |
| GPU 访问 |
需要上传到显存 |
直接访问 |
| 内存拷贝 |
需要 CPU→GPU 拷贝 |
无拷贝 |
| 适用场景 |
CPU 处理、编码 |
渲染、GPU 编码 |
| 延迟 |
较高(多一次拷贝) |
较低 |
各平台纹理采集支持
| 平台 |
API |
纹理类型 |
| Windows |
Media Foundation + D3D11 |
ID3D11Texture2D |
| macOS |
AVFoundation + Metal |
MTLTexture |
| iOS |
AVCaptureSession + Metal |
MTLTexture |
| Android |
Camera2 + SurfaceTexture |
SurfaceTexture / OpenGL ES |
| 浏览器 |
WebRTC 内部处理 |
WebGL Texture |
4.4 内存拷贝问题与零拷贝优化
传统采集流程的拷贝开销
传统流程(多次拷贝):
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ 摄像头 │───▶│驱动缓冲 │───▶│系统缓冲 │───▶│应用缓冲 │───▶│显存缓冲 │
│ DMA │ │(内核态) │ │(用户态) │ │(处理) │ │(渲染) │
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
│ │ │ │
└──────────────┴──────────────┴──────────────┘
拷贝 1 拷贝 2 拷贝 3
1080p@30fps 每秒数据量:1920×1080×1.5×30 ≈ 93 MB/s
3 次拷贝 = 279 MB/s 内存带宽占用
零拷贝优化方案
| 方案 |
原理 |
拷贝次数 |
适用场景 |
| 共享内存 |
应用直接访问驱动缓冲 |
1 |
跨进程通信 |
| 内存映射(mmap) |
将内核缓冲映射到用户态 |
1 |
Linux |
| Direct3D Surface |
采集直接到 GPU 纹理 |
0 |
Windows |
| Metal Texture |
采集直接到 Metal 纹理 |
0 |
Apple |
| SurfaceTexture |
采集到 OpenGL 纹理 |
0 |
Android |
零拷贝采集流程
零拷贝流程:
┌─────────┐ ┌─────────────────┐ ┌─────────┐
│ 摄像头 │───▶│ 共享缓冲/纹理 │───▶│ 消费者 │
│ DMA │ │ (无拷贝传递) │ │ │
└─────────┘ └─────────────────┘ └─────────┘
消费者可以是:
- GPU 渲染管线(直接绑定纹理)
- 硬件编码器(直接编码纹理)
- CPU 处理(按需映射访问)
4.5 避免内存到显存拷贝的策略
为什么内存到显存拷贝代价高
内存到显存拷贝的代价:
系统内存 ──────────────────▶ 显存
│ │
│ PCIe 总线传输 │
│ 带宽约 16 GB/s │
│ 1080p@30fps ≈ 93 MB/s │
│ │
└───────────────────────────┘
问题:
1. 占用 PCIe 带宽,影响其他传输
2. CPU 需要参与调度
3. 增加延迟
4. 功耗增加
优化策略一览
┌────────────────────────────────────────────────────────────┐
│ 避免内存→显存拷贝 │
├────────────────────────────────────────────────────────────┤
│ │
│ 1. 采集直出纹理 │
│ 摄像头 ──▶ GPU 纹理 ──▶ 渲染/编码 │
│ 适用于:渲染预览、硬件编码 │
│ │
│ 2. 共享纹理跨组件 │
│ 采集纹理 ──▶ 共享句柄 ──▶ 编码器/渲染器 │
│ 适用于:多组件协作 │
│ │
│ 3. GPU 上完成所有处理 │
│ 纹理 ──▶ GPU 处理 ──▶ GPU 编码 │
│ 适用于:美颜、滤镜等 │
│ │
│ 4. 仅在必要时回读 │
│ 纹理 ──▶ (按需) ──▶ CPU 处理 │
│ 适用于:需要 CPU 分析的场景 │
│ │
└────────────────────────────────────────────────────────────┘
各平台共享纹理机制
| 平台 |
共享机制 |
跨组件共享 |
| Windows |
ID3D11Texture2D + 共享句柄 |
D3D11 ↔ D3D12 ↔ CUDA |
| macOS/iOS |
MTLTexture + IOSurface |
Metal ↔ OpenGL ↔ CoreAnimation |
| Android |
SurfaceTexture / HardwareBuffer |
Camera ↔ OpenGL ↔ MediaCodec |
| Linux |
DMABUF / EGLImage |
V4L2 ↔ OpenGL ↔ VA-API |
4.6 采集数据流转场景分析
场景一:仅渲染预览(最简单)
采集 ──▶ 纹理 ──▶ 渲染到屏幕
最优路径:摄像头 → GPU 纹理 → 直接渲染
拷贝次数:0
CPU 参与:几乎无
场景二:渲染 + 软编码
采集 ──▶ 纹理 ──▶ 渲染
│
└──▶ CPU 回读 ──▶ 软编码
需要权衡:
- 回读会触发 GPU→CPU 拷贝
- 可以降低回读频率或分辨率
- 或改用硬件编码
场景三:渲染 + 硬编码
采集 ──▶ 共享纹理 ──┬──▶ 渲染
└──▶ 硬件编码器
最优路径:纹理共享,无拷贝
拷贝次数:0
编码效率:高
场景四:渲染 + 硬编码 + CPU 处理
采集 ──▶ 纹理 ──┬──▶ 渲染
├──▶ 硬件编码
└──▶ 降采样回读 ──▶ CPU 处理
优化:
- CPU 处理使用降采样后的数据
- 减少回读数据量
- 异步回读避免阻塞
4.7 零拷贝实现注意事项
数据生命周期管理
零拷贝的数据生命周期挑战:
传统拷贝模式:
原数据 ──拷贝──▶ 新数据
原数据可以立即释放
零拷贝模式:
原数据 ──引用──▶ 消费者A
──引用──▶ 消费者B
需要等所有消费者使用完毕才能释放
解决方案:
- 引用计数
- 信号量/条件变量
- 回调通知
- 固定缓冲池 + 轮转
线程安全与同步
生产者-消费者同步:
生产者(采集线程) 消费者(处理线程)
│ │
▼ ▼
写入缓冲区 读取缓冲区
│ │
├─────────同步点──────────┤
│
需要保证:
- 写入完成后再可读
- 读取时不被覆盖
- 缓冲区轮转正确
内存对齐与格式约束
| 约束 |
说明 |
影响 |
| 内存对齐 |
GPU 纹理通常要求 16/64 字节对齐 |
影响缓冲区分配策略 |
| 格式匹配 |
采集格式必须与消费格式一致 |
可能需要格式转换 |
| 跨平台差异 |
不同平台的纹理格式不同 |
需要抽象层适配 |
4.8 数据所有权与生命周期
采集数据流通过程中的所有权转移:
硬件 → 驱动 → 系统API → 应用 → 编码器
│ │ │ │ │
└──────┴───────┴────────┴───────┘
需要明确数据何时可以释放
关键问题:
- 谁分配内存?
- 谁负责释放?
- 数据是否需要复制?
- 引用计数如何管理?
5. 各平台采集机制
5.1 Windows 采集架构
flowchart TB
subgraph 应用层
A[应用程序]
end
subgraph API层
B[Media Foundation]
C[DirectShow]
D[WASAPI]
end
subgraph 系统层
E[AVStream 驱动]
end
subgraph 硬件层
F[摄像头/麦克风]
end
A --> B --> E --> F
A --> C --> E
A --> D --> E
| API |
用途 |
特点 |
| Media Foundation |
现代 Windows 视频/音频 |
推荐,功能完整 |
| DirectShow |
传统视频采集 |
兼容性好 |
| WASAPI |
低延迟音频 |
专业音频 |
| WDM/KS |
内核级访问 |
最低延迟 |
5.2 macOS/iOS 采集架构
flowchart TB
subgraph 应用层
A[应用程序]
end
subgraph 框架层
B[AVFoundation]
C[CoreAudio]
D[ScreenCaptureKit]
end
subgraph 系统层
E[IOKit]
F[CoreMedia]
end
subgraph 硬件层
G[摄像头/麦克风]
end
A --> B --> E --> G
A --> C --> F
A --> D
| 框架 |
用途 |
特点 |
| AVFoundation |
视频/音频采集 |
现代 API,推荐 |
| CoreAudio |
音频处理 |
底层,灵活 |
| ScreenCaptureKit |
屏幕采集 |
macOS 12.3+ |
| CoreMediaIO |
设备访问 |
底层框架 |
5.3 Android 采集架构
flowchart TB
subgraph 应用层
A[App]
end
subgraph Framework层
B[Camera2 API]
C[CameraX]
D[AudioRecord]
E[MediaProjection]
end
subgraph Native层
F[Camera HAL]
G[Audio HAL]
end
subgraph 内核层
H[V4L2]
I[ALSA]
end
A --> B --> F --> H
A --> C --> B
A --> D --> G --> I
A --> E
| API |
用途 |
特点 |
| Camera2 |
视频采集 |
底层控制,复杂 |
| CameraX |
视频采集 |
简化版,推荐 |
| AudioRecord |
音频采集 |
原始 PCM |
| MediaProjection |
屏幕采集 |
需要用户授权 |
5.4 Linux 采集架构
flowchart TB
subgraph 应用层
A[应用程序]
end
subgraph 库层
B[libv4l]
C[ALSA/PulseAudio/PipeWire]
end
subgraph 内核层
D[V4L2]
E[ALSA]
end
subgraph 硬件层
F[设备]
end
A --> B --> D --> F
A --> C --> E
| 子系统 |
用途 |
说明 |
| V4L2 |
视频采集 |
Video for Linux 2 |
| ALSA |
音频采集 |
底层音频 |
| PulseAudio |
音频服务 |
用户态音频服务 |
| PipeWire |
现代 media 框架 |
整合音视频 |
5.5 浏览器采集机制
flowchart TB
subgraph Web应用
A[JavaScript]
end
subgraph Web API
B[getUserMedia]
C[getDisplayMedia]
D[enumerateDevices]
end
subgraph 浏览器引擎
E[MediaStream 实现]
F[平台适配层]
end
subgraph 系统API
G[Windows/macOS/Linux API]
end
A --> B --> E --> F --> G
A --> C
A --> D
6. 屏幕采集原理
6.1 屏幕采集方式
| 方式 |
原理 |
性能 |
兼容性 |
| GDI/BitBlt |
系统截图 API |
一般 |
Windows |
| DXGI Desktop Duplication |
GPU 级别复制 |
高效 |
Windows 8+ |
| X11/XDamage |
X Window 截图 |
一般 |
Linux |
| CoreGraphics |
macOS 截图 |
一般 |
macOS |
| ScreenCaptureKit |
现代框架 |
高效 |
macOS 12.3+ |
6.2 屏幕采集优化技术
| 技术 |
说明 |
效果 |
| 脏区域检测 |
只捕获变化区域 |
减少 CPU |
| 硬件加速 |
GPU 直接复制 |
减少 CPU |
| 光标分离 |
光标单独处理 |
效率提升 |
| 帧率自适应 |
根据内容调整 |
节省资源 |
6.3 屏幕采集 vs 摄像头采集
| 特性 |
屏幕采集 |
摄像头采集 |
| 帧率需求 |
较低(5-15fps) |
较高(30fps) |
| 分辨率 |
高(1080p-4K) |
中(720p-1080p) |
| 内容类型 |
静态为主,文字 |
动态为主,人脸 |
| 编码策略 |
屏幕内容编码优化 |
自然视频编码 |
7. 采集注意事项
7.1 时序与同步
| 问题 |
原因 |
解决方案 |
| 音视频不同步 |
独立采集,时钟漂移 |
统一时间戳基准 |
| 帧间隔不均匀 |
系统调度、缓冲抖动 |
使用时间戳而非系统时间 |
| 丢帧 |
处理跟不上采集 |
合理的缓冲区设计 |
7.2 资源管理
采集设备是独占资源,需要正确管理生命周期:
1. 打开设备 → 获取访问权
2. 配置参数 → 设置格式、分辨率等
3. 开始采集 → 数据持续输出
4. 停止采集 → 停止数据流
5. 释放设备 → 允许其他应用使用
常见问题:
- 设备未释放导致其他应用无法访问
- 多次打开同一设备
- 异常情况下资源泄漏
7.3 权限模型
| 平台 |
权限机制 |
说明 |
| 浏览器 |
MediaDevices API |
用户弹窗授权 |
| iOS |
Info.plist 声明 |
首次使用弹窗 |
| Android |
Manifest 声明 + 运行时权限 |
Android 6.0+ |
| macOS |
TCC 权限 |
系统偏好设置 |
| Windows |
UAC / 隐私设置 |
Windows 10+ |
7.4 热插拔处理
设备热插拔事件流:
设备插入 → 系统检测 → 驱动加载 → 设备可用 → 通知应用
↓
应用处理:
- 枚举新设备
- 更新 UI
- 可能自动切换
设备拔出 → 系统检测 → 驱动卸载 → 设备不可用 → 通知应用
↓
应用处理:
- 停止使用该设备
- 切换到其他设备
- 更新 UI
7.5 性能考量
| 因素 |
影响 |
优化建议 |
| 分辨率 |
CPU/GPU 负载 |
按需选择,不要过高 |
| 帧率 |
处理频率 |
实时通话 30fps 足够 |
| 格式转换 |
CPU 消耗 |
优先使用硬件支持的格式 |
| 缓冲区大小 |
延迟 vs 稳定 |
根据场景权衡 |
8. 多设备与并发采集
8.1 多摄像头采集
场景:同时使用前置和后置摄像头
挑战:
- 大多数系统 API 一次只能打开一个摄像头
- 需要特殊的多摄 API 支持
- 数据量翻倍,处理压力增大
解决方案:
- Android: CameraManager 支持多摄
- iOS: AVCaptureMultiCamSession
- 浏览器: 分别调用 getUserMedia(受限)
8.2 音视频同步采集
flowchart TB
subgraph 采集
A[视频采集] --> C[时间戳 T1]
B[音频采集] --> D[时间戳 T2]
end
subgraph 同步
C --> E[共享时钟基准]
D --> E
E --> F[计算相对偏移]
end
subgraph 输出
F --> G[同步的音视频流]
end
8.3 屏幕共享 + 摄像头组合
常见布局模式:
画中画模式:
┌────────────────────────────┐
│ │
│ 屏幕共享内容 │
│ │
│ ┌────────┐ │
│ │ 摄像头 │ │
│ └────────┘ │
└────────────────────────────┘
并排模式:
┌──────────────┬─────────────┐
│ │ │
│ 屏幕共享 │ 摄像头 │
│ │ │
└──────────────┴─────────────┘
9. 常见问题与排查
9.1 采集失败原因
| 问题 |
原因 |
排查方向 |
| 黑屏 |
设备未就绪、权限问题 |
检查设备状态和权限 |
| 无声音 |
设备选择错误、静音 |
检查设备配置 |
| 帧率低 |
系统负载高、参数过高 |
降低分辨率/帧率 |
| 延迟大 |
缓冲区过大 |
减小缓冲区 |
| 花屏 |
格式不匹配、内存问题 |
检查格式协商 |
9.2 跨平台兼容性
| 差异点 |
Windows |
macOS |
Linux |
移动端 |
| 默认格式 |
NV12 |
BGRA |
I420 |
NV21/YV12 |
| 权限 |
系统设置 |
TCC |
XDG |
运行时 |
| 设备枚举 |
Media Foundation |
AVFoundation |
V4L2 |
CameraManager |
| 屏幕采集 |
DXGI |
ScreenCaptureKit |
X11/Pipewire |
MediaProjection |
10. 总结
采集是 RTC 的起点,需要理解以下核心概念:
| 模块 |
关键点 |
| 视频采集 |
传感器原理、YUV 格式、分辨率帧率选择 |
| 音频采集 |
采样率、位深、PCM 格式 |
| 内存模型 |
缓冲区设计、零拷贝、数据所有权 |
| 平台差异 |
各系统 API、格式偏好、权限机制 |
| 屏幕采集 |
脏区域检测、硬件加速、内容类型 |
| 资源管理 |
设备生命周期、热插拔、并发采集 |
下一章将介绍如何将采集到的数据进行渲染显示。