Camera 基础

Android高通Camx框架, OIS数据流与NCS机制详解

 OIS(光学防抖)大家都听说过,但OIS的镜头位置数据是怎么从硬件读到算法层的?NCS又是什么?这篇深入聊聊Camera子系统里这条不太被关注的数据流。 

一、为什么需要NCS?

先说背景。CamX的Node和Pipeline通常以像素帧边界为单位处理数据——一帧进、一帧出。但EIS(电子防抖)算法不一样,它需要的是帧边界附近的数据,甚至要聚合多帧数据,在任意时刻请求数据。

这就产生了一个矛盾:

  • CamX Pipeline按帧对齐
  • EIS按时间对齐,不按帧对齐

NCS(Node Communication Service)就是为了解决这个矛盾而设计的基础设施——它允许Node之间传递不按帧边界对齐的数据。

二、OIS数据流全链路

OIS数据从硬件到算法的完整路径:

OIS硬件控制器
    ↓ I2C读取
OIS Submodule Driver(轮询线程)
    ↓ NCS接口
CamX OIS Driver
    ↓ NCS数据源注册
EIS Node(NCS客户端)
    ↓ 请求OIS数据
EIS算法处理

关键点:OIS数据不是按帧来的,而是按固定频率采样。OIS硬件有一个固定大小的片上缓存,数据需要通过I2C以足够快的速率读出来,否则会丢数据。

为此,OIS驱动会启动一个独立的轮询线程,按照XML配置的频率定期读取数据。这里没有直接的I2C Read API,是通过寄存器配置的方式读取。

三、XML配置详解

OIS数据的读取完全由XML配置驱动。核心配置结构是OISLensPositionReadInfo,包含三个部分:

3.1 采样参数

参数
含义
示例值
sampleFrequencyInHZ
OIS采样频率
500(即每2ms一次)
totalSamples
每次读取的采样数
12
readIntervalInMS
读取间隔(毫秒)
10

这三个参数的关系很关键:

采样频率 = 500Hz → 每个采样2ms
总采样数 = 12 → 12个采样需要24ms(12 × 2ms)
读取间隔 = 10ms → 每10ms读一次

约束:readIntervalInMS < (1000 / sampleFrequencyInHZ) × totalSamples
         10ms < (1000/500) × 12 = 24ms ✓

注意:readInterval要考虑软件和CCI延迟,留出余量

3.2 寄存器读取配置

<!-- readSettings:I2C寄存器读取配置 -->
<readSettings>
  <regSetting>
    <registerAddr>0x8A</registerAddr>      <!-- OIS数据寄存器地址 -->
    <registerData>51</registerData>          <!-- 读取51字节 -->
    <regAddrType>2</regAddrType>            <!-- 地址类型:2字节 -->
    <regDataType>1</regDataType>           <!-- 数据类型:1字节 -->
    <operation>READ</operation>              <!-- 操作类型:读 -->
  </regSetting>
</readSettings>

3.3 数据格式配置(FormatInfo)

这是最核心的部分——告诉框架如何解析读回来的原始数据。每个字段都用FormatInfo结构描述:

字段
含义
offset
在原始数据中的起始位置(从0开始)
sizeInBytes
该字段的字节大小
offsetIncrementBytes
从当前样本到下一个样本的偏移量

以一个实际例子说明数据布局:

<!-- 每个采样5字节:1字节有效标记 + 2字节X + 2字节Y -->
<!-- 12个采样 + 2字节时间戳 = 51字节 -->

<formatInfo>
  <positionValidSamples>
    <offset>0</offset>                    <!-- 第0字节:有效采样数 -->
    <sizeInBytes>1</sizeInBytes>
    <offsetIncrementBytes>0</offsetIncrementBytes>  <!-- 只有一个,不递增 -->
  </positionValidSamples>
  
  <positionX>
    <offset>1</offset>                     <!-- 第1-2字节:X位置 -->
    <sizeInBytes>2</sizeInBytes>
    <offsetIncrementBytes>4</offsetIncrementBytes>  <!-- 下一个X在+4处 -->
  </positionX>
  
  <positionY>
    <offset>3</offset>                     <!-- 第3-4字节:Y位置 -->
    <sizeInBytes>2</sizeInBytes>
    <offsetIncrementBytes>4</offsetIncrementBytes>  <!-- 下一个Y在+4处 -->
  </positionY>
  
  <timeStamp>
    <offset>49</offset>                    <!-- 最后2字节:时间戳 -->
    <sizeInBytes>2</sizeInBytes>
    <offsetIncrementBytes>0</offsetIncrementBytes>  <!-- 只有一个时间戳 -->
  </timeStamp>
  
  <isPerSampleTimestamp>FALSE</isPerSampleTimestamp>  <!-- 只有最后一个采样有时间戳 -->
  <timerClockFrequency>19200000</timerClockFrequency>
  <timeStampUnit>INTERNALCLOCKTICKS</timeStampUnit>   <!-- 时间戳单位:内部时钟周期 -->
</formatInfo>

数据布局图示:

原始数据(51字节):
┌───┬──────────────────────────────────────────────────────┬──────┐
│ V │ S1(X,Y)  S2(X,Y)  S3(X,Y) ... S12(X,Y)                │ TS   │
│ 1 │ 2+2     2+2      2+2       2+2                        │ 2    │
└───┴──────────────────────────────────────────────────────┴──────┘
 0   1  3  5  7  9  11 13 ...                              49    51

V = positionValidSamples (1字节)
X = positionX (2字节)
Y = positionY (2字节)
TS = timestamp (2字节, 仅最后一个采样)

💡 关键理解:offsetIncrementBytes是4,意味着从一个样本的X位置到下一个样本的X位置偏移4字节。因为中间隔了2字节的Y位置,所以X→Y是2字节,Y→下一个X是2字节,总共4字节。 

3.4 每个采样都有时间戳的情况

如果isPerSampleTimestamp设为TRUE,数据布局会变化:

每个采样5字节 → 6字节(增加2字节时间戳)
总数据量 = 1 + (2+2+2) × 12 = 73字节

offsetIncrementBytes变为6(X→Y是2,Y→TS是2,TS→下一个X是2)
timestamp的offset从49变为5(第一个时间戳在第5字节)

四、自定义OIS Library

如果OIS硬件的原始数据格式不符合标准的FormatInfo结构,可以实现自定义解析库。

4.1 核心数据结构

// OIS库需要填充的输出结构
typedef struct LensPosition {
    FLOAT   shiftX;         // X方向镜头位移
    FLOAT   shiftY;         // Y方向镜头位移
    UINT64  timeStampInNS;  // 纳秒级时间戳
} LensPosition;

// 框架传给OIS库的输入+输出结构
typedef struct LensPositionDataInfo {
    LensPosition*   pLensPos;         // 输出:格式化后的数据
    UINT            validSamples;      // 输出:有效采样数
    UINT32          totalSamples;     // 输入:pLensPos能容纳的采样数
    UINT8*          pRawLensPosData;  // 输入:原始数据指针
    UINT32          rawDataSize;      // 输入:原始数据大小
    UINT64          QTimerInNS;        // 输入:读取时的QTimer值
} LensPositionDataInfo;

4.2 实现自定义解析函数

// 自定义格式化函数实现
static bool FormatOISLensPositionData(
    LensPositionDataInfo* pLensPosData) 
{
    // 1. 从pRawLensPosData解析原始数据
    UINT8* rawData = pLensPosData->pRawLensPosData;
    UINT32 dataSize = pLensPosData->rawDataSize;
    
    // 2. 按自己的格式解析每个采样
    // ... 自定义解析逻辑 ...
    
    // 3. 填充输出
    pLensPosData->pLensPos[0].shiftX = parsedX;
    pLensPosData->pLensPos[0].shiftY = parsedY;
    pLensPosData->pLensPos[0].timeStampInNS = 
        convertToNS(rawTimestamp, pLensPosData->QTimerInNS);
    
    pLensPosData->validSamples = actualValidCount;
    return true;
}

// 注册API
void GetOISLibraryAPIs(OISLibraryAPI* pOISLibraryAPI) {
    pOISLibraryAPI->pFormatOISLensPositionData = 
        FormatOISLensPositionData;
}

4.3 部署流程

# 1. 编译生成so库
# 库命名规范:com.<vendor>.ois.<ois_name>.so
# 例如:com.oem.ois.custom_ois.so

# 2. 放置到指定路径
# /vendor/etc/camera/com.oem.ois.custom_ois.so

# 3. 在vendor.mk中添加
# PRODUCT_PACKAGES += com.oem.ois.custom_ois.so

# 4. OIS驱动会自动加载指定名称的so库

五、CHI Node如何使用NCS获取OIS数据

EIS等CHI Node通过NCS接口获取OIS数据,核心是三个API:

API
作用
pGetDataSource
注册为NCS数据源客户端,获取数据源句柄
pGetData
请求指定时间段的OIS数据,不限于帧边界
pPutDataSource
释放数据源句柄

这种设计让EIS可以在任意时刻请求OIS数据,不受帧边界限制,实现了跨帧数据聚合。

六、避坑指南

  1. readInterval要留余量
     — 必须小于理论最大值,考虑CCI延迟和软件调度抖动
  2. offsetIncrementBytes别算错
     — 这是格式化正确性的关键,算错一个字节后面全部错位
  3. 时间戳单位要对
     — INTERNALCLOCKTICKS需要配合timerClockFrequency转换成纳秒
  4. 自定义Library优先级更高
     — 如果实现了pFormatOISLensPositionData,XML中的FormatInfo不会生效
  5. WriteTimeSettings是可选的
     — 只有OIS FW支持写入系统时间时才需要配置
  6. 采样率不是越高越好
     — 高采样率带来更多I2C中断和CPU负载,功耗文档也提到过这点

📌 本文核心要点


  1. OIS数据通过I2C轮询读取,由XML完全配置驱动

  2. NCS解决的是"帧对齐Pipeline"和"时间对齐算法"的矛盾

  3. FormatInfo的offset/sizeInBytes/offsetIncrementBytes三参数定义数据解析规则

  4. 自定义OIS Library可以处理非标准数据格式,优先级高于XML配置

  5. CHI Node通过GetDataSource/GetData/PutDataSource三接口使用NCS

 觉得有帮助?点个「在看」转发给同事吧 👇 

更多Camera开发实战内容

欢迎加入知识星球「小驰成长圈」

120+ Camera工程师 · 340+ 实战内容 · 已运营1565天

小驰成长圈 知识星球

微信扫码 · 加入星球

读到这里,说明你是真做 Camera 的 加个关注、进个圈子,后面调试卡壳时,有个能直接问的人。
小驰行动派公众号 公众号 · 小驰行动派 每周 HAL / Camx / Sensor 干货
小驰行动派知识星球 知识星球 · 小驰行动派 资料包 + 随时答疑
系统学 Camera 开发 →
分享到: 复制链接
← 上一篇 Camera功耗排查全攻略 | 建议收藏备用 下一篇 → Android Camera稳定性排查实战指南(Qcom Camx)

相关文章

推荐课程

想系统学习 Camera 开发?看看这些课程

评论 (0)

暂无评论,快来抢沙发吧