20、音视频采集:摄像头与麦克风数据采集、屏幕录制、采集参数设置与优化
音视频采集,是整个音视频处理管线的起点。说白了,就是怎么把摄像头看到的画面、麦克风听到的声音,以及屏幕上的内容,变成我们程序能处理的数据流。这一步如果做不好,后面编码、推流、渲染全是白搭。
我个人习惯把采集模块称为「数据水龙头」——水龙头开得太大,CPU扛不住;开得太小,画面卡顿模糊。怎么调这个阀门,就是本章要聊的核心。
20.1 摄像头数据采集
摄像头采集,在Windows上我常用DirectShow,macOS上用AVFoundation,Linux下则是V4L2。跨平台项目,我一般推荐用OpenCV的VideoCapture,或者直接封装FFmpeg的libavdevice。
先看一个最简单的OpenCV采集示例:
#include <opencv2/opencv.hpp>
int main() {
cv::VideoCapture cap(0); // 打开默认摄像头
if (!cap.isOpened()) {
// 嗯,这里要注意:有些笔记本摄像头索引是0,外接可能是1
std::cerr << "无法打开摄像头" << std::endl;
return -1;
}
// 设置采集分辨率——我建议先设成1280x720,兼容性好
cap.set(cv::CAP_PROP_FRAME_WIDTH, 1280);
cap.set(cv::CAP_PROP_FRAME_HEIGHT, 720);
cap.set(cv::CAP_PROP_FPS, 30);
cv::Mat frame;
while (cap.read(frame)) {
// 处理每一帧
cv::imshow("Camera", frame);
if (cv::waitKey(1) == 'q') break;
}
return 0;
}
这段代码看起来简单,但坑不少。我在项目中遇到过一个问题:cap.set() 设置分辨率后,实际读到的帧尺寸可能和设置的不一样。为什么?因为摄像头驱动不一定支持你指定的参数,它会自动选一个最接近的。所以,一定要在设置后读取实际参数:
int actualWidth = cap.get(cv::CAP_PROP_FRAME_WIDTH);
int actualHeight = cap.get(cv::CAP_PROP_FRAME_HEIGHT);
double actualFps = cap.get(cv::CAP_PROP_FPS);
cap.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc('M','J','P','G')) 开启MJPG压缩模式,能大幅降低带宽占用。
20.2 麦克风数据采集
音频采集和视频采集是两套体系。视频是逐帧的,音频是流式的。我一般用PortAudio或者RtAudio做跨平台音频采集,Windows下也可以用WASAPI。
下面是一个用PortAudio采集麦克风的骨架:
#include <portaudio.h>
static int audioCallback(const void* inputBuffer, void* outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void* userData) {
// inputBuffer 就是麦克风采集到的原始PCM数据
// 你可以在这里做音频处理,比如降噪、增益
return paContinue;
}
int main() {
Pa_Initialize();
PaStream* stream;
PaStreamParameters inputParams;
inputParams.device = Pa_GetDefaultInputDevice(); // 默认麦克风
inputParams.channelCount = 2; // 立体声
inputParams.sampleFormat = paFloat32; // 32位浮点
inputParams.suggestedLatency = 0.01; // 10ms延迟
Pa_OpenStream(&stream, &inputParams, NULL, 44100,
256, paClipOff, audioCallback, NULL);
Pa_StartStream(stream);
// ... 程序运行中 ...
Pa_StopStream(stream);
Pa_CloseStream(stream);
Pa_Terminate();
return 0;
}
这里有个关键参数:framesPerBuffer。我设的是256,也就是每次回调处理256个采样帧。值越小,延迟越低,但CPU开销越大。值越大,CPU省了,但延迟会变高。你想想看,做直播的话,256是个不错的起点;做语音通话,可能得降到128甚至64。
20.3 屏幕录制
屏幕录制比摄像头采集复杂一些,因为涉及操作系统级别的权限和API。Windows上我用DXGI Duplication,macOS用CGDisplayStream,Linux用X11的ShmGetImage或者PipeWire。
这里给一个Windows下用DXGI采集屏幕的简化流程:
// 伪代码,展示核心逻辑
IDXGIOutputDuplication* duplication;
// 1. 创建DXGI设备
// 2. 获取桌面输出
// 3. 创建Duplication接口
while (true) {
IDXGIResource* desktopResource = nullptr;
DXGI_OUTDUPL_FRAME_INFO frameInfo;
duplication->AcquireNextFrame(500, &frameInfo, &desktopResource);
if (frameInfo.LastPresentTime != 0) {
// 有新的屏幕变化
// 将desktopResource转为纹理,再映射到CPU内存
// 得到像素数据后,可以编码或显示
}
duplication->ReleaseFrame();
}
屏幕录制最头疼的是性能问题。我曾经在4K显示器上做录屏,直接采集原始数据,帧率只有15fps。后来发现,不要每帧都全屏采集。DXGI的AcquireNextFrame会告诉你哪些区域发生了变化,只采集脏矩形区域,性能能提升好几倍。
20.4 采集参数设置与优化
参数优化,说白了就是在「质量」和「性能」之间找平衡。我整理了一张常用参数表:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 视频分辨率 | 1280x720 | 兼容性好,带宽适中。1080p以上需要硬件支持 |
| 视频帧率 | 30fps | 直播够用,游戏录制建议60fps |
| 音频采样率 | 44100Hz | CD音质,人声采集足够 |
| 音频位深 | 16bit 或 float32 | 16bit省带宽,float32方便处理 |
| 音频缓冲区 | 256~512帧 | 低延迟用256,稳定用512 |
| 视频像素格式 | NV12 或 YUYV | NV12是编码器最爱,YUYV兼容性好 |
优化方面,我总结了几条实战经验:
- 不要用RGB采集:摄像头原生输出大多是YUV格式,RGB是转换出来的。直接采集YUV,省掉颜色空间转换的开销。
- 多线程分离:采集线程只做采集,不要做编码或渲染。把采集到的帧丢进队列,让其他线程处理。
- 丢帧策略:如果处理速度跟不上采集速度,果断丢帧。我一般用「最新帧策略」——队列里只保留最新的一帧,旧的直接扔掉。
- 硬件加速:能用硬件编码就用硬件编码。NVIDIA的NVENC、Intel的QSV,都能把CPU占用降到个位数。
20.5 知识体系总览
下面这张图,把本章的核心知识点串了起来。你可以把它当作采集模块的设计蓝图:
从图上可以看得很清楚:三大采集源各有各的API和坑,但最终都汇聚到参数设置和优化策略上。参数设置是「选什么」,优化策略是「怎么用好」。这两层搞定了,采集模块就稳了。
最后说一句:采集这块,不要迷信理论值。同样的参数,在不同硬件上表现天差地别。我的习惯是写一个参数探测工具,自动遍历所有支持的分辨率和帧率组合,找出最稳定的那一组。嗯,这个方法虽然笨,但真的管用。