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

前言

在上一篇文章中,我们讲解了如何使用 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.rhkb.cn/news/1055.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

快速、可靠且高性价比的定制IP模式提升芯片设计公司竞争力

作者&#xff1a;Karthik Gopal&#xff0c;SmartDV Technologies亚洲区总经理 智权半导体科技&#xff08;厦门&#xff09;有限公司总经理 无论是在出货量巨大的消费电子市场&#xff0c;还是针对特定应用的细分芯片市场&#xff0c;差异化芯片设计带来的定制化需求也在芯片…

【ARM】MDK如何将变量存储到指定内存地址

1、 文档目标 通过MDK的工程配置&#xff0c;将指定的变量存储到指定的内存地址上。 2、 问题场景 在项目工程的开发过程中&#xff0c;对于flash要进行分区&#xff0c;需要规划出一个特定的内存区域来存储变量。 3、软硬件环境 1&#xff09;、软件版本&#xff1a;MDK 5.…

Mysql--运维篇--空间管理(表空间,索引空间,临时表空间,二进制日志,数据归档等)

MySQL的空间管理是指对数据库存储资源的管理和优化。确保数据库能够高效地使用磁盘空间、内存和其他系统资源。良好的空间管理不仅有助于提高数据库的性能&#xff0c;还能减少存储成本并防止因磁盘空间不足导致的服务中断。MySQL的空间管理涉及多个方面&#xff0c;包括表空间…

Compose 的集成与导航

首先我们来看如何在 View 体系中集成 Compose。 1、迁移策略 Codelab 给出了从 View 迁移到 Compose 的策略&#xff0c;以下内容基本上来自该 Codelab。 Jetpack Compose 从设计之初就考虑到了 View 互操作性。如需迁移到 Compose&#xff0c;我们建议您执行增量迁移&#…

蓝桥杯备考:数据结构之栈 和 stack

目录 栈的概念以及栈的实现 STL 的stack 栈和stack的算法题 栈的模板题 栈的算法题之有效的括号 验证栈序列 后缀表达式 括号匹配 栈的概念以及栈的实现 栈是一种只允许在一端进行插入和删除的线性表 空栈&#xff1a;没有任何元素 入栈&#xff1a;插入元素消息 出…

gesp(C++五级)(1)洛谷:B3941:[GESP样题 五级] 小杨的锻炼

gesp(C五级)&#xff08;1&#xff09;洛谷&#xff1a;B3941&#xff1a;[GESP样题 五级] 小杨的锻炼 题目描述 小杨的班级里共有 n n n 名同学&#xff0c;每位同学都有各自的锻炼习惯。具体来说&#xff0c;第 i i i 位同学每隔 a i a_i ai​ 天就会进行一次锻炼&#x…

MIUI显示/隐藏5G开关的方法,信号弱时开启手机Wifi通话方法

5G网速虽快&#xff0c;手机功耗也大。 1.取消MIUI强制的5G&#xff0c;手动设置4G的方法&#xff01; 【小米澎湃OS, Xiaomi HyperOS显示/隐藏5G开关的方法】 1.1.小米MIUI系统升级后&#xff0c;被强制连5G&#xff0c;手动设置开关被隐藏&#xff0c;如下图&#xff1a; 1…

Gateway 网关

1.Spring Cloud Gateway Spring cloud gateway是spring官方基于Spring 5.0、Spring Boot2.0和Project Reactor等技术开发的网关&#xff0c;Spring Cloud Gateway旨在为微服务架构提供简单、有效和统一的API路由管理方式&#xff0c;Spring Cloud Gateway作为Spring Cloud生态…

python 轮廓 获取环形区域

目录 效果图&#xff1a; 代码&#xff1a; 效果图&#xff1a; 代码&#xff1a; import cv2 import numpy as np# 读取图像 image cv2.imread(rE:\project\jijia\tools_jijia\img_tools\ground_mask.jpg, cv2.IMREAD_GRAYSCALE) # 二值化图像 # 二值化图像 _, binary cv…

MySQL主从复制

文章目录 1.主从复制1.1 概念和原理1.2 案例&#xff1a;一主一从1&#xff09;准备工作2&#xff09;master3&#xff09;slave4&#xff09;测试 1.主从复制 1.1 概念和原理 1.2 案例&#xff1a;一主一从 1&#xff09;准备工作 同步时间 # 安装 ntpdate yum -y install…

网络应用技术 实验七:实现无线局域网

一、实验简介 在 eNSP 中构建无线局域网&#xff0c;并实现全网移动终端互相通信。 二、实验目的 1 、理解无线局域网的工作原理&#xff1b; 2 、熟悉无线局域网的规划与构建过程&#xff1b; 3 、掌握无线局域网的配置方法&#xff1b; 三、实验学时 2 学时 四、实…

51c大模型~合集104

我自己的原文哦~ https://blog.51cto.com/whaosoft/13076849 #Deepfake Detection ACM Computing Surveys | 港大等基于可靠性视角的深度伪造检测综述&#xff0c;覆盖主流基准库、模型 本文作者包括香港大学的王天一、Kam Pui Chow&#xff0c;湖南大学的廖鑫 (共同通讯…

人工智能实验(四)-A*算法求解迷宫寻路问题实验

零、A*算法学习参考资料 1.讲解视频 A*寻路算法详解 #A星 #启发式搜索_哔哩哔哩_bilibili 2.A*算法学习网站 A* 算法简介 一、实验目的 熟悉和掌握A*算法实现迷宫寻路功能&#xff0c;要求掌握启发式函数的编写以及各类启发式函数效果的比较。 二、实验要求 同课本 附录…

Web开发(一)HTML5

Web开发&#xff08;一&#xff09;HTML5 写在前面 参考黑马程序员前端Web教程做的笔记&#xff0c;主要是想后面自己搭建网页玩。 这部分是前端HTML5CSS3移动web视频教程的HTML5部分。主要涉及到HTML的基础语法。 HTML基础 标签定义 HTML定义 HTML(HyperText Markup Lan…

LabVIEW水位监控系统

LabVIEW开发智能水位监控系统通过集成先进的传感技术与控制算法&#xff0c;为工业液体存储提供精确的水位调控&#xff0c;保证了生产过程的连续性与安全性。 项目背景 在化工和饮料生产等行业中&#xff0c;水位控制的准确性对保证生产安全和提高产品质量至关重要。传统的水…

【Rust】结构体定义域实例化

目录 思维导图 1. 结构体的定义与实例化 1.1 结构体的基本概念 1.2 定义结构体 1.3 创建结构体实例 1.4 结构体的定义与实例化示例 2. 访问与修改结构体字段 2.1 访问字段 2.2 修改字段 3. 结构体实例的构造函数 3.1 构造函数的定义 3.2 使用字段初始化简写 4. 结…

013:深度学习之神经网络

本文为合集收录&#xff0c;欢迎查看合集/专栏链接进行全部合集的系统学习。 合集完整版请参考这里。 深度学习是机器学习中重要的一个学科分支&#xff0c;它的特点就在于需要构建多层且“深度”的神经网络。 人们在探索人工智能初期&#xff0c;就曾设想构建一个用数学方式…

Java 将RTF文档转换为Word、PDF、HTML、图片

RTF文档因其跨平台兼容性而广泛使用&#xff0c;但有时在不同的应用场景可能需要特定的文档格式。例如&#xff0c;Word文档适合编辑和协作&#xff0c;PDF文档适合打印和分发&#xff0c;HTML文档适合在线展示&#xff0c;图片格式则适合社交媒体分享。因此我们可能会需要将RT…

【2024年华为OD机试】(C卷,100分)- 攀登者1 (Java JS PythonC/C++)

一、问题描述 题目描述 攀登者喜欢寻找各种地图&#xff0c;并且尝试攀登到最高的山峰。 地图表示为一维数组&#xff0c;数组的索引代表水平位置&#xff0c;数组的元素代表相对海拔高度。其中数组元素0代表地面。 例如&#xff1a;[0,1,2,4,3,1,0,0,1,2,3,1,2,1,0]&…

day06_Spark SQL

文章目录 day06_Spark SQL课程笔记一、今日课程内容二、DataFrame详解&#xff08;掌握&#xff09;5.清洗相关的API6.Spark SQL的Shuffle分区设置7.数据写出操作写出到文件写出到数据库 三、Spark SQL的综合案例&#xff08;掌握&#xff09;1、常见DSL代码整理2、电影分析案例…