ffmpeg + cuda(cuvid) 硬解码+像素格式转换(cpu主导)实战-CSDN博客
Excerpt
文章浏览阅读6.1k次,点赞4次,收藏22次。注意:VAAPI 是inter gpu 提供的硬编解码接口VDPAU 是 video decode present api for unixnvdec / ncvid 都是nivida产出的硬解接口,区别在于解码方式,和数据传输方式不同nvenc nivida 硬编接口编译 & 运行linux: gcc -g video_decode_gpu.c `pkg-co…_ffmpeg cuda 编解码
注意:
1 | VAAPI 是inter gpu 提供的硬编解码接口 |
编译 & 运行
1 | linux: |
gpu解码原理
1 | 问题1? gpu 解码 是把内存中AVPacket 拷贝到gp显存中进行处理的吗? |
VDPAU 简介
1 | Developed by NVIDIA for Unix/Linux systems. To enable this you typically need the libvdpau development package in your distribution, and a compatible graphics card. |
VDPAU 学习:
1 | VdpDecoder -> 解码 压缩包数据 |
cuvid 与 VDPAU 是平级的东西,不能拿来直接使用,使用成本太大
cuvid 学习
1 | cuvid nvidia 提供的gpu 视频硬解码库,底层依赖cuda并行计算框架 |
CUVID 硬解码
note:
1 | cuvid nvdec 两者都是解码api,不同点在于解码方式 & 数据传输 |
CUVID解码rtsp视频流
note
1 | OpenCV中VideoReader_GPU可以方便地利用GPU读取视频文件,加速解码过程,但OpenCV中VideoReader_GPU无法读取rtsp视频流数据。 |
查看nvidia 驱动 & nvcc 版本
1 | cat /proc/driver/nvidia/version |
Note: For Video Codec SDK 7.0 and later, NVCUVID has been renamed to NVDECODE API.
编译 & 运行
编译
1 | linux: |
运行
1 | ./hw_decode_cuvid cuda input_data/left.mp4 ./output_data/raw.out |
运行结果
raw.out 文件生成
raw.out 文件生成
cpu 软解码 cpu 占用率
cpu 软解码 cpu使用率g
gpu cuvid 硬解码 cpu 占用率
gpu 硬解码 cpu使用率
gpu 硬解码 gpu 使用情况
gpu 硬解码 gpu 使用情况
问题:
1 | 1、为什么 gpu 硬解码显卡使用率那么低?需要排查下问题。 |
gpu decoded frame pix format AV_PIX_FMT_CUDA 直接在显存中 转化为 AV_PIX_FMT_BGR24
可行路径,试了三种:
1 | 两种cpu层面转换像素格式 的方法(1种失败,1种成功); |
CPU 主导像素转换
1. 使用 sws_scale 实现 AV_PIX_FMT_CUDA-> AV_PIX_FMT_BGR24 的直接转换(cpu 层面)
这是我第一次使用的方式,模仿 cpu 上软解码(获取视频帧,并存储为bmp格式,经验原则,这种方式最容易想到)运行结果:
失败,bad src img pointers
运行结果如下图所示:
bad_src_img_pointers.png
问题原因:
如代码 hw_decode_cuvid_origin.c 中所示, 直接通过transfer_data 将gpu 中解码后的frame download到
系统内存,则系统内存中的frames piex->format 仍为 AV_PIX_FMT_CUDA ,而 AV_PIX_FMT_CUDA 是gpu 显存中存储的解码后的帧像素格式
所以通过 sws_scale 是不能直接change的
GPU 主导像素转换
gpu 不支持 sws_scale + AV_PIX_FMT_CUDA-> AV_PIX_FMT_BGR24 的直接像素转换方式,那么 能否直接在gpu中直接转化 AV_PIX_FMT_CUDA 为 AV_PIX_FMT_BGR24呢?
如果可以直接实现,性能会有很大提升,因为减少了device->host 的数据传输,且gpu多核心并行处理,肯定比cpu处理性能要强悍。
av_hwframe_transfer_data() 执行操作前 设置 内存中目标frame的像素格式为 AV_PIX_FMT_BGR24,gpu 黑盒操作实现在gpu上直接将像素格式转化为目标bgr24格式
运行结果:
失败,像素没对齐,只有亮度
运行结果如下图所示:
![预先设置内存中frame目标像素格式为 AV_PIX_FMT_BGR24]
error_pre_set_pix_format
问题原因:
如下图所示:
问题原因-transfer_data_pix_format_limit
1 | 红框表示的意思为:src->frame->format 转换为 dst->frame->format 是受限制的,主要是受av_hwframe_transfer_get_formats() 函数返回的formats 列表限制 |
所以gdb了下源码,发现src->frame->format 转换为 dst->frame->format 的受限范围很小,然后找出了 av_hwframe_transfer_get_formats 支持的formats,
调试过程如下所示:gdb -tui hw_decode_cuvid (-tui 支持查看源码)
gdb_tui.png
在调用 av_hwframe_transfer_data() 函数处打上断点,且设置程序运行所需参数
enter_break_point.png
run 程序,step 进入函数调用栈
run & step-run_enter_func_call_stack
n 单步运行,函数调用至 transfer_data_alloc()
enter_transfer_data_alloc.png
发现 av_hwframe_transfer_get_formats()函数
find_func_call_av_hwframe_transfer_get_formats.png
更改 dst->format 的值为<0的值,并打印支持的像素转换列表
run_av_hwframe_transfer_get_formats_success.png
get_can_changed_pix_formats.png
可以看到只支持 gpu 硬件像素编码格式->AV_PIX_FMT_NV12 的转换
CPU 主导像素转换
经过前两次的试验,可以明确当前最新版本的ffmpeg还不支持硬解完成之后直接将像素格式转换为目标rgb24数据,还是回归到 cpu + sws_scale 上,
经过第二步,可以知道AV_PIX_FMT_CUDA->AV_PIX_FMT_NV12这条路行的通,AV_PIX_FMT_NV12 其实是 YUV 格式的数据,yuv 数据到 rgb 的像素转换
是完全支持的,所以就自然编写了 AV_PIX_FMT_CUDA->AV_PIX_FMT_NV12->AV_PIX_FMT_BGR24 的代码,经测试没问题。
当然,不可否认:
实现 AV_PIX_FMT_CUDA-> AV_PIX_FMT_NV12->AV_PIX_FMT_BGR24 格式转换 (cpu 实现 pix format 转换,这种cpu层面上的像素格式转换方式比较弱)
运行结果:
成功,如下图所示:
change_cuda_pix_format_2_rgb_format_success.png