rpicam-hello 的 event_loop 是 从相机采集到显示预览 的完整运行管线。下面我帮你提炼出主要流程,并用 Pipeline 的形式总结:
🔧 Pipeline 步骤拆解
1️⃣ 初始化配置
Options const *options = app.GetOptions();
- 从
Options解析命令行参数(分辨率、帧率、超时、翻转等)。 - 提供后续 pipeline 的运行参数。
2️⃣ 相机启动
app.OpenCamera();
app.ConfigureViewfinder();
app.StartCamera();
- OpenCamera:打开
libcamera,选择合适的 Camera。 - ConfigureViewfinder:配置 Viewfinder 流(通常是预览低分辨率流)。
- StartCamera:启动相机流并提交初始的 request 队列(开始采帧)。
3️⃣ 事件循环
RPiCamApp::Msg msg = app.Wait();
这个 app.Wait() 方法的底层实现是一个典型的线程安全阻塞队列取消息的机制,它确保主线程能安全地等待摄像头帧事件或其他消息的到来。我们来逐行拆解:
1️⃣ msgqueue的底层实现
T Wait()
{
std::unique_lock<std::mutex> lock(mutex_);
cond_.wait(lock, [this] { return !queue_.empty(); });
T msg = std::move(queue_.front());
queue_.pop();
return msg;
}
这里 T 通常是 RPiCamApp::Msg 类型。
2️⃣ 关键成员
mutex_:互斥锁,保护消息队列queue_的访问。cond_:条件变量(std::condition_variable),用于线程同步和阻塞唤醒。queue_:消息队列(std::queue<T>),存储从相机回调或其他线程推送的事件。
3️⃣ 运行机制逐行解析
① 加锁
std::unique_lock<std::mutex> lock(mutex_);
- 获取
mutex_锁,确保在Wait()期间对queue_的访问是线程安全的。
② 阻塞等待
cond_.wait(lock, [this] { return !queue_.empty(); });
-
条件变量等待:
- 当前线程会被挂起,直到
queue_非空。 - 其他线程(通常是相机帧回调线程)调用
cond_.notify_one()或cond_.notify_all()才会唤醒。
- 当前线程会被挂起,直到
-
lambda作为谓词:- 只有当
queue_不为空时才会退出等待。 - 避免虚假唤醒(spurious wakeup)。
- 只有当
③ 取出消息
T msg = std::move(queue_.front());
queue_.pop();
- 从队列头取出一条消息(move 语义避免拷贝)。
pop()移除队列头部消息。
④ 返回消息
return msg;
-
将取出的
msg返回给调用者(如event_loop)。 -
调用线程即可根据消息类型处理,例如:
RequestComplete→ 调用ShowPreviewTimeout→ 重启相机Quit→ 退出循环
4️⃣ 消息流来源
消息是由 RPiCamApp 的其他线程 PostMessage 推送的:
void Post(Msg const &msg)
{
std::lock_guard<std::mutex> lock(mutex_);
queue_.push(msg);
cond_.notify_one(); // 唤醒 Wait()
}
例如:
- 相机帧完成回调:收到帧后调用
PostMessage({ MsgType::RequestComplete, completed_request }) - 超时检测:定时器线程检测到超时后推送
MsgType::Timeout - 退出事件:用户终止时推送
MsgType::Quit
✅ 总结:
Wait()是一个阻塞式消息获取方法。- 使用
mutex + condition_variable保证线程安全和高效等待。 - 和
PostMessage搭配,实现摄像头帧采集与事件处理的生产者-消费者模型。
要不要我帮你继续 追溯 PostMessage 调用链,看相机帧是如何从 libcamera 回调一路传到 Wait() 的?(这样你能看到完整的帧采集 → 消息推送 → 主线程消费的链路)
- 进入消息循环,等待相机事件(RequestComplete、Timeout、Quit)。
app.Wait()阻塞直到收到新的帧或事件。
4️⃣ 事件处理
-
Timeout:
app.StopCamera(); app.StartCamera();设备超时,重启相机,确保 pipeline 恢复。
-
Quit:
return;用户请求退出。
-
RequestComplete:
CompletedRequestPtr &completed_request = std::get<CompletedRequestPtr>(msg.payload);摄像头成功采集到一帧,使用
std::get从消息负载中提取帧。
5️⃣ 预览与渲染
app.ShowPreview(completed_request, app.ViewfinderStream());
- 将采集到的
CompletedRequest帧传入ShowPreview。 - 映射帧 buffer → 渲染到预览窗口(EGL/OpenGL 或 DRM)。
- 可能执行后处理(JSON pipeline、OpenCV、TFLite 推理等)。
6️⃣ 超时判断
if (options->Get().timeout && (now - start_time) > options->Get().timeout.value)
return;
- 如果命令行指定
-t超时参数,到达时退出循环。
📊 Pipeline 总览图
flowchart TD
A[Options.Parse()] --> B[OpenCamera]
B --> C[ConfigureViewfinder]
C --> D[StartCamera]
D --> E[Event Loop]
E --> |Timeout| F[StopCamera & Restart]
E --> |Quit| G[Exit Loop]
E --> |RequestComplete| H[Extract CompletedRequest]
H --> I[ShowPreview]
I --> J[Render Frame & Post-Processing]
J --> E
✅ 总结
- 初始化阶段:解析参数 → 打开相机 → 配置预览 → 启动相机
- 循环阶段:事件驱动(采帧/超时/退出)
- 核心路径:
RequestComplete → CompletedRequestPtr → ShowPreview → 渲染 - 附加功能:超时重启、可选后处理、用户退出。
要不要我再帮你 对 ShowPreview 的内部流程(EGL/DRM 渲染和 JSON 后处理集成)做一次拆解?这样你就能完全理解如何接入自定义 AI 推理逻辑了。
浙公网安备 33010602011771号