在之前的《SkeyeRTSPLive高效转码之SkeyeVideoDecoder高效解码》系列文章中我们已经将视频解码成了原始图像数据(YUV/RGB),然后根据不同的转码需求进行编码。如视频分辨率缩放,调整码率,多码率输出等;为了解决转码过程中编码高分辨率高质量或者高压缩率(如H265)耗时的问题,我们采用Nvidia硬件驱动编码器进行编码,以追求最高效率的转码和最低的推送延迟。

SkeyeVideoEncoder基Nvidia独立显卡的硬件编码库SkeyeNvEncoder

1. 接口声明如下:
class SkeyeNvEncoder
{
public://codec: 编码格式 0=h264, 1=h265/hevcint InitNvEncoder(int width,int height,int fps=25, int bitrate=4096, int gop=50, int qp=28, int rcMode=/*NV_ENC_PARAMS_RC_2_PASS_QUALITY*/NV_ENC_PARAMS_RC_CONSTQP, char* encoderPreset = "Default", int codec = 0,int nDeviceType=0, int nDeviceID=0 );//H264获取SPS和PPS    int GetSPSAndPPS(unsigned char*sps,long&spslen,unsigned char*pps,long&ppslen);//H265获取VPS,SPS和PPS   int GetH265VPSSPSAndPPS(unsigned char*vps, long&vpslen, unsigned char*sps, long&spslen, unsigned char*pps, long&ppslen);// 编码InputFormat我们固定为YUV420PL(I420),可修改为NV12, YUY2 等等在Init()时进行格式转换, [12/18/2016 dingshuai]unsigned char* NvEncodeSync(unsigned char* pYUV420, int inLenth, int& outLenth, bool& bKeyFrame);//关闭编码器,停止编码int CloseNvEncoder();
};
2. SkeyeNvEncoder编码库调用流程
  • 第一步,初始化编码器及其参数
//初始化编码器参数
int InitNvEncoder(int width,int height,int fps, int bitrate, int gop,  int qp, int rcMode,  char* encoderPreset , int codec, int nDeviceType, int nDeviceID)
{//初始化设置参数 -- Startmemset(&m_encodeConfig, 0, sizeof(EncodeConfig));m_encodeConfig.width = width;m_encodeConfig.height = height;m_nVArea = width*height;m_nCheckyuvsize = m_nVArea*3/2;//编码器识别的码率是bps, 但是我们输入的是kbps, so*1024m_encodeConfig.bitrate = bitrate*1024;//多通道编码优化图像质量只有在低延迟模式下工作(LOW_LATENCY)m_encodeConfig.rcMode = rcMode;//NV_ENC_PARAMS_RC_2_PASS_QUALITYm_encodeConfig.encoderPreset = encoderPreset;   //NV_ENC_PARAMS_RC_2_PASS_QUALITY;//默认指定低延时模式以及图像的压缩格式(HQ,HP,LOSSLESS ......)m_encodeConfig.presetGUID = NV_ENC_PRESET_LOW_LATENCY_HQ_GUID;// I帧间隔 [12/16/2016 dingshuai]m_encodeConfig.gopLength = gop;//NVENC_INFINITE_GOPLENGTH;//CUDA m_encodeConfig.deviceType = nDeviceType;m_encodeConfig.deviceID = nDeviceID;m_encodeConfig.codec =  codec;//NV_ENC_H264;m_encodeConfig.fps = fps;m_encodeConfig.qp = qp;m_encodeConfig.i_quant_factor = DEFAULT_I_QFACTOR;m_encodeConfig.b_quant_factor = DEFAULT_B_QFACTOR;  m_encodeConfig.i_quant_offset = DEFAULT_I_QOFFSET;m_encodeConfig.b_quant_offset = DEFAULT_B_QOFFSET; m_encodeConfig.pictureStruct = NV_ENC_PIC_STRUCT_FRAME;//编码异步输出模式, 1-异步 0-同步m_encodeConfig.enableAsyncMode = 0;//默认输入给编码器的格式为NV12(所以需要格式转换:YUV420->NV12)m_encodeConfig.inputFormat = NV_ENC_BUFFER_FORMAT_NV12;//暂不知道这些参数什么用m_encodeConfig.invalidateRefFramesEnableFlag = 0;m_encodeConfig.endFrameIdx = INT_MAX;//没有B帧,且目前编码器也不支持B帧,设了也没用m_encodeConfig.numB = 0;if (m_encodeConfig.numB > 0){//PRINTERR("B-frames are not supported\n");return -1;}// 其他参数,欢迎补充...... [12/18/2016 dingshuai]//// //初始化设置参数 -- END//初始化编码器 -- StartNVENCSTATUS nvStatus = NV_ENC_SUCCESS;switch (m_encodeConfig.deviceType){
#if defined(NV_WINDOWS)case NV_ENC_DX9:nvStatus = InitD3D9(m_encodeConfig.deviceID);break;case NV_ENC_DX10:nvStatus = InitD3D10(m_encodeConfig.deviceID);break;case NV_ENC_DX11:nvStatus = InitD3D11(m_encodeConfig.deviceID);break;
#endif// initialize Cudacase NV_ENC_CUDA:InitCuda(m_encodeConfig.deviceID,0);break;}if (nvStatus != NV_ENC_SUCCESS)return -1;if (m_encodeConfig.deviceType != NV_ENC_CUDA)nvStatus = m_pNvHWEncoder->Initialize(m_pDevice, NV_ENC_DEVICE_TYPE_DIRECTX);elsenvStatus = m_pNvHWEncoder->Initialize(m_pDevice, NV_ENC_DEVICE_TYPE_CUDA);if (nvStatus != NV_ENC_SUCCESS)return 1;//nvStatus = InitCuda(m_encodeConfig.deviceID, 0);//nvStatus = m_pNvHWEncoder->Initialize((void*)m_cuContext, NV_ENC_DEVICE_TYPE_CUDA);//if (nvStatus != NV_ENC_SUCCESS)//    return -2;m_encodeConfig.presetGUID = m_pNvHWEncoder->GetPresetGUID(m_encodeConfig.encoderPreset, m_encodeConfig.codec);nvStatus = m_pNvHWEncoder->CreateEncoder(&m_encodeConfig);if (nvStatus != NV_ENC_SUCCESS){Deinitialize();return -3;}// 编码缓存帧数 [12/16/2016 dingshuai]uint32_t uEncodeBufferCount = 1;//分配编码缓冲区nvStatus = AllocateIOBuffers(m_pNvHWEncoder->m_uMaxWidth, m_pNvHWEncoder->m_uMaxHeight, uEncodeBufferCount);if (nvStatus != NV_ENC_SUCCESS)return -4;m_spslen = 0;m_ppslen = 0;memset(m_sps, 0x00, 100);memset(m_pps, 0x00, 100);m_bWorking = true;return 1;
}

其中,我们需要设置编码格式(0=H264,1=H265目前只支持这两种格式),视频分辨率,帧率,码率和I帧间隔(Gop),编码质量以及硬件编码器相关参数,参数详解如下:

//rcMode: Rate Control Modes(编码码率/质量控制模式),详见如下枚举://    typedef enum _NV_ENC_PARAMS_RC_MODE//   {//         NV_ENC_PARAMS_RC_CONSTQP                = 0x0,       /**< Constant QP mode *///         NV_ENC_PARAMS_RC_VBR                    = 0x1,       /**< Variable bitrate mode *///        NV_ENC_PARAMS_RC_CBR                    = 0x2,       /**< Constant bitrate mode *///        NV_ENC_PARAMS_RC_VBR_MINQP              = 0x4,       /**< Variable bitrate mode with MinQP *///         NV_ENC_PARAMS_RC_2_PASS_QUALITY         = 0x8,       /**< Multi pass encoding optimized for image quality and works only with low latency mode *///         NV_ENC_PARAMS_RC_2_PASS_FRAMESIZE_CAP   = 0x10,      /**< Multi pass encoding optimized for maintaining frame size and works only with low latency mode *///    }//encoderPreset: 编码预设// 预设编码器编码图像的延时和清晰度// if (encoderPreset && (stricmp(encoderPreset, "HQ") == 0))// else if (encoderPreset && (stricmp(encoderPreset, "LowLatencyHP") == 0))// else if (encoderPreset && (stricmp(encoderPreset, "HP") == 0))// else if (encoderPreset && (stricmp(encoderPreset, "LowLatencyHQ") == 0))// else if (encoderPreset && (stricmp(encoderPreset, "BD") == 0))// else if (encoderPreset && (stricmp(encoderPreset, "LOSSLESS") == 0))//  else if (encoderPreset && (stricmp(encoderPreset, "LowLatencyDefault") == 0))// else if (encoderPreset && (stricmp(encoderPreset, "LosslessDefault") == 0))//   详见nvEncoderAPI.h  /*   Preset GUIDS supported by the NvEncodeAPI interface.  */
  • 第二步,获取编码信息参数
    如果编码格式为H264,我们通过GetSPSAndPPS获取编码信息头SPS和PPS,如下代码段所示:
//获取SPS和PPS
int GetSPSAndPPS(unsigned char*sps,long&spslen,unsigned char*pps,long&ppslen)
{if (!m_bWorking){return -1;}if (m_spslen == 0 || m_ppslen == 0){unsigned char* pEncData = NULL;int nDataSize = 0;bool bKeyFrame = false;unsigned char* pTempBuffer = new unsigned char[m_nCheckyuvsize];memset(pTempBuffer, 0x00, m_nCheckyuvsize);pEncData = NvEncodeSync(pTempBuffer, m_nCheckyuvsize, nDataSize, bKeyFrame);if (pEncData && nDataSize>0){GetH264SPSandPPS((char*)pEncData, nDataSize, (char*)m_sps, (int*)&m_spslen, (char*)m_pps, (int*)&m_ppslen);}m_encPicCommand.bForceIDR = 1;if (pTempBuffer){delete[] pTempBuffer;pTempBuffer = NULL;}}if (m_spslen>0&&m_ppslen>0){memcpy(sps, m_sps, m_spslen);memcpy(pps, m_pps, m_ppslen);spslen = m_spslen;ppslen = m_ppslen;}return 1;
}

如果编码格式为H265,我们通过GetH265VPSSPSAndPPS获取编码信息头VPS,SPS和PPS,如下代码段所示:

int GetH265VPSSPSAndPPS(unsigned char*vps, long&vpslen, unsigned char*sps, long&spslen, unsigned char*pps, long&ppslen)
{if (!m_bWorking){return -1;}if (m_spslen == 0 || m_ppslen == 0){unsigned char* pEncData = NULL;int nDataSize = 0;bool bKeyFrame = false;unsigned char* pTempBuffer = new unsigned char[m_nCheckyuvsize];memset(pTempBuffer, 0x00, m_nCheckyuvsize);pEncData = NvEncodeSync(pTempBuffer, m_nCheckyuvsize, nDataSize, bKeyFrame);if (pEncData && nDataSize>0){GetH265VPSandSPSandPPS((char*)pEncData, nDataSize, (char*)m_vps, (int*)&m_vpslen, (char*)m_sps, (int*)&m_spslen, (char*)m_pps, (int*)&m_ppslen);}m_encPicCommand.bForceIDR = 1;if (pTempBuffer){delete[] pTempBuffer;pTempBuffer = NULL;}}spslen = m_spslen;ppslen = m_ppslen;vpslen = m_vpslen;if (m_spslen > 0)memcpy(sps, m_sps, m_spslen);if(m_ppslen>0)memcpy(pps, m_pps, m_ppslen);if(m_vpslen)memcpy(vps, m_vps, m_vpslen);return 1;
}
  • 第三步,调用编码函数进行视频帧编码
    编码输入格式InputFormat我们固定为YUV420PL(I420),如源图像色彩格式为NV12, YUY2 等,需要在传入编码器时进行格式转换。

unsigned char* NvEncodeSync(unsigned char* pYUV420, int inLenth, int& outLenth, bool& bKeyFrame)
{if(    !m_bWorking  || inLenth !=m_nCheckyuvsize)//初始化尚未完成,或者传入的数据不满足YUV数据的长度,则返回错误{outLenth = 0;return NULL;}NVENCSTATUS nvStatus = NV_ENC_SUCCESS;bool bError = false;EncodeBuffer* pEncodeBuffer = m_EncodeBufferQueue.GetAvailable();EncodeFrameConfig stEncodeFrame;memset(&stEncodeFrame, 0, sizeof(stEncodeFrame));stEncodeFrame.yuv[0] = pYUV420;//YstEncodeFrame.yuv[1] = pYUV420+m_nVArea;//UstEncodeFrame.yuv[2] = pYUV420+m_nVArea+(m_nVArea>>2);//Vint nHelfWidth = m_encodeConfig.width >> 1;stEncodeFrame.stride[0] = m_encodeConfig.width;stEncodeFrame.stride[1] = nHelfWidth;stEncodeFrame.stride[2] = nHelfWidth;stEncodeFrame.width = m_encodeConfig.width;stEncodeFrame.height = m_encodeConfig.height;if (m_encodeConfig.deviceType == 0)//CUDA{//CUDA Lock CCudaAutoLock cuLock((CUcontext)m_pDevice);//m_cuContextnvStatus = PreProcessInput(pEncodeBuffer, stEncodeFrame.yuv, stEncodeFrame.width, stEncodeFrame.height,m_pNvHWEncoder->m_uCurWidth, m_pNvHWEncoder->m_uCurHeight,m_pNvHWEncoder->m_uMaxWidth, m_pNvHWEncoder->m_uMaxHeight);if (nvStatus != NV_ENC_SUCCESS){outLenth = 0;return NULL;}nvStatus = m_pNvHWEncoder->NvEncMapInputResource(pEncodeBuffer->stInputBfr.nvRegisteredResource, &pEncodeBuffer->stInputBfr.hInputSurface);if (nvStatus != NV_ENC_SUCCESS){PRINTERR("Failed to Map input buffer %p\n", pEncodeBuffer->stInputBfr.hInputSurface);bError = true;outLenth = 0;return NULL;}}else//DirectX or any others{unsigned char *pInputSurface = NULL;uint32_t lockedPitch = 0;while (pInputSurface == NULL){nvStatus = m_pNvHWEncoder->NvEncLockInputBuffer(pEncodeBuffer->stInputBfr.hInputSurface, (void**)&pInputSurface, &lockedPitch);if (nvStatus != NV_ENC_SUCCESS)return NULL;if (pInputSurface == NULL){nvStatus = m_pNvHWEncoder->NvEncUnlockInputBuffer(pEncodeBuffer->stInputBfr.hInputSurface);if (nvStatus != NV_ENC_SUCCESS)return NULL;Sleep(1);}}if (pEncodeBuffer->stInputBfr.bufferFmt == NV_ENC_BUFFER_FORMAT_NV12_PL){unsigned char *pInputSurfaceCh = pInputSurface + (pEncodeBuffer->stInputBfr.dwHeight*lockedPitch);CmnConvertYUVtoNV12(stEncodeFrame.yuv[0], stEncodeFrame.yuv[1], stEncodeFrame.yuv[2], pInputSurface, pInputSurfaceCh, stEncodeFrame.width, stEncodeFrame.height, stEncodeFrame.width, lockedPitch);}}nvStatus = m_pNvHWEncoder->NvEncEncodeFrame(pEncodeBuffer, &m_encPicCommand, m_encodeConfig.width, m_encodeConfig.height,NV_ENC_PIC_STRUCT_FRAME, m_qpDeltaMapArray, m_qpDeltaMapArraySize);if (nvStatus != NV_ENC_SUCCESS){bError = true;outLenth= 0;return NULL;}pEncodeBuffer = m_EncodeBufferQueue.GetAvailable();if (!pEncodeBuffer){pEncodeBuffer = m_EncodeBufferQueue.GetPending();// 获取编码的h264/h265数据 [12/15/2016 dingshuai]nvStatus = m_pNvHWEncoder->ProcessOutput(pEncodeBuffer, m_pOutputBuffer, m_nOutputBufLen);if(nvStatus != NV_ENC_SUCCESS){bError = true;outLenth= 0;}if (m_encodeConfig.deviceType == 0)//CUDA{// UnMap the input buffer after frame doneif (pEncodeBuffer->stInputBfr.hInputSurface){nvStatus = m_pNvHWEncoder->NvEncUnmapInputResource(pEncodeBuffer->stInputBfr.hInputSurface);pEncodeBuffer->stInputBfr.hInputSurface = NULL;}//pEncodeBuffer = m_EncodeBufferQueue.GetAvailable();}else{nvStatus = m_pNvHWEncoder->NvEncUnlockInputBuffer(pEncodeBuffer->stInputBfr.hInputSurface);if (nvStatus != NV_ENC_SUCCESS)return NULL;}}else{outLenth= 0;return NULL;}if (m_encPicCommand.bForceIDR){m_encPicCommand.bForceIDR = 0;}outLenth = m_nOutputBufLen;return m_pOutputBuffer;
}
  • 第四步,关闭编码器,释放编码器申请的内存和显卡资源
int CloseNvEncoder()
{m_bWorking = false; NVENCSTATUS nvStatus = NV_ENC_SUCCESS;ReleaseIOBuffers();m_pNvHWEncoder->NvEncDestroyEncoder();if (m_cuContext){__cu(cuCtxDestroy(m_cuContext));}return nvStatus;
}

有任何技术问题,欢迎大家加入SkeyePlayer流媒体播放器 QQ群和我技术交流,进行讨论:102644504

SkeyeRTSPLive高效转码之SkeyeVideoEncoder高效硬件编码解决方案(附源码)相关推荐

  1. (附源码)spring boot校园二手销售网站 附源码161417

    目 录 摘要 1 1 绪论 1 1.1 研究背景 1 1.2国内外研究现状 1 1.3论文结构与章节安排 1 2开发工具及相关技术介绍 技术介绍 3 2.1 MVVM模式介绍 3 2.2 B/S体系工 ...

  2. c语言 gb2312转utf8,嵌入式utf-8转码gb2312的c语言实现,附源码

    部分参考: www.360doc.com/content/12/0926/12/1072296_238242301.shtml https://blog.csdn.net/wyingquan/arti ...

  3. kesci数据分类练习赛:提供银行精准营销解决方案(附源码)

    (kesci数据分类预测)提供银行精准营销解决方案练习赛 kesci的一个练习赛:https://www.kesci.com/home/competition/5c234c6626ba91002bfd ...

  4. 城乡投票源码php_PHP发表心情投票功能示例(附源码)

    当浏览新闻页面或者其它页面的时候会有阅读后的感受,比如给力.淡定.打酱油.加油.坑爹等等的表情.让读者打分,看看自己的感受是否与其他读者一样.很不错的交互! 立即下载:moodjb51.rar(htt ...

  5. easypoi 批量导出_POI导出大量数据的简单解决方案(附源码)

    说明:我的电脑 2.0CPU 2G内存 能够十秒钟导出 20W 条数据 ,12.8M的excel内容压缩后2.68M 我们知道在POI导出Excel时,数据量大了,很容易导致内存溢出.由于Excel ...

  6. php 简单考试系统源码,php实现在线考试系统【附源码】

    温馨提示:本信息由[金聪采编]搜集整理发布,版权归原作者及发布者所有,您如有异议请 举报 或者 版权申诉. 多任务 (并行和并发) 在讲协程之前,先谈谈多进程.多线程.并行和并发. 对于单核处理器,多 ...

  7. 掘金量化 | 短周期量价策略(附源码)

    可能不少朋友都有阅读过国泰君安<基于短周期价量特征的多因子选股体系>这篇研报,对其内多达191个量价因子印象深刻.该研报是在2017年中旬发布的,时至今日已过去四年时光,为此大家可能会好奇 ...

  8. 用JavaScript实现网红太空人表盘(绝对详细、绝对原创),附源码下载

    引言:网上最近太空人表盘很火,之前看到有个兄弟用svg写的,但是我也不会这个啊,我就琢磨着用canvas写了一个,效果感觉还不错,拿出来大家唠唠! 效果图: 思路 分两个画布来绘制,画布1用来放置不动 ...

  9. 吃豆人游戏【附源码】

    吃豆游戏[附源码] 吃豆人游戏[附源码] 我的网站已经上线了 http://javapub.net.cn/ 博主介绍:

  10. html制作好看的个人简历(附源码)

    文章目录 1.设计来源 1.1 主界面 1.2 基本资料页面 1.3 个人名言页面 1.4 教育经历页面 1.5 联系方式页面 1.6 自我评价页面 1.7 工作经历页面 1.8 兴趣爱好页面 1.9 ...

最新文章

  1. 友元类实例:日期类 学生类
  2. 安装 | Android studio连接不上真机解决办法(电脑安装虚拟机不成功的情况下)
  3. 人人都能做游戏!3D次世代CE云端引擎发布
  4. Centos 7.5安装配置MongoDB 4.0.4
  5. 如何让多文本内容只显示一行,其余用省略号来显示
  6. 箱线图怎么判断异常值_极简统计学---箱线图[2]
  7. 第十章:SpringCloud Zuul路由器和过滤器
  8. jmeter常用功能
  9. 在OLT上查看SLAN
  10. SharePoint2010 空白站点集无法找到术语管理库
  11. 9.触摸屏驱动(IIC)移植实战
  12. 这五大基础原理,总是牛逼的无话可说
  13. 平面波角谱积分 matlab,第2章2_5平面波角谱.ppt
  14. Java多线程系列--“JUC线程池”03之 线程池原理——线程池源码分析
  15. 《论文写作》课程感想
  16. 9大代理服务器软件的比较与分析之校园局域网代理蝴蝶
  17. 高速计数器转RS485Modbus RTU模块IBF150
  18. plc串口通讯 qt_Qt 编写串口调试助手
  19. Revit MEP 平面视图中(立管)怎么设置二维表达?
  20. ssm基于JAVA的求职招聘网站的设计与实现计算机毕业设计

热门文章

  1. 解题1953 World Cup Noise
  2. 360日历精选弹窗如何关闭?
  3. 累加功能的实现 (累计计算)
  4. 基于51单片机的简易抢答器
  5. unlink(freenote_x64)
  6. 线性代数笔记——汤家凤
  7. Emacs AucTex
  8. 【ElM分类】基于哈里斯鹰优化ElM神经网络实现数据分类附matlab代码
  9. 黑苹果mac未能安装在你的电脑上_技嘉z97,安装黑苹果,无法进入安装界面
  10. Impala 在网易大数据的优化和实践