使用 NVENC 硬编码视频
简介 NVENC 是 NVIDIA 视频编解码器 中的编码部分,使用硬件加速视频编码,官方参考文档 。
测试编码能力
H264 编码最大分辨率为 4096 x 4096。
H265 编码最大分辨率为 8192 x 8192。
NVENC NV12 编码视频 H264,不支持推流到 RTMP。
NVENC NV12 编码视频 H265,不支持推流到 RTSP、RTMP、UDP,所以选用推流到 TCP,使用 TCP 转发到 RTSP 的方式。
构建代码 构建环境
构建步骤
下载 NVIDIA VIDEO CODEC SDK
使用 CMake 构建项目 ./Video_Codec_SDK_12.0.16/Samples
启动构建好的 NvCodec.sln
本文基于工程中 AppEncCuda 项目开发
配置参数 编码数据类型 设置编码输入格式,指编码数据的类型。1 2 3 4 5 6 7 typedef enum _NV_ENC_BUFFER_FORMAT { NV_ENC_BUFFER_FORMAT_IYUV, NV_ENC_BUFFER_FORMAT_NV12 } NV_ENC_BUFFER_FORMAT;
编码器类型预设参数 设置编码器类型与预设参数,修改 NvEncoderCLIOptions.h 文件,在 NvEncoderInitParam 构造函数中增加配置。
编码器类型支持 H264 或 H265。
编码器预设参数,从 P1 到 P7,性能下降,质量提高。默认情况下,H264 的预设 P3 至 P7 和 HEVC 的预设 P2 至 P7。1 2 3 4 5 NvEncoderInitParam (const char * szParam = "" , std::function<void (NV_ENC_INITIALIZE_PARAMS* pParams)>* pfuncInit = NULL , bool _bLowLatency = false ) : strParam (szParam), bLowLatency (_bLowLatency) , guidCodec (NV_ENC_CODEC_HEVC_GUID) , guidPreset (NV_ENC_PRESET_P3_GUID)
代码 初始化 NVENC 1 2 3 4 5 6 7 8 9 CUcontext cuContext_ = NULL ; unique_ptr<NvEncoderCuda> pEnc_;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 NV_ENC_BUFFER_FORMAT eFormat = NV_ENC_BUFFER_FORMAT_NV12; int iGpu = 0 ;NvEncoderInitParam encodeCLIOptions = NvEncoderInitParam ("" , NULL , true ); ck (cuInit (0 ));int nGpu = 0 ;ck (cuDeviceGetCount (&nGpu));if (iGpu < 0 || iGpu >= nGpu){ cout << "GPU ordinal out of range. Should be within [" << 0 << ", " << nGpu - 1 << "]" << endl; return ; } CUdevice cuDevice = 0 ; ck (cuDeviceGet (&cuDevice, iGpu));char szDeviceName[80 ];ck (cuDeviceGetName (szDeviceName, sizeof (szDeviceName), cuDevice));cout << "GPU in use: " << szDeviceName << endl; ck (cuCtxCreate (&cuContext_, 0 , cuDevice));pEnc_ = unique_ptr<NvEncoderCuda>(new NvEncoderCuda (cuContext_, width, height, eFormat)); NV_ENC_INITIALIZE_PARAMS initializeParams = { NV_ENC_INITIALIZE_PARAMS_VER }; NV_ENC_CONFIG encodeConfig = { NV_ENC_CONFIG_VER }; initializeParams.encodeConfig = &encodeConfig; pEnc_->CreateDefaultEncoderParams (&initializeParams, encodeCLIOptions.GetEncodeGUID (), encodeCLIOptions.GetPresetGUID (), encodeCLIOptions.GetTuningInfo ()); initializeParams.enableEncodeAsync = true ; encodeConfig.gopLength = control_fps_ * 2 ; encodeConfig.frameIntervalP = 1 ; if (encodeCLIOptions.IsCodecH264 ()){ encodeConfig.encodeCodecConfig.h264Config.idrPeriod = encodeConfig.gopLength; } else { encodeConfig.encodeCodecConfig.hevcConfig.idrPeriod = encodeConfig.gopLength; } encodeConfig.rcParams.rateControlMode = NV_ENC_PARAMS_RC_VBR; int64_t bit = width * height * control_fps_ / 100 ;if (bit > 2000000 ){ encodeConfig.rcParams.averageBitRate = width * height * control_fps_ / 100 ; } else { encodeConfig.rcParams.averageBitRate = 2000000 ; } initializeParams.frameRateDen = 1 ; initializeParams.frameRateNum = control_fps_; encodeCLIOptions.SetInitParams (&initializeParams, eFormat); pEnc_->CreateEncoder (&initializeParams);
编码视频 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void * pSrcFrame;uint32_t nSrcPitch;vector<vector<uint8_t >> vPacket; const NvEncInputFrame* encoderInputFrame = pEnc_->GetNextInputFrame ();NvEncoderCuda::CopyToDeviceFrame (cuContext_, pSrcFrame, nSrcPitch, (CUdeviceptr)encoderInputFrame->inputPtr, (int )encoderInputFrame->pitch, pEnc_->GetEncodeWidth (), pEnc_->GetEncodeHeight (), CU_MEMORYTYPE_HOST, encoderInputFrame->bufferFormat, encoderInputFrame->chromaOffsets, encoderInputFrame->numChromaPlanes); pEnc_->EncodeFrame (vPacket);
处理编码视频 保存为本地文件 1 2 3 4 5 6 7 8 9 10 11 12 13 string szOutFilePath = "FilePath" ; std::ofstream fpOut (szOutFilePath, std::ios::out | std::ios::binary) ;if (!fpOut){ std::ostringstream err; err << "Unable to open output file: " << szOutFilePath << std::endl; throw std::invalid_argument (err.str ()); } for (std::vector<uint8_t >& packet : packets){ fpOut.write (reinterpret_cast <char *>(packet.data ()), packet.size ()); } fpOut.close ();
使用 FFmpeg 推流视频 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 AVFormatContext* outputContext_; int control_fps_ = 25 ;AVRational time_base_; int64_t pts_ = 0 ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 for (auto & packet : vPacket) { AVPacket avPacket; av_init_packet (&avPacket); avPacket.size = packet.size (); avPacket.data = packet.data (); avPacket.pts = av_rescale_q (pts_++, { 1 , control_fps_ }, time_base_); ret = av_interleaved_write_frame (outputContext_, &avPacket); if (ret < 0 ) { av_packet_unref (&avPacket); printf ("Push stream failed, unable to connect to streaming server!\n" ); return ; } av_packet_unref (&avPacket); }
释放内存 1 2 cuCtxDestroy (cuContext_);
注意事项 由于驱动版本导致的异常 NvEncodeAPIGetMaxSupportedVersion(&version) 报错 192>117: Current Driver Version does not support this NvEncodeAPI version, please upgrade driver 显卡驱动程序包含的 CUDA 版本号,可以从 nvidia-smi 中查看,NvEncodeAPI 12.0 要求 cuda 版本 12.0,22年12月
编码会话数限制 消除 Nvidia 对消费级 GPU 施加的最大同时 NVENC 视频编码会话数限制
FFmpeg 推流 TCP 转发到 RTSP 推流到 TCP 需要先使用命令启动服务1 2 3 4 5 6 :: 使用默认编码器 ffmpeg -listen 1 -i tcp://0 .0 .0 .0 :1234 -f rtsp rtsp://localhost:8554 /main :: 使用 libx264 编码器 ffmpeg -listen 1 -i tcp://0 .0 .0 .0 :1234 -c:v libx264 -f rtsp rtsp://localhost:8554 /main :: 使用 libx265 编码器 ffmpeg -listen 1 -i tcp://0 .0 .0 .0 :1234 -c:v libx265 -f rtsp rtsp://localhost:8554 /main