ARTICLE DETAIL

资讯详情

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

FFmpeg + 昇腾310B硬解码实战:从CPU软解瓶颈到多路视频分析

FFmpeg + 昇腾310B硬解码实战:从CPU软解瓶颈到多路视频分析 如果只让我说一句话那就是这两年撑住我视频业务不崩的不是给服务器堆CPU而是把 FFmpeg 的解码这活儿从 CPU 上挪到了昇腾310B上。之前有段时间我们平台要做多路 RTSP 直播流的实时分析服务器上跑的是标准 FFmpeg 软解流程。一开始只是几路CPU 还算撑得住后来监控点位一加到几十路单台 24 核的机器直接被打满视频抽帧延迟从几十毫秒飙到几百毫秒后面排队等待解码的帧越来越多整个实时分析链路跟得了哮喘一样走走停停。后来我把昇腾310B的硬解码能力接进 FFmpeg才真正把这条路跑通。这篇不聊虚的就讲清楚昇腾310B FFmpeg 这套硬解码方案是怎么设计的、怎么落地的、中间踩了哪些坑以及解完码之后瓶颈到底转移到了哪里。这篇适合谁看如果你正在做视频分析、直播流处理、边缘视频网关这类项目手上有昇腾设备但不知道怎么让 FFmpeg 把解码任务交给硬件或者说你已经用 CPU 软解跑到性能极限、打算换硬解方案那可以认真把这篇看完。1. 视频处理瓶颈到底卡在哪软解与硬解的拉锯战1.1 为什么 CPU 软解会成为瓶颈很多人一开始做视频处理都是直接从 FFmpeg 默认链路开始的。也就是说avcodec 的解码逻辑跑在 CPU 上用 CPU 的通用计算单元去处理 H.264/H.265 的熵编码、反变换、帧内预测、运动补偿这些环节。这套流程从代码角度来说没有任何问题FFmpeg 的软解非常成熟兼容性也好几乎所有编码视频都能解但问题是它太吃 CPU 了。我举个实际数据感受一下。拿 1080p60 的 H.264 高码率流来说一台 24 核的服务器纯软解大概能跑 8 到 12 路这还是在编码参数正常、没有 B 帧乱序、没有长时间 I 帧堆积的情况下。如果码率达到 10Mbps 以上或者分辨率升级到 4K路数还会往下掉。你想一个视频分析系统不只是解码后面还要做抽帧、缩放、推理、推流CPU 全部被解码吃掉真正留给算法的资源就少得可怜了。生活化一点说CPU 软解就像一个全能大厨什么菜系都会做但你让他一个人同时炒几十桌菜他再厉害也会手忙脚乱。视频硬解则是把“炒菜”这个动作拆出去让专门的流水线设备来做CPU 只负责“点菜、上菜和调度”整体效率完全不是一个量级。1.2 FFmpeg 传统硬解方案的局限性其实 FFmpeg 早就支持硬解了常见的有 NVDEC、VAAPI、Intel QSV、AMF 这些。但在实际工程里传统硬解方案有几个很现实的问题。第一个是显存和系统内存之间的拷贝开销。你用 NVDEC 解出来的数据默认存在 GPU 显存里如果后续处理逻辑是 CPU 的就必须把数据从显存拷回内存DMA 拷贝大帧的时候延迟不小路数一多这条路径也变得拥挤并不像理论上那么“零拷贝”。第二个是硬件绑定的碎片化问题。NVDEC 绑定 NVIDIA 显卡QSV 绑定 Intel 核显VAAPI 在 AMD 和 Intel 上行为还不完全一样。你的代码一旦用了某个硬解方案后面想换平台基本等于重写牵一发动全身。第三个是被忽略的能耗和部署密度问题。传统 GPU 显卡功耗高、体积大还要主动散热放到边缘机房或者现场的盒子里面供电和散热都是麻烦。而昇腾310B这类专门做推理和视频编解码的芯片功耗低、能被动散热、卡身小适合嵌进边缘设备里这在场景适配上有明显优势。所以说传统硬解不是不行只是对于做大量实时视频处理、又要在边缘端落地的场景来说它不够“清爽”。我们需要的是一个能跟 FFmpeg 无缝衔接、性能高、功耗低、方便批量部署的硬解模块昇腾310B正好在这个位置。2. 为什么选中昇腾310B一颗为视频编解码而生的芯片2.1 昇腾310B 的硬件解码能力定位昇腾310B是华为昇腾系列里偏边缘推理的一款芯片。很多人一听到昇腾就想到 AI 推理实际上它的媒体处理单元DVPPDigital Vision Pre-Processing天生就是干视频解码、缩放、抠图、格式转换这些脏活累活的。这不是我硬吹而是芯片架构里专门留了这么一块硬件逻辑跟 CPU 做通用计算是两条路线。我在项目里用的具体卡型是 Atlas 300I 推理卡板卡里面就是昇腾310B。从实际表现来看H.264/H.265 硬件解码的并发路数和单路帧率都相当可观。官方标称值我不在这里给大家背书毕竟不同驱动版本、不同码率参数下的结果差异很大但可以给出一个参考量级在常见监控码率4Mbps 到 8Mbps、1080p 或 3MP下一块卡处理多路实时流是轻轻松松的CPU 占用率能压到非常低。更关键的是DVPP 不只能解码还能把分辨率缩放和格式转换一并做了这部分在纯 CPU 方案里也是要单独立项优化的。2.2 FFmpeg 为外部硬解留的接口路子FFmpeg 能接外部硬件解码器靠的是它本身留了一套硬件加速接口体系。核心是 AVCodecHWConfig、AVHWFramesContext、AVHWDeviceContext 这几个结构体。简单说FFmpeg 定义一个“硬件设备上下文”然后解码器把解码输出绑定到这个上下文管理的帧上应用程序可以通过 av_hwframe_transfer_data() 把数据从设备侧取回 CPU 侧也可以直接在设备侧操作数据避免不必要的拷贝。接入昇腾310B的时候一般有两个方向。一个方向是给 FFmpeg 写一个自定义解码器封装昇腾的媒体处理接口最终向 FFmpeg 暴露普通 AVCodec 的样子另一个方向更省事直接用昇腾 CANN 软件栈里提供的 FFmpeg 适配补丁或插件编译 FFmpeg 的时候带上对应选项让硬件解码作为一个 hwaccel 类型出现。两种做法我在不同项目里都试过后一种对业务代码改动最小你甚至可以不改上层逻辑只需要在命令或代码里指定硬件设备就行。2.3 为什么这套组合能解决“瓶颈”问题这套组合的本质是改变了瓶颈的位置。CPU 软解的时候瓶颈在 CPU 的通用计算能力用了 FFmpeg 昇腾310B硬解之后解码和图像预处理都被挪到了专用硬件上CPU 腾出精力去做协议解析、业务逻辑和推理调度。更符合实际的是很多视频处理任务解码只是第一步真正的目的是抽帧做推理。昇腾310B本身就是 AI 推理芯片解码输出的帧可以直接送给芯片上的 AI Core 做推理推理不需要把每一帧都搬回 CPU。这个特性非常讨喜因为视频数据量巨大谁能在数据源头把处理做完、谁就省掉了大量传输开销。从架构上讲这就是“数据不动计算动”的思路也是我后来坚定选这条路的原因。3. 实操昇腾310B FFmpeg 硬解码流水线搭建3.1 环境准备与驱动工具链硬件部分推荐使用 Atlas 300I 推理卡或集成了昇腾310B的开发套件通过 PCIe 插到 x86 服务器上或者直接选用自带昇腾芯片的智能小站。软件部分核心是四样东西驱动固件、CANN Toolkit、FFmpeg 源码、昇腾媒体处理插件如果走官方适配方案的话。安装顺序不要乱。我踩过一次坑先装了 CANN 再补驱动结果版本对不上设备节点死活不出来。正确的顺序是先装昇腾驱动固件跑通 npu-smi 能看到芯片状态再装对应版本的 CANN Toolkit确认 runtime 和媒体处理接口可用最后才是准备 FFmpeg 相关的适配代码或插件。装完之后你用 npu-smi info 看一下能看到昇腾310B的芯片信息、温度、算力占用这一步通了再往下走。注意驱动、固件、CANN 的版本号是绑定的不能随便混搭。昇腾官方有一个版本配套表装之前一定要去查对应型号的推荐组合否则后面编译 FFmpeg 时链接不上媒体处理库你会查原因查到怀疑人生。3.2 FFmpeg 编译与硬解码模块引入如果你走官方适配补丁这条路编译 FFmpeg 之前要先把补丁打进源码。昇腾社区的补丁一般基于某个特定版本的 FFmpeg比如 4.x 系列不是所有新版本都适配我在 6.0 上试过一次没有对应的 patch后来还是回到 4.x 才顺利编过。configure 的时候需要显式启用硬件加速选项常见做法是加上 --enable-ascend 或者 --enable-hwaccel具体开关名称取决于你手上的适配包。编译步骤大概是./configure --prefix/usr/local/ffmpeg-ascend \ --enable-ascend \ --enable-ffmpeg \ --enable-avcodec \ --enable-avformat \ --enable-avfilter \ --enable-hwaccel make -j$(nproc) make install注意如果编译时提示找不到 CANN 相关的头文件或库文件就是环境变量没配好。CANN 安装完成后要导出 PATH 和 LD_LIBRARY_PATH确保编译器能定位到 ascend 相关的 include 和 lib 目录。我在脚本里一般会写成这样source /usr/local/Ascend/ascend-toolkit/set_env.sh export LD_LIBRARY_PATH/usr/local/Ascend/ascend-toolkit/latest/lib64:$LD_LIBRARY_PATH编译完成之后用 ffmpeg -hwaccels 看一下如果列表里出现了类似 ascend 的加速器名称说明硬解模块已经成功注册进 FFmpeg。3.3 硬解码流水线核心实现把硬解接到 FFmpeg 应用层核心代码并不复杂。核心思路就是先把硬件设备上下文创建出来再把它绑定到解码器上下文中。伪代码风格的流程可以写成下面这样实际使用时要处理错误返回我就不堆琐碎的判断了。先看 C API 的方式AVBufferRef *hw_device_ctx NULL; // 创建昇腾硬件设备上下文 int ret av_hwdevice_ctx_create(hw_device_ctx, AV_HWDEVICE_TYPE_ASCEND, /dev/davinci0, NULL, 0); if (ret 0) { // 处理创建失败 } AVCodecContext *dec_ctx avcodec_alloc_context3(decoder); dec_ctx-hw_device_ctx av_buffer_ref(hw_device_ctx); // 把硬件上下文挂给解码器 // 后续照常打开解码器、把编码数据喂给 avcodec_send_packet // 解码输出的 AVFrame 就会保存在设备侧。 // 如果一定要拿回 CPU 内存用 av_hwframe_transfer_data() 做显式拷贝。命令行方式则更直观比如ffmpeg -hwaccel ascend -c:v h264 -i input.mp4 \ -vf scale1280:720 -c:v h264 -f mp4 output.mp4在这个命令里-hwaccel ascend 告诉 FFmpeg 使用昇腾硬件加速后面的 filter 和编码逻辑基本不用改。当然这只是最简单的演示实际项目里我更多是直接用 C 接口把硬解集成进视频分析服务因为这样可以更精细地控制帧的流向和内存生命周期。3.4 让 DVPP 顺路把缩放和格式转换做了很多人忽略的一点是解码出来之后千万别急着把帧拷回 CPU。昇腾310B的 DVPP 模块可以在设备侧做缩放、裁剪、抠图、色域转换这些操作用传统 CPU 做也很吃资源但由 DVPP 做几乎是顺路的事。我在抽帧脚本里经常这样优化RTSP 流解码后先用 DVPP 把 4K 缩放到 1280x720再把 NV12 转成 RGB这些都在设备侧完成最后才把处理好的小图传给推理模块。相比早期方案“解码-拷回CPU-缩放-转格式-送推理”这条路少走了两个大步骤实测下来单帧处理延迟明显下降CPU 占用几乎可以忽略。不过要提醒一句DVPP 对分辨率有对齐要求具体对齐值不同版本不一样常见的要求是宽度、高度按 2 的整数倍对齐有些任务还要求 16 或 32 对齐。如果不满足缩放接口会直接报错或者生成花屏图这在后面踩坑小节里我再细说。3.5 多路并发与资源池设计用硬解之后单路解码的 CPU 基本是 0但芯片也有它的并发上限。实际项目里要处理几十路流不能简单地对每路流各开一个解码器然后指望芯片自动排队而是要考虑资源池化。我的做法是初始化的时候建立一个 DMDevice Memory池和会话池每个会话绑定一路流或者一组流解码请求以任务形式提交给芯片。芯片忙不过来的时候任务要在队列里等待这时候如果你每个解码器都无脑往硬件里塞帧就会导致排队延迟无限增长。正确的方式是控制每个解码器的 send 速率或者在一个解码器内部维护一个待解帧队列超过阈值就丢旧帧保新帧优先保证实时性。对于多路高清流把资源池思路理顺了才能把昇腾310B的性能真正榨出来。我见过不少项目设备和驱动都装好了但因为没有做并发控制和缓冲管理多路一起来就 OOM 或卡死最后只能降路数白白浪费了芯片能力。4. 实测到底能快多少瓶颈转移到了哪里4.1 一组参考量级数据不拿具体版本号说事因为驱动、固件、码流内容都会影响结果。我这里给一组我们在常见环境下1080p、H.264、4Mbps 码率实测出来的参考量级目的是让大家对软解和硬解的差距有个直观概念。方案可并发路数参考CPU占用率整机单路延迟参考备注CPU软解24核服务器8~12路80%~100%50~200ms波动大抽帧、缩放后CPU更紧张传统GPU硬解20~40路30%~50%20~80ms显存拷贝占用一定带宽功耗高昇腾310B硬解DVPP30路以上5%~15%10~40ms后面如果接推理可在芯片内完成上面不是精确的 Benchmark只是量级参考。但你应该能感受到昇腾方案最突出的不是单纯的路数有多高而是 CPU 被真正解放出来了系统的稳定性也随之上来。4.2 硬解之后的瓶颈转移解码不是瓶颈之后你的关注点要换一换了。我实际跑起来发现新的瓶颈主要出现在三个地方设备内存Device Memory是否够用、PCIe 或片内总线带宽是否吃紧、以及上层应用消费帧的速度是否跟得上。这里有个很反直觉的点硬解虽然释放了 CPU但同时也把压力转移到了“设备侧内存管理”上。如果视频路数非常多几张卡的 DM 全被解码帧占满新来的帧就会因为拿不到内存而被硬件拒绝表现为 avcodec_send_packet 报错或者解码线程卡死。解决思路是控制每个解码器内的缓冲内帧数或者在软件层面做一个帧率降采样比如从 30fps 只解码 10fps不一定每帧都要送硬件。提示在实时监控场景里千万不要无脑把所有帧都送去硬解。很多流的画面变化很小连续帧之间几乎是重复的。结合场景做抽帧策略比单纯升级硬解设备更省资源。5. 踩坑实录从编译到上线的十个典型问题5.1 环境与编译阶段的问题设备节点找不到。最常见是 /dev/davinci0 不存在。先确认驱动装好没有、板卡是否被系统识别、用户是否在 ascend 用户组里。另外昇腾的容器部署还要注意把设备映射进容器–device 参数要写对否则容器里永远看不到芯片。FFmpeg 编译报找不到头文件。CANN 的环境变量没有 source或者安装路径不对。用 find 查一下 ascend 相关的头文件在哪再把这个 include 路径加进 CFLAGS。hwaccel 名称注册不上。如果你参照网上资料改代码可能遇到 AV_HWDEVICE_TYPE 枚举里根本没有你对应类型的情况。这时候要么确认你的适配补丁是否被正确包含要么检查枚举定义里是否已经添加了昇腾类型。这个坑在源码版本升级后特别容易遇到。5.2 数据处理与格式问题解码输出的是 NV12不是你要的 RGB。昇腾硬解出来的原始帧格式一般是 NV12很多做算法的人拿到手就懵了因为他们模型输入是 RGB。你需要做颜色空间转换。用 DVPP 的转换模块在设备侧直接转成 RGB避免先把 NV12 拷回 CPU 再转否则拷贝开销又会回来。分辨率对齐问题导致花屏。DVPP 对大多数操作都有对齐要求比如缩放后的宽高建议按 2 对齐某些版本要求 16 甚至 32 对齐。如果你的原始画面分辨率是奇数或者不满足对齐条件先做 padding 再缩放。我的经验是写一个统一的预处理函数把所有输入分辨率映射到合法的对齐尺寸然后再做缩放不然你会被间歇性花屏折磨。PTS/DTS 乱序。硬解通常不保证输出帧的 PTS 严格递增特别是 B 帧较多的码流。如果你要用 FFmpeg 滤镜链或者转封装最好在解码后先按 PTS 排队重排再交给后续模块否则会出现视频回放卡顿、抽帧时间戳错乱的问题。5.3 多路并发与稳定性问题长时间运行后内存逐渐上涨。这是比较麻烦的。解码器内部的 AVFrame 如果没有被及时释放或者 DM 池在硬件非逻辑错误下没有归还内存就会出现内存泄漏。建议每路流定期打印已分配的设备内存大小设置监控阈值超过之后强制释放该路会话并重建。我自己接入 30 路流以后就是靠这种“定时体检”机制保证服务长期稳定。RTSP 流断掉之后解码器卡死。网络摄像头经常断流重连如果解码线程一直阻塞在 avcodec_receive_frame 上重连之后就再也回不来了。处理办法是给解码线程设置超时机制超过一定时间没有新帧进来就主动释放解码器、重新初始化再重连 RTSP。send 和 receive 的速度不匹配。FFmpeg 新接口是异步的send 一堆 packet 之后并不代表硬件已经全部解完。如果你不看 receive 的返回值盲目继续 send内部缓冲满了之后 send 会返回 EAGAIN。正确做法是收到 EAGAIN 就先 receive把已解出的帧消费掉再继续 send。我把这些典型问题整理成了一个速查表方便后续排查。问题可能原因排查方向解决方案设备节点不存在驱动未装或未映射进容器npu-smi info重装驱动容器映射 /dev/davinci0编译找不到头文件CANN环境变量未生效echo $LD_LIBRARY_PATHsource set_env.sh输出花屏分辨率未对齐检查输入宽高与DVPP约束先padding再缩放NV12不是想要的格式硬解默认输出NV12检查帧格式用DVPP转RGB避免CPU拷贝PTS乱序B帧重排序检查解码输出PTS序列按PTS排队重排内存持续上涨帧未释放或DM池泄漏监控设备内存定期重建会话并释放DM断流后解码器卡死receive阻塞增加超时监测超时后重建解码器send返回EAGAIN解码缓冲已满检查send循环逻辑先receive再继续send5.4 一个让我印象最深的现场说起来都不太好意思有次上线前压测40 路流并发跑了半小时后整机内存突然暴涨系统差点卡死。一开始以为是解码帧泄漏在 CPU 侧排查了三天没结果。后来把目光转到设备侧发现是 DVPP 缩放任务里有一批格式转换的输出没有绑定到解码器的帧池而是每次单独分配了新的 DMA 内存用完又没及时释放。这个坑在单路调试时根本不会爆多路一并发就暴露了。所以在那以后我给自己定了个规矩所有跨模块的数据分配必须明确所有权和释放时机多路并发项目尤其要按“内存生命周期”的角度去审查代码。6. 进阶把硬解出的帧直接喂给推理和编码6.1 设备侧数据流转的几种玩法硬解只是万里长征第一步真正让昇腾310B发光的是把解码后的帧留在设备侧直接喂给 AI 推理模块或者视频编码模块。玩法一解码 - 缩放 - 推理。这是视频分析最典型的链路。DVPP 解出 4K 帧后在设备侧缩放到模型需要的尺寸转成 RGB送入 AI Core 推理。全程不需要把帧搬回 CPU只有最终的检测结果以元数据形式传出去占用带宽忽略不计。玩法二解码 - 编码 - 转码。比如把 H.265 的输入转成 H.264 的 RTMP 推流格式硬解之后直接硬编码中间不经过 CPU。这样做的好处是转码延迟很低适合直播场景。玩法三解码 - 抽帧 - 存储。对实时流做抽帧归档比如每秒钟存一帧 JPEG。DVPP 可以把 YUV 帧转成 JPEG这部分如果交给 CPU 做会比较吃力设备侧直接生成就轻快很多。6.2 多卡扩展与负载均衡如果你的视频路数特别多一块昇腾310B不够可以考虑多卡方案。多卡部署最大的问题不是性能而是任务调度。架构上可以在应用层做一个 Round Robin 调度器给每张卡维护一个“当前解码路数”的计数器分配流的时候优先发给负载低的卡。每张卡内部再按前述的资源池方案做并发控制这样整体系统的扩展性就出来了。我目前这套系统的路由逻辑很简单每来一路 RTSP 流先看哪张卡的活跃解码会话最少就发给谁。实测下来两块卡的路数分配基本均衡没有出现过一张卡打满、另一张卡空闲的情况。6.3 软解硬解混跑也是一个可用策略最后说一点比较接地气的经验。昇腾310B的硬解不是万能的某些特殊编码格式比如 MJPEG 的某些变体、高帧率屏幕录制流用硬解可能连路数上的优势都体现不出来这时候不要迷信硬解可以针对不同业务流做不同的解码策略。我现在的架构是支持软硬混跑的对常见 H.264/H.265 监控流默认硬解遇到特殊编码或者硬解初始化失败时自动回退 CPU 软解。这样即保证了大多数场景的性能也保住了兼容性的下限。这个回退逻辑写起来并不复杂核心就是解码器初始化失败时不要直接报错而是把硬件设备上下文置空重新用软件的 AVCodec 初始化解码器。上层业务无感但系统的鲁棒性提升了一个档次。最后再分享一个小细节。启动参数里可以给硬解码器设置一个帧缓冲上限比如限制设备侧最多缓冲 8 帧。很多团队上线后遇到画面延迟越来越大的问题就是因为没设这个缓冲上限硬件解码速度远大于消费速度帧在设备侧越积越多延迟就不断攀升。一开始我也没注意这个后来发现 FFmpeg 的 AVCodecContext 里可以通过类似 delay 或内部线程参数来控制缓冲行为稍微调低一点端到端延迟立马下降。这种细节纯看文档是发现不了的得在真实场景里被坑过才记得住。希望这篇内容能帮你少走几步弯路。
返回列表