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

news2025/1/14 9:59:16

前言

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Web前端界面开发

前沿&#xff1a;介绍自适应和响应式布局 自适应布局&#xff1a;-----针对页面1个像素的变换而变化 就是我们上一个练习的效果 我们的页面效果&#xff0c;随着我们的屏幕大小而发生适配的效果&#xff08;类似等比例&#xff09; 如&#xff1a;rem适配 和 vw/vh适配 …

OpenCV基础:视频的采集、读取与录制

从摄像头采集视频 相关接口 - VideoCapture VideoCapture 用于从视频文件、摄像头或其他视频流设备中读取视频帧。它可以捕捉来自多种源的视频。 主要参数&#xff1a; cv2.VideoCapture(source): source: 这是一个整数或字符串&#xff0c;表示视频的来源。 如果是整数&a…

C++内存泄露排查

内存泄漏是指程序动态分配的内存未能及时释放&#xff0c;导致系统内存逐渐耗尽&#xff0c;最终可能造成程序崩溃或性能下降。在C中&#xff0c;内存泄漏通常发生在使用new或malloc等分配内存的操作时&#xff0c;但没有正确地使用delete或free来释放这块内存。 在日常开发过程…

Golang 简要概述

文章目录 1. Golang 的学习方向2. Golang 的应用领域2.1 区块链的应用开发2.2 后台的服务应用2.3 云计算/云服务后台应用 1. Golang 的学习方向 Go 语言&#xff0c;我们可以简单的写成 Golang 2. Golang 的应用领域 2.1 区块链的应用开发 2.2 后台的服务应用 2.3 云计算/云服…

51c自动驾驶~合集46

我自己的原文哦~ https://blog.51cto.com/whaosoft/13050104 #世界模型会是L3自动驾驶的唯一解吗 三维空间占有率&#xff08;3D Occupancy&#xff09;预测的目的是预测三维空间中的每个体素是否被占有&#xff0c;如果被占有&#xff0c;则对应的体素将被标记。3D Semant…

从CentOS到龙蜥:企业级Linux迁移实践记录(龙蜥开局)

引言&#xff1a; 在我们之前的文章中&#xff0c;我们详细探讨了从CentOS迁移到龙蜥操作系统的基本过程和考虑因素。今天&#xff0c;我们将继续这个系列&#xff0c;重点关注龙蜥系统的实际应用——特别是常用软件的安装和配置。 龙蜥操作系统&#xff08;OpenAnolis&#…

WMS仓库管理系统,Vue前端开发,Java后端技术源码(源码学习)

一、项目背景和建设目标 随着企业业务的不断扩展&#xff0c;仓库管理成为影响生产效率、成本控制及客户满意度的重要环节。为了提升仓库作业的透明度、准确性和效率&#xff0c;本方案旨在构建一套全面、高效、易用的仓库管理系统&#xff08;WMS&#xff09;。该系统将涵盖库…

Docker Search失败,但是Pull成功的解决方法(环境:腾讯云服务器CentOS7系统安装Docker)

Docker Search失败&#xff0c;但是Pull成功的解决方法&#xff08;环境&#xff1a;腾讯云服务器CentOS7系统安装Docker&#xff09; 背景解决思路 背景 在安装完成Docker后&#xff0c;成功启动Docker(Systemctl start Docker)&#xff0c;使用搜索镜像命令&#xff08;Dock…

ClickHouse vs StarRocks 选型对比

一、面向列存的 DBMS 新的选择 Hadoop 从诞生已经十三年了&#xff0c;Hadoop 的供应商争先恐后的为 Hadoop 贡献各种开源插件&#xff0c;发明各种的解决方案技术栈&#xff0c;一方面确实帮助很多用户解决了问题&#xff0c;但另一方面因为繁杂的技术栈与高昂的维护成本&…

慧集通(DataLinkX)iPaaS集成平台-业务建模之业务对象(二)

3.UI模板 当我们选择一条已经建好的业务对象点击功能按钮【UI模板】进入该业务对象的UI显示配置界面。 右边填写的是UI模板的编码以及对应名称&#xff1b;菜单界面配置以业务对象UI模板编码获取显示界面。 3.1【列表-按钮】 展示的对应业务对象界面的功能按钮配置&#xff1…

TCL小蓝翼新风空调亮相CES2025,斩获智慧新风技术创新大奖

1月7日-11日&#xff0c;被称为“科技界春晚”的CES 2025&#xff08;国际消费类电子产品展览会&#xff09;在美国拉斯维加斯举行。 本届CES&#xff0c;TCL小蓝翼新风空调更是惊艳亮相TCL展区&#xff0c;以领先的健康新风及AI技术&#xff0c;斩获CES2025 智慧新风技术创新…

【I/O编程】UNIX文件基础

IO编程的本质是通过 API 操作 文件。 什么是 IO I - Input 输入O - Output 输出 这里的输入和输出都是站在应用&#xff08;运行中的程序&#xff09;的角度。外部特指文件。 这里的文件是泛指&#xff0c;并不是只表示存在存盘中的常规文件。还有设备、套接字、管道、链接…

【计算机网络】深入浅出计算机网络

第一章 计算机网络在信息时代的作用 计算机网络已由一种通信基础设施发展成一种重要的信息服务基础设施 CNNIC 中国互联网网络信息中心 因特网概述 网络、互联网和因特网 网络&#xff08;Network&#xff09;由若干结点&#xff08;Node&#xff09;和连接这些结点的链路…

在IDEA上运行Java项目

新建一个项目&#xff0c;下面创建模块&#xff0c;然后在src下新建包名&#xff0c;最后见类&#xff08;class&#xff09; 设置主题 settings>apparence 设置字体 Editor> Font 设置注释 Editor>Color Scheme>Language Defaults>Comments 设置自动导包 …

ASO优化之应用程序本地化的类型和策略

应用程序本地化是进入全球移动电话用户市场的关键一步。但它到底是什么&#xff1f;应用程序本地化是指定制您的应用程序以适应多种语言、文化、语言和区域设置。这不仅仅是翻译&#xff0c;它考虑了地区法规、文化细微差别和当地偏好。本地化良好的应用程序可以引起用户的共鸣…

gcc编译过程中-L和-rpath的作用

前言 今天记录一下&#xff0c;在gcc编译过程中-L和-rpath的区别 -L是程序链接过程中指定链接动态库的路径&#xff0c;-rpath是程序运行过程中指定链接动态库的路径。&#xff08;官方话术&#xff09; 其实就是当gcc编译生成可执行文件的时候需要指定-L参数&#xff0c;才能找…

3D目标检测数据集——kitti数据集

KITTI官网网址&#xff1a;The KITTI Vision Benchmark Suite 下载数据集&#xff1a;The KITTI Vision Benchmark Suite KITTI数据集论文&#xff1a;CMSY9 github可视化代码&#xff1a;GitHub - kuixu/kitti_object_vis: KITTI Object Visualization (Birdview, Volumetric …

AI大模型赋能!移远通信打造具有“超能力”的AI智能玩具解决方案

随着无线通信、先进算法以及AI大模型等前沿技术的蓬勃发展&#xff0c;许多玩具已经从简单的互动设备进化为集教育、陪伴和娱乐功能于一身的AI智能玩具&#xff0c;在儿童群体中日渐风靡。不仅如此&#xff0c;因其能提供满满的情绪价值&#xff0c;在成年人和老年人市场中也展…

LED灯按键调光芯片、PWM调光IC、发光灯控制调光芯片

按键调光芯片&#xff0c;特别是LED灯使用PWM调光的芯片IC&#xff0c;是一种用于控制LED灯具亮度的集成电路&#xff0c;常用于台灯、壁灯、吊灯等照明设备中。这种芯片通过脉冲宽度调制&#xff08;PWM&#xff09;技术来调节LED的亮度&#xff0c;可以实现从最亮到最暗的平滑…