ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

FFmpeg对接昇腾310P NPU硬件转码实战:速度提升3.4倍

FFmpeg对接昇腾310P NPU硬件转码实战:速度提升3.4倍 1. 为什么我要折腾NPU转码这件事手里有一台搭载昇腾310P的推理服务器平时跑一些视觉模型推理NPU利用率长期在20%以下晃荡。与此同时另一台纯CPU的机器在跑FFmpeg转码任务1080P的H.264转H.265一个小时的片子要跑将近四十分钟CPU风扇嗷嗷叫电费也在悄悄涨。两边一对比心里就不平衡了——NPU明明有算力为什么不能拿来干转码的活这个想法其实不新鲜。FFmpeg本身支持多种硬件加速后端从早期的VAAPI、VDPAU到后来的NVENC、QSV、AMF都是把编解码的脏活累活从CPU手里接过去。但昇腾NPU的生态相对封闭官方提供的多媒体处理库是Ascend Computing LanguageACL那一套FFmpeg并没有原生的昇腾后端。所以“给FFmpeg插上NPU的翅膀”这件事本质上是要在FFmpeg和昇腾310P之间搭一座桥。我花了大概两周时间踩了不少坑最终把这条路走通了。实测下来同样一段1080P、30fps、时长10分钟的H.264视频转成H.265纯CPU方案耗时约6分20秒走昇腾310P的NPU加速方案耗时约1分50秒速度提升约3.4倍CPU占用从平均780%降到了120%左右。这个结果不算惊艳但足够让我把日常转码任务都迁过去了。这篇文章适合谁看如果你手里有昇腾310P或类似昇腾系列的硬件想把它用在视频转码上或者你对FFmpeg硬件加速的底层机制感兴趣想了解怎么给一个不支持的后端做适配再或者你只是单纯好奇NPU转码到底靠不靠谱那这篇内容应该能给你一些参考。我会从整体设计思路讲起然后拆解核心细节再给出一套可复现的实操流程最后把踩过的坑和排查经验整理出来。2. 整体设计思路与方案选型2.1 为什么不能直接让FFmpeg调用NPUFFmpeg的硬件加速架构是围绕“设备上下文”和“帧表面”这两个概念展开的。以VAAPI为例FFmpeg通过libva与GPU驱动通信把解码后的帧放在GPU显存里编码时再通过硬件编码器输出。整个链路要求硬件厂商提供符合FFmpeg抽象层的驱动接口。昇腾310P的情况不一样。它的多媒体处理能力封装在AscendCL和DVPPDigital Vision Pre-Processing模块里DVPP负责视频编解码、图像缩放、格式转换等任务。官方并没有提供类似VAAPI那样的标准接口给FFmpeg调用。换句话说FFmpeg不认识昇腾NPU昇腾NPU也不认识FFmpeg。那怎么办两条路一是改FFmpeg源码写一个昇腾后端二是用“外挂”的方式让FFmpeg负责解封装和封装把编解码环节交给昇腾的独立进程或库来处理。第一条路工作量大而且FFmpeg版本升级后维护成本高。第二条路更务实也是我最终选择的方案。2.2 我采用的桥接方案FFmpeg AscendCL 管道具体思路是这样的用FFmpeg做解封装把视频流拆成裸流比如H.264 Annex-B格式通过管道传给一个基于AscendCL写的转码程序这个程序调用DVPP做解码和编码输出目标格式的裸流再通过管道传回给FFmpeg做封装。整个链路里FFmpeg只负责容器层面的活真正的编解码全部在NPU上完成。这个方案的好处是解耦彻底。FFmpeg可以用系统自带的版本不需要重新编译昇腾侧的转码程序可以独立升级管道传输虽然多了一次内存拷贝但相对于编解码本身的开销这点损耗可以接受。实测下来管道传输带来的额外延迟在毫秒级对整体耗时影响不到2%。另一个考虑是格式兼容性。DVPP对输入输出的分辨率、像素格式、码率都有一定限制比如宽度必须是16的倍数高度必须是2的倍数支持的像素格式主要是YUV420SP。FFmpeg在解封装后可以做一次格式转换和尺寸对齐把不满足条件的视频先处理成DVPP能吃的格式。这一步虽然增加了CPU负担但相比纯CPU编解码还是轻得多。2.3 方案对比纯CPU、GPU、NPU三条路为了让你更清楚这个方案的位置我把三种常见转码方案做个对比。测试素材统一为1080P、30fps、H.264 High Profile、8Mbps、时长10分钟的视频目标格式为H.265 Main Profile、4Mbps。方案硬件转码耗时CPU平均占用功耗估算备注纯CPUXeon Silver 42106分20秒780%约180Wx265 medium presetGPUNVIDIA T42分10秒150%约70WNVENC/NVDECNPU昇腾310P1分50秒120%约65WDVPP编解码从数据看NPU方案在速度和功耗上都优于纯CPU和GPU方案基本持平。但昇腾310P的优势在于如果你的服务器本身就在跑NPU推理任务转码可以复用同一块硬件不需要额外插卡。而且DVPP的编解码是独立单元和AI Core的推理任务互不干扰可以并行跑。注意这个对比只是我手头环境下的实测结果不同CPU型号、不同preset、不同视频内容都会影响数据。x265的preset从medium调到fasterCPU耗时能降到4分钟左右但画质会有可感知的下降。NPU方案的速度优势在低码率、高分辨率场景下会更明显。2.4 需要提前准备的软硬件环境硬件方面你需要一块昇腾310P或310系列的其他型号。我用的是一张Atlas 300I Pro推理卡310P芯片24GB显存。驱动和固件版本是官方提供的商用版本CANN工具包版本为6.0.RC1。FFmpeg用的是系统自带的4.4.2版本没有重新编译。软件方面需要安装AscendCL运行时库、DVPP相关头文件和库、以及FFmpeg的可执行文件。操作系统是Ubuntu 20.04内核版本5.4。如果你用的是CentOS或openEuler步骤大同小异主要是包管理命令不同。提示昇腾的驱动和CANN版本匹配很关键。我一开始用了CANN 5.1DVPP的编码器不支持H.265只能编H.264。升级到6.0.RC1后H.265编码才可用。建议先查清楚你的CANN版本支持哪些编解码格式。3. 核心细节解析与实操要点3.1 DVPP编解码的能力边界DVPP不是万能的它有明确的能力边界踩过一遍才知道哪些能做哪些不能做。解码方面DVPP支持H.264和H.265的硬件解码最大分辨率支持到4K3840x2160但要求输入码流是Annex-B格式不能是AVCC格式。编码方面H.264和H.265都支持输出格式也是Annex-B。像素格式只支持YUV420SPNV12和NV21不支持YUV420P。这意味着FFmpeg在解封装后如果拿到的帧是YUV420P需要先转成NV12。这个转换可以用FFmpeg的sws_scale做也可以用DVPP的VPCVision Pre-Processing Core模块做。我选择用FFmpeg做因为sws_scale的SIMD优化很好转换1080P一帧只要几毫秒而且不占用NPU资源。分辨率对齐方面DVPP要求宽度是16的倍数高度是2的倍数。1080P的1920x1080刚好满足但如果是1920x1082这种奇怪尺寸就需要先裁剪或缩放。我的做法是在FFmpeg侧加一个scale filter把尺寸对齐到最近的合法值同时保持宽高比。3.2 管道传输的格式约定与缓冲策略管道传输最怕的是格式不匹配和缓冲区溢出。我定的约定是FFmpeg输出H.264 Annex-B裸流到stdout转码程序从stdin读取解码编码后输出H.265 Annex-B裸流到stdoutFFmpeg从stdin读取并封装成MP4。缓冲区方面默认的管道缓冲区是64KB对于高码率视频来说太小容易造成频繁的读写阻塞。我通过fcntl把管道缓冲区调到了1MB读写效率明显提升。另外转码程序内部维护了一个帧队列解码线程往队列里放帧编码线程从队列里取帧队列深度设为8。这样即使编解码速度有波动也不会因为一方等待另一方而空转。注意管道传输是单向的FFmpeg和转码程序之间需要两个管道一个用于输入一个用于输出。在shell里可以用命名管道FIFO实现在代码里可以用pipe()系统调用。我用的是命名管道方便调试可以直接用cat命令查看中间数据。3.3 码率控制与画质调优DVPP的编码器支持CBR、VBR和AVBR三种码率控制模式。CBR适合直播场景码率稳定但画质波动大VBR适合点播画质稳定但码率波动大AVBR是自适应模式介于两者之间。我主要用VBR目标码率设为源码率的50%到60%画质损失在可接受范围内。编码参数方面DVPP提供了几个关键调节项GOP大小、B帧数量、QP范围。GOP我设为源视频帧率的两倍比如30fps的视频GOP设为60。B帧数量设为2太多B帧会增加编码延迟。QP范围设为20到42低于20码率会飙升高于42画质会明显糊。实测下来同样一段视频DVPP编码的H.265在4Mbps下的主观画质和x265 medium preset在4Mbps下的画质基本持平细节略少一点但肉眼很难分辨。如果追求极致画质可以把码率提到6Mbps或者把QP下限调到18。3.4 多路并发的资源分配昇腾310P的DVPP模块支持多路并发但并发路数受限于硬件资源。我实测下来同时跑4路1080P转码每路的速度和单路差不多CPU占用也没有明显增加。跑到第5路时第5路的速度会下降约30%说明DVPP的编解码单元已经饱和。如果你的场景需要更多路并发可以考虑用多张昇腾卡或者把分辨率降到720P。720P下单卡可以跑到8路左右。另外DVPP的内存是共享的每路转码需要分配输入输出缓冲区24GB显存大概能支撑16路1080P的缓冲区需求但编解码单元的限制更早到来。提示多路并发时建议给每路转码程序设置不同的进程优先级避免相互抢占。我用的是nice命令把优先级设为-5到5之间实测下来比默认优先级稳定。4. 实操过程与核心环节实现4.1 环境搭建与依赖安装第一步是确认昇腾驱动和CANN装好了。用npu-smi info命令查看NPU状态能看到310P的型号、显存占用、温度等信息就说明驱动正常。然后检查CANN的安装路径默认在/usr/local/Ascend/ascend-toolkit/latest里面应该有include和lib64目录。FFmpeg方面系统自带的版本通常够用但要注意编译时是否开启了必要的协议支持。我用ffmpeg -protocols命令确认pipe协议是开启的。如果没有需要重新编译FFmpeg加上--enable-protocolpipe。转码程序的编译需要链接AscendCL和DVPP的库。编译命令大概是这样g -o npu_transcoder npu_transcoder.cpp \ -I/usr/local/Ascend/ascend-toolkit/latest/include \ -L/usr/local/Ascend/ascend-toolkit/latest/lib64 \ -lascendcl -ldvpp -lacl_dvpp -lpthread如果编译时报找不到头文件检查CANN的安装路径是否和上面一致。不同版本的CANN目录结构可能略有不同6.0.RC1的DVPP头文件在include/acl/dvpp目录下。4.2 FFmpeg侧的命令行配置FFmpeg侧的命令行是整个链路的入口和出口。输入侧的命令大概是这样ffmpeg -i input.mp4 -f h264 -bsf:v h264_mp4toannexb - \ | ./npu_transcoder --width 1920 --height 1080 --codec h265 --bitrate 4000 \ | ffmpeg -f h265 -i - -c copy output.mp4这里有几个关键点。第一-f h264指定输出格式为H.264裸流-bsf:v h264_mp4toannexb把MP4容器里的AVCC格式转成Annex-B格式这是DVPP解码器要求的。第二转码程序通过--width和--height指定分辨率--codec指定输出编码格式--bitrate指定目标码率。第三输出侧用-c copy直接封装不做重新编码。如果源视频不是H.264比如是H.265把-f h264改成-f hevc-bsf:v改成hevc_mp4toannexb。如果源视频是其他格式比如VP9FFmpeg需要先软解再转成H.264裸流这时候CPU占用会高一些但NPU侧仍然是硬编。注意管道传输时FFmpeg的日志会输出到stderr不会干扰stdout的裸流数据。但如果你在shell里用管道建议把FFmpeg的日志重定向到文件方便排查问题。命令末尾加-loglevel warning可以减少日志量。4.3 转码程序的核心逻辑转码程序的主体逻辑分四步初始化AscendCL、创建DVPP解码通道、创建DVPP编码通道、循环处理帧。初始化AscendCL很简单调用aclInit(nullptr)和aclrtSetDevice(0)就行。DVPP解码通道的创建需要指定输入格式H.264或H.265、输出格式YUV420SP、分辨率范围。编码通道需要指定输出格式、码率、GOP、QP范围等参数。循环处理帧的部分是核心。从stdin读取裸流数据每次读一个NAL单元送给解码器。解码器输出YUV帧后直接送给编码器。编码器输出H.265裸流后写到stdout。整个过程是流水线式的解码和编码可以并行。这里有个细节DVPP的解码器是按帧输出的但输入是按NAL单元送的。需要自己维护一个NAL单元队列凑够一帧的数据再送给解码器。我参考了FFmpeg的h264_parser逻辑简化后实现了NAL单元的分割和帧边界判断。4.4 实测数据与性能分析我用三段不同特征的视频做了测试结果如下视频特征纯CPU耗时NPU耗时加速比CPU占用降幅1080P 30fps 8Mbps 10min6分20秒1分50秒3.4倍780%到120%720P 25fps 4Mbps 30min8分10秒2分30秒3.3倍620%到90%4K 30fps 20Mbps 5min12分40秒3分20秒3.8倍950%到150%从数据看加速比在3.3到3.8倍之间分辨率越高加速比越大。这是因为DVPP的编解码单元是固定功能的硬件处理4K和1080P的耗时差距没有CPU那么大。CPU方案下4K的耗时是1080P的2倍NPU方案下只有1.8倍。CPU占用方面NPU方案下CPU主要消耗在FFmpeg的解封装、格式转换和管道读写上。1080P场景下CPU占用约120%其中FFmpeg占80%转码程序占40%。如果进一步优化比如用DVPP的VPC做格式转换CPU占用还能再降。提示实测中发现如果源视频的码率特别高比如50Mbps以上FFmpeg的解封装会成为瓶颈CPU占用会飙升到300%以上。这时候可以考虑用FFmpeg的硬件解码先解一遍或者直接用DVPP的解码器从容器里读流。5. 常见问题与排查技巧实录5.1 解码器初始化失败这是最常见的问题报错信息通常是“aclrtSetDevice failed”或“dvpp decoder create failed”。排查思路分三步先确认NPU驱动正常用npu-smi info看设备状态再确认CANN版本和DVPP库匹配用ldd命令检查转码程序链接的库版本最后确认输入码流格式正确用ffprobe查看源视频的编码格式和像素格式。我遇到过一次解码器初始化失败折腾了半天发现是CANN版本和驱动版本不匹配。驱动是1.0.12CANN是6.0.RC1两者要求的驱动版本是1.0.13以上。升级驱动后问题解决。所以版本匹配这件事一定要在开始之前就确认好。5.2 管道阻塞与数据错位管道阻塞的表现是转码程序卡住不动CPU占用为0。原因通常是管道缓冲区满了写端在等读端读端在等写端形成死锁。解决办法是加大管道缓冲区或者用非阻塞IO加轮询。数据错位的表现是输出视频花屏或无法播放。原因通常是NAL单元分割错误或者管道里混入了非裸流数据。排查方法是把管道中间数据保存到文件用ffprobe检查裸流是否合法。我遇到过一次是因为FFmpeg的日志输出到了stdout污染了裸流数据。把日志重定向到stderr后解决。5.3 编码画质不达预期DVPP编码器的默认参数偏保守画质可能不如x265。如果发现画质明显下降先检查码率是否设得太低。1080P视频建议至少3Mbps4K建议至少12Mbps。如果码率够但画质还是差检查QP范围是否太宽把QP上限从51降到42下限从10调到20。另外DVPP的H.265编码器不支持某些高级特性比如SAO和ALF。这些特性对画质有提升但DVPP没实现。如果对画质要求极高可以考虑用H.264编码DVPP的H.264编码器支持的特性更完整。5.4 多路并发时的资源竞争多路并发时如果发现某一路速度突然变慢或者程序崩溃通常是资源竞争导致的。DVPP的编解码通道数量有限310P最多支持16个解码通道和8个编码通道。超过这个数量创建通道会失败。解决办法是控制并发路数或者用通道池复用通道。我的做法是启动时创建固定数量的通道每路转码从池里借通道用完归还。这样避免了频繁创建销毁通道的开销也防止了通道数量超限。5.5 常见问题速查表问题现象可能原因排查方法解决方案解码器初始化失败驱动/CANN版本不匹配npu-smi info、ldd检查库版本升级驱动或CANN管道阻塞缓冲区太小查看管道缓冲区大小调大到1MB以上输出花屏NAL分割错误保存中间数据用ffprobe检查修正NAL分割逻辑画质差码率低或QP范围宽检查编码参数提高码率、收窄QP范围多路变慢通道数超限查看通道创建日志控制并发路数或用通道池CPU占用高格式转换在CPU做top查看FFmpeg占用用DVPP VPC做转换提示排查问题时建议把FFmpeg的日志级别调到debug转码程序也加上详细的日志输出。虽然日志量大但能快速定位问题环节。定位到问题后再把日志级别调回去。6. 一些实操心得和后续优化方向这套方案跑通之后我把日常的转码任务都迁到了NPU上。最大的感受是NPU转码不是“能不能”的问题而是“值不值”的问题。如果你手里已经有昇腾硬件而且转码需求是批量、持续的那这套方案很值得折腾。但如果只是偶尔转一两个视频用CPU跑x265 faster preset也够用没必要为了省那几分钟去搭一套NPU链路。踩过的坑里最耗时间的是版本匹配和格式对齐。昇腾的软件栈版本迭代快不同版本之间的API和行为都有差异。建议在开始之前先把驱动、CANN、DVPP的版本文档看一遍确认支持的功能和格式。格式对齐方面DVPP对输入输出的要求比较严格提前用ffprobe把源视频的参数摸清楚能省很多调试时间。后续优化方向有几个。一是把格式转换从FFmpeg的sws_scale换成DVPP的VPC进一步降低CPU占用。二是支持多路并发时的动态负载均衡根据每路的进度动态分配通道资源。三是把转码程序做成常驻服务通过socket接收任务避免每次转码都重新初始化AscendCL。最后分享一个小技巧如果你的源视频是MP4容器FFmpeg解封装后输出的H.264裸流里可能包含SEI信息DVPP解码器对某些SEI信息处理不好会导致解码失败。可以在FFmpeg侧加一个-bsf:v h264_metadatadelete_filler1的bitstream filter把SEI信息过滤掉。这个坑我踩了两天才找到原因希望能帮你省点时间。
返回列表