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库。

验证步骤:

  1. 使用adb shell dumpsys media.camera查看预览帧时间戳日志;
  2. 运行testPreviewStability()单元测试,观察返回值;
  3. 连续预览5分钟,使用adb shell cat /proc/kmsg | grep "frame_drop"检查是否有丢帧记录。

判定标准:修复后,相邻帧时间戳间隔标准差小于2ms(原问题通常大于10ms),且无buffer dequeue超时错误(错误码NO_BUFFER_AVAILABLE出现次数为0)。