4、Request流程深度剖析:Capture Request生命周期、Result回调机制、Frame Number同步策略
各位做Camera HAL的兄弟,今天咱们来聊聊Request流程。说实话,这个环节是HAL层最核心、也最容易出bug的地方。我见过太多同事在Request生命周期管理上翻车,轻则预览卡顿,重则直接ANR。嗯,咱们今天就把这块彻底讲透。
4.1 Capture Request的生命周期
一个Request从诞生到消亡,到底经历了什么?我习惯把它分成五个阶段:
| 阶段 | 状态 | 说明 |
|---|---|---|
| 1 | PENDING | App通过CameraManager提交,HAL还没开始处理 |
| 2 | ACTIVE | HAL开始处理,Sensor正在曝光 |
| 3 | CAPTURING | ISP正在处理RAW数据,输出YUV/JPEG |
| 4 | COMPLETED | Result回调已发出,Buffer已返回 |
| 5 | ERROR | 任何环节出错,进入异常处理 |
这里有个坑,我当年刚做HAL时踩过。你以为Request是串行处理的?其实不是。Android Camera框架允许同时有多个Request在Pipeline里流转。举个例子,预览场景下,App可能连续提交了3个Request,第一个还在曝光,第二个已经开始ISP处理,第三个刚进队列。这就是所谓的「流水线并行」。
核心要点:每个Request必须有一个唯一的Frame Number。这个编号从1开始递增,不能跳号,不能重复。我见过有人用时间戳当Frame Number,结果并发时重复了,导致上层回调错乱。
4.2 Result回调机制
Result回调,说白了就是HAL告诉Framework:「这帧拍完了,这是结果数据」。但这里面的门道可不少。
回调分两种:
- Partial Result:部分结果。比如Metadata先回来,Buffer还在路上。我建议在HAL里尽早回调Metadata,这样Framework可以先做3A统计。
- Final Result:最终结果。Metadata + Buffer都齐了,一次性回调。
为什么要分Partial和Final?你想想看,如果非要等Buffer处理完才回调,那3A算法的延迟就太大了。我在项目中遇到过,预览时如果等YUV Buffer回来才回调,画面明显卡顿。后来改成先回Metadata,流畅度直接提升30%。
个人经验:回调Result时,一定要保证Frame Number和Request的Frame Number一致。我曾经因为线程安全问题,把Frame Number搞串了,结果上层看到第5帧的Result里带着第4帧的Metadata,画面直接闪了一下。
4.3 Frame Number同步策略
Frame Number同步,这是整个Request流程里最容易出bug的地方。为什么?因为HAL是多线程的,Sensor线程、ISP线程、Callback线程各干各的,稍不注意就乱套。
我常用的同步策略是这样的:
// 伪代码示例
class RequestManager {
std::mutex mFrameLock;
uint32_t mNextFrameNumber = 1;
std::map<uint32_t, Request> mActiveRequests;
uint32_t submitRequest(Request& req) {
std::lock_guard<std::mutex> lock(mFrameLock);
uint32_t frameNumber = mNextFrameNumber++;
req.frameNumber = frameNumber;
mActiveRequests[frameNumber] = req;
return frameNumber;
}
void onCaptureComplete(uint32_t frameNumber) {
std::lock_guard<std::mutex> lock(mFrameLock);
auto it = mActiveRequests.find(frameNumber);
if (it != mActiveRequests.end()) {
// 处理完成,移除
mActiveRequests.erase(it);
}
}
}
这段代码看着简单,但实际项目中要考虑的更多:
- 如果Sensor丢帧了怎么办?Frame Number不能补,只能跳过。
- 如果ISP处理超时了,Result回调要不要等?我建议设个超时阈值,超了就报ERROR。
- 多个Stream(比如预览+拍照)共用同一个Frame Number吗?答案是共用。一个Request可以包含多个Stream的Buffer,但Frame Number只有一个。
避坑指南:我曾经在某个项目里,因为没处理好Frame Number的原子递增,导致两个线程同时拿到了同一个编号。结果上层收到两个一模一样的Frame Number的Result,直接崩溃。解决方案很简单:用std::atomic
4.4 实战中的常见问题
说了这么多理论,咱们来点实际的。我总结了几种常见场景:
- 预览卡顿:检查Request提交频率和Result回调频率是否匹配。如果App每秒提交30帧,但HAL只能处理15帧,那肯定卡。
- 拍照延迟:拍照Request通常优先级高,但如果你在预览Pipeline里插队,可能导致预览帧丢失。我建议拍照时暂停预览Request,拍完再恢复。
- ANR问题:Framework等待Result超时(默认5秒)。如果HAL在某个Request上卡住了,赶紧回调ERROR状态,别让Framework死等。
嗯,最后说一句。Request流程这东西,纸上谈兵容易,真正调起来全是坑。我建议你在开发时,先在HAL层打日志,把每个Request的Frame Number、状态变化、时间戳都打出来。这样出了问题,一看日志就知道是哪一步卡住了。
好了,这一章就到这儿。下一章咱们聊聊Buffer管理,那个更刺激。