瑞芯微 RK 系列 RK3588 使用 ffmpeg-rockchip 实现 MPP 视频硬件编解码-代码版
前言
在上一篇文章中,我们讲解了如何使用 ffmpeg-rockchip 通过命令来实现 MPP 视频硬件编解码和 RGA 硬件图形加速,在这篇文章,我将讲解如何使用 ffmpeg-rockchip 用户空间库(代码)实现 MPP 硬件编解码。
本文不仅适用于 RK3588,还适用于 RK 家族系列的芯片,具体的细节可查看官方 MPP 文档。
前置条件
本文假设你已经了解或掌握如下知识:
- ffmpeg 用户空间库使用流程
- 视频编解码原理
ffmpeg 的处理流程

上面这张图展示了 ffmpeg 的处理流程:
输入源 -> 解复用 -> 解码成帧 -> 执行各种操作,如缩放、旋转等 -> 编码 -> 复用 -> 输出
使用 ffmpeg-rochip 的好处
传统的使用硬件编解码的开发思路是:使用 ffmpeg 获取视频流,然后用 MPP 库进行硬件编解码,最后再传给 ffmpeg 进行复用,生成容器文件或推流。这样做的缺点是整个开发成本较高,需要学习 ffmpeg,还要学习 MPP库。
而现在有了 ffmpeg-rochip 之后,我们可以省略去学习使用 MPP 库的步骤,因为这个库已经帮我们封装好了 MPP 的功能,我们只需要像之前那样使用 ffmpeg 即可,只需在使用编解码器时换成 xxx_rkmpp,比如 h264_rkmpp。这样做的好处就是大大降低我们的开发学习成本。
编写思路
整个编写思路和我们日常编写 ffmpeg 时的思路是一致的,ffmpeg-rockchip 只是在 ffmpeg 的基础上封装了 MPP 和 RGA 的 api,实现了对应编解码器和过滤器,使得我们可以直接使用 ffmpeg 的 api 就能直接调用 MPP 和 RGA 功能。
下面的 demo,使用 cpp 语言,实现:”读取 MP4 文件,使用 MPP 的 h264 进行硬件解码,再使用 MPP 的 H265 进行硬件编码后输出 output.hevc 文件“的功能。
编写思路如下:
- 初始化各种上下文
- 读取当前目录下的 test.mp4 文件,进行解复用,获取视频流
- 使用 h264_rkmpp 解码器对视频帧进行硬解码
- 将解码后的视频帧使用 hevc_rkmpp 编码器进行硬编码
- 将编码的视频帧写入 output.hevc 文件中
#include <csignal>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
#include <libavutil/pixfmt.h>
}#define MP4_PATH "./test.mp4"
#define OUTPUT_FILENAME "./output.hevc"
#define DECODEC_NAME "h264_rkmpp"
#define ENCODEC_NAME "hevc_rkmpp"static const AVInputFormat *input_format;
static AVStream *video_in_stream;
static int video_in_stream_idx = -1;
static const AVCodec *rk_h264_decodec;
static const AVCodec *rk_hevc_encodec;
static AVCodecContext *rk_decodec_ctx = nullptr;
static AVCodecContext *rk_encodec_ctx = nullptr;
static AVFormatContext *mp4_fmt_ctx = nullptr;
static FILE *ouput_file;
static AVFrame *frame;
static AVPacket *mp4_video_pkt;
static AVPacket *hevc_pkt;static void encode(AVFrame *frame, AVPacket *hevc_pkt, FILE *outfile) {int ret;if (frame)printf("Send frame %3" PRId64 "\n", frame->pts);ret = avcodec_send_frame(rk_encodec_ctx, frame);if (ret < 0) {char errbuf[AV_ERROR_MAX_STRING_SIZE];av_strerror(ret, errbuf, sizeof(errbuf));std::cerr << "Error sending a frame for encoding: " << errbuf << std::endl;exit(1);}while (ret >= 0) {ret = avcodec_receive_packet(rk_encodec_ctx, hevc_pkt);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)return;else if (ret < 0) {char errbuf[AV_ERROR_MAX_STRING_SIZE];av_strerror(ret, errbuf, sizeof(errbuf));std::cerr << "Error during encoding: " << errbuf << std::endl;exit(1);}printf("Write packet %3" PRId64 " (size=%5d)\n", hevc_pkt->pts,hevc_pkt->size);fwrite(hevc_pkt->data, 1, hevc_pkt->size, outfile);av_frame_unref(frame);av_packet_unref(hevc_pkt);}
}static void decode(AVPacket *mp4_video_pkt, AVFrame *frame) {int ret;ret = avcodec_send_packet(rk_decodec_ctx, mp4_video_pkt);if (ret < 0) {char errbuf[AV_ERROR_MAX_STRING_SIZE];av_strerror(ret, errbuf, sizeof(errbuf));std::cerr << "Error sending a frame for decoding: " << errbuf << std::endl;exit(1);}while (ret >= 0) {ret = avcodec_receive_frame(rk_decodec_ctx, frame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {return;} else if (ret < 0) {char errbuf[AV_ERROR_MAX_STRING_SIZE];av_strerror(ret, errbuf, sizeof(errbuf));std::cerr << "Error during decoding: " << errbuf << std::endl;exit(1);}encode(frame, hevc_pkt, ouput_file);}
}int main(int argc, char **argv) {int ret;input_format = av_find_input_format("mp4");if (!input_format) {std::cerr << "Could not find input format" << std::endl;return EXIT_FAILURE;}// 分配一个AVFormatContext。mp4_fmt_ctx = avformat_alloc_context();if (!mp4_fmt_ctx) {std::cerr << "Could not allocate format context" << std::endl;return EXIT_FAILURE;}// 打开输入流并读取头部信息。此时编解码器尚未开启。if (avformat_open_input(&mp4_fmt_ctx, MP4_PATH, input_format, nullptr) < 0) {std::cerr << "Could not open input" << std::endl;return EXIT_FAILURE;}// 读取媒体文件的数据包以获取流信息。if (avformat_find_stream_info(mp4_fmt_ctx, nullptr) < 0) {std::cerr << "Could not find stream info" << std::endl;return EXIT_FAILURE;}// 打印视频信息av_dump_format(mp4_fmt_ctx, 0, MP4_PATH, 0);// 查找视频流if ((ret = av_find_best_stream(mp4_fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1,nullptr, 0)) < 0) {std::cerr << "Could not find video stream" << std::endl;return EXIT_FAILURE;}video_in_stream_idx = ret;video_in_stream = mp4_fmt_ctx->streams[video_in_stream_idx];std::cout << "video_in_stream->duration: " << video_in_stream->duration<< std::endl;const char *filename = OUTPUT_FILENAME;int i = 0;// 查找解码器rk_h264_decodec = avcodec_find_decoder_by_name(DECODEC_NAME);if (!rk_h264_decodec) {std::cerr << "Codec '" << DECODEC_NAME << "' not found" << std::endl;exit(1);}rk_decodec_ctx = avcodec_alloc_context3(rk_h264_decodec);if (!rk_decodec_ctx) {std::cerr << "Could not allocate video rk_h264_decodec context"<< std::endl;exit(1);}// 将视频参数复制到rk_h264_decodec上下文中。if (avcodec_parameters_to_context(rk_decodec_ctx, video_in_stream->codecpar) <0) {std::cerr << "Could not copy video parameters to rk_h264_decodec context"<< std::endl;exit(1);}AVDictionary *opts = NULL;av_dict_set_int(&opts, "buf_mode", 1, 0);if (avcodec_open2(rk_decodec_ctx, rk_h264_decodec, &opts) < 0) {std::cerr << "Could not open rk_h264_decodec" << std::endl;exit(1);}// 查找编码器rk_hevc_encodec = avcodec_find_encoder_by_name(ENCODEC_NAME);if (!rk_hevc_encodec) {std::cerr << "Codec '" << ENCODEC_NAME << "' not found" << std::endl;exit(1);}rk_encodec_ctx = avcodec_alloc_context3(rk_hevc_encodec);if (!rk_encodec_ctx) {std::cerr << "Could not allocate video rk_hevc_encodec context"<< std::endl;exit(1);}// 设置编码器参数rk_encodec_ctx->width = video_in_stream->codecpar->width;rk_encodec_ctx->height = video_in_stream->codecpar->height;rk_encodec_ctx->pix_fmt = AV_PIX_FMT_NV12;rk_encodec_ctx->time_base = video_in_stream->time_base;rk_encodec_ctx->framerate = video_in_stream->r_frame_rate;rk_encodec_ctx->gop_size = 50;rk_encodec_ctx->bit_rate = 1024 * 1024 * 10;av_opt_set(rk_encodec_ctx->priv_data, "profile", "main", 0);av_opt_set(rk_encodec_ctx->priv_data, "qp_init", "23", 0);av_opt_set_int(rk_encodec_ctx->priv_data, "rc_mode", 0, 0);ret = avcodec_open2(rk_encodec_ctx, rk_hevc_encodec, nullptr);if (ret < 0) {std::cerr << "Could not open rk_hevc_encodec: " << std::endl;exit(1);}mp4_video_pkt = av_packet_alloc();if (!mp4_video_pkt)exit(1);hevc_pkt = av_packet_alloc();if (!hevc_pkt)exit(1);ouput_file = fopen(filename, "wb");if (!ouput_file) {std::cerr << "Could not open " << filename << std::endl;exit(1);}frame = av_frame_alloc();if (!frame) {std::cerr << "Could not allocate video frame" << std::endl;exit(1);}while (true) {ret = av_read_frame(mp4_fmt_ctx, mp4_video_pkt);if (ret < 0) {std::cerr << "Could not read frame" << std::endl;break;}if (mp4_video_pkt->stream_index == video_in_stream_idx) {std::cout << "mp4_video_pkt->pts: " << mp4_video_pkt->pts << std::endl;decode(mp4_video_pkt, frame);}av_packet_unref(mp4_video_pkt);i++;}// 确保将所有帧写入av_packet_unref(mp4_video_pkt);decode(mp4_video_pkt, frame);encode(nullptr, mp4_video_pkt, ouput_file);fclose(ouput_file);avcodec_free_context(&rk_encodec_ctx);avformat_close_input(&mp4_fmt_ctx);avformat_free_context(mp4_fmt_ctx);av_frame_free(&frame);av_packet_free(&mp4_video_pkt);av_packet_free(&hevc_pkt);return 0;
}
将上面的代码放入 main.cpp 中,将 test.mp4 文件放入当前目录,在开发板中运行如下命令编译并运行:
g++ -o main main.cpp -lavformat -lavcodec -lavutil./main
确保你的 rk 开发板环境中有 ffmpeg-rockchip 库,如果没有的可以参考我上篇文章的编译教程:《瑞芯微 RK 系列 RK3588 使用 ffmpeg-rockchip 实现 MPP 硬件编解码和 RGA 图形加速-命令版》
查看 VPU 的运行情况,如下说明成功使用了硬件编解码功能。如果不知道怎么查看 VPU 的运行情况,可以参考我这篇文章:《瑞芯微 RK 系列 RK3588 CPU、GPU、NPU、VPU、RGA、DDR 状态查看与操作》。

优化点
以上的代码示例有个缺点,就是解码时会将视频帧上传到 VPU,之后传回内存,编码时又上传到 VPU,编码后再传回内存。这样就造成了不必要的数据拷贝,我们可以将视频帧解码之后,在 VPU 编码后再传回内存,提高编解码效率。
实现方案是使用 hw_device_ctx 上下文,由于篇幅问题,这里不给出代码示例。有需要的小伙伴可以在评论区回复或直接私聊我。
结语
本篇文章介绍了如何使用 ffmpeg-rockchip 进行 MPP 硬件编解码,在下一篇文章,我将介绍如何使用 ffmpeg-rockchip 使用 RGA 2D 图形加速,RGA 可以实现图像缩放、旋转、bitBlt、alpha混合等常见的2D图形操作。
如果觉得本文写得不错,请麻烦帮忙点赞、收藏、转发,你的支持是我继续写作的动力。我是 Leon_Chenl,我们下篇文章见~
相关文章:
瑞芯微 RK 系列 RK3588 使用 ffmpeg-rockchip 实现 MPP 视频硬件编解码-代码版
前言 在上一篇文章中,我们讲解了如何使用 ffmpeg-rockchip 通过命令来实现 MPP 视频硬件编解码和 RGA 硬件图形加速,在这篇文章,我将讲解如何使用 ffmpeg-rockchip 用户空间库(代码)实现 MPP 硬件编解码。 本文不仅适…...
uniapp 预加载分包,减少loading
在 uniapp 中,可以通过配置 pages.json 文件中的 preloadRule 属性来实现页面预加载功能。以下是具体操作步骤: 1. 在 pages.json 中配置 preloadRule preloadRule 用于指定哪些页面需要预加载,以及预加载时机。下面是一个示例配置…...
c#删除文件和目录到回收站
之前在c上遇到过这个问题,折腾许久才解决了,这次在c#上再次遇到这个问题,不过似乎容易了一些,亲测代码如下,两种删除方式都写在代码中了。 直接上完整代码: using Microsoft.VisualBasic.FileIO; using Sy…...
GESP2024年12月认证C++六级( 第三部分编程题(1)树上游走)
参考程序: #include <iostream> #include <string>using namespace std;int main() {long long n, s; // n为移动次数,s为初始节点编号string moves; // 移动指令串// 输入处理cin >> n >> s;cin >> moves;long long…...
Redis数据结构服务器
Redis数据结构服务器 什么是Redis数据结构服务器 的概念和特点 是一个开源(BSD许可),内存中的数据结构存储服务器,可用作数据库、缓存和消息中间件。它支持多种类型的数据结构,如字符串(strings)…...
【向量数据库 Milvus】centos8源码安装和部署 Milvus 2.5.3
在龙晰操作系统(LoongArch 架构)的 CentOS 8 环境中通过源码安装和部署 Milvus 2.5.3 可能会面临一些挑战,因为 Milvus 的官方支持主要集中在 x86 和 ARM 架构上。以下是一个详细的安装步骤,但需要注意,某些依赖项可能…...
MySQL数据库(SQL分类)
SQL分类 分类全称解释DDLData Definition Language数据定义语言,用来定义数据库对象(数据库,表,字段)DMLData Manipulation Language数据操作语言,用来对数据库表中的数据进行增删改DQLData Query Languag…...
C++实现设计模式---原型模式 (Prototype)
原型模式 (Prototype) 原型模式 是一种创建型设计模式,它通过复制现有对象来创建新对象,而不是通过实例化。 意图 使用原型实例指定要创建的对象类型,并通过复制该原型来生成新对象。提供一种高效创建对象的方式,尤其是当对象的…...
鸿蒙面试 2025-01-10
写了鉴权工具,你在项目中申请了那些权限?(常用权限) 位置权限 : ohos.permission.LOCATION_IN_BACKGROUND:允许应用在后台访问位置信息。 ohos.permission.LOCATION:允许应用访问精确的位置信息…...
Linux Top 命令 load average 指标解读
前言 作为平台开发的同学,维护平台稳定性是我们最基本的工作职责,下面主要介绍下top 命令里 ,load average 这个指标如何去衡量机器负载程度。 概念介绍 load average 是系统在过去 1 分钟、5 分钟、15 分钟 的平均负载,它表示运…...
31_搭建Redis分片集群
Redis的主从复制模式和哨兵模式可以解决高可用、高并发读的问题。但是依然有两个问题没有解决:海量数据存储问题、高并发写的问题。由于数据量过大,单个master复制集难以承担,因此需要对多个复制集进行集群,形成水平扩展每个复制集只负责存储整个数据集的一部分,这就是Red…...
客户案例 | Ansys与索尼半导体解决方案公司合作推进自动驾驶汽车基于场景的感知测试
该合作使OEM厂商和一级供应商能够可靠地评估和验证 ADAS/AV 功能在各种天气和照明条件下的性能 主要亮点 Ansys AVxcelerate Sensors™自动驾驶汽车(AV)传感器仿真软件,可实现面向基于场景的感知测试的实时多光谱摄像头仿真 利用AVxcelerat…...
c#-Halcon入门教程——标定
Halcon代码 read_image (NinePointCalibration, D:/Desktop/halcon/ca74d-main/九点标定/NinePointCalibration.gif)rgb1_to_gray (NinePointCalibration, GrayImage)get_image_size (GrayImage, Width, Height) dev_display (GrayImage)* 获取当前显示的窗口句柄 dev_get_win…...
MC1.12.2 macOS高清修复OptiFine运行崩溃
最近在玩RLCraft,在windows中运行正常的,移植到macOS中发现如果加载OptiFine模组就会崩溃 报错日志 报错日志如下,其中已经包含了各种版本信息,我就不单独说明了。这里说一下,报错的时候用的是oracle jdk x64的&…...
精选2款.NET开源的博客系统
前言 博客系统是一个便于用户创建、管理和分享博客内容的在线平台,今天大姚给大家分享2款.NET开源的博客系统。 StarBlog StarBlog是一个支持Markdown导入的开源博客系统,后端基于最新的.Net6和Asp.Net Core框架,遵循RESTFul接口规范&…...
转运机器人在物流仓储行业的优势特点
在智能制造与智慧物流的浪潮中,一款革命性的产品正悄然改变着行业的面貌——富唯智能转运机器人,它以卓越的智能科技与创新的设计理念,引领着物流领域步入一个全新的高效、智能、无人的时代。 一、解放双手,重塑物流生态 富唯智能…...
简识MySQL的InnoDB Locking锁的分类
( 参考官方网页: MySQL :: MySQL 5.7 Reference Manual :: 14.7.1 InnoDB Locking) 一、InnoDB Locking锁的分类: 锁的分类英文缩写共享锁Shared LocksS排他锁Exclusive LocksX意向共享锁Intention Shared LocksIS意向排他锁Int…...
如何通过openssl生成.crt和.key
生成 .crt(证书文件)和 .key(私钥文件)的过程通常涉及使用加密工具或库来创建密钥对,并生成证书请求,最终由证书颁发机构(CA)或自签名生成证书。以下是生成 .crt 和 .key 文件的详细…...
.NetCore 使用 NPOI 读取带有图片的excel数据
在.NetCore使用NPOI插件进行批量导入时,获取Excel中的所有的图片数据,存到集合中。 1.定义类PictureData 代码如下: public class PictureData { public byte[] Data { get; set; } } 2.数据集引用 using NPOI.XSSF.UserModel; usin…...
linux上使用update-alternatives来选择软件版本
比如我在linux系统上安装多个版本的gcc /usr/local/gcc-4.8.2/ /usr/local/gcc-8.4.0/ /usr/local/gcc-9.4.0/我要根据需要来切换系统环境下的gcc命令的版本,我可以先 update-alternatives --install /usr/bin/gcc gcc /usr/local/gcc-4.8.2/bin/gcc 1 update-alt…...
简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...
CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...
1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...
Spring数据访问模块设计
前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...
基于matlab策略迭代和值迭代法的动态规划
经典的基于策略迭代和值迭代法的动态规划matlab代码,实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...
Java编程之桥接模式
定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的核心意图是将抽象部分与实现部分分离,使它们可以独立地变化。这种模式通过组合关系来替代继承关系,从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...
(一)单例模式
一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...
苹果AI眼镜:从“工具”到“社交姿态”的范式革命——重新定义AI交互入口的未来机会
在2025年的AI硬件浪潮中,苹果AI眼镜(Apple Glasses)正在引发一场关于“人机交互形态”的深度思考。它并非简单地替代AirPods或Apple Watch,而是开辟了一个全新的、日常可接受的AI入口。其核心价值不在于功能的堆叠,而在于如何通过形态设计打破社交壁垒,成为用户“全天佩戴…...
