瑞芯微 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,我们下篇文章见~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/954072.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Element Plus 之 el-table相同行合并(通用函数),相同列合并(自行判断需合并的字段以及相应的列下标)

展示 代码 <el-table :data"tableData" border style"width: 100%" :span-method"objectSpanMethod"><el-table-column prop"date" label"Date" width"180" align"center" /><el-table…

深入理解计算机系统阅读笔记-第十二章

第12章 网络编程 12.1 客户端-服务器编程模型 每个网络应用都是基于客户端-服务器模型的。根据这个模型&#xff0c;一个应用时由一个服务器进程和一个或者多个客户端进程组成。服务器管理某种资源&#xff0c;并且通过操作这种资源来为它的客户端提供某种服务。例如&#xf…

ubuntu20.04安装MySQL5.7

deb安装 下载deb文件并配置 wget https://repo.mysql.com//mysql-apt-config_0.8.12-1_all.deb sudo dpkg -i mysql-apt-config_0.8.12-1_all.deb我使用xshell可以正常。 这个弹出框里&#xff0c;选择的是“ubuntu bionic”。(在终端工具上&#xff0c;有可能显示不了选项)【…

CSS | CSS实现两栏布局(左边定宽 右边自适应,左右成比自适应)

目录 一、左边定宽 右边自适应 1.浮动 2.利用浮动margin 3.定位margin 4.flex布局 5.table 布局 二、左右成比自适应 1:1 1flex布局 table布局 1:2 flex布局 三列布局链接&#xff1a;CSS | 实现三列布局&#xff08;两边边定宽 中间自适应&#xff0c;自适应成比&#xff09;-…

Win10微调大语言模型ChatGLM2-6B

在《Win10本地部署大语言模型ChatGLM2-6B-CSDN博客》基础上进行&#xff0c;官方文档在这里&#xff0c;参考了这篇文章 首先确保ChatGLM2-6B下的有ptuning AdvertiseGen下载地址1&#xff0c;地址2&#xff0c;文件中数据留几行 模型文件下载地址 &#xff08;注意&#xff1…

Java中异常的学习

目录 Java 异常概述 异常的抛出机制 异常的解决方法(处理机制) Java异常体系结构 常见的异常 异常处理 try catch finally throws throw 运行期异常和编译期异常 自定义异常 Java 异常概述 在使用计算机语言进行项目开发的过程中&#xff0c;即使程序员把代码写得尽…

python 连接高斯数据库报错

问题1&#xff1a;报错信息&#xff1a; import psycopg2时报错 /lib64/libgssapi_krib5.so.2 symbol krb5_ser_contect_init version krb5_3_MIT not defined in file libkrb5.so.3 错误原因&#xff1a; 解决&#xff1a; 若通过更换krb相关安装包&#xff0c;psycopg2 …

excel 整理表格,分割一列变成多列数据

数据准备 对于很多系统页面的数据是没有办法下载的。 这里用表格数据来举例。随便做数据的准备。想要看excel部分的可以把这里跳过&#xff0c;从数据准备完成开始看。 需要一点前端基础知识&#xff0c;但不多&#xff08;不会也行&#xff09;。 把鼠标放在你想要拿到本地的…

刀客doc:快手的商业化架构为什么又调了?

一、 1月10日&#xff0c;快手商业化及电商事业部进行新一轮的架构调整。作为2025年快手的第一次大调整&#xff0c;变动最大的是负责广告业务的商业化事业部。快手商业化将原来的8个业务中心&#xff0c;现在统合成了5个&#xff0c;行业归拢看上去更加明晰了。 根据自媒体《…

thinkphp 5.0 结合redis 做延迟队列,队列无法被消费

目录 一、Linux 环境下 二、如何验证消息队列被正确监听 一、Linux 环境下 项目部署在Linux 环境下&#xff0c;首先找到项目的部署路径&#xff0c;接着输入命令,这个命令是以守护进程方式进行监听你的队列&#xff0c;只要redis 不关闭 就可以一直监听这个队列 nohup php …

计算机网络 (40)域名系统DNS

前言 计算机网络域名系统DNS&#xff08;Domain Name System&#xff09;是互联网的基础技术之一&#xff0c;它负责将人类可读的域名转换为计算机用来通信的数字IP地址。 一、基本概念 DNS的主要目的是将域名解析或翻译为IP地址&#xff0c;使得用户可以通过简单易记的域名来访…

做一个 简单的Django 《股票自选助手》显示 用akshare 库(A股数据获取)

图&#xff1a; 股票自选助手 这是一个基于 Django 开发的 A 股自选股票信息查看系统。系统使用 akshare 库获取实时股票数据&#xff0c;支持添加、删除和更新股票信息。 功能特点 支持添加自选股票实时显示股票价格和涨跌幅一键更新所有股票数据支持删除不需要的股票使用中…

C#使用OpenTK绘制3D可拖动旋转图形三棱锥

接上篇,绘制着色矩形 C#使用OpenTK绘制一个着色矩形-CSDN博客 上一篇安装OpenTK.GLControl后,这里可以直接拖动控件GLControl 我们会发现GLControl继承于UserControl //// 摘要:// OpenGL-aware WinForms control. The WinForms designer will always call the default//…

《C++11》nullptr介绍:从NULL说起

在C11之前&#xff0c;我们通常使用NULL来表示空指针。然而&#xff0c;NULL在C中有一些问题和限制&#xff0c;这就是C11引入nullptr的原因。本文将详细介绍nullptr的定义、用法和优点。 1. NULL的问题 在C中&#xff0c;NULL实际上是一个整数0&#xff0c;而不是一个真正的…

Postman 接口测试平替工具,可视化开发省事!

在软件开发的漫长旅程中&#xff0c;接口测试工具一直是开发者的得力助手。Postman 作为全球知名的接口测试工具&#xff0c;长期占据市场主导地位。然而&#xff0c;随着国产工具的崛起&#xff0c;越来越多的开发者开始寻找更适合中国开发者的替代方案。一款 Apifox&#xff…

代码随想录算法训练营day20(0113)

1.二叉搜索树的最近公共祖先 在上次做完二叉树的最近公共祖先后&#xff0c;此题就显得比较简单了。不过要拓展一下&#xff0c;因为二叉搜索树有一些特性的&#xff0c;可以更加方便的解题。 题目 235. 二叉搜索树的最近公共祖先 给定一个二叉搜索树, 找到该树中两个指定节…

使用C# CEFSharp在WPF中开发桌面程序实现同一网站多开功能

在网络商业运营领域&#xff0c;同时运营多个淘宝店铺的现象屡见不鲜。为了满足这一需求&#xff0c;实现同一网址的多开功能变得尤为关键。这一需求虽然实用&#xff0c;但实现起来却面临诸多挑战。在这个过程中&#xff0c;技术人员们也经历了不少喜怒哀乐。 开发经历回顾 …

Shell 经典面试例题

1.shell 脚本写出检测 /tmp/size.log 文件如果存在显示它的内容&#xff0c;不存在则创建一个文件将创建时间写入。 编写脚本&#xff1a; #!/bin/bash FILE"/tmp/size.log" if [ -f "$FILE" ]; then echo "文件存在&#xff0c;显示文件内容&…

移动云自研云原生数据库入围国采!

近日&#xff0c;中央国家机关2024年度事务型数据库软件框架协议联合征集采购项目产品名单正式公布&#xff0c;移动云自主研发的云原生数据库产品顺利入围。这一成就不仅彰显了移动云在数据库领域深耕多年造就的领先技术优势&#xff0c;更标志着国家权威评审机构对移动云在数…

Centos 宝塔安装

yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh 安装成功界面 宝塔说明文档 https://www.bt.cn/admin/servers#wcu 或者可以注册宝塔账号 1 快速部署 安装docker 之后 2 需要在usr/bin下下载do…