书接上文......
"轰——!"
五颗龙珠在我掌心轰然合一,霎时间金光冲天而起。系统面板在虚空中骤然展开,鎏金古篆与科技蓝光交相辉映:
《RKMedia·天工开物》已解锁!
刹那间,无数玄奥符文化作数据洪流涌入紫府。原本晦涩难懂的功法要诀,此刻竟如掌上观纹般清晰明了。
突然,功法扉页浮现一段赤色警示文字:
"道友且慢!"
"前篇所述,不过模块调用之粗浅法门。本篇将授尔'模块融合'的无上心法——"
"然!未参透VI、VO、RGA等基础道纹者,强行修炼必致真元逆乱!轻则代码崩溃,重则道基尽毁!"
"修炼一途,当循序渐进。望道友谨记:贪功冒进者,终将走火入魔!"
最后几个字竟渗出丝丝血色,在虚空中不断震颤,显是功法前辈留下的血泪警示。
以下内容抽象于官方SDK,实际进行小型项目开发时,直接按照官方SDK进行调用即可,无需劳心费神进行抽象,抽象是为了进行大型项目开发时方便管理和复用。本文章提供抽象思路,如有不足,欢迎各位大佬指正。
1. 管道交互类抽象实现
如上篇文章所讲,RKMedia实际是将媒体资源封装成了若干个模块,调用相应的模块就能实现相应的功能。那不同模块之间如何进行数据传输呢?RKMedia 提供了一对接口,调用这对接口即可实现两个模块间的绑定与解绑:
RK_MPI_SYS_Bind(const MPP_CHN_S *pstSrcChn, const MPP_CHN_S *pstDestChn);
RK_MPI_SYS_UnBind(const MPP_CHN_S *pstSrcChn, const MPP_CHN_S *pstDestChn);
抽象思路:上述接口接受的两个参数类型均为MPP_CHN_S,MPP_CHN_S数据结构如下:
typedef struct rkMPP_CHN_S {
MOD_ID_E enModId;
RK_S32 s32DevId;
RK_S32 s32ChnId;
} MPP_CHN_S;
同时,在创建 VI / VO / RGA 等通道时,都会设置 dev_id 和 chn_id,因此抽象管道交互类时,需要传递 dev_id(我认为 pipe_id 更好理解,所以更名为 pipe_id )和 chn_id, 管道交互类定义如下:
#ifndef MEDIA_PIPE_H_
#define MEDIA_PIPE_H_
#include "rkmedia_api.h"
#include "rkmedia_common.h"
#include <cassert>
#include <stdint.h>
#include <string>
#include <vector>
namespace owner
{
namespace media
{
enum class PipeType : int32_t { DEV_VIE_E = 0, DEV_VPE_E, DEV_VEN_E, DEV_VDE_E, DEV_RGN_E, DEV_VO_E, DEV_AO_E, DEV_VMIX_E, DEV_NULL_E };
template <typename... Args> class PipeLine
{
public:
PipeLine(enum PipeType pipe_type, int32_t pipe_id) : pipe_type_(pipe_type), pipe_id_(pipe_id) {}; // 构造
virtual ~PipeLine(){}; // 纯虚析构函数,留给子类实现
virtual int32_t Init(Args...) = 0; // 纯虚初始化函数,留给子类实现
virtual int32_t DeInit(void) = 0; // 纯虚反初始化函数,留给子类实现
virtual int32_t Start(void) = 0; // 纯虚启动函数,留给子类实现
virtual int32_t Stop(void) = 0; // 纯虚停止函数,留给子类实现
template <typename... Args1> int32_t BindNextPipe(int32_t src_dev, int32_t src_dev_id, int32_t dst_dev, int32_t dst_dev_id, PipeLine<Args1...> *next_pipe)
{
MPP_CHN_S stSrcChn;
MPP_CHN_S stDestChn;
GetRKMediaType(pipe_type_, stSrcChn.enModId); // 获得源设备类型
stSrcChn.s32DevId = src_dev; // 设置源设备ID
stSrcChn.s32ChnId = src_dev_id; // 设置源通道索引
GetRKMediaType(next_pipe->GetPipeType(), stDestChn.enModId);
stDestChn.s32DevId = dst_dev;
stDestChn.s32ChnId = dst_dev_id;
int32_t ret = RK_MPI_SYS_Bind(&stSrcChn, &stDestChn); // 绑定管道设备
CHECK_STATUS(ret, -1, "RK_MPI_SYS_Bind src[%d,%d] des[%d,%d] failed with %d!\n", src_dev, src_dev_id, dst_dev, dst_dev_id, ret); // 校验返回结果
assert(0 == ret);
return ret;
}
template <typename... Args1> int32_t UnBindPipe(int32_t src_dev, int32_t src_dev_id, int32_t dst_dev, int32_t dst_dev_id, PipeLine<Args1...> *next_pipe)
{
MPP_CHN_S stSrcChn;
MPP_CHN_S stDestChn;
GetRKMediaType(pipe_type_, stSrcChn.enModId);
stSrcChn.s32DevId = src_dev;
stSrcChn.s32ChnId = src_dev_id;
GetRKMediaType(next_pipe->GetPipeType(), stDestChn.enModId);
stDestChn.s32DevId = dst_dev;
stDestChn.s32ChnId = dst_dev_id;
int32_t ret = RK_MPI_SYS_UnBind(&stSrcChn, &stDestChn); // 解绑管道设备
CHECK_STATUS(ret, -1, "RK_MPI_SYS_Bind src[%d,%d] des[%d,%d] failed with %d!\n", src_dev, src_dev_id, dst_dev, dst_dev_id, ret);
assert(0 == ret);
return ret;
}
enum PipeType GetPipeType(void) return pipe_type_;
int32_t GetPipeId(void) return pipe_id_;
private:
static int32_t GetRKMediaType(enum PipeType pipe_type, MOD_ID_E &mod_id)
{
if (PipeType::DEV_VIE_E == pipe_type) mod_id = RK_ID_VI;
else if (PipeType::DEV_VPE_E == pipe_type) mod_id = RK_ID_RGA;
else if (PipeType::DEV_VO_E == pipe_type) mod_id = RK_ID_VO;
else if (PipeType::DEV_VEN_E == pipe_type) mod_id = RK_ID_VENC;
else if (PipeType::DEV_VDE_E == pipe_type) mod_id = RK_ID_VDEC;
else if (PipeType::DEV_VMIX_E == pipe_type) mod_id = RK_ID_VMIX;
else assert(0); // 未知的设备类型
return 0;
};
protected:
PipeType pipe_type_;
int32_t pipe_id_;
};
}
}
#endif // MEDIA_PIPE_H_
2. VI 类抽象实现
VI 类继承管道交互类,由于管道交互类 PipeLine 定义了几个纯虚函数,所以继承该父类的 VI类需要实现这些函数。同时 VI 类需要定义 pipe_id 和 chanel_id 用于指明需要操作的管道 id 和 通道 id,同时预留接口 GetFrame 用于获取 VI 通道中的数据,以供后续调试, VI 类实现如下:
#ifndef MEDIA_VI_H_
#define MEDIA_VI_H_
#include "rkmedia_common.h"
#include "rkmedia_vi.h"
#include "media_pipe.h"
namespace owner
{
namespace media
{
typedef struct ViPipeParam {
uint32_t width;
uint32_t height;
uint32_t frame_rate;
} ViPipeParam;
class ViPipe final : public PipeLine<ViPipeParam &>
{
public:
ViPipe(int32_t pipe_id = 0);
~ViPipe(void);
int32_t Init(ViPipeParam &vi_pipe_param) { return 0; }; // 以下为父类纯虚函数的实现
int32_t Start(void);
int32_t Stop(void);
int32_t DeInit(void);
int32_t GetFrame(MEDIA_BUFFER &frame, MB_IMAGE_INFO_S &img_info, RK_S32 timeout); // 获取视频帧函数
int32_t ReleaseFrame(MEDIA_BUFFER &frame); // 释放视频帧函数
private:
static int32_t number_of_sensor_;
bool config_done_;
VI_PIPE vi_pipe_;
VI_CHN vi_chan_;
ViPipeParam vi_param_;
};
}
}
#endif // MEDIA_VI_H_
3. RGA 类和 VO 类的抽象
同理,RGA 类和 VO 类也继承管道交互类 PipeLine,实现方式类似 VI 类
3.1 RGA 类抽象实现
RGA 通道在预留数据获取接口的基础上,又添加了回调接口SetRGACallBack, 为啥要在RGA 类中添加回调接口?------ 因为我在做算法处理,通过RGA进行缩放以及格式转换后,我能够直接将 RGA 通道中的数据以(BGR)格式送入算法进行处理,同时在 RGA 通道上也方便进行OSD 图层叠加,实现一些VO显示上的需求。
#ifndef MEDIA_RGA_H_
#define MEDIA_RGA_H_
#include <functional>
#include <thread>
#include "rkmedia_common.h"
#include "rkmedia_rga.h"
#include "media_pipe.h"
#include "media_frame.h"
namespace owner
{
namespace media
{
using VpPipeFrameCallback = std::function<int32_t(MediaFrameInfo &frame)>;
typedef struct RGAPipeParam {
bool tmp;
} RGAPipeParam;
class RGAPipe final : public PipeLine<RGAPipeParam &>
{
public:
RGAPipe(int32_t pipe_id = 0);
~RGAPipe(void);
int32_t Init(RGAPipeParam &vp_pipe_para) { return 0; };
int32_t Start(void); // 以下为父类纯虚函数的实现
int32_t Stop(void);
int32_t DeInit(void);
int32_t SetRGACallBack(VpPipeFrameCallback frame_proc_cb); // 视频帧回调函数
int32_t GetFrame(RKMEDIA_FRAME_INFO &frame, RK_S32 timeout); // 获取视频帧函数
int32_t ReleaseFrame(RKMEDIA_FRAME_INFO &frame); // 释放视频帧函数
int32_t FrameProcess(void);
private:
RGA_CHN vp_chan_;
RK_U32 u32TimeRef;
VpPipeFrameCallback callback;
std::shared_ptr<std::thread> rga_thread_proc_frame;
bool exit;
};
}
}
#endif // MEDIA_RGA_H_
3.2 VO 类抽象实现
VO 类预留了一个媒体缓冲池 mbp,预留目的:涉及到算法推理时,如果要将算法推理结果通过 VO 进行显示,是没有办法通过管道绑定的方式实现的。必须将经过算法推理的帧数据,送入VO 的媒体缓冲池中实现。
#ifndef MEDIA_VO_H_
#define MEDIA_VO_H_
#include "media_pipe.h"
#include "rkmedia_common.h"
#include "rkmedia_api.h"
#include "rkmedia_vo.h"
namespace owner
{
namespace media
{
typedef struct VoPipeParam
{
uint32_t chan_cnt;
VO_CHN_ATTR_S chan_attr[VO_MAX_CHN_NUM];
} VoPipeParam;
class VoPipe : public PipeLine<VoPipeParam &>
{
public:
VoPipe(int32_t pipe_id = 0);
~VoPipe(void);
int32_t Init(VoPipeParam &vo_pipe_param) { return 0; }; // 以下为父类纯虚函数的实现
int32_t Start(void);
int32_t Stop(void);
int32_t DeInit(void);
private:
bool config_done_; // 配置完成标志
uint32_t vo_layer_; // 视频输出图层
VO_CHN vo_chn;
VoPipeParam vo_param_;
MEDIA_BUFFER_POOL mbp; // VO的媒体缓冲池(非必要)
};
}
}
#endif // MEDIA_VO_H_
"眼下修炼尚未涉及 VENC 与 VDEC 两大玄妙模块,故未将其纳入本命法宝的抽象道纹之中。然万法同源,殊途同归——"
"诸位道友且看,那 VI、VO、RGA 等模块的炼化之法,与此二者实乃一脉相承。不过是在数据流转间多结几道法印,多布几重阵法罢了。"
"吾辈修士,当持'明知山有虎,偏向虎山行'的锐气!今日未炼,非是不能,只是机缘未至。他日若遇视频编解码之需,相信以各位天骄的悟性,定能:"
1. 参透模块真意——细品官方文档中的天道法则
2. 构筑抽象道基——以面向对象之法,凝练模块精魄
3. 融会贯通——将新模块无缝接入现有功法体系"届时,诸位的本命法宝必将更上一层楼!正所谓:'源码即功法,抽象即大道'..."
(虚空中隐约浮现模块类架构图,道纹流转间,隐约可见VENC、VDEC的预留接口)
"本章修炼至此暂告一段落。然道途漫漫,此间所授不过基础心法要诀..."
"下卷功法预告:"
《万法归宗篇》
完整呈现各本命头文件的道纹铭刻之法(附源码真解)
详解VI/VO/VENC等模块如何以"数据流转大阵"串联
《跨界融合篇》
传授将RKMedia功法与深度学习模型融合的无上秘术
实战演示人脸识别+视频编码的复合仙术
"鉴于天机不可轻泄,待本座将各派系功法梳理完善后,自当择吉日开坛讲法..."