重生之我和RV1126 RKMedia的恩怨情仇二——不破不立篇

书接上文......

        "轰——!"

        五颗龙珠在我掌心轰然合一,霎时间金光冲天而起。系统面板在虚空中骤然展开,鎏金古篆与科技蓝光交相辉映:

        《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的预留接口)

        "本章修炼至此暂告一段落。然道途漫漫,此间所授不过基础心法要诀..."

"下卷功法预告:"

  1. 《万法归宗篇》

    • 完整呈现各本命头文件的道纹铭刻之法(附源码真解)

    • 详解VI/VO/VENC等模块如何以"数据流转大阵"串联

  2. 《跨界融合篇》

    • 传授将RKMedia功法与深度学习模型融合的无上秘术

    • 实战演示人脸识别+视频编码的复合仙术

        "鉴于天机不可轻泄,待本座将各派系功法梳理完善后,自当择吉日开坛讲法..."

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不想起名字呢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值