WebRTC M80:VP9 多层(SVC/联播)崩溃问题定位与分析
1. 问题背景
现象:在 VP9 编码场景下,开启 FlexFEC 时会触发 WebRTC 内部断言,导致崩溃。具体表现如下:
numSimulcastLayers = 1时正常numSimulcastLayers = 2时崩溃(在pc.setRemoteDescription()后触发编码线程崩溃)典型的崩溃日志:
txt
Fatal error in .../vp9_impl.cc
Check failed: ref_buf_.find(fb_idx) != ref_buf_.end()
2. 核心结论
-
并非"内存只分配了两个 buffer"
ref_buf_是引用缓存映射,崩溃时仅有 key0/1,但编码配置要求引用fb_idx=2,导致断言失败。 -
根本原因在于 VP9 SVC 参考链状态不同步 (M80 旧路径问题),不是 FlexFEC 单点问题。
常见触发条件:多层 + 动态
setParameters(isActive)切层 + 码率/层激活重配置。 -
即使只设置了
numSimulcastLayers,仍可能进入 VP9 SVC关键在于
VideoCodecVP9.numberOfSpatialLayers/numberOfTemporalLayers,只要大于 1 就会设置is_svc_=1。---
3. 层数到底在哪里赋值?
默认值来源:
VideoEncoder::GetDefaultVp9Settings()numberOfTemporalLayers = 1numberOfSpatialLayers = 1
真正覆盖来源(关键):
VideoCodecInitializer::VideoEncoderConfigToVideoCodec(...)- 执行
config.encoder_specific_settings->FillEncoderSpecificSettings(&video_codec);后,video_codec.VP9().numberOfSpatialLayers/numberOfTemporalLayers被写入实际值。
你贴的配置逻辑中(VP9 分支)会按 SSRC/FieldTrial 计算并赋值:
vp9_settings.numberOfSpatialLayers = ...vp9_settings.numberOfTemporalLayers = ...

图片解读:VP9层数赋值调用逻辑
上图展示了从业务配置到VP9编码器初始化过程中,层数参数传递的关键路径:
- 配置入口 :
VideoEncoderConfig中的encoder_specific_settings存储了VP9的专属配置 - 转换过程 :
VideoCodecInitializer::VideoEncoderConfigToVideoCodec()负责将高层配置转换为底层VideoCodec结构 - 关键赋值点 :在
FillEncoderSpecificSettings(&video_codec)中,根据以下逻辑确定最终层数:- 优先使用
SimulcastUtility计算的层数(基于SSRC和FieldTrial配置) - 如果未配置,则回退到默认值(spatial=1, temporal=1)
- 最终写入
video_codec.VP9().numberOfSpatialLayers和video_codec.VP9().numberOfTemporalLayers
- 优先使用
- 编码器生效 :
VP9EncoderImpl::InitEncode()读取这些值,设置is_svc_标志和内部状态
关键发现:
- 即使
numSimulcastLayers=1,如果encoder_specific_settings中设置了多层,仍会进入SVC模式 SetSvcRates()根据当前激活的 spatial layers 更新参考缓冲区,但动态切换时可能出现不同步- 崩溃的根本原因是:参考缓冲区更新(
UpdateReferenceBuffers)与引用索引计算(FillReferenceIndices)在动态重配置时出现时序错位
4. 调用链(函数级)
text
上层(iOS/ObjC) 改 encodings[i].isActive
-> RTCRtpSender::setParameters()
-> 触发发送编码重配置
-> VideoCodecInitializer::VideoEncoderConfigToVideoCodec(...)
-> FillEncoderSpecificSettings(&video_codec)
(VP9层数写入 numberOfSpatialLayers/numberOfTemporalLayers)
-> VP9EncoderImpl::InitEncode(...)
-> VP9EncoderImpl::SetSvcRates(...)
-> VP9EncoderImpl::SetReferences(...)
-> VP9EncoderImpl::FillReferenceIndices(...)
(读取 enc_layer_conf.*_fb_idx,访问 ref_buf_[fb_idx])
-> VP9EncoderImpl::UpdateReferenceBuffers(...)
5. 关键日志解读(你的日志)
VP9 Init: spatial=2 temporal=3 is_svc=1
=> 已进入 VP9 SVC。SetSvcRates: active_spatial=0 -> 2
=> 存在层激活变化。UpdateReferenceBuffers ... update ref_buf idx=0/1
=> 当前只更新到了 0/1。VP9 reference ... fb_idx=2 ref_buf size=2
=> 读取引用槽位 2 时找不到,触发崩溃。
6. 为什么 po 看到 <nil> / '\x03'?
numberOfTemporalLayers/numberOfSpatialLayers 是 unsigned char。
po 用于 ObjC 对象,不适合此类 C++ 基本类型,显示会误导。
建议用:
lldb
p (int)video_codec.VP9().numberOfTemporalLayers
p (int)video_codec.VP9().numberOfSpatialLayers
7. 流程图(问题触发路径)
8. 修复建议
8.1 业务侧(优先,最稳)
- M80 下 VP9 禁止运行时动态切层 (
setParameters(isActive))。 - VP9 使用单层(
spatial=1),或改为重协商切层。 - 需要动态联播时优先 VP8/H264。
- 清理非法 SDP(如错误
red fmtp)。
8.2 编码器侧(止崩,不是根治)
在 FillReferenceIndices() 对 last/alt/golden 引用做安全检查:
- 找不到
fb_idx:跳过引用 +force_key_frame_ = true - 引用异常:跳过引用 +
force_key_frame_ = true - 不再直接依赖
RTC_DCHECK(find != end)
同时在 SetSvcRates() 中当 active spatial 变化时,强制关键帧恢复参考链。
9. 快速验证清单
- VP9 + 单层,不做
isActive切换:是否稳定 - VP8/H264 + 多层 +
isActive切换:是否稳定 - VP9 + 多层 + 改为重协商切层:是否稳定
-
FillReferenceIndices防护后是否不再崩溃
10. 本地排查命令
bash
cd /Users/vrv/Documents/Github/webrtc/webrtc_m80_src/src
git grep -n "numberOfSpatialLayers[[:space:]]*="
git grep -n "numberOfTemporalLayers[[:space:]]*="
git grep -n "FillEncoderSpecificSettings"
git grep -n "SetSvcRates\\|SetReferences\\|FillReferenceIndices\\|UpdateReferenceBuffers" modules/video_coding/codecs/vp9
11. 最终结论
这是 M80 VP9 多层参考链在动态切层场景下的稳定性问题 。
numSimulcastLayers=2 只是触发条件之一,真正决定是否进入该路径的是 VP9 的 numberOfSpatialLayers/numberOfTemporalLayers 被赋值为多层后,SVC 引用链在重配置阶段出现不同步。
关键补充:
- 上层接口在同时配置 VP9 的联播(simulcast)多个 encoder 后,WebRTC 内部会默认开启 SVC L3T2(3层空间+2层时间)支持
- 开启 FlexFEC 时,这种复杂组合(simulcast + SVC + FlexFEC + RTX)会导致内部状态管理混乱
- 根本解决方案:在 M80 版本中,需要主动关闭 SVC 支持来避免此问题
- 现状限制:当前 M80 版本不支持 simulcast + SVC + FlexFEC + RTX 这种复杂组合,建议业务侧简化配置或升级到更高版本
优化建议:
- 业务层明确关闭 VP9 SVC:设置
numberOfSpatialLayers = 1且numberOfTemporalLayers = 1 - 避免同时启用 simulcast、SVC、FlexFEC、RTX 四种特性
- 如必须使用多层编码,考虑降级到 VP8/H264 或升级 WebRTC 版本
- 使用fieldTrials的配置
WebRTC-SupportVP9SVC/EnabledByFlag_1SL2TL(1个空间层 2 个时间层)或者EnabledByFlag_1SL1TL,关闭 SVC 的多个空间层;