RKMedia 模块介绍 "RKMedia" 是对 "RV1126/RV1109" 内所有媒体资源调用进行了整合封装的一套 "API" 接口,它可以大大降低刚接触 "RV1126/RV1109" 芯片平台用户的开发成 本,以少量代码就可以很快速、简单地调用 Soc 上所有媒体资源。同时 "RKMedia" 还提供了媒体资源以外的硬件资源联合调用 DEMO,如:"RKAIQ、 RKNN、RTSP" 等等。"RKMedia" 的核心思想是把各个硬件资源独立成模块,模块 开放出输入和输出端通过绑定的方式控制流从某个模块流出并且流入另外一个模 块。这里会对每个模块进行相关介绍。 VI VI 为视频输入模块,可以理解为摄像头采集模块,需要注意的是 VI 需要 RKAIQ 的支持,在使用 VI 前需要提前初始化好 RKAIQ,详细初始化步骤可以在 Example 中查看。 VI 通道属性设置 MPP_CHN_S stViChn; #定义模块设备通道结构体 stViChn.enModId = RK_ID_VI; #模块号为 RK_ID_VI stViChn.s32ChnId #设置 VI 通道号 VI_CHN_ATTR_S vi_chn_attr; #定义 VI 通道属性结构体指针 vi_chn_attr.u32BufCnt # VI 捕获视频缓冲区计数 vi_chn_attr.u32Width # video 宽度 vi_chn_attr.u32Height # video 高度 vi_chn_attr.enWorkMode # VI 通道工作模式 vi_chn_attr.pcVideoNode # video 节点路径 vi_chn_attr.enPixFmt # video 格式 设置 VI 通道属性 #函数定义: RK_MPI_VI_SetChnAttr(VI_PIPE ViPipe, VI_CHN ViChn, const VI_CHN_ATTR_S *pstChnAttr); # ViPipe 为 VI 管道号;ViChn 为 VI 通道号; pstChnAttr 为 VI 通道属性结构体指针。 #使用示例: RK_MPI_VI_SetChnAttr(vi_pipe, stViChn.s32ChnId, &vi_chn_attr); 启用 VI 通道 #函数定义: RK_S32 RK_MPI_VI_EnableChn(VI_PIPE ViPipe, VI_CHN ViChn); # ViPipe 为 VI 管道号; ViChn 为 VI 通道号。 #使用示例: RK_MPI_VI_EnableChn(vi_pipe, stViChn.s32ChnId); VDEC VDEC 模块,即视频解码模块。本模块支持多路实时解码,且每路解码独立,支 持 H264/H265/MJPEG/JPEG 解码。 解码通道属性设置 MPP_CHN_S stVdecChn; #定义模块设备通道结构体 stVdecChn.enModId = RK_ID_VDEC; #模块号为 RK_ID_VDEC stVdecChn.s32ChnId #设置解码通道号 VDEC_CHN_ATTR_S stVdecAttr; #定义解码通道属性结构体 stVdecAttr.enCodecType #解码格式 stVdecAttr.enMode #解码输入模式,支持帧/流 stVdecAttr.enDecodecMode #解码模式,支持硬件或软件解码 创建 VDEC 解码通道 #函数定义: RK_S32 RK_MPI_VDEC_CreateChn(VDEC_CHN VdChn, const VDEC_CHN_ATTR_S *pstAttr); # VdChn 为解码通道号; pstAttr 为解码通道属性指针。 #使用示例: RK_MPI_VDEC_CreateChn(stVdecChn.s32ChnId, &stVdecAttr); RGA RGA 模块用于 2D 图像的裁剪、格式转换、缩放、旋转、图片叠加等。 使用 RGA 处理可以大大降低 CPU 的负担,同时加快图片的处理速度。(参考: 1080p 图片格式转换。RGA 耗时:7ms,Opencv 耗时:50ms) rkmedia 中 RGA 通道仅支持裁剪、格式转换、缩放、旋转功能,图片叠加则需 要单独调用librga.so库,参见《Rockchip_Developer_Guide_Linux_RGA_CN.pdf 》 RGA 通道属性设置 MPP_CHN_S stRgaChn; #定义模块设备通道结构体 stRgaChn.enModId = RK_ID_RGA; #模块号为 RK_ID_RGA stRgaChn.s32ChnId #设置解码通道号 RGA_ATTR_S stRgaAttr; #定义 RGA 属性结构体 stRgaAttr.bEnBufPool #使能缓冲池 stRgaAttr.u16BufPoolCnt #缓冲池计数 stRgaAttr.u16Rotaion #旋转 取值范围 0,90,180,270 stRgaAttr.stImgIn.u32X # RGA 通道输入图片的X轴坐标 stRgaAttr.stImgIn.u32Y # RGA 通道输入图片的Y轴坐标 stRgaAttr.stImgIn.imgType # RGA 通道输入图片格式 stRgaAttr.stImgIn.u32Width #输入图片宽度 stRgaAttr.stImgIn.u32Height #输入图片高度 stRgaAttr.stImgIn.u32HorStride #输入图片虚宽 stRgaAttr.stImgIn.u32VirStride #输入图片虚高 stRgaAttr.stImgOut.u32X # RGA 通道输出图片的 X 轴坐标 stRgaAttr.stImgOut.u32Y # RGA 通道输出图片的 Y 轴坐标 stRgaAttr.stImgOut.imgType # RGA 通道输出图片格式 stRgaAttr.stImgOut.u32Width #输出图片宽度 stRgaAttr.stImgOut.u32Height #输出图片高度 stRgaAttr.stImgOut.u32HorStride #输出图片虚宽 stRgaAttr.stImgOut.u32VirStride #输出图片虚高 创建 RGA 通道 #函数定义: RK_S32 RK_MPI_RGA_CreateChn(RGA_CHN RgaChn, RGA_ATTR_S *pstRgaAttr); # RgaChn 为 RGA 通道号; pstAttr 为 RGA 通道属性指针。 #使用示例: RK_MPI_RGA_CreateChn(stRgaChn.s32ChnId, &stRgaAttr); "stride" 宽度,通常与 "buffer_width" 相同。若 "u32VirWidth" 大于 "buffer" 宽度,则必须满足 "16" 对齐。 可使用 "CELING_2_POWER" 函数对变 量进行 "16" 对齐设置。例如: stRgaAttr.stImgOut.u32VirStride = CELING_2_POWER(u32Height,16); VO VO 模块用于视频输出管理。VO 模块是对 DRM/KMS 的封装,支持多 VOP 以及多 图层显示。 MPP_CHN_S VoChn; #定义模块设备通道结构体 VO_CHN_ATTR_S stVoAttr = {0}; #定义视频输出属性结构体并初始化为 0 memset(&stVoAttr, 0, sizeof(stVoAttr)); stVoAttr.pcDevNode = "/dev/dri/card0"; #视频 VO 输出设备节点设置为"/dev/dri/card0" stVoAttr.emPlaneType #视频输出图层类型 stVoAttr.enImgType #输入图片格式 stVoAttr.u16Zpos #输出图层 Z 轴高度 stVoAttr.u32Width #视频输出宽度 stVoAttr.u32Height #视频输出高度 stVoAttr.stImgRect.s32X #视频输入图像区域的 X 轴坐标 stVoAttr.stImgRect.s32Y #视频输入图像区域的 Y 轴坐标 stVoAttr.stImgRect.u32Width #输入图像区域的宽度 stVoAttr.stImgRect.u32Height #输入图像区域的高度 stVoAttr.stDispRect.s32X #显示区域的起始 X 轴坐标 stVoAttr.stDispRect.s32Y #显示区域的起始 Y 轴坐标 stVoAttr.stDispRect.u32Width #显示区域的宽度 stVoAttr.stDispRect.u32Height #显示区域的高度 VoChn.enModId = RK_ID_VO; #模块号为 RK_ID_VO VoChn.s32DevId #模块设备号 VoChn.s32ChnId #模块通道号 创建 VO 通道 #函数定义: RK_S32 RK_MPI_VO_CreateChn(VO_CHN VoChn, const VO_CHN_ATTR_S *pstAttr); # VoChn 为 VO 通道号;pstAttr 为 VO 通道属性指针。 #使用示例: RK_MPI_VO_CreateChn(VoChn.s32ChnId, &stVoAttr); VENC VENC 模块,即视频编码模块。本模块支持多路实时编码,且每路编码独立,编 码协议和编码 profile 可以不同。支持视频编码同时,调度 Region 模块对编 码图像内容进行叠加和遮挡。支持 H264/H265/MJPEG/JPEG 编码。 MPP_CHN_S stVenChn; #定义模块设备通道结构体 stVenChn.enModId = RK_ID_VENC; #模块号设置为 RK_ID_VENC stVenChn.s32ChnId #编码通道号 memset(&venc_chn_attr, 0, sizeof(venc_chn_attr)); VENC_CHN_ATTR_S venc_chn_attr; #定义 VENC 通道属性结构体 venc_chn_attr.stVencAttr.enType #编码格式 venc_chn_attr.stVencAttr.imageType #编码图片格式 venc_chn_attr.stVencAttr.u32PicWidth #图片宽度 venc_chn_attr.stVencAttr.u32PicHeight #图片高度 venc_chn_attr.stVencAttr.u32VirWidth #图片虚宽 venc_chn_attr.stVencAttr.u32VirHeight #图片虚高 venc_chn_attr.stVencAttr.u32Profile #用于H264编码器的 Profile IDC 值 "H264" 或 "H265" 编码的参数参照以下配置 venc_chn_attr.stRcAttr.enRcMode #编码协议类型 根据具体使用编码格式配置 venc_chn_attr.stRcAttr.stH264Cbr.u32Gop = fps; # I 帧间隔,取值范围:[1, 65536] venc_chn_attr.stRcAttr.stH264Cbr.u32BitRate = u32Width * u32Height * fps / 14; #平均比特率,取值范围:[2000, 98000000],单位:bps venc_chn_attr.stRcAttr.stH264Cbr.fr32DstFrameRateDen = 1; #目标帧率分子 venc_chn_attr.stRcAttr.stH264Cbr.fr32DstFrameRateNum = fps; #目标帧率分母 venc_chn_attr.stRcAttr.stH264Cbr.u32SrcFrameRateDen = 1; #数据源帧率分子 venc_chn_attr.stRcAttr.stH264Cbr.u32SrcFrameRateNum = fps; #数据源帧率分母 创建 VENC 通道 #函数定义: RK_S32 RK_MPI_VENC_CreateChn(VENC_CHN VeChn, VENC_CHN_ATTR_S *stVencChnAttr); # VeChn 为编码通道号; stVencChnAttr 编码通道属性指针。 #使用示例: RK_MPI_VENC_CreateChn(stVenChn.s32ChnId, &venc_chn_attr); 通道绑定 设置好通道属性之后就可以对通道进行绑定了。 数据源和数据接收者绑定接口如下: RK_S32 RK_MPI_SYS_Bind(const MPP_CHN_S *pstSrcChn,const MPP_CHN_S *pstDestChn); #pstSrcChn 为源通道指针; pstDestChn 目的通道指针。 注:通道绑定需要注意源通道的输出与目的通道的输入的数据格式要对应。 通道数据发送与获取 常用向指定通道输入数据函数如下: RK_S32 RK_MPI_SYS_SendMediaBuffer(MOD_ID_E enModID, RK_S32 s32ChnID, MEDIA_BUFFER buffer); # enModID 为模块号; s32ChnID 为通道号; buffer 为缓冲区。 常用向指定通道获取数据函数如下: MEDIA_BUFFER RK_MPI_SYS_GetMediaBuffer(MOD_ID_E enModID, RK_S32 s32ChnID, RK_S32 s32MilliSec); # enModID 为模块号; s32ChnID 为通道号; s32MilliSec 为阻塞等待时间,当该值为 -1 的时候表示阻塞等待。 RTSP 拉流与推流 为了配合网络摄像头视频分析场景,我们提供了简单的 RTSP 拉流和推流接口。 RTSP 拉流 使用 firefly 编写的库函数接口 ffrtspGet 可轻松实现拉取 RTSP 数据流。 需要配置结构体 ffrtsp_get ,其结构体配置介绍如下: struct FFRTSPGet ffrtsp_get; #定义 FFRTSPGet 类型结构体 ffrtsp_get.count = 0; ffrtsp_get.callback = NULL; ffrtsp_get.callback = FFRKMedia_Vdec_Send; #回调函数 ffrtsp_get.count #拉取的 RTSP 数据流的个数 ffrtsp_get.ffrtsp_get_info[i].url #拉取的 RTSP 数据流的链接 注:配置好结构体之后只需要创建一个线程任务即可实现拉取单个或多个 "RTSP" 数据流。 RTSP 推流 使用 firefly 编写的库函数接口 ffrtspPush 可轻松实现推送 RTSP 数据流。 需要配置结构体 ffrtsp_push ,其结构体配置介绍如下: ffrtsp_push[i].idex #推送 RTSP 数据流的编号 ffrtsp_push[i].port #推送 RTSP 数据流的端口 ffrtsp_push[i].type = cfg.session_cfg[i].video_type; #推送 RTSP 数据流的视频类型 注:配置好结构体之后需要创建的线程个数要匹配推送的 "RTSP" 数据流的个数 。 通道析构顺序 需要特别注意的是 rkmedia 对模块的析构顺序有特殊的要求:数据流管道中后 级模块要先于前级模块销毁。比如: VI --> RGA --> VENC 则建议析构顺序如下: destroy VENC --> destroy RGA --> destroy VI 以 "VI" 为例, "VI" 是数据产生端。其生产的 "buffer" 在数据管道销毁时可 能被后级占用,从而导致 "VI" 管理的资源也被占用。再次打开就会遇到 "Device Busy" 的错误。这个问题在频繁创建销毁数据通道时有概率发生。 详细 RKMEDIA 使用请参考手册《 Rockchip_Developer_Guide_Linux_RKMedia_CN.pdf》。 具体示例 为了方便用户快速掌握 RKMedia 的开发流程,Firefly 提供了大量的测试 DEMO 。代码路径 "SDK/external/rkmedia/example/"、 "SDK/app/firefly_rkmedia_demo"。 VI->GetFrame 该开发流程对应的 DEMO 示例为 rkmedia_vi_get_frame_test.c 说明:设备输入保存至文件。演示 VI 没有 Bind 时如何取视频流。 快速使用: #从摄像头节点 rkispp_scale0 抓取 10 帧图片并保存为 1080p.nv12 文件 ./rkmedia_vi_get_frame_test -a /oem/etc/iqfiles/ -w 1920 -h 1080 -d rkispp_scale0 -o /tmp/1080p.nv12 -c 10 # 命令录取 10 帧图像数据,截取最后一帧来预览 # 使用 dd 跳过前 9 帧数据,得到最后一帧。3110400 = 1920 x 1080 x 3 / 2 一帧 NV12 数据大小 dd if=1080p.nv12 of=1080pl.nv12 bs=3110400 skip=9 # 使用 ffmpeg 把 NV12 图像转换为 PNG 格式。 fmpeg -y -f rawvideo -pix_fmt nv12 -ss 00:01 -r 1 -s 1920x1080 -i 1080pl.nv12 -frames:v 1 output.png # 打开 output.png 预览。 DEMO 效果截图如下图所示: VI->RKNN->VENC->RTSP 该开发流程对应的 DEMO 示例为 rkmedia_vi_rknn_venc_rtsp_test.c 说明:该示例解析 rtsp-nn.cfg 配置文件来配置 VI 接口和 VENC 接口的输入 端与输出端信息,将 VI 接口获取摄像头节点图片数据送入 NPU 进行 AI 模型 推理,然后解析推理结果。根据推理结果进行像素点偏移画框,然后将处理好的 数据使用接口 RK_MPI_SYS_SendMediaBuffer 发送到 VENC 编码接口进行编码, 最后使用 rtsp_tx_video 接口将 VENC 编码好的数据流推流到 RTSP 网络。 快速使用: # 路径需要有相关文件 ./rkmedia_vi_rknn_venc_rtsp_test -a /oem/etc/iqfiles/ -c /oem/usr/share/rtsp-nn.cfg -b /oem/usr/share/rknn_model/box_priors.txt -l /oem/usr/share/rknn_model/coco_labels_list.txt -p /oem/usr/share/rknn_model/ssd_inception_v2_rv1109_rv1126.rknn # PC 端使用 VLC 播放器预览 RTSP 推流命令为 vlc rtsp://168.168.101.208:554/live/main_stream #注: 168.168.101.208 为开发板的 IP 地址,根据实际情况进行调整 DEMO 效果截图如下图所示: RTSPGet->VDEC(Multi)->VO 该开发流程对应的 DEMO 示例为 rkmedia_rtspget_multi_test.cc 说明:该示例使用 RTSP 拉流接口 ffrtspGet 从多个网络摄像头获取数据,然 后使用 VDEC 接口解码,接着使用 RGA 加速将原始数据构建成目标数据,最后 使用 RK_MPI_SYS_SendMediaBuffer 接口将目标数据发送到 VO 接口以分屏输出 显示。 快速使用: # RTSP 取流 2 个网络摄像头,每个摄像头取流 2 次,并输出显示到显示屏 ./rkmedia_rtspget_multi_test rtsp://admin:firefly123@168.168.100.94:554/av_stream rtsp://admin:firefly123@168.168.100.94:554/av_stream rtsp://admin:firefly123@168.168.100.97:554/av_stream rtsp://admin:firefly123@168.168.100.97:554/av_stream DEMO 效果截图如下图所示: RTSPGet->VDEC->RKNN->VENC->RTSPPush 该开发流程对应的 DEMO 示例为 rkmedia_rtspget_vdec_rknn_venc_rtsp_test.cc 说明:该示例可实现多个 RTSP 拉流解码与多个 RTSP 编码推流的效果。其流程 使用 RTSP 拉流接口 ffrtspGet 从多个网络摄像头获取数据,然后使用 VDEC 接口解码。将解码的图片数据送入 NPU 进行 AI 模型推理,然后解析推理结果 ,使用 OpenCV 将推理结果应用到图片上。VENC 则对 OpenCV 处理后的图片进 行编码,最后使用接口 ffrtspPush 将 VENC 编码好的数据流推流到 RTSP 网络 。 快速使用: #运行该示例需要有库文件 libffrtsp.so ./rkmedia_rtspget_vdec_rknn_venc_rtsp_test -c /usr/share/ffrtsp-nn.cfg -p /usr/share/rknn_model/ssd_inception_v2_rv1109_rv1126.rknn -l /usr/share/rknn_model/coco_labels_list.txt -b /usr/share/rknn_model/box_priors.txt # ffrtsp-nn.cfg 两个网络摄像头的配置分别为 video_type=6 video_fps=25 width=1920 height=1080 image_type=4 port=8554 video_url=rtsp://admin:firefly123@168.168.100.94:554/av_stream video_type=6 video_fps=25 width=1920 height=1080 image_type=4 port=8555 video_url=rtsp://admin:firefly123@168.168.100.97:554/av_stream #则 PC 端使用 VLC 预览 RTSP 推流画面命令为 vlc rtsp://168.168.101.208:8554/H264_stream_0 vlc rtsp://168.168.101.208:8555/H264_stream_1 #注:168.168.101.208 为开发板的 IP ,根据实际情况进行调整 DEMO 效果截图如下图所示: