测试手动传一帧vb的内存进行264编码和获取到本地,运行库报错,附上注释代码

Viewed 47

运行报错
get VB block : 393216(这是我申请的vb打印)
VA and PA must be page aligned. va=0x70000000, pa=0x13ed3400.
<3>[9] [Func]:venc_mvx_qbuf [Line]:373 [Info]:venc_mvx_qbuf>error line 373

例程里面全是用显示屏的,vi bind vo,我这里客户不需要显示屏,下面是手动封装一帧发送的代码。封装帧在vb进行dump 保存本地查看是可以的。

下面的代码都是一点点按思维贴的,大佬劳烦看一下错在哪

代码


先获取vb
#define PAGE_SIZE 4096
#define DTS_SIZE VICAP_ALIGN_UP(1920 * 1080 * 3 / 2, PAGE_SIZE)
k_vb_blk_handle vb = kd_mpi_vb_get_block(VB_INVALID_POOLID, DTS_SIZE, NULL);

然后id
k_s32 pool_id = kd_mpi_vb_handle_to_pool_id(vb);

物理地址
unsigned long phys_addr = kd_mpi_vb_handle_to_phyaddr(vb);

映射虚拟地址
uint8_t *vir = mmap_vb_blk(phy, DTS_SIZE);

获取vi数据

  for (int cam = 0; cam < 3; ++cam) 
    ret = kd_mpi_vicap_dump_frame(cam, 0, VICAP_DUMP_YUV, &vf[cam], 1000);
这里每一个数据都是960*540的

把独立的3帧合成一个画面,算法是ai给的
#define DST_W 1920
#define DST_H 1080
    
static int fill_4grid_to_mmz(uint8_t *dst_mmz, k_video_frame_info vf[3]) {
  memset(dst_mmz, 0x10, DST_W * DST_H);
  memset(dst_mmz + DST_W * DST_H, 0x80, DST_W * DST_H / 2);
  /* 整幅背景置暗灰 (Y=16, UV=128) */
  memset(dst_mmz, 0x10, DST_W * DST_H);                     /* Y  */
  memset(dst_mmz + DST_W * DST_H, 0x80, DST_W * DST_H / 2); /* UV */

  for (int cam = 0; cam < 3; ++cam) {
    /* ---- mmap Y / UV ---- */
    int src_stride = vf[cam].v_frame.stride[0]; /* ← 一行真实字节 */
    /* 2. mmap 时用这个真实行宽算大小: */
    size_t y_size = VICAP_ALIGN_UP(src_stride * SUB_H, 4096);
    size_t uv_size = VICAP_ALIGN_UP(src_stride * SUB_H / 2, 4096);
    uint8_t *srcY = kd_mpi_sys_mmap(vf[cam].v_frame.phys_addr[0], y_size);
    uint8_t *srcUV = kd_mpi_sys_mmap(vf[cam].v_frame.phys_addr[1], uv_size);
    if (!srcY || !srcUV) {
      perror("mmap");
      if (srcY)
        kd_mpi_sys_munmap(srcY, y_size);
      if (srcUV)
        kd_mpi_sys_munmap(srcUV, uv_size);
      kd_mpi_vicap_dump_release(cam, 0, &vf[cam]);
      return -1;
    }
    /* 目标起点:0=左上, 1=右上, 2=左下 */
    int dx = (cam == 1) * SUB_W;
    int dy = (cam == 2) * SUB_H;

    uint8_t *dstY = dst_mmz + dy * DST_W + dx;
    uint8_t *dstUV = dst_mmz + DST_W * DST_H + (dy / 2) * DST_W + dx;

    /* ---- copy Y ---- */
    for (int r = 0; r < SUB_H; ++r)
      memcpy(dstY + r * DST_W, srcY + r * src_stride, SUB_W);

    /* ---- copy UV (NV12) ---- */
    for (int r = 0; r < SUB_H / 2; ++r)
      memcpy(dstUV + r * DST_W, srcUV + r * src_stride, SUB_W);
    kd_mpi_sys_munmap(srcY, y_size);
    kd_mpi_sys_munmap(srcUV, uv_size);
  }
  return 0;
}

最后得到合成的一帧虚拟地址vir和物理地址phy

刷新一下kd_mpi_sys_mmz_flush_cache(phys_addr, virt_addr, size);


下面进行编码264初始化


#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

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;
}

配置k_video_frame_info

  vf->mod_id = K_ID_VO;  
  vf->pool_id = pool_id;

  vf->v_frame.width = width;
  vf->v_frame.height = height;
  vf->v_frame.stride[0] = width; /* 1920 已经 64 对齐 */
  vf->v_frame.pixel_format = PIXEL_FORMAT_YUV_SEMIPLANAR_420;

  vf->v_frame.phys_addr[0] = phy;                  /* Y plane */
  vf->v_frame.phys_addr[1] = phy + width * height; /* UV plane */


发送这帧
 kd_mpi_venc_send_frame(CH, (k_video_frame_info *)vf, -1);

阻塞结束后开一个线程想保存

/* ---------- 取码流线程 ---------- */
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;
}

2 Answers

你好,发送给编码器的帧(frame)其物理首地址需满足 4K 对齐要求。对于格式为 PIXEL_FORMAT_YUV_SEMIPLANAR_420 的帧,其 Y 分量和 UV 分量各自的物理首地址也都必须单独满足 4K 对齐。
建议您在构造帧之后,打印出该帧的Y 分量、UV 分量各自的物理首地址,将这些实际地址与报错信息中提示的未对齐地址进行对比,即可定位具体是哪个地址未满足 4K 对齐要求,从而排查问题根源。

你好,我怎么知道分配的vb内存池和申请的vb内存对上呢?比如vb里面的 config.comm_pool[k].blk_cnt = 3;
config.comm_pool[k].mode = VB_REMAP_MODE_NOCACHE;
config.comm_pool[k].blk_size =
VICAP_ALIGN_UP((1920 * 1080 * 3 / 2), VICAP_ALIGN_1K);然后在申请就只是kd_mpi_vb_get_block。这个kd_mpi_vb_get_block是不是根据内存池的大小来获取要的size,和vb分配无关?还是说vb需要注意什么

优先匹配size相同的

大佬,已经4k对齐了构造帧,错误少了,但还是有qbuf错误,您看看<3>[9] [Func]:venc_mvx_qbuf [Line]:373 [Info]:venc_mvx_qbuf>error line 373
这是什么情况,

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;
}

参照demo:/src/rtsmart/mpp/userapps/sample/sample_venc,这里有从vicap保存编码数据的示例

大哥。我现在状态是这样的

①3路摄像头编码264给uvc然后电脑看(jpeg怕usb2.0带宽不够),那3路摄像头就只能先把3路构造在同一个1080p帧再进行编码

②之前有咨询过您用vo合成帧再wbc给264编码,但是没跑通,没找到vo怎么合成,是用3个layer么

③现在用cpu构造1080p帧需要2-300ms(感觉太慢了,跑通给到uvc显示估计只有几帧?),dump下来能看,寻思想把全流程跑通再说。

④但是昨天编码就不行,4k对齐出来的图全是条纹。现在就是想单纯测264编码功能跑通,发现老出现老是有问题

这种问题还是需要依赖硬件来处理可能比较好避免。所以又得回到②去,踏实走好每一步

感激大哥指导小弟下了,先用硬件怎么把3路流构造为1080p帧,这部做好了,后续编码应该就不会出现错误,全硬件处理了。带带弟弟

vo合成帧再wbc给264编码,这个方案可行。
参看smart_ipc,先把smart_ipc这个demo 运行起来,-D参数来指定从vo wbc编码。