4. 预览帧数据不连续
任务说明
本案例针对Android相机HAL层预览帧间隔抖动问题,要求检查HAL buffer管理及同步机制,定位并修复帧数据不连续的根本原因。完成目标:通过代码审查与最小修复,使预览帧间隔恢复稳定(相邻帧时间戳差值波动小于设定阈值)。
问题定位路径
预览帧不连续通常由三类原因引起:buffer复用冲突(dequeue/queue乱序)、同步信号丢失(vsync/sof未正确等待)、帧间隔计算错误(时间戳基准不一致)。以下实现聚焦于HAL层最常见的buffer管理缺陷——未等待硬件空闲即复用buffer,导致帧数据覆盖或跳帧。
自洽实现
// preview_stream_impl.cpp
// 核心修复:在dequeue_buffer前增加硬件忙等待,确保前一帧已完全读出
#include <system/camera_metadata.h>
#include <system/graphics.h>
#include <utils/Mutex.h>
#include <utils/Condition.h>
#include <time.h>
#define MAX_BUFFERS 4
#define FRAME_INTERVAL_NS 33333333L // 30fps标称间隔
struct PreviewBufferState {
buffer_handle_t handle;
bool inUse; // 是否已dequeue给framework
bool hwReading; // 硬件是否正在读取该buffer
nsecs_t lastTimestamp;
};
class PreviewStream {
public:
PreviewStream(int w, int h) : width(w), height(h), nextFree(0) {
for (int i = 0; i < MAX_BUFFERS; i++) {
states[i].inUse = false;
states[i].hwReading = false;
states[i].lastTimestamp = 0;
}
}
// 修复点:dequeue前检查硬件占用
status_t dequeueBuffer(buffer_handle_t* outBuf, nsecs_t* timestamp) {
Mutex::Autolock lock(mutex);
// 等待所有硬件读取中的buffer完成
for (int i = 0; i < MAX_BUFFERS; i++) {
while (states[i].hwReading) {
// 模拟等待硬件中断/完成回调
// 实际项目中此处应阻塞在waitForHwComplete(i)上
usleep(500); // 500us轮询间隔
}
}
// 查找空闲buffer(非inUse且非hwReading)
int idx = -1;
for (int i = 0; i < MAX_BUFFERS; i++) {
if (!states[i].inUse && !states[i].hwReading) {
idx = i;
break;
}
}
if (idx == -1) {
return NO_BUFFER_AVAILABLE;
}
states[idx].inUse = true;
*outBuf = states[idx].handle;
*timestamp = systemTime(SYSTEM_TIME_MONOTONIC);
states[idx].lastTimestamp = *timestamp;
return OK;
}
status_t queueBuffer(buffer_handle_t buf, nsecs_t timestamp) {
Mutex::Autolock lock(mutex);
for (int i = 0; i < MAX_BUFFERS; i++) {
if (states[i].handle == buf) {
states[i].inUse = false;
states[i].hwReading = true; // 标记硬件开始读取
// 启动硬件读取线程(示意)
startHwRead(i, timestamp);
return OK;
}
}
return BAD_VALUE;
}
// 硬件读取完成回调(由ISP中断触发)
void onHwReadComplete(int idx) {
Mutex::Autolock lock(mutex);
states[idx].hwReading = false;
}
// 帧间隔抖动检测(验证用)
float getJitterMetric() {
Mutex::Autolock lock(mutex);
if (historyCount < 2) return 0.0f;
float sum = 0;
for (int i = 1; i < historyCount; i++) {
float diff = (float)(history[i] - history[i-1]) / 1e6f; // ms
sum += (diff - 33.33f) * (diff - 33.33f);
}
return sqrt(sum / (historyCount - 1));
}
private:
void startHwRead(int idx, nsecs_t ts) {
// 实际项目中创建线程或注册回调
// 此处模拟:立即完成读取(真实场景需等待VSYNC)
onHwReadComplete(idx);
}
int width, height;
PreviewBufferState states[MAX_BUFFERS];
Mutex mutex;
int nextFree;
nsecs_t history[128];
int historyCount = 0;
};
// 使用示例
void testPreviewStability() {
PreviewStream stream(1920, 1080);
buffer_handle_t buf;
nsecs_t ts;
// 模拟连续dequeue/queue 60帧
for (int i = 0; i < 60; i++) {
if (stream.dequeueBuffer(&buf, &ts) == OK) {
// 填充图像数据(省略)
stream.queueBuffer(buf, ts);
}
usleep(33000); // 33ms模拟帧间隔
}
// 预期:getJitterMetric() < 2.0f 表示稳定
}
关键修复说明
上述代码中dequeueBuffer()增加了对hwReading标志的轮询等待,确保硬件完成上一帧读取后才允许framework获取buffer。修复前,若framework在硬件读取期间dequeue同一buffer,会导致数据覆盖,表现为帧内容错乱或时间戳跳变。修复后,buffer生命周期严格遵循“dequeue→填充→queue→硬件读取→释放”的顺序,从同步机制上消除帧间隔抖动。
落地与验证
集成方式:将PreviewStream替换原HAL实现中的buffer管理模块,编译进camera.msmXXXX.so,替换设备上的HAL库。
验证步骤:
- 使用
adb shell dumpsys media.camera查看预览帧时间戳日志; - 运行
testPreviewStability()单元测试,观察返回值; - 连续预览5分钟,使用
adb shell cat /proc/kmsg | grep "frame_drop"检查是否有丢帧记录。
判定标准:修复后,相邻帧时间戳间隔标准差小于2ms(原问题通常大于10ms),且无buffer dequeue超时错误(错误码NO_BUFFER_AVAILABLE出现次数为0)。