找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1954

积分

0

好友

257

主题
发表于 前天 23:43 | 查看: 5| 回复: 0

最近在开发一套视频压测与对比分析工具。其核心目标是:针对同一源视频,利用FFmpeg进行多次重新编码,输出不同分辨率、码率、GOP结构和编码参数的版本,并通过RTSP推流,以模拟真实业务场景下的解码与播放环境。

这套工具可用于:

  1. 客户端或服务器同时拉取多路不同质量的视频流,进行质量分析。
  2. 验证播放器、解码器在不同分辨率下的表现。
  3. 测试弱网、丢包等条件下的恢复能力。
  4. 检查系统对多路流的处理能力(稳定性、并发性)。
  5. 比较编码参数对画质和延迟的影响。
  6. 进行功能回归测试或性能瓶颈排查。

推流使用的核心命令如下:

ffmpeg -re -i 1080p_30fps.mp4 -vf scale=640:640 -c:v libx265 -preset slow -tune zerolatency -x265-params "repeat-headers=1:keyint=60:min-keyint=60:scenecut=0:idr-period=60:no-open-gop=1" -b:v 4096k -bufsize 2M -f rtsp -rtsp_transport udp rtsp://admin:admin@192.168.xx.xxx:8554/video

但在验证过程中,同事发现了一个问题:FFmpeg 推流的 H.265 码流似乎不携带 SPS、PPS、VPS 等参数集,这导致他几乎要放弃相关的 H.265 推流脚本。

这显然是不可接受的,于是我开始着手排查问题所在。

首先,我咨询了 AI 助手:“FFMPEG 推流出来 X265 编码 I 帧之前没有 SPS 数据,是否有相关配置?”

AI 给出了回复:libx265 默认只在关键帧(IDR)前加入 VPS/SPS/PPS,而不会在每个 I 帧前都添加。这可能是由于封装格式或“每个关键帧重复头部”功能未启用。

使用x265编码器repeat-headers参数的方案说明

要让 FFmpeg 的 libx265 在 I/IDR 帧前输出 SPS/PPS,必须开启 -x265-params repeat-headers=1。如果仍然不输出,可以再加 open-gop=0

这与我们命令中已有的参数基本一致。于是,我将完整的 FFmpeg 命令行发给 AI 进行确认:

ffmpeg -re -i 1080p_30fps.mp4 -vf scale=640:640 -c:v libx265 -preset slow -tune zerolatency -x265-params "repeat-headers=1:keyint=60:min-keyint=60:scenecut=0:idr-period=60:no-open-gop=1" -b:v 4096k -bufsize 2M -f rtsp -rtsp_transport udp rtsp://admin:admin@192.168.13.176:8554/video

AI分析命令行并确认参数可输出SPS/PPS/VPS

AI 确认了命令中的参数设置足以让每 60 帧(一个 IDR 周期)输出一次 VPS/SPS/PPS。同时,它还提供了一个验证方法。

验证H.265输出是否包含VPS/SPS/PPS的两种方法

我按照方法一,将转码后的视频输出到本地文件进行验证:

将视频转码为H.265文件以进行验证的FFmpeg命令

通过专业工具解析生成的 out.h265 文件,可以清晰地看到,在 IDR 帧(切片 #60)之前,确实有独立的 VPS、SPS、PPS NAL 单元。

H.265文件解析结果,显示VPS/SPS/PPS独立存在

至此,问题原因被定位:编码器本身已经正确生成了参数集,但问题出在推流封装和发送环节

于是,我使用 Wireshark 抓取推流时的网络包进行分析,发现了关键线索。

Wireshark抓包显示VPS/SPS/PPS被整合在AP包中

从抓包结果看,VPS、SPS、PPS 等小尺寸的 NAL 单元被合并到了一个 RTP 包中发送。这种包在 H.265 over RTP 标准中被称为 Aggregation Packet (AP)。而我们的解码端目前并不支持解析这种 AP 包。

我向 AI 提出了更具体的问题:“FFMPEG 推流 H265,禁用 APS,单独发送 VPS,SPS,PPS”。AI 的建议包括:

  1. 确保参数集以 Annex-B 格式作为独立 NAL 单元插入。
  2. 尽量避免或禁用 AP 的产生。
  3. 在推流时使用 hevc_mp4toannexb 比特流过滤器,并让封装器单独打包发送参数集。

其中,使用比特流过滤器的命令示例如下:

ffmpeg -re -i input.mp4 \
 -c:v libx265 -x265-params "repeat-headers=1:no-open-gop=1" \
 -bsf:v hevc_mp4toannexb \
 -f rtsp -rtsp_transport udp rtsp://user:pass@host:8554/stream

但经过实际交叉验证,添加 -bsf:v hevc_mp4toannexb 并未生效,抓包显示仍然是 AP 包。

因此,只能深入 FFmpeg 源码寻找根本原因。通过查阅 libavformat/rtpenc_h264_hevc.c 文件中的相关代码,我找到了问题的关键。

FFmpeg源码中决定是否使用聚合包的关键代码段

代码逻辑显示,对于 H.265,默认会尝试将小 NAL 单元聚合打包(skip_aggregate 变量默认取决于 flag,通常为 0,即启用聚合)。FFmpeg 的 rtsp 封装输出并未提供直接的 命令行 参数来禁用这一行为。

既然找到了症结,解决方案就明确了:修改源码,将默认的聚合行为关闭。核心修改点就是将 skip_aggregate 的默认值或判断条件改为 1(跳过聚合),然后重新编译 FFmpeg。

附录:关于 Aggregation Packet (AP)

1. 什么是 Aggregation Packet?
AP 在 RFC 7798 (H.265 over RTP) 中定义,旨在减少小型 NAL 单元(如 VPS、SPS、PPS 等非 VCL 单元,通常只有几个字节)的封装开销。

RFC文档中对聚合包(AP)的定义

其核心思想是:在一个接入单元内,将多个 NAL 单元聚合到一个 RTP 包中发送。每个被聚合的 NAL 单元都封装在一个“聚合单元”内,并按照解码顺序排列。

2. AP 的数据结构
一个 AP 包的基本结构如下:

包含两个聚合单元的AP包结构示例

它由 RTP 头、HEVC 负载头(类型为 48,表示 AP),以及后续一个或多个 NAL单元长度 + NAL单元数据 对组成。

第一个聚合单元的结构可能包含额外的 DONL 字段:

第一个聚合单元的内部结构

后续的聚合单元结构则有所不同:

非首个聚合单元的内部结构

3. AP 的主要应用场景

场景 是否推荐使用 AP 原因
发送 VPS/SPS/PPS ✔ 强烈推荐 防止单个小包丢失导致解码器缺少关键参数集
发送 SEI + IDR ✔ 可能 SEI 通常很小,与 IDR 帧一同发送可保持同步
发送非常小的 NAL 单元 有效降低 RTP 头的相对开销
发送大的 I/P/B 帧 NAL (> MTU) ✘ 不用 大 NAL 必须使用分片单元 (Fragmentation Unit, FU) 传输

总结来说,遇到 FFmpeg 推流 H.265 不携带独立参数集的问题时,首先应确认编码参数(如 repeat-headers=1)已正确设置。若问题依旧,则很可能是由于 FFmpeg 的 RTP 封装器默认启用了 AP 聚合功能。此时,最彻底的解决方案就是修改其源码并重新编译,强制其以独立的 NAL 单元形式发送参数集。希望这篇在 云栈社区 分享的排查心得能帮助到遇到类似问题的开发者。




上一篇:搜索引擎高级技巧:6个实用操作符,让你在Google/Bing中高效信息检索
下一篇:Dubbo 3.x微服务实战:Java RPC框架核心架构与高可用部署
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-11 20:14 , Processed in 0.283829 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表