瑞芯微 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…...

【p2p、分布式,区块链笔记 MESH】 论文阅读 Thread/OpenThread Low-Power Wireless Multihop Net
paperauthorThread/OpenThread: A Compromise in Low-Power Wireless Multihop Network Architecture for the Internet of ThingsHyung-Sin Kim, Sam Kumar, and David E. Culler 目录 引言RPL 标准设计目标与架构设计选择与特性shortcomIngs of RPL设计选择的反面影响sImulta…...

go语言map扩容
map是什么? 在Go语言中,map是一种内置的无序key/value键值对的集合,可以根据key在O(1)的时间复杂度内取到value,有点类似于数组或者切片结构,可以把数组看作是一种特殊的map,数组的key为数组的下标&…...
嵌入式面试高频(5)!!!C++语言(嵌入式八股文,嵌入式面经)
一、C有几种传值方式之间的区别 一、值传递(Pass by Value) 机制:创建参数的副本,函数内操作不影响原始数据语法:void func(int x)特点: 数据安全:原始数据不受影响性能开销:需要复…...

GPUCUDA 发展编年史:从 3D 渲染到 AI 大模型时代(上)
目录 文章目录 目录1960s~1999:GPU 的诞生:光栅化(Rasterization)3D 渲染算法的硬件化实现之路 学术界算法研究历程工业界产品研发历程光栅化技术原理光栅化技术的软件实现:OpenGL 3D 渲染管线设计 1. 顶点处理&…...

跨平台资源下载工具:res-downloader 的使用体验
一款基于 Go Wails 的跨平台资源下载工具,简洁易用,支持多种资源嗅探与下载。res-downloader 一款开源免费的下载软件(开源无毒、放心使用)!支持Win10、Win11、Mac系统.支持视频、音频、图片、m3u8等网络资源下载.支持视频号、小程序、抖音、…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析LLP (二)
低层协议(Low Level Protocol, LLP)详细解析 1. 低层协议(Low Level Protocol, LLP)核心特性 包基础 :基于字节的包协议,支持 短包 (32位)和 长包 (可变长度࿰…...

【超详细】英伟达Jetson Orin NX-YOLOv8配置与TensorRT测试
文章主要内容如下: 1、基础运行环境配置 2、Torch-GPU安装 3、ultralytics环境配置 4、Onnx及TensorRT导出详解 5、YOLOv8推理耗时分析 基础库版本:jetpack5.1.3, torch-gpu2.1.0, torchvision0.16.0, ultralytics8.3.146 设备的软件开发包基础信息 需…...

算法练习-回溯
今天开始新的章节,关于算法中回溯法的练习,这部分题目的难度还是比较大的,但是十分锻炼人的思维与思考能力。 处理这类题目首先要注意几个基本点: 1.关于递归出口的设置,这是十分关键的,要避免死循环的产…...

64、js 中require和import有何区别?
在 JavaScript 中,require 和 import 都是用于模块导入的语法,但它们属于不同的模块系统,具有显著的区别: 1. 模块系统不同 require 属于 CommonJS 模块系统(Node.js 默认使用)。 语法:const…...
分布式微服务系统架构第144集:FastAPI全栈开发教育系统
加群联系作者vx:xiaoda0423 仓库地址:https://webvueblog.github.io/JavaPlusDoc/ https://1024bat.cn/ https://github.com/webVueBlog/fastapi_plus https://webvueblog.github.io/JavaPlusDoc/ 使用docker搭建常用开发环境 docker安装mysql docker ru…...