总结:这次卡顿不是单纯的网络丢包或编码帧率不足,而是先由跨 pipeline 视频桥接和缩放增加了处理负担,后又暴露出 H.264 B 帧导致的非单调 PTS;最终通过 appsink/appsrc 直连、限制 videorate 补帧,并让不满足 PTS 条件的素材回退到 MPP 硬件编码,恢复了 WebRTC 的稳定呈现。
一、背景:RK3588 实时流为什么会卡顿
我们在RK3588上开发了一个音视频播放器,该播放器同时做两件事:一条路径把素材解码后送到 HDMI,另一条路径把视频和音频编码为 WebRTC 流,交给浏览器播放。两条路径共享解码输入,但对格式、时间戳、编码和调度的要求不同,因此“HDMI 看起来正常”并不等于“网页实时流正常”。
视频文件 / 采集输入 | v MPP 硬件解码(把压缩视频变成 NV12 原始帧) | +--------------------+ | | v v HDMI / kmssink WebRTC 视频路径 | +-- H.264 编码 -- RTP -- 浏览器 解码音频 -- HDMI/DEP 输出 | +-- PCM -- Opus 编码 -- RTP -- 浏览器本文中的rk3588_gst_player是运行在板端的 GStreamer 播放器进程;MPP 是 Rockchip 的硬件媒体处理框架,负责硬件解码和 H.264 编码。WebRTC 不是“把 HDMI 画面直接发出去”,它还要求稳定的 RTP 时间线、浏览器可处理的编码格式和可预测的呈现时间戳。
(一)需要知道的几个术语
| 术语 | 本文中的含义 |
|---|---|
appsink/appsrc | GStreamer 用于把一个 pipeline 的样本取出,再送入另一个 pipeline 的接口;本文用它替代跨 pipeline 的 inter sink。 |
| RTP | 承载 WebRTC 音视频的实时传输包;一帧 H.264 可能拆成多个 RTP 分片。 |
| PTS / DTS | PTS 是应该显示的时间,DTS 是解码顺序时间;B 帧可能让 PTS 暂时回退而 DTS 仍递增。 |
| jitter buffer | 浏览器为吸收网络和时间戳抖动而设置的缓冲区;时间线异常会让它变大并增加延迟。 |
| B 帧 | 依赖前后参考帧进行显示重排的帧类型,压缩效率较高,但直通 WebRTC 时必须检查 PTS 是否仍然单调。 |
| H.264 直通 | 不重新解码再编码,而是把素材中已压缩的 H.264 帧直接送入 WebRTC;它省掉编码成本,但也把源时间戳问题带进了实时流。 |
ZLMediaKit 是板端媒体服务器,编码后的 RTP 由实时流管理器共享给它,再由它分发给浏览器;因此本文重点分析的是“播放器产生什么样的帧和时间线”,不是公网端口或浏览器页面本身。
本文记录对 RK3588 GStreamer Player 网页实时流卡顿问题的完整排查和优化过程,包括:
- 最初现象和假设;
- 为板端和浏览器补充的诊断指标;
- 每一轮 pipeline 和配置调整;
- H.264 直通引入 B 帧时间戳问题的原因;
- 最终自动混合策略及验证数据;
- 后续更换素材或继续优化时的回归方法。
本文只讨论“物理 HDMI 播放正常或相对流畅,但开启网页实时流后 HDMI 或网页出现卡顿”的问题。项目早期 4K60 双通道播放能力问题不在本文范围内。
(二)优化过程路线
这次排查不是一次性找到 PTS,而是沿着“先确认性能,再确认时间线”的路线逐步收敛:
阶段 1:发现 WebRTC 开启后卡顿 | v 阶段 2:发现跨 pipeline 桥接增加了额外负担 | v 阶段 3:用 appsink/appsrc 替代 intervideosink/intervideosrc | v 阶段 4:尝试 H.264 直通,降低编码压力 | v 阶段 5:发现 B 帧 PTS 重排导致 WebRTC 延迟和呈现卡顿 | v 阶段 6:根据 PTS/DTS 自动判断直通资格,不合格时回退硬件编码后文每一轮优化都回答一个具体问题:瓶颈到底在板端处理量、桥接时间线、网络传输,还是源视频的显示时间戳。
二、最终结论
实时流卡顿不是由单一环节造成,而是先后包含三个问题:
- 固定输出为 1920x1080 时,将约 1280x718 的素材放大,增加了板端缩放、编码、网络和浏览器解码负担。
- 早期
intervideosink/intervideosrc + videorate桥接会重复 surface 或产生 GAP buffer,开启实时流后可能同时影响 HDMI 和网页流的节奏。 - 后期 H.264 直通虽然消除了缩放和重新编码,但含 B 帧的点播素材存在非单调 PTS。该时间戳进入 WebRTC 后显著增大 jitter buffer,并导致 Chrome 丢帧和呈现卡顿。
最终方案由以下部分组成:
- 使用
appsink/appsrc直接桥接解码后的视频和 PCM 音频; videorate设置 200 ms 最大补帧间隔,避免长时间停播后生成大量 GAP 帧;- 支持
fixed和source两种分辨率模式; - 使用共享 RTP,一路通道只处理/编码一次,多个浏览器复用结果;
source模式使用自动混合视频策略;- H.264 先验证首个 GOP,只有 PTS 单调的素材才允许直通;
- 检测到 PTS 回退且 DTS 单调时,将当前素材锁定到 MPP H.264 硬件编码;
- 音频始终由 PCM 编码为 Opus,不执行音频直通。
最终测试中,浏览器丢帧由 57 降为 0,freeze 由 1 降为 0,实际呈现帧率由约 25.75 fps 恢复到约 30.04 fps。
三、初始现象与第一轮判断
最初观察到:
- 只播放 HDMI 时相对流畅;
- 开启实时流后,网页播放比 HDMI 卡;
- 部分测试中,开启实时流后物理 HDMI 也比未开启时卡;
- 板端硬件编码约 29.9 fps,最大输入帧间隔约 33 ms,说明最初不能只把问题归因于 MPP 编码不稳定。
当时使用的网页流接近 1920x1080、30 fps,而素材约为 1280x718、29.97 fps。初步风险包括:
videoscale将非标准 720p 素材放大到 1080p;- 固定 30 fps 与素材原始帧率或 HDMI 显示刷新率不完全一致;
- WebRTC 网络 jitter、浏览器 jitter buffer 和 VSync 合成可能丢帧;
- 浏览器显示的“解码 fps”不等于用户真正看到的“呈现 fps”。
第一轮采用低风险配置,将网页流降到 720p30:
[stream] video_resolution_mode = fixed video_width = 1280 video_height = 720 video_fps = 30 video_bitrate = 2500000该调整降低了缩放、网络传输和浏览器解码压力,但只能缓解问题,不能解释所有偶发卡顿。
videoscale负责改变画面尺寸,VSync是显示器刷新与浏览器合成节奏。它们会影响最终“看到的帧率”,但不能替代板端 RTP 和浏览器呈现指标。
四、先建立可观测性
排查过程中没有直接缩短 RTP 队列。原因是 H.264 一个图像可能拆成多个 RTP 分片,盲目缩短或丢弃队列中的单个 buffer 可能破坏一帧,反而增加花屏和卡顿。
(一)网页 CSV 指标
内置播放器增加了每 2 秒采样的 CSV 日志,主要字段包括:
| 字段 | 含义 |
|---|---|
decoded_fps | Chrome 解码帧率 |
frames_dropped/dropped_delta | 浏览器累计/本窗口丢帧 |
jitter_ms | WebRTC 入站 RTP jitter |
packets_received/packets_lost | 视频 RTP 收包和丢包 |
freeze_count | 浏览器检测到的累计冻结次数 |
presented_fps | requestVideoFrameCallback统计的实际呈现帧率 |
max_present_gap_ms | 当前窗口最大呈现帧间隔 |
long_present_gap_count | 当前窗口超过 50 ms 的呈现间隔次数 |
visibility_state | 页面是visible还是hidden |
decoder_implementation | 浏览器上报的解码器实现,部分 Chrome 版本为空 |
power_efficient_decoder | 浏览器是否报告高能效解码器,部分版本为空 |
jitter_buffer_avg_delay_ms | 浏览器累计 jitter buffer 平均延迟 |
CSV 最多保留约 12 小时,刷新页面后清空。分析时必须排除visibility_state=hidden的窗口,因为后台标签页会暂停或降低视频呈现频率。
(二)板端 5 秒统计
板端为共享实时流 pipeline 增加了每 5 秒汇总:
Encoder cadence:RTP 视频输出 fps 和最大帧间隔;Audio RTP:Opus RTP 包数和字节数;Audio bridge input:PCM 输入数量、字节数和时长;Video mode:h264-passthrough或hardware-encode;Video stage:appsrc_out、videorate_out、videoscale_out、encoder_out四阶段帧率和间隔;Video rate/queue:videorate输入、输出、丢弃、复制以及 queue 深度和溢出次数。
这些数据用于区分:
板端输入或编码不稳 ↓ RTP/network jitter 或丢包 ↓ Chrome 解码不足 ↓ 浏览器合成/VSync 呈现不稳仅看网页底部瞬时 fps 不足以判断问题,因为短窗口、抖动补偿和页面可见性都会使数值波动。
五、替换实时流桥接链路
早期重点检查的链路是:
intervideosrc -> videorate -> videoscale -> mpph264encintervideosink/intervideosrc按独立周期传递 surface,停流、恢复或上下游时钟不一致时可能重复旧 surface,并产生 GAP buffer。开启实时流后,该行为会让videorate继续补帧,增加额外调度和时间线扰动。
这里的 GAP buffer 可以理解为“时间线上有位置、但没有真实视频内容的占位帧”;如果下游继续追赶时间线,短暂停流可能被放大成大量补帧。
最终改为:
HDMI playbin 解码输出 | +-> kmssink(物理 HDMI) | +-> appsink -> appsrc -> WebRTC 共享处理链音频也改为 PCMappsink/appsrc桥接,不再依赖interaudiosink/interaudiosrc。每个 buffer 只转交一次,实时流开关通过常驻分支中的 valve 控制,不需要重建 HDMI 播放 pipeline。
这轮修改后,物理 HDMI 和网页流的主观流畅度都明显改善。
六、限制 videorate 长时间补帧
硬件编码回退链中的videorate使用:
skip-to-first=true max-duplication-time=200000000即最大补帧间隔为 200 ms。正常的 29.97 fps 到 30 fps 调整远小于该值,不受影响;当素材停止很久后恢复时,不会为中断期间生成大量 GAP 帧追赶时间线。
对应实现位于app/WebRtcStreamManager.cpp的StartEncoder()。
“实时流持续开启、素材停止数小时后恢复”仍属于需要单独长时间验证的场景,不能仅凭短时测试认定完全覆盖。
七、fixed/source 两种分辨率模式
为避免要求节目单中的所有素材都使用同一分辨率,增加了两种配置模式。fixed表示先把所有素材转换到统一输出规格;source表示尽量保留素材的源分辨率,再由浏览器按页面尺寸显示。
(一)fixed
video_resolution_mode = fixed video_width = 1280 video_height = 720 video_fps = 30 video_bitrate = 2500000行为:
- 始终经过
videorate ! videoscale ! mpph264enc; - 输出严格遵循配置宽高、帧率和码率;
- 不启用源 H.264 直通;
- 适合必须统一网页流规格的场景。
(二)source
video_resolution_mode = source video_fps = 30 video_bitrate = 2500000行为:
video_width和video_height被忽略;- 两个 HDMI 通道分别按各自素材分辨率工作;
- 浏览器负责把视频按页面尺寸显示;
- 无法直通时仍使用 MPP H.264 编码,但不强制缩放到固定宽高;
- 4K 等高分辨率仍需确认浏览器解码能力和网络带宽。
物理 HDMI 和网页流是不同输出路径。HDMI 可由 DRM/KMS plane 适配显示器模式;网页流是否缩放由[stream].video_resolution_mode决定。
阶段小结:fixed更容易控制输出规格,但会付出缩放成本;source减少不必要的缩放,却要求后续的 H.264 直通资格判断更加严格。
八、第一版自动混合模式
H.264 直通看起来非常有吸引力,因为它可以跳过重新编码,减少 CPU 占用和端到端延迟;但代价是播放器必须承担源 H.264 时间戳质量的风险。为了进一步降低开启实时流后的开销,source模式增加了两个视频分支:
硬件编码分支: NV12 appsrc -> videorate -> videoscale -> mpph264enc -> h264parse --+ | +-> input-selector | -> rtph264pay H.264 直通分支: | -> 共享视频 RTP 源 h264parse probe -> H.264 appsrc -> h264parse ------------------+音频链保持:
PCM appsrc -> audioconvert -> audioresample -> opusenc -> rtpopuspay第一版判断 Baseline、Constrained Baseline、Main 和 High Profile 为浏览器可接受的 H.264,并在关键帧切换到直通。H.265、其他编码和不兼容 Profile 自动使用 MPP 编码。
测试曾确认直通工作:
Video mode: h264-passthrough passthroughInputFrames: 约 150 帧/5秒 appsrc_out: 0 videorate_out: 0 videoscale_out: 0 encoder_out: 0这证明直通期间mpph264enc没有接收视频帧,网页实时流不再额外执行缩放和视频编码。HDMI 播放本身仍需要解码,音频仍需要 Opus 编码。
阶段小结:H.264 直通确实降低了板端处理量,但“能直通”只说明编码格式可接受,还没有证明 RTP 时间戳适合 WebRTC。
转折提示:到这里,问题已经从“性能不足”转变为“时间线不适合实时传输”。前面的桥接优化解决了额外处理负担,但不能保证点播 H.264 的 PTS 适合 WebRTC 直通。
九、直通后仍卡顿:发现 B 帧问题
第一版 H.264 直通后,板端输入和 RTP 输出仍接近 30 fps,但网页 CSV 显示:
- Chrome 解码接近 30 fps;
- 实际呈现显著低于 30 fps;
- RTP 没有网络丢包;
- jitter 和 jitter buffer 延迟明显升高;
- 浏览器出现累计丢帧和 freeze。
问题不在“Chrome 是否支持 High Profile”,而在“该压缩码流是否适合低延迟 WebRTC 直通”。
对测试素材执行:
ffprobe -v error -select_streams v:0 \ -show_entries stream=codec_name,profile,level,width,height,has_b_frames,r_frame_rate,avg_frame_rate,bit_rate \ -of default=noprint_wrappers=1 \ '/mnt/udisk/materials/复仇者联盟4-单音轨.mp4'关键结果:
codec_name=h264 profile=High width=1280 height=718 has_b_frames=1 level=31 avg_frame_rate=30000/1001 bit_rate=1501271再检查压缩包时间戳:
ffprobe -v error -select_streams v:0 -read_intervals '%+#30' \ -show_entries packet=pts_time,dts_time,flags -of csv=p=0 \ '/mnt/udisk/materials/复仇者联盟4-单音轨.mp4'前几帧呈现为:
PTS: 33 -> 100 -> 67 -> 167 -> 133 -> 234 -> 200 ms DTS: 0 -> 33 -> 67 -> 100 -> 133 -> 167 -> 200 msDTS 按解码顺序单调递增,但 PTS 因 B 帧显示重排持续前跳和回退。板端直通统计中的最大正向时间戳间隔因此反复达到 100 ms,而正常 29.97 fps 相邻显示帧间隔应约为 33.4 ms。
Chrome 虽然能够解码这些帧,但 WebRTC jitter buffer 需要吸收非单调显示时间戳,最终表现为缓冲延迟增大、呈现帧率下降和间歇丢帧。更换开源网页播放器不能消除源 RTP 时间戳问题。
这并不意味着 WebRTC 协议禁止 B 帧;这里的结论只针对本案例的低延迟直通链路:当源 PTS 频繁重排时,重新编码成单调时间线比直接复用点播码流更稳妥。
定位结论:这一轮数据把问题从“网络是否丢包”进一步缩小到“源 H.264 的显示时间线是否适合实时直通”。
十、最终自动混合策略
最终策略不再只检查 H.264 Profile,而是加入运行时 PTS 资格判断。
(一)新素材开始
- 默认选择硬件编码分支;
- 记录压缩 H.264 的 PTS 和 DTS;
- 在硬件编码继续输出的同时观察首个完整 GOP;
- 首个 GOP 内 PTS 始终单调时,才在下一个关键帧切换直通。
(二) B 帧判定
同时满足以下条件时判定存在呈现重排:
当前 PTS < 上一个 PTS DTS 没有回退 当前 buffer 没有 DISCONT caps 没有切换这样可把 B 帧重排与 seek、素材切换或时间线重置区分开。
一旦检测到 B 帧重排:
- 当前素材锁定为
hardware-encode; - 后续即使遇到关键帧也不再尝试直通;
- 只有
NotifyVideoSourceReset()收到真实素材切换事件后才清除锁定并重新判断。
板端会打印:
H.264 passthrough disabled for current source: PTS rollback ... with monotonic DTS (B-frame reordering detected) Video mode: hardware-encode (H.264 PTS reordering blocked passthrough)资格判断和状态切换位于app/WebRtcStreamManager.cpp的PushEncodedVideoSample();压缩 H.264 probe 位于app/CGStreamerPlayer.cpp;通道转发位于app/ChannelPlayer.cpp。
十一、最终数据对比
对比日志:
- B 帧 H.264 直通:
rk3588-webrtc-channel-1-20260807133047103.csv - 增加 PTS 回退保护后:
rk3588-webrtc-channel-1-20260807140256820.csv
统计均排除连接初始阶段;最终日志还排除了visibility_state=hidden的三个后台标签页窗口。
| 指标 | B 帧 H.264 直通 | PTS 检测后 MPP 回退 |
|---|---|---|
| Chrome 解码 fps | 29.60 | 30.03 |
| 页面可见时实际呈现 fps | 25.75 | 30.04 |
| jitter | 55.9 ms | 1.4 ms |
| jitter buffer 平均延迟 | 151.9 ms | 12.4 ms |
| 最大呈现帧间隔 | 200.1 ms | 66.7 ms |
| 每 2 秒长呈现间隔次数 | 14.9 | 1.8 |
| 浏览器累计丢帧 | 57 | 0 |
| freeze | 1 | 0 |
| 视频 RTP 丢包 | 0 | 0 |
| 音频 RTP 丢包 | 0 | 0 |
板端最终稳定统计:
Video mode: hardware-encode (H.264 PTS reordering blocked passthrough) Encoder cadence: 约 30 fps maxFrameGapMs: 33 encoder_out: 约 150 帧/5秒 queueOverruns: 0 Audio RTP: 约 250 包/5秒这组数据说明:
- MPP 编码输出时间戳恢复为单调的约 33 ms 间隔;
- jitter buffer 平均延迟降低约 92%;
- 浏览器解码和实际呈现都恢复到约 30 fps;
- 主观改善与板端、RTP 和浏览器三层数据一致。
十二、快照下的推荐配置
素材分辨率不统一、希望避免不必要缩放时:
[stream] max_viewers_per_channel = 4 video_resolution_mode = source video_width = 1280 video_height = 720 video_fps = 30 video_bitrate = 2500000 hdmi1_av_delay_ms = 0 hdmi2_av_delay_ms = 0在source模式下,video_width和video_height不参与网页流处理;保留这两个值是为了以后切回fixed时有明确配置。
如果业务要求所有网页流严格统一为 1280x720、30 fps,则使用:
video_resolution_mode = fixed video_width = 1280 video_height = 720 video_fps = 30 video_bitrate = 2500000十三、回归测试方法
(一)构建和部署
在 Ubuntu 开发机执行:
bash scripts/build.sh bash scripts/upload.sh板端启动:
/root/Test/gst-test/use-gst-test.sh \ /root/Test/gst-test/app/rk3588_gst_player(二)含 B 帧 H.264
预期日志:
H.264 passthrough disabled ... PTS rollback Video mode: hardware-encode (H.264 PTS reordering blocked passthrough) encoder_out: 约目标 fps maxFrameGapMs: 接近单帧间隔不应在后续关键帧重新切回直通。
(三)无 B 帧且 PTS 单调的 H.264
预期日志:
H.264 passthrough qualification started Video mode switched to H.264 passthrough Video stage encoder_out: frames=0需要使用已确认has_b_frames=0的素材完成板端回归。
(四)H.265 或其他编码
预期始终为:
Video mode: hardware-encode encoder_out: 约目标 fps(五)浏览器 CSV
测试时保持页面可见并至少采集 2 分钟。重点检查:
decoded_fps和presented_fps是否接近目标帧率;frames_dropped是否持续增长;freeze_count是否增长;jitter_ms是否长期偏高;jitter_buffer_avg_delay_ms是否持续升高;packets_lost是否增长;max_present_gap_ms是否反复达到 100 ms 以上。
(六)仍需覆盖的场景
- 实时流持续开启、素材停止数小时后恢复;
- H.264 无 B 帧素材完成首 GOP 验证并切换直通;
- H.264/H.265/不同分辨率素材连续切换;
- 两个 HDMI 同时使用不同分辨率和不同编码;
- 多浏览器同时连接同一通道;
- 浏览器开启声音后的长时间音视频连续性;
fixed与source重启切换;- 页面后台再回到前台后的统计恢复。
十四、经验总结
- 编码 fps 稳定不代表网页实际呈现稳定,必须同时采集浏览器呈现数据。
packets_lost=0不代表没有 RTP 时序问题;非单调 PTS 同样会放大 jitter buffer。- 浏览器支持某个 H.264 Profile,不等于该码流适合低延迟 WebRTC 直通。
- 点播 H.264 常使用 B 帧提高压缩效率,而在低延迟 WebRTC 场景中,无 B 帧、单调 PTS 的编码输出通常更容易获得稳定表现。
- RTP 队列不是第一个应该调整的参数。先定位丢帧发生在板端、网络、解码还是呈现阶段。
- 自动直通必须有可回退条件,并且回退结果需要按素材锁定,避免模式反复切换。
- 页面不可见时的
presented_fps=0是浏览器正常节流,分析 CSV 时必须结合visibility_state。 - 优化最终必须由主观观察和板端、RTP、浏览器三层数据共同验证。