瑞芯微 RK 系列 RK3588 使用 ffmpeg-rockchip 实现 MPP 视频硬件编解码-代码版

article/2025/7/28 15:04:07

前言

在上一篇文章中,我们讲解了如何使用 ffmpeg-rockchip 通过命令来实现 MPP 视频硬件编解码和 RGA 硬件图形加速,在这篇文章,我将讲解如何使用 ffmpeg-rockchip 用户空间库(代码)实现 MPP 硬件编解码。

本文不仅适用于 RK3588,还适用于 RK 家族系列的芯片,具体的细节可查看官方 MPP 文档。

前置条件

本文假设你已经了解或掌握如下知识:

  • ffmpeg 用户空间库使用流程
  • 视频编解码原理

ffmpeg 的处理流程

image.png

上面这张图展示了 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 文件“的功能。

编写思路如下:

  1. 初始化各种上下文
  2. 读取当前目录下的 test.mp4 文件,进行解复用,获取视频流
  3. 使用 h264_rkmpp 解码器对视频帧进行硬解码
  4. 将解码后的视频帧使用 hevc_rkmpp 编码器进行硬编码
  5. 将编码的视频帧写入 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 状态查看与操作》。

image.png

优化点

以上的代码示例有个缺点,就是解码时会将视频帧上传到 VPU,之后传回内存,编码时又上传到 VPU,编码后再传回内存。这样就造成了不必要的数据拷贝,我们可以将视频帧解码之后,在 VPU 编码后再传回内存,提高编解码效率。

实现方案是使用 hw_device_ctx 上下文,由于篇幅问题,这里不给出代码示例。有需要的小伙伴可以在评论区回复或直接私聊我。

结语

本篇文章介绍了如何使用 ffmpeg-rockchip 进行 MPP 硬件编解码,在下一篇文章,我将介绍如何使用 ffmpeg-rockchip 使用 RGA 2D 图形加速,RGA 可以实现图像缩放、旋转、bitBlt、alpha混合等常见的2D图形操作。

如果觉得本文写得不错,请麻烦帮忙点赞、收藏、转发,你的支持是我继续写作的动力。我是 Leon_Chenl,我们下篇文章见~


http://www.hkcw.cn/article/EVUtJwcoWu.shtml

相关文章

【计算机视觉】OpenCV实战项目:基于OpenCV的车牌识别系统深度解析

基于OpenCV的车牌识别系统深度解析 1. 项目概述2. 技术原理与算法设计2.1 图像预处理1) 自适应光照补偿2) 边缘增强 2.2 车牌定位1) 颜色空间筛选2) 形态学操作3) 轮廓分析 2.3 字符分割1) 投影分析2) 连通域筛选 2.4 字符识别 3. 实战部署指南3.1 环境配置3.2 项目代码解析 4.…

2024电赛H题参考方案(+视频演示+核心控制代码)——自动行驶小车

目录 一、题目要求 二、参考资源获取 三、TI板子可能用到的资源 1、环境搭建及工程移植 2、相关模块的移植 四、控制参考方案 1、整体控制方案视频演示 2、视频演示部分核心代码 五、总结 一、题目要求 小编自认为&#xff1a;此次控制类类型题目的H题&#xff0c;相较于往年较…

【开源工具】PyQt6录音神器:高颜值多功能音频录制工具开发全解析

【开源工具】&#x1f399;️ PyQt6录音神器&#xff1a;高颜值多功能音频录制工具开发全解析 &#x1f308; 个人主页&#xff1a;创客白泽 - CSDN博客 &#x1f525; 系列专栏&#xff1a;&#x1f40d;《Python开源项目实战》 &#x1f4a1; 热爱不止于代码&#xff0c;热情…

在PPT中同时自动播放多个视频的方法

在PPT中同时自动播放多个视频的方法 文章目录 在PPT中同时自动播放多个视频的方法1 准备视频2 设置动画为“出现”3 设置所有视频为“自动播放”4 最终效果与其他设置 在PPT制作的过程中&#xff0c;我们经常遇到需要同时自动播放多个视频的情况。本文将详细介绍实现这种效果的…

【智能驱蚊黑科技】基于OpenCV的蚊子雷达追踪打击系统(附完整Python源码)

【智能驱蚊黑科技】基于OpenCV的蚊子雷达追踪打击系统&#xff08;附完整Python源码&#xff09; &#x1f308; 个人主页&#xff1a;创客白泽 - CSDN博客 &#x1f525; 系列专栏&#xff1a;&#x1f40d;《Python开源项目实战》 &#x1f4a1; 热爱不止于代码&#xff0c;热…

打造沉浸式古诗欣赏页面:HTML5视频背景与音频的完美结合

个人名片 &#x1f393;作者简介&#xff1a;java领域优质创作者 &#x1f310;个人主页&#xff1a;码农阿豪 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务&#xff09; &#x1f48c;个人邮箱&#xff1a;[2435024119qq.com] &#x1f4f1…

Python - 爬虫;Scrapy框架之插件Extensions(四)

阅读本文前先参考 https://blog.csdn.net/MinggeQingchun/article/details/145904572 在 Scrapy 中&#xff0c;扩展&#xff08;Extensions&#xff09;是一种插件&#xff0c;允许你添加额外的功能到你的爬虫项目中。这些扩展可以在项目的不同阶段执行&#xff0c;比如启动…

vscode 连接远程服务器

文章目录 1. 背景2. vscode 连接 服务器步骤2.1 安装 remote-ssh 插件2.2 配置 ssh 秘钥2.3 连接 server vscode 连接远程服务器 1. 背景 有服务器的同学&#xff0c;或许都有这样的感觉&#xff0c;服务器是 linux 系统&#xff0c;且只给个人提供一个终端进行连接&#xff0c…

JavaScript 模块系统:CJS/AMD/UMD/ESM

文章目录 前言一、CommonJS (CJS) - Node.js 的同步模块系统1.1 设计背景1.2 浏览器兼容性问题1.3 Webpack 如何转换 CJS1.4 适用场景 二、AMD (Asynchronous Module Definition) - 浏览器异步加载方案2.1 设计背景2.2 为什么现代浏览器不原生支持 AMD2.3 Webpack/Rollup 如何处…

乌称摧毁34%俄远程机队 俄媒否认 谎言蛛网行动

俄罗斯“与假新闻作战”网站发布文章称,通过分析乌克兰方面发布的视频可以确认,乌总统泽连斯基关于“已摧毁34%俄罗斯远程机队”的说法并不属实。俄方认为,乌克兰实际上可能仅摧毁了两架图-95战略轰炸机及一架安-12运输机,其余受损飞机在维修后均可恢复作战能力。乌克兰国家…

加沙停火协议为何一波三折 美斡旋遇阻

本周,美国就巴勒斯坦伊斯兰抵抗运动(哈马斯)和以色列的停火展开斡旋,提出一项为期60天的加沙地带停火方案。然而,围绕是否接受这份方案,哈马斯和以色列的态度不一,谈判频频出现变数。美国白宫5月29日表示,以色列已接受并签署美国提出的加沙地带临时停火方案。但该方案在…

基于springboot的宠物领养系统

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;没有什么华丽的语言&#xff0…

中国王朝简史

文章目录 一、先秦时期&#xff1a;文明起点与制度雏形夏&#xff08;约前2070年–前1600年&#xff09;商&#xff08;约前1600年–前1046年&#xff09;周&#xff08;前1046年–前256年&#xff09; 二、大一统帝国的试验与成熟秦&#xff08;前221年–前207年&#xff09;汉…

Freefilesync配置windows与windows,windows与linux之间同步

说明 Freefilesync&#xff1a;用于windows与windows&#xff0c;windows与linux之间同步 linux 之间同步&#xff0c;使用系统的自带的 corn 软件&#xff0c;执行 sync 命名的脚本即可 一 、下载Freefilesync windows服务器上打开官网 https://freefilesync.org/&#xff0…

数字创新智慧园区建设及运维方案

该文档是 “数字创新智慧园区” 建设及运维方案,指出传统产业园区存在管理粗放等问题,“数字创新园区” 通过大数据、AI、物联网、云计算等数字化技术,旨在提升园区产业服务、运营管理水平,增强竞争力,实现绿色节能、高效管理等目标。建设内容包括智能设施、核心支撑平台、…

P1541 [NOIP 2010 提高组] 乌龟棋

P1541 [NOIP 2010 提高组] 乌龟棋 - 洛谷 题目背景 NOIP2010 提高组 T2 题目描述 小明过生日的时候&#xff0c;爸爸送给他一副乌龟棋当作礼物。 乌龟棋的棋盘是一行 N 个格子&#xff0c;每个格子上一个分数&#xff08;非负整数&#xff09;。棋盘第 1 格是唯一的起点&a…

设计模式——享元设计模式(结构型)

摘要 享元设计模式是一种结构型设计模式&#xff0c;旨在通过共享对象减少内存占用和提升性能。其核心思想是将对象状态分为内部状态&#xff08;可共享&#xff09;和外部状态&#xff08;不可共享&#xff09;&#xff0c;并通过享元工厂管理共享对象池。享元模式包含抽象享…

Qt OpenGL编程常用类

Qt提供了丰富的类来支持OpenGL编程&#xff0c;以下是常用的Qt OpenGL相关类&#xff1a; 一、QOpenGLWidget 功能&#xff1a;用于在 Qt 应用程序中嵌入 OpenGL 渲染的窗口部件。替代了旧版的QGLWidget。提供了OpenGL上下文和渲染表面。 继承关系&#xff1a;QWidget → QOp…

【JMeter】性能测试知识和工具

目录 何为系统性能 何为性能测试 性能测试分类 性能测试指标 性能测试流程 性能测试工具&#xff1a;JMeter&#xff08;主测web应用&#xff09; jmeter文件目录 启动方式 基本元件&#xff1a;元件内有很多组件 jmeter参数化 jmeter关联 自动录制脚本 直连数据库…

[Linux] nginx源码编译安装

初次学习&#xff0c;如有错误欢迎指正 目录 环境包部署 创建程序用户 软件包压缩 配置 编译 安装 建立快捷启动 启动nginx&#xff1f; 防火墙管理 查看规则 清空规则 关闭服务 开启服务 查看状态 开机自启 开机禁用 查看开机启动状态 nginx&#xff0c;启…