3、Pipeline深度优化:减少request队列深度、调整hal_pipeline_depth参数、同步vsync与sensor帧率

Pipeline优化,说白了就是让数据在Camera HAL里跑得更顺畅。我见过太多团队一上来就调各种参数,结果越调越乱。其实核心就三件事:队列深度、Pipeline深度、帧率同步。咱们一个一个说。

3.1 减少request队列深度

先说说request队列。你想想看,APP发下来的request,HAL要一个个处理。如果队列太深,就像高速路上堵车——后面的车等得越久,延迟就越大。

我个人的习惯是把队列深度控制在2-3个。为什么?因为Android Camera框架本身就有buffer管理,队列太深反而容易造成内存压力。

核心思路:队列深度 = 处理速度 × 允许的最大等待时间。一般建议不超过3。

具体怎么调?看代码:

// 在HAL的configureStreams中设置
camera3_stream_configuration_t config;
// ... 其他配置

// 限制request队列深度
config.num_buffers = 2;  // 我建议设2-3个buffer

// 或者在HAL内部实现队列控制
class RequestQueue {
    static const int MAX_DEPTH = 2;  // 队列最大深度
    std::queue<camera3_capture_request_t*> mQueue;
    
    bool pushRequest(camera3_capture_request_t* request) {
        if (mQueue.size() >= MAX_DEPTH) {
            // 队列满了,直接丢弃或返回错误
            ALOGE("Request queue full, dropping request");
            return false;
        }
        mQueue.push(request);
        return true;
    }
};

我在项目中遇到过一个坑:某款芯片把队列深度设成5,结果预览延迟直接飙到200ms。后来改成2,延迟降到80ms。嗯,有时候少即是多。

小技巧:如果发现预览卡顿,先检查队列深度。我一般用adb shell dumpsys media.camera看队列状态。

3.2 调整hal_pipeline_depth参数

这个参数很多人搞不懂。说白了,它就是HAL内部Pipeline的深度——从sensor取图到最终输出,中间经过多少级处理。

默认值通常是4-6,但不同平台不一样。我见过高通平台默认是4,MTK平台默认是5。你想想看,每多一级处理,就多一次buffer拷贝和等待,延迟自然就上去了。

Pipeline深度 延迟影响 适用场景
2 低延迟(~30ms) 实时预览、AR应用
3 中等延迟(~50ms) 普通拍照
4-6 高延迟(>80ms) 多帧合成、HDR

怎么调?一般在HAL的初始化代码里:

// 在HAL的open()或init()中设置
static const int HAL_PIPELINE_DEPTH = 2;  // 我建议设2-3

int hal_open(const struct hw_module_t* module, 
             const char* id, struct hw_device_t** device) {
    // ... 初始化代码
    
    // 设置pipeline深度
    camera3_device_t* camera_dev = (camera3_device_t*)*device;
    camera_dev->ops->get_metadata_value(
        CAMERA3_METADATA_PIPELINE_DEPTH, 
        &HAL_PIPELINE_DEPTH);
    
    return 0;
}

注意:不要把Pipeline深度设成1。我曾经试过,结果sensor帧率和HAL处理速度不匹配,直接导致丢帧。2是最低安全值。

3.3 同步vsync与sensor帧率

这是最容易被忽视的一环。vsync是显示系统的节奏,sensor帧率是采集系统的节奏。如果两者不同步,就像两个人跳舞踩不到点上——延迟和卡顿就来了。

为什么会这样?因为vsync通常是60Hz(16.6ms),而sensor帧率可能是30fps(33.3ms)或60fps(16.6ms)。如果sensor帧率比vsync快,buffer就会堆积;如果慢,就会有空闲周期。

我建议的做法:

  1. 匹配帧率:让sensor帧率等于vsync帧率,或者成整数倍关系。
  2. 使用vsync信号:在HAL里监听vsync,控制sensor的曝光和输出时机。
  3. 调整sensor配置:通过sensor驱动设置帧率。

看代码示例:

// 在HAL中同步vsync和sensor帧率
class SensorController {
    int mVsyncFd;  // vsync文件描述符
    int mSensorFps;  // sensor帧率
    
    void init() {
        // 打开vsync设备
        mVsyncFd = open("/dev/vsync", O_RDONLY);
        if (mVsyncFd < 0) {
            ALOGE("Failed to open vsync device");
            return;
        }
        
        // 设置sensor帧率等于vsync帧率
        mSensorFps = 60;  // 假设vsync是60Hz
        setSensorFps(mSensorFps);
    }
    
    void waitForVsync() {
        struct vsync_event event;
        read(mVsyncFd, &event, sizeof(event));
        // 收到vsync信号后,触发sensor曝光
        triggerSensorExposure();
    }
    
    void setSensorFps(int fps) {
        // 通过sensor驱动设置帧率
        struct sensor_config config;
        config.fps = fps;
        ioctl(mSensorFd, SENSOR_SET_FPS, &config);
    }
};

关键点:vsync和sensor帧率必须严格对齐。我见过一个项目,vsync是60Hz,sensor设成30fps,结果预览延迟忽高忽低。后来改成60fps,问题就解决了。

我曾经踩过一个坑:某款sensor支持30fps和60fps,但60fps下画质下降。我一开始没注意,直接设成60fps,结果用户反馈预览清晰度不够。后来我改成30fps,但通过vsync同步,延迟反而比60fps还低。嗯,有时候不是越快越好,要综合考虑。

3.4 实战总结

好了,三个核心点都讲完了。我总结一下:

  • 队列深度:设2-3,别贪多。
  • Pipeline深度:设2-3,别设1。
  • 帧率同步:让sensor帧率等于vsync帧率,或者成整数倍。

你可能会问:这些参数调完,延迟能降多少?我实测过,在某个项目中,从默认配置(队列深度5、Pipeline深度4、sensor 30fps)优化到(队列深度2、Pipeline深度2、sensor 60fps),预览延迟从180ms降到了45ms。效果还是很明显的。

最后提醒:不同平台、不同sensor的默认值不一样。别照搬我的参数,一定要在自己的设备上实测。用adb shell dumpsys media.camera看延迟数据,用systrace看Pipeline状态。数据说话,别靠感觉。

下一章咱们聊聊buffer管理,那又是另一个坑。先消化这些吧。