ffmpeg硬件编码

news2025/1/16 3:52:31

使用FFmpeg进行硬件编码可以显著提高视频编码的性能,尤其是在处理高分辨率视频时。硬件编码利用GPU或其他专用硬件(如Intel QSV、NVIDIA NVENC、AMD AMF等)来加速编码过程。以下是使用FFmpeg进行硬件编码的详细说明和示例代码。

 

1. 硬件编码支持的检查

在开始之前,确保你的系统支持硬件编码。可以通过以下命令检查FFmpeg支持的硬件编码器:

ffmpeg -hwaccels

 然后检查可用的硬件编码器:

ffmpeg -encoders | grep h264

2. 硬件编码的基本流程

硬件编码的基本流程与软件编码类似,但需要额外设置硬件设备上下文和硬件帧上下文。以下是主要步骤:

  1. 初始化硬件设备上下文:指定硬件加速类型(如QSV、CUDA等)。

  2. 创建硬件帧上下文:配置硬件帧的格式、分辨率等。

  3. 查找硬件编码器:如h264_qsvh264_nvenc等。

  4. 配置编码器上下文:绑定硬件帧上下文,设置编码参数。

  5. 编码帧:将帧数据发送到编码器,接收编码后的数据包。

  6. 写入输出文件:将编码后的数据包写入文件。

 3. 示例代码(Intel QSV 硬件编码)

#include <iostream>
#include <chrono>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/hwcontext.h>
#include <libswscale/swscale.h>
#include <libavutil/opt.h>
#include <libavutil/imgutils.h>
#ifdef HAVE_NVENC
#include <libnvenc/nvenc.h>
#endif
#ifdef HAVE_QSV
#include <libx264/x264.h> // 注意:QSV的实际头文件可能与此不同,这只是一个示例
#endif
}
#include <iostream>
#include <stdexcept>
#include <thread>
#include <chrono>
// 记录当前时间,用于计算编码过程的总耗时
auto nows = std::chrono::steady_clock::now();

// 输出文件名
const char* output_filename = "output.h264";
// 视频分辨率
const int width = 1920;
const int height = 1080;
// 帧率(30帧/秒)
const AVRational frame_rate = { 30, 1 };
// 硬件像素格式(Intel QSV)
const AVPixelFormat hw_pix_fmt = AV_PIX_FMT_QSV;
// 软件像素格式(NV12)
const AVPixelFormat sw_pix_fmt = AV_PIX_FMT_NV12;

// FFmpeg 相关上下文和结构体
AVFormatContext* fmt_ctx = nullptr;  // 输出文件上下文
AVCodecContext* codec_ctx = nullptr; // 编码器上下文
AVFrame* hw_frame = nullptr;         // 硬件帧
AVFrame* sw_frame = nullptr;         // 软件帧
AVPacket* pkt = nullptr;             // 编码后的数据包
AVBufferRef* hw_device_ctx = nullptr; // 硬件设备上下文

try {
    // 1. 初始化硬件设备上下文
    int ret = av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_QSV, nullptr, nullptr, 0);
    if (ret < 0) {
        throw std::runtime_error("Failed to create hardware device context");
    }

    // 2. 创建硬件帧上下文
    AVBufferRef* hw_frames_ref = av_hwframe_ctx_alloc(hw_device_ctx);
    if (!hw_frames_ref) {
        throw std::runtime_error("Failed to create hardware frames context");
    }

    // 配置硬件帧上下文参数
    AVHWFramesContext* hw_frames_ctx = (AVHWFramesContext*)hw_frames_ref->data;
    hw_frames_ctx->format = AV_PIX_FMT_QSV;       // 硬件像素格式
    hw_frames_ctx->sw_format = AV_PIX_FMT_NV12;   // 软件像素格式
    hw_frames_ctx->width = width;                 // 视频宽度
    hw_frames_ctx->height = height;               // 视频高度
    hw_frames_ctx->initial_pool_size = 20;        // 初始帧池大小

    // 初始化硬件帧上下文
    ret = av_hwframe_ctx_init(hw_frames_ref);
    if (ret < 0) {
        throw std::runtime_error("Failed to initialize hardware frames context");
    }

    // 3. 查找编码器(使用 Intel QSV 的 H.264 编码器)
    const AVCodec* codec = avcodec_find_encoder_by_name("h264_qsv");
    if (!codec) {
        throw std::runtime_error("Codec h264_qsv not found");
    }

    // 4. 创建编码器上下文
    codec_ctx = avcodec_alloc_context3(codec);
    if (!codec_ctx) {
        throw std::runtime_error("Could not allocate video codec context");
    }

    // 配置编码器参数
    codec_ctx->hw_frames_ctx = av_buffer_ref(hw_frames_ref); // 绑定硬件帧上下文
    codec_ctx->width = width;                                // 视频宽度
    codec_ctx->height = height;                              // 视频高度
    codec_ctx->time_base = av_inv_q(frame_rate);             // 时间基(帧率的倒数)
    codec_ctx->framerate = frame_rate;                       // 帧率
    codec_ctx->pix_fmt = AV_PIX_FMT_QSV;                     // 像素格式
    codec_ctx->bit_rate = 4000000;                           // 码率(4 Mbps)
    codec_ctx->gop_size = 1;                                 // GOP 大小(关键帧间隔)

    // 打开编码器
    ret = avcodec_open2(codec_ctx, codec, nullptr);
    if (ret < 0) {
        throw std::runtime_error("Could not open codec");
    }

    // 5. 创建硬件帧
    hw_frame = av_frame_alloc();
    if (!hw_frame) {
        throw std::runtime_error("Could not allocate video frame");
    }
    hw_frame->format = AV_PIX_FMT_QSV; // 硬件像素格式
    hw_frame->width = width;           // 视频宽度
    hw_frame->height = height;         // 视频高度

    // 为硬件帧分配内存
    ret = av_hwframe_get_buffer(av_buffer_ref(hw_frames_ref), hw_frame, 0);
    if (ret < 0) {
        throw std::runtime_error("Could not allocate hardware frame buffer");
    }

    // 6. 创建软件帧
    sw_frame = av_frame_alloc();
    if (!sw_frame) {
        throw std::runtime_error("Could not allocate software frame");
    }
    sw_frame->format = AV_PIX_FMT_NV12; // 软件像素格式
    sw_frame->width = width;            // 视频宽度
    sw_frame->height = height;          // 视频高度

    // 为软件帧分配内存
    ret = av_frame_get_buffer(sw_frame, 0);
    if (ret < 0) {
        throw std::runtime_error("Could not allocate software frame buffer");
    }

    // 7. 创建输出文件上下文
    ret = avformat_alloc_output_context2(&fmt_ctx, nullptr, nullptr, output_filename);
    if (ret < 0) {
        throw std::runtime_error("Could not create output context");
    }

    // 8. 创建视频流
    AVStream* stream = avformat_new_stream(fmt_ctx, nullptr);
    if (!stream) {
        throw std::runtime_error("Could not create video stream");
    }

    // 从编码器上下文复制参数到视频流
    avcodec_parameters_from_context(stream->codecpar, codec_ctx);
    stream->time_base = AVRational{ 1, 90000 }; // 时间基

    // 9. 打开输出文件
    if (!(fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
        ret = avio_open(&fmt_ctx->pb, output_filename, AVIO_FLAG_WRITE);
        if (ret < 0) {
            throw std::runtime_error("Could not open output file");
        }
    }

    // 10. 写入文件头
    ret = avformat_write_header(fmt_ctx, nullptr);
    if (ret < 0) {
        throw std::runtime_error("Error writing header to output file");
    }

    // 11. 编码帧
    FILE* f = fopen("asdhfladghakl.yuv", "rb"); // 打开 YUV 文件
    if (!f) {
        throw std::runtime_error("Could not open YUV file");
    }

    for (int i = 0; i < 50; i++) { // 编码 50 帧
        // 从 YUV 文件读取数据到软件帧
        fread(sw_frame->data[0], 1, width * height, f);      // Y 分量
        fread(sw_frame->data[1], 1, width * height / 2, f);  // UV 分量

        // 将软件帧数据拷贝到硬件帧
        ret = av_hwframe_transfer_data(hw_frame, sw_frame, 0);
        if (ret < 0) {
            throw std::runtime_error("Error transferring data to hardware frame");
        }

        // 设置帧的显示时间戳(PTS)
        hw_frame->pts = i * 3000; // PTS = 帧序号 * 帧间隔
        hw_frame->time_base = AVRational{ 1, 90000 }; // 时间基

        // 发送帧到编码器
        ret = avcodec_send_frame(codec_ctx, hw_frame);
        if (ret < 0) {
            throw std::runtime_error("Error sending frame to encoder");
        }

        // 接收编码后的数据包
        pkt = av_packet_alloc();
        while (ret >= 0) {
            ret = avcodec_receive_packet(codec_ctx, pkt);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                av_packet_free(&pkt);
                break;
            }

            // 设置数据包的流索引和时间基
            pkt->stream_index = stream->index;
            pkt->time_base = AVRational{ 1, 90000 };

            // 写入数据包到输出文件
            ret = av_interleaved_write_frame(fmt_ctx, pkt);
            if (ret < 0) {
                throw std::runtime_error("Error writing packet to file");
            }

            // 释放数据包
            av_packet_unref(pkt);
        }
    }

    // 12. 刷新编码器(发送空帧以刷新缓冲区)
    ret = avcodec_send_frame(codec_ctx, nullptr);
    while (ret >= 0) {
        pkt = av_packet_alloc();
        ret = avcodec_receive_packet(codec_ctx, pkt);
        if (ret == AVERROR_EOF) {
            break;
        }

        // 写入剩余的数据包到输出文件
        pkt->stream_index = stream->index;
        pkt->time_base = AVRational{ 1, 90000 };
        ret = av_interleaved_write_frame(fmt_ctx, pkt);
        av_packet_unref(pkt);
    }

    // 13. 写入文件尾
    av_write_trailer(fmt_ctx);

    // 关闭 YUV 文件
    fclose(f);

    std::cout << "Encoding completed successfully!" << std::endl;
}
catch (const std::exception& e) {
    std::cerr << "Error: " << e.what() << std::endl;
}

// 计算并输出编码过程的总耗时
std::cout << "Total time taken: "
          << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - nows).count()
          << " milliseconds" << std::endl;

// 14. 释放资源
if (fmt_ctx && !(fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
    avio_closep(&fmt_ctx->pb);
}
avformat_free_context(fmt_ctx);
av_frame_free(&hw_frame);
av_frame_free(&sw_frame);
av_packet_free(&pkt);
avcodec_free_context(&codec_ctx);
av_buffer_unref(&hw_device_ctx);

代码总结

  1. 硬件初始化:初始化 Intel QSV 硬件设备上下文和硬件帧上下文。

  2. 编码器设置:查找并配置 H.264 编码器,绑定硬件帧上下文。

  3. 帧处理:从 YUV 文件读取数据,拷贝到硬件帧,编码并写入输出文件。

  4. 资源释放:释放所有分配的资源,避免内存泄漏。

  5. 性能统计:计算并输出编码过程的总耗时。

 

可以看见运行编码得到明显提升 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2277326.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【高可用自动化体系】自动化体系

架构设计的愿景就是高可用、高性能、高扩展、高效率。为了实现架构设计四高愿景&#xff0c;需要实现自动化系统目标&#xff1a; 标准化。 流程自助化。 可视化&#xff1a;可观测系统各项指标、包括全链路跟踪。 自动化&#xff1a;ci/cd 自动化部署。 精细化&#xff1a…

elasticsearch中IK分词器

1、什么是IK分词器 ElasticSearch 几种常用分词器如下&#xff1a; 分词器分词方式StandardAnalyzer单字分词CJKAnalyzer二分法IKAnalyzer词库分词 分词∶即把一段中文或者别的划分成一个个的关键字&#xff0c;我们在搜索时候会把自己的信息进行分词&#xff0c;会把数据库…

arcgis中生成格网矢量带高度

效果 1、数据准备 (1)矢量边界(miain.shp) (2)DEM(用于提取格网标高) (3)DSM(用于提取格网最高点) 2、根据矢量范围生成格网 模板范围选择矢量边界,像元宽度和高度根据坐标系来输入,我这边是4326的,所以输入的是弧度,输出格网矢量gewang.shp 3、分区统计 …

一文了解如何使用 DBeaver 管理 DolphinDB

在日常的数据开发、分析和数据库运维中&#xff0c;一款优秀的 IDE 能够极大地提升工作效率。DBEaver 是一款由 Java 编写的一站式跨平台连接器&#xff0c;其社区版本已能支持连接近百种数据库&#xff0c;受到广大开发者的喜爱。近期。DolphinDB 与 DBeaver 团队共同努力&…

【ArcGIS微课1000例】0138:ArcGIS栅格数据每个像元值转为Excel文本进行统计分析、做图表

本文讲述在ArcGIS中,以globeland30数据为例,将栅格数据每个像元值转为Excel文本,便于在Excel中进行统计分析。 文章目录 一、加载globeland30数据二、栅格转点三、像元值提取至点四、Excel打开一、加载globeland30数据 打开配套实验数据包中的0138.rar中的tif格式栅格土地覆…

JVM之垃圾回收器ZGC概述以及垃圾回收器总结的详细解析

ZGC ZGC 收集器是一个可伸缩的、低延迟的垃圾收集器&#xff0c;基于 Region 内存布局的&#xff0c;不设分代&#xff0c;使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记压缩算法 在 CMS 和 G1 中都用到了写屏障&#xff0c;而 ZGC 用到了读屏障 染色指针&a…

C# XPTable 日期字段处理(XPTable控件使用说明十三)

1、SQLite数据库定义为日期类型 2、XPtable中日期字段定义与显示 //显示时间表columnModel1.Columns.Clear();columnModel1.Columns.Add(new NumberColumn("id", 30));NumberColumn numberColumn new NumberColumn("次数", 50);numberColumn.Maximum 100…

【pycharm发现找不到python打包工具,且无法下载】

发现找不到python打包工具,且无法下载 解决方法&#xff1a; 第一步&#xff1a;安装distutils&#xff0c;在CMD命令行输入&#xff1a; python -m ensurepip --default-pip第二步&#xff1a;检查和安装setuptools和wheel&#xff1a; python -m pip install --upgrade …

晨辉面试抽签和评分管理系统之六:面试答题倒计时

晨辉面试抽签和评分管理系统&#xff08;下载地址:www.chenhuisoft.cn&#xff09;是公务员招录面试、教师资格考试面试、企业招录面试等各类面试通用的考生编排、考生入场抽签、候考室倒计时管理、面试考官抽签、面试评分记录和成绩核算的面试全流程信息化管理软件。提供了考生…

王炸组合:Dolphinscheudler 3.1.*搭配SeaT unnel2.3.*高效完成异构数据数据集成

概述 本篇主要介绍如何通过Dolphinscheduler海豚调度搭配Seatunnel完成异构数据源之间的数据同步功能&#xff0c;这个在大数据流批一体数仓建设的过程中是一个非常好的解决方案&#xff0c; 稳定高效&#xff0c;只要用上了你肯定爱不释手。 环境准备 dolphinscheduler集群…

【AI日记】25.01.11 Weights Biases | AI 笔记 notion

【AI论文解读】【AI知识点】【AI小项目】【AI战略思考】【AI日记】【读书与思考】 AI kaggle 比赛&#xff1a;Forecasting Sticker Sales笔记&#xff1a;我的 AI 笔记主要记在两个地方 有道云笔记&#xff1a;数学公式和符号比较多的笔记notion&#xff1a;没什么数学公式的…

Oracle EBS GL定期盘存WIP日记账无法过账数据修复

系统环境 RDBMS : 12.1.0.2.0 Oracle Applications : 12.2.6 问题症状 用户反映来源为“定期盘存”和类别为“WIP”的日记账无法过账,标准日记账的界面上的过账按钮灰色不可用。但是,在超级用户职责下,该日记账又可以过账,细心检查发现该业务实体下有二个公司段值15100和…

欧拉路径算法

欧拉图&#xff1a; 对于应该连通图G&#xff0c;有&#xff1a; 1欧拉路径&#xff1a;一条路径&#xff0c;它能够不重复地遍历完所有的边&#xff0c;这个性质很像不重复地一笔画完所有边&#xff0c;所以有些涉及到欧拉路径的问题叫做一笔画问题。 2欧拉回路&#xff1a…

【进程与线程】程序和进程在内存中的表现

在计算机系统中&#xff0c;程序和进程是两个密切相关但又有本质区别的概念&#xff0c;尤其在内存中的表现上有显著不同&#xff1a; 在这张图中可以直观地看出程序和进程在内存中的结构区别。 基本定义 程序 程序 是一个 静态实体&#xff0c;表示一组写好的指令和数据的…

“多维像素”多模态雷视融合技术构建自动驾驶超级感知能力|上海昱感微电子创始人蒋宏GADS演讲预告

2025年1月14日&#xff0c;第四届全球自动驾驶峰会将在北京中关村国家自主创新示范区展示交易中心-会议中心举行。经过三年的发展&#xff0c;全球自动驾驶峰会已经成长为国内自动驾驶领域最具影响力、规模最大的产业峰会之一。昱感微电子创始人&CEO受到主办方邀请&#xf…

Linux创建server服务器实现多方信息收发

一&#xff0c;服务端 1.创建socket套接字&#xff0c;用于网络通信&#xff0c;同一台机器上的进程也可以通过本地套接字进行通信 //1.socket s_fd socket(AF_INET,SOCK_STREAM,0); if(s_fd -1){ perror("socket"); exit(-1); } //server address s_addr.sin_fami…

UML系列之Rational Rose笔记七:状态图

一、新建状态图 依旧是新建statechart diagram&#xff1b; 二、工作台介绍 接着就是一个状态的开始&#xff1a;开始黑点依旧可以从左边进行拖动放置&#xff1a; 这就是状态的开始&#xff0c;和活动图泳道图是一样的&#xff1b;只能有一个开始&#xff0c;但是可以有多个…

jsx语法中el-table-v2中cellRender如何使用动态绑定

答案&#xff1a;:attribute"xx"改为attribute{xx} 改写&#xff1a; const columns ref([{ key: index, dataKey: index, title: t(setting.index), width: 100 },{ key: no, dataKey: no, title: t(setting.key), width: 100 },{ key: name, dataKey: name, tit…

【初识扫盲】厚尾分布

厚尾分布&#xff08;Fat-tailed distribution&#xff09;是一种概率分布&#xff0c;其尾部比正态分布更“厚”&#xff0c;即尾部的概率密度更大&#xff0c;极端值出现的概率更高。 一、厚尾分布的特征 尾部概率大 在正态分布中&#xff0c;极端值&#xff08;如距离均值很…

EFK采集k8s日志

在 Kubernetes 集群中&#xff0c;需要全面了解各个 pod 应用运行状态、故障排查和性能分析。但由于 Pod 是动态创建和销毁的&#xff0c;其日志分散且存储不持久&#xff0c;因此需要通过集中式日志采集方案&#xff0c;将日志收集到统一的平台并配置日志可视化分析和监控告警…