// 系统初始化
RK_MPI_SYS_Init();
// 分别为rknn跟monitor创建vi通道,通道号分别为0跟1
ret = create_vi(u32Width, u32Height, "rkispp_scale0", DRAW_RESULT_BOX_CHN_INDEX);
if (ret < 0) {
printf("create_vi %d failed\n", DRAW_RESULT_BOX_CHN_INDEX);
return -1;
}
ret = create_vi(u32VC1Width, u32VC1Height, "rkispp_scale1", RK_NN_RGA_CHN_INDEX);
if (ret < 0) {
printf("create_vi %d cb\n", RK_NN_RGA_CHN_INDEX);
return -1;
}
mpi初始化没啥可说的
创建两个vi通道,正如前面说的,有两个输入通道,一个负责展示推理结果,一个负责作为推理的输入.通道号分别为0跟1
DRAW_RESULT_BOX_CHN_INDEX 是0
RK_NN_RGA_CHN_INDEX 是1
// 创建rga通道,通道号为1
ret = create_rga_chn(RK_NN_RGA_CHN_INDEX, u32VC1Width,
u32VC1Height, MODEL_INPUT_SIZE, MODEL_INPUT_SIZE);
if (ret < 0) {
printf("create_rga_chn failed ret:%d\n", ret);
return -1;
}
// 把rga chn1绑定到vi chn1
bind_vi_rga(s32CamId, RK_NN_RGA_CHN_INDEX, RK_NN_RGA_CHN_INDEX);
创建rga通道1,接到vi通道1,rga通道的设置有点混乱:
RGA_ATTR_S stRgaAttr;
memset(&stRgaAttr, 0, sizeof(stRgaAttr));
stRgaAttr.bEnBufPool = RK_TRUE;
stRgaAttr.u16BufPoolCnt = 3;
stRgaAttr.stImgIn.imgType = IMAGE_TYPE_NV12;
stRgaAttr.u16Rotaion = 0;
stRgaAttr.stImgIn.u32X = (u32WidthIn - u32WidthOut) / 2; // 输入的起始位置, 例如输入如果是1920x1080, 输出是640x640, 那么输入的起始位置就是(1920-640)/2=640
stRgaAttr.stImgIn.u32Y = (u32HeightIn - u32HeightOut) / 2;
stRgaAttr.stImgIn.u32Width = u32WidthOut; // 注意这里是输出的宽高
stRgaAttr.stImgIn.u32Height = u32HeightOut;
stRgaAttr.stImgIn.u32HorStride = u32WidthIn; // 所谓虚高
stRgaAttr.stImgIn.u32VirStride = u32HeightIn;
stRgaAttr.stImgOut.u32X = 0; // 输出的起始位置
stRgaAttr.stImgOut.u32Y = 0;
stRgaAttr.stImgOut.imgType = IMAGE_TYPE_NV12; // 输出的格式
stRgaAttr.stImgOut.u32Width = u32WidthOut; // 输出的宽高
stRgaAttr.stImgOut.u32Height = u32HeightOut;
stRgaAttr.stImgOut.u32HorStride = u32WidthOut; // 输出的虚高;
stRgaAttr.stImgOut.u32VirStride = u32HeightOut;
RK_S32 ret = RK_MPI_RGA_CreateChn(rgaChn, &stRgaAttr); // 创建rga通道
改天我得画个图才能说明白这个事儿.简单来说,咱们输入的可能是1920x1080分辨率,为了给推理当作输入,比如裁剪成640x640,这就涉及比如是否缩放的问题,起始位置的问题,就像你拿一张很大的图片,当作你windows的墙纸一样,有平铺,中心对齐(缩放)跟中心对齐(不缩放),等等几种,现在这个模式就是类似单纯裁剪的模式,仅仅使用画面中640*640的部分,其他部分直接丢掉.
绑定vi到rga就非常简单,指定通道调用RK_MPI_SYS_Bind绑定就行
// 创建venc通道,通道号为0
ret = create_venc_chn(30, u32Width, u32Height, DRAW_RESULT_BOX_CHN_INDEX);
if (ret < 0) {
printf("create_venc_chn %d\n", DRAW_RESULT_BOX_CHN_INDEX);
return -1;
}
接下来创建编码通道,还是使用通道号0, 分辨率跟vi分辨率一样,1920*1080
// 注册venc的回调函数,即产生了编码好的数据,就会回调这个函数
ret = reg_venc_cb();
if (ret < 0) {
printf("reg venc cb\n");
return -1;
}
rkmedia获取数据流的方法有2种:
1.使用RK_MPI_SYS_GetMediaBuffer获取数据在内存中的块
2.注册一个回调获取数据
int reg_venc_cb() {
MPP_CHN_S stVenChn;
memset(&stVenChn, 0, sizeof(MPP_CHN_S));
stVenChn.enModId = RK_ID_VENC;
stVenChn.s32ChnId = DRAW_RESULT_BOX_CHN_INDEX; // 使用monitor进程画框子的那个通道
RK_S32 ret = RK_MPI_SYS_RegisterOutCb(&stVenChn, venc_packet_cb);
if (ret) {
printf("ERROR: register output callback for VENC[0] error! ret=%d\n", ret);
return -1;
}
return 0;
}
这里的编码器就是用回调的方法,注册了venc_packet_cb这个函数,它的输入就是已经编码好的数据.
void venc_packet_cb(MEDIA_BUFFER mb) {
static RK_S32 packet_cnt = 0;
if (g_flag_run == 0)
return;
printf("#Get packet-%d, size %zu\n", packet_cnt, RK_MPI_MB_GetSize(mb));
if (g_rtsplive && g_rtsp_session) {
rtsp_tx_video(g_rtsp_session, (const uint8_t *) RK_MPI_MB_GetPtr(mb), RK_MPI_MB_GetSize(mb),
RK_MPI_MB_GetTimestamp(mb));
rtsp_do_event(g_rtsplive);
}
RK_MPI_MB_ReleaseBuffer(mb);
packet_cnt++;
}
这个回调拿到编码完成的数据后,就丢给rtsp去准备推流处理了.
聪明的你可能会问,那我的推理部分跟画框框过程呢?
// bind_vi_venc();
// 打开vi通道0的输出,即开始从isp获取数据
ret = RK_MPI_VI_StartStream(s32CamId, DRAW_RESULT_BOX_CHN_INDEX);
if (ret < 0) {
printf("RK_MPI_VI_StartStream failed\n");
return -1;
}
首先打开监控的vi通道,因为后面我们要手动画框,所以不能直接绑定,而是使用RK_MPI_VI_StartStream(s32CamId, DRAW_RESULT_BOX_CHN_INDEX); 再RK_MPI_SYS_GetMediaBuffer(RK_ID_VI, DRAW_RESULT_BOX_CHN_INDEX..的方法
// 创建rknn的队列,用于线程间保存rknn的推理结果
create_rknn_list(&rknn_list_);
创建推理的结果队列,其实就是个简单的限定长度的fifo链表, 用于保存推理结果.推理结果就是一个结构体,记录找到多少个框,每个框的类别,xy长宽信息.
下面分析最后两个也是最重要的进程, monitor进程跟推理进程.