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

ffmpeg库视频硬编码使用流程

一、硬件编码核心流程

  1. 硬件设备初始化

    // 创建CUDA硬件设备上下文‌
    AVBufferRef *hw_device_ctx = NULL;
    av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_CUDA, NULL, NULL, 0);
    
    // 绑定硬件设备到编码器上下文‌
    codec_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);
  2. 编码器选择与参数配置

    // 查找NVIDIA H.264硬件编码器‌
    const AVCodec *encoder = avcodec_find_encoder_by_name("h264_nvenc");
    
    // 设置编码参数(分辨率、码率、GOP等)‌
    codec_ctx->width = 1920;
    codec_ctx->height = 1080;
    codec_ctx->bit_rate = 5000000;
    codec_ctx->time_base = (AVRational){1, 30};
    codec_ctx->pix_fmt = AV_PIX_FMT_CUDA;  // 指定硬件像素格式‌
    
    // 设置编码预设参数(NVIDIA专用)‌
    av_opt_set(codec_ctx->priv_data, "preset", "llhp", 0);  // 低延迟高性能
  3. 硬件帧内存分配

    // 创建硬件帧并绑定GPU内存‌
    AVFrame *hw_frame = av_frame_alloc();
    hw_frame->format = codec_ctx->pix_fmt;
    hw_frame->width = codec_ctx->width;
    hw_frame->height = codec_ctx->height;
    av_hwframe_get_buffer(codec_ctx->hw_frames_ctx, hw_frame, 0);
  4. 编码数据流处理

    // 送入硬件编码器‌
    avcodec_send_frame(codec_ctx, hw_frame);
    
    // 接收编码后的数据包
    AVPacket *pkt = av_packet_alloc();
    while (avcodec_receive_packet(codec_ctx, pkt) >= 0) {
        av_interleaved_write_frame(output_ctx, pkt);  // 写入输出文件‌
        av_packet_unref(pkt);
    }

二、跨平台适配示例

硬件平台编码器名称像素格式初始化函数
NVIDIA GPUh264_nvencAV_PIX_FMT_CUDAav_hwdevice_ctx_create(..., AV_HWDEVICE_TYPE_CUDA)
Intel QSVh264_qsvAV_PIX_FMT_QSVav_hwdevice_ctx_create(..., AV_HWDEVICE_TYPE_QSV)
AMD AMFh264_amfAV_PIX_FMT_D3D11av_hwdevice_ctx_create(..., AV_HWDEVICE_TYPE_D3D11VA)

三、关键问题解决

  1. 编码器初始化失败

    • 检查FFmpeg编译时是否启用对应硬件加速选项(如--enable-nvenc--enable-libmfx)‌
    • 确认硬件驱动版本与FFmpeg兼容性‌
  2. CPU-GPU内存拷贝开销优化

    // 使用hwupload滤镜直接上传数据至GPU‌
    AVFilterContext *upload_filter;
    const AVFilter *hwupload = avfilter_get_by_name("hwupload");
    avfilter_graph_create_filter(&upload_filter, hwupload, "upload", NULL, NULL, filter_graph);

 ‌四、资源释放

av_buffer_unref(&hw_device_ctx);  // 释放硬件设备上下文‌
avcodec_free_context(&codec_ctx);  // 释放编码器上下文
av_frame_free(&hw_frame);          // 释放硬件帧
av_packet_free(&pkt);              // 释放数据包

五、编译依赖

  • NVIDIA平台‌:需安装CUDA Toolkit,编译时添加--enable-cuda --enable-nvenc
  • Intel平台‌:需安装Intel Media SDK,编译时添加--enable-libmfx

六、硬件编码示例代码

支持从本地YUV文件读取数据、GPU加速编码并输出H.264视频流到MP4文件。

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libavutil/hwcontext.h>
#include <libswscale/swscale.h>

#define INPUT_FILE "input.yuv"
#define OUTPUT_FILE "output.mp4"
#define WIDTH      1280
#define HEIGHT     720
#define FRAME_RATE 30

int main() {
    AVFormatContext *fmt_ctx = NULL;
    AVCodecContext *enc_ctx = NULL;
    AVBufferRef *hw_device_ctx = NULL;
    SwsContext *sws_ctx = NULL;
    int ret = 0;

    // 1. 初始化硬件设备上下文
    ret = av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_CUDA, NULL, NULL, 0);  // ‌:ml-citation{ref="1,3" data="citationList"}
    if (ret < 0) {
        fprintf(stderr, "Failed to create CUDA device\n");
        goto cleanup;
    }

    // 2. 打开输出文件并配置封装格式
    avformat_alloc_output_context2(&fmt_ctx, NULL, NULL, OUTPUT_FILE);  // ‌:ml-citation{ref="8" data="citationList"}
    if (!fmt_ctx) {
        fprintf(stderr, "Failed to create output context\n");
        ret = -1;
        goto cleanup;
    }

    // 3. 查找并配置硬件编码器
    const AVCodec *encoder = avcodec_find_encoder_by_name("h264_nvenc");  // ‌:ml-citation{ref="1,4" data="citationList"}
    if (!encoder) {
        fprintf(stderr, "NVENC encoder not found\n");
        ret = -1;
        goto cleanup;
    }

    enc_ctx = avcodec_alloc_context3(encoder);
    enc_ctx->width = WIDTH;
    enc_ctx->height = HEIGHT;
    enc_ctx->time_base = (AVRational){1, FRAME_RATE};
    enc_ctx->pix_fmt = AV_PIX_FMT_CUDA;       // 硬件像素格式 ‌:ml-citation{ref="3,5" data="citationList"}
    enc_ctx->bit_rate = 4000000;              // 4Mbps码率 ‌:ml-citation{ref="5,8" data="citationList"}
    enc_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);  // 绑定设备 ‌:ml-citation{ref="3,6" data="citationList"}

    // 4. 打开编码器并添加视频流
    if ((ret = avcodec_open2(enc_ctx, encoder, NULL)) < 0) {  // ‌:ml-citation{ref="4" data="citationList"}
        fprintf(stderr, "Failed to open encoder\n");
        goto cleanup;
    }
    AVStream *stream = avformat_new_stream(fmt_ctx, NULL);
    avcodec_parameters_from_context(stream->codecpar, enc_ctx);

    // 5. 打开输出文件并写入头信息
    if (!(fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
        ret = avio_open(&fmt_ctx->pb, OUTPUT_FILE, AVIO_FLAG_WRITE);  // ‌:ml-citation{ref="8" data="citationList"}
        if (ret < 0) {
            fprintf(stderr, "Failed to open output file\n");
            goto cleanup;
        }
    }
    avformat_write_header(fmt_ctx, NULL);  // ‌:ml-citation{ref="8" data="citationList"}

    // 6. 初始化YUV到CUDA格式转换器
    sws_ctx = sws_getContext(WIDTH, HEIGHT, AV_PIX_FMT_YUV420P,
                             WIDTH, HEIGHT, AV_PIX_FMT_CUDA,
                             SWS_BILINEAR, NULL, NULL, NULL);  // ‌:ml-citation{ref="4,7" data="citationList"}

    // 7. 准备输入帧和硬件帧
    AVFrame *yuv_frame = av_frame_alloc();
    yuv_frame->width = WIDTH;
    yuv_frame->height = HEIGHT;
    yuv_frame->format = AV_PIX_FMT_YUV420P;
    av_frame_get_buffer(yuv_frame, 0);

    AVFrame *hw_frame = av_frame_alloc();
    hw_frame->format = enc_ctx->pix_fmt;
    hw_frame->width = WIDTH;
    hw_frame->height = HEIGHT;
    av_hwframe_get_buffer(enc_ctx->hw_frames_ctx, hw_frame, 0);  // ‌:ml-citation{ref="3,6" data="citationList"}

    // 8. 处理每一帧数据
    FILE *yuv_file = fopen(INPUT_FILE, "rb");
    AVPacket *pkt = av_packet_alloc();
    for (int i = 0; i < 100; i++) {  // 编码100帧测试
        // 从YUV文件读取数据
        fread(yuv_frame->data, 1, WIDTH*HEIGHT, yuv_file);          // Y分量
        fread(yuv_frame->data‌:ml-citation{ref="1" data="citationList"}, 1, (WIDTH/2)*(HEIGHT/2), yuv_file); // U分量
        fread(yuv_frame->data‌:ml-citation{ref="2" data="citationList"}, 1, (WIDTH/2)*(HEIGHT/2), yuv_file); // V分量

        // 转换到硬件帧
        sws_scale(sws_ctx, (const uint8_t**)yuv_frame->data, yuv_frame->linesize,
                  0, HEIGHT, hw_frame->data, hw_frame->linesize);  // ‌:ml-citation{ref="4,7" data="citationList"}

        // 编码并写入文件
        avcodec_send_frame(enc_ctx, hw_frame);
        while (avcodec_receive_packet(enc_ctx, pkt) >= 0) {
            av_packet_rescale_ts(pkt, enc_ctx->time_base, stream->time_base);
            av_interleaved_write_frame(fmt_ctx, pkt);  // ‌:ml-citation{ref="8" data="citationList"}
            av_packet_unref(pkt);
        }
    }

cleanup:
    // 9. 释放所有资源
    if (yuv_file) fclose(yuv_file);
    if (sws_ctx) sws_freeContext(sws_ctx);
    av_packet_free(&pkt);
    av_frame_free(&yuv_frame);
    av_frame_free(&hw_frame);
    if (fmt_ctx) avformat_free_context(fmt_ctx);
    avcodec_free_context(&enc_ctx);
    av_buffer_unref(&hw_device_ctx);
    return ret;
}

关键代码说明:‌

组件功能说明依赖项
hw_device_ctx管理CUDA设备上下文,用于GPU内存分配和硬件加速操作CUDA驱动和NVENC支持 ‌
sws_ctx将CPU端的YUV420P数据转换为GPU端的CUDA格式(如NV12)libswscale库 
av_hwframe_get_buffer直接从GPU显存分配帧内存,避免CPU-GPU内存拷贝FFmpeg硬件帧支持
avcodec_send_frame将硬件帧送入编码器队列,触发异步编码操作编码器线程模型

http://www.kler.cn/a/597723.html

相关文章:

  • 如何为在线游戏选择合适的游戏盾?
  • 互相关-信号增强
  • Verilog学习之TASK的使用
  • 【linux】scp和rsync
  • 深度学习-151-Dify工具之创建一个生成财务报表的智能体Agent
  • 使用bat批量获取WORD中包含对应字符的段落,段落使用回车换行
  • CEFPN
  • for循环 jdk8 stream Api写法
  • 爬虫逆向解决debugger问题
  • 社区医疗管理系统基于Spring Boot SSM
  • xss复现
  • 常见CMS漏洞之二:DeDeCMS
  • GGUF 和 llama.cpp 是什么关系
  • 用 pytorch 从零开始创建大语言模型(六):对分类进行微调
  • [leetcode]1263. 推箱子(A*+优先队列BFS+DFS)
  • 基于Redis分布锁+事务补偿解决数据不一致性问题
  • 游戏引擎学习第173天
  • MySQL 安全传输
  • 【leetcode hot 100 131】分割回文串
  • 2025-03-21 学习记录--C/C++-PTA 练习7-7 矩阵运算