RV1126B开发板学习篇(一)MPP的编译和基础使用

本篇主要内容

一、基础概念扫盲

1. GStreamer是什么?

GStreamer是⼀个开源多媒体框架, ⽬前Linux SDK(除了IPC外)的多媒体都主要⽤GStreamer来对接app和编解码组件。利⽤GStreamer插件的强⼤特性,通过编写的GStreamer插件适配Rockchip硬件,使得app能够使⽤硬件编解码进⾏加速。 音视频框架

2. IPC是什么?

IPC是Internet Protocol Camera 的缩写,译为网络摄像机(或IP摄像头)。是一种通过网络传输视频、音频数据的监控设备,无需依赖传统模拟信号传输,可直接与网络设备(路由器、NVR 等)连接,支持远程访问和管理。

3. ISP是什么?

ISP是Image Signal Processor(图像信号处理器)的缩写,是处理图像原始数据的硬件模块。

4. MPP是什么?

MPP(Media Processing Platform-媒体处理平台)是RK自研的底层音视频编解码中间件,能实现在RK平台针对视频编解码的硬件加速, 其中包括解码插件:mppvideodec和mppjpegdec;编码插件: mpph264enc, mppvp8enc等 。专注编解码

5. rockchipmpp是什么?

rockchipmpp是RK基于MPP开发的GStreamer专用插件对接GStreamer的插件

6. Rockchip平台视频编解码和显⽰基本流程

这里指的是GStreamer框架下的流程,在官方文档Rockchip_User_Guide_Linux_Gstreamer_CN有详细描述
主要关注rockchipmpp和mpp之间是如何配合工作的即可,简单来说就是rockchipmpp为编解码过程提供了硬件加速,其中编解码mppvideodec调用的是mpp提供的接口,mpp为上层提供接口,去调用底层驱动vpu_service,底层做的就是硬件编解码相关的工作

二、MPP编解码

1. MPP交叉编译

MPP源码在SDK的路径:atk_dlrv1126b_linux6.1_sdk_release_v1.0.0_20251202/external/mpp

在源码顶层目录下的build/linux/aarch64/make-Makefiles.bash声明交叉编译器和安装目录,这里已经把交叉编译器安装到了/opt下

shell 复制代码
vi build/linux/aarch64/make-Makefiles.bash

修改完make-Makefiles.bash就是基本流程

shell 复制代码
./make-Makefiles.bash
make -j16
make install	# 安装到系统默认路径需要加sudo

最后,可以将生成的文件拷贝到开发板上验证,不过这里主要是给我们基于这个库在ubuntu开发和编译使用,开发板上已经移植好了这些东西,如果和我一样喜欢用CLion的小伙伴们,使用类似以下的项目结构,在CmakeLists这样配置,就可以在CLion中有代码提示,方便开发

cmake 复制代码
cmake_minimum_required(VERSION 3.22.1)

# 交叉编译器
set(CROSS_COMPILE aarch64-buildroot-linux-gnu-)
set(CMAKE_C_COMPILER ${CROSS_COMPILE}gcc)
project(smartHome_v2.0_rebuild C)

# MPP相关头文件
set(SDK_INCLUDE_PATH ${CMAKE_SOURCE_DIR}/include)
set(SDK_INCLUDE_PATH ${CMAKE_SOURCE_DIR}/include/rockchip)
# MPP库链接路径
set(SDK_LIBS_PATH ${CMAKE_SOURCE_DIR}/lib)
link_libraries("-L ${SDK_LIBS_PATH} -lrockchip_mpp")
link_libraries("-L ${SDK_LIBS_PATH} -lrockchip_vpu")

# 包含系统头文件和系统库
include_directories(${SDK_INCLUDE_PATH})
# include_directories(/usr/local/include)

# 源文件
set(OBJS src/camera.c src/mpp_encode.c src/main.c)
set(CMAKE_C_STANDARD 11)
add_executable(main ${OBJS})

2. MPP的基本使用

以下几个数据结构是实现编解码的基本结构,此外还有其它高级功能的封装,像MppTask、MppMeta以及相关的MPI本篇没有记录

(1)MPP的实例封装MppCtx

🏸提供给用户使⽤的MPP实例上下⽂句柄,⽤于指代解码器或编码器实例。使用比较简单,只需要调用mpp_create()和mpp_init()进行创建和初始化即可,如下

c 复制代码
    /* 创建mpp实例 */
    int ret;
    MppCtx ctx;
    MppApi *mpi;
    ret = mpp_create(&ctx, &mpi);
    if (ret) {
        mpp_err("mpp_create failed ret %d\n", ret);
        return -1;
    }

    /* 初始化mpp编解码类型和格式 */
    ret = mpp_init(ctx, MPP_CTX_ENC,MPP_VIDEO_CodingAVC);
    if (ret) {
        mpp_err("mpp_init failed ret %d\n", ret);
        mpp_destroy(ctx);
        return -1;
    }
(2)MPP的编码配置MppEncCfg

🏸 官方文档推荐我们使用MppEncCfg这一封装和对应的接口如:mpp_enc_cfg_set_s32()和mpi->control()来配置编码器。具体的配置项比较多,详细可看官方文档《Rockchip_Developer_Guide_MPP_CN.md》。使用上也比较简单,代码如下

c 复制代码
  /* 调用mpi的控制函数control()配置MPP关键属性 */
    MppEncCfg cfg;
    mpp_enc_cfg_init(&cfg);
    mpp_enc_cfg_set_s32(cfg, "codec:type", MPP_VIDEO_CodingAVC);
    mpp_enc_cfg_set_s32(cfg, "prep:width", 1920);
    mpp_enc_cfg_set_s32(cfg, "prep:height", 1080);
    mpp_enc_cfg_set_s32(cfg, "prep:hor_stride", 1920);
    mpp_enc_cfg_set_s32(cfg, "prep:ver_stride", 1080);
    mpp_enc_cfg_set_s32(cfg, "prep:format", MPP_FMT_YUV420SP);

    // 码率控制参数
    mpp_enc_cfg_set_s32(cfg, "rc:mode", MPP_ENC_RC_MODE_CBR); // 恒定码率模式,适合网络传输
    mpp_enc_cfg_set_s32(cfg, "rc:bps_target", 4000000); // 目标码率4Mbps
    mpp_enc_cfg_set_s32(cfg, "rc:bps_max", 5000000);    // 最大码率5Mbps
    mpp_enc_cfg_set_s32(cfg, "rc:bps_min", 2000000);    // 最小码率2Mbps

    // 设置帧率参数
    mpp_enc_cfg_set_s32(cfg, "rc:fps_in_flex", 0); // 输入帧率是否可变,0为固定
    mpp_enc_cfg_set_s32(cfg, "rc:fps_in_num", 30); // 输入帧率分子
    mpp_enc_cfg_set_s32(cfg, "rc:fps_in_denorm", 1); // 输入帧率分母,即 30/1 = 30fps
    mpp_enc_cfg_set_s32(cfg, "rc:fps_out_flex", 0);
    mpp_enc_cfg_set_s32(cfg, "rc:fps_out_num", 30); // 输出帧率
    mpp_enc_cfg_set_s32(cfg, "rc:fps_out_denorm", 1);

    // 设置H.264特有参数
    mpp_enc_cfg_set_s32(cfg, "h264:profile", 100); // 对应 High profile
    mpp_enc_cfg_set_s32(cfg, "h264:level", 42);   // Level 4.2,支持1080p@30fps
    mpp_enc_cfg_set_s32(cfg, "h264:cabac_en", 1); // 启用CABAC熵编码,压缩率更高
    mpp_enc_cfg_set_s32(cfg, "h264:trans8x8", 1); // 允许8x8变换

    // 使能以上配置
    ret = mpi->control(ctx, MPP_ENC_SET_CFG, cfg);
    if(ret){
        mpp_err("mpi->control failed ret %d\n", ret);
        mpp_enc_cfg_deinit(cfg);
        mpp_destroy(ctx);
        return -1;
    }
(3)内存封装MppBuffer

🏸 在解码过程中,解码图像的缓存通常需要在固定的缓存池⾥进⾏轮转,为了实现这⼀点,MPP在MppBuffer基础之上⼜定义了MppBufferGroup。MppBuffer的使⽤⽅式有两种:常规使⽤⽅式和外部导⼊⽅式,由于⼆者的内存分配⽅式不同,也称为internal模式和external模式。

🏸 MppBufferGroup由MPP内部⽣成和维护。通过mpp_buffer_get和mpp_buffer_put对内存块进⾏申请和释放。
通过官方文档对内存封装的描述,可以发现,mpp提供了这两种内存封装,MppBuffer和MppBufferGroup,对于我们只需要关注MppBufferGroup和mpp_buffer_get()和mpp_buffer_put()来申请和释放,在使用上不需要我们实际操作到内存,均由底层管理和操作

(4)码流封装MppPacket和图像封装MppFrame

🏸MppPacket主要⽤于描述⼀维码流的相关信息,如有效数据的位置与⻓度;

🏸MppFrame主要⽤于定义⼆维图像缓存的相关信息。
关于以上内存、码流和图像的封装,这里只关心在应用时的使用,对于底层如何实现内存管理、码流和图像如何封装,up看了官方文档资料也是一知半解,可能得深究到其底层代码,这已经超出笔者能力范围,这里便简单记录

(5)MPP的编码API(MPI)

🏸MPI(Media Process Interface)是MPP提供给用户的接⼝,⽤于提供硬件编解码功能,以及⼀些必要的相关功能。MPI是通过C结构⾥的函数指针⽅式提供给用户,简单的编码流程如图所示,具体可能看到下面的代码会更好理解一下:

🏸到这里,我们可以发现前面所提到的MppBuffer、MppPacket、MppFrame等,在头文件中实际数据类型都是void *,实际上可以猜测出上层封装隐藏真实数据类型的目的是为了向上层调用提供统一的、标准的数据类型和接口,也增强上层和底层的兼容性。这样对于我们使用mpp来说,实际上避免各种繁琐的内存申请、释放或者配置等操作,只需要按照一个标准化的流程去调用即可。

5. NV12编码成H.264的demo

这里我们可以通过以下命令从摄像头捕获到10s的NV12数据,再通过上面编译出来的官方编码例程编码成h264,在windows上使用PotPlayer播放器播放验证,接着我们在通过mpp提供的api自己实现这个编码过程,也就是第二条命令的效果

shell 复制代码
# 从摄像头录制10s的NV12源数据
gst-launch-1.0 -e v4l2src device=/dev/video31 num-buffers=300 ! video/x-raw,format=NV12,width=1920,height=1080,framerate=30/1 ! filesink location=raw.nv12
# 使用rk官方例程(也就是前面编译mpp /bin下的可执行程序)编码成H264
mpi_enc_test -i ./raw.nv12 -o ./encoded.h264 -w 1920 -h 1080 -f YUV420SP -t h.264

以上是用官方例程进行NV12编码成H264的结果,拷贝输出文件encoded.h264到windows用potplayer播放或者在ubuntu安装vlc播放验证即可

c 复制代码
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <rockchip/mpp_buffer.h>
#include <rockchip/mpp_compat.h>
#include <rockchip/mpp_err.h>
#include <rockchip/mpp_frame.h>
#include <rockchip/mpp_log.h>
#include <rockchip/mpp_meta.h>
#include <rockchip/mpp_packet.h>
#include <rockchip/mpp_rc_api.h>
#include <rockchip/mpp_rc_defs.h>
#include <rockchip/mpp_sys_cfg_st.h>
#include <rockchip/mpp_task.h>
#include <rockchip/rk_mpi_cmd.h>
#include <rockchip/rk_mpi.h>
#include <rockchip/rk_mpp_cfg.h>
#include <rockchip/rk_type.h>
#include <rockchip/rk_vdec_cfg.h>
#include <rockchip/rk_vdec_cmd.h>
#include <rockchip/vpu.h>
#include <rockchip/vpu_api.h>

int main(int argc,char **argv){
    if(argc < 2){
        fprintf(stderr,"Usage:%s [raw file].\n",argv[0]);
        return -1;
    }
    char *filename = argv[1];
    /* 创建mpp实例 */
    int ret;
    MppCtx ctx;
    MppApi *mpi;
    ret = mpp_create(&ctx, &mpi);
    if (ret) {
        mpp_err("mpp_create failed ret %d\n", ret);
        return -1;
    }
    /* 初始化mpp编解码类型和格式 */
    ret = mpp_init(ctx, MPP_CTX_ENC,MPP_VIDEO_CodingAVC);
    if (ret) {
        mpp_err("mpp_init failed ret %d\n", ret);
        mpp_destroy(ctx);
        return -1;
    }

    /* 调用mpi的控制函数control()配置MPP关键属性 */
    MppEncCfg cfg;
    mpp_enc_cfg_init(&cfg);
    mpp_enc_cfg_set_s32(cfg, "codec:type", MPP_VIDEO_CodingAVC);
    mpp_enc_cfg_set_s32(cfg, "prep:width", 1920);
    mpp_enc_cfg_set_s32(cfg, "prep:height", 1080);
    mpp_enc_cfg_set_s32(cfg, "prep:hor_stride", 1920);
    mpp_enc_cfg_set_s32(cfg, "prep:ver_stride", 1080);
    mpp_enc_cfg_set_s32(cfg, "prep:format", MPP_FMT_YUV420SP);
    // 码率控制参数
    mpp_enc_cfg_set_s32(cfg, "rc:mode", MPP_ENC_RC_MODE_CBR); // 恒定码率模式,适合网络传输
    mpp_enc_cfg_set_s32(cfg, "rc:bps_target", 4000000); // 目标码率 4 Mbps
    mpp_enc_cfg_set_s32(cfg, "rc:bps_max", 5000000);    // 最大码率 5 Mbps
    mpp_enc_cfg_set_s32(cfg, "rc:bps_min", 2000000);    // 最小码率 2 Mbps
    // 设置帧率参数
    mpp_enc_cfg_set_s32(cfg, "rc:fps_in_flex", 0); // 输入帧率是否可变,0为固定
    mpp_enc_cfg_set_s32(cfg, "rc:fps_in_num", 30); // 输入帧率分子
    mpp_enc_cfg_set_s32(cfg, "rc:fps_in_denorm", 1); // 输入帧率分母,即 30/1 = 30fps
    mpp_enc_cfg_set_s32(cfg, "rc:fps_out_flex", 0);
    mpp_enc_cfg_set_s32(cfg, "rc:fps_out_num", 30); // 输出帧率
    mpp_enc_cfg_set_s32(cfg, "rc:fps_out_denorm", 1);
    // 设置H.264特有参数
    mpp_enc_cfg_set_s32(cfg, "h264:profile", 100); // 对应 High profile
    mpp_enc_cfg_set_s32(cfg, "h264:level", 42);   // Level 4.2,支持1080p@30fps
    mpp_enc_cfg_set_s32(cfg, "h264:cabac_en", 1); // 启用CABAC熵编码,压缩率更高
    mpp_enc_cfg_set_s32(cfg, "h264:trans8x8", 1); // 允许8x8变换
    // 使能以上配置
    ret = mpi->control(ctx, MPP_ENC_SET_CFG, cfg);
    if(ret){
        mpp_err("mpi->control failed ret %d\n", ret);
        mpp_enc_cfg_deinit(cfg);
        mpp_destroy(ctx);
        return -1;
    }
    /* 读取源文件(这里是一个从摄像头获取的NV12数据) */
    FILE *ifile = fopen(filename,"rb");
    if (!ifile) {
        perror("fopen input file");
        mpp_destroy(ctx);
        return -1;
    }
    /* 计算原文件大小 */
    if(fseek(ifile,0,SEEK_END) < 0){
        perror("fseek");
        fclose(ifile);
        return -1;
    }
    int filesize = ftell(ifile);
    printf("---- %s file size: %d.\n",filename,filesize);
    fseek(ifile,0,SEEK_SET);        //指针复位

    /* 打开/创建输出文件 */
    FILE *ofile = fopen("out.h264", "wb");
    if (!ofile) {
        perror("fopen output file");
        mpp_destroy(ctx);
        return -1;
    }
    //mpp内存管理(这里的类型都是void*,底层实际封装的结构体对于我们不可见,并不是一片buffer)
    MppBufferGroup mpp_buf_grp;     //buffer组
    MppBuffer mpp_buf;   //可以看成用户buffer,用于我们存放待编码的源数据,实际使用也需要用mpp_buffer_get_ptr()获得指向内存/缓冲区的指针
    MppFrame frame;     //存放码流(传入编码器)
    MppPacket packet;   //存放图像(编码器传出)

    int nread = 0;
    int nwrite = 0;
    int total_read = 0;
    int end_read = 0;
    /* 申请一片内存(或者理解成内存池) */
    ret = mpp_buffer_group_get_internal(&mpp_buf_grp, MPP_BUFFER_TYPE_ION);
    if (ret) {
        mpp_err_f("failed to get mpp buffer group ret %d\n", ret);
        return -1;
    }
    ret = mpp_buffer_group_limit_config(mpp_buf_grp,0,0);
    if (ret) {
        mpp_err_f("mpp_buffer_group_limit_config ret %d\n", ret);
        return -1;
    }

    /* 从内存池中获取指定一帧大小的buffer */
    mpp_buffer_get(mpp_buf_grp,&mpp_buf,1920 * 1080 * 3 / 2);
    /* 初始化封装码流的结构体 */
    mpp_frame_init(&frame);
    mpp_frame_set_width(frame, 1920);
    mpp_frame_set_height(frame, 1080);
    mpp_frame_set_hor_stride(frame,1920); 
    mpp_frame_set_ver_stride(frame, 1080); 
    mpp_frame_set_fmt(frame, MPP_FMT_YUV420SP);
    /* 循环读取NV12数据并编码成h.264 */
    while(1){
        /* 读取到用户buffer里 */
        nread = fread(mpp_buffer_get_ptr(mpp_buf),1,1920 * 1080 * 3 / 2,ifile);
        if(nread < 0){
            perror("fread");
            fclose(ifile);
            break;
        }
        if(nread == 0){         //文件结束
            mpp_frame_set_eos(frame,1);
            break;
        }
        mpp_frame_set_eos(frame,0);
        printf("---- nread=%d.\n",nread);
        /* 开始编码NV12数据 */
        mpp_frame_set_buffer(frame, mpp_buf);
        if((ret = mpi->encode_put_frame(ctx,frame)) < 0){
            mpp_log("encode_get_packet occurred error.(%d).\n",ret);
            break;
        }
        /* 循环读取编码后的H.264数据 */
        while(1){
            mpp_packet_init(&packet, NULL, 0);
            ret = mpi->encode_get_packet(ctx,&packet);
            if (ret != MPP_OK) break; 
            if(ret == MPP_OK && packet){
                void *ptr = mpp_packet_get_pos(packet);
                size_t len = mpp_packet_get_length(packet);
                if(len != 0)nwrite = fwrite(ptr,1,len,ofile);
                if (mpp_packet_get_eos(packet))break;
            }
            if(ret == MPP_ERR_TIMEOUT){
                fprintf(stderr,"mpi->encode_get_packet() time out.\n");
                break;
            }
            break;
        }
        mpp_packet_deinit(&packet);
    }
    mpp_frame_deinit(&frame);
    fclose(ofile);
    fclose(ifile);
    mpp_destroy(ctx);
    return 0;
}

🏸运行结果如下,这里使用的源文件还是前面gst命令采集的10s NV12数据

三、高级任务封装MppTask的使用

🏸当MppPacket与MppFrame组成的接⼝⽆法满⾜需求时,需要使⽤MppTask做为⼀个数据容器,来满⾜复杂的输入输出需求。MppTask需要与poll/dequeuer/enqueue接⼝来配合使⽤,对⽐put_packet/get_frame等简单流程接⼝,MppTask的使⽤流程复杂,效率低,是为了满⾜复杂需求的代价。

🏸使用暂略

相关推荐
吉哥机顶盒刷机3 小时前
创维电视刷机通用教程
经验分享·刷机
番茄灭世神3 小时前
空气质量检测仪项目笔记——硬件介绍
stm32·单片机·嵌入式·gd32·国产芯片
嵌入式Linux,4 小时前
逻辑分析仪dump音频实锤排查问题
音视频
asdzx674 小时前
解锁PDF数据:使用免费Python API将PDF转换为Excel
经验分享
NGBQ121384 小时前
Camtasia 2026.0.7.dmg 全解析:Mac 端专业视频编辑工具深度指南
macos·音视频
阿甘编程点滴4 小时前
2026年,我找到了以下8款支持视频变声的配音软件
音视频
云边散步4 小时前
godot2D游戏教程系列二(16)
笔记·学习·音视频
Metaphor6924 小时前
使用 Python 复制 Excel 工作表
经验分享
CWM-183125336394 小时前
士模微CM4101:低噪声精密运算放大器完美替代OP27
嵌入式硬件·音视频