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);
我曾经踩过的坑: 在某个USB摄像头上,我设了1920x1080,结果实际返回的是640x480。后来发现是USB带宽不够,摄像头自动降级了。解决方案是先用 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。

我的经验: 音频采样率用44100Hz就够了,48kHz对普通麦克风意义不大。位深用16位整数或32位浮点——浮点的好处是后续做音量归一化不用考虑溢出。

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会告诉你哪些区域发生了变化,只采集脏矩形区域,性能能提升好几倍。

核心要点: 屏幕录制一定要做增量采集。全屏30fps的原始数据量大约是:3840x2160x4x30 ≈ 1GB/s。这个数据量,任何软件都扛不住。

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占用降到个位数。
注意: 不要盲目追求高参数。我曾经在某个项目里把摄像头设成4K 60fps,结果CPU占用直接飙到90%,画面反而因为丢帧变得卡顿。后来降到1080p 30fps,一切顺畅。合适的才是最好的。

20.5 知识体系总览

下面这张图,把本章的核心知识点串了起来。你可以把它当作采集模块的设计蓝图:

音视频采集知识体系 摄像头采集 麦克风采集 屏幕录制 参数设置:分辨率 | 帧率 | 采样率 | 位深 | 像素格式 | 缓冲区大小 优化策略:YUV原生采集 | 多线程分离 | 最新帧策略 | 硬件加速 输出:原始音视频数据流 → 编码/处理/推流

从图上可以看得很清楚:三大采集源各有各的API和坑,但最终都汇聚到参数设置和优化策略上。参数设置是「选什么」,优化策略是「怎么用好」。这两层搞定了,采集模块就稳了。

最后说一句:采集这块,不要迷信理论值。同样的参数,在不同硬件上表现天差地别。我的习惯是写一个参数探测工具,自动遍历所有支持的分辨率和帧率组合,找出最稳定的那一组。嗯,这个方法虽然笨,但真的管用。


公众号:蓝海资料掘金营,微信deep3321