当前位置: 首页 > news >正文

RK3588 使用 FFmpeg 硬件解码输出到 DRM Prime (DMA Buf) 加速数据传输

一、前言

        我之前做过一个 RK3588 开发板部署 YOLO 算法的一个项目,该项目的源码已公开,并且有专门的文章做讲解:

基于 RK3588 的 YOLO 多线程推理多级硬件加速引擎框架设计(项目总览和加速效果)_rk3588 c++ yolo-CSDN博客https://blog.csdn.net/plmm__/article/details/146542002?spm=1001.2014.3001.5502       

        本项目的视频数据处理流程始于本地 MP4 视频文件,依次经过以下核心环节:

  1. ​FFmpeg 硬件解码:利用 Rockchip 多媒体处理平台 RKmpp 进行硬件加速解码,高效获取视频帧。

  2. ​AVFrame 数据结构 (NV12 格式)​​:解码后数据暂存于 FFmpeg 的 AVFrame 结构中,保持 NV12 色彩格式。

  3. ​CV::Mat 数据结构 (NV12 格式)​​:将 AVFrame 数据转换并封装至 OpenCV 的 Mat 结构中,便于后续图像操作。

  4. ​RGA 硬件转换​​:通过 Rockchip RGA 硬件模块加速图像色彩空间与格式转换。

  5. ​CV::Mat 数据结构 (BGR 格式)​​:最终获得 BGR 格式的 Mat 数据,为后续计算机视觉任务(如目标检测、图像识别)提供标准输入。

该流程充分利用硬件加速(RKmpp, RGA),显著提升了视频解码与格式转换的效率。

二、DRM Prime 

        考虑到数据在传输过程中,存在过多的数据拷贝操作,以及 CPU 需要全程监控数据的传输,因此想到了 DMA 来作为数据传输的媒介,提高传输效率。

        FFmpeg 解码器编译支持 RKmpp 插件,支持输出 DRM Prime,也就是输出到一个 DMA Buf 中,这个过程不需要 CPU 参与。我本人也是第一次接触 DRM Prime,所以这次的目的仅限于加速解码,其他功能暂不讨论。

        有关编译支持 RKmpp 硬件解码的 FFmpeg,可以查看我的另一篇文章:

编译支持 RKmpp 和 RGA 的 ffmpeg 源码_ffmpeg rkmpp-CSDN博客https://blog.csdn.net/plmm__/article/details/146188927?spm=1001.2014.3001.5502

        可以看到,解码器的输出支持 NV12 等色彩空间的 AVFrame,并且支持输出到 DRM Prime。

三、修改编码器设置

1、修改初始化

        在原来初始化解码器的基础上,增加输出格式,通过 AVDictionary 结构体设置,并由 avcodec_open2 接口进行写入:

#include <drm_fourcc.h>/* 设置解码器输出为 DRM PRIME 格式 */
AVDictionary* codec_opts = nullptr;
av_dict_set(&codec_opts, "output_mode", "drm_prime", 0);
av_dict_set(&codec_opts, "output_format", "drm_prime", 0); // 可选但推荐/* 打开编解码器,传递选项 */
if (avcodec_open2(codecContext, codec, &codec_opts) < 0) {av_dict_free(&codec_opts);throw std::runtime_error("Couldn't open decoder with DRM_PRIME");
}
av_dict_free(&codec_opts);

        注意头文件,这个一般开发板都会有,路径在:

/usr/include/libdrm

2、修改取帧操作

        在取帧时,增加两个检查的语句,确保输出格式设置成功,以及解码器编译成功:

// 检查解码输出格式
if (tempFrame->format != AV_PIX_FMT_DRM_PRIME) {std::cout << "Format not DRM_PRIME: " << av_get_pix_fmt_name((AVPixelFormat)tempFrame->format) << std::endl;break;
}// 获取DRM描述符
AVDRMFrameDescriptor* desc = (AVDRMFrameDescriptor*)tempFrame->data[0];
// DMA-BUF文件描述符
int drm_prime_fd = desc->objects[0].fd;
uint32_t format = desc->layers[0].format;// 检查帧格式是否为NV12
if (format != DRM_FORMAT_NV12) {printf("Not NV12: Format = %c%c%c%c (0x%08X)\n",(char)(format), (char)(format >> 8),(char)(format >> 16), (char)(format >> 24),format);break;
}

        首先是帧的格式,如果不使用 DRM,这里就是对应的色彩空间,例如 NV12;如果使用 DRM,这里的输出就是 AV_PIX_FMT_DRM_PRIME。

        其次是 DRM 内部数据的色彩空间,使用 AVDRMFrameDescriptor 结构体来获取,然后与 DRM_FORMAT_NV12 进行对比,检查 DRM 内部是否为 NV12 格式。

        这里的 drm_prime_fd 就是 DRM 对应的 DMA Buf 的文件描述符,可以直接送入 RGA 进行硬件转换。我暂时没找到相关的官方示例,不过我觉得理论上没问题。从结果来看转换没有问题,并且速度极快

3、传入 RGA

        下面这个是 RK 官方的使用 DMA FD 调用 RGA 的demo,我在此基础上进行更改的:

librga/samples/cvtcolor_demo/src/rga_cvtcolor_csc_demo.cpp at main · airockchip/librgahttps://github.com/airockchip/librga/blob/main/samples/cvtcolor_demo/src/rga_cvtcolor_csc_demo.cpp        我这里也放一个源码:

/** Copyright (C) 2022  Rockchip Electronics Co., Ltd.* Authors:*     YuQiaowei <cerf.yu@rock-chips.com>** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/#define LOG_NDEBUG 0
#undef LOG_TAG
#define LOG_TAG "rga_cvtcolor_csc_demo"#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <linux/stddef.h>#include "RgaUtils.h"
#include "im2d.hpp"#include "utils.h"
#include "dma_alloc.h"#define LOCAL_FILE_PATH "/data"int main() {int ret = 0;int src_width, src_height, src_format;int dst_width, dst_height, dst_format;int src_dma_fd, dst_dma_fd;char *src_buf, *dst_buf;int src_buf_size, dst_buf_size;rga_buffer_t src_img, dst_img;rga_buffer_handle_t src_handle, dst_handle;memset(&src_img, 0, sizeof(src_img));memset(&dst_img, 0, sizeof(dst_img));src_width = 1280;src_height = 720;src_format = RK_FORMAT_RGBA_8888;dst_width = 1280;dst_height = 720;dst_format = RK_FORMAT_YCbCr_420_SP;src_buf_size = src_width * src_height * get_bpp_from_format(src_format);dst_buf_size = dst_width * dst_height * get_bpp_from_format(dst_format);ret = dma_buf_alloc(DMA_HEAP_DMA32_UNCACHED_PATH, src_buf_size, &src_dma_fd, (void **)&src_buf);if (ret < 0) {printf("alloc src dma_heap buffer failed!\n");return -1;}ret = dma_buf_alloc(DMA_HEAP_DMA32_UNCACHED_PATH, dst_buf_size, &dst_dma_fd, (void **)&dst_buf);if (ret < 0) {printf("alloc dst dma_heap buffer failed!\n");dma_buf_free(src_buf_size, &src_dma_fd, src_buf);return -1;}/* fill image data */if (0 != read_image_from_file(src_buf, LOCAL_FILE_PATH, src_width, src_height, src_format, 0)) {printf("src image read err\n");memset(src_buf, 0xaa, src_buf_size);}memset(dst_buf, 0x80, dst_buf_size);src_handle = importbuffer_fd(src_dma_fd, src_buf_size);dst_handle = importbuffer_fd(dst_dma_fd, dst_buf_size);if (src_handle == 0 || dst_handle == 0) {printf("importbuffer failed!\n");goto release_buffer;}src_img = wrapbuffer_handle(src_handle, src_width, src_height, src_format);dst_img = wrapbuffer_handle(dst_handle, dst_width, dst_height, dst_format);imsetColorSpace(&src_img, IM_RGB_FULL);imsetColorSpace(&dst_img, IM_YUV_BT709_LIMIT_RANGE);ret = imcheck(src_img, dst_img, {}, {});if (IM_STATUS_NOERROR != ret) {printf("%d, check error! %s", __LINE__, imStrError((IM_STATUS)ret));goto release_buffer;}ret = imcvtcolor(src_img, dst_img, src_format, dst_format);if (ret == IM_STATUS_SUCCESS) {printf("%s running success!\n", LOG_TAG);} else {printf("%s running failed, %s\n", LOG_TAG, imStrError((IM_STATUS)ret));goto release_buffer;}write_image_to_file(dst_buf, LOCAL_FILE_PATH, dst_width, dst_height, dst_format, 0);release_buffer:if (src_handle)releasebuffer_handle(src_handle);if (dst_handle)releasebuffer_handle(dst_handle);dma_buf_free(dst_buf_size, &dst_dma_fd, dst_buf);dma_buf_free(src_buf_size, &src_dma_fd, src_buf);return ret;
}

        由于 DRM 的输出本身就是 DMA,所以这里删除与输入图像有关的部分,也不需要申请原始图像的 DMA。

        修改输出输出的尺寸,图像格式,准备各项数据,将刚刚得到的 drm_prime_fd 通过函数传参,直接传递给 importbuffer_fd 函数。

        imsetColorSpace 函数我暂时还没有搞明白具体的用法,我尝试注释也能进行转换,这个好像是最近才增加的 API。

4、设置 RGA stride 填充信息

        在 H264 编码中是以宏块为单位的,宏块的大小为 16*16,有的时候图像的宽度和高度不是16的整数倍,那么最右边会有一部分的长度在 1-15 之间,但是我们编码不能把这些数据丢掉,所以就需要对这些元素进行补齐,补齐之后的长度就叫做 stride,所以正常情况下 stride >= 宽。

        如果想对 FFmpeg 解码后的数据进行处理,就要考虑对齐的填充数据处理。

        解决对齐的操作在我之前的项目中,是将图像存储的媒介由 AVFrame 转 CV::Mat 进行保存时处理的,因为 CV::Mat 的数据指针需要连续的颜色数据。

        当前版本的 RGA 数据结构是支持自动解决 stride 对齐问题的,只需要在填充 rga_buffer_t 结构体时,设置 wstride 变量即可,一般来叔高度不会填充,所以 hstride 不需要设置,需要根据自身情况而定。

四、参考源码

        下面是我根据官方的 demo 修改后的 API,读者自行参考:

int RGA_handle_nv12_to_rgb_fd_stride(int src_dma_fd, int dst_dma_fd, int width, int height, int src_hor_stride, int src_ver_stride) {int ret = 0;int src_width = width, src_height = height;int dst_width = width, dst_height = height;int src_format = RK_FORMAT_YCbCr_420_SP;  // NV12int dst_format = RK_FORMAT_RGB_888;       // 3 通道 RGBint src_buf_size, dst_buf_size;rga_buffer_t src_img, dst_img;rga_buffer_handle_t src_handle, dst_handle;memset(&src_img, 0, sizeof(src_img));memset(&dst_img, 0, sizeof(dst_img));// 根据RGA文档,NV12格式需要计算正确的缓冲区大小// Y平面: hor_stride * ver_stride// UV平面: hor_stride * (ver_stride / 2)src_buf_size = src_hor_stride * src_ver_stride * get_bpp_from_format(src_format);// src_buf_size = src_width * src_height * get_bpp_from_format(src_format);dst_buf_size = dst_width * dst_height * get_bpp_from_format(dst_format);src_handle = importbuffer_fd(src_dma_fd, src_buf_size);dst_handle = importbuffer_fd(dst_dma_fd, dst_buf_size);if (src_handle == 0 || dst_handle == 0) {printf("importbuffer failed!\n");goto release_buffer;}// 使用wrapbuffer_handle封装缓冲区,并设置正确的stridesrc_img = wrapbuffer_handle(src_handle, src_width, src_height, src_format);dst_img = wrapbuffer_handle(dst_handle, dst_width, dst_height, dst_format);// !!! 关键修改:设置源图像的实际步长 (stride) !!!src_img.wstride = src_hor_stride; // 设置Y平面的水平步长(字节数)src_img.hstride = src_ver_stride; // 设置垂直步长(总高度,包括填充行)// 对于NV12格式,RGA会自动根据Y平面的stride和高度计算UV数据的位置// UV数据通常紧跟在Y数据之后,且其水平步长与Y平面相同(hor_stride)// 设置颜色空间(NV12 → RGB)imsetColorSpace(&src_img, IM_YUV_BT709_LIMIT_RANGE);imsetColorSpace(&dst_img, IM_RGB_FULL);// 可选:添加参数检查(开发阶段建议开启)/*ret = imcheck(src_img, dst_img, (im_rect){}, (im_rect){});if (IM_STATUS_NOERROR != ret) {printf("%d, check error! %s\n", __LINE__, imStrError((IM_STATUS)ret));goto release_buffer;}*/// 执行颜色空间转换ret = imcvtcolor(src_img, dst_img, src_format, dst_format);if (ret != IM_STATUS_SUCCESS) {printf("RGA conversion failed: %s\n", imStrError((IM_STATUS)ret));ret = -1;} else {ret = 0;}release_buffer:if (src_handle)releasebuffer_handle(src_handle);if (dst_handle)releasebuffer_handle(dst_handle);return ret;
}

http://www.dtcms.com/a/520136.html

相关文章:

  • 基于蚁群算法的PID参数整定方法及MATLAB实现
  • 排序算法大全——插入排序
  • 手搓一个CUDA JIT编译器
  • 网站引导页模板互联网公司排名全球
  • JDK 9 List.of(...)
  • 做一个vue3 v-model 双向绑定的弹窗
  • 为超过10亿条记录的订单表新增字段
  • 哪里做网站最便宜WordPress功能模块排版
  • 每日算法刷题Day78:10.23:leetcode 一般树7道题,用时1h30min
  • 薄膜测厚选CWL法还是触针法?针对不同厚度与材质的台阶仪技术选型指南
  • WPF-MVVM的简单入门(第一个MVVM程序)
  • blender拓扑建模教程
  • asp.net手机网站开发教程翻译网站建设方案
  • 佛山建设网站公司哪家好特斯拉ceo进厂拧螺丝
  • 如何做新网站保留域名wordpress基础
  • C# 实现 Modbus TCP 通信
  • 《Git:从入门到精通(七)——Git分支管理与协作开发实战》
  • 超越传统工具:利用Reddit发现关键词的独特视角与前沿方法
  • 数据结构——二叉搜索树深度解析
  • macOS 无法在根目录创建目录的原因与解决方案
  • 11.23 鸿蒙HTTP数据请求
  • 郑州网站建设最低价网址导航的意思
  • LOESS回归
  • 跨平台开发中的图形渲染:Canvas与View+CSS的性能分析与决策路径
  • 能源经济选题推荐:可再生能源转型政策如何提高能源韧性?基于双重机器学习的因果推断
  • 《R for Data Science (2e)》免费中文翻译 (第11章) --- Communication(1)
  • 生成式对抗网络 GAN:从零理解生成对抗网络的原理与魅力
  • 李宏毅机器学习笔记30
  • 做塑胶材料的网站深圳网站设计平台
  • 【设计模式】装饰器模式(Decorator)