当前位置: 首页 > news >正文

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资源。

相关文章:

  • C++经典框架案例(三)
  • 2025雅森北京展今天开幕,全景展现新能源汽车时代
  • 算法分享——弗洛伊德算法暴力破解多源最短路问题
  • Java-01-源码篇-04集合-05-ConcurrentHashMap(1)
  • 关于实际工作中如何定位、复现、解决bug的个人心得
  • DeepSeek 部署全指南:常见问题解析与最新技术实践
  • 【Linux网络编程】数据链路层和网络层的几个问题:MTU,校验和,全球网段,路由表
  • C1车证学习笔记
  • Ryu:轻量开源,开启 SDN 新程
  • Ubuntu及其衍生系统安装Python
  • 自制操作系统前置知识汇编学习
  • C++核心指导原则: 资源管理
  • centos7中Open-Webui的部署
  • dataframe如何在末尾添加多行
  • MySQL 中的锁:为数据安全加把锁
  • SMT贴片加工关键技术解析
  • 基于计算机视觉的手势识别:让机器理解我们的手势语言
  • leetcode day20 滑动窗口209+904
  • 基于 Python 的项目管理系统开发
  • 【前端】react大全一本通
  • 国家发改委答澎湃:将建立和实施育儿补贴制度,深入实施提振消费专项行动
  • 仲裁法修订草案二审稿拟增加规定规制虚假仲裁
  • 孟泽:我们简化了历史,因此也简化了人性
  • 偷拍拷贝某轨道车技术信息后撰写论文发表,工程师被判一年有期徒刑
  • 经济日报金观平:充分发挥增量政策的经济牵引力
  • 瑞士外长答澎湃:瑞中都愿升级自贸协定,关税战没有任何好处