使用FFmpeg调用NVIDIA GPU实现H265转码H264
-
- 背景
- H265和H264一些基本知识
-
- 1、H265码流nalu头
- 2、H264码流nalu头
- 3、补充:IDR帧和I帧的关系
- 转码的一些基本知识
-
- 1、软编码和硬编码如何区分
- 2、软编码和硬编码比较
- 3、目前的主流GPU加速平台
- 4、目前主流的GPU平台开发框架
- 5、流程区别
- NVIDIA+ffmpeg硬件加速部署
-
- 1、环境安装部署:Windows10 + ffmpeg4.1.3 + NVIDIA GeForce GTX 1660Ti
-
- 1.1 ffmpeg Windows版本的下载
- 1.2 下载NVIDIA驱动(GTX 1660Ti)
- 1.3 下载cuda toolkit(GTX 1660Ti)
- 1.4 用ffmpeg.exe -hwaccels显示所有可用的硬件加速器
- 1.5 用ffmpeg.exe -codecs查看编解码器支持
- NVENC介绍
- FFmpeg命令行硬件转码H265裸流文件
-
- 1、H265软件解码,H264硬件编码
- 2、全硬件转码(H265硬件解码,H264硬件编码)
- FFmpeg API进行H265裸流文件进行转码H264
-
- 1、主体代码
- 2、流程解析
- 3、遇到的问题
- 4、补充
- Download
- 参考
背景
最近项目遇到难题,就是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)
下载地址:
版本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进行判断)
3、把一帧H265送进去H265解码器进行软件解码
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