RK3588 4K 视频拉流、推理与重新推流的性能优化实践
4K 视频处理不是把视频“拉进来再推出来”这么简单。它更像把一辆 42.8 Mbps 的满载货车送进板端:先解码、再推理、叠加检测框、重新编码、推送 RTMP。任何一个环节慢一点,后面的帧就开始排队;队列一长,播放器看到的不是“系统在努力”,而是卡顿和延迟。
本次测试链路如下:
4K H.264 视频源→ FFmpeg 发布 → SRS
→ RK3588 PullStreamThread→ RKNN 推理→ EncodeThread 画框/文字→ MPP H.264 编码→ StreamThread
→ SRS → 播放器
输入源为 3840×2160、约 30.03 FPS、平均约 42.8 Mbps。第一次复现时,输出却只有约 15.17 FPS、3.11 Mbps。此时如果只看 NPU,日志并不算差:NPU 推理约 25.8~29.2 ms/帧,完整推理链约 36.5~49.1 ms/帧。问题显然不只是“NPU 跑不动”,而是整条流水线的节奏没有协调好。
初始输出配置中,4K 流被设为 15 FPS、3000 kbps。简化后的配置可以理解为:
{
"source": {
"type": "pull_stream",
"url": "rtmp://192.168.0.168/live/testsrc2",
"width": 3840,
"height": 2160,
"fps": 15
},
"stream": {
"bitrate_kbps": 3000,
"draw_detection_labels": true
}
}
这里有两个问题。第一,输入实际接近 30 FPS,而目标配置是 15 FPS;第二,3 Mbps 对“4K 解码、处理、再次编码”的输出而言过低。它不是一定不能看,但很难保住文字、桌面纹理和运动边缘。
早期日志的关键数据如下:
Input: 3840x2160 H.264, 30.03 FPS, 42.8 Mbps
NPU: 25.8~29.2 ms/frame
Infer: 36.5~49.1 ms/frame
Output: 3840x2160 H.264, 15.17 FPS, 3.11 Mbps
帧率问题首先通过显式抽帧解决。对于输入 30 FPS、目标 15 FPS 的场景,不能只靠线程休眠“碰运气”,而是应明确计算抽帧步长:
// 简化示意:30 FPS 输入,15 FPS 输出
frame_step = source_fps / target_fps; // 2
if ((frame_index % frame_step) != 0) {
++dropped_by_pacing;
return;
}
push_to_encoder(frame);
修正后,拉流、编码和推流端的节奏统一,日志能够清楚验证结果:
source_fps=30
target_fps=15
frame_step=2EncodeStats:
fps=15.02
bitrate=5.92 Mbps
put_fail=0
out_drop=0StreamStats:
fps=15.02
bitrate=5.98 Mbps
write_fail=0
queue_depth=0
这一步解决的是“目标为 15 FPS 时,为什么输出节奏不稳定”的问题。但后续需求是尽可能高地输出,因此最终 30 FPS 方案不再在编码前执行 30→15 FPS 节流,而是让完整源帧进入编码链路。
真正影响实时体验的另一个关键点是队列。阻塞式队列在离线任务里很正常,但在实时视频里很容易变成“历史画面仓库”:
推流变慢
→ 编码输出积压
→ 编码线程阻塞
→ 上游继续等待
→ 延迟不断增加
→ 播放器越来越卡
因此,编码输入和推流输出改为非阻塞传递;推理仍采用最新帧优先策略。其思想可以概括为:
// 简化示意:实时模式下不等待旧帧
if (!encoder_queue.try_push(frame)) {
++encoder_drop;
}
if (!stream_queue.try_push(packet)) {
++stream_drop;
}
这不是为了“多丢帧”,而是为了防止过期帧排队。对实时画面而言,当前帧比五秒前的旧帧更有价值。最终 30 FPS 测试中,队列深度和写失败均为 0,说明没有形成持续反压。
画质问题则通过码率预设进行对比。测试配置提供 6、12、18 Mbps 三档:
{
"stream": {
"bitrate_kbps": 12000,
"draw_detection_labels": true
}
}
{
"stream": {
"bitrate_kbps": 18000,
"draw_detection_labels": true
}
}
测试不是拿两张随机截图比一比,而是使用同一源视频,分别抓取 12 Mbps 和 18 Mbps 输出,先做帧对齐,再计算 PSNR、SSIM,并保存原始帧、输出帧、并排图和 FLV 文件。最终数据如下:
指标 12 Mbps 18 Mbps
实测码率 12007.9 kbps 18087.0 kbps
输出帧率 29.99 FPS 30.08 FPS
PSNR 28.4200 dB 28.4099 dB
SSIM 0.5735 0.5813
写失败 0 0
队列深度 0 0
对应的运行统计可以概括为:
[12 Mbps]
output_fps=29.99
bitrate=12007.9 kbps
write_fail=0
queue_depth=0
[18 Mbps]
output_fps=30.08
bitrate=18087.0 kbps
write_fail=0
queue_depth=0
结果说明,12 Mbps 和 18 Mbps 都可以稳定完成 4K 30 FPS 的处理和推流。18 Mbps 在单帧对齐样本中的 SSIM 略有优势,但 PSNR 基本持平;因此 12 Mbps 是更节省带宽的默认选项,18 Mbps 则适合带宽充足、希望保留更多局部细节的场景。
另外,4K 画面还有一个很现实的问题:检测框文字太小。原本适用于低分辨率的字体,在 3840×2160 显示器上会缩成“蚂蚁字幕”。因此绘制层加入分辨率相关缩放:
// 简化示意
const int label_scale =
(frame_width >= 3840 || frame_height >= 2160) ? 2 : 1;
glyph_width = 8 * label_scale;
glyph_height = 16 * label_scale;
最终的结论并不复杂:4K 卡顿不是单一性能指标的问题,而是帧率策略、队列反压和输出码率共同决定的。现在系统能够稳定输出 4K 30 FPS,支持 6、12、18 Mbps 配置;下一步若希望进一步逼近源画质,应测试 24、30、42 Mbps 等档位,并基于多帧 PSNR、SSIM 或 VMAF 建立码率收益曲线。这样,视频链路优化就不再是“看着差不多就调高一点码率”,而是有日志、有数据、有证据的工程决策。
github:https://github.com/johnjiamzhong-project/AlertGateway

浙公网安备 33010602011771号