零拷贝要求硬解帧全程保留在GPU显存中,需确保解码器输出AV_PIX_FMT_CUDA格式、下游模块原生支持CUDA指针、避免sws_scale/av_hwframe_map隐式拷贝,并严格管理AVBufferRef生命周期。

硬解帧必须留在GPU显存里,否则就不是“零拷贝”。FFmpeg的cuvid(即h264_cuvid、hevc_cuvid等)解码器本身支持输出AV_PIX_FMT_CUDA格式帧,但关键在于:你得让后续处理环节也接受并直接消费这个格式,否则av_hwframe_map或sws_scale一调,立刻触发显存→内存拷贝。
确认解码器输出的是 AV_PIX_FMT_CUDA 帧
硬解不等于自动零拷贝。很多开发者误以为用了h264_cuvid就万事大吉,其实默认行为仍可能把帧拉回内存——除非你显式配置硬件帧上下文并检查输出格式。
- 调用
avcodec_find_decoder_by_name("h264_cuvid")后,必须用avcodec_parameters_to_context()初始化解码器上下文 - 在
avcodec_open2()前,设置ctx->get_format = get_hw_format回调函数,该函数需返回AV_PIX_FMT_CUDA(不能只返回AV_PIX_FMT_NV12等CPU格式) - 解码成功后,检查
frame->format == AV_PIX_FMT_CUDA且frame->buf[0]->data指向的是CUDA设备指针(可用cudaPointerGetAttributes验证) - 若
frame->format是AV_PIX_FMT_NV12或AV_PIX_FMT_YUV420P,说明已被自动下载到内存,零拷贝已失效
避免 sws_scale 和 av_hwframe_map 的隐式拷贝
sws_scale根本不认识AV_PIX_FMT_CUDA,只要传进去就会崩溃或静默失败;av_hwframe_map若目标格式设为CPU格式(如AV_PIX_FMT_RGB24),也会强制执行一次hwdownload。
- 不要对
AV_PIX_FMT_CUDA帧调用sws_scale——它只处理内存中的像素数据 - 若需颜色空间转换(如NV12→RGB),必须用CUDA kernel(如NPP库的
nppiNV12ToRGB_8u_P2C3R)或OpenGL/Vulkan shader,在GPU内完成 - 若必须映射到CPU内存(例如喂给OpenCV
cv::Mat),用av_hwframe_map时目标AVBufferRef*必须基于AV_HWDEVICE_TYPE_CUDA创建,且目标frame->format仍设为AV_PIX_FMT_CUDA(即映射出另一个设备指针,而非下载) - 真正需要CPU访问时,才调用
av_hwframe_map+AV_PIX_FMT_NV12,但这是有拷贝的,不属于零拷贝链路
下游模块必须原生支持 CUDA 设备指针
零拷贝不是单点优化,而是一条端到端的数据流契约。只要任一环节要求uint8_t*内存地址,整条链路就退化为“硬解+下载+CPU处理”。
- OpenCV 4.5+ 的
cv::cuda::GpuMat可直接绑定CUDA设备指针:GpuMat mat(height, width, CV_8UC1, frame->data[0], frame->linesize[0]) - Unity、Unreal 或自研渲染引擎中,应使用
cudaGraphicsGLRegisterBuffer或cudaExternalMemory_t将帧注册为纹理资源,跳过内存中转 - AI推理(如TensorRT)输入Binding也需设为
cudaMalloc分配的显存地址,不能是malloc出来的 - 如果下游是纯CPU库(如libass字幕渲染、大多数音频重采样器),那就别强求零拷贝——硬解+下载+CPU滤镜反而比软解还慢(实测慢15%)
最易被忽略的一点:AVFrame里的data[0]是CUDA指针,但它的生命周期完全由FFmpeg的AVBufferRef管理。如果你把它传给其他CUDA kernel后忘了av_frame_unref(),或者在avcodec_receive_frame()循环外提前释放了引用,极易触发显存非法访问或内存泄漏。零拷贝的代价,是更严格的资源所有权控制。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











