首先vi部分应该是没问题,利用dump保存3个摄像头的帧文件在电脑本地查看正常。
需求合成这三个流在同一个画面 最后是想编码264 给小核心uvc
main代码和卡死log在评论区
vi代码如下
#include "pengzhihao_vi.h"
#include "pengzhihao_log.h"
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
// 输出图像尺寸
#define OUT_WIDTH 960
#define OUT_HEIGHT 540
// 裁剪参数
#define CROP_X 0 // 裁剪窗口宽度(0表示不裁剪)
#define CROP_Y 0 // 裁剪窗口高度(0表示不裁剪)
#define X_START 0 // 裁剪窗口水平起始坐标
#define Y_START 0 // 裁剪窗口垂直起始坐标
// 图像变换参数
#define ROTATION 0 // 旋转角度(0表示不旋转,硬件支持0/90/180/270度)
#define MIRROR 0 // 镜像模式(0表示不镜像,1表示水平/垂直镜像)
#define FPS 30 // 帧率设置(30FPS)
// 数据对齐模式
#define DALIGN 0
//
#define WORK_MODE VICAP_WORK_OFFLINE_MODE
// VICAP 输入缓冲块数量
#define VICAP_INPUT_BUF_NUM 4 // 默认 3 块
// VICAP 输出缓冲块数量
#define VICAP_OUTPUT_BUF_NUM 6 // 默认 5 块,可调整
// GDMA 缓冲块数量,用于旋转等操作
#define GDMA_BUF_NUM 3 // 旋转时额外使用
#define WBC_WIDTH 1920
#define WBC_HEIGHT 1080
#define WBC_BLK_CNT 4
// 初始化 VB(Video Buffer)池,根据设备对象配置各缓冲区
k_s32 sample_vicap_vb_init(vicap_device_obj *dev_obj) {
k_s32 ret = 0; // 返回值
k_vb_config config; // VB 配置结构体
k_vb_supplement_config supplement_config; // VB 补充配置
memset(&config, 0, sizeof(config)); // 清零 config
config.max_pool_cnt = 64; // 最大池数量
int k = 0; // 当前 comm_pool 索引
for (int i = 0; i < VICAP_DEV_ID_MAX; i++) {
if (!dev_obj[i].dev_enable) // 若设备未使能则跳过
continue;
printf("%s, enable dev(%d)\n", __func__, i);
// 离线或瓦片模式需要输入缓冲
if ((dev_obj[i].mode == VICAP_WORK_OFFLINE_MODE) ||
(dev_obj[i].mode == VICAP_WORK_SW_TILE_MODE)) {
config.comm_pool[k].blk_cnt = VICAP_INPUT_BUF_NUM; // 块数
config.comm_pool[k].mode = VB_REMAP_MODE_NOCACHE; // 无缓存模式
config.comm_pool[k].blk_size = dev_obj[i].in_size; // 块大小
printf("%s, dev(%d) pool(%d) in_size(%d) blk_cnt(%d)\n", __func__, i, k,
dev_obj[i].in_size, config.comm_pool[k].blk_cnt);
k++;
}
// 为每个使能的通道配置输出缓冲
for (int j = 0; j < VICAP_CHN_ID_MAX; j++) {
if (!dev_obj[i].chn_enable[j]) // 若通道未使能则跳过
continue;
printf("%s, enable chn(%d), k(%d)\n", __func__, j, k);
config.comm_pool[k].blk_cnt = VICAP_OUTPUT_BUF_NUM; // 输出缓冲块数
config.comm_pool[k].mode = VB_REMAP_MODE_NOCACHE;
// 若使用 GDMA 旋转,额外分配缓冲
if (dev_obj[i].preview[j] && dev_obj[i].rotation[j] > 16)
config.comm_pool[k].blk_cnt += GDMA_BUF_NUM;
// 根据输出格式和尺寸计算 blk_size
k_pixel_format pix_format = dev_obj[i].out_format[j];
k_u16 out_width = dev_obj[i].out_win[j].width;
k_u16 out_height = dev_obj[i].out_win[j].height;
k_u16 in_width = dev_obj[i].in_width;
k_u16 in_height = dev_obj[i].in_height;
switch (pix_format) {
case PIXEL_FORMAT_YUV_SEMIPLANAR_420:
// YUV420SP 每像素 1.5 字节,上对齐 1K
config.comm_pool[k].blk_size =
VICAP_ALIGN_UP((out_width * out_height * 3 / 2), VICAP_ALIGN_1K);
break;
case PIXEL_FORMAT_RGB_888:
case PIXEL_FORMAT_RGB_888_PLANAR:
// RGB888 每像素 3 字节
config.comm_pool[k].blk_size =
VICAP_ALIGN_UP((out_width * out_height * 3), VICAP_ALIGN_1K);
break;
case PIXEL_FORMAT_RGB_BAYER_10BPP:
// RAW10 每像素 2 字节
config.comm_pool[k].blk_size =
VICAP_ALIGN_UP((in_width * in_height * 2), VICAP_ALIGN_1K);
break;
default:
// 默认回退到 YUV420SP
dev_obj[i].out_format[j] = PIXEL_FORMAT_YUV_SEMIPLANAR_420;
config.comm_pool[k].blk_size =
VICAP_ALIGN_UP((out_width * out_height * 3 / 2), VICAP_ALIGN_1K);
break;
}
// 保存 buf_size
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);
k++;
}
// 去畸变功能需额外缓冲
if (dev_obj[i].dw_enable) {
// another buffer for isp->dw
config.comm_pool[k].blk_size = VICAP_ALIGN_UP(
(dev_obj[i].in_width * dev_obj[i].in_height * 3 / 2), VICAP_ALIGN_1K);
config.comm_pool[k].blk_cnt = VICAP_MIN_FRAME_COUNT * 2;
config.comm_pool[k].mode = VB_REMAP_MODE_NOCACHE;
dev_obj[i].buf_size[0] = config.comm_pool[k].blk_size;
printf("%s, dev(%d) pool(%d) buf_size(%d)\n", __func__, i, k,
dev_obj[i].buf_size[0]);
k++;
}
}
config.comm_pool[k].blk_cnt = WBC_BLK_CNT;
config.comm_pool[k].blk_size =
VICAP_ALIGN_UP(WBC_WIDTH * WBC_HEIGHT * 3 / 2, VICAP_ALIGN_1K);
config.comm_pool[k].mode = VB_REMAP_MODE_NOCACHE;
k++;
// 应用 VB 配置
ret = kd_mpi_vb_set_config(&config);
if (ret) {
printf("vb_set_config failed ret:%d\n", ret);
return ret;
}
memset(&supplement_config, 0, sizeof(supplement_config));
supplement_config.supplement_config |= VB_SUPPLEMENT_JPEG_MASK;
// 设置附加 JPEG 支持
ret = kd_mpi_vb_set_supplement_config(&supplement_config);
if (ret) {
printf("vb_set_supplement_config failed ret:%d\n", ret);
return ret;
}
// 初始化 VB
ret = kd_mpi_vb_init();
if (ret) {
printf("vb_init failed ret:%d\n", ret);
return ret;
}
return 0;
}
// 退出 VB
void vb_exit() { kd_mpi_vb_exit(); }
// 读取 RISC-V 时间戳,用于计算耗时
static uint64_t get_ticks() {
static volatile uint64_t time_elapsed = 0;
__asm__ __volatile__("rdtime %0" : "=r"(time_elapsed));
return time_elapsed;
}
static void set_default_device_config(vicap_device_obj *obj, k_u8 dev_id) {
memset(obj, 0, sizeof(vicap_device_obj));
k_u8 cur_chn = 0;
// 默认 AE/AWB 开启,3DNR/HDR 关闭
obj->dev_num = dev_id;
obj->dev_enable = K_TRUE;
obj->ae_enable = K_TRUE; // default enable ae
obj->awb_enable = K_TRUE; // default enable awb
obj->dnr3_enable = K_FALSE; // default disable 3ndr
obj->hdr_enable = K_FALSE; // default disable hdr
obj->chn_num[cur_chn] = cur_chn;
obj->chn_enable[cur_chn] = K_TRUE;
obj->preview[cur_chn] = K_FALSE;
obj->out_win[cur_chn].h_start = X_START;
obj->out_win[cur_chn].v_start = Y_START;
obj->crop_win[cur_chn].width = CROP_X;
obj->crop_win[cur_chn].height = CROP_Y;
obj->out_win[cur_chn].width = VICAP_ALIGN_UP(OUT_WIDTH, 16);
obj->out_win[cur_chn].height = OUT_HEIGHT;
obj->rotation[cur_chn] = ROTATION;
obj->sensor_mirror = MIRROR;
obj->fps[cur_chn] = FPS;
obj->crop_enable[cur_chn] = K_FALSE;
obj->out_format[cur_chn] = PIXEL_FORMAT_YUV_SEMIPLANAR_420;
obj->dalign = DALIGN;
obj->input_type = VICAP_INPUT_TYPE_SENSOR;
obj->sensor_type = OV_OV5647_MIPI_CSI1_1920X1080_30FPS_10BIT_LINEAR_V2;
}
void vicap_dev_conf(vicap_device_obj *dev_obj, k_u8 dev_count) {
if (dev_count > VICAP_DEV_ID_MAX)
return;
set_default_device_config(&dev_obj[0], 0);
if (1 < dev_count) {
dev_obj[1] = dev_obj[0];
dev_obj[1].dev_num = 1;
dev_obj[1].sensor_type =
OV_OV5647_MIPI_CSI0_1920X1080_30FPS_10BIT_LINEAR_V2;
}
if (2 < dev_count) {
dev_obj[2] = dev_obj[0];
dev_obj[2].dev_num = 2;
dev_obj[2].sensor_type = GC2093_MIPI_CSI2_1920X1080_30FPS_10BIT_LINEAR;
// dev_obj[2].sensor_mirror = VICAP_MIRROR_VER;
// dev_obj[2].rotation[cur_chn] = ROTATION;
}
PZH_I("------------- vicap_dev_conf-------------------\r\n");
}
k_s32 vicap_devices_init(vicap_device_obj *device_obj,
k_vicap_dev_attr *dev_attr) {
k_s32 ret = 0;
k_u32 work_mode = WORK_MODE;
k_u32 pipe_ctrl = 0xFFFFFFFF;
// 屏幕宽高,显示相关尺寸(用于缓冲 VB 对齐和后续可能的拼接)
k_u32 display_width = 1920;
k_u32 display_height = 1080;
// 宽度向上对齐到 16 的整数倍(硬件要求)
display_width = VICAP_ALIGN_UP(display_width, 16);
// 外层循环:遍历所有可能的设备编号(dev_num 从 0 到 VICAP_DEV_ID_MAX-1)
for (int dev_num = 0; dev_num < VICAP_DEV_ID_MAX; dev_num++) {
// 如果当前设备未启用,则跳过该设备
if (!device_obj[dev_num].dev_enable)
continue;
// 传感器输入
if (device_obj[dev_num].input_type == VICAP_INPUT_TYPE_SENSOR) {
// 设置设备属性中的输入类型为传感器
dev_attr->input_type = VICAP_INPUT_TYPE_SENSOR;
// vicap get sensor info
// 调用 MPI 接口获取指定传感器类型的信息
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;
}
// 将获取到的传感器信息复制到 dev_attr 结构体中,供后续设置使用
memcpy(&dev_attr->sensor_info, &device_obj[dev_num].sensor_info,
sizeof(k_vicap_sensor_info));
// 更新设备对象的输入宽高为传感器的实际分辨率
device_obj[dev_num].in_width = device_obj[dev_num].sensor_info.width;
device_obj[dev_num].in_height = device_obj[dev_num].sensor_info.height;
}
printf("sample_vicap, dev[%d] in size[%dx%d]\n", dev_num,
device_obj[dev_num].in_width, device_obj[dev_num].in_height);
// 设置采集窗口(acquisition window)起始位置和大小
dev_attr->acq_win.h_start = 0; // 水平起始偏移为0
dev_attr->acq_win.v_start = 0; // 垂直起始偏移为0
dev_attr->acq_win.width = device_obj[dev_num].in_width; // 宽度等于输入宽度
dev_attr->acq_win.height =
device_obj[dev_num].in_height; // 高度等于输入高度
// 判断是否为离线模式
if ((work_mode == VICAP_WORK_OFFLINE_MODE) ||
(work_mode == VICAP_WORK_LOAD_IMAGE_MODE) ||
(work_mode == VICAP_WORK_SW_TILE_MODE)) {
// 设置设备工作模式
dev_attr->mode = work_mode;
// 设置输入缓冲区数量
dev_attr->buffer_num = VICAP_INPUT_BUF_NUM;
// 根据不同模式计算 buffer_size(缓冲区大小)
// SW Tile 模式
if (work_mode == VICAP_WORK_SW_TILE_MODE)
dev_attr->buffer_size =
VICAP_ALIGN_UP((device_obj[dev_num].in_width *
device_obj[dev_num].in_height * 12 / 8),
VICAP_ALIGN_1K);
else
// 其他离线模式
dev_attr->buffer_size = VICAP_ALIGN_UP(
(device_obj[dev_num].in_width * device_obj[dev_num].in_height * 2),
VICAP_ALIGN_1K);
// 保存计算出的输入缓冲区大小到设备对象
device_obj[dev_num].in_size = dev_attr->buffer_size;
// 记录当前设备的工作模式
if (work_mode == VICAP_WORK_SW_TILE_MODE)
device_obj[dev_num].mode = VICAP_WORK_SW_TILE_MODE;
else
device_obj[dev_num].mode = VICAP_WORK_OFFLINE_MODE;
// 如果是加载图像模式(Load Image),设置图像模式相关参数
if (work_mode == VICAP_WORK_LOAD_IMAGE_MODE) {
dev_attr->image_pat = device_obj[dev_num].pattern;
dev_attr->sensor_info.sensor_name = device_obj[dev_num].calib_file;
device_obj[dev_num].image_data = NULL;
}
} else {
// 默认为在线模式(实时从摄像头采集)
dev_attr->mode = VICAP_WORK_ONLINE_MODE;
}
// Pipe 控制参数
dev_attr->pipe_ctrl.data = pipe_ctrl; // 默认值
// 关闭自动对焦(AF)
dev_attr->pipe_ctrl.bits.af_enable = 0;
// 根据配置决定是否启用自动曝光(AE)
dev_attr->pipe_ctrl.bits.ae_enable = device_obj[dev_num].ae_enable;
// 根据配置决定是否启用自动白平衡(AWB)
dev_attr->pipe_ctrl.bits.awb_enable = device_obj[dev_num].awb_enable;
// 在 SW_TILE 模式下强制开启 3D 降噪(DNR3)
if (work_mode == VICAP_WORK_SW_TILE_MODE)
dev_attr->pipe_ctrl.bits.dnr3_enable = 1;
else
// 否则根据配置决定是否开启 DNR3
dev_attr->pipe_ctrl.bits.dnr3_enable = device_obj[dev_num].dnr3_enable;
// 根据配置决定是否启用 HDR(多帧合成高动态范围)
dev_attr->pipe_ctrl.bits.ahdr_enable = device_obj[dev_num].hdr_enable;
// 设置捕获帧数(0 表示连续捕获)
dev_attr->cpature_frame = 0;
// 根据配置决定是否启用
dev_attr->dw_enable = device_obj[dev_num].dw_enable;
// 设置镜像(mirror)属性
dev_attr->mirror = device_obj[dev_num].sensor_mirror;
// 设置采集模式(在线/离线)、自动曝光、木马白平衡等参数, 应用设备属性
// 调用 MPI 接口设置设备属性
ret = kd_mpi_vicap_set_dev_attr(dev_num, *dev_attr);
if (ret) {
printf("sample_vicap, kd_mpi_vicap_set_dev_attr failed.\n");
return ret;
}
if (work_mode == VICAP_WORK_LOAD_IMAGE_MODE) {
printf("work_mode error\r\n");
}
// 内层循环:遍历该设备的所有通道(Channel,通常一个设备可支持多个输出流)
for (int chn_num = 0; chn_num < VICAP_CHN_ID_MAX; chn_num++) {
if (!device_obj[dev_num].chn_enable[chn_num])
continue; // 如果当前通道未启用,则跳过
// set default value
// 如果未设置输出像素格式,则使用默认格式:YUV 半平面 420(I420/NV12)
if (!device_obj[dev_num].out_format[chn_num]) {
device_obj[dev_num].out_format[chn_num] =
PIXEL_FORMAT_YUV_SEMIPLANAR_420;
}
// 如果未设置输出宽度,则默认与输入宽度一致
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;
}
// 如果输出窗口有水平或垂直偏移,则认为需要裁剪(Crop)
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;
}
// 如果输出分辨率大于显示区域,则不用于预览(preview)
if ((device_obj[dev_num].out_win[chn_num].width > display_width) &&
(device_obj[dev_num].out_win[chn_num].height > display_height)) {
device_obj[dev_num].preview[chn_num] = K_FALSE;
}
// 如果未设置旋转,且输出宽度 > 显示宽 但 < 显示高,自动设置旋转
if (!device_obj[dev_num].rotation[chn_num] &&
((device_obj[dev_num].out_win[chn_num].width > display_width) &&
(device_obj[dev_num].out_win[chn_num].width < display_height))) {
device_obj[dev_num].rotation[chn_num] = 1;
}
// 打印每个通道的详细配置信息(调试用)
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);
}
}
PZH_I("------------- vicap_devices_init-----------------\r\n");
return ret;
}
k_s32 vicap_channels_init(vicap_device_obj *device_obj) {
k_s32 ret = 0;
k_vicap_chn_attr chn_attr;
// 外层循环:遍历所有可能的VICAP设备(从0到VICAP_DEV_ID_MAX-1)
for (int dev_num = 0; dev_num < VICAP_DEV_ID_MAX; dev_num++) {
// 如果当前设备未启用,则跳过该设备
if (!device_obj[dev_num].dev_enable)
continue; // 跳过未启用的设备
// 内层循环:遍历当前设备(dev_num)的所有通道(从0到VICAP_CHN_ID_MAX-1)
for (int chn_num = 0; chn_num < VICAP_CHN_ID_MAX; chn_num++) {
if (!device_obj[dev_num].chn_enable[chn_num])
continue; // 跳过未启用的通道
kd_mpi_vicap_set_dump_reserved(dev_num, chn_num, K_TRUE);
// 通道属性初始化
memset(&chn_attr, 0, sizeof(k_vicap_chn_attr));
if (device_obj[dev_num].out_format[chn_num] ==
PIXEL_FORMAT_RGB_BAYER_10BPP) {
chn_attr.out_win.width = device_obj[dev_num].in_width;
chn_attr.out_win.height = device_obj[dev_num].in_height;
} else {
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];
chn_attr.fps = device_obj[dev_num].fps[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);
if (ret) {
printf("sample_vicap, kd_mpi_vicap_set_chn_attr failed.\n");
return ret;
}
}
}
PZH_I("------------ vicap_channels_init-----------\r\n");
return ret;
}
k_s32 vicap_mpi_init(vicap_device_obj *device_obj) {
k_s32 ret = 0;
for (int dev_num = 0; dev_num < VICAP_DEV_ID_MAX; dev_num++) {
if (!device_obj[dev_num].dev_enable)
continue;
printf("sample_vicap, vicap dev(%d) init\n", dev_num);
ret = kd_mpi_vicap_init(dev_num);
if (ret) {
printf("sample_vicap, vicap dev(%d) init failed.\n", dev_num);
return ret;
}
}
PZH_I("------------- vicap_mpi_init-----------------\r\n");
return ret;
}
k_s32 vicap_start_stream(vicap_device_obj *device_obj) {
k_s32 ret = 0;
for (int dev_num = 0; dev_num < VICAP_DEV_ID_MAX; dev_num++) {
if (!device_obj[dev_num].dev_enable)
continue;
printf("sample_vicap, vicap dev(%d) start stream\n", dev_num);
ret = kd_mpi_vicap_start_stream(dev_num);
if (ret) {
printf("sample_vicap, vicap dev(%d) start stream failed.\n", dev_num);
return ret;
}
}
PZH_I("------------- vicap_start_stream-----------------\r\n");
return ret;
}
void vicap_dump_frame(vicap_device_obj *device_obj) {
// 已配置设备计数、当前设备
k_video_frame_info dump_info;
k_s32 ret = 0;
static k_u32 dump_count = 0;
for (int dev_num = 0; dev_num < VICAP_DEV_ID_MAX; dev_num++) {
if (!device_obj[dev_num].dev_enable)
continue;
for (int chn_num = 0; chn_num < VICAP_CHN_ID_MAX; chn_num++) {
if (!device_obj[dev_num].chn_enable[chn_num])
continue;
printf("sample_vicap, dev(%d) chn(%d) dump frame.\n", dev_num, chn_num);
memset(&dump_info, 0, sizeof(k_video_frame_info));
uint64_t start = get_ticks();
ret = kd_mpi_vicap_dump_frame(dev_num, chn_num, VICAP_DUMP_YUV,
&dump_info, 1000);
if (ret) {
printf("sample_vicap, dev(%d) chn(%d) dump frame failed.\n", dev_num,
chn_num);
continue;
}
uint64_t end = get_ticks();
printf("dump cost %lu us\n", (end - start) / 27);
k_char *suffix;
k_u32 data_size = 0;
k_u8 lbit = 0;
k_u8 *virt_addr = NULL;
k_char filename[256];
if (dump_info.v_frame.pixel_format == PIXEL_FORMAT_YUV_SEMIPLANAR_420) {
suffix = "yuv420sp";
data_size = dump_info.v_frame.width * dump_info.v_frame.height * 3 / 2;
} else if (dump_info.v_frame.pixel_format ==
PIXEL_FORMAT_YUV_SEMIPLANAR_422) {
suffix = "yuv422sp";
data_size = dump_info.v_frame.width * dump_info.v_frame.height * 3 / 2;
} else if (dump_info.v_frame.pixel_format == PIXEL_FORMAT_RGB_888) {
suffix = "rgb888";
data_size = dump_info.v_frame.width * dump_info.v_frame.height * 3;
} else if (dump_info.v_frame.pixel_format ==
PIXEL_FORMAT_RGB_888_PLANAR) {
suffix = "rgb888p";
data_size = dump_info.v_frame.width * dump_info.v_frame.height * 3;
} else if (dump_info.v_frame.pixel_format ==
PIXEL_FORMAT_RGB_BAYER_10BPP) {
suffix = "raw10";
lbit = 6;
data_size = dump_info.v_frame.width * dump_info.v_frame.height * 2;
} else if (dump_info.v_frame.pixel_format ==
PIXEL_FORMAT_RGB_BAYER_12BPP) {
suffix = "raw12";
lbit = 4;
data_size = dump_info.v_frame.width * dump_info.v_frame.height * 2;
} else if (dump_info.v_frame.pixel_format ==
PIXEL_FORMAT_RGB_BAYER_14BPP) {
suffix = "raw14";
lbit = 2;
data_size = dump_info.v_frame.width * dump_info.v_frame.height * 2;
} else if (dump_info.v_frame.pixel_format ==
PIXEL_FORMAT_RGB_BAYER_16BPP) {
suffix = "raw16";
data_size = dump_info.v_frame.width * dump_info.v_frame.height * 2;
} else {
suffix = "unkown";
}
virt_addr = kd_mpi_sys_mmap(dump_info.v_frame.phys_addr[0], data_size);
if (virt_addr) {
memset(filename, 0, sizeof(filename));
snprintf(filename, sizeof(filename), "dev_%02d_chn_%02d_%dx%d_%04d.%s",
dev_num, chn_num, dump_info.v_frame.width,
dump_info.v_frame.height, dump_count, suffix);
printf("save dump data to file(%s)\n", filename);
FILE *file = fopen(filename, "wb+");
if (file) {
if (device_obj[dev_num].dalign && lbit) {
for (k_u32 index = 0; index < data_size; index += 2) {
k_u16 raw_data = (virt_addr[index + 1] << 8) | virt_addr[index];
raw_data = raw_data << lbit;
fwrite(&raw_data, sizeof(raw_data), 1, file);
}
} else {
fwrite(virt_addr, 1, data_size, file);
}
fclose(file);
} else {
printf("sample_vicap, open dump file failed(%s)\n", strerror(errno));
}
kd_mpi_sys_munmap(virt_addr, data_size);
} else {
printf("sample_vicap, map dump addr failed.\n");
}
printf("sample_vicap, release dev(%d) chn(%d) dump frame.\n", dev_num,
chn_num);
ret = kd_mpi_vicap_dump_release(dev_num, chn_num, &dump_info);
if (ret) {
printf("sample_vicap, dev(%d) chn(%d) release dump frame failed.\n",
dev_num, chn_num);
}
dump_count++;
}
}
PZH_I("------------- vicap_dump_frame-----------------\r\n");
}
void slave_en(void) {
// 开启多摄像头同步模式,多摄同步标志(1表示启用多摄同步,0表示禁用)
k_u8 salve_en = SLAVE_EN;
if (salve_en == SLAVE_EN) {
k_vicap_slave_info slave_info;
memset(&slave_info, 0, sizeof(k_vicap_slave_info));
slave_info.vs_high =
10; // 1 / 27 = 37ns // VSYNC 高电平脉宽,单位 = 1/27 MHz ≈ 37 ns
slave_info.vs_cycle = 900902; // 33ms / 37ns = 900902 // 一个 VSYNC 周期,≈
// 900 902×37 ns ≈ 33 ms → 30 fps
kd_mpi_vicap_set_slave_attr(VICAP_SLAVE_ID1, &slave_info);
k_vicap_slave_enable slave_en;
memset(&slave_en, 0, sizeof(k_vicap_slave_enable));
slave_en.vs_enable = 1; // 只开 VSYNC,同步行 (hs) 关闭
slave_en.hs_enable = 0;
kd_mpi_vicap_set_slave_enable(VICAP_SLAVE_ID1, &slave_en);
}
}
void app_exit(vicap_device_obj *device_obj) {
k_s32 ret = 0;
k_u8 salve_en = SLAVE_EN;
// 记录是否已经启动过 GDMA(图像旋转等),GDMA
// 是否已启动(当前未启用旋转,故为0)
k_u8 gdma_enable = 0;
if (salve_en == SLAVE_EN) {
k_vicap_slave_enable slave_en;
memset(&slave_en, 0, sizeof(k_vicap_slave_enable));
slave_en.vs_enable = 0;
slave_en.hs_enable = 0;
kd_mpi_vicap_set_slave_enable(VICAP_SLAVE_ID1, &slave_en);
}
for (int dev_num = 0; dev_num < VICAP_DEV_ID_MAX; dev_num++) {
if (!device_obj[dev_num].dev_enable)
continue;
printf("sample_vicap, vicap dev(%d) stop stream\n", dev_num);
ret = kd_mpi_vicap_stop_stream(dev_num);
if (ret) {
printf("sample_vicap, vicap dev(%d) stop stream failed.\n", dev_num);
}
printf("sample_vicap, vicap dev(%d) deinit\n", dev_num);
ret = kd_mpi_vicap_deinit(dev_num);
if (ret) {
printf("sample_vicap, vicap dev(%d) deinit failed.\n", dev_num);
}
#if (WORK_MODE != VICAP_WORK_OFFLINE_MODE)
if (work_mode == VICAP_WORK_LOAD_IMAGE_MODE) {
if (device_obj[dev_num].image_data != NULL) {
free(device_obj[dev_num].image_data);
device_obj[dev_num].image_data = NULL;
}
}
#endif
}
if (gdma_enable) {
ret = kd_mpi_dma_stop_dev();
if (ret != K_SUCCESS) {
printf("stop dev error\r\n");
}
}
printf("Press Enter to exit!!!!\n");
getchar();
/*Allow one frame time for the VO to release the VB block*/
k_u32 display_ms = 1000 / 33;
usleep(1000 * display_ms);
}
注意客户不需要屏幕,所以不用屏幕相关功能
下面是vo部分,这里开始就有可能有问题,麻烦大佬看看
#include "pengzhihao_vo.h"
#include "k_sys_comm.h"
#include "k_vb_comm.h"
#include "k_video_comm.h"
#include "mpi_sys_api.h"
#include "mpi_vb_api.h"
#include "mpi_vicap_api.h"
#include "mpi_vo_api.h"
#include "mpi_vvi_api.h"
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
/* ---------- 常量 ---------- */
#define MOSAIC_W 1920 /* 最终合成帧大小 */
#define MOSAIC_H 1080
typedef struct {
k_u64 osd_phy_addr;
void *osd_virt_addr;
k_pixel_format format;
k_vo_point offset;
k_vo_size act_size;
k_u32 size;
k_u32 stride;
k_u8 global_alptha;
} osd_info;
typedef struct {
k_u64 layer_phy_addr;
k_pixel_format format;
k_vo_point offset;
k_vo_size act_size;
k_u32 size;
k_u32 stride;
k_u8 global_alptha;
// only layer0、layer1
k_u32 func;
// only layer0
k_vo_scaler_attr attr;
} layer_info;
// 绑定 VICAP 通道到 VO 通道
void sample_vicap_bind_vo(k_s32 vicap_dev, k_s32 vicap_chn, k_s32 vo_chn) {
k_s32 ret;
k_mpp_chn vicap_mpp_chn, vo_mpp_chn;
vicap_mpp_chn.mod_id = K_ID_VI; // VI 模块
vicap_mpp_chn.dev_id = vicap_dev; // 设备 ID
vicap_mpp_chn.chn_id = vicap_chn; // 通道 ID
vo_mpp_chn.mod_id = K_ID_VO; // VO 模块
vo_mpp_chn.dev_id = K_VO_DISPLAY_DEV_ID; // VO 设备 ID
vo_mpp_chn.chn_id = vo_chn; // VO 通道 ID
// 绑定
ret = kd_mpi_sys_bind(&vicap_mpp_chn, &vo_mpp_chn);
if (ret) {
printf("kd_mpi_sys_unbind failed:0x%x\n", ret);
}
return;
}
// 解除绑定
void sample_vicap_unbind_vo(k_s32 vicap_dev, k_s32 vicap_chn, k_s32 vo_chn) {
k_s32 ret;
k_mpp_chn vicap_mpp_chn, vo_mpp_chn;
vicap_mpp_chn.mod_id = K_ID_VI;
vicap_mpp_chn.dev_id = vicap_dev;
vicap_mpp_chn.chn_id = vicap_chn;
vo_mpp_chn.mod_id = K_ID_VO;
vo_mpp_chn.dev_id = K_VO_DISPLAY_DEV_ID;
vo_mpp_chn.chn_id = vo_chn;
// 解绑
ret = kd_mpi_sys_unbind(&vicap_mpp_chn, &vo_mpp_chn);
if (ret) {
printf("kd_mpi_sys_unbind failed:0x%x\n", ret);
}
return;
}
// 函数:创建视频图层测试并配置属性
int vo_creat_layer_test(k_vo_layer chn_id, layer_info *info) {
k_vo_video_layer_attr attr; // 视频图层属性结构体
// check layer
// 校验图层ID合法性
// 条件:图层ID超出最大数量,或(启用缩放且非layer0),或(layer2且有功能配置)
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;
}
/* 新写法:NV12 */
if (info->format != PIXEL_FORMAT_YUV_SEMIPLANAR_420) {
printf("unsupported pixel format (%d)\n", info->format);
return -1;
}
// 初始化图层属性结构体
memset(&attr, 0, sizeof(attr));
// set offset // 设置显示偏移(位置)
attr.display_rect = info->offset;
// set act // 设置图像大小(实际分辨率)
attr.img_size = info->act_size;
// sget size// 计算图像数据大小(YUV420格式:宽×高×1.5)
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;
// 应用视频图层属性配置
// set video layer atrr
kd_mpi_vo_set_video_layer_attr(chn_id, &attr);
// enable layer // 使能视频图层
kd_mpi_vo_enable_video_layer(chn_id);
return 0;
}
// 函数:创建OSD测试图层并配置属性
k_u32 vo_creat_osd_test(k_vo_osd osd, osd_info *info) {
k_vo_video_osd_attr attr; // OSD属性结构体
// set attr 设置OSD全局透明度
attr.global_alptha = info->global_alptha;
// 根据像素格式计算OSD大小和步长(stride)
if (info->format == PIXEL_FORMAT_ABGR_8888 ||
info->format == PIXEL_FORMAT_ARGB_8888) {
info->size = info->act_size.width * info->act_size.height * 4;
info->stride = info->act_size.width * 4 / 8;
} else if (info->format == PIXEL_FORMAT_RGB_565 ||
info->format == PIXEL_FORMAT_BGR_565) {
info->size = info->act_size.width * info->act_size.height * 2;
info->stride = info->act_size.width * 2 / 8;
} else if (info->format == PIXEL_FORMAT_RGB_888 ||
info->format == PIXEL_FORMAT_BGR_888) {
info->size = info->act_size.width * info->act_size.height * 3;
info->stride = info->act_size.width * 3 / 8;
} else if (info->format == PIXEL_FORMAT_ARGB_4444 ||
info->format == PIXEL_FORMAT_ABGR_4444) {
info->size = info->act_size.width * info->act_size.height * 2;
info->stride = info->act_size.width * 2 / 8;
} else if (info->format == PIXEL_FORMAT_ARGB_1555 ||
info->format == PIXEL_FORMAT_ABGR_1555) {
info->size = info->act_size.width * info->act_size.height * 2;
info->stride = info->act_size.width * 2 / 8;
} else {
printf("set osd pixel format failed \n");
}
// 配置OSD属性
attr.stride = info->stride; // 步长
attr.pixel_format = info->format; // 像素格式
attr.display_rect = info->offset; // 显示位置偏移
attr.img_size = info->act_size; // 图像大小
// 应用OSD属性配置
kd_mpi_vo_set_video_osd_attr(osd, &attr);
// 使能OSD图层
kd_mpi_vo_osd_enable(osd);
return 0;
}
// 配置 VO 层:根据屏幕分辨率和每层需求,计算位置、大小并创建 VO 层
// 初始化 VO 层布局:基于 display 宽高和各层尺寸自动计算位置
k_s32 sample_vicap_vo_layer_init(k_vicap_vo_layer_conf *layer_conf,
k_u32 display_width, k_u32 display_height) {
k_s32 ret = 0;
layer_info info[K_MAX_VO_LAYER_NUM];
k_u16 margin = 0; // 层间间隔
k_u16 rotation = 0; // 临时旋转值
k_u16 relative_height = 0; // 已布局高度
k_u16 total_height = 0; // 累积高度
osd_info osd_info;
memset(&info, 0, sizeof(info));
memset(&osd_info, 0, sizeof(osd_info));
// 计算每一层输出的实际尺寸和旋转模式
for (int i = 0; i < K_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: // 90° 旋转:宽高互换
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);
}
}
// 设置每层的偏移和属性并创建 VO 层
for (int i = 0; i < K_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; // YUV420 默认格式
info[i].global_alptha = 0xff; // 不透明
// 创建视频层
vo_creat_layer_test(layer_conf->layer[i], &info[i]);
}
}
// osd enable // 如果第三层(OSD)使能,则创建 OSD,创建(显示文字/时间)
if (layer_conf->enable[2]) {
osd_info.act_size.width = layer_conf->width[2];
;
osd_info.act_size.height = layer_conf->height[2];
;
osd_info.offset.x = (display_width - layer_conf->width[2]) / 2;
;
osd_info.offset.y = margin + relative_height;
osd_info.global_alptha = 0xff;
// OSD 使用 RGB888
osd_info.format =
PIXEL_FORMAT_RGB_888; // PIXEL_FORMAT_ARGB_4444;
// //PIXEL_FORMAT_ARGB_1555;//PIXEL_FORMAT_ARGB_8888;
vo_creat_osd_test(layer_conf->layer[2], &osd_info);
}
return ret;
}
void vo_en(void) {
k_vo_pub_attr pub = {
.bg_color = 0x000000, /* 黑底 */
.intf_sync = K_VO_OUT_1080P30, /* 或 30,根据你 VI FPS */
};
kd_mpi_vo_init();
kd_mpi_vo_set_dev_param(&pub);
kd_mpi_vo_enable();
}
void disable_vo_layer(void) { kd_mpi_vo_disable_video_layer(K_VO_LAYER0); }
/* === 四宫格固定布局:3 路摄像头 + 1 留空 === */
void vicap_vo_layer_init(void) {
/* layer 顺序固定:Cam0→layer0、Cam1→layer1、Cam2→layer2 */
const k_vo_layer layers[3] = {K_VO_LAYER0, K_VO_LAYER1, K_VO_LAYER2};
/* 每格在 1920×1080 里的左上角坐标 */
const k_vo_point offsets[3] = {{0, 0}, {960, 0}, {0, 540}};
layer_info info;
memset(&info, 0, sizeof(info));
/* 公共参数 */
info.act_size.width = 960;
info.act_size.height = 540;
info.format = PIXEL_FORMAT_YUV_SEMIPLANAR_420; /* VI 默认出 NV12/YUV420 */
info.global_alptha = 0xff;
for (int i = 0; i < 3; ++i) {
info.offset = offsets[i];
info.func = 0;
vo_creat_layer_test(layers[i], &info); /* 设置属性并 enable layer */
sample_vicap_bind_vo(i, 0, layers[i]);
}
}
/* 配置并打开 WBC */
int wbc_enable(void) {
k_vo_wbc_attr attr;
memset(&attr, 0, sizeof(attr));
attr.target_size.width = MOSAIC_W; /* 1920 */
attr.target_size.height = MOSAIC_H; /* 1080 */
attr.pixel_format = PIXEL_FORMAT_YUV_SEMIPLANAR_420;
attr.stride = (MOSAIC_W / 8 - 1) + ((MOSAIC_H - 1) << 16);
attr.stride = 0; /* 0 = 交给驱动自己算 */
attr.y_phy_addr = 0; /* 驱动内部分 VB */
kd_mpi_vo_set_wbc_attr(&attr);
return kd_mpi_vo_enable_wbc();
}
/* 取帧示例(阻塞 1000 ms)*/
void *wbc_dump_task(void *arg) {
k_video_frame_info vf_info;
while (1) {
if (!kd_mpi_wbc_dump_frame(&vf_info, 1000)) { /* 成功拿到混合帧 */
/* TODO: 这里把 vf 直接写进 USB-UVC(V4L2)队列即可 */
kd_mpi_wbc_dump_release(&vf_info); /* 用完务必释放 */
}
}
return NULL;
}
int save_nv12_frame(const k_video_frame_info *vf, const char *filepath) {
const k_video_frame *frm = &vf->v_frame;
int width = frm->width;
int height = frm->height;
/* 1. 把物理地址映射到虚拟地址(仅当前帧生效) */
void *vaddr_y = kd_mpi_sys_mmap(frm->phys_addr[0], frm->stride[0] * height);
void *vaddr_uv =
kd_mpi_sys_mmap(frm->phys_addr[1], frm->stride[1] * height / 2);
if (!vaddr_y || !vaddr_uv) {
printf("mmap failed\n");
return -1;
}
/* 2. 逐行拷贝到本地缓冲,去掉 stride 填充 */
size_t frame_size = width * height * 3 / 2;
k_u8 *buf = (k_u8 *)malloc(frame_size);
if (!buf) {
printf("malloc failed\n");
goto unmap;
}
/* --- Y --- */
for (int h = 0; h < height; ++h) {
memcpy(buf + h * width, (k_u8 *)vaddr_y + h * frm->stride[0], width);
}
/* --- UV --- */
k_u8 *dst_uv = buf + width * height;
for (int h = 0; h < height / 2; ++h) {
memcpy(dst_uv + h * width, (k_u8 *)vaddr_uv + h * frm->stride[1], width);
}
/* 3. 写文件 */
int fd = open(filepath, O_CREAT | O_WRONLY | O_TRUNC, 0666);
if (fd < 0) {
printf("open %s failed\n", filepath);
goto free_buf;
}
if (write(fd, buf, frame_size) != (ssize_t)frame_size) {
printf("write failed\n");
}
close(fd);
printf("save frame ok → %s (%zu bytes)\n", filepath, frame_size);
free_buf:
free(buf);
unmap:
kd_mpi_sys_munmap(vaddr_y, frm->stride[0] * height);
kd_mpi_sys_munmap(vaddr_uv, frm->stride[1] * height / 2);
return 0;
}