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=2

EncodeStats:
fps=15.02
bitrate=5.92 Mbps
put_fail=0
out_drop=0

StreamStats:
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

posted @ 2026-07-14 16:26  rambos1996  阅读(17)  评论(0)    收藏  举报