rkipc取jpeg图片分析
main.c部分
在main.c的main函数中rk_video_init(); 函数作为入口,开启rkipc取jpeg图片
rk_video_init函数
rk_video_init函数在
luckfox-pico/project/app/rkipc/rkipc/src/rv1106_ipc/video/video.c
文件中
int rk_video_init() {
LOG_DEBUG("begin\n");
int ret = 0;
//根据ini文件对各变量进行配置
enable_ivs = rk_param_get_int("video.source:enable_ivs", 1);
//enable_jpeg这里控制是否开启jpeg功能
enable_jpeg = rk_param_get_int("video.source:enable_jpeg", 1);
enable_venc_0 = rk_param_get_int("video.source:enable_venc_0", 1);
enable_venc_1 = rk_param_get_int("video.source:enable_venc_1", 1);
enable_rtsp = rk_param_get_int("video.source:enable_rtsp", 1);
enable_rtmp = rk_param_get_int("video.source:enable_rtmp", 1);
LOG_INFO("enable_jpeg is %d, enable_venc_0 is %d, enable_venc_1 is %d, enable_rtsp is %d, "
"enable_rtmp is %d\n",
enable_jpeg, enable_venc_0, enable_venc_1, enable_rtsp, enable_rtmp);
g_vi_chn_id = rk_param_get_int("video.source:vi_chn_id", 0);
g_enable_vo = rk_param_get_int("video.source:enable_vo", 1);
g_vo_dev_id = rk_param_get_int("video.source:vo_dev_id", 3);
enable_npu = rk_param_get_int("video.source:enable_npu", 0);
enable_osd = rk_param_get_int("osd.common:enable_osd", 0);
LOG_DEBUG("g_vi_chn_id is %d, g_enable_vo is %d, g_vo_dev_id is %d, enable_npu is %d, "
"enable_osd is %d\n",
g_vi_chn_id, g_enable_vo, g_vo_dev_id, enable_npu, enable_osd);
g_video_run_ = 1;
ret |= rkipc_vi_dev_init();
if (enable_rtsp)
ret |= rkipc_rtsp_init(RTSP_URL_0, RTSP_URL_1, NULL);
if (enable_rtmp)
ret |= rkipc_rtmp_init();
if (enable_venc_0)
ret |= rkipc_pipe_0_init();
if (enable_venc_1)
ret |= rkipc_pipe_1_init();
//如果开启jpeg功能,则调用rkipc_pipe_jpeg_init函数
if (enable_jpeg)
ret |= rkipc_pipe_jpeg_init();
// if (g_enable_vo)
// ret |= rkipc_pipe_vpss_vo_init();
rk_roi_set_callback_register(rk_roi_set);
ret |= rk_roi_set_all();
// rk_region_clip_set_callback_register(rk_region_clip_set);
// rk_region_clip_set_all();
if (enable_npu || enable_ivs) {
ret |= rkipc_pipe_2_init();
}
// The osd dma buffer must be placed in the last application,
// otherwise, when the font size is switched, holes may be caused
if (enable_osd)
ret |= rkipc_osd_init();
LOG_DEBUG("over\n");
return ret;
}
系统根据ini文件的enable_jpeg项决定是否调用rkipc_pipe_jpeg_init函数
rkipc_pipe_jpeg_init函数
int rkipc_pipe_jpeg_init() {
// jpeg resolution same to video.0
int ret;
//获取图像的宽度
int video_width = rk_param_get_int("video.jpeg:width", 1920);
//获取图片的高度
int video_height = rk_param_get_int("video.jpeg:height", 1080);
//获取图像旋转角度
int rotation = rk_param_get_int("video.source:rotation", 0);
// VENC[3] init
//设置编码通道结构体的值
VENC_CHN_ATTR_S jpeg_chn_attr;
memset(&jpeg_chn_attr, 0, sizeof(jpeg_chn_attr));
jpeg_chn_attr.stVencAttr.enType = RK_VIDEO_ID_JPEG;
jpeg_chn_attr.stVencAttr.enPixelFormat = RK_FMT_YUV420SP;
jpeg_chn_attr.stVencAttr.u32MaxPicWidth = rk_param_get_int("video.0:max_width", 2560);
jpeg_chn_attr.stVencAttr.u32MaxPicHeight = rk_param_get_int("video.0:max_height", 1440);
jpeg_chn_attr.stVencAttr.u32PicWidth = video_width;
jpeg_chn_attr.stVencAttr.u32PicHeight = video_height;
jpeg_chn_attr.stVencAttr.u32VirWidth = video_width;
jpeg_chn_attr.stVencAttr.u32VirHeight = video_height;
jpeg_chn_attr.stVencAttr.u32StreamBufCnt = 2;
jpeg_chn_attr.stVencAttr.u32BufSize = rk_param_get_int("video.jpeg:jpeg_buffer_size", 204800);
// jpeg_chn_attr.stVencAttr.u32Depth = 1;
//创建解码通道chn
//上面定义了#define JPEG_VENC_CHN 4
ret = RK_MPI_VENC_CreateChn(JPEG_VENC_CHN, &jpeg_chn_attr);
if (ret) {
LOG_ERROR("ERROR: create VENC error! ret=%d\n", ret);
return -1;
}
//jpeg的参数结构体
VENC_JPEG_PARAM_S stJpegParam;
memset(&stJpegParam, 0, sizeof(stJpegParam));
//q因子,约小越模糊,文件越小
stJpegParam.u32Qfactor = rk_param_get_int("video.jpeg:jpeg_qfactor", 70);
//设置jpeg参数
RK_MPI_VENC_SetJpegParam(JPEG_VENC_CHN, &stJpegParam);
//设置旋转
if (rotation == 0) {
RK_MPI_VENC_SetChnRotation(JPEG_VENC_CHN, ROTATION_0);
} else if (rotation == 90) {
RK_MPI_VENC_SetChnRotation(JPEG_VENC_CHN, ROTATION_90);
} else if (rotation == 180) {
RK_MPI_VENC_SetChnRotation(JPEG_VENC_CHN, ROTATION_180);
} else if (rotation == 270) {
RK_MPI_VENC_SetChnRotation(JPEG_VENC_CHN, ROTATION_270);
}
//编码器文件参数结构体
VENC_RECV_PIC_PARAM_S stRecvParam;
memset(&stRecvParam, 0, sizeof(VENC_RECV_PIC_PARAM_S));
//喂多少张图片
stRecvParam.s32RecvPicNum = 1;
RK_MPI_VENC_StartRecvFrame(JPEG_VENC_CHN,
&stRecvParam); // must, for no streams callback running failed
//开线程,获得jpeg图片
pthread_create(&jpeg_venc_thread_id, NULL, rkipc_get_jpeg, NULL);
//开线程喂图片
pthread_create(&get_vi_send_jpeg_thread_id, NULL, rkipc_get_vi_send_jpeg, NULL);
//根据enable_cycle_snapshot开关定时抓图
if (rk_param_get_int("video.jpeg:enable_cycle_snapshot", 0)) {
cycle_snapshot_flag = 1;
pthread_create(&cycle_snapshot_thread_id, NULL, rkipc_cycle_snapshot, NULL);
}
return ret;
}
这个函数设定了一系列的编码参数,开启三个线程,用于抓图,喂图,定时抓图
rkipc_get_jpeg线程,抓图
static void *rkipc_get_jpeg(void *arg) {
LOG_DEBUG("#Start %s thread, arg:%p\n", __func__, arg);
//改线程名称
prctl(PR_SET_NAME, "RkipcGetJpeg", 0, 0, 0);
VENC_STREAM_S stFrame;
int loopCount = 0;
int ret = 0;
char file_name[128] = {0};
char record_path[256];
//设定保存图片的路径
memset(&record_path, 0, sizeof(record_path));
strcat(record_path, rk_param_get_string("storage:mount_path", "/userdata"));
strcat(record_path, "/");
strcat(record_path, rk_param_get_string("storage.0:folder_name", "video0"));
//分配内存
stFrame.pstPack = malloc(sizeof(VENC_PACK_S));
// drop first frame
//第一张图,丢弃
ret = RK_MPI_VENC_GetStream(JPEG_VENC_CHN, &stFrame, 1000);
if (ret == RK_SUCCESS)
RK_MPI_VENC_ReleaseStream(JPEG_VENC_CHN, &stFrame);
else
LOG_ERROR("RK_MPI_VENC_GetStream timeout %x\n", ret);
//死循环
while (g_video_run_) {
//需要取图
if (!get_jpeg_cnt) {
usleep(300 * 1000);
continue;
}
// get the frame
ret = RK_MPI_VENC_GetStream(JPEG_VENC_CHN, &stFrame, 1000);
//成功
if (ret == RK_SUCCESS) {
//获得图片数据的地址
void *data = RK_MPI_MB_Handle2VirAddr(stFrame.pstPack->pMbBlk);
LOG_DEBUG("Count:%d, Len:%d, PTS is %" PRId64 ", enH264EType is %d\n", loopCount,
stFrame.pstPack->u32Len, stFrame.pstPack->u64PTS,
stFrame.pstPack->DataType.enH264EType);
// save jpeg file
time_t t = time(NULL);
struct tm tm = *localtime(&t);
//使用当前时间来做文件名
snprintf(file_name, 128, "%s/%d%02d%02d%02d%02d%02d.jpeg", record_path,
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min,
tm.tm_sec);
LOG_DEBUG("file_name is %s, u32Len is %d\n", file_name, stFrame.pstPack->u32Len);
//创建文件,把数据写进去
FILE *fp = fopen(file_name, "wb");
if (fp == NULL) {
LOG_ERROR("fp is NULL\n");
} else {
fwrite(data, 1, stFrame.pstPack->u32Len, fp);
}
// release the frame
//释放数据
ret = RK_MPI_VENC_ReleaseStream(JPEG_VENC_CHN, &stFrame);
if (ret != RK_SUCCESS) {
LOG_ERROR("RK_MPI_VENC_ReleaseStream fail %x\n", ret);
}
if (fp) {
fflush(fp);
fclose(fp);
}
//记录+1
loopCount++;
} else {
//超时
LOG_ERROR("RK_MPI_VENC_GetStream timeout %x\n", ret);
}
//取图数量-1
get_jpeg_cnt--;
//停止取图
RK_MPI_VENC_StopRecvFrame(JPEG_VENC_CHN);
}
//结束前释放内存
if (stFrame.pstPack)
free(stFrame.pstPack);
return 0;
}
该函数通过get_jpeg_cnt为开关来获取图片。
rkipc_get_vi_send_jpeg 线程,喂图
static void *rkipc_get_vi_send_jpeg(void *arg) {
LOG_DEBUG("#Start %s thread, arg:%p\n", __func__, arg);
prctl(PR_SET_NAME, "RkipcSendJPEG", 0, 0, 0);
int jpeg_width, jpeg_height, ret;
// TDE 2D加速器
TDE_HANDLE hHandle;
TDE_SURFACE_S pstSrc, pstDst;
TDE_RECT_S pstSrcRect, pstDstRect;
VIDEO_FRAME_INFO_S stViFrame, DstFrame;
PIC_BUF_ATTR_S Dst_stPicBufAttr;
MB_PIC_CAL_S Dst_stMbPicCalResult;
VENC_CHN_ATTR_S pstChnAttr;
MB_BLK dstBlk = RK_NULL;
Dst_stPicBufAttr.u32Width = rk_param_get_int("video.0:max_width", 2304);
Dst_stPicBufAttr.u32Height = rk_param_get_int("video.0:max_height", 1296);
Dst_stPicBufAttr.enPixelFormat = RK_FMT_YUV420SP;
Dst_stPicBufAttr.enCompMode = COMPRESS_MODE_NONE;
//根据Dst_stPicBufAttr 计算 Dst_stMbPicCalResult值
ret = RK_MPI_CAL_TDE_GetPicBufferSize(&Dst_stPicBufAttr, &Dst_stMbPicCalResult);
if (ret != RK_SUCCESS) {
LOG_ERROR("get picture buffer size failed. err 0x%x\n", ret);
return;
}
//根据计算的值分配缓存
ret = RK_MPI_SYS_MmzAlloc(&dstBlk, RK_NULL, RK_NULL, Dst_stMbPicCalResult.u32MBSize);
if (ret != RK_SUCCESS) {
LOG_ERROR("RK_MPI_SYS_MmzAlloc err 0x%x\n", ret);
return;
}
//设定源数据和目标数据参数
pstSrc.enColorFmt = RK_FMT_YUV420SP;
pstSrc.enComprocessMode = COMPRESS_MODE_NONE;
pstSrcRect.s32Xpos = 0;
pstSrcRect.s32Ypos = 0;
pstDst.enColorFmt = RK_FMT_YUV420SP;
pstDst.enComprocessMode = COMPRESS_MODE_NONE;
pstDstRect.s32Xpos = 0;
pstDstRect.s32Ypos = 0;
//目标frame的参数
memset(&DstFrame, 0, sizeof(VIDEO_FRAME_INFO_S));
DstFrame.stVFrame.enPixelFormat = RK_FMT_YUV420SP;
DstFrame.stVFrame.enCompressMode = COMPRESS_MODE_NONE;
//开启TDE
ret = RK_TDE_Open();
if (ret != RK_SUCCESS) {
LOG_ERROR("RK_TDE_Open fail %x\n", ret);
RK_MPI_SYS_Free(dstBlk);
return;
}
//死循环
while (g_video_run_) {
//开关
if (!send_jpeg_cnt) {
usleep(300 * 1000);
continue;
}
//根据ini文件配置源数据参数
pstSrc.u32Width = rk_param_get_int("video.0:width", -1);
pstSrc.u32Height = rk_param_get_int("video.0:height", -1);
pstSrcRect.u32Width = rk_param_get_int("video.0:width", -1);
pstSrcRect.u32Height = rk_param_get_int("video.0:height", -1);
//根据ini文件配置jpeg图像参数
jpeg_width = rk_param_get_int("video.jpeg:width", 1920);
jpeg_height = rk_param_get_int("video.jpeg:height", 1080);
//获得vi通道参数,PIPE_0
ret = RK_MPI_VI_GetChnFrame(pipe_id_, VIDEO_PIPE_0, &stViFrame, 1000);
if (ret == RK_SUCCESS) {
// tde begin job
//tde开始工作
hHandle = RK_TDE_BeginJob();
if (RK_ERR_TDE_INVALID_HANDLE == hHandle) {
LOG_ERROR("start job fail\n");
RK_MPI_VI_ReleaseChnFrame(pipe_id_, VIDEO_PIPE_0, &stViFrame);
continue;
}
// tde quick resize
//源内存指针设置为vi的输出内存指针
pstSrc.pMbBlk = stViFrame.stVFrame.pMbBlk;
//目标内存指针设置为刚才分配的内存
pstDst.pMbBlk = dstBlk;
//目标数据参数
pstDst.u32Width = jpeg_width;
pstDst.u32Height = jpeg_height;
pstDstRect.u32Width = jpeg_width;
pstDstRect.u32Height = jpeg_height;
//快速的变换尺寸
ret = RK_TDE_QuickResize(hHandle, &pstSrc, &pstSrcRect, &pstDst, &pstDstRect);
if (ret != RK_SUCCESS) {
LOG_ERROR("RK_TDE_QuickResize failed. err 0x%x\n", ret);
RK_TDE_CancelJob(hHandle);
RK_MPI_VI_ReleaseChnFrame(pipe_id_, VIDEO_PIPE_0, &stViFrame);
continue;
}
// tde end job
//tde结束工作
ret = RK_TDE_EndJob(hHandle, RK_FALSE, RK_TRUE, 10);
if (ret != RK_SUCCESS) {
LOG_ERROR("RK_TDE_EndJob failed. err 0x%x\n", ret);
RK_TDE_CancelJob(hHandle);
RK_MPI_VI_ReleaseChnFrame(pipe_id_, VIDEO_PIPE_0, &stViFrame);
continue;
}
//等待tde处理完成
ret = RK_TDE_WaitForDone(hHandle);
if (ret != RK_SUCCESS)
LOG_ERROR("RK_TDE_WaitForDone fail %x\n", ret);
// set jpeg venc w,h
//获取jpeg编码通道的参数
ret = RK_MPI_VENC_GetChnAttr(JPEG_VENC_CHN, &pstChnAttr);
if (ret != RK_SUCCESS)
LOG_ERROR("RK_MPI_VENC_GetChnAttr fail %x\n", ret);
//设定编码通道参数,就是刚才目标数据的参数
pstChnAttr.stVencAttr.u32PicWidth = pstDst.u32Width;
pstChnAttr.stVencAttr.u32PicHeight = pstDst.u32Height;
ret = RK_MPI_VENC_SetChnAttr(JPEG_VENC_CHN, &pstChnAttr);
if (ret != RK_SUCCESS)
LOG_ERROR("RK_MPI_VENC_SetChnAttr fail %x\n", ret);
// send frame to jpeg venc
//把TDE转换出来的数据发给编码器
DstFrame.stVFrame.pMbBlk = dstBlk;
DstFrame.stVFrame.u32Width = pstDst.u32Width;
DstFrame.stVFrame.u32Height = pstDst.u32Height;
DstFrame.stVFrame.u32VirWidth = pstDst.u32Width;
DstFrame.stVFrame.u32VirHeight = pstDst.u32Height;
ret = RK_MPI_VENC_SendFrame(JPEG_VENC_CHN, &DstFrame, 1000);
if (ret != RK_SUCCESS)
LOG_ERROR("RK_MPI_VENC_SendFrame fail %x\n", ret);
// release the frame
//释放内存
ret = RK_MPI_VI_ReleaseChnFrame(pipe_id_, VIDEO_PIPE_0, &stViFrame);
if (ret != RK_SUCCESS)
LOG_ERROR("RK_MPI_VI_ReleaseChnFrame fail %x\n", ret);
} else {
LOG_ERROR("RK_MPI_VI_GetChnFrame timeout %x\n", ret);
}
//发送的数据--
send_jpeg_cnt--;
}
//退出的时候关闭相关模块释放内存
RK_TDE_Close();
RK_MPI_SYS_Free(dstBlk);
return NULL;
}
该线程根据send_jpeg_cnt,从vi取到数据,使用tde进行快速转换,把数据从vi尺寸转换成jpeg所需要的尺寸,送到编码通道,进行编码。
rkipc_cycle_snapshot线程,定时抓图
static void *rkipc_cycle_snapshot(void *arg) {
LOG_INFO("start %s thread, arg:%p\n", __func__, arg);
prctl(PR_SET_NAME, "RkipcCycleSnapshot", 0, 0, 0);
//死循环,调用rk_take_photo设定抓图数量
while (g_video_run_ && cycle_snapshot_flag) {
usleep(rk_param_get_int("video.jpeg:snapshot_interval_ms", 1000) * 1000);
rk_take_photo();
}
LOG_INFO("exit %s thread, arg:%p\n", __func__, arg);
return 0;
}
int rk_take_photo() {
LOG_DEBUG("start\n");
//抓图没有结束,不给新任务
if (send_jpeg_cnt || get_jpeg_cnt) {
LOG_WARN("the last photo was not completed\n");
return -1;
}
//没有保存的位置,不抓图
if (rkipc_storage_dev_mount_status_get() != DISK_MOUNTED) {
LOG_WARN("dev not mount\n");
return -1;
}
VENC_RECV_PIC_PARAM_S stRecvParam;
memset(&stRecvParam, 0, sizeof(VENC_RECV_PIC_PARAM_S));
stRecvParam.s32RecvPicNum = 1;
//启动接收frame
RK_MPI_VENC_StartRecvFrame(JPEG_VENC_CHN, &stRecvParam);
send_jpeg_cnt++;
get_jpeg_cnt++;
return 0;
}
总结
整个流程就是
通过send_jpeg_cnt和get_jpeg_cnt控制数据从vi—>ted—>jpeg enc—>文件
步骤为:
设定保存图片的路径
为jpeg数据stFrame.pstPack分配内存 :
根据Dst_stPicBufAttr 计算 Dst_stMbPicCalResult值 TDE输出的内存大小RK_MPI_CAL_TDE_GetPicBufferSize(&Dst_stPicBufAttr, &Dst_stMbPicCalResult)
根据计算的值分配TDE输出缓存
RK_MPI_SYS_MmzAlloc(&dstBlk, RK_NULL, RK_NULL, Dst_stMbPicCalResult.u32MBSize);
开启TDE:RK_TDE_Open()
开始任务:RK_TDE_BeginJob()
转换
RK_TDE_QuickResize(hHandle, &pstSrc, &pstSrcRect, &pstDst, &pstDstRect);
其中在转换前要设置好各个参数
pstSrc.enColorFmt = RK_FMT_YUV420SP;
pstSrc.enComprocessMode = COMPRESS_MODE_NONE;
pstSrcRect.s32Xpos = 0;
pstSrcRect.s32Ypos = 0;
pstSrc.u32Width = rk_param_get_int("video.0:width", -1);
pstSrc.u32Height = rk_param_get_int("video.0:height", -1);
pstSrc.pMbBlk = stViFrame.stVFrame.pMbBlk; //这个要在读vi参数,拿到内存指针
pstDst.enColorFmt = RK_FMT_YUV420SP;
pstDst.enComprocessMode = COMPRESS_MODE_NONE;
pstDstRect.s32Xpos = 0;
pstDstRect.s32Ypos = 0;
pstDst.pMbBlk = dstBlk; //上面分配的TDE输出内存
pstDst.u32Width = jpeg_width;
pstDst.u32Height = jpeg_height;
pstDstRect.u32Width = jpeg_width;
pstDstRect.u32Height = jpeg_height;
结束转换:RK_TDE_EndJob
等待全部完成:RK_TDE_WaitForDone
设置编码器的参数:RK_MPI_VENC_SetChnAttr
向编码器输入数据:RK_MPI_VENC_SendFrame
释放frame:RK_MPI_VI_ReleaseChnFrame
获取编码码流:RK_MPI_VENC_GetStream
停止接收并销毁通道:RK_MPI_VENC_StopRecvFrame → RK_MPI_VENC_DestroyChn
写数据到fp里面
其中TDE是为了转换vi的尺寸到jpeg尺寸,如果两者相同,可以省略。
这里没使用通道绑定,需要cpu把数据从vi放到TDE放到ENC,会占用一定的CPU资源。