vi+vo
#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 // 旋转时额外使用
// 初始化 VB(Video Buffer)池,根据设备对象配置各缓冲区
k_s32 pengzhihao_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 = 3;
config.comm_pool[k].mode = VB_REMAP_MODE_NOCACHE;
config.comm_pool[k].blk_size = ((1920 * 1080 * 3 / 2) + 0xfff) & ~0xfff;
k++; /* k 指向下一个空槽,留给后续其它模块再加 pool */
config.comm_pool[k].blk_cnt = 6;
config.comm_pool[k].mode = VB_REMAP_MODE_NOCACHE;
config.comm_pool[k].blk_size = (1920 * 1080 * 2 + 0xfff) & ~0xfff;
k++; /* k 指向下一个空槽,留给后续其它模块再加 pool */
config.comm_pool[k].blk_cnt = 15;
config.comm_pool[k].mode = VB_REMAP_MODE_NOCACHE;
config.comm_pool[k].blk_size = (1920 * 1080 / 2 + 0xfff) & ~0xfff;
k++; /* k 指向下一个空槽,留给后续其它模块再加 pool */
// 应用 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 pengzhihao_vb_exit() { kd_mpi_vb_exit(); }
// 读取 RISC-V 时间戳,用于计算耗时
uint64_t pengzhihao_get_ticks() {
static volatile uint64_t time_elapsed = 0;
__asm__ __volatile__("rdtime %0" : "=r"(time_elapsed));
return time_elapsed;
}
static void pengzhihao_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] = VICAP_CHN_ID_0;
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 pengzhihao_vicap_dev_conf(vicap_device_obj *dev_obj, k_u8 dev_count) {
if (dev_count > VICAP_DEV_ID_MAX)
return;
pengzhihao_set_default_device_config(&dev_obj[0], VICAP_DEV_ID_0);
// log_pengzhihao_i("配置设备0完成\r\n");
if (1 < dev_count) {
dev_obj[1] = dev_obj[0];
dev_obj[1].dev_num = VICAP_DEV_ID_1;
dev_obj[1].sensor_type =
OV_OV5647_MIPI_CSI0_1920X1080_30FPS_10BIT_LINEAR_V2;
// log_pengzhihao_i("配置设备1完成\r\n");
}
if (2 < dev_count) {
dev_obj[2] = dev_obj[0];
dev_obj[2].dev_num = VICAP_DEV_ID_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;
// dev_obj[2].preview[cur_chn] = K_FALSE;
// log_pengzhihao_i("配置设备2完成\r\n");
}
// PZH_I("-------------pengzhihao_vicap_dev_conf-------------------\r\n");
}
k_s32 pengzhihao_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("-------------pengzhihao_vicap_devices_init-----------------\r\n");
return ret;
}
k_s32 pengzhihao_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("-----------------------\r\n");
return ret;
}
k_s32 pengzhihao_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("------------------------------\r\n");
return ret;
}
k_s32 pengzhihao_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("------------------------------\r\n");
return ret;
}
void pengzhihao_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 = pengzhihao_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 = pengzhihao_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("------------------------------\r\n");
}
void pengzhihao_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 pengzhihao_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);
}
下面是h264+送去编码
#include "pengzhihao_h264.h"
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "k_module.h"
#include "k_sys_comm.h"
#include "k_type.h"
#include "k_vb_comm.h"
#include "k_venc_comm.h"
#include "mpi_sys_api.h"
#include "mpi_vb_api.h"
#include "mpi_venc_api.h"
#include "mpi_vvi_api.h"
bool p_en = 1;
#define ENC_WIDTH 1920 // 编码宽度
#define ENC_HEIGHT 1080 // 编码高度
#define VENC_MAX_IN_FRAMES 30
#define MAX_WIDTH 1920 // 最大宽度
#define MAX_HEIGHT 1080 // 最大高度
#define STREAM_BUF_SIZE \
((MAX_WIDTH * MAX_HEIGHT / 2 + 0xfff) & \
~0xfff) // 码流缓冲区大小(对齐到4096)
#define OUTPUT_BUF_CNT 15 // 输出缓冲区数量
#define CH 0
#define FPS 30
#define BITRATE_KBPS 4000
// 编码状态枚举:管理编码流程的生命周期
typedef enum {
VENC_SAMPLE_STATUS_IDLE = 0, // 初始状态
VENC_SAMPLE_STATUS_INIT, // 初始化完成
VENC_SAMPLE_STATUS_START, // 编码器启动
VENC_SAMPLE_STATUS_BINDED, // 输入与编码器绑定
VENC_SAMPLE_STATUS_UNBINDED, // 输入与编码器解绑
VENC_SAMPLE_STATUE_RUNING, // 运行中
VENC_SAMPLE_STATUS_STOPED, // 已停止
VENC_SAMPLE_STATUS_BUTT // 状态结束标记
} VENC_SAMPLE_STATUS;
static VENC_SAMPLE_STATUS g_venc_sample_status =
VENC_SAMPLE_STATUS_IDLE; // 全局编码状态
// 检查函数返回值的宏:若出错则打印错误信息
static inline void CHECK_RET(k_s32 ret, const char *func, const int line) {
if (ret)
printf("error ret %d, func %s line %d\n", ret, func, line);
}
/**
*编码器输出线程逻辑
*/
// static void *venc_output_thread(void *arg) {}
int h264_init() {
int ch = CH; // 通道ID
int ret = 0;
k_venc_chn_attr attr;
memset(&attr, 0, sizeof(attr));
attr.venc_attr.pic_width = ENC_WIDTH; // 图像宽度
attr.venc_attr.pic_height = ENC_HEIGHT; // 图像高度
attr.venc_attr.stream_buf_size = STREAM_BUF_SIZE; // 码流缓冲区大小
attr.venc_attr.stream_buf_cnt = OUTPUT_BUF_CNT; // 码流缓冲区数量
// 码率控制配置(CBR) 码率控制模式:CBR(恒定码率)
attr.rc_attr.rc_mode = K_VENC_RC_MODE_CBR; // 码率控制模式
attr.rc_attr.cbr.src_frame_rate = FPS; // 源帧率
attr.rc_attr.cbr.dst_frame_rate = FPS; // 目标帧率
attr.rc_attr.cbr.bit_rate = BITRATE_KBPS; // 目标码率
attr.venc_attr.type = K_PT_H264; // 编码类型 编码类型:H.264
attr.venc_attr.profile = VENC_PROFILE_H264_HIGH; // 编码Profile H.264高Profile
printf("payload type is H264\n");
// 创建编码器通道
ret = kd_mpi_venc_create_chn(ch, &attr);
CHECK_RET(ret, __func__, __LINE__);
g_venc_sample_status = VENC_SAMPLE_STATUS_INIT;
// 启动编码器通道
ret = kd_mpi_venc_start_chn(ch);
CHECK_RET(ret, __func__, __LINE__);
g_venc_sample_status = VENC_SAMPLE_STATUS_START;
return ret;
}
int h264_init_w_h(unsigned int w, unsigned int h) {
int ch = CH; // 通道ID
int ret = 0;
k_venc_chn_attr attr;
memset(&attr, 0, sizeof(attr));
attr.venc_attr.pic_width = w; // 图像宽度
attr.venc_attr.pic_height = h; // 图像高度
attr.venc_attr.stream_buf_size = STREAM_BUF_SIZE; // 码流缓冲区大小
attr.venc_attr.stream_buf_cnt = OUTPUT_BUF_CNT; // 码流缓冲区数量
// 码率控制配置(CBR) 码率控制模式:CBR(恒定码率)
attr.rc_attr.rc_mode = K_VENC_RC_MODE_CBR; // 码率控制模式
attr.rc_attr.cbr.src_frame_rate = FPS; // 源帧率
attr.rc_attr.cbr.dst_frame_rate = FPS; // 目标帧率
attr.rc_attr.cbr.bit_rate = BITRATE_KBPS; // 目标码率
attr.venc_attr.type = K_PT_H264; // 编码类型 编码类型:H.264
attr.venc_attr.profile = VENC_PROFILE_H264_HIGH; // 编码Profile H.264高Profile
printf("payload type is H264\n");
// 创建编码器通道
ret = kd_mpi_venc_create_chn(ch, &attr);
CHECK_RET(ret, __func__, __LINE__);
g_venc_sample_status = VENC_SAMPLE_STATUS_INIT;
// 启动编码器通道
ret = kd_mpi_venc_start_chn(ch);
CHECK_RET(ret, __func__, __LINE__);
g_venc_sample_status = VENC_SAMPLE_STATUS_START;
return ret;
}
/* 直接把物理地址送进去 */
int h264_send_frame(const k_video_frame_info *vf) {
k_s32 ret = kd_mpi_venc_send_frame(CH, (k_video_frame_info *)vf, -1);
if (ret) {
printf("kd_mpi_venc_send_frame failed ret:%d\n", ret);
}
return ret;
}
/* ---------- 取码流线程 ---------- */
void *venc_output_thread(void *arg) {
FILE *fp = fopen("out.h264", "wb"); /* 想写文件就保留,推流可以去掉 */
k_venc_stream st;
k_venc_pack *pack_buf = NULL;
while (p_en) {
/* 1. 查询当前有多少 pack */
k_venc_chn_status stat;
if (kd_mpi_venc_query_status(CH, &stat) || stat.cur_packs == 0) {
usleep(2000); /* 没数据就小睡 2 ms */
continue;
}
/* 2. 申请 pack 缓冲并拉流 */
st.pack_cnt = stat.cur_packs;
pack_buf = (k_venc_pack *)malloc(sizeof(k_venc_pack) * st.pack_cnt);
st.pack = pack_buf;
if (kd_mpi_venc_get_stream(CH, &st, 200) == 0) { /* 200 ms 超时 */
/* 3. 逐个写文件/推流 */
for (int i = 0; i < st.pack_cnt; ++i) {
k_u8 *vir =
(k_u8 *)kd_mpi_sys_mmap(st.pack[i].phys_addr, st.pack[i].len);
if (fp) {
fwrite(vir, 1, st.pack[i].len, fp); /* or send_rtp(...) */
printf("save 264\r\n");
}
kd_mpi_sys_munmap(vir, st.pack[i].len);
}
/* 4. 归还码流 */
kd_mpi_venc_release_stream(CH, &st);
}
free(pack_buf);
}
if (fp)
fclose(fp);
return NULL;
}
/* 仅编码一路:dev = 摄像头号,chn = 通道号 (都填 0 就行) */
void encode_one_raw_frame(int dev, int chn) {
k_video_frame_info vf;
/* 1. 拉 1 帧 YUV;超时 1000 ms */
if (kd_mpi_vicap_dump_frame(dev, chn, VICAP_DUMP_YUV, &vf, 1000)) {
printf("vicap dump timeout\n");
return;
}
/* 2. 把这帧送去编码;-1 = 阻塞直到编码器吃完 */
if (h264_send_frame(&vf) == 0)
printf("send frame ✓\n");
else
printf("send frame ✗\n");
/* 3. 归还这帧给 VICAP */
kd_mpi_vicap_dump_release(dev, chn, &vf);
}
main(按t测试)
int main(void) {
k_s32 ret = 0;
// 设备属性
k_vicap_dev_attr dev_attr;
// 清零 dev_attr
memset(&dev_attr, 0, sizeof(k_vicap_dev_attr));
// 存放每路摄像头的配置,最多 VICAP_DEV_ID_MAX 路
vicap_device_obj device_obj[VICAP_DEV_ID_MAX];
pengzhihao_vicap_dev_conf(device_obj, VICAP_DEV_ID_MAX);
ret = pengzhihao_vicap_devices_init(device_obj, &dev_attr);
if (ret) {
printf("---------pengzhihao_vicap_devices_init failed---------\n");
return -1;
}
// 初始化 VB
ret = pengzhihao_sample_vicap_vb_init(device_obj);
if (ret) {
printf("pengzhihao_sample_vicap_vb_init failed\n");
return -1;
}
atexit(pengzhihao_vb_exit);
ret = pengzhihao_vicap_channels_init(device_obj);
if (ret) {
goto vb_exit;
}
ret = pengzhihao_vicap_mpi_init(device_obj);
if (ret) {
goto app_exit;
}
ret = pengzhihao_vicap_start_stream(device_obj);
if (ret) {
goto app_exit;
}
pengzhihao_slave_en();
h264_init_w_h(960, 540);
k_char select = 0;
uint64_t start, end;
while (K_TRUE) {
if (select != '\n') {
printf("---------------------------------------\n");
printf(" d: dump data addr test\n");
printf(" t: test one 264\n");
printf(" q: to exit\n");
printf("---------------------------------------\n");
}
select = (k_char)getchar();
switch (select) {
case 't': {
pthread_t tid;
pthread_create(&tid, NULL, venc_output_thread, NULL);
for (int i = 0; i < 300; ++i)
encode_one_raw_frame(0, 0);
sleep(2); // 等最后几帧写盘
p_en = 0;
pthread_join(tid, NULL);
} break;
case 'd':
printf("sample_vicap... dump frame.\n");
start = pengzhihao_get_ticks();
// compose_4grid_and_save(device_obj, "grid_1920x1080_nv12.yuv420sp");
compose_4grid_to_vb_and_save(device_obj, "grid_1920x1080_nv12.yuv420sp");
end = pengzhihao_get_ticks();
printf("dump cost %lu us\n", (end - start) / 27);
break;
case 'q':
goto app_exit;
default:
break;
}
sleep(1);
}
app_exit:
pengzhihao_app_exit(device_obj);
vb_exit:
return ret;
}