Skip to main content

VE 使用指南

1. 模块介绍

1.1. 术语定义

术语定义注释说明
VEVideo Engine视频加速引擎
MPPMedia Process Platform通用多媒体处理软件平台

1.2. 模块简介

VE(Video Engine)是 Artinchip 自主研发的视频加速引擎,支持视频、图片的编解码功能。在 Linux 设备中,注册为字符设备,通过中间件 MPP(Media Process Platform)实现对 VE 字符设备驱动的调用,实现对视频、图片多媒体文件的编解码。

VE 支持的视频编解码标准如下:

标准ProfileLevel最大分辨率最小分辨率最大码率
H264/AVC decoderBP/MP/HP4.21920x108816x1680Mbps
MJPEG decoderBaseline8192x819216x16120Mbps(YUV444)
PNG decoder4096x4096
JPEG encoderBaseline8192x819216x16120Mbps(YUV444)
  • H.264/AVC 解码器:

    全兼容 ITU-T 建议 H.264 规定的 BP、MP 和 HP支持 CABAC/CAVLC支持可变块大小(16x16, 16x8, 8x16, 8x8, 8x4, 4x8 and 4x4)支持错误检查

  • MJPEG 基线解码器

    兼容 ISO/IEC 10918-1 JPEG 基线支持 1 或者 3 个颜色分量支持8 bit 位深支持4:2:0, 4:2:2, 2:2:4, 4:4:4 和4:0:0 颜色格式(每个MCU包括最多6个8x8块)支持1/2、1/4、1/8缩放支持0、90、180、270度旋转以及水平、垂直镜像,不能和缩放同时开启

  • PNG解码器

    支持 png8,存储方式为索引色存储,索引色位深支持1,2,4,8 bit,索引最多256色,支持通过数据块tRNS来设置索引透明度支持 png24,每个像素包含 R, G, B 三个通道,每个通道8 bits支持 png32,每个像素包含 R, G, B 和 alpha 四个通道,每个通道8bits支持 png 标准5种 filter(none,sub,up,average,paeth)支持标准的 zlib和 gzip 解压缩,LZ77 最大窗口为32K

  • JPEG编码器

    兼容 ISO/IEC 10918-1 JPEG 基线支持 1 或者 3 个颜色分量支持8 bit 位深支持4:2:0, 4:2:2, 2:2:4, 4:4:4 和4:0:0 颜色格式

2. 参数配置

2.1. 内核配置

在luban根目录下执行 make kernel-menuconfig,进入kernel的功能配置,按如下选择:

Linux
Device Drivers --->
Graphics support --->
Artinchip Graphics --->
<*> Artinchip Video Engine Driver

2.2. DTS参数配置

VE模块DTS参数已验证,一般情况下不需要进行更改,如有必要修改,请先咨询原厂技术支持。

2.2.1. D211的配置

ve: ve@0x18c00000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "artinchip,aic-ve-v1.0";
reg = <0x0 0x18c00000 0x0 0x4000>;
interrupts-extended = <&plic0 61 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cmu CLK_VE>;
clock-names = "ve_clk";
resets = <&rst RESET_VE>;
reset-names = "ve_rst";
status = "okay";
};

3. 调试指南

待完善

4. 测试指南

VE 驱动无法独立运行,必须依赖 MPP 的调用才能运行。因此,VE 驱动测试请参考MPP模块的测试用例。

5. 设计说明

5.1. 源码说明

源代码位于:linux-5.10/drivers/video/artinchip/ve/aic_ve.c

5.2. 模块架构

VE 硬件需要由内核态 VE 驱动和用户态程序MPP相互配合实现编解码功能。其中,内核 VE 驱动主要负责VE硬件资源初始化和获取等,用户态 MPP 程序主要负责处理编解码逻辑、寄存器配置等。

VE 驱动基于字符设备实现,应用层通过设备节点(/dev/aic_ve)进行交互。

../../../_https://photos.100ask.net/artinchip-docs/d213-devkit/ve_framework1.png

图 6.20 ve驱动框架

基本流程如下:

  • 1.用户态程序通过 open /dev/aic_ve打开 VE 驱动;
  • 2.用户态程序通过 ioctl 获取ve硬件设备独占权限;
  • 3.用户态程序完成一次独立的编解码任务:配置寄存器、等待VE当前任务完成;
  • 4.用户态程序通过 ioctl 释放ve硬件设备独占权限;
  • 5.视频帧解码完成,用户态程序调用 close 关闭 VE 驱动。

5.3. 关键流程设计

5.3.1. 寄存器地址空间映射

由于寄存器是在用户空间配置,VE 驱动需要实现 mmap 接口,将寄存器地址空间映射到用户态进程的虚拟地址空间,以便用户态程序读写寄存器。

不同 VE 硬件版本,寄存器基地址和寄存器地址范围可能不一样,因此需要在 dts 中针对不同平台正确配置 reg 项(如下所示)。

ve: ve@0x18c00000 {
...
reg = <0x0 0x18c00000 0x0 0x4000>;
...
};

用户态程序映射寄存器地址空间步骤如下:

  • 通过 ioctl 获取寄存器地址范围;
  • 调用 mmap 映射寄存器地址空间到虚拟地址空间,并得到寄存器在虚拟地址空间的起始地址;
  • 根据寄存器起始地址和偏移地址读写 VE 寄存器。

5.3.2. 中断处理流程

1.VE 中断类型有三种,包括:finish 中断、error 中断、bit request 中断。

  • finish 中断表示 VE 任务正常结束
  • error 中断表示 VE 处理任务过程中出错
  • bit request 中断表示当前数据不足以完成一次完整的任务,需要继续发送数据

2.VE 的中断处理分为两部分:VE 驱动和用户态程序

1)内核 VE 驱动的中断处理比较简单,只是把中断状态返回到用户态

  • 读模块的状态寄存器,并将状态值返回到用户态,由用户态程序处理;
  • 关闭中断,避免中断重复发送;
  • 中断超时处理,当中断在规定时间(默认值为2s)内未收到,则表示出现异常,此时返回出错由用户态处理。

注解

  1. 不同模块(H264/JPEG/PNG等)的状态寄存器不一样,读状态寄存器时需要根据模块获取相应状态寄存器的值;
  2. VE 每次任务处理完后,都会关闭中断,因此在下次启动 VE 前,必须再次使能 VE 中断。

2)用户态程序根据 VE 驱动返回的中断状态并处理中断

  • finish 中断处理:VE 正常结束,释放 VE 硬件设备独占权限
  • bit request 中断处理:再次发送一笔数据,重新启动 VE 执行任务,重复以上过程,直到 VE 返回 finish 或 error 中断
  • error 中断:VE 执行出错,必须对 VE 进行硬件复位避免错误影响下次任务
  • 等中断超时:此时 VE 出现未知异常,必须对 VE 进行硬件复位

注解

处理 bit request 中断时,不能释放 VE 硬件设备独占权限,因为两次任务处理有相关性。如果这两次任务之间执行其他任务,会影响 VE 内部状态,从而导致该次任务执行出错。

5.3.3. 多进程支持

VE 设备只有一个,所以同一时间只能执行一个任务。当多个进程同时操作 VE 驱动时,VE 只能分时复用。因此用户态进程在操作 VE 之前,必须获得 VE 的独占权限。 VE 驱动通过 IOC_VE_GET_CLIENT/IOC_VE_PUT_CLIENT 这两个 ioctl 接口为用户态提供获取和释放 VE 独占权限功能。

VE 驱动实现这一功能的几个概念:

  • client:与进程相关的对象,保存该进程的信息,包括:进程 pid、该进程的 task、该进程使用的 dmabuf 队列等
  • service:与 VE 资源相关的唯一对象,所有 client 通过 service 获取 VE 硬件的使用权限

具体实现如下:

  • VE 驱动在 probe 中创建一个唯一的 service 对象,用于管理 VE 资源
  • VE 驱动为每个进程创建一个 client 对象,用于维护该进程的 VE 状态
  • 用户态进程调用 IOC_VE_GET_CLIENT 接口时,对应进程的 client 会通过 service 判断当前 VE 是否正在运行。如果是,当前 client 在此等待 VE 资源释放信号;否则得到 VE 操作权限
  • 用户态进程等待当前解码任务执行完成后(等到中断),调用 IOC_VE_PUT_CLIENT 接口,当前 client 发出释放 VE 资源信号通知其它 client

5.3.4. 多线程支持

当一个进程中的多个线程同时操作 VE 驱动时,VE 只能分时复用。为避免每个线程都执行 VE 驱动初始化等重复操作,建议用户态程序使用单例模式实现 VE 驱动调用。具体实现可参考 mpp 代码(base/ve/ve.c)。

5.4. 数据结构设计

// VE 寄存器地址范围
struct ve_info {
int reg_size;
};

// dma buffer信息,用于获取dma buffer的物理地址
struct dma_buf_info {
int fd;
unsigned int phy_addr;
};

// 中断信息结构体
struct wait_info {
int wait_time;
unsigned int reg_status;
};

5.5. 接口设计

用户进程通过 /dev/aic_ve 节点打开 VE 驱动。

5.5.1. IOC_VE_GET_CLIENT

接口语法:

int ioctl(int fd, unsigned long cmd);
| 功能说明 | 获取 VE 设备独占权限   |
| -------- | ---------------------- |
| 参数 | CMD:IOC_VE_GET_CLIENT |
| 返回值 | 0:成功<0:失败 |
| 注意事项 | 无 |

5.5.2. IOC_VE_PUT_CLIENT

接口语法:

int ioctl(int fd, unsigned long cmd);
| 功能说明 | 释放 VE 设备独占权限                                      |
| -------- | --------------------------------------------------------- |
| 参数 | CMD:IOC_VE_PUT_CLIENT |
| 返回值 | 0:成功<0:失败 |
| 注意事项 | IOC_VE_GET_CLIENT 和 IOC_VE_PUT_CLIENT 的调用必须一一对应 |

5.5.3. IOC_VE_WAIT

接口语法:

int ioctl(int fd, unsigned long cmd, struct wait_info *info);
| 功能说明 | 等待 VE 驱动编解码完成,获取 VE 寄存器状态                   |
| -------- | ------------------------------------------------------------ |
| 参数 | CMD:IOC_VE_WAITinfo: 指向struct wait_info指针 |
| 返回值 | 0:成功<0:失败 |
| 注意事项 | IOC_VE_WAIT 的调用必须在 IOC_VE_GET_CLIENT 和 IOC_VE_PUT_CLIENT 之间 |

5.5.4. IOC_VE_GET_INFO

接口语法:

int ioctl(int fd, unsigned long cmd, struct ve_info *info);
| 功能说明 | 获取 struct ve_info 数据                         |
| -------- | ------------------------------------------------ |
| 参数 | CMD:IOC_VE_GET_INFOinfo: 指向struct ve_info指针 |
| 返回值 | 0:成功<0:失败 |
| 注意事项 | 无 |

5.5.5. IOC_VE_SET_INFO

接口语法:

int ioctl(int fd, unsigned long cmd, struct ve_info *info);
| 功能说明 | 设置 struct ve_info 数据                         |
| -------- | ------------------------------------------------ |
| 参数 | CMD:IOC_VE_SET_INFOinfo: 指向struct ve_info指针 |
| 返回值 | 0:成功<0:失败 |
| 注意事项 | 无 |

5.5.6. IOC_VE_RESET

接口语法:

int ioctl(int fd, unsigned long cmd);
| 功能说明 | VE 驱动硬件复位   |
| -------- | ----------------- |
| 参数 | CMD:IOC_VE_RESET |
| 返回值 | 0:成功<0:失败 |
| 注意事项 | 无 |

5.5.7. IOC_VE_ADD_DMA_BUF

接口语法:

int ioctl(int fd, unsigned long cmd, struct dma_buf_info *buf);
| 功能说明 | 加载 DMA buffer, 获取 DMA buffer 物理地址               |
| -------- | ------------------------------------------------------- |
| 参数 | CMD:IOC_VE_ADD_DMA_BUFbuf: 指向struct dma_buf_info指针 |
| 返回值 | 0:成功<0:失败 |
| 注意事项 | 无 |

5.5.8. IOC_VE_RM_DMA_BUF

接口语法:

int ioctl(int fd, unsigned long cmd, struct dma_buf_info *buf);
| 功能说明 | 移除 DMA buffer                                        |
| -------- | ------------------------------------------------------ |
| 参数 | CMD:IOC_VE_RM_DMA_BUFbuf: 指向struct dma_buf_info指针 |
| 返回值 | 0:成功<0:失败 |
| 注意事项 | 无 |

5.6. APP Demo参考

以下 demo 实现 VE 驱动基本调用流程,具体可参考 mpp 代码(base/ve/ve.c)

//* 1. 打开VE驱动
int fd = open("/dev/aic_ve", O_RDWR);

//* 2. 获取ve寄存器空间大小
struct ve_info info = {0};
ioctl(fd, IOC_VE_GET_INFO, &info);

//* 3. 映射寄存器地址空间
unsigned long reg_base = (unsigned long)mmap(NULL,
info.reg_size,
PROT_READ | PROT_WRITE, MAP_SHARED,
fd,
0);

//* 4. 获取VE权限
ioctl(fd, IOC_VE_GET_CLIENT);

//* 5. 配置寄存器(省略)
...

//* 6. 等VE中断
struct wait_info wt_info;
wt_info.wait_time = VE_TIMEOUT;
int ret = ioctl(fd, IOC_VE_WAIT, &wt_info);
if(ret < 0) {
// 中断超时,VE硬件复位
ioctl(fd, IOC_VE_RESET);
}

//* 7. 释放VE权限
ioctl(fd, IOC_VE_PUT_CLIENT);

6. 常见问题

6.1. VE驱动未加载

6.1.1. 现象

在/dev路径下,未发现字符设备/dev/aic_ve

6.1.2. 原因分析

确认内核中是否加载VE驱动。

../../../_https://photos.100ask.net/artinchip-docs/d213-devkit/ve-faq.jpg

6.2. 解码报错

6.2.1. 现象

解码异常报错,未得到解码后的视频帧,或者视频帧数据出错。

6.2.2. 原因分析

逐步排除以下原因:

  1. 源文件是否是VE驱动支持的媒体文件格式,具体请参考模块介绍的硬件编解码格式章节;
  2. 源文件是否损坏,可参考PC端是否可正常播放;
  3. 码流解析是否正确,相关VE寄存器信息是否配置正确。

解码报错后,VE驱动中返回了解码当前帧的寄存器状态信息,请参考VE Spec说明对应分析。