You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何通过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的datalinesize字段,注意要匹配像素格式的内存布局(比如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_frameavcodec_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_ctxhw_frames_ctx,用av_frame_free释放AVFrame,避免内存泄漏。
  • 错误处理:每个FFmpeg API调用都要检查返回值,硬件相关操作更容易出现设备不兼容、内存不足等问题。

内容的提问来源于stack exchange,提问作者Vincentz

火山引擎 最新活动