2、采集流程基础:采集流程的五个阶段与状态机模型
做采集开发这些年,我见过太多新手一上来就写采集循环,结果不是丢帧就是设备挂死。其实采集这件事,说白了就是一套固定的流程——你只要把每个阶段该做的事做对,整个系统就稳了。
我个人习惯把采集流程拆成五个阶段:初始化、配置、启动、循环采集、停止/清理。这五个阶段就像五级台阶,少走一步都不行。
2.1 五个阶段详解
2.1.1 初始化阶段
这个阶段干的事很简单:分配资源、打开设备、注册回调。我刚开始做采集时,总觉得初始化就是调个 open() 完事,结果有一次在嵌入式设备上跑,忘了分配 DMA 缓冲区,采集一启动就崩了。
初始化阶段的核心任务:
- 设备枚举与选择:找到可用的采集设备(摄像头、采集卡、传感器等)
- 资源预分配:申请内存池、DMA 缓冲区、帧缓冲队列
- 驱动/内核模块加载:确保底层硬件就绪
- 错误状态检查:确认设备未被其他进程占用
2.1.2 配置阶段
配置阶段是「谈条件」的阶段。你要告诉设备:我要什么分辨率、什么帧率、什么像素格式。设备会告诉你:我能给什么。
配置项通常包括:
| 配置参数 | 说明 | 常见取值 |
|---|---|---|
| 分辨率 | 采集图像的宽高 | 1920x1080, 3840x2160 |
| 帧率 | 每秒采集帧数 | 30fps, 60fps, 120fps |
| 像素格式 | 色彩编码方式 | NV12, YUYV, RGB24 |
| 缓冲区数量 | 帧缓冲池大小 | 3~8 个 |
| 触发模式 | 采集触发方式 | 软件触发, 硬件触发 |
这里有个坑:配置顺序很重要。我遇到过先设分辨率再设帧率,结果帧率被自动降级的情况。你想想看,设备内部可能有依赖关系——先设大参数,再设小参数,设备会做兼容调整。反过来可能就报错了。
2.1.3 启动阶段
启动阶段就是「扣扳机」。配置完成后,调用 start() 或 streamon(),设备开始往缓冲区里填数据。
启动时要注意:
- 确认所有配置已提交:有些设备需要 commit 操作才能生效
- 检查缓冲区状态:所有缓冲区必须处于「空闲」或「入队」状态
- 启动顺序:先启动采集端,再启动处理端(避免数据堆积)
2.1.4 循环采集阶段
这是采集流程的核心。说白了就是一个死循环:
while (running) {
// 1. 从设备取一帧(阻塞或非阻塞)
frame = dequeue_buffer(device);
// 2. 处理帧数据(显示、编码、分析)
process_frame(frame);
// 3. 把缓冲区还给设备
enqueue_buffer(device, frame);
// 4. 检查是否要停止
if (should_stop()) break;
}
这个循环看起来简单,但实际项目里最容易出问题:
- 丢帧:处理速度跟不上采集速度,缓冲区被覆盖
- 死锁:取帧和处理帧用了同一个锁
- 内存泄漏:取出来的帧没有及时归还
我建议在循环里加一个性能监控——统计采集帧率、处理帧率、丢帧率。这样出问题时能快速定位是采集端慢了还是处理端慢了。
2.1.5 停止/清理阶段
很多人觉得停止就是调个 stop() 完事。其实不然。停止阶段要做的事:
- 停止采集流:通知设备停止数据推送
- 等待未完成帧:确保所有正在处理的帧完成
- 归还所有缓冲区:把设备占用的缓冲区释放
- 释放资源:关闭设备、释放内存、销毁线程
- 状态重置:把状态机重置到初始状态
2.2 状态机模型在采集中的应用
采集流程天然适合用状态机来管理。为什么?因为采集的每个阶段都有明确的进入条件和退出条件,状态之间不能乱跳。
下面是我常用的采集状态机模型:
这个状态机有几个关键规则:
- 单向流转:只能按 IDLE → INIT → CONFIG → RUNNING → STOPPING → IDLE 的顺序走
- 失败回退:任何阶段失败,都必须回到 IDLE 状态,不能原地重试
- 状态锁定:在某个状态下,只处理该状态对应的事件,忽略其他事件
2.3 状态机带来的好处
用状态机管理采集流程,说白了就是给系统上了一道「保险」:
- 防止非法操作:比如在 RUNNING 状态下调用 open(),状态机直接拒绝
- 简化错误处理:任何错误都统一回退到 IDLE,不用写复杂的 if-else
- 便于调试:看日志里的状态转换,就知道系统卡在哪一步
- 可测试性强:每个状态可以单独测试,状态转换也可以写单元测试
嗯,这里要注意:状态机不是万能的。如果你的采集流程特别简单(比如就一个 open-read-close),用状态机反而显得臃肿。但一旦涉及多线程、异步回调、热插拔,状态机就是你的救星。