API GUIDE / I2D / Middleware

G32S10 I2D Middleware 接口

Middleware 层接口:中间件是阻塞式的:im_i2d_process() 占用调用任务,直到这次变换完成、超时或被 取消才返回。

1. 分层关系与依赖

1.1 分层与调用方向

APPLICATION  Application / Example / Driver  业务代码、板级支持与示例入口
↓
MIDDLEWARE  MID I2D  状态机、IRQ 门控、等待、超时与 cache 维护
对外隐藏硬件复位和错误恢复细节
↓
HAL  HAL I2D  实例状态、参数校验、寄存器编程与中断分发
提供面向图像处理流程的语义接口
↓
LL  LL I2D  寄存器访问与字段级控制
↓
HARDWARE REGISTERS  I2D Registers · 0x13330000  实例地址、寄存器布局与位域定义
IRQ  IRQ_I2D / PLIC  硬件中断源与平台分发入口
DISPATCH  im_irq dispatcher  把中断路由到 MID 注册的处理函数
EVENT  MID event waiter  将硬件完成转换为等待线程可消费的事件

中断路径:IRQ_I2D → PLIC → im_irq → HAL IRQ handler → MID event waiter

1.2 各层职责

层负责不负责
应用 / 示例 / 测试提供源/目标 buffer,填写几何与格式;调用 im_i2d_process() 并处理返回码;buffer 的容量、对齐与生命周期寄存器、中断
im_i2d单实例设备对象;公共枚举到 HAL 配置的映射;IRQ_I2D 的申请/使能;事件位图与等待;超时;按 manage_cache 维护 cache分配内存、像素格式转换、缩放
HAL I2D参数校验、stride/平面大小推导、寄存器编程、启停、中断状态读取与清除cache 维护、SoC 中断申请
LL I2D寄存器字段级读写、shadow 更新、安全复位、启动/停止、时钟门控参数语义

中间件是阻塞式的:im_i2d_process() 占用调用任务,直到这次变换完成、超时或被 取消才返回。公共接口没有「启动后立即返回」的版本,也没有状态查询接口。

2. 构建配置与文件组织

2.1 Kconfig

code/middleware/i2d/Kconfig:

config MIDDLEWARE_I2D
    bool "I2D image conversion middleware"
    depends on DRIVER_I2D && OS_API
配置作用
CONFIG_DRIVER_I2D编译 I2D LL/HAL(code/drivers/g32s10/i2d/)
CONFIG_OS_API / CONFIG_OS任务/延时与 thread_waiter
CONFIG_DRIVER_IRQim_irq_* 注册与分发框架
CONFIG_DRIVER_PLICIRQ_I2D 的 PLIC 使能(G32S10 上默认随 core 打开)
CONFIG_MIDDLEWARE_I2D编译 im_i2d

2.2 CMake

code/middleware/i2d/CMakeLists.txt 在 CONFIG_MIDDLEWARE_I2D 打开时注册组件im_i2d:

if(CONFIG_MIDDLEWARE_I2D)
    imde_component_register(NAME im_i2d
        SRCS "${CMAKE_CURRENT_LIST_DIR}/src/im_i2d.c"
        INCLUDE_DIRS
        "${CMAKE_CURRENT_LIST_DIR}/include")
endif()

应用只需要 #include "im_i2d.h" 并打开 CONFIG_MIDDLEWARE_I2D。示例目录code/middleware/i2d/example/CMakeLists.txt 会在没有打开中间件时直接FATAL_ERROR,避免「配置忘开、构建却通过」。

2.3 文件职责

文件职责
include/im_i2d.h对外唯一头文件:枚举、两个结构体、六个函数
src/im_i2d.c全部实现,struct im_i2d 只在这里可见
example/main/main.c示例入口,只调用 i2d_single_transform_run()
example/main/i2d_single_transform.c示例本体:可改配置、缓冲、图案、一次变换、抽样校验
example/README.md示例的用户说明(构建、日志、改参数)
example/i2d_example_defconfig示例的 defconfig(打开 I2D 驱动与中间件)

3. 资源模型与公开类型

3.1 设备状态机

STATE  RESET
→
STATE  READY
→
STATE  RUNNING
→
STATE  STOPPING
→
STATE  TIMEOUT
→
STATE  ERROR

RESET → READY · im_i2d_init()

READY → RUNNING · im_i2d_process() begin + start

RUNNING → STOPPING · finish() 屏蔽中断并收尾

STOPPING → READY · IM_OK / 取消

STOPPING → TIMEOUT · 等待或硬件超时

STOPPING → ERROR · outstanding 或硬件错误

READY / TIMEOUT / ERROR → RESET · im_i2d_deinit()

状态含义可继续做的操作
RESET未初始化或已反初始化im_i2d_init();im_i2d_deinit() 返回 IM_OK;im_i2d_process() 返回 IM_ERR_NOT_READY;im_i2d_cancel() 返回 last_status
READY空闲,可以启动下一次变换im_i2d_process()、im_i2d_deinit();im_i2d_cancel() 返回 last_status
RUNNING已启动,等待完成事件等待由 im_i2d_process() 内部完成;另一任务可 im_i2d_cancel()
STOPPINGfinish 的中间态,仅在函数内部短暂出现应用不应观察到
TIMEOUT上一次变换超时只能 im_i2d_deinit() 后重新初始化
ERROR上一次变换遇到硬件错误或缺少完成事件只能 im_i2d_deinit() 后重新初始化

关键点:

  • TIMEOUT/ERROR 是终态。 此时再调用 im_i2d_process(),begin 阶段会因状态 不是 READY 而返回 IM_ERR_BUSY。恢复方式是 im_i2d_deinit() + im_i2d_init()。

  • begin 阶段的参数/布局错误、start 阶段的 HAL 错误都不会进入 RUNNING,状态保持READY,修正参数或降低负载后可以直接重试。

  • 正常完成或 im_i2d_cancel() 会把状态送回 READY。

3.2 缓冲区所有权

对象谁分配谁释放谁维护 cache
im_i2d_t 设备对象中间件(静态)中间件不涉及
源 buffer调用方调用方默认中间件(manage_cache = true)
目标 buffer调用方调用方默认中间件

中间件不持有 buffer,im_i2d_process() 返回后不会再用它们(除了 finish 阶段对目标 做一次 invalidate)。因此调用方可以在返回后立刻读取或释放目标 buffer。

3.3 公开类型

3.3.1 im_i2d_t

typedef struct im_i2d im_i2d_t;

只有前向声明,定义在 im_i2d.c 内部。应用只能持有指针并原样传回,不能 sizeof(struct im_i2d) 分配自己的实例、不能访问字段、不能在 im_i2d_deinit() 之后继续使用。

3.3.2 im_i2d_instance_t

枚举 值 说明
IM_I2D_INSTANCE_0 0 唯一支持、也是唯一合法的实例

3.3.3 im_i2d_format_t

枚举 值 每像素字节 宽高对齐 平面数
IM_I2D_FORMAT_NV12 0 1(Y 平面) 8 2(Y + UV 半平面)
IM_I2D_FORMAT_RAW8 1 1 8 1
IM_I2D_FORMAT_RGB565 2 2 4 1
IM_I2D_FORMAT_ARGB8888 3 4 2 1
IM_I2D_FORMAT_YUV422 4 2 4 1(打包)

format 同时描述源和目标:I2D 不做像素格式转换、不做缩放、不做色彩空间转换。 源和目标必须是同一种格式。YUV422 是打包的单平面格式(每像素 2 字节),UV 字段必须 为 NULL,它的字节序由上游决定,I2D 只按字节搬运。

3.3.4 im_i2d_rotate_t

枚举 值 说明
IM_I2D_ROTATE_0 0 不旋转
IM_I2D_ROTATE_90 1 顺时针 90°
IM_I2D_ROTATE_180 2 180°
IM_I2D_ROTATE_270 3 顺时针 270°(= 逆时针 90°)

方向约定由几何映射确定(§6.7):ROTATE_90 把源图左上角送到输出右上角。 test/g32s10/module/vic/slt_103/gc0308_dump_i2d 已在硬件上逐像素验证过这个方向。

3.3.5 im_i2d_plane_t

字段 类型 说明
y void * Y 平面或打包图像的基地址;必须非空
uv void * NV12 的 UV 平面地址;其余格式必须为 NULL
stride_y uint32_t Y 平面或打包图像的行 stride(字节);0 表示由 HAL 推导
stride_uv uint32_t NV12 UV 平面 stride(字节);0 表示由 HAL 推导

3.3.6 im_i2d_transform_t

字段 类型 说明
src im_i2d_plane_t 源平面信息
dst im_i2d_plane_t 目标平面信息
width / height uint32_t 源图宽高(像素)
format im_i2d_format_t 源和目标共用的像素格式
rotate im_i2d_rotate_t 旋转角度
horizontal_mirror bool 水平镜像(作用在输出坐标系)
vertical_flip bool 垂直翻转(作用在输出坐标系)

平面规则:

  • 打包格式(RAW8、RGB565、ARGB8888、YUV422)只有一个平面:uv = NULL、 stride_uv = 0。
  • NV12 有两种写法:
    1. 连续布局:uv = NULL,HAL 按 y + stride_y * 按 16 对齐后的 Y 行数 推导 UV 地址;
    2. 分离布局:显式给出 uv,并可用 stride_uv 指定 UV 行 stride。
  • stride 单位是字节,不是像素;非 0 时必须 <= 0xFFFF 且不小于最小行字节数。
  • 非 NV12 却给了 UV 指针或 UV stride,会被 HAL 拒绝(IM_ERR_INVALID_PARAM)。

3.3.7 im_i2d_config_t

字段 类型 说明
instance im_i2d_instance_t 必须为 IM_I2D_INSTANCE_0
hal_init hal_i2d_init_config_t 原样传给 hal_i2d_init();其中 instance 会被强制改写为 I2D
timeout_ms uint32_t 默认等待超时;0 表示使用 IM_I2D_DEFAULT_TIMEOUT_MS
manage_cache bool 是否由中间件维护源/目标 buffer 的 cache

hal_init 中与中间件行为关系最大的字段:

字段 中间件行为
outstanding_limit 透传给 HAL;合法范围 1..255,默认 0x40
timeout_value 透传给 HAL 的硬件超时上限;默认 UINT32_MAX
timeout_restart 透传给 HAL;默认 true(硬件超时后自动重启)
frame_done_irq_enable / timeout_irq_enable 只影响 hal_i2d_init() 写入的初始中断门;每次 im_i2d_process() 都会把外设中断门重写为「FRAME_DONE + TIMEOUT」,因此不能用这两个字段关闭一次变换的完成/超时事件
outstanding_irq_enable 为 true 时,中间件在本次变换中额外放行 OUTSTANDING 中断,命中会以 IM_ERR_HARDWARE 返回

4. 接口规范

4.1 初始化与默认值

4.1.1 im_i2d_config_struct_init

void im_i2d_config_struct_init(im_i2d_config_t *config);
参数 方向 说明
config 输出 待填写的初始化配置;NULL 时直接返回

填默认值(§4.3),应用在调用 im_i2d_init() 之前先调它,再覆盖需要改的字段。 不要在未初始化的情况下手填 im_i2d_config_t,否则 hal_init 里的其它字段是随机的。

4.1.2 im_i2d_transform_default

void im_i2d_transform_default(im_i2d_transform_t *transform);
参数 方向 说明
transform 输出 待填写的变换对象;NULL 时直接返回

清零后格式为 NV12、旋转为 0°,地址/尺寸/stride 全为 0。每次填变换前都应该先调 它,这样未使用的字段(uv、stride_uv、镜像/翻转)一定是合法值:非 NV12 的 UV 字段如果是随机值,会被 HAL 判为非法参数。

4.1.3 im_i2d_init

error_code_t im_i2d_init(im_i2d_t **out_dev, const im_i2d_config_t *config);
参数 方向 说明
out_dev 输出 成功后写入设备句柄
config 输入 初始化配置,内部保存一份副本

执行顺序:

  1. 校验 out_dev、config 非空;
  2. 校验 config->instance == IM_I2D_INSTANCE_0;
  3. 检查静态设备状态是否为 RESET,否则返回 IM_ERR_BUSY;
  4. 清零设备对象,保存配置副本;
  5. 把 config.hal_init.instance 强制设为 I2D;
  6. timeout_ms == 0 时填入 IM_I2D_DEFAULT_TIMEOUT_MS;
  7. 初始化等待器,状态置 RESET,last_status = IM_ERR_NOT_READY;
  8. 写入 *out_dev;
  9. hal_i2d_init():打开 CPM/模块时钟、安全复位、写 outstanding/超时/初始中断门;
  10. hal_i2d_callback_register(im_i2d_hal_event, dev);
  11. sys_irq_request(IRQ_I2D, im_i2d_irq_entry, "im_i2d", dev) + sys_irq_enable();
  12. 状态置 READY,last_status = IM_OK。

返回码:IM_OK;IM_ERR_INVALID_PARAM;IM_ERR_BUSY;或 HAL/CPM 错误码。

失败回滚:

  • 第 9 步失败:直接返回 HAL 错误码,状态保持 RESET,可以再次调用 im_i2d_init()。
  • 第 10/11 步失败:释放已申请的 IRQ,hal_i2d_deinit()(其中会清空回调),返回错误码, 状态保持 RESET。
  • 只有参数校验失败时才保证 *out_dev 不被改写;HAL 初始化失败时 *out_dev 已经指向 内部对象。失败时一律以返回值为准;该句柄处于 RESET,对其调用 im_i2d_deinit() 是安全的空操作。

IRQ 默认优先级由 sys_irq_request() 的框架默认值决定;需要调整可在初始化后调用 sys_irq_set_priority(IRQ_I2D, ...)。

初始化代码:

#include "im_i2d.h"

static im_i2d_t *s_i2d;

static error_code_t i2d_module_init(void)
{
    im_i2d_config_t cfg;
    error_code_t ret;

    im_i2d_config_struct_init(&cfg);
    /* 默认超时 400 ms;大分辨率可以放宽。manage_cache 保持默认 true。 */
    cfg.timeout_ms = 500U;

    ret = im_i2d_init(&s_i2d, &cfg);
    if (ret != IM_OK) {
        return ret;
    }
    return IM_OK;
}

4.2 im_i2d_process

error_code_t im_i2d_process(im_i2d_t *dev,
                            const im_i2d_transform_t *transform,
                            uint32_t timeout_ms);
参数 方向 说明
dev 输入 im_i2d_init() 返回的句柄
transform 输入 本次变换的完整描述(地址、尺寸、格式、旋转、镜像)
timeout_ms 输入 等待上限(毫秒);0 表示用 config.timeout_ms,再为 0 用 400

行为:

  1. begin:把 transform 映射成 HAL 配置,调 hal_i2d_layout_get() 完成全部几何 校验并推导 stride/平面大小;准备 cache;状态置 RUNNING。
  2. start:打开完成/超时事件,编程寄存器并启动硬件。
  3. wait:阻塞等待完成事件或被取消;超时则主动停止。
  4. finish:屏蔽中断、把事件位图映射成返回码、invalidate 目标 cache、停止硬件、 迁移状态。

返回码见 §5.6。要点:

  • 这是阻塞接口,只能在任务上下文调用;不要在 ISR、定时器回调里调用。
  • 每次调用都会重新解析布局,所以同一个 dev 连续处理多帧时,分辨率/格式可以逐帧 不同(中间件不缓存上一帧的配置)。
  • 一次变换失败后能否继续,取决于返回码:IM_ERR_INVALID_PARAM 等参数类错误可以直接 修正后重试;IM_ERR_TIMEOUT / IM_ERR_HARDWARE 必须 deinit + init。

4.3 im_i2d_cancel

error_code_t im_i2d_cancel(im_i2d_t *dev);
  • 只在 RUNNING 时生效:置 STOPPING,走 finish,停止硬件并唤醒等待者;正在阻塞的im_i2d_process() 返回 IM_ERR_INVALID_STATE。

  • 没有运行中的变换时,返回 last_status,不做任何事。

典型用法是另一个任务中止阻塞中的变换。取消后状态回 READY,可以继续启动下一帧。 不要用「cancel + 立刻 deinit」代替正常等待:取消路径同样会停止硬件并 invalidate 目标, 但应用拿不到完成的图像。

4.4 im_i2d_deinit

error_code_t im_i2d_deinit(im_i2d_t *dev);

执行顺序:

  1. 校验句柄;
  2. 状态为 RESET 时直接返回 IM_OK;
  3. 状态为 RUNNING/STOPPING 时,先以 IM_ERR_INVALID_STATE 走一次 finish:屏蔽 中断、对目标做 cache invalidate、停止硬件,状态回 READY;
  4. 屏蔽全部 I2D 外设中断;
  5. 按注册/使能状态关闭并释放 IRQ_I2D;
  6. 注销 HAL 回调;
  7. hal_i2d_deinit():停止硬件、屏蔽/清除中断、关闭模块时钟与 CPM 时钟;
  8. 状态置 RESET,last_status 置为 hal_i2d_deinit() 的返回值。

从 TIMEOUT/ERROR 状态调用时第 3 步不执行(没有运行中的变换),直接释放资源——这 正是错误后的标准恢复路径。

4.5 对外接口速查

函数说明上下文
im_i2d_config_struct_init()填默认配置任意
im_i2d_transform_default()填默认变换(NV12 / 0°)任意
im_i2d_init()初始化模块、申请 IRQ、初始化 HAL任务/初始化上下文
im_i2d_process()启动一次变换并阻塞等待完成任务上下文
im_i2d_cancel()中止正在运行的变换任务上下文
im_i2d_deinit()停止并释放 IRQ/HAL/时钟任务/关闭上下文

需要并发时,由一个任务串行执行变换,另一个任务用 im_i2d_cancel() 中止。

5. 行为约束与实现说明

5.1 连续处理多帧

static error_code_t i2d_run_frames(im_i2d_t **dev,
                                   const im_i2d_config_t *cfg,
                                   im_i2d_transform_t *t,
                                   uint32_t count)
{
    error_code_t ret = IM_OK;

    for (uint32_t i = 0U; i < count; i++) {
        ret = im_i2d_process(*dev, t, 500U);
        if (ret != IM_OK) {
            /* 只有 READY 状态能启动下一次变换:TIMEOUT/ERROR 必须先重开会话。 */
            if ((ret == IM_ERR_TIMEOUT) || (ret == IM_ERR_HARDWARE) ||
                (ret == IM_ERR_NOT_READY)) {
                (void)im_i2d_deinit(*dev);
                ret = im_i2d_init(dev, cfg);
                if (ret != IM_OK) {
                    return ret;
                }
            } else {
                return ret;
            }
        }
        /* 正常路径下目标 buffer 已经可以直接读(cache 已 invalidate)。 */
    }
    return IM_OK;
}

两帧之间可以改 t 的地址、尺寸、格式、旋转;下一次 begin 会重新解析布局。

6. 典型用法:从零开始

本节假设你手上有一块能跑 SDK 的板子,想用 im_i2d 做「内存里的图 → 旋转/镜像 → 内存里的图」。整个过程是固定的五步。

6.1 第一步:打开设备

#include "im_i2d.h"

static im_i2d_t *s_i2d;

static error_code_t open_i2d(void)
{
    im_i2d_config_t cfg;

    im_i2d_config_struct_init(&cfg);   /* 默认 400 ms、manage_cache = true */
    return im_i2d_init(&s_i2d, &cfg);
}
  • 整个系统只开一次;再次调用会返回 IM_ERR_BUSY。

  • 打开后 IRQ_I2D 与 I2D 时钟都归中间件管,应用不要再申请/关闭。

  • 普通 cacheable 内存不要关 manage_cache。

6.2 第四步:填变换对象并执行

im_i2d_transform_t t;
error_code_t ret;

im_i2d_transform_default(&t);       /* 先给所有字段一个合法默认值 */
t.src.y        = src;
t.src.stride_y = layout.src_y_stride;   /* 源行字节数;相机帧来自 line_length */
t.dst.y        = dst;
t.dst.stride_y = layout.dst_y_stride;   /* 输出行字节数,旋转后可能不同 */
t.width        = 640U;                  /* 源尺寸 */
t.height       = 480U;
t.format       = IM_I2D_FORMAT_YUV422;
t.rotate       = IM_I2D_ROTATE_90;      /* 顺时针 */
t.horizontal_mirror = false;
t.vertical_flip     = false;

ret = im_i2d_process(s_i2d, &t, 500U);
if (ret != IM_OK) {
    /* 按 §5.6 处理:参数错修正后重试;超时/硬件错 deinit + init */
}
/* 到这里 dst 里就是旋转后的图像,cache 已被中间件 invalidate 过 */

打包格式(RAW8 / RGB565 / ARGB8888 / YUV422)的 src.uv / dst.uv 保持默认的NULL。NV12 也可以用连续布局(uv = NULL),此时 UV 紧跟在对齐后的 Y 平面之后, 地址由 HAL 推导。

6.3 第五步:关闭

(void)im_i2d_deinit(s_i2d);   /* 停硬件、释放 IRQ、关时钟 */
s_i2d = NULL;

发生超时/硬件错误后同样走这一步,再重新 im_i2d_init()。

6.4 连续处理多帧

同一 dev 可以反复调用 im_i2d_process(),帧间不需要 deinit/init。见 §6.5 的循环 写法。需要注意:

  • 只有 IM_ERR_TIMEOUT / IM_ERR_HARDWARE / IM_ERR_NOT_READY 需要重开会话;IM_ERR_INVALID_PARAM 直接修正参数重试即可。

  • 每帧的 transform 可以不同(分辨率、旋转都能变),但同一时刻只能有一次变换。

  • 双缓冲:目标 buffer 在 im_i2d_process() 返回后就可以交给下游;下游还在读的时候, 下一帧要写到另一块 buffer,否则会撕裂。

6.5 把结果交给下游

I2D 只负责写内存,之后的目标 buffer 怎么送出去由下游决定:

下游要注意的
JPEGE只接受 NV12 / YUV422(YUYV) / 灰度(Y)等,具体以 im_jpege 的格式枚举为准;宽高填输出尺寸;stride 要等于 宽 × bpp,否则编码器会按连续布局读错
显示(DPU/framebuffer)宽高、stride、像素格式必须与显示配置一致;RGB565/ARGB8888 是常见选择
USB/UVC同 JPEGE;另外要 flush_dcache_force()(或等价操作)再交给 USB DMA
另一个 I2D直接传目标 buffer 即可,中间件会 clean 源
存文件按 stride × 行数 写;若 stride 大于 宽 × bpp,文件里会带行尾填充

如果目标 stride 比 输出宽 × bpp 大(例如为了对齐),下游必须知道这个 stride;不支持 自定义 stride 的下游(例如某些 JPEG 编码配置)就要让 I2D 输出紧凑布局——把dst.stride_y 设为 0 或 输出宽 × bpp。

7. 示例程序解读

示例位于 code/middleware/i2d/example/。它把一张 1280x1280 ARGB8888 的图像顺时针 旋转 90°,抽样校验后打印结论,全程不接相机、不读 SD 卡、不显示。它是「最小可运行」 的 im_i2d 用法,也是 §7 那五步的完整落地。

7.1 内存账

示例同时持有源、目标两帧,参考板默认堆约 15.8 MB(其它板以实际堆大小为准):

配置单帧两帧结果
1280x1280 ARGB8888(默认)6,553,60013.1 MB放得下
640x480 YUV422614,4001.2 MB放得下
1920x1080 NV123,125,7606.2 MB放得下
1920x1080 ARGB88888,294,40016.6 MB大概率 IM_ERR_NO_MEMORY
3840x2160 NV1212,441,60024.9 MBIM_ERR_NO_MEMORY
3840x2160 ARGB888833,177,60066 MBIM_ERR_NO_MEMORY

分配失败是 heap 的限制,不是 I2D 的限制;示例会打印 alloc ... failed, ret=-... 后退出。

8. 把 sensor 画面旋转

真实工程里更常见的需求是:相机采到一帧,方向不对,转正后再送去编码/显示/USB。参考 用例是 test/g32s10/module/vic/slt_103/gc0308_i2d_jpeg_usb/(位于独立的 test/仓库)。它把 GC0308 的 640x480 YUV422 画面顺时针转 90°,编码成 MJPEG,通过 UVC 送给 PC;主机看到的是 480x640 的竖图。

8.1 旋转函数

用例里只有一个薄封装,核心就是填 im_i2d_transform_t:

static error_code_t gc0308_i2d_jpeg_usb_rotate_90cw(im_i2d_t *dev,
                                                   const void *src,
                                                   uint32_t src_stride,
                                                   void *dst,
                                                   uint32_t dst_stride,
                                                   uint32_t width,
                                                   uint32_t height)
{
    im_i2d_transform_t transform;

    im_i2d_transform_default(&transform);
    transform.src.y        = (void *)src;
    transform.src.uv       = NULL;          /* packed 格式必须为 NULL */
    transform.src.stride_y = src_stride;
    transform.dst.y        = dst;
    transform.dst.uv       = NULL;
    transform.dst.stride_y = dst_stride;
    transform.width        = width;         /* 源尺寸;输出尺寸由 HAL 按 rotate 推出 */
    transform.height       = height;
    transform.format       = IM_I2D_FORMAT_YUV422;
    transform.rotate       = IM_I2D_ROTATE_90;   /* 顺时针 */
    transform.horizontal_mirror = false;
    transform.vertical_flip     = false;

    return im_i2d_process(dev, &transform, GC0308_I2D_JPEG_USB_ROTATE_TIMEOUT_MS);
}

几点值得照抄:

  • im_i2d_transform_default() 先清零,保证 uv/stride_uv 等未用字段是合法值;

  • width/height 是源尺寸,旋转后尺寸不填;

  • 打包 YUV422 的 uv = NULL;

  • 超时 500 ms(ROTATE_TIMEOUT_MS),比默认 400 ms 略宽,给总线拥塞留余量;

  • cache 用默认策略,函数里没有任何 im_cache_* 调用。

8.2 迁移到你自己的工程:检查清单

  1. 先确认源格式:YUV422/NV12/RAW8……决定 uv 是否为 NULL、每像素几字节。camera_get_status() 里的 format/line_length 是唯一可信来源。

  2. 源 stride 用生产者给的值(相机的 line_length、解码器的行宽),不要用width × bpp 硬算。

  3. 目标 stride 按输出几何算:90°/270° 时输出宽 = 源高。

  4. 目标 buffer 至少是 layout 的 dst_buffer_size,并 64 字节对齐(后面还要接 JPEGE 时按它的 256 字节要求来)。

  5. cache:保持 manage_cache = true。如果源 buffer 由另一个 DMA 引擎写、且你 在外层已经 clean 过,中间件再 clean 一次是安全的。不要在 im_i2d_process() 返回后 再对目标做 clean——目标此时只有 DMA 写入的新数据,clean 会把 CPU 里的旧脏行 写回、盖掉结果;多一次 invalidate 倒不会出错,只是多余。

  6. 超时:默认 400 ms;大分辨率或总线同时被 IPU/USB 占用时给到 500~1000 ms。

  7. 失败处理:单帧失败计数并丢帧;IM_ERR_TIMEOUT/IM_ERR_HARDWARE 连续出现时 重开会话(deinit + init)。

  8. 下游尺寸:编码器/显示配置必须用输出尺寸和输出 stride,而不是源尺寸。

  9. 方向验证:第一次接入时跑一次 §9.5 的图案自测,确认顺时针/逆时针符合预期。

8.3 sensor 旋转特有现象

现象常见原因排查
画面转了但方向反ROTATE_90 是顺时针;想要逆时针用 ROTATE_270用图案自测确认
画面上下颠倒相机装反或需要 vertical_flip叠加 vertical_flip,不是换 rotate
红蓝对调YUV 字节序(YVYU/UYVY)由上游决定查相机中间件的 dump_data_order,不是 I2D
每行错位/斜条纹dst_stride 用了源 stride用输出宽 × bpp
输出整片 0 或旧数据目标 buffer 太小、cache 未 invalidate、I2D 没启动看自测的「目标非零」计数;检查返回码
第一帧正确、后续花目标 buffer 被下游同时复用双缓冲,或等下游读完再写
偶发 IM_ERR_TIMEOUT总线被 IPU/USB 抢、分辨率大放宽超时;必要时调 outstanding_limit

9. 调试与验证

9.1 可观测性

中间件本身没有任何日志,也没有状态查询接口。定位问题只能依靠:

  • im_i2d_process() / im_i2d_init() 的返回码(§5.6);

  • 应用自己在调用前后打印的几何、stride、buffer 地址与耗时;

  • 目标 buffer 的内容(图案自测、抽样比对);

  • 直接读 I2D 寄存器(VERSION、IRQ_STATE、IRQ_MASK、OUTSTANDING)。

9.2 上板诊断顺序

  1. 先跑 register,确认 I2D 寄存器窗口可访问;

  2. 再跑 code/middleware/i2d/example,确认单帧旋转与抽样校验通过;

  3. 再跑 function,确认所有格式与几何组合;

  4. 出现 IM_ERR_TIMEOUT/IM_ERR_HARDWARE 时,先降分辨率、降 outstanding 压力, 排除总线带宽因素;

  5. 出现数据不一致时,优先检查 stride、NV12 的 UV 偏移、旋转后的 buffer 尺寸和 cache 维护;

  6. 需要判断是否是总线拥塞时,用 test/g32s10/module/i2d/outstanding 的扫描方法, 而不是继续怀疑几何。