ffmpeg实现硬件转码(使用FFmpeg调用NVIDIA GPU实现H265转码H264)

2023-11-04

使用FFmpeg调用NVIDIA GPU实现H265转码H264

背景

最近项目遇到难题,就是web前端需要播放视频设备出来的H265码流,但目前只支持播放H264的码流,所以想快速解决这个问题的话,我想出了两种解决方案。

方案1:直接对H265进行RTMP封装成自定义FLV发布给前端播放,web前端得支持解析H265播放的控件,如果使用这种方案目前基本很难找到适用的开源方案,而且后端和前端的改动可以说基本是推倒重来,所以这种方案在短时间内是很难实现的。
方案2:做一个转码服务对H265进行解码再编码成H264,web前端播放方案就无需做任何改动,转码的话有软件转码和硬件转码两种方案,由于软件转码非常耗CPU资源,基本可以排除这种想法,那就只能考虑硬件转码的方案了,本文下面要介绍的就是硬件转码的方案。

对比这两种方案,第二种方案相对会合理一些,能比较快速解决H265的播放问题。

H265和H264一些基本知识

1、H265码流nalu头

00 00 00 01 40 01 的nuh_unit_type的值为 32, 语义为视频参数集 VPS
00 00 00 01 42 01 的nuh_unit_type的值为 33, 语义为序列参数集 SPS
00 00 00 01 44 01 的nuh_unit_type的值为 34, 语义为图像参数集 PPS
00 00 00 01 4E 01 的nuh_unit_type的值为 39, 语义为补充增强信息 SEI
00 00 00 01 26 01 的nuh_unit_type的值为 19, 语义为可能有RADL图像的IDR图像的SS编码数据 IDR
00 00 00 01 02 01 的nuh_unit_type的值为 1, 语义为被参考的后置图像,且非TSA、非STSA的SS编码数据

2、H264码流nalu头

00 00 00 01 06 type的值为 06, NALU_TYPE_SEI 语义为补充增强信息 SEI
00 00 00 01 67 type的值为 67, NALU_TYPE_SPS 语义为序列参数集 SPS
00 00 00 01 68 type的值为 68, NALU_TYPE_PPS 语义为图像参数集 PPS
00 00 00 01 65 type的值为 65, NALU_TYPE_IDR 语义为IDR图像中的片 IDR

3、补充:IDR帧和I帧的关系

IDR帧就是I帧,但是I帧不一定是IDR帧,在一个完整的视频流单元中第一个图像帧是IDR帧,IDR帧是强制刷新帧,在解码过程中,当出现了IDR帧时,要更新sps、pps,原因是防止前面I帧错误,导致sps,pps参考I帧导致无法纠正。
再普及一个概念是GOP,GOP的全称是Group of picture图像组,也就是两个I帧之间的距离,GOP值越大,那么I帧率之间P帧和B帧数量越多,图像画质越精细,如果GOP是120,如果分辨率是720P,帧率是60,那么两I帧的时间就是120/60=2s.

转码的一些基本知识

1、软编码和硬编码如何区分

软编码:使用CPU进行编码
硬编码:使用非CPU进行编码,如显卡GPU、专用的DSP、FPGA芯片等

2、软编码和硬编码比较

软编码:实现直接、简单,参数调整方便,升级易,但CPU负载重,性能较硬编码低,低码率下质量通常比硬编码要好一点。
硬编码:性能高,低码率下通常质量低于软编码器,但部分产品在GPU硬件平台移植了优秀的软编码算法(如X264)的,质量基本等同于软编码。

3、目前的主流GPU加速平台

NVIDIA、INTEL、AMD等

4、目前主流的GPU平台开发框架

CUDA:NVIDIA的封闭编程框架,通过框架可以调用GPU计算资源
AMD APP:AMD为自己的GPU提出的一套通用并行编程框架,标准开放,通过在CPU、GPU同时支持OpenCL框架,进行计算力融合。
OpenCL:开放计算语言,为异构平台编写程序的该框架,异构平台可包含CPU、GPU以及其他计算处理器,目标是使相同的运算能支持不同平台硬件加速。
Inel QuickSync:集成于Intel显卡中的专用视频编解码模块。

5、流程区别

硬解软编:read(ffmpeg)->decoder(NVIDIA cuvid)->encoder(ffmpeg)
软解软编:read(ffmpeg)->decoder(ffmpeg)->encoder(ffmpeg)
软解硬编:read(ffmpeg)->decoder(ffmpeg)->encoder(NVIDIA nvenc)

NVIDIA+ffmpeg硬件加速部署

1、环境安装部署:Windows10 + ffmpeg4.1.3 + NVIDIA GeForce GTX 1660Ti

注意:需要你的电脑上有如下https://developer.nvidia.com/video-encode-decode-gpu-support-matrix#Encoder之一的NVIDIA显卡,我自己使用的是GeForce GTX 1660Ti这个型号的显卡(在NVIDIA tesla T4这块显卡上面也验证过,可以同时转码八路以上)

1.1 ffmpeg Windows版本的下载

下载地址:https://ffmpeg.zeranoe.com/builds/
在这里插入图片描述

支持下载static/dev/shared
我用的ffmpeg版本是4.1.3

1.2 下载NVIDIA驱动(GTX 1660Ti)

下载地址:https://www.nvidia.com/download/index.aspx?lang=en-us
在这里插入图片描述
版本451.77 (11.0)地址:https://www.nvidia.cn/Download/driverResults.aspx/162530/cn
在这里插入图片描述

1.3 下载cuda toolkit(GTX 1660Ti)

下载地址:https://developer.nvidia.com/cuda-toolkit
版本451.48(11.0)地址:https://developer.nvidia.com/cuda-downloads?target_os=Windows&target_arch=x86_64&target_version=10&target_type=exelocal
在这里插入图片描述

安装就不做介绍了。

1.4 用ffmpeg.exe -hwaccels显示所有可用的硬件加速器

我下载的FFmpeg就已经包含了英伟达的硬件加速器了
在这里插入图片描述

1.5 用ffmpeg.exe -codecs查看编解码器支持

DEV.LS h264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 (decoders: h264 h264_qsv h264_cuvid ) (encoders: libx264 libx264rgb h264_amf h264_nvenc h264_qsv nvenc nvenc_h264 )
DEV.L. hevc H.265 / HEVC (High Efficiency Video Coding) (decoders: hevc hevc_qsv hevc_cuvid ) (encoders: libx265 nvenc_hevc hevc_amf hevc_nvenc hevc_qsv )
h264_cuvid:h264硬件解码器
h264_nvenc:h264硬件编码器
hevc_cuvid:h265硬件解码器
hevc_nvenc:h265硬件编码器
在这里插入图片描述

NVENC介绍

NVENC是由NVIDIA开发的一个API允许使用NVIDIA GPU显卡执行h.264和HEVC(就是H.265)编码。FFmpeg通过h264_nvenc和hevc_nvenc编码器支持NVENC。

为了在FFmpeg中启用它,你需要:
一个支持硬件编解码的英伟达GPU
英伟达GPU驱动程序
没有配置——disable-nvenc的ffmpeg

使用的例子:

ffmpeg -i input -c:v h264_nvenc -profile high444p -pixel_format yuv444p -preset default output.mp4

你可以通过ffmpeg -h encoder=h264_nvenc或ffmpeg -h encoder=hevc_nvenc看到可用的预设值、其他选项和编码器信息。
注意:如果你发现没有NVENC功能的设备的错误,请确保你的编码是支持的像素格式。见编码器信息如上所示。

CUDA / CUVID / NvDecode
CUVID现在也被Nvidia称为nvdec,可以在Windows和Linux上进行解码。结合nvenc,它提供了完整的硬件转码。
CUVID提供H264, HEVC, MJPEG, mpeg1/2/4, vp8/9, vc1解码器。编解码器支持因硬件而异。

1、使用CUVID解码器,本例中CUVID解码器将帧复制到系统内存中:

ffmpeg -c:v h264_cuvid -i input output.mkv

2、使用CUVID和NVENC实现全硬件转码:

ffmpeg -hwaccel cuvid -c:v h264_cuvid -i input -c:v h264_nvenc -preset slow output.mkv

3、部分硬件转码,帧通过系统内存(这是必要的转码10位内容):

ffmpeg -c:v h264_cuvid -i input -c:v h264_nvenc -preset slow output.mkv

4、如果编译ffmpeg时支持libnpp,可以使用它在链中插入一个基于GPU的scaler:

ffmpeg -hwaccel_device 0 -hwaccel cuvid -c:v h264_cuvid -i input -vf scale_npp=-1:720 -c:v h264_nvenc -preset slow output.mkv

hwaccel_device选项可以用来指定ffmpeg中的cuvid hwaccel要使用的GPU:。

FFmpeg命令行硬件转码H265裸流文件

使用NVIDIA GTX1660ti显卡 + ffmpeg4.1.3

1、H265软件解码,H264硬件编码

ffmpeg.exe -i h265toh264.h265 -vcodec h264_nvenc -r 30 -y h265toh264.h264

2、全硬件转码(H265硬件解码,H264硬件编码)

ffmpeg.exe -hwaccel cuvid -c:v hevc_cuvid -i h265toh264.h265 -c:v h264_nvenc -r 30 -y h265toh264.h264

FFmpeg API进行H265裸流文件进行转码H264

在VS2017工程下面使用ffmpeg API的方式实现H265的软件解码成YUV并使用h264_nvenc(NVIDIA硬件编码器)或libx264(h264软件编码器)实现YUV编码成H264.
直接上源码:

1、主体代码

#include "H265Decoder.h"
#include "H264Encoder.h"

#define ISSTORAGEYUV 0

int main()
{
	int fpts = 0;
	char frame_buf[1024] = { 0 };
	int H265Lenth = 0;
	char H265Buf[1024 * 512];

	H265Decoder h265Decoder;
	H264Encoder h264Encoder;
	//初始化编解码器时需要把正确的分辨率送进去
	/*h265Decoder.Init(1920, 1080);
	h264Encoder.Init(1920, 1080);*/
	h265Decoder.Init(480, 272);
	h264Encoder.Init(480, 272);
	
	//读取文件
	FILE * InFile = fopen("h265toh264.h265", "rb");
	//输出文件
#if ISSTORAGEYUV
	FILE * OutFile = fopen("h265toYUV.YUV", "wb");
#else
	FILE * OutFile = fopen("h265toh264.h264", "wb");
#endif
	//打印ffmpeg输出日志等级
	printf("log level %d \n", av_log_get_level());
	//设置ffmpeg输出日志等级
	av_log_set_level(AV_LOG_INFO);
	
	while (true)
	{
		int iReadSize = fread(frame_buf, 1, 512, InFile);
		if (iReadSize <= 0)
		{
			break;
		}
		memcpy(H265Buf + H265Lenth, frame_buf, iReadSize);
		H265Lenth = H265Lenth + iReadSize;
		//获取一帧数据
		while (true)
		{
			bool OneFrame = false;
			if (H265Lenth <= 8)
			{
				break;
			}
			for (int i = 4; i < H265Lenth - 4; i++)
			{
				//解析H265的nalu头
				if (H265Buf[i] == 0x00 && H265Buf[i + 1] == 0x00 && H265Buf[i + 2] == 0x00 && H265Buf[i + 3] == 0x01)
				{
					h265Decoder.AddData(H265Buf, i);
					while (true)
					{
						char * pOutData = NULL;
						int OutSize = 0;
#if ISSTORAGEYUV
						//获取H265解码后的YUV并存文件
						if (!h265Decoder.GetYUVData(pOutData, OutSize))
						{
							break;
						}
						fwrite(pOutData, 1, OutSize, OutFile);
#else
						//获取H265解码frame并编码H264存文件
						AVFrame *frame = NULL;
						frame = h265Decoder.GetData();
						if (!frame) {
							break;
						}
						frame->pts = fpts++;
						h264Encoder.encode(frame, pOutData, OutSize);
						fwrite(pOutData, 1, OutSize, OutFile);
#endif

					}
					H265Lenth = H265Lenth - i;
					memcpy(H265Buf, H265Buf + i, H265Lenth);
					OneFrame = true;
					break;
				}
			}
			if (OneFrame)
			{
				continue;
			}
			else
			{
				break;
			}
		}
	}
	if (OutFile) {
		fclose(OutFile);
		OutFile = NULL;
	}
	if (InFile) {
		fclose(InFile);
		InFile = NULL;
	}
	printf("pasing end\r\n");
	return 0;
}

2、流程解析

1、初始化编解码器,需要把H265正确的分辨率格式送进去初始化

	h265Decoder.Init(480, 272);
	h264Encoder.Init(480, 272);

2、循环读取H265文件,组成一帧完整的H265(根据H265的nalu头00 00 00 01进行判断)

	if (H265Buf[i] == 0x00 && H265Buf[i + 1] == 0x00 && H265Buf[i + 2] == 0x00 && H265Buf[i + 3] == 0x01)

3、把一帧H265送进去H265解码器进行软件解码

	h265Decoder.AddData(H265Buf, i);

4、获取H265解码后的YUV可以选择存YUV文件或送进去H264编码器进行编码后存成H264文件

#if ISSTORAGEYUV
			//获取H265解码后的YUV并存文件
			if (!h265Decoder.GetYUVData(pOutData, OutSize))
			{
				break;
			}
			fwrite(pOutData, 1, OutSize, OutFile);
#else
			//获取H265解码frame并编码H264存文件
			AVFrame *frame = NULL;
			frame = h265Decoder.GetData();
			if (!frame) {
				break;
			}
			frame->pts = fpts++;
			h264Encoder.encode(frame, pOutData, OutSize);
			fwrite(pOutData, 1, OutSize, OutFile);
#endif

3、遇到的问题

调试期间,有遇到调用h264_nvenc硬件编码器的时候出错,排查后发现,H265解码后的原始视频格式是YUVJ420P,原因是有的视频设备出来的H265视频流,编码前的数据是YUVJ420P,不全部都是YUV420P,如果直接把这个原始的YUVJ420P送进去NVIDIA硬件编码器的时候会直接导致程序奔溃,后来经过一番排查,才发现原来NVIDIA是不支持对YUVJ420P直接进行编码的,如下图所示,只对YUV420P或者YUV444P支持H264编码。
在这里插入图片描述针对这个问题,只能用ffmpeg的sws_scale把YUVJ420P转换成YUV420P后再送给h264_nvenc硬件编码器进行编码。

#if ISCHANGEFRAME
	if (frame->format == AV_PIX_FMT_YUVJ420P) {
		sws_scale(convert_ctx,frame->data, frame->linesize, 0, Height, frameYUV->data, frameYUV->linesize);
		frameYUV->format = AV_PIX_FMT_YUV420P;
		frameYUV->width = Width;
		frameYUV->height = Height;
		return frameYUV;
	}
#endif

4、补充

1、libx264是可以对YUVJ420P直接进行264编码的,不需要做格式转换。
2、YUVJ420P和YUV420P简单介绍
YUVJ420P的字面含义是“使用了JPEG颜色范围的YUV420P,像素使用表示颜色的数值范围发生了变化。
YUV420p的像素颜色范围是[16,235],16表示黑色,235表示白色
YUVJ420P的像素颜色范围是[0,255]。0表示黑色,255表示白色
FFmpeg的定义和解释:

AV_PIX_FMT_YUV420P,   ///< planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per 2x2 Y samples)
AV_PIX_FMT_YUVJ420P,  ///< planar YUV 4:2:0, 12bpp, full scale (JPEG), deprecated in favor of AV_PIX_FMT_YUV420P and setting color_range

Download

1、测试视频
2、FFmpeg API H265转码H264源码

参考

1、https://developer.nvidia.com/ffmpeg

2、ffmpeg nvidia硬件加速方案(参考的博客)
https://blog.csdn.net/zhengbin6072/article/details/88202268

3、ffmpeg关于硬解码和软解码一些参考
https://www.cnblogs.com/chenpingzhao/p/12359725.html

4、YUV420P和YUVJ420P介绍
https://blog.csdn.net/samsung12345678/article/details/102383708

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ffmpeg实现硬件转码(使用FFmpeg调用NVIDIA GPU实现H265转码H264) 的相关文章

随机推荐

  • 【最清晰】ThreadLocal和局部变量和成员变量的区别

    ThreadLocal是进程级别的全局变量 最近有一个疑惑 为什么线程类的局部变量不能完全替代ThreadLocal 每一次new 线程都是创建了一个副本啊照理来说也是独立的 为什么还需要ThreadLocal 实际上确实是独立的 但是答案
  • postman token 请求头添加

    思路 1 登录成功后将 得到的token设置为集合变量 2 在需要携带Authorization的请求头上使用该集合变量 关键代码 const responseData pm response json if responseData co
  • JsoupDemo123_Jsoup_三种解析方法(parse)

    文章目录 Jsoup 工具类 1 Jsoup快速入门 解析XML文件 2 parse String html 解析字符串的 3 解析URL parse URL url int timeoutMillis Jsoup 工具类 可以解析HTML
  • 数据结构(树的结构与定义)

    树的定义 树是由结点或顶点和边组成的 可能是非线性的 且不存在着任何环的一种数据结构 没有结点的树称为空 null或empty 树 一棵非空的树包括一个根结点 还 很可能 有多个附加结点 所有结点构成一个多级分层结构 二叉树的定义 每个结点
  • 一份DBA面试题及解答(zt)

    今天在浏览网页时 无意发现了这篇文章 觉得很好 一份DBA面试题及解答 zt 作者 xsb http xsb itpub net 发表于 2006 03 17 13 29 分类 Oracle 出处 http xsb itpub net po
  • dedecms添加搜索功能:

    1 在模板目录中新建模板文件 search htm 文件名是固定的 因为后台文件是根据这个文件名做判断的 2 在form表单中的action改为 dede global cfg cmsurl plus search php 把input中的
  • Java后端技术栈的应用

    作者 禅与计算机程序设计艺术 1 简介 在互联网企业中 Java技术占据了最重要的角色 原因很简单 Java已经成为主流开发语言 拥有众多优秀的第三方框架 工具和库 足以支撑企业在全球范围内的业务发展 由于Java具有跨平台特性 安全性高
  • Tracy IOS 小笔记 Xcode

    Xcode 是运行在操作系统 Mac OS X上的集成开发工具 IDE 新建项目 Hello world 添加用户界面元素 这些控件都在 对象库 中 就是有导航 一个 立方体icon 的按钮 查看控件属性是在 有导航的一个 向下夹头两边有两
  • 数码相框实现遍历文件夹图片文件

    遍历文件夹图片文件 一 功能介绍 在为数码相框所在文件系统实现U盘自动挂载之后 将U盘自动挂载在开发板上文件系统中的 mnt usb目录 故还需为数码相框添加遍历 mnt usb路径下的文件夹内图片文件 暂定为扫描指定目录下一层文件夹内的图
  • npm安装报Error: EPERM: operation not permitted, mkdir ‘D:\Program Files\nodejs\node_cache\_cacache

    node js安装完成后执行命令 npm install express g 报如下错误 npm ERR code EPERM npm ERR syscall mkdir npm ERR path D Program Files nodej
  • OpenWrt结构分析

    openwrt项目目录 目录 内容描述 config 编译选项配置文件 包含全局编译设置 开发人员设置和内核编译设置 include 准备环境脚本 下载补丁脚本 编译Makefile和编译指令 Openwrt的很多Makefile都存放在这
  • 微信小程序:使用canvas 生成图片 并分享

    废话不多说直接上代码 html
  • 云服务器ECS远程监控

    云服务器ECS Elastic Compute Service 是阿里云提供的IaaS Infrastructure as a Service 级别云计算服务 ECS免去了用户采购IT硬件的前期准备 实现计算资源的即开即用和弹性伸缩 一 E
  • TCP拥塞控制技术 与BBR的加速原理

    什么是拥塞 拥塞现象 是指数据到达通信子网的过程中 某一部分的分组数量过多 使得该部分网络来不及处理 以致引起这部分乃至整个网络性能下降的现象 严重时会导致网络陷入死锁 这种现象好比公路上常见的交通拥挤 当节假日公路车辆大量增加时 道路拥堵
  • 计算机网络笔记之22(2) IP数据报的封装(Encapsulation),分段(Fragmentation),重组(Reassembly)

    由于本人只是个大二学生 所以请各位有不同意见绝对绝对的欢迎批评指正与补充 感谢 一起进步 数据报的装帧 Frames 和传输 Transmission IP软件先选出下一跳的站点 然后通过物理网络传输数据报 IP协议是网路层 独立于硬件存在
  • 更改当前文件夹及文件夹下文件日期shell脚本

    bin bash function changeName new echo 1 sed s abc g new echo 1 sed r s abc 1 g new echo 1 sed r s 20220630 20220707 g 以6
  • k8s笔记20--基于 K8S 的 cicd 概述

    k8s笔记20 基于 K8S 的 cicd 概述 1 介绍 2 方案实施 2 1 Jenkins kubectl k8s 2 2 Jenkins helm k8s 2 3 Zadig helm k8s 3 注意事项 4 说明 1 介绍 近年
  • Codeforces Round #364 (Div. 2)【贪心、数学、尺取】

    Codeforces 701 A Cards 直接贪心即可 写法各异 include
  • 算法:优先队列-实战

    实战的题目都是leetcode的题目 目录 leetcode 703实时判断数据流中第K大的元素 方法一 直接快速排序 方法二 创建长度为K的数组 判断最小元素 第三种方法 运用小顶堆代替 长度为K的数组 判断最小元素 leetcode 2
  • ffmpeg实现硬件转码(使用FFmpeg调用NVIDIA GPU实现H265转码H264)

    使用FFmpeg调用NVIDIA GPU实现H265转码H264 背景 H265和H264一些基本知识 1 H265码流nalu头 2 H264码流nalu头 3 补充 IDR帧和I帧的关系 转码的一些基本知识 1 软编码和硬编码如何区分