实现摄像头的显示屏实时预览
硬件要求:
- DshanPI-CanMV开发板
- GC2093摄像头
- Type-C数据线 x2
- MIPI显示屏/HDMI显示屏
开发环境:
- Ubuntu20.04
配套源码:https://pan.baidu.com/s/1VBd0n3FKO0bj8yHOWk4HEw?pwd=ov5d 提取码:ov5d
具体位置: 12_多媒体应用示例源码\05_sample_vivo*
1.数据流框图
摄像头采集数据后,送至显示屏设备对应的图层中显示。
2.应用程序编译
2.1 新增程序
2.1.1 新建工程文件夹
在k230_sdk/src/big/mpp/userapps/sample目录下新建工程
mkdir sample_vivo_100ask
2.1.2 修改Makefile
修改k230_sdk/src/big/mpp/userapps/sample目录下的Makefile文件,新增sample_vivo_100ask工程的编译规则
@cd sample_vivo_100ask; make || exit 1
新增sample_vivo_100ask工程的清理规则:
@cd sample_vivo_100ask; make clean
2.1.3 进入工程目录
cd sample_vivo_100ask
2.1.4 新建源码文件
vi sample_vivo_100ask.c
填入源码。
2.1.5 新建Makefile文件
vi Makefiel
填入以下内容:
include $(MPP_SRC_DIR)/userapps/sample/mpp.mk
include $(MPP_SRC_DIR)/userapps/sample/rt-smart.mk
CURRECT_DIR_NAME=$(shell basename `pwd`)
LOCAL_SRC_DIR = $(shell pwd)
BIN = $(MPP_SRC_DIR)/userapps/sample/elf/$(CURRECT_DIR_NAME).elf
LIBPATH = $(MPP_LIB_PATH)
LIBS = $(MPP_LIBS)
LOCAL_CFLAGS = -I$(LOCAL_SRC_DIR) \
SRCS = $(wildcard $(LOCAL_SRC_DIR)/*.c)\
OBJS = $(patsubst %.c,%.o,$(SRCS))
all: $(BIN)
@-rm -f $(OBJS)
@echo "${PWD}/Makefile all"
$(OBJS): %.o : %.c
@echo CC $@
@$(CC) $(CC_CFLAGS) $(LOCAL_CFLAGS) $(BSP_CFLGAS) $(RTSMART_CFLAGS) $(MPP_USER_CFLGAS) -c $< -o $@
$(BIN): $(OBJS)
@echo LD $@
@$(CC) -o $(BIN) $(LINKFLAG) -Wl,--whole-archive -Wl,--no-whole-archive -n --static $(OBJS) -L$(LIBPATH) -Wl,--start-group $(LIBS) -Wl,--end-group
clean:
echo "${PWD}/Makefile clean"
-rm -rf $(BIN)
-rm -f $(OBJS)
.PHONY: all clean
2.2 程序编译
1.进入K230SDK目录
cd ~/k230_sdk
2.下载toolchain和准备源码
source tools/get_download_url.sh && make prepare_sourcecode
3.挂载工具链目录
sudo mount --bind $(pwd)/toolchain /opt/toolchain
4.配置板级型号
make CONF=k230_canmv_dongshanpi_defconfig prepare_memory
5.编译程序
make mpp-apps
等待编译完成,编译完成后,可执行程序sample_vivo_100ask.elf
会生成在k230_sdk/src/big/mpp/userapps/sample/elf
目录下。
使用ADB将可执行程序传输至开发板中
adb push src/big/mpp/userapps/sample/elf/sample_vivo_100ask.elf /sharefs/app
2.2 程序运行
使用串口软件访问开发板的大核串口终端。
如果没有关闭开机自启程序,可按下q+回车键可退出开机自启程序。
1.进入可执行文件目录
cd /sharefs/app
2.运行程序
./sample_vivo_100ask.elf -dev 0
执行完成后效果如下所示:
此时可以看到显示屏实时显示摄像头捕获数据。
3.程序解析
3.1 设置摄像头属性
chn_count = 0;
cur_dev = atoi(argv[2]);;
printf("cur_dev(%d), dev_count(%d)\n", cur_dev, dev_count);
device_obj[cur_dev].dev_num = cur_dev; //sensor设备号
device_obj[cur_dev].dev_enable = K_TRUE; //是否启用设备
device_obj[cur_dev].ae_enable = K_TRUE;//default enable ae 自动曝光
device_obj[cur_dev].awb_enable = K_TRUE;//default enable awb 自动白平衡
device_obj[cur_dev].dnr3_enable = K_FALSE;//default disable 3ndr 3D降噪
device_obj[cur_dev].hdr_enable = K_FALSE;//default disable hdr 高动态范围
device_obj[cur_dev].dw_enable = K_FALSE;//default disable dw 宽动态模式
//parse dev paramters
printf("cur_chn(%d), chn_count(%d)\n", cur_chn ,chn_count);
device_obj[cur_dev].chn_num[cur_chn] = cur_chn; //VI通道的编号
device_obj[cur_dev].chn_enable[cur_chn] = K_TRUE; //是否启动通道
device_obj[cur_dev].preview[cur_chn] = K_TRUE;//default enable preview 通道的预览功能
device_obj[cur_dev].sensor_type = GC2093_MIPI_CSI2_1920X1080_30FPS_10BIT_LINEAR;//传感器类型
3.2 复位并设置显示设备
// rst display subsystem
kd_display_reset();
connector_type = ILI9806_MIPI_2LAN_480X800_30FPS;
printf("connector_type = ILI9806_MIPI_2LAN_480X800_30FPS;");
3.3 设置摄像头输出格式
k_u16 out_width = 800;
out_width = VICAP_ALIGN_UP(out_width, 16);
device_obj[cur_dev].out_win[cur_chn ].width = out_width;
k_u16 out_height = 480;
// out_height = VICAP_ALIGN_UP(out_height, 16);
device_obj[cur_dev].out_win[cur_chn].height = out_height;
int dev_num = 0;
dev_attr.input_type = VICAP_INPUT_TYPE_SENSOR;//输入类型为sensor数据
3.4 根据sensor配置获取sensor信息
ret = kd_mpi_vicap_get_sensor_info(device_obj[dev_num].sensor_type, &device_obj[dev_num].sensor_info);
if (ret) {
printf("sample_vicap, the sensor type not supported!\n");
return ret;
}
memcpy(&dev_attr.sensor_info, &device_obj[dev_num].sensor_info, sizeof(k_vicap_sensor_info));
3.5 获取摄像头输入宽高
device_obj[dev_num].in_width = device_obj[dev_num].sensor_info.width; //1920
device_obj[dev_num].in_height = device_obj[dev_num].sensor_info.height; //1080
printf("sample_vicap, dev[%d] in size[%dx%d]\n", \
dev_num, device_obj[dev_num].in_width, device_obj[dev_num].in_height);
3.6 设置VI设备属性
//vicap device attr set 设置VI设备属性
dev_attr.acq_win.h_start = 0; //采集窗口的水平起始位置
dev_attr.acq_win.v_start = 0; //采集窗口的垂直起始位置
dev_attr.acq_win.width = device_obj[dev_num].in_width; //采集窗口的宽度
dev_attr.acq_win.height = device_obj[dev_num].in_height; //采集窗口的高度
dev_attr.mode = VICAP_WORK_ONLINE_MODE; //设备工作模式为在线模式
dev_attr.pipe_ctrl.data = pipe_ctrl; //设置ISP相关控制数据
dev_attr.pipe_ctrl.bits.af_enable = 0; //禁用自动对焦
dev_attr.pipe_ctrl.bits.ae_enable = device_obj[dev_num].ae_enable;
dev_attr.pipe_ctrl.bits.awb_enable = device_obj[dev_num].awb_enable;
dev_attr.pipe_ctrl.bits.dnr3_enable = device_obj[dev_num].dnr3_enable;
dev_attr.pipe_ctrl.bits.ahdr_enable = device_obj[dev_num].hdr_enable;
dev_attr.cpature_frame = 0; //捕获帧数
dev_attr.dw_enable = device_obj[dev_num].dw_enable;
dev_attr.mirror = device_obj[cur_dev].sensor_mirror; //设置镜像模式为设备对象中的传感器镜像模式
ret = kd_mpi_vicap_set_dev_attr(dev_num, dev_attr);//设置VICAP设备属性
if (ret) {
printf("sample_vicap, kd_mpi_vicap_set_dev_attr failed.\n");
return ret;
}
3.7设置VI默认输出属性
int chn_num = 0;
//set default value 设置默认值
if (!device_obj[dev_num].out_format[chn_num]) {
device_obj[dev_num].out_format[chn_num] = PIXEL_FORMAT_YUV_SEMIPLANAR_420; //捕获图像格式为YUV420
}
if (!device_obj[dev_num].out_win[chn_num].width) {
device_obj[dev_num].out_win[chn_num].width = device_obj[dev_num].in_width; //捕获图像宽度
}
if (!device_obj[dev_num].out_win[chn_num].height) {
device_obj[dev_num].out_win[chn_num].height = device_obj[dev_num].in_height; //捕获图像高度
}
if ( device_obj[dev_num].out_win[chn_num].h_start || device_obj[dev_num].out_win[chn_num].v_start) {
device_obj[dev_num].crop_enable[chn_num] = K_TRUE;
}
printf("sample_vicap, dev_num(%d), chn_num(%d), in_size[%dx%d], out_offset[%d:%d], out_size[%dx%d]\n", \
dev_num, chn_num, device_obj[dev_num].in_width, device_obj[dev_num].in_height, \
device_obj[dev_num].out_win[chn_num].h_start, device_obj[dev_num].out_win[chn_num].v_start, \
device_obj[dev_num].out_win[chn_num].width, device_obj[dev_num].out_win[chn_num].height);
3.8 VO设备初始化
ret = sample_vicap_vo_init(connector_type);
if (ret) {
printf("sample_vicap_vo_init failed\n");
return -1;
}
3.8.1 获取连接器属性
ret = kd_mpi_get_connector_info(connector_type, &connector_info);
if (ret) {
printf("sample_vicap, the sensor type not supported!\n");
return ret;
}
3.8.2 获取连接器设备节点
connector_fd = kd_mpi_connector_open(connector_info.connector_name);
if (connector_fd < 0) {
printf("%s, connector open failed.\n", __func__);
return K_ERR_VO_NOTREADY;
}
3.8.3 打开connector 的电源
kd_mpi_connector_power_set(connector_fd, 1);
3.8.4 初始化连接器设备
kd_mpi_connector_init(connector_fd, connector_info);
3.9 配置缓冲区
ret = sample_vicap_vb_init(device_obj);//配置缓冲区
if (ret) {
printf("sample_vicap_vb_init failed\n");
return -1;
}
3.9.1 设置视频缓冲池属性
config.comm_pool[k].blk_cnt = VICAP_OUTPUT_BUF_NUM; //缓冲区数量
config.comm_pool[k].mode = VB_REMAP_MODE_NOCACHE; //无缓存重映射
k_u16 out_width = dev_obj[i].out_win[j].width; //输出宽度
k_u16 out_height = dev_obj[i].out_win[j].height; //输出高度
config.comm_pool[k].blk_size = VICAP_ALIGN_UP((out_width * out_height * 3 / 2) + 4096, VICAP_ALIGN_1K);//计算每个缓冲区的大小,并且进行对齐
dev_obj[i].buf_size[j] = config.comm_pool[k].blk_size;//每个缓冲区大小
printf("%s, dev(%d) chn(%d) pool(%d) buf_size(%d) blk_cnt(%d)\n", __func__, i, j, k ,dev_obj[i].buf_size[j], config.comm_pool[k].blk_cnt);
ret = kd_mpi_vb_set_config(&config); //设置MPP视频缓存池属性
3.9.2 设置缓冲池附加信息
memset(&supplement_config, 0, sizeof(supplement_config));
supplement_config.supplement_config |= VB_SUPPLEMENT_JPEG_MASK;
ret = kd_mpi_vb_set_supplement_config(&supplement_config);
if (ret) {
printf("vb_set_supplement_config failed ret:%d\n", ret);
return ret;
}
3.9.3 初始化视频缓冲池
ret = kd_mpi_vb_init();//初始化 MPP 视频缓存池
if (ret) {
printf("vb_init failed ret:%d\n", ret);
return ret;
}
3.10 允许VI通道dump
kd_mpi_vicap_set_dump_reserved(dev_num, chn_num, K_TRUE);//设置是否开启快速dump 模式
3.11 设置VI通道属性
memset(&chn_attr, 0, sizeof(k_vicap_chn_attr));
chn_attr.out_win.width = device_obj[dev_num].out_win[chn_num].width; //设置输出图像宽度
chn_attr.out_win.height = device_obj[dev_num].out_win[chn_num].height; //设置输出图像高度
if (device_obj[dev_num].crop_enable[chn_num]) { //如果设置裁剪参数
chn_attr.crop_win.width = device_obj[dev_num].crop_win[chn_num].width; //chn_attr.out_win;1166;//
chn_attr.crop_win.height = device_obj[dev_num].crop_win[chn_num].height; //1944;//
chn_attr.crop_win.h_start =device_obj[dev_num].out_win[chn_num].h_start; //713;
chn_attr.crop_win.v_start =device_obj[dev_num].out_win[chn_num].v_start; //0;//
} else {
chn_attr.crop_win.width = device_obj[dev_num].in_width;
chn_attr.crop_win.height = device_obj[dev_num].in_height;
}
chn_attr.scale_win = chn_attr.out_win;
chn_attr.crop_enable = device_obj[dev_num].crop_enable[chn_num];
chn_attr.scale_enable = K_FALSE; //缩放关闭
chn_attr.chn_enable = K_TRUE; //通道使能
chn_attr.pix_format = device_obj[dev_num].out_format[chn_num];//图像格式
chn_attr.buffer_num = VICAP_OUTPUT_BUF_NUM;//缓冲区数量
chn_attr.buffer_size = device_obj[dev_num].buf_size[chn_num];//缓冲区大小
printf("sample_vicap, set dev(%d) chn(%d) attr, buffer_size(%d), out size[%dx%d]\n", \
dev_num, chn_num, chn_attr.buffer_size, chn_attr.out_win.width, chn_attr.out_win.height);
printf("sample_vicap out_win h_start is %d ,v_start is %d \n", chn_attr.out_win.h_start, chn_attr.out_win.v_start);
ret = kd_mpi_vicap_set_chn_attr(dev_num, chn_num, chn_attr);//设置VICAP设备通道属性
3.12 获取连接器信息
ret = kd_mpi_get_connector_info(connector_type, &connector_info);
if (ret) {
printf("sample_vicap, the sensor type not supported!\n");
return ret;
}
display_width = connector_info.resolution.hdisplay;
display_height = connector_info.resolution.vdisplay;
display_width = VICAP_ALIGN_UP(display_width, 16);
3.13 绑定VI和VO通道
k_s32 vo_chn = K_VO_DISPLAY_CHN_ID1;
k_vo_layer layer = K_VO_LAYER1;
k_u16 rotation = device_obj[dev_num].rotation[chn_num];
printf("sample_vicap, vo_count(%d), dev(%d) chn(%d) bind vo chn(%d) layer(%d) rotation(%d)\n", vo_count, dev_num, chn_num, vo_chn, layer, rotation);
sample_vicap_bind_vo(dev_num, chn_num, vo_chn);
3.14 设置图层信息
layer_conf.enable[vo_count] = K_TRUE;
layer_conf.width[vo_count] = chn_attr.out_win.width;
layer_conf.height[vo_count] = chn_attr.out_win.height;
layer_conf.rotation[vo_count] = 1;
layer_conf.layer[vo_count] = layer;
3.15 图层初始化
ret = sample_vicap_vo_layer_init(&layer_conf, display_width, display_height);
if (ret) {
printf("sample_vicap, vo layer init failed.\n");
goto app_exit;
}
3.15.1 设置旋转属性
for (int i = 0; i < MAX_VO_LAYER_NUM; i++) {
if (layer_conf->enable[i]) {
rotation = layer_conf->rotation[i];
switch (rotation) {
case 0:
info[i].act_size.width = layer_conf->width[i];
info[i].act_size.height = layer_conf->height[i];
info[i].func = K_ROTATION_0;
break;
case 1:
info[i].act_size.width = layer_conf->height[i];
info[i].act_size.height = layer_conf->width[i];
info[i].func = K_ROTATION_90;
break;
case 2:
info[i].act_size.width = layer_conf->width[i];
info[i].act_size.height = layer_conf->height[i];
info[i].func = K_ROTATION_180;
break;
case 3:
info[i].act_size.width = layer_conf->height[i];
info[i].act_size.height = layer_conf->width[i];
info[i].func = K_ROTATION_270;
break;
case 4:
info[i].act_size.width = layer_conf->width[i];
info[i].act_size.height = layer_conf->height[i];
info[i].func = 0;
break;
default:
printf("invalid roation paramters.\n");
return -1;
}
total_height += info[i].act_size.height;
margin = ((display_height - total_height) / (i+2));
if ((total_height > display_height) || (info[i].act_size.width > display_width)) {
printf("%s, the preview window size[%dx%d] exceeds the display window size[%dx%d].\n", \
__func__, info[i].act_size.width, total_height, display_width, display_height);
return -1;
}
printf("%s, width(%d), height(%d), margin(%d), total_height(%d)\n", \
__func__, info[i].act_size.width, info[i].act_size.height, margin, total_height);
}
}
3.15.2 设置图层属性
for (int i = 0; i < MAX_VO_LAYER_NUM - 1; i++) {
if (layer_conf->enable[i]) {
info[i].offset.x = (display_width - info[i].act_size.width)/2;
info[i].offset.y = margin + relative_height;
printf("%s, layer(%d), offset.x(%d), offset.y(%d), relative_height(%d)\n", __func__, layer_conf->layer[i], info[i].offset.x, info[i].offset.y, relative_height);
relative_height += info[i].act_size.height + margin;
info[i].format = PIXEL_FORMAT_YVU_PLANAR_420;
info[i].global_alptha = 0xff;
vo_creat_layer_test(layer_conf->layer[i], &info[i]);
}
}
1.检查图层序号
if ((chn_id >= K_MAX_VO_LAYER_NUM) || ((info->func & K_VO_SCALER_ENABLE) && (chn_id != K_VO_LAYER0))
|| ((info->func != 0) && (chn_id == K_VO_LAYER2)))
{
printf("input layer num failed \n");
return -1 ;
}
2.设置属性信息
// set offset
attr.display_rect = info->offset;
// set act
attr.img_size = info->act_size;
// sget size
info->size = info->act_size.height * info->act_size.width * 3 / 2;
//set pixel format
attr.pixel_format = info->format;
if (info->format != PIXEL_FORMAT_YVU_PLANAR_420)
{
printf("input pix format failed \n");
return -1;
}
// set stride
attr.stride = (info->act_size.width / 8 - 1) + ((info->act_size.height - 1) << 16);
// set function
attr.func = info->func;
// set scaler attr
attr.scaler_attr = info->attr;
3.设置属性至对应图层
kd_mpi_vo_set_video_layer_attr(chn_id, &attr);
4.使能对应图层
kd_mpi_vo_enable_video_layer(chn_id);
3.16 VI设备初始化
ret = kd_mpi_vicap_init(dev_num);//VICAP设备初始化
if (ret) {
printf("sample_vicap, vicap dev(%d) init failed.\n", dev_num);
goto app_exit;
}
3.17 启动VI设备
ret = kd_mpi_vicap_start_stream(dev_num);//启动VICAP设备输出数据流
if (ret) {
printf("sample_vicap, vicap dev(%d) start stream failed.\n", dev_num);
goto app_exit;
}