硬编码支持情况(一)
图片信息原文链接:http://trac.ffmpeg.org/wiki/HWAccelIntro
截个图:
注:
(一):Intel 平台
1:Intel 平台的Quick Sync Video(qsv)是对于音视频编解码的框架具体对外接口则为上面图中的LIBMFX,可以理解为Quick Sync Video(qsv)是对外宣称的技术ffmpeg对外设备TYPE选择也是这个,但如果不用ffmpeg直接用intel自身api则操作的是LIBMFX库提供的对外API接口。
2:SDK下载地址:https://github.com/Intel-Media-SDK/MediaSDK,需要安装。
3:samples下载地址:https://github.com/Intel-Media-SDK/samples/tree/4293f965b42310c043bc49d23c6f9f5d3d587dd8,里面有编码和解码的samples。
4:【Intel(R)_Media_SDK】官方文档翻译摘要:https://blog.csdn.net/zhuweigangzwg/article/details/83861644。
5:如果开发程序需要添加的lib库如下:
//用intel的mfx的media库command工程中引用如下才能编译成功生成lib库。
//本项目再引用此lib库。
/*
..\include;
$(INTELMEDIASDKROOT); //intelmediasdk
..\include\vm;
*/
/*
本工程添加的头文件目录
..\INTEL_MFX\include;
$(INTELMEDIASDKROOT)\include; //intelmediasdk
添加的lib库目录:..\Debug; ..\Release; 即上面command生成的lib库。
添加lib库目录:$(INTELMEDIASDKROOT)\lib\$(Platform) //intelmediasdk
*/
//添加command库
#pragma comment(lib,"libmfx_vs2015.lib")
#pragma comment(lib,"dxva2.lib")
#pragma comment(lib,"d3d9.lib")
#pragma comment(lib,"d3d11.lib")
#pragma comment(lib,"dwmapi.lib")
#pragma comment(lib,"comsuppw.lib")
#pragma comment(lib,"dxgi.lib")
//目录C:\Program Files (x86)\IntelSWTools\Intel(R) Media SDK 2018 R2\Software Development Kit\lib
#pragma comment(lib,"libmfx.lib")
//目录C:\Program Files (x86)\IntelSWTools\Intel(R) Media SDK 2018 R2\Software Development Kit\lib
#pragma comment(lib,"libmfx_vs2015.lib")
#pragma comment(lib,"command.lib")
6:CBuffering::m_pSurfaces内存管理的类,由于此类中的一些函数或变量是protected的,
只有用c++继承方式,但这里是c语言,需要修改源码protected为public,文件是mfx_buffering.h,函数和变量都修改为public。
7:mfx解码时候的5个surface,输入两个在(1)中说明,输出两个在(2)中说明,(3)中是个临时变量为解决解码参数问题。
如果输出的surface不用直接将(3)的解码后的surface用于输出也可以。
(1)CurrentFreeSurface是空闲的surface,传入解码器,用于解码,如果在解码途中则需要转给m_UsedSurfacesPool队列。
即这段数据正在解码被锁住则传入正在使用的surface队列中
m_FreeSurfacesPool队列中取出的m_pCurrentFreeInSurface和m_UsedSurfacesPool都是用于存放对H264处理的surface.都是输入surface。current字段是防止和系统的变量名称冲突。
(2)CurrentFreeOutputSurface是空闲的输出surface,传入解码器,用于接收解码之后的yuv数据,接收之后传给m_OutputSurfacesPool队列。GetFreeOutputSurface中取出的m_pCurrentFreeOutputSurface和m_OutputSurfacesPool都是用于存放解码后的yuv数据surface,都是输出队列。
(3)引入m_pOutSurface这个变量只是为了适应DecodeFrameAsync这个函数传入**指针的操作,如果可以用m_pCurrentFreeOutputSurface也可以。
8:这里一定要注意有个大坑:
mfxBitstream * pBitstream 这个变量中就是存放一帧要解码数据的结构体其中,MaxLength:申请的内存大小,DataLength:解码前是传入的数据大小,解码后是剩余的数据大小。DataOffset:解码前为0,解码后实际解码了多少数据。
解码后的状态码MFX_ERR_MORE_DATA == sts时pBitstream->DataLength一定等于0这时候再读取一帧数据没问题。
如果MFX_ERR_NONE == sts时分为两种情况,1:pBitstream->DataLength == 0,这时再去读取一帧数据。
如果MFX_ERR_NONE == sts时分为两种情况,2:pBitstream->DataLength != 0,这时一定不能读取新的数据,要继续用剩下的数据去解码.否则会有帧减少产生图像花屏绿屏情况。
也就是说解码传进去一帧数据有可能返回的时候剩余一些字段,需要将这些字段再传给解码器做解码,而不是再读取一帧用于解码这个值就是pBitstream->DataLength。
9:mfxEncodeCtrl encCtrl; //编码的帧类型和一些扩展信息存储结构体。只是扩展用填写为NULL也可以如果没有扩展。
//编码时这个值必须清零否则EncodeFrameAsync会失败返回-15.
MSDK_ZERO_MEMORY(encCtrl);
编码后需要做异步同步操作。//同步操作,输出数据到m_mfxBS中
sts = m_mfxSession.SyncOperation(UserSyncp, MSDK_WAIT_INTERVAL);
10:mfx 填写qp时,unsigned short UserRateControlMethod = MFX_RATECONTROL_CQP; //CBR,ABR,CQP
m_mfxEncParams.mfx.RateControlMethod = UserRateControlMethod; //CBR,ABR,CQP
if (UserRateControlMethod == MFX_RATECONTROL_CQP)
{
m_mfxEncParams.mfx.QPI = 24;
m_mfxEncParams.mfx.QPP = 34;
m_mfxEncParams.mfx.QPB = 34;
}
其他值RateControlMethod 则一旦填写QP初始化会失败。
11: //解决实时编码问题(传入空数据)。
do
{
sts = m_pmfxENC->EncodeFrameAsync(&encCtrl, pSurf, &m_mfxBS, &UserSyncp);
pSurf = NULL;
Sleep(1);
} while (sts == MFX_ERR_MORE_DATA);
12://解决实时解码问题(传入空数据)。
do
{
sts = m_pmfxDEC->DecodeFrameAsync(NULL, &(m_pCurrentFreeSurface->frame), &m_pOutSurface, &usersyncp);
if (sts == MFX_ERR_MORE_DATA)
{
Sleep(1);
}
} while (MFX_ERR_MORE_DATA== sts);
13: //解决实时解码问题(传入空数据)。
//错误处理
if (sts == MFX_ERR_DEVICE_LOST ||
sts == MFX_ERR_INCOMPATIBLE_VIDEO_PARAM ||
sts == MFX_ERR_INVALID_VIDEO_PARAM ||
sts == MFX_ERR_UNDEFINED_BEHAVIOR ||
sts == MFX_ERR_DEVICE_FAILED)
{
DISPATCHER_LOG_INFO((("CMfxDecoder::DecodeFrameAsync = %d."), sts));
return -1;
}
这些返回的错误或者可以根据测试情况添加其他错误,即解码失败而不能循环处理解决实时解码的问题。
14:如果想在解码途中修改解码参数需要。
//m_pmfxDEC->Reset接口不好用,用下面方式替代。
if (m_pmfxDEC != NULL)
{
m_pmfxDEC->Close();
MSDK_SAFE_DELETE(m_pmfxDEC);
m_pmfxDEC = NULL;
}
FreeBuffers();
m_pCurrentFreeSurface = NULL;
MSDK_SAFE_FREE(m_pCurrentFreeOutputSurface);
// delete frames
if (m_pGeneralAllocator)
{
m_pGeneralAllocator->Free(m_pGeneralAllocator->pthis, &m_mfxResponse);
}
MSDK_SAFE_DELETE(m_pGeneralAllocator);
MSDK_SAFE_DELETE(m_pmfxAllocatorParams);
//重新初始化
mfxStatus sts = Init();
15:控制码率情况:
if (UserRateControlMethod == MFX_RATECONTROL_CQP)
{
//只填写这三个值起作用但控制不住码率。相同分辨率下,图像剧烈时码率会有较大变化。
m_mfxEncParams.mfx.QPI = 24;
m_mfxEncParams.mfx.QPP = 24;
m_mfxEncParams.mfx.QPB = 24;
//通过下列方式填写MaxQPI,MinQPI等六个参数在MFX_RATECONTROL_CQP模式下不起作用。
// for look ahead BRC configuration
//m_CodingOption2.MaxQPI = m_mfxEncParams.mfx.QPI + 5;
//m_CodingOption2.MinQPI = m_mfxEncParams.mfx.QPI - 5;
//m_CodingOption2.MaxQPP = m_mfxEncParams.mfx.QPP + 5;
//m_CodingOption2.MinQPP = m_mfxEncParams.mfx.QPP - 5;
//m_CodingOption2.MaxQPB = m_mfxEncParams.mfx.QPB + 5;
//m_CodingOption2.MinQPB = m_mfxEncParams.mfx.QPB - 5;
m_EncExtParams.push_back((mfxExtBuffer *)&m_CodingOption2);
if (!m_EncExtParams.empty())
{
m_mfxEncParams.ExtParam = &m_EncExtParams[0]; // vector is stored linearly in memory
m_mfxEncParams.NumExtParam = (mfxU16)m_EncExtParams.size();
}
}
else if (UserRateControlMethod == MFX_RATECONTROL_VBR)
{
//采用MFX_RATECONTROL_VBR模式用填写码率控制。
m_bitRateIn = 800;
m_mfxEncParams.mfx.InitialDelayInKB = 0;
m_mfxEncParams.mfx.TargetKbps = (unsigned short)m_bitRateIn; //码率
m_mfxEncParams.mfx.MaxKbps = m_bitRateIn;
}
16:
(二)AMD平台
1:AMD平台的AMF对外API接口开源地址:https://gpuopen.com/gaming-product/advanced-media-framework/ 。
https://github.com/GPUOpen-LibrariesAndSDKs/AMF。里面有编码和解码的samples。
2:AMF的库Version 1.4.9: AMD Radeon™ Software Adrenalin 18.8.1 (18.30.01.01) or later。需要显卡类型是这个,否则在安装驱动时里面不会包含amfrt32.dll和amfrt64.dll,这两个运行时库用在
//初始化AMF解码所需的全局库 res = g_AMFFactory.Init();
如果找不到会失败驱动版本应该是17.几以后,像老的ATI的显卡驱动里面没有,但是把这两个库拷贝到运行程序同目录可能有未知问题。
3:从GPU拷贝数据到CPU,解码后的数据如果是1080P的耗时约7-10毫秒一帧,这已经是AMF优化过的,如果不优化耗时更多。
long long llbegin = __rdtsc();
// convert to system memory 转换为系统内存AMF_MEMORY_HOST就是系统内存CPU中。
dataDecoder->Convert(amf::AMF_MEMORY_HOST);
long long llend = __rdtsc();
float ftime = (llend - llbegin) / (float)3400000000;
printf("************************%f\n", ftime);
这里需要注意耗时情况,以便做相应的处理。
4:关于amfrt32.dll,win32程序运行时首先查找可执行程序同目录下是否有amfrt32.dll,如果没有则去C:\Windows\SysWOW64这个目录查找是否有,AMD的这个动态库有个bug,先看下下面版本对应信息。
//产品版本:1.4.0.0的amfrt32.dll :AMFRuntimeVersion : 281492156579840 (decoder->Init成功)
//产品版本:1.4.1.0的amfrt32.dll :AMFRuntimeVersion : 281492156645376 (decoder->Init成功)
//产品版本:1.4.2.0的amfrt32.dll :AMFRuntimeVersion : 281492156710912 (decoder->Init成功)
//产品版本:1.4.4.0的amfrt32.dll :AMFRuntimeVersion : 281492156841984 (decoder->Init失败)
//产品版本:1.4.6.0的amfrt32.dll :AMFRuntimeVersion : 281492156973056 (decoder->Init失败)
//产品版本:1.4.8.0的amfrt32.dll :AMFRuntimeVersion : 281492157104128 (decoder->Init失败)
即1.4.4.0以前的版本amfrt32.dll ,decoder->Init时无论是否正常安装了AMD的显卡驱动并且显卡驱动是否支持编解码都能初始化init成功,如在intel机器中将此库放到可执行程序同目录下也可以初始化成功。
AMD显卡启动程序将amfrt32.dll_1.4.4.0(不包含这个版本只以下1.4.0.0或1.4.1.0或1.4.2.0)远程桌面才能支持amd显卡解码,并且将amfrt32.dll_1.4.0.0和可执行的exe程序放入同一个目录,则init为非常慢。甚至达到1秒左右。
amfrt32.dll的各个版本库可以在这个链接下。
https://www.pconlife.com/viewfileinfo/amfrt64-dll/
显卡支持amf的情况可在官网查看:
Version 1.4.9: AMD Radeon™ Software Adrenalin 18.8.1 (18.30.01.01) or later
Version 1.4.7: AMD Radeon™ Software Crimson 18.3.4 (17.50.33) or later
Version 1.4.6: AMD Radeon™ Software Crimson 17.12.1 (17.50.02) or later
Version 1.4.4: AMD Radeon™ Software Crimson 17.7.2 (17.30.1041) or later
Version 1.4.0: AMD Radeon™ Software Crimson 17.1.1 (16.50.2611) or later
Version 1.3.0: AMD Radeon™ Software Crimson 16.7.3 (16.30.2311) or later
Microsoft® Windows® 7, Microsoft® Windows® 8.1 or Microsoft® Windows® 10
AMF 1.4.4 or later requires OCL_SDK_LIGHT. Previous versions require AMD APP SDK (Version 3.0 or later), Microsoft® Windows® 10 SDK (Version 10586 as required by the build solution) and some samples require the Microsoft® Foundation Class Library.
Examples are built with Visual Studio® 2013, Visual Studio® 2015 and Visual Studio® 2017
链接地址:https://gpuopen.com/gaming-product/advanced-media-framework/。
5:amf::AMFBufferPtr,amf::AMFDataPtr ,amf::AMFSurfacePtr这些变量都是智能指针,生命周期结束自动释放,但在Detach()之后即 amf::AMFDataPtr = amf::AMFBufferPtr.Detach();之后amf::AMFDataPtr有时候需要手动释放,有时候不需要释放释放会崩溃,这个问题百思不得其解。需要手动释放时如果不手动释放会有内存泄漏。
6:AMD编码在多进程情况下支持多个测试程序输出并输出正常。但在多线程情况下只支持一路输出。其他路不能正常编码。
初步分析:查找amd官方文档查找对应参数如下,AMF_VIDEO_ENCODER_MAX_INSTANCES 1 or 2 Hardware-dependent, only some hardware supports 2 instances,
AMF_VIDEO_ENCODER_MULTI_INSTANCE_MODE True or False Enables or disables,multi-instance mode,default – disabled
测试发现只支持一个实例编码输出。
7: 编码之后取出编码后的数据有可能返回AMF_REPEAT,编码后的数据为空,可以用下列方法解决取出数据为空的问题。
//解决实时编码问题。
do
{
res = m_pEncoder->QueryOutput(&dataEncoder);
amf_sleep(1);
} while (res == AMF_REPEAT);
8:如果想在解码途中修改解码参数需要。
//m_pDecoder->ReInit接口不好用,用下面方式替代。
// drain decoder queue
m_pDecoder->Drain();
m_pDecoder->Terminate();
res = m_pDecoder->Init(m_formatOut, VideoWidth,VideoHeight);
9:SubmitInput第一次由于需要解析sps,pps所以耗时较高约3-5毫秒,其他没有sps,pps解析的帧数据传入SubmitInput耗时只需要0.3-0.5毫秒。
10:码率控制情况
//码率控制模式,CBR,VBR,CQP;
if (amf_video_encode_rate_modle == AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CONSTANT_QP)
{
m_bitRateIn *= 1024;
res = m_pEncoder->SetProperty(AMF_VIDEO_ENCODER_TARGET_BITRATE, m_bitRateIn);
//设置最大码率
res = m_pEncoder->SetProperty(AMF_VIDEO_ENCODER_PEAK_BITRATE, m_bitRateIn);
//设置qp_min
m_qp_min = 20;
res = m_pEncoder->SetProperty(AMF_VIDEO_ENCODER_MIN_QP, m_qp_min);
//设置qp_max
m_qp_max = 30;
res = m_pEncoder->SetProperty(AMF_VIDEO_ENCODER_MAX_QP, m_qp_max);
}
else if (amf_video_encode_rate_modle == AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_LATENCY_CONSTRAINED_VBR ||
amf_video_encode_rate_modle == AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR)
{
//设置码率
m_bitRateIn = 800;
m_bitRateIn *= 1024; //传入的是Kbps不是bps,所以要*1024.
res = m_pEncoder->SetProperty(AMF_VIDEO_ENCODER_TARGET_BITRATE, m_bitRateIn);
//设置最大码率
res = m_pEncoder->SetProperty(AMF_VIDEO_ENCODER_PEAK_BITRATE, m_bitRateIn);
//buf缓存:VBV Buffer Size in bits;填写这个很重要,否则码率不会按照真实码率控制。
res = m_pEncoder->SetProperty(AMF_VIDEO_ENCODER_VBV_BUFFER_SIZE, (m_bitRateIn * 1 / m_frameRateIn) * 5);
//buf缓存:Initial VBV Buffer Fullness 0=0% 64=100%;填写这个很重要,否则码率不会按照真实码率控制。
res = m_pEncoder->SetProperty(AMF_VIDEO_ENCODER_INITIAL_VBV_BUFFER_FULLNESS, 64);
}
}
11:
(三)NVDIA平台
1:开发者官网链接:https://developer.nvidia.com。
SDK链接:https://developer.nvidia.com/nvidia-video-codec-sdk。需要注册账号才能下载,下载:找到界面链接,Download Video Codec SDK 8.2,或其他版本的SDK用于开发。
CUDA工具包:https://developer.nvidia.com/cuda-toolkit。
2:在安装CUDA的时候,会安装三个大的组件,分别是NVIDIA驱动、toolkit和samples。驱动用来控制gpu硬件,
toolkit里面包括nvcc编译器、Nsight调试工具(支持Eclipse和VS,linux用cuda-gdb)、分析和调试工具和函数库。
samples或者说SDK,里面包括很多样例程序包括查询设备、带宽测试等等。
3:toolkit安装:在这个目录https://developer.nvidia.com/cuda-downloads?target_os=Windows&target_arch=x86_64&target_version=7&target_type=exelocal。
下载click cuda_10.0.130_411.31_windows.exe或其他版本,安装到目录C:\Users\用户\AppData\Local\Temp\CUDA下。
在这个目录下查找nvcc文件夹(如果只是取出这个库一定要在安装的途中解压进度条运行完成后到安装时将这个库取出,否则取消安装或安装失败会删除这个安装文件夹),如果安装成功则会有这个文件夹。
4://附加包含目录
//..\NVDIA_NVDEC_NVENC
//..\NVDIA_NVDEC_NVENC\Common
//..\NVDIA_NVDEC_NVENC\External\FFmpeg\include //编码不需要,解码为了分帧
//..\NVDIA_NVDEC_NVENC\NvCodec
//..\NVDIA_NVDEC_NVENC\Utils
//..\NVDIA_NVDEC_NVENC\nvcc\include
//附加库目录
//..\NVDIA_NVDEC_NVENC\External\FFmpeg\lib\Win32 //编码不需要,解码为了分帧
//..\NVDIA_NVDEC_NVENC\NvCodec\Lib\Win32
//..\NVDIA_NVDEC_NVENC\nvcc\lib\Win32
//$(CudaToolkitLibDir)
//添加新建筛选器,将NvCodec/NvDecoder加入,以编译cpp文件
//添加新建筛选器,将NvCodec/NvEncoder加入,以编译cpp文件
//忽略警告:在 项目->属性->C / C++->预处理器->预处理器定义中添加_CRT_SECURE_NO_WARN。
//运行时需要dll动态库
//nvcuvid.dll nvcuda.dll
//运行时有可能缺少nvfatbinaryloader.dll,动态加载dll方式还未验证。
5://如果是静态加载则需要加载这些lib,nvcuvid.lib在官网下载的实例中,cuda.lib在工具包nvcc中,并将所需要的dll放在和exe同目录下。动态加载用LoadLibrary加载dll。
//..\NVDIA_NVDEC_NVENC\nvcc\lib\Win32中存在这几个库cuda.lib cudadevrt.lib cudart.lib cudart_static.lib OpenCL.lib。根据自己需求添加
//#pragma comment(lib,"nvcuvid.lib")
//#pragma comment(lib,"cuda.lib")
6://如果是动态加载需要修改源码添加函数指针(解码)
//解码:
//(1):修改NvDecoder类的构造函数和析构函数,修改为:NvDecoderInit和NvDecoderUninit,初始化和反初始化,代码从原构造函数和析构函数拷贝得来。
//修改源码,设置nvcuvid.dll库的指针和nvcuda.dll库的指针传入void SetNvDecoderDllHandle。
//修改源码,设置nvcuvid.dll库的指针和nvcuda.dll库的指针句柄
//static void * m_pcuNvcuvidLib = NULL; //nvcuvid.dll库的指针
//static void * m_pcuNvcudaLib = NULL; //nvcuda.dll库的指针
//(2):修改NvDecoder/NvDecoder.h中的包含//#include "nvcuvid.h"
//新建#include "dylink_nvcuvid.h"文件,将nvcuvid.h中所有CUDAAPI的对外导出函数全部注释掉。
//在dylink_nvcuvid.h中添加#include "nvcuvid.h"。
//例如:在nvcuvid.h中注释掉//CUresult CUDAAPI cuvidCreateVideoSource(CUvideosource *pObj, const char *pszFileName, CUVIDSOURCEPARAMS *pParams);
//在dylink_nvcuvid.h中添加typedef CUresult (CUDAAPI *tcuvidCreateVideoSource)(CUvideosource *pObj, const char *pszFileName, CUVIDSOURCEPARAMS *pParams);
//static tcuvidCreateVideoSource cuvidCreateVideoSource = NULL; 其他函数同理。
//(3):修改nvcuvid.h中的包含//#include "cuviddec.h"
//新建#include "dylink_cuviddec.h"文件,将cuviddec.h中所有CUDAAPI的对外导出函数全部注释掉。
//在dylink_cuviddec.h中添加#include "cuviddec.h"。
//例如:在cuviddec.h中注释掉//extern CUresult CUDAAPI cuvidGetDecoderCaps(CUVIDDECODECAPS *pdc);
//在dylink_cuviddec.h中添加typedef CUresult (CUDAAPI * tcuvidGetDecoderCaps)(CUVIDDECODECAPS *pdc);
//static tcuvidGetDecoderCaps cuvidGetDecoderCaps = NULL; 其他函数同理。
//(4):修改cuviddec.h中的包含//#include <cuda.h>
//新建#include "dylink_cuda.h"文件,将cuda.h中所有CUDAAPI的对外导出函数全部注释掉。
//在dylink_cuda.h中添加#include "cuda.h"。
//例如:在cuda.h中注释掉//CUresult CUDAAPI cuGetErrorString(CUresult error, const char **pStr);
//在dylink_cuda.h中添加typedef CUresult (CUDAAPI *tcuGetErrorString)(CUresult error, const char **pStr);
//static tcuGetErrorString cuGetErrorString; 其他函数同理(约400多个)。
//(5):在main.cpp中添加loadCudaLibrary引用nvcuvid.dll nvcuda.dll两个dll.
//根据函数需求getProcAddress获取对外函数地址并使用。
//例如:if ((cuInit = (tcuInit)getProcAddress(cuNvcudaLib, "cuInit")) == NULL)
//if ((cuInit(0)) == CUDA_SUCCESS) 根据函数指针名称获取并使用。
//(6):修改NvDecoder/NvDecoder.cpp中所有用到dylink_nvcuvid.h,dylink_cuviddec.h以及dylink_cuda.h修改后的函数指针应用,改为动态加载方式。
//例如: //修改源码,改为动态加载方式
//if ((cuvidCtxLockCreate = (tcuvidCtxLockCreate)GetProcAddress((HMODULE)m_pcuNvcuvidLib, "cuvidCtxLockCreate")) == NULL)
//{
// return false;
//}
//所有例如cuCtxPushCurrent_v2包含_v2的对外函数名称在GetProcAddress时必须用_v2的名称。
//例如:修改源码,改为动态加载方式
//if ((cuCtxPushCurrent = (tcuCtxPushCurrent)GetProcAddress((HMODULE)m_pcuNvcudaLib, "cuCtxPushCurrent_v2")) == NULL)
//{
// return nDecodeSurface;
//}
//用cuCtxPushCurrent_v2这个函数名称获取,如果用cuCtxPushCurrent会出现崩溃的情况。
//例如NVDEC_API_CALL(cuvidCtxLockCreate(&m_ctxLock, cuContext));
//或者写个宏
//有些扩展的对外函数例如 #define cuCtxCreate cuCtxCreate_v2,
//需要GetProcAddress用cuCtxCreate_v2这个名称而不是cuCtxCreate这个名称。
//自动检查是否有_v2,如果有用_v2函数名称,如果没有用原名称。
//#define USERTOT(x) # x //#作用将x变成字符串。
//#define USERMA(x) USERTOT(x) //作用是将x展开,即如果x为fuc,因为有(#define fuc fuc_v2),所以x将变为fuc_v2.
//应用例如:if ((cuInit = (tcuInit)getProcAddress(cuNvcudaLib, USERMA(cuInit))) == NULL)。
//(7):需要将所有的导出函数指针dylink_文件中添加extern "C"。
//(8):
7://如果是动态加载需要修改源码添加函数指针(编码)
//编码:
//(1):修改NvEncoderCuda类的构造函数和析构函数,修改为:NvEncoderCudaInit和NvEncoderCudaUninit,初始化和反初始化,代码从原构造函数和析构函数拷贝得来。
//修改源码,设置nvcuvid.dll库的指针和nvcuda.dll库的指针传入void SetNvEncoderCudaDllHandle。
//修改源码,设置nvcuvid.dll库的指针和nvcuda.dll库的指针句柄
//static void * m_pcuNvcuvidLib = NULL; //nvcuvid.dll库的指针
//static void * m_pcuNvcudaLib = NULL; //nvcuda.dll库的指针
//(2):修改NvEncoder/NvEncoderCuda.h中的包含//#include "cuda.h"
//新建#include "dylink_cuda.h"文件,将cuda.h中所有CUDAAPI的对外导出函数全部注释掉。
//在dylink_cuda.h中添加#include "cuda.h"。
//例如:在cuda.h中注释掉//CUresult CUDAAPI cuGetErrorString(CUresult error, const char **pStr);
//在dylink_cuda.h中添加typedef CUresult (CUDAAPI *tcuGetErrorString)(CUresult error, const char **pStr);
//static tcuGetErrorString cuGetErrorString; 其他函数同理(约400多个)。
//(这里注意可以用和解码decoder操作同一个文件dylink_cuda.h,即解码和编码重复用和包含cuda.h,和dylink_cuda.h两个一样的文件)。
//(3):由于NvEncoder.h和NvEncoder.cpp这两个文件中只用到了nvEncodeAPI.h中的内容未用到cuda.h和nvcuvid.h中的内容所以不需要修改。
//4:由于nvEncodeAPI.h这个文件里面的导出函数在NvEncoder.cpp中是动态加载的LoadLibrary(TEXT("nvEncodeAPI.dll"));
//所以nvEncodeAPI.h这个文件的导出函数不需要处理。
//(4):在main.cpp中添加loadCudaLibrary引用nvcuvid.dll nvcuda.dll两个dll.
//根据函数需求getProcAddress获取对外函数地址并使用。
//例如:if ((cuInit = (tcuInit)getProcAddress(cuNvcudaLib, "cuInit")) == NULL)
//if ((cuInit(0)) == CUDA_SUCCESS) 根据函数指针名称获取并使用。
//(5):只用到cuda.h,未用到nvcuvid.h所以没有类似dylink_nvcuvid.h这样的文件包含nvcuvid.h的函数指针。
//解码需要cuda.h和nvcuvid.h,编码只需要cuda.h。
//(6):修改NvEncoder/NvEncoderCuda.cpp中所有用到dylink_cuda.h修改后的函数指针应用,改为动态加载方式。
//修改源码,改为动态加载方式
//if ((cuCtxPushCurrent = (tcuCtxPushCurrent)GetProcAddress((HMODULE)m_pcuNvcudaLib, USERMA(cuCtxPushCurrent))) == NULL)
//{
// break;
//}
//所有例如cuCtxPushCurrent_v2包含_v2的对外函数名称在GetProcAddress时必须用_v2的名称。
//例如:修改源码,改为动态加载方式
//if ((cuCtxPushCurrent = (tcuCtxPushCurrent)GetProcAddress((HMODULE)m_pcuNvcudaLib, "cuCtxPushCurrent_v2")) == NULL)
//{
// return;
//}
//用cuCtxPushCurrent_v2这个函数名称获取,如果用cuCtxPushCurrent会出现崩溃的情况。
//例如CUDA_DRVAPI_CALL(cuCtxPushCurrent(m_cuContext));
//或者写个宏
//有些扩展的对外函数例如 #define cuCtxCreate cuCtxCreate_v2,
//需要GetProcAddress用cuCtxCreate_v2这个名称而不是cuCtxCreate这个名称。
//自动检查是否有_v2,如果有用_v2函数名称,如果没有用原名称。
//#define USERTOT(x) # x //#作用将x变成字符串。
//#define USERMA(x) USERTOT(x) //作用是将x展开,即如果x为fuc,因为有(#define fuc fuc_v2),所以x将变为fuc_v2.
//应用例如:if ((cuInit = (tcuInit)getProcAddress(cuNvcudaLib, USERMA(cuInit))) == NULL)。
//(7):需要将所有的导出函数指针dylink_文件中添加extern "C"。
//(8):
8:https://developer.nvidia.com/video-encode-decode-gpu-support-matrix;这个链接里面有各个型号的显卡,编码解码情况的列表,值得提的一点Max # of concurrent sessions,这是并行编码支持的最大路数。有些型号得显卡只支持最多2路。测试发现如果这种显卡启动第三路编码:在调用NVENC_API_CALL(m_nvenc.nvEncOpenEncodeSessionEx(&encodeSessionExParams, &hEncoder));时会报NV_ENC_ERR_OUT_OF_MEMORY 并崩溃,需要做处理限制路数。
9:如果想调整参数或者将编码的谋一帧强制为idr帧用如下方法。
//重新填写编码的参数信息,测试//if (numframe % 3 == 0)
if ((numframe % m_gopsize)== m_gopsize)
{
NV_ENC_RECONFIGURE_PARAMS reconfigureParams = { NV_ENC_RECONFIGURE_PARAMS_VER };
memcpy(&reconfigureParams.reInitEncodeParams, &initializeParams, sizeof(initializeParams));
NV_ENC_CONFIG reInitCodecConfig = { NV_ENC_CONFIG_VER };
memcpy(&reInitCodecConfig, &encodeConfig, sizeof(reInitCodecConfig));
reconfigureParams.reInitEncodeParams.encodeConfig = &reInitCodecConfig;
//指定某一帧为IDR帧
reconfigureParams.forceIDR = 1;
//重新设置编码参数
pencoder->Reconfigure(&reconfigureParams);
}
10:如何设置编码为H264还是H265(HEVC)
if (m_encodeH264orHEVC == 0)
{
const char *m_encodeCLIOptionsParam = "-codec h264"; //如果想编码成hevc用"-codec hevc";如果编码成H264用"-codec h264";如果是空字符串则是编码为h264。
m_encodeCLIOptions = NvEncoderInitParam(m_encodeCLIOptionsParam, nullptr, true);
}
else if (m_encodeH264orHEVC == 1)
{
const char *m_encodeCLIOptionsParam = "-codec hevc"; //如果想编码成hevc用"-codec hevc";如果编码成H264用"-codec h264";如果是空字符串则是编码为h264。
m_encodeCLIOptions = NvEncoderInitParam(m_encodeCLIOptionsParam, nullptr, true);
}
else
{
m_encodeCLIOptions = NvEncoderInitParam("", nullptr, true);
}
11:如果想在解码途中修改解码参数需要。
//第一次初始化init.
m_oDecoder = new NvDecoder();
//初始化解码器(m_decodemaxWidth, m_decodemaxHeight必须填写为0,否则有些显卡解码出来的视频为绿色空数据)
// Here set bLowLatency=true in the constructor.
//Please don't use this flag except for low latency, it is harder to get 100% utilization of
//hardware decoder with this flag set.
//bLowLatency=true 低延迟模式,无缓存在解码器中
lbReslut = m_oDecoder->NvDecoderInit(m_cuContext, m_pNvidiaCodec->m_sVideoWidth, m_pNvidiaCodec- >m_sVideoHeight, false,
cudaVideoCodec_H264, NULL, true, false, &m_cropRect, &m_resizeDim, m_decodemaxWidth, m_decodemaxHeight);
//第二次初始化
//This function allow app to set decoder reconfig params
//此功能允许app设置解码器重新配置参数(有些显卡不起作用)
m_resizeDim.w = m_pNvidiaCodec->m_sVideoWidth;
//m_resizeDim.h = m_pNvidiaCodec->m_sVideoHeight;
//int ret = m_oDecoder.setReconfigParams(&m_cropRect,&m_resizeDim);
//析构decoder
if (m_oDecoder != NULL)
{
m_oDecoder->NvDecoderUninit();
delete m_oDecoder;
m_oDecoder = NULL;
}
m_oDecoder = new NvDecoder();
//初始化解码器(m_decodemaxWidth, m_decodemaxHeight必须填写为0,否则有些显卡解码出来的视频为绿色空数据)
// Here set bLowLatency=true in the constructor.
//Please don't use this flag except for low latency, it is harder to get 100% utilization of
//hardware decoder with this flag set.
//bLowLatency=true 低延迟模式,无缓存在解码器中
lbReslut = m_oDecoder->NvDecoderInit(m_cuContext, m_pNvidiaCodec->m_sVideoWidth, m_pNvidiaCodec->m_sVideoHeight, false,
cudaVideoCodec_H264, NULL, true, false, &m_cropRect, &m_resizeDim, m_decodemaxWidth, m_decodemaxHeight);
12:码率控制情况
if (m_encodeConfig.rcParams.rateControlMode == NV_ENC_PARAMS_RC_CONSTQP)
{
m_bitRateIn = 0; //CQP模式码率填写为0
m_encodeConfig.rcParams.averageBitRate = m_bitRateIn; //指定用于编码的平均比特率(以位/秒为单位,例如 = 800000; //输入编码的码率800k。
m_encodeConfig.rcParams.vbvBufferSize = (m_encodeConfig.rcParams.averageBitRate * m_initializeParams.frameRateDen / m_initializeParams.frameRateNum) * 5; //buffer size. in bits. Set 0 to use the default VBV buffer size.
m_encodeConfig.rcParams.vbvInitialDelay = m_encodeConfig.rcParams.vbvBufferSize; //buffer size. in bits. Set 0 to use the default VBV buffer size.
m_encodeConfig.rcParams.maxBitRate = m_encodeConfig.rcParams.averageBitRate; //指定用于编码的最大比特率(以位/秒为单位,例如 = 800000; //输入编码的码率800k。
m_encodeConfig.rcParams.enableMinQP = 1; //Set this to 1 if minimum QP used for rate control.
m_encodeConfig.rcParams.enableMaxQP = 1; //Set this to 1 if maximum QP used for rate control.
m_qp_min = 15;
m_encodeConfig.rcParams.minQP.qpIntra = m_qp_min; //Specifies the minimum QP used for rate control. Client must set NV_ENC_CONFIG::enableMinQP to 1.
m_encodeConfig.rcParams.minQP.qpInterP = m_encodeConfig.rcParams.minQP.qpIntra;
m_encodeConfig.rcParams.minQP.qpInterB = m_encodeConfig.rcParams.minQP.qpIntra;
m_qp_max = 25;
m_encodeConfig.rcParams.maxQP.qpIntra = m_qp_max; //Specifies the maximum QP used for rate control.Client must set NV_ENC_CONFIG::enableMaxQP to 1.
m_encodeConfig.rcParams.maxQP.qpInterP = m_encodeConfig.rcParams.maxQP.qpIntra;
m_encodeConfig.rcParams.maxQP.qpInterB = m_encodeConfig.rcParams.maxQP.qpIntra;
}
else if (m_encodeConfig.rcParams.rateControlMode == NV_ENC_PARAMS_RC_VBR_HQ)
{
//采用NV_ENC_PARAMS_RC_VBR_HQ模式用填写码率控制。
m_bitRateIn = 800;
m_bitRateIn *= 1024; //传入的是Kbps不是bps,所以要*1024.
m_encodeConfig.rcParams.averageBitRate = m_bitRateIn; //指定用于编码的平均比特率(以位/秒为单位,例如 = 800000; //输入编码的码率800k。
m_encodeConfig.rcParams.vbvBufferSize = (m_encodeConfig.rcParams.averageBitRate * m_initializeParams.frameRateDen / m_initializeParams.frameRateNum) * 5; //buffer size. in bits. Set 0 to use the default VBV buffer size.;填写这个很重要,否则码率不会按照真实码率控制。
m_encodeConfig.rcParams.vbvInitialDelay = m_encodeConfig.rcParams.vbvBufferSize; //buffer size. in bits. Set 0 to use the default VBV buffer size.;填写这个很重要,否则码率不会按照真实码率控制。
m_encodeConfig.rcParams.maxBitRate = m_encodeConfig.rcParams.averageBitRate; //指定用于编码的最大比特率(以位/秒为单位,例如 = 800000; //输入编码的码率800k。
}
13:
用于记录。
如有错误请指正: