设计说明
源码说明
源文件目录:
aic-mpp$ tree
.
├── base // 公共模块:包括内存分配和链表等基础功能
│ ├── memory
├── ge // 2D 图形加速模块
├── ve // 编解码器模块
│ ├── include // ve 模块头文件
│ ├── common // 编解码器公共组件
│ ├── decoder
│ ├── h264 // h.264 解码模块
│ ├── jpeg // jpg 解码模块
│ └── png // png 解码模块
├── vin // video input模块
├── include // mpp 对外头文件
├── mpp_test // mpp 测试用例
2
3
4
5
6
7
8
9
10
11
12
13
14
15
软件架构
MPP 软件框图如下所示:
总体上,分为三层:
- 应用层:Luban-Lite 提供了 MPP player(播放器)、MPP 测试、LVGL Demo,也支持增加其他 App
- MPP 中间件:从功能上可以划分为 4 大块
- MPP Decoder,实现 h264、jpeg、png 等解码功能
- MPP Encoder,实现 jpeg 编码
- MPP GE,实现 2D 图形加速功能
- MPP VIN,实现视频输入采集的功能
- Driver 层:MPP 需要用到的驱动有 VE、GE、DVP、Camera 驱动
MPP Decoder 设计及接口说明
MPP Decoder 由三个主要模块组成:
- 解码模块(H264、JPEG、PNG 等):负责将码流数据解码成视频图像
- 输入码流数据管理模块(Packet manager):负责视频、图片码流数据和 buffer 的管理
- 显示帧管理模块(Frame manager):负责解码图像 buffer 的管理
packet 管理机制
Packet manager 负责管理码流数据和 buffer。初始化时,该模块申请一块物理连续的内存(buffer 大小可由外部配置),用于存放视频/图片码流数据。
Packet manager 管理的数据单元为 packet,packet 表示一笔码流数据,它可以是完整的一帧数据,也支持不是完整的一帧数据。每个 packet 与物理内存中的码流数据一一对应,它记录了每一笔码流的物理内存基地址、物理内存结束地址、物理内存偏移、虚拟内存地址、码流数据长度等信息。
packet 通过 empty list 和 ready list 两个链表进行管理。其中,empty list 用于存放空闲的 packet,ready list 用于存放待解码的 packet。
送码流数据时,从 empty list 获取一个空闲 packet,填充数据后,再把 packet 放入 ready list;
解码前,解码器从 ready list 获取一个填充数据的 packet,使用完后再把该 packet 放入 empty list。
frame 管理机制
Frame manager 负责管理图像 buffer。Frame manager 内部通过两个链表来管理图像 buffer:empty list 和 render list。其中,empty list 存放可以给解码输出使用的图像 buffer,render list 存放解码完成但还未显示的图像 buffer。在运行过程中,正在显示的图像 buffer 和用于参考的图像 buffer 可能不在这两个 list 中。
1.frame 状态迁移
初始化时,该模块申请指定个数的图像 buffer(个数可由外部配置),每个图像 buffer 的信息存放在内部数组中。每个图像 buffer 有 4 种状态:
- Decoding: 该帧正在被解码器使用(用于解码输出或作为参考帧)
- wait_render: 该帧在 render list 中,等待显示
- Rendering: 该帧正在被显示占用
- IDLE: 该帧处于空闲状态(既没有被显示占用,也没有被解码器用作参考帧)
其状态转移如下图所示:
初始化时,所有图像 buffer 都在 empty list 中,此时处于 IDLE 状态;
解码模块从 empty list 链表头部获取一个空图像 buffer,此时 buffer 被解码模块占用,从 IDLE 状态变为 Decoding 状态;
解码完成后,解码模块还图像数据。此时分两种情况:
1)如果当前帧还未被显示,该帧加入 render list 链表尾部,从 Decoding 状态变为 wait render 状态;
2)该帧不再用做参考帧且已显示完成,此时该帧加入 empty list 链表尾部,由 Decoding 状态进入 IDLE 状态;
显示模块从 render list 链表头部取一帧图像,此时当前帧由 wait render 状态进入 Rendering 状态;
显示模块还图像 buffer,分两种情况:
1)如果当前帧不用于参考,此时由 Rendering 状态回到 IDLE 状态,该帧加入 empty list 链表尾部;
2)如果当前帧用于参考,此时由 Rendering 状态进入 Decoding 状态,该图像 buffer 不进入任何队列,等待解码器还参考帧;
2.frame manager 调用流程
对于 JPEG、PNG 这类没有参考帧概念的编码格式,每一帧的状态是唯一的,解码后的数据帧可直接送 render list
但对于 H264 这类有参考帧的编码格式,解码后的视频帧可能既被显示占用也会被解码器用作参考帧,并且由于双向参考帧的存在,视频帧需要重排序后才能送显示。不同于 JPG,H264 解码库内部存在一个 delay list 用于为显示帧重排序。
物理连续内存使用情况
H264 解码所需的物理连续内存如下所示:
内存占用模块 | 计算方式 | 说明 |
---|---|---|
输入码流 | 大小由应用层配置 | |
输出帧 | widthheight3/2*frame_num | frame_num至少需要(参考帧个数+1)个显示占用个数可由应用层通过struct decode_config结构体中的extra_frame_num配置 |
帧内预测(需要上一行数据) | 帧格式:width2 MBAFF:width4 | |
宏块信息 | 固定12K | |
dblk模块(上一个宏块行最后4行数据) | 帧格式:width8 MBAFF:width16 | |
co-located信息 | 固定68K | |
每一帧co-located数据缓存 | (width/16)*(height/16)*32*frame_num |
注解
co-located 两个 buffer,I、P 帧解码时会往 buffer 里写数据,B 帧解码时从 buffer 读数据。如果当前码流中没有 B 帧,这两块内存也需要申请。
MPP Decoder 调用流程
在调用 MPP Decoder 的解码函数时,解码模块从 Packet manager 取一笔码流,同时从 Frame manager 取一个空闲图像 buffer,对码流进行解码并输出图像到图像 buffer。
解码后,解码模块将码流 buffer 归还 Packet manager,将解码图像 buffer 归还 Frame manager。
为保证解码效率,建议调用者创建 3 个线程实现解码功能:
- send data thread: 通过 mpp_decoder_get_packet 和 mpp_decoder_put_packet 这两个接口把码流数据送到 packet 管理模块
- decode thread: 通过调用 mpp_decoder_decode 控制解码,解码库从 packet 管理模块取一笔码流数据,解码完成后,将视频帧送入 frame 管理模块
- render thread: 通过 mpp_decoder_get_frame 和 mpp_decoder_put_frame 两个接口从 frame 管理模块获取视频帧,并控制该帧显示时机
MPP Decoder 数据结构
struct decode_config
struct decode_config {
enum mpp_pixel_format pix_fmt; // output pixel format
int bitstream_buffer_size; // bitstream buffer size in pm
int packet_count; // packet number in pm
int extra_frame_num; // extra frame number in fm
};
2
3
4
5
6
decode_config 结构体用于配置解码器初始化使用的参数。
- pix_fmt 表示解码输出的颜色格式
- bitstream_buffer_size 表示存放输入码流缓存的总长度
- packet_count 表示 packet manager 中 packet 的最大个数
- extra_frame_num 表示解码器额外分配的帧个数,主要用于缓存显示帧以保证显示平滑。
struct mpp_packet
struct mpp_packet {
void *data;
int size;
long long pts;
unsigned int flag;
};
2
3
4
5
6
mpp_packet 结构体用于表示输入码流信息。
- data 表示码流数据存放的起始地址
- size 表示该笔码流数据长度
- pts 表示该笔码流的时间戳
- flag 表示该笔码流的标记位,目前仅用于确定该码流是否为最后一笔码流(PACKET_FLAG_EOS)
struct mpp_frame
struct mpp_size {
int width;
int height;
};
struct mpp_rect {
int x;
int y;
int width;
int height;
};
enum mpp_buf_type {
MPP_DMA_BUF_FD,
MPP_PHY_ADDR,
};
struct mpp_buf {
enum mpp_buf_type buf_type;
union {
int fd[3];
unsigned int phy_addr[3];
};
unsigned int stride[3];
struct mpp_size size;
unsigned int crop_en;
struct mpp_rect crop;
enum mpp_pixel_format format;
unsigned int flags;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
- buf_type:表示 mpp_buf 类型,以 fd 方式 MPP_DMA_BUF_FD 或 以物理地址方式 MPP_PHY_ADDR;
- fd[3]:表示 buffer 三个分量的 fd
- phy_addr[3]:表示 buffer 三个分量的物理地址
- stride[3]:表示 buffer 三个分量的 stride
- size:表示 buffer 的宽、高
- crop_en:表示该 buffer 是否需要 crop
- crop:表示该 buffer 的 crop 信息
- format:表示该 buffer 的颜色格式类型
struct mpp_frame {
struct mpp_buf buf;
long long pts;
unsigned int id;
unsigned int flags;
};
2
3
4
5
6
- buf:表示 mpp_frame 的 buffer 信息
- pts:表示 mpp_frame 的时间戳
- id:表示 mpp_frame 的唯一标识
- flags:表示 mpp_frame 的标志位
enum mpp_dec_errno
enum mpp_dec_errno {
DEC_ERR_NOT_SUPPORT = 0x90000001,
DEC_ERR_NO_EMPTY_PACKET = 0x90000002, // no packet in empty list
DEC_ERR_NO_READY_PACKET = 0x90000003, //
DEC_ERR_NO_EMPTY_FRAME = 0x90000004, //
DEC_ERR_NO_RENDER_FRAME = 0x90000005, //
DEC_ERR_NULL_PTR = 0x90000006,
DEC_ERR_FM_NOT_CREATE = 0x90000006,
};
2
3
4
5
6
7
8
9
- DEC_ERR_NOT_SUPPORT:该码流不支持
- DEC_ERR_NO_EMPTY_PACKET:packet manager 中缺少空闲的 packet,可能是解码速度小于送 packet 速度,此时需要等待一段时间;
- DEC_ERR_NO_READY_PACKET:packet manager 中缺少填好码流数据的 packet,可能是送 packet 速度小于解码速度,此时需要等待一段时间;
- DEC_ERR_NO_EMPTY_FRAME:frame manager 中缺少空闲的 frame,表示所有帧都处于使用状态,通常是解码速度大于显示速度导致,此时需要等待一段时间;
- DEC_ERR_NO_RENDER_FRAME:frame manager 中缺少待显示的 frame,表示所有帧都处于空闲状态,通常是解码速度小于显示速度导致,此时需要等待一段时间;
- DEC_ERR_NULL_PTR:表示接口函数输入参数存在空指针
- DEC_ERR_FM_NOT_CREATE:表示在获取待显示 frame 时 frame manager 还未创建
enum mpp_codec_type
enum mpp_codec_type {
MPP_CODEC_VIDEO_DECODER_H264 = 0x1000, // decoder
MPP_CODEC_VIDEO_DECODER_MJPEG,
MPP_CODEC_VIDEO_DECODER_PNG,
MPP_CODEC_VIDEO_ENCODER_H264 = 0x2000, // encoder
};
2
3
4
5
6
mpp_codec_type 枚举类型表示支持的编解码格式。
enum mpp_dec_cmd
enum mpp_dec_cmd {
MPP_DEC_INIT_CMD_SET_EXT_FRAME_ALLOCATOR, // frame buffer allocator
MPP_DEC_INIT_CMD_SET_ROT_FLIP_FLAG,
MPP_DEC_INIT_CMD_SET_SCALE,
MPP_DEC_INIT_CMD_SET_CROP_INFO,
MPP_DEC_INIT_CMD_SET_OUTPUT_POS,
};
2
3
4
5
6
7
- MPP_DEC_INIT_CMD_SET_EXT_FRAME_ALLOCATOR:表示由外部设置帧 buffer 分配器
- MPP_DEC_INIT_CMD_SET_ROT_FLIP_FLAG: 表示设置旋转、镜像后处理,只用于 JPEG
- MPP_DEC_INIT_CMD_SET_SCALE:表示设置缩放系数,只用于 JPEG
- MPP_DEC_INIT_CMD_SET_CROP_INFO:表示设置输出 crop 信息
- MPP_DEC_INIT_CMD_SET_OUTPUT_POS:表示设置解码图像在输出缓存的位置
MPP Decoder 接口设计
接口如下 :
struct decode_config {
enum mpp_pixel_format pix_fmt; // output pixel format
int bitstream_buffer_size; // bitstream buffer size in pm
int packet_count; // packet number in pm
int extra_frame_num; // extra frame number in fm
};
struct mpp_decoder* create_mpp_decoder(enum mpp_codec_type type);
void destory_mpp_decoder(struct mpp_decoder* decoder);
int mpp_decoder_init(struct mpp_decoder *decoder, struct decode_config *config);
int mpp_decoder_decode(struct mpp_decoder* decoder);
int mpp_decoder_control(struct mpp_decoder* decoder, int cmd, void *param);
int mpp_decoder_reset(struct mpp_decoder* decoder);
int mpp_decoder_get_packet(struct mpp_decoder* decoder, struct mpp_packet* packet, int size);
int mpp_decoder_put_packet(struct mpp_decoder* decoder, struct mpp_packet* packet);
int mpp_decoder_get_frame(struct mpp_decoder* decoder, struct mpp_frame* frame);
int mpp_decoder_put_frame(struct mpp_decoder* decoder, struct mpp_frame* frame);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mpp_decoder_create
函数原型 | struct mpp_decoder* mpp_decoder_create(enum mpp_codec_type type) |
---|---|
功能说明 | 创建mpp_decoder对象 |
参数定义 | type: 解码器类型 |
返回值 | mpp_decoder对象 |
注意事项 | 无 |
mpp_decoder_destroy
函数原型 | void mpp_decoder_destroy(struct mpp_decoder* decoder) |
---|---|
功能说明 | 销毁mpp_decoder对象 |
参数定义 | decoder: mpp_decoder对象 |
返回值 | 无 |
注意事项 | 无 |
mpp_decoder_init
函数原型 | int mpp_decoder_init(struct mpp_decoder *decoder, struct decode_config *config) |
---|---|
功能说明 | 初始化解码器 |
参数定义 | decoder: mpp_decoder对象 config:解码器的配置参数 |
返回值 | 0:成功 <0:失败 |
注意事项 | 无 |
mpp_decoder_decode
函数原型 | int mpp_decoder_decode(struct mpp_decoder* decoder) |
---|---|
功能说明 | 解码一笔数据 |
参数定义 | decoder: mpp_decoder对象 |
返回值 | 0:成功 <0:失败 |
注意事项 | 无 |
mpp_decoder_control
函数原型 | int mpp_decoder_control(struct mpp_decoder* decoder, int cmd, void* param) |
---|---|
功能说明 | 向mpp_decoder对象发送控制命令 |
参数定义 | decoder: mpp_decoder对象 cmd: 控制命令类型 param: 控制参数 |
返回值 | 0:成功 <0:失败 |
注意事项 | 无 |
mpp_decoder_reset
函数原型 | int mpp_decoder_reset(struct mpp_decoder* decoder) |
---|---|
功能说明 | 重置mpp_decoder对象 |
参数定义 | decoder: mpp_decoder对象 |
返回值 | 0:成功 <0:失败 |
注意事项 | 无 |
mpp_decoder_get_packet
函数原型 | int mpp_decoder_get_packet(struct mpp_decoder* decoder, struct mpp_packet* packet, int size) |
---|---|
功能说明 | 获取一个写流数据的packet |
参数定义 | decoder: mpp_decoder对象 packet:码流数据结构指针 size:上层应用申请packet的buffer大小 |
返回值 | 0:成功 <0:失败 |
注意事项 | 无 |
mpp_decoder_put_packet
函数原型 | int mpp_decoder_put_packet(struct mpp_decoder* decoder, struct mpp_packet* packet) |
---|---|
功能说明 | 归还码流数据的packet对象 |
参数定义 | decoder: mpp_decoder对象 packet:码流数据结构指针 |
返回值 | 0:成功 <0:失败 |
注意事项 | 无 |
mpp_decoder_get_frame
函数原型 | int mpp_decoder_get_frame(struct mpp_decoder* decoder, struct mpp_frame* frame) |
---|---|
功能说明 | 获取一个视频帧对象 |
参数定义 | decoder: mpp_decoder对象 frame:视频帧数据结构指针 |
返回值 | 0:成功 <0:失败 |
注意事项 | 无 |
mpp_decoder_put_frame
函数原型 | int mpp_decoder_put_frame(struct mpp_decoder* decoder, struct mpp_frame* frame) |
---|---|
功能说明 | 归还视频帧对象 |
参数定义 | decoder: mpp_decoder对象 frame:视频帧数据结构指针 |
返回值 | 0:成功 <0:失败 |
注意事项 | 无 |
MPP Decoder 参考 Demo
以下 Demo 为基本流程调用,具体实现可以参考代码 mpp/mpp_test/picture_decoder_test.c
//* 1.创建 mpp_decoder 对象
struct mpp_decoder* dec = mpp_decoder_create(type);
struct decode_config config;
config.bitstream_buffer_size = (file_len + 1023) & (~1023);
config.extra_frame_num = 0;
config.packet_count = 1;
config.pix_fmt = MPP_FMT_ARGB_8888;
//* 2. 初始化 mpp_decoder
mpp_decoder_init(dec, &config);
//* 3. 获取一个空的 packet
struct mpp_packet packet;
memset(&packet, 0, sizeof(struct mpp_packet));
mpp_decoder_get_packet(dec, &packet, file_len);
//* 4. 把视频码流数据拷贝到 packet
fread(packet.data, 1, file_len, fp);
packet.size = file_len;
packet.flag = PACKET_FLAG_EOS;
//* 5. 归还 packet
mpp_decoder_put_packet(dec, &packet);
//* 6. 解码该笔码流数据
mpp_decoder_decode(dec);
//* 7. 获取解码后视频帧数据
struct mpp_frame frame;
memset(&frame, 0, sizeof(struct mpp_frame));
mpp_decoder_get_frame(dec, &frame);
//* 8. 显示该视频帧
// render_frame...
//* 9. 归还该视频帧
mpp_decoder_put_frame(dec);
//* 10. 销毁 mpp_decoder
mpp_decoder_destory(dec);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
MPP Encoder 设计及接口说明
MPP Encoder 目前只支持 JPEG 图片编码。
接口设计
mpp_encode_jpeg
函数原型 | int mpp_encode_jpeg(struct mpp_frame* frame, int quality, int dma_buf_fd, int buf_len, int* len) |
---|---|
功能说明 | 编码一帧 JPEG 图片 |
参数定义 | frame:待编码的原始 YUV 数据 quality: 编码质量,取值范围1~100,1表示编码图片质量最差,100表示最好 dma_buf_fd: 输出 JPEG 图片存放的 dma-buf fd buf_len: 输出 JPEG 图片 dma-buf 的长度 len: 输出 JPEG 图片的真实大小 |
返回值 | 0: 成功 <0:失败 |
注意事项 | 无 |
小技巧
输出 JPEG 图片的缓存 buffer 由调用者申请,但调用者并不知道编码后图片的实际大小,为避免 VE 写输出数据时越界,该 buffer 需要预先申请较大的内存。
MPP Encoder 参考 Demo
以下 Demo 为基本流程调用,具体实现可以参考代码 mpp/mpp_test/jpeg_encoder_test.c
//* 1. 获取 dma-buf device 句柄
int dma_fd = dmabuf_device_open();
//* 2. 设置输入 YUV 数据结构体
struct mpp_frame frame;
// ....
/* 3. 申请编码输出 buffer */
int len = 0;
int buf_len = width * height * 4/5 * quality / 100;
int jpeg_data_fd = dmabuf_alloc(dma_fd, buf_len);
//* 4. 编码 JPEG 图片
mpp_encode_jpeg(&frame, quality, jpeg_data_fd, buf_len, &len);
//* 5. 保存编码后 JPEG 图片
unsigned char* jpeg_vir_addr = dmabuf_mmap(jpeg_data_fd, buf_len);
FILE* fp_save = fopen("/save.jpg", "wb");
fwrite(jpeg_vir_addr, 1, len, fp_save);
fclose(fp_save);
//* 6. 释放资源
dmabuf_munmap(jpeg_vir_addr, buf_len);
dmabuf_free(jpeg_data_fd);
dmabuf_device_close(dma_fd);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
MPP zlib 设计及接口说明
MPP zlib 的功能:利用硬件加速解压 zlib 文件。
接口设计
mpp_zlib_uncompressed
函数原型 | int mpp_zlib_uncompressed(unsigned char *compressed_data,unsigned int compressed_len, unsigned char *uncompressed_data,unsigned int uncompressed_len); |
---|---|
功能说明 | 解压zlib文件 |
参数定义 | compressed_data - 压缩数据起始地址 compressed_len - 压缩数据长度 uncompressed_data - 存放解压数据 buffer 的起始地址 uncompressed_len - 存放解压数据 buffer 的长度 |
返回值 | >0,解压数据实际长度; <0,失败 |
注意事项 | compressed_data - 16 byte 对齐,填充数据后,需要刷 cache compressed_len - 实际有效数据长度 uncompressed_data - 8 byte 对齐,获取数据前,先清掉 cache uncompressed_len - 8 byte 对齐,必须大于等于解压数据实际长度 |
参考 Demo
#include "dfs.h"
#include "unistd.h"
#include "mpp_zlib.h"
#include "mpp_mem.h"
#include "mpp_log.h"
#include <console.h>
#include "aic_core.h"
static void print_help(char *program)
{
printf("Compile time: %s\n", __TIME__);
printf("usage:%s input_file out_file out_put_buffer_len \n", program);
printf("note:out_put_buffer_len >= out_file_size \n");
printf("exsample:%s readme.zlib readme.txt 204800\n",program);
}
int zlib_test(int argc,char **argv)
{
int ret = 0;
int fd_in = 0;
int fd_out = 0;
int file_len;
int in_len_align;
int out_len;
int out_len_align;
int r_len=0,w_len=0;
int uncompress_len;
unsigned long in_buff = 0;;
unsigned long in_buff_align;
unsigned long out_buff = 0;
unsigned long out_buff_align;
int align;
unsigned int before;
unsigned int after;
if (argc != 4) {
print_help(argv[0]);
return -1;
}
fd_in = open(argv[1], O_RDONLY);
if (fd_in < 0) {
loge("open %s fail\n",argv[1]);
return -1;
}
file_len = lseek(fd_in, 0, SEEK_END);
lseek(fd_in, 0, SEEK_SET);
#define INPUT_BUFFER_ALIGN 16
if (CACHE_LINE_SIZE > INPUT_BUFFER_ALIGN) {
align = CACHE_LINE_SIZE;
} else {
align = INPUT_BUFFER_ALIGN;
}
//input buffer len align max of {CACHE_LINE_SIZE,
INPUT_BUFFER_ALIGN}
in_len_align = (file_len+align-1)/align*align;
in_buff = (unsigned long)aicos_malloc(MEM_CMA, in_len_align+align-1);
if (in_buff == 0) {
loge("mpp_alloc fail\n");
ret = -1;
goto _exit;
}
//input buffer addr align max of {CACHE_LINE_SIZE,INPUT_BUFFER_ALIGN}
in_buff_align = ((in_buff+align-1)&(~(align-1)));
r_len = read(fd_in,(void *)in_buff_align, file_len);
logd("r_len:%d,in_len:%d\n",r_len,file_len);
//flush cache*
aicos_dcache_clean_range((unsigned long *)in_buff_align, (int64_t)in_len_align);
out_len = atoi(argv[3]);
if (out_len < file_len) {
loge("param error :%d\n",out_len);
ret = -1;
goto _exit;
}
// out buffer len align CACHE_LINE_SIZE
out_len_align = (out_len + CACHE_LINE_SIZE -1)/CACHE_LINE_SIZE*CACHE_LINE_SIZE;
out_buff = (unsigned long)aicos_malloc(MEM_CMA, out_len_align+(CACHE_LINE_SIZE -1));
if (out_buff == 0) {
loge("mpp_alloc fail\n");
ret = -1;
goto _exit;
}
//out buffer addr align CACHE_LINE_SIZE
out_buff_align = ((out_buff+CACHE_LINE_SIZE -1)&(~(CACHE_LINE_SIZE-1)));
// uncompressed
before = aic_get_time_us();
uncompress_len = mpp_zlib_uncompressed((unsigned char*)in_buff_align, file_len, (unsigned char*)out_buff_align, out_len_align);
after = aic_get_time_us();
logd("diff:%u\n",after-before);
if (uncompress_len < 0) {
loge("mpp_zlib_uncompressed fail\n");
ret = -1;
goto _exit;
}
//save uncompressed data
fd_out = open(argv[2], O_RDWR|O_CREAT);
if (fd_out < 0) {
loge("open %s fail\n",argv[2]);
ret = -1;
goto _exit;
}
// invalid cache
aicos_dcache_invalid_range((unsigned long *)out_buff_align, (int64_t)out_len_align);
w_len = write(fd_out,(void *)out_buff_align, uncompress_len);
logd("w_len:%d,uncompress_len:%d\n", w_len,uncompress_len);
close(fd_out);
_exit:
if(out_buff)
aicos_free(MEM_CMA, (void *)out_buff);
if(in_buff)
aicos_free(MEM_CMA, (void *)in_buff);
if(fd_in)
close(fd_in);
return ret;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
MPP GE 设计及接口说明
MPP GE设计说明请参考。
由于驱动支持非命令队列和命令队列两种模式,在提供的用户MPP接口中,对调用驱动的接口进行了封装,保持了统一的调用API,建议用户统一使用MPP中间层API。在命令队列模式下,task会先缓存在用户的cmd buffer中,当调用mpp_ge_emit后,会通过write接口把命令写入内核的ring buffer。
mpp_ge_open
struct mpp_ge *mpp_ge_open();
函数原型 | struct mpp_ge *mpp_ge_open(); |
---|---|
功能说明 | 打开ge设备 |
参数定义 | 无 |
返回值 | struct mpp_ge 结构体指针 |
NULL:失败 | |
注意事项 | 无 |
mpp_ge_close
void mpp_ge_close(struct mpp_ge *ge);
函数原型 | void mpp_ge_close(struct mpp_ge *ge); |
---|---|
功能说明 | 关闭ge设备 |
参数定义 | ge: struct mpp_ge 结构体指针 |
返回值 | 无 |
注意事项 | 无 |
mpp_ge_get_mode
enum ge_mode mpp_ge_get_mode(struct mpp_ge *ge);
函数原型 | enum ge_mode mpp_ge_get_mode(struct mpp_ge *ge); |
---|---|
功能说明 | 获取GE模式 |
参数定义 | ge: struct mpp_ge 结构体指针 |
返回值 | enum ge_mode枚举类型 |
通过返回值可以获取GE是否工作在命令队列模式 | |
注意事项 | 无 |
mpp_ge_fillrect
int mpp_ge_fillrect(struct mpp_ge *ge, struct ge_fillrect *fillrect);
函数原型 | int mpp_ge_fillrect(struct mpp_ge *ge, struct ge_fillrect *fillrect); |
---|---|
功能说明 | 矩形填充 |
参数定义 | ge: struct mpp_ge 结构体指针 |
fillrect: struct ge_fillrect 结构体指针 | |
返回值 | 0:成功 |
<0:失败 | |
注意事项 | normal(非命令队列)模式此接口是同步的。 |
命令队列模式此接口是异步的: | |
(1)当用户的缓存buffer足够时仅把命令缓存在用户 | |
(2)当用户的缓存空间不够的时候,先通过write接口,把缓存的命令全部 | |
写入驱动,然后再把当前命令缓存到用户buffer |
矩形填充在目标图像中指定一块矩形区域,填充颜色格式只能为ARGB8888格式,在进行固定颜色填充的时候,不支持scaler,不支持90/180/270度旋转,不支持镜像,填充的颜色可以和目标层进行alpha blending和color key。
mpp_ge_bitblt
int mpp_ge_bitblt(struct mpp_ge *ge, struct ge_bitblt *blt);
函数原型 | int mpp_ge_bitblt(struct mpp_ge *ge, struct ge_bitblt *blt); |
---|---|
功能说明 | 位块搬移 |
参数定义 | ge: struct mpp_ge 结构体指针 |
blt: struct ge_bitblt 结构体指针 | |
返回值 | 0:成功 |
<0:失败 | |
注意事项 | normal(非命令队列)模式此接口是同步的。 |
命令队列模式此接口是异步的: | |
(1)当用户的缓存buffer足够时仅把命令缓存在用户 | |
(2)当用户的缓存空间不够的时候,先通过write接口,把缓存的命令全部 | |
写入驱动,然后再把当前命令缓存到用户buffer |
位块搬移可以分两种情况:
1.原图的矩形区域搬移到目标图的矩形区域中不进行缩放。
2.原图的矩形区域搬移到目标图的矩形区域中同时进行放大或者缩小。
在进行位块搬移的同时可以进行alpha blending和color key,同时也支持90/180/270度旋转和镜像。
mpp_ge_rotate
int mpp_ge_rotate(struct mpp_ge *ge, struct ge_rotation *rot);
函数原型 | int mpp_ge_rotate(struct mpp_ge *ge, struct ge_rotation *rot); |
---|---|
功能说明 | 任意角度旋转 |
参数定义 | ge: struct mpp_ge 结构体指针 |
rot: struct ge_rotation 结构体指针 | |
返回值 | 0:成功 |
<0:失败 | |
注意事项 | normal(非命令队列)模式此接口是同步的。 |
命令队列模式此接口是异步的: | |
(1)当用户的缓存buffer足够时,仅把命令缓存在用户 | |
(2)当用户的缓存空间不够时,先通过write接口,把缓存的命令全部 | |
写入驱动,然后再把当前命令缓存到用户buffer |
进行任意角度旋转的时候可以进行alpha blending,并且可以指定原图和目标图的旋转中心,任意角度旋转原图和目标图都只支持RGB格式。 其中旋转角度传给驱动的是旋转角度的sin和cos值,为2.12定点数,其中小数部分12 bits,则应用程序计算sin和cos值的方法如下:
#include <stdio.h>
#include <math.h>
#define PI 3.14159265
#define SIN(x) (sin(x * PI / 180.0))
#define COS(x) (cos(x * PI / 180.0))
double degree = 30.0; // (0 <= degree < 360)
angle_sin = (int)(SIN(degree) * 4096);
angle_cos = (int)(COS(degree) * 4096);
// 应用程序也可以预先生成需要的角度的sin和cos值列表,通过查表减小计算量
2
3
4
5
6
7
8
9
10
11
12
mpp_ge_emit
int mpp_ge_emit(struct mpp_ge *ge);
函数原型 | int mpp_ge_emit(struct mpp_ge *ge); |
---|---|
功能说明 | 向驱动发送命令 |
参数定义 | ge: struct mpp_ge 结构体指针 |
返回值 | 0:成功 |
<0:失败 | |
注意事项 | normal(非命令队列)模式此接口为空,不产生任何作用。 |
命令队列模式此接口通过write接口,把用户buffer中缓存的命令写入驱动。 |
mpp_ge_sync
int mpp_ge_sync(struct mpp_ge *ge);
函数原型 | int mpp_ge_sync(struct mpp_ge *ge); |
---|---|
功能说明 | 阻塞等待所有任务执行完成 |
参数定义 | ge: struct mpp_ge 结构体指针 |
返回值 | 0:成功 |
<0:失败 | |
注意事项 | normal(非命令队列)模式此接口为空,不产生任何作用。 |
命令队列模式此接口通过调用IOC_GE_SYNC接口,等待所有任务都完成。 |
MPP VIN 设计及接口说明
MPP VIN模块主要实现两个功能:
1.对上封装了DVP、Camera驱动的ioctl接口(尽量做到和Linux MPP VIN保持一致)
2.对视频Buf队列的管理,实现了DVP应用、DVP驱动之间的Buf轮转管理
Buf队列管理
VIN模块中的队列管理,参考了Linux的V4L2框架,通过 struct vb_queue 结构中的两个Buf队列来管理,DVP驱动中还需要维护一个buf_list来配合DVP控制器的地址更新。 整个Buf流转的过程如下图:
queued_list:是一些空闲Buf,等待Sensor的数据到来后,DVP驱动会从这个队列中找可用Buf来保存下一帧数据。
done_list:是一些填了视频数据的Buf,等待用户来处理这些数据,一般用户处理完后需要将Buf还给驱动,也就是通过Q_BUF命令还给queued_list。
从图中的流转过程看,运行期间,在某一时刻,DVP需要使用一个Buf,APP需要使用一个Buf,QBuf需要有一个Buf在等待(否则DVP的done中断来了后发现没有等待的Qbuf会发生丢帧),一共至少要有3个Buf。
以YUV422格式计算,有两个plane,在V4L2框架中这一组plane算一个Buf,3个Buf就需要申请6个plane,总大小 = 长 _ 宽 _ 2 * 3。
对于每一帧图像数据来说,DVP的输出有两个plane:Y和UV。针对DVP的两种输出格式:YUV422_COMBINED_NV16和YUV420_COMBINED_NV12,两个plane的空间大小如下表:
Plane | YUV422_COMBINED_NV16 | YUV420_COMBINED_NV12 |
---|---|---|
Plane Y | Width * height | Width * height |
Plane UV | Width * height | Width * height / 2 |
根据前面对“Buf队列管理”的分析可知:我们要分配的内存空间至少要有3个Buf,每个Buf包含两个Plane。
接口设计
mpp_vin_init
函数原型 | int mpp_vin_init(char *camera) |
---|---|
功能说明 | 初始化VIN模块,包括打开Camera设备、DVP设备,为Video Buf分配内存池 |
参数定义 | camera - 摄像头的设备名称 |
返回值 | 0,成功;<0,失败 |
注意事项 | 内存池默认配置8 MB,需要根据实际的应用场景来修改 |
mpp_vin_deinit
函数原型 | int mpp_vin_deinit(void) |
---|---|
功能说明 | 释放VIN模块的资源,包括关闭Camera设备和DVP设备,释放Video Buf内存池 |
参数定义 | 无 |
返回值 | 无 |
注意事项 | 无 |
mpp_dvp_ioctl
函数原型 | int mpp_dvp_ioctl(int cmd, void *arg) |
---|---|
功能说明 | DVP 设备的ioctl接口 |
参数定义 | cmd - ioctl 命令码 arg - ioctl 命令相应的参数 |
返回值 | 0,成功;<0,失败 |
注意事项 | 无 |
参考Demo
命令行工具test_dvp实现了一个完整的 Sensor -> DVP -> DE
数据通路,从Camera采集数据、然后会显到DE的Video layer。 整体的处理流程如下图(图中按照访问对象分为三列,实际上整体是串行执行):
小技巧
test-dvp支持了先旋转再显示,上图中为了简化并未展示GE的处理。在图中将当前Buf传给Video Layer环节,如果打开了旋转功能(参数 -a),会先将Buf送给GE旋转,然后才给DE去显示。
此Demo的代码实现可作为MPP VIN的APP设计参考:(详见test_dvp.c)
#define VID_BUF_NUM 3
#define VID_BUF_PLANE_NUM 2
#define VID_SCALE_OFFSET 0
static bool g_dvp_running = true;
static const char sopts[] = "f:c:a:h";
static const struct option lopts[] = {
{"format", required_argument, NULL, 'f'},
{"capture", required_argument, NULL, 'c'},
{"angle", required_argument, NULL, 'a'},
{"usage", no_argument, NULL, 'h'},
{0, 0, 0, 0}
};
struct aic_dvp_data {
int w;
int h;
int frame_size;
int frame_cnt;
int rotation;
int dst_fmt; // output format
struct mpp_video_fmt src_fmt;
uint32_t num_buffers;
struct vin_video_buf binfo;
};
static struct aic_dvp_data g_vdata = {0};
#ifdef AIC_DISPLAY_DRV
static struct mpp_fb *g_fb = NULL;
static struct aicfb_screeninfo g_fb_info = {0};
#endif
#ifdef SUPPORT_ROTATION
static struct mpp_ge *g_ge_dev = NULL;
#endif
/* Functions */
static void usage(char *program)
{
printf("Usage: %s [options]: \n", program);
printf("\t -f, --format\t\tformat of input video, NV16/NV12 etc\n");
printf("\t -c, --count\t\tthe number of capture frame.(0 means infinity) \n");
#ifdef SUPPORT_ROTATION
printf("\t -a, --angle\t\tthe angle of rotation \n");
#endif
printf("\t -u, --usage \n");
printf("\n");
printf("Example: %s -f nv16 -c 1\n", program);
}
static long long int str2int(char *_str)
{
if (_str == NULL) {
pr_err("The string is empty!\n");
return -1;
}
if (strncmp(_str, "0x", 2))
return atoi(_str);
else
return strtoll(_str, NULL, 16);
}
int get_fb_info(void)
{
int ret = 0;
#ifdef AIC_DISPLAY_DRV
ret = mpp_fb_ioctl(g_fb, AICFB_GET_SCREENINFO, &g_fb_info);
if (ret < 0)
pr_err("ioctl() failed! errno: -%d\n", -ret);
#endif
pr_info("Screen width: %d, height %d\n",
g_fb_info.width, g_fb_info.height);
return ret;
}
int set_ui_layer_alpha(int val)
{
int ret = 0;
#ifdef AIC_DISPLAY_DRV
struct aicfb_alpha_config alpha = {0};
alpha.layer_id = AICFB_LAYER_TYPE_UI;
alpha.enable = 1;
alpha.mode = 1;
alpha.value = val;
ret = mpp_fb_ioctl(g_fb, AICFB_UPDATE_ALPHA_CONFIG, &alpha);
if (ret < 0)
pr_err("ioctl() failed! errno: -%d\n", -ret);
#endif
return ret;
}
int sensor_get_fmt(void)
{
int ret = 0;
struct mpp_video_fmt f = {0};
ret = mpp_dvp_ioctl(DVP_IN_G_FMT, &f);
if (ret < 0) {
pr_err("ioctl() failed! err -%d\n", -ret);
// return -1;
}
g_vdata.src_fmt = f;
g_vdata.w = g_vdata.src_fmt.width;
g_vdata.h = g_vdata.src_fmt.height;
pr_info("Sensor format: w %d h %d, code 0x%x, bus 0x%x, colorspace 0x%x\n",
f.width, f.height, f.code, f.bus_type, f.colorspace);
return 0;
}
int dvp_subdev_set_fmt(void)
{
int ret = 0;
ret = mpp_dvp_ioctl(DVP_IN_S_FMT, &g_vdata.src_fmt);
if (ret < 0) {
pr_err("ioctl() failed! err -%d\n", -ret);
return -1;
}
return 0;
}
int dvp_cfg(int width, int height, int format)
{
int ret = 0;
struct dvp_out_fmt f = {0};
f.width = g_vdata.src_fmt.width;
f.height = g_vdata.src_fmt.height;
f.pixelformat = format;
f.num_planes = VID_BUF_PLANE_NUM;
ret = mpp_dvp_ioctl(DVP_OUT_S_FMT, &f);
if (ret < 0) {
pr_err("ioctl() failed! err -%d\n", -ret);
return -1;
}
return 0;
}
int dvp_request_buf(struct vin_video_buf *vbuf)
{
int i;
if (mpp_dvp_ioctl(DVP_REQ_BUF, (void *)vbuf) < 0) {
pr_err("ioctl() failed!\n");
return -1;
}
pr_info("Buf Plane[0] size Plane[1] size\n");
for (i = 0; i < vbuf->num_buffers; i++) {
pr_info("%3d 0x%x %8d 0x%x %8d\n", i,
vbuf->planes[i * vbuf->num_planes].buf,
vbuf->planes[i * vbuf->num_planes].len,
vbuf->planes[i * vbuf->num_planes + 1].buf,
vbuf->planes[i * vbuf->num_planes + 1].len);
}
return 0;
}
void dvp_release_buf(int num)
{
#if 0
int i;
struct video_buf_info *binfo = NULL;
for (i = 0; i < num; i++) {
binfo = &g_vdata.binfo[i];
if (binfo->vaddr) {
munmap(binfo->vaddr, binfo->len);
binfo->vaddr = NULL;
}
}
#endif
}
int dvp_queue_buf(int index)
{
if (mpp_dvp_ioctl(DVP_Q_BUF, (void *)(ptr_t)index) < 0) {
pr_err("ioctl() failed!\n");
return -1;
}
return 0;
}
int dvp_dequeue_buf(int *index)
{
int ret = 0;
ret = mpp_dvp_ioctl(DVP_DQ_BUF, (void *)index);
if (ret < 0) {
pr_err("ioctl() failed! err -%d\n", -ret);
return -1;
}
return 0;
}
int dvp_start(void)
{
int ret = 0;
ret = mpp_dvp_ioctl(DVP_STREAM_ON, NULL);
if (ret < 0) {
pr_err("ioctl() failed! err -%d\n", -ret);
return -1;
}
return 0;
}
int dvp_stop(void)
{
int ret = 0;
ret = mpp_dvp_ioctl(DVP_STREAM_OFF, NULL);
if (ret < 0) {
pr_err("ioctl() failed! err -%d\n", -ret);
return -1;
}
return 0;
}
#define DVP_SCALE 1
int video_layer_disable(void)
{
int ret = 0;
#ifdef AIC_DISPLAY_DRV
struct aicfb_layer_data layer = {0};
layer.enable = 0;
ret = mpp_fb_ioctl(g_fb, AICFB_UPDATE_LAYER_CONFIG, &layer);
if (ret < 0)
pr_err("g_fb ioctl AICFB_UPDATE_LAYER_CONFIG failed !");
#endif
return ret;
}
#ifdef SUPPORT_ROTATION
int do_rotate(struct aic_dvp_data *vdata, int index)
{
struct ge_bitblt blt = {0};
struct mpp_buf *src = &blt.src_buf;
struct mpp_buf *dst = &blt.dst_buf;
struct vin_video_buf *binfo = &vdata->binfo;
int ret = 0;
if (vdata->dst_fmt == MPP_FMT_NV16) {
src->format = MPP_FMT_NV16;
dst->format = MPP_FMT_NV16;
} else {
src->format = MPP_FMT_NV12;
dst->format = MPP_FMT_NV12;
}
src->buf_type = MPP_PHY_ADDR;
src->phy_addr[0] = binfo->planes[index * VID_BUF_PLANE_NUM].buf;
src->phy_addr[1] = binfo->planes[index * VID_BUF_PLANE_NUM + 1].buf;
src->stride[0] = vdata->w;
src->stride[1] = vdata->w;
src->size.width = vdata->w;
src->size.height = vdata->h;
dst->buf_type = MPP_PHY_ADDR;
dst->phy_addr[0] = binfo->planes[VID_BUF_NUM * VID_BUF_PLANE_NUM].buf;
dst->phy_addr[1] = binfo->planes[VID_BUF_NUM * VID_BUF_PLANE_NUM + 1].buf;
if (vdata->rotation == MPP_ROTATION_0
|| vdata->rotation == MPP_ROTATION_180) {
dst->stride[0] = vdata->w;
dst->stride[1] = vdata->w;
dst->size.width = vdata->w;
dst->size.height = vdata->h;
} else {
dst->stride[0] = vdata->h;
dst->stride[1] = vdata->h;
dst->size.width = vdata->h;
dst->size.height = vdata->w;
}
blt.ctrl.flags = vdata->rotation;
#if 0
printf("GE: %d(%d) * %d -> %d * %d, canvas %d(%d) * %d\n",
src->size.width, src->stride[0],
src->size.height,
dst->crop.width, dst->crop.height,
dst->size.width, dst->stride[0],
dst->size.height);
#endif
ret = mpp_ge_bitblt(g_ge_dev, &blt);
if (ret < 0) {
pr_err("GE bitblt failed\n");
return -1;
}
ret = mpp_ge_emit(g_ge_dev);
if (ret < 0) {
pr_err("GE emit failed\n");
return -1;
}
ret = mpp_ge_sync(g_ge_dev);
if (ret < 0) {
pr_err("GE sync failed\n");
return -1;
}
return 0;
}
#endif
int video_layer_set(struct aic_dvp_data *vdata, int index)
{
#ifdef AIC_DISPLAY_DRV
int i;
struct aicfb_layer_data layer = {0};
struct vin_video_buf *binfo = &vdata->binfo;
layer.layer_id = AICFB_LAYER_TYPE_VIDEO;
layer.enable = 1;
#if DVP_SCALE
#if 1
layer.scale_size.width = g_fb_info.width - VID_SCALE_OFFSET * 2;
layer.scale_size.height = g_fb_info.height - VID_SCALE_OFFSET * 2;
layer.pos.x = VID_SCALE_OFFSET;
layer.pos.y = VID_SCALE_OFFSET;
#else
/* Reduce the size to fb0*1/2 */
layer.scale_size.width = g_fb_info.width / 2;
layer.scale_size.height = g_fb_info.height / 2;
layer.pos.x = g_fb_info.width / 2 - VID_SCALE_OFFSET;
layer.pos.y = g_fb_info.height / 2 - VID_SCALE_OFFSET;
#endif
#else
layer.scale_size.width = vdata->w;
layer.scale_size.height = vdata->h;
layer.pos.x = g_fb_info.width - vdata->w;
layer.pos.y = 0;
#endif
if (vdata->rotation == MPP_ROTATION_0
|| vdata->rotation == MPP_ROTATION_180) {
layer.buf.size.width = vdata->w;
layer.buf.size.height = vdata->h;
} else {
layer.buf.size.width = vdata->h;
layer.buf.size.height = vdata->w;
}
if (vdata->dst_fmt == MPP_FMT_NV16)
layer.buf.format = MPP_FMT_NV16;
else
layer.buf.format = MPP_FMT_NV12;
layer.buf.buf_type = MPP_PHY_ADDR;
for (i = 0; i < VID_BUF_PLANE_NUM; i++) {
layer.buf.stride[i] = layer.buf.size.width;
layer.buf.phy_addr[i] = binfo->planes[index * VID_BUF_PLANE_NUM + i].buf;
}
if (mpp_fb_ioctl(g_fb, AICFB_UPDATE_LAYER_CONFIG, &layer) < 0) {
pr_err("ioctl() failed!\n");
return -1;
}
#endif
return 0;
}
void dvp_thread_stop()
{
g_dvp_running = false;
}
static void test_dvp_thread(void *arg)
{
int i, index = 0;
struct timespec begin, now;
if (dvp_request_buf(&g_vdata.binfo) < 0)
return;
for (i = 0; i < VID_BUF_NUM; i++) {
if (dvp_queue_buf(i) < 0)
return;
}
if (dvp_start() < 0)
return;
#if DVP_SCALE
pr_info("DVP scale is enable\n");
#else
pr_info("DVP scale is disable\n");
#endif
gettimespec(&begin);
g_dvp_running = true;
i = 0;
while (g_dvp_running) {
if (g_vdata.frame_cnt != 0 && i >= g_vdata.frame_cnt) {
break;
}
i++;
if (dvp_dequeue_buf(&index) < 0)
break;
// pr_debug("Set the buf %d to video layer\n", index);
if (g_vdata.rotation) {
#ifdef SUPPORT_ROTATION
if (do_rotate(&g_vdata, index) < 0)
break;
if (video_layer_set(&g_vdata, VID_BUF_NUM) < 0)
break;
#endif
} else {
if (video_layer_set(&g_vdata, index) < 0)
break;
}
dvp_queue_buf(index);
if (i && (i % 1000 == 0)) {
gettimespec(&now);
show_fps("DVP", &begin, &now, i);
}
}
if ((i > 0) && ((i - 1) % 1000 != 0)) {
gettimespec(&now);
show_fps("DVP", &begin, &now, i);
}
dvp_stop();
dvp_release_buf(g_vdata.binfo.num_buffers);
mpp_vin_deinit();
if (g_fb) {
video_layer_disable();
mpp_fb_close(g_fb);
g_fb = NULL;
}
#ifdef SUPPORT_ROTATION
if (g_ge_dev) {
mpp_ge_close(g_ge_dev);
g_ge_dev = NULL;
}
#endif
pr_info("Total receive %d frames, so exit\n");
}
static void cmd_test_dvp(int argc, char **argv)
{
int c;
aicos_thread_t thid = NULL;
memset(&g_vdata, 0, sizeof(struct aic_dvp_data));
g_vdata.dst_fmt = MPP_FMT_NV16;
g_vdata.frame_cnt = 1;
optind = 0;
while ((c = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) {
switch (c) {
case 'f':
if (strncasecmp("nv12", optarg, strlen(optarg)) == 0)
g_vdata.dst_fmt = MPP_FMT_NV12;
continue;
case 'c':
g_vdata.frame_cnt = str2int(optarg);
continue;
#ifdef SUPPORT_ROTATION
case 'a':
g_vdata.rotation = (str2int(optarg) % 360) / 90;
break;
#endif
case 'h':
usage(argv[0]);
return;
default:
break;
}
}
pr_info("Capture %d frames from camera\n", g_vdata.frame_cnt);
pr_info("DVP out format: %s\n",
g_vdata.dst_fmt == MPP_FMT_NV16 ? "NV16" : "NV12");
if (mpp_vin_init(CAMERA_DEV_NAME))
return;
if (sensor_get_fmt() < 0)
goto error_out;
if (dvp_subdev_set_fmt() < 0)
goto error_out;
if (g_vdata.dst_fmt == MPP_FMT_NV16)
g_vdata.frame_size = g_vdata.w * g_vdata.h * 2;
else if (g_vdata.dst_fmt == DVP_OUT_FMT_NV12)
g_vdata.frame_size = (g_vdata.w * g_vdata.h * 3) >> 1;
g_fb = mpp_fb_open();
if (!g_fb) {
pr_err("Failed to open FB\n");
goto error_out;
}
#ifdef SUPPORT_ROTATION
if (g_vdata.rotation) {
g_ge_dev = mpp_ge_open();
if (!g_ge_dev)
goto error_out;
printf("Rotate %d by GE\n", g_vdata.rotation * 90);
}
#endif
if (get_fb_info() < 0)
goto error_out;
if (set_ui_layer_alpha(0) < 0)
goto error_out;
if (dvp_cfg(g_vdata.w, g_vdata.h, g_vdata.dst_fmt) < 0)
goto error_out;
#ifdef SUPPORT_ROTATION
/* Use the last buf connect GE and Video layer */
g_vdata.num_buffers = VID_BUF_NUM + 1;
#else
g_vdata.num_buffers = VID_BUF_NUM;
#endif
thid = aicos_thread_create("test_dvp", 4096, 0, test_dvp_thread, NULL);
if (thid == NULL) {
pr_err("Failed to create DVP thread\n");
return;
}
return;
error_out:
mpp_vin_deinit();
#ifdef SUPPORT_ROTATION
if (g_ge_dev) {
mpp_ge_close(g_ge_dev);
g_ge_dev = NULL;
}
#endif
if (g_fb) {
mpp_fb_close(g_fb);
g_fb = NULL;
}
}
MSH_CMD_EXPORT_ALIAS(cmd_test_dvp, test_dvp, Test DVP and camera);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564