4. Stream复用策略设计:基于分辨率、格式、Usage与动态复用

在Camera HAL层开发中,Stream复用(Stream Reuse)是提升内存效率、降低延迟、减少带宽消耗的核心技术。复用策略的核心在于:在满足下游消费者(Preview、Video、Snapshot、Analysis)需求的前提下,尽可能共享Buffer或Stream管道,避免重复分配和拷贝。本章将系统阐述四种主流复用策略:基于分辨率、基于格式、基于Usage以及动态复用策略。

核心原则: Stream复用不是简单的“共用同一个Stream”,而是通过分析Consumer对分辨率、格式、帧率、旋转/缩放需求以及内存访问模式的差异,找到最大公约数,实现Buffer级或Stream级共享。

4.1 基于分辨率的复用策略

分辨率复用是最直观、最常用的策略。当多个Consumer请求的分辨率相同或存在整数倍缩放关系时,可以共享同一个Stream的输出Buffer,或通过Scale/Downscale共享数据。

4.1.1 策略模型

  • 完全匹配复用: 多个Consumer请求完全相同的分辨率(如Preview和Video都请求1920x1080),直接共享同一个Stream ID和Buffer队列。
  • 整数倍缩放复用: 高分辨率Stream(如4K)通过硬件Downscaler输出低分辨率(如1080p),供多个Consumer使用。
  • 裁剪复用: 从大分辨率Stream中裁剪出感兴趣区域(ROI),供不同Consumer使用。
性能收益: 分辨率复用可减少ISP输出通道数,降低DDR带宽占用30%~50%,尤其在高帧率场景(如4K 60fps + 1080p 60fps)下效果显著。
基于分辨率的Stream复用架构 ISP Output Stream 4K (3840x2160) 1080p (1920x1080) 720p (1280x720) Snapshot Preview + Video Analysis 复用策略说明 1080p Stream同时服务Preview和Video,实现Buffer级共享 4K Stream仅用于Snapshot,避免不必要的Downscale开销

4.2 基于格式的复用策略

格式复用关注的是Pixel Format的兼容性。当不同Consumer请求的格式在内存布局上兼容时,可以共享Buffer,仅通过不同的解析方式(Interpretation)来满足各自需求。

4.2.1 格式兼容性矩阵

Consumer A Consumer B 兼容性 复用方式
NV12 NV12 完全兼容 直接共享Buffer
NV12 NV21 部分兼容 UV分量交换(需CPU/GPU处理)
NV12 YV12 不兼容 无法复用,需格式转换
NV12 Y8(Grayscale) 部分兼容 仅使用Y平面,忽略UV
NV12 P010(10-bit) 不兼容 位深不同,需转换
注意: 格式复用需要HAL层对Buffer的Plane布局有精确控制。对于部分兼容的情况,必须确保Consumer B不会读取Consumer A写入的“脏数据”,否则会导致色彩异常。建议在复用前通过Metadata同步访问状态。

4.2.2 典型场景:NV12与Y8复用

// 格式复用示例:NV12 Buffer同时用于Preview(NV12)和Face Detection(Y8)
struct BufferReuseConfig {
    int stream_id;
    uint32_t width;
    uint32_t height;
    uint32_t format;          // HAL_PIXEL_FORMAT_YCbCr_420_SP
    uint32_t usage;           // GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_SW_READ_OFTEN
    
    // 复用描述
    struct ReusePlane {
        int consumer_id;
        uint32_t plane_index;  // 0: Y plane, 1: UV plane
        uint32_t offset;
        uint32_t stride;
    } planes[2];
};

// 初始化:NV12 Buffer,Y plane供Face Detection使用
struct BufferReuseConfig config = {
    .stream_id = 1,
    .width = 1920,
    .height = 1080,
    .format = HAL_PIXEL_FORMAT_YCbCr_420_SP,
    .usage = GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_SW_READ_OFTEN,
    .planes = {
        { .consumer_id = 0, .plane_index = 0, .offset = 0, .stride = 1920 },  // Y plane
        { .consumer_id = 1, .plane_index = 1, .offset = 1920*1080, .stride = 1920 } // UV plane
    }
};

4.3 基于Usage的复用策略

Usage复用关注的是Buffer的访问模式(CPU读/写、GPU纹理、HW Video Encoder、Display等)。当多个Consumer的Usage位掩码兼容时,可以共享同一块物理内存,避免重复分配。

4.3.1 Usage兼容性分析

  • HW_TEXTURE + HW_COMPOSER: 高度兼容,GPU和Display都可以从同一Buffer读取。
  • HW_VIDEO_ENCODER + SW_READ_OFTEN: 部分兼容,Encoder需要连续物理内存,CPU读可能触发Cache同步。
  • SW_WRITE_OFTEN + HW_TEXTURE: 不推荐,CPU写后GPU读需要显式Cache Flush,性能开销大。
  • HW_CAMERA_WRITE + HW_VIDEO_ENCODER: 最佳复用组合,Camera直接写入Encoder可访问的内存区域,零拷贝。
基于Usage的Buffer复用决策流程 Consumer Usage集合 Usage位掩码检查 完全兼容 → 直接复用 冲突 → 分配独立Buffer 部分兼容 → 加Cache Sync Usage复用决策需结合平台Gralloc实现:部分平台支持Usage OR合并,部分要求严格匹配

4.4 动态复用策略

动态复用策略是前三种策略的融合与运行时优化。它根据当前系统负载、Consumer活跃状态、Buffer分配压力等因素,在运行时动态调整复用关系。

4.4.1 动态复用决策引擎

输入参数 来源 影响决策
活跃Consumer列表 Camera Framework 决定哪些Stream需要复用
当前Buffer压力 Gralloc Allocator 压力高时倾向复用,低时倾向独立分配
帧率需求 Consumer配置 高帧率Consumer优先独立Stream
平台带宽余量 ISP/VPU状态 带宽不足时强制复用减少输出
Cache同步开销 运行时统计 开销超过阈值时切换为独立Buffer

4.4.2 动态复用状态机

// 动态复用状态机伪代码
enum ReuseState {
    REUSE_INDEPENDENT,   // 独立分配,无复用
    REUSE_SHARED_BUFFER, // 共享Buffer
    REUSE_SHARED_STREAM  // 共享整个Stream管道
};

struct DynamicReuseManager {
    // 当前复用状态
    ReuseState current_state;
    
    // 决策阈值
    float bandwidth_threshold;    // 带宽利用率阈值,超过则强制复用
    float cache_sync_cost;        // 当前Cache同步开销(us)
    float cache_sync_limit;       // Cache同步开销上限(us)
    
    // 动态决策函数
    ReuseState evaluate_reuse(const ConsumerGroup &group) {
        // 1. 检查带宽压力
        if (get_bandwidth_usage() > bandwidth_threshold) {
            return REUSE_SHARED_STREAM;  // 带宽不足,强制共享Stream
        }
        
        // 2. 检查Cache同步开销
        if (cache_sync_cost > cache_sync_limit) {
            return REUSE_INDEPENDENT;    // Cache同步太贵,独立分配
        }
        
        // 3. 检查Consumer兼容性
        if (group.has_compatible_resolution() && 
            group.has_compatible_format() &&
            group.has_compatible_usage()) {
            return REUSE_SHARED_BUFFER;  // 完全兼容,共享Buffer
        }
        
        // 4. 默认:独立分配
        return REUSE_INDEPENDENT;
    }
    
    // 状态切换回调
    void on_state_change(ReuseState old_state, ReuseState new_state) {
        // 重新配置ISP输出通道
        // 更新Gralloc Buffer分配策略
        // 通知Consumer重新配置
    }
};
动态复用最佳实践:
  1. 在Camera Open阶段进行静态分析,建立初始复用关系。
  2. 在Stream配置阶段(ConfigureStreams)进行动态评估,调整复用策略。
  3. 在运行阶段(ProcessCaptureRequest)监控性能计数器,触发策略切换。
  4. 策略切换时需保证Buffer Pipeline的完整性,避免出现“黑帧”或“花屏”。

4.5 复用策略对比与选型指南

策略类型 复用粒度 实现复杂度 内存节省 带宽节省 适用场景
基于分辨率 Stream/Buffer 低 中(30%~50%) 高(40%~60%) Preview + Video + Snapshot
基于格式 Buffer Plane 中 低(10%~20%) 中(20%~30%) Face Detection + Preview
基于Usage Buffer 中 中(20%~40%) 高(30%~50%) Camera + Encoder + Display
动态复用 全链路 高 高(40%~70%) 高(50%~80%) 多Camera、高帧率、资源受限平台
避坑指南:
  • 不要盲目复用:当Consumer对延迟敏感时(如Video Encoder),独立Stream可减少排队等待。
  • 注意Buffer生命周期:复用Buffer时,必须确保所有Consumer都完成读取后才能归还Buffer池。
  • 平台差异:不同SoC的ISP输出通道数、Downscaler能力、Gralloc实现差异很大,复用策略需针对性调优。
  • 测试覆盖:复用策略必须在所有Camera使用场景(拍照、录像、预览、第三方App)下充分测试。

4.6 本章小结

Stream复用策略是Camera HAL层性能优化的核心手段。基于分辨率的复用最易实现且收益显著;基于格式的复用适用于特定场景(如Y plane共享);基于Usage的复用需要深入理解Gralloc和硬件访问模式;动态复用策略则是在前三种基础上的智能化升级,能够根据运行时状态自适应调整。在实际项目中,建议优先实现分辨率复用,然后根据性能瓶颈逐步引入格式和Usage复用,最后在资源紧张的高端平台上部署动态复用引擎。