如何通过FFmpeg从CUDA缓冲区而非RAM缓冲区编码视频?
直接编码CUDA缓冲区图像到H.264(h264_nvenc)的实现方案
当然可行!我之前也帮人解决过类似需求,FFmpeg的h264_nvenc编码器原生支持直接处理GPU端的CUDA帧,完全不用把数据从GPU拷贝回CPU内存,完美匹配你的场景。下面是具体的实现步骤和关键细节:
核心前提
确保你使用的FFmpeg是带CUDA硬件加速支持编译的,版本建议不低于4.x(API更稳定,对硬件帧的支持更完善)。Windows 7下Pascal架构的GPU只要驱动版本对应CUDA 8.0+,就能正常工作。
实现步骤
1. 创建CUDA硬件设备上下文
首先初始化FFmpeg的CUDA设备上下文,让编码器明确要使用的GPU设备:
AVBufferRef *hw_device_ctx = NULL; int ret = av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_CUDA, NULL, NULL, 0); if (ret < 0) { // 处理设备初始化失败的错误,比如GPU不可用 return ret; }
2. 配置编码器启用硬件帧输入
在打开编码器前,把编码器的hw_device_ctx绑定到刚才创建的CUDA上下文,同时指定输入像素格式为CUDA帧:
AVCodecContext *codec_ctx = avcodec_alloc_context3(avcodec_find_encoder_by_name("h264_nvenc")); // 先设置常规编码参数:分辨率、比特率、帧率等 codec_ctx->width = 1920; codec_ctx->height = 1080; codec_ctx->bit_rate = 5000000; codec_ctx->framerate = (AVRational){30, 1}; codec_ctx->time_base = (AVRational){1, 30}; codec_ctx->pix_fmt = AV_PIX_FMT_CUDA; // 关键:指定输入为CUDA硬件帧 // 绑定CUDA设备上下文 codec_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx); // 打开编码器 ret = avcodec_open2(codec_ctx, NULL, NULL); if (ret < 0) { // 处理编码器打开失败的错误 return ret; }
3. 创建关联CUDA上下文的输入AVFrame
需要生成一个和CUDA上下文绑定的AVFrame,用来承载GPU端的图像数据:
AVFrame *frame = av_frame_alloc(); frame->format = AV_PIX_FMT_CUDA; frame->width = codec_ctx->width; frame->height = codec_ctx->height; // 创建硬件帧上下文,关联到CUDA设备 AVBufferRef *hw_frames_ctx = NULL; ret = av_hwframe_ctx_create(&hw_frames_ctx, hw_device_ctx, AV_PIX_FMT_CUDA, frame->width, frame->height, 0); if (ret < 0) { // 处理硬件帧上下文创建失败的错误 return ret; } frame->hw_frames_ctx = av_buffer_ref(hw_frames_ctx);
4. 绑定你的CUDA缓冲区到AVFrame
把你已有的CUDA缓冲区指针填充到AVFrame的data和linesize字段,注意要匹配像素格式的内存布局(比如YUV420格式下,data[0]是Y分量的CUDA指针,data[1]是U分量,data[2]是V分量,linesize对应各分量的 stride):
// 假设你已经持有CUDA端的YUV420缓冲区指针 frame->data[0] = your_cuda_y_ptr; frame->data[1] = your_cuda_u_ptr; frame->data[2] = your_cuda_v_ptr; frame->linesize[0] = your_y_stride; frame->linesize[1] = your_u_stride; frame->linesize[2] = your_v_stride; frame->pts = current_frame_pts; // 设置正确的时间戳,保证编码后视频的时序正确
5. 执行编码(使用新API替代avcodec_encode_video2)
注意avcodec_encode_video2已经被废弃,推荐使用avcodec_send_frame和avcodec_receive_packet这套新API,它们对硬件帧的支持更稳定:
AVPacket pkt = {0}; av_init_packet(&pkt); // 发送CUDA帧到编码器 ret = avcodec_send_frame(codec_ctx, frame); if (ret < 0) { // 处理帧发送失败的错误,比如编码器缓存已满 return ret; } // 循环接收编码后的H.264数据包 while (ret >= 0) { ret = avcodec_receive_packet(codec_ctx, &pkt); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } else if (ret < 0) { // 处理编码错误 return ret; } // 处理输出的H.264数据包:写入文件、推流等操作 // ... av_packet_unref(&pkt); }
关键注意事项
- 不要混用CPU帧和CUDA帧:一旦编码器配置为CUDA输入,所有输入帧都必须是
AV_PIX_FMT_CUDA格式的硬件帧。 - 内存管理:记得用
av_buffer_unref释放hw_device_ctx和hw_frames_ctx,用av_frame_free释放AVFrame,避免内存泄漏。 - 错误处理:每个FFmpeg API调用都要检查返回值,硬件相关操作更容易出现设备不兼容、内存不足等问题。
内容的提问来源于stack exchange,提问作者Vincentz




