本文记录一次 RK3588 WebRTC 实时流启动后播放器崩溃的完整定位过程:问题最终并非来自 MPP 编解码,而是 Rockchip 修改版 GStreamer 视频转换库中的 RGA 加速路径。

最终根因:Rockchip 修改版 libgstvideo-1.0.so 在 GST_VIDEO_CONVERT_USE_RGA=1 时使用 RGA 1.10.1 执行视频格式转换。 对于可见分辨率为 1280x718、硬件 stride 高度为 720 的解码缓冲, RGA 的 NV12 到 I420 转换失败并破坏进程内存,最终触发 Segmentation fault。

一、问题背景

我用MYZR-RK3588-EK360(官网为:MYZR-RK3588-EK360 — 明远智睿的文档 文档)生成WebRTC网页实时流,效果如下:

网页实时流通过GStreamer的webrtcbin插件实现。webrtcbin负责:RTP封装、SRTP加密、DTLS握手、ICE穿透、NAT处理、SDP offer/answer、数据通道.....这样这些功能就不用用户自己实现了。webrtcbin的官方文档为:webrtcbin

RK3588 实时流系统同时承担物理 HDMI 显示和浏览器 WebRTC 分发。视频文件或采集输入先由 MPP 硬件解码为 NV12,播放器再把同一帧送往 HDMI 和网页流两条不同路径。收到 streamControl(播放器的启流控制消息)后,板端播放器会为指定通道启动网页实时流。

{
  "messageType": "streamControl",
  "payload": "{\"channelIndex\":1}"
}

本文中的 rk3588_gst_player 是运行在 RK3588 上的 GStreamer 播放器进程;streamControl 是请求它启动实时流的控制消息。

整体架构如下:

硬件解码后的原始视频
        |
        v
      MPP 解码
        |
        v
       NV12
        |
        +------------------+
        |                  |
        v                  v
   kmssink              WebRTC 编码
   HDMI 输出                |
                           v
                     H.264 Encoder
                           |
                           v
                       浏览器播放

解码后的原始音频
        |
        +-- tee --> alsasink(物理 HDMI)
        |
        +-- interaudiosink --> opusenc --> WebRTC

HDMI 显示只需要 NV12;网页流还需要把视频送入编码器并通过网络发送,因此两条路径的格式和内存要求并不相同。音频还可以沿独立的 DEP(Dante 音频输出服务)路径输出。出现问题的素材为:

文件复仇者联盟4-单音轨.mp4
视频编码H.264
可见分辨率1280x718
硬件解码 stride1280x720
帧率约 29.97 fps

二、RGA 是什么

RGA(Raster Graphic Accelerator)是 Rockchip 提供的 2D 硬件加速模块,擅长执行格式转换、图像缩放、色彩转换和图像搬运。本案例中的 NV12 → I420 本来属于 RGA 的典型使用场景。

问题在于,解码帧的可见高度是 718,底层缓冲的 stride 高度是 720。这种“可见尺寸”和“硬件对齐尺寸”不同的组合,触发了 RGA 1.10.1 的异常处理路径;错误最终表现为内存破坏,而不只是一次转换失败。

三、故障现象

HDMI 已正常显示画面时发送 streamControl,接口先返回成功:

[WebRTC-1] Shared H.264/Opus encoder started
[ChannelPlayer-1] ReloadPlaylist called
 Response sent: {"code":200,"data":"{...streamControlReply...}","msg":"Success."}

播放器随后重建 HDMI pipeline,并在视频格式转换阶段输出:

Video sink: capsfilter caps=video/x-raw,format=NV12
  ! identity drop-allocation=true
  ! tee name=stream_video_tee
  ...
  ! videoconvert
  ! video/x-raw,format=I420
  ! intervideosink ...

rga_api version 1.10.1_[10]
RGA_BLIT fail: Invalid argument
rect[0, 0, 1280, 718, 1280, 720, 2560, 0]
rect[0, 0, 1280, 718, 1280, 718, 2816, 0]
Segmentation fault

这不是普通的 pipeline 协商失败。RGA 在失败路径中破坏了内存,除了播放器段错误外, 还可能导致控制台、SSH 或其他板端命令异常,只能重启板子恢复。

四、为什么 HDMI 正常,网页流崩溃

不开启网页流时,路径很短:

解码 → NV12 → kmssink → HDMI

开启网页流后,新增了格式转换和编码路径:

解码 → NV12 → videoconvert → I420 → MPP H.264 编码 → WebRTC → 浏览器

因此“HDMI 正常”只能证明解码和显示路径正常,不能证明新增的 WebRTC 转换路径也正常。这个案例的崩溃发生在 videoconvert 的 NV12 到 I420 转换阶段,而不是 MPP 解码阶段。

  1. 未开启网页流时,解码后的 NV12 主要送入 kmssink,素材可以正常显示。
  2. streamControl 启动共享 H.264/Opus 编码 pipeline。
  3. ChannelPlayer::ReloadPlaylist() 中断旧 pipeline,并按当前播放位置重新开始。
  4. 新 pipeline 增加网页流分支,需要将 NV12 转换成 I420 后送入 intervideosink。
  5. Rockchip 修改版 libgstvideo 尝试用 RGA 加速该转换。
  6. 源画面可见高度为 718,但底层硬件缓冲按 720 行对齐。
  7. RGA 1.10.1 对这组参数返回 Invalid argument,随后发生内存破坏和段错误。

五、如何从日志定位 RGA 问题

看到 RGA_BLIT fail、Invalid argument 和 Segmentation fault 时,不要第一时间下结论说“MPP 坏了”。更可靠的顺序是:

  1. 确认是谁调用了 RGA:MPP 插件、videoconvert,还是其他库。
  2. 确认哪个 pipeline 分支触发:HDMI、WebRTC 视频,还是音频。
  3. 核对输入输出 buffer 的格式、可见尺寸和 stride,寻找异常组合。

(一)从崩溃日志锁定 RGA 和特殊高度

首先从日志提取错误、pipeline 和尺寸信息:

rg -n "RGA|Segmentation|ERROR|Failed|streamControl|WebRTC|Video sink" player.log

关键证据不是单独的 Segmentation fault,而是它前面的两组矩形参数:

源:1280x718,stride 1280x720,格式 2560(NV12)
目标:1280x718,stride 1280x718,格式 2816(I420)

这说明崩溃与网页流新增的 NV12 到 I420 转换直接相关,而不是 HTTP 接口解析、节目单调度或 DEP 音频输出。

(二)第一阶段判断:怀疑 MPP 插件内部 RGA

仓库中的 Rockchip MPP 插件源码存在 RGA 转换函数:

rg -n "gst_mpp_use_rga|gst_mpp_dec_rga_convert|gst_mpp_enc_convert|RgaBlit" \
  third_party/sources/gstreamer1-rockchip-master

对应源码包括:

  • gst/rockchipmpp/gstmpp.c
  • gst/rockchipmpp/gstmppdec.c
  • gst/rockchipmpp/gstmppenc.c

因此第一阶段采取了以下措施:

  • 在解码器和 tee 之间固定 NV12 caps。
  • 增加 identity drop-allocation=true,阻止下游分支的 allocation query 影响解码器。
  • 设置 GST_MPP_NO_RGA=1。
  • 使用 -Drga=disabled 重新交叉编译 libgstrockchipmpp.so。

(三)核对板端实际加载的插件

为了排除“改了源码但板子仍加载旧插件”,使用启动脚本运行 gst-inspect:

/root/Test/gst-test/use-gst-test.sh gst-inspect-1.0 mppvideodec
/root/Test/gst-test/use-gst-test.sh gst-inspect-1.0 mpph264enc

确认实际路径为:

/root/Test/gst-test/lib/gstreamer-1.0/libgstrockchipmpp.so

随后通过 SHA-256、ELF 依赖和字符串确认:

sha256sum /root/Test/gst-test/lib/gstreamer-1.0/libgstrockchipmpp.so
readelf -d libgstrockchipmpp.so
strings libgstrockchipmpp.so | grep "RGA disabled"

结果表明新 MPP 插件已部署,插件自身不再直接依赖 RGA,并包含:

RGA disabled at compile time

(四)关键转折:禁用 MPP RGA 后仍出现完全相同的 RgaBlit

重新测试后,日志仍在 videoconvert ! I420 处出现相同 RGA 错误。这证明:

MPP 插件确实包含一条 RGA 路径,但它不是本次最终仍在执行的 RGA 调用者。 仅设置 GST_MPP_NO_RGA 无法关闭 GStreamer 视频转换库里的另一条 RGA 路径。

阶段小结:到这里可以确认,问题已经从“MPP RGA”缩小到“GStreamer Base 视频转换 RGA”;下一步应扫描动态符号和依赖,寻找真正调用 RgaBlit 的库。

(五)扫描全部插件的动态符号和依赖

使用交叉工具链扫描目标运行时中的插件:

for f in third_party/rk3588-sdk/target/usr/lib/gstreamer-1.0/*.so; do
  aarch64-none-linux-gnu-nm -D "$f" | rg "RgaBlit|c_RkRgaBlit"
done

同时检查动态依赖:

aarch64-none-linux-gnu-readelf -d libgstvideoconvertscale.so
aarch64-none-linux-gnu-readelf -d libgstvideo-1.0.so.0.2209.0

发现 videoconvert 元素本身位于 libgstvideoconvertscale.so,它通过 libgstvideo-1.0.so 完成真正的像素转换;后者直接依赖 librga.so.2,并含有 c_RkRgaBlit 未定义符号。

(六)从二进制证据回到源码

继续在 GStreamer Base 源码中搜索:

rg -n "RgaBlit|GST_VIDEO_CONVERT_USE_RGA" \
  third_party/sources/gst1-plugins-base-1.22.9

最终定位到:

third_party/sources/gst1-plugins-base-1.22.9/
  gst-libs/gst/video/video-converter.c

关键逻辑为:

buf = g_getenv ("GST_VIDEO_CONVERT_USE_RGA");
if (!buf || strcmp (buf, "1"))
  return FALSE;

...

if (c_RkRgaBlit (&src_info, &dst_info, NULL) < 0)
  return FALSE;

这段源码说明:只要 GST_VIDEO_CONVERT_USE_RGA 的值精确等于 1, videoconvert/videoscale 就会优先走 Rockchip RGA;否则回退到标准 GStreamer CPU 转换。

六、第一次修复为什么失败

第一次排查根据 MPP 插件源码中的 RGA 函数,设置了 GST_MPP_NO_RGA=1,并用 -Drga=disabled 重新编译 libgstrockchipmpp.so。这个方向并非完全错误:它确实关闭了 MPP 插件内部的一条 RGA 路径。

最初的假设:
mppvideodec / mpph264enc
        |
        v
      RGA

实际调用链:
videoconvert → libgstvideo → librga.so.2 → RgaBlit()

重新部署后仍出现完全相同的 RgaBlit 错误,说明 MPP 没有调用这一次 RGA。真正的调用者是 Rockchip 修改版 libgstvideo-1.0.so:它在 GST_VIDEO_CONVERT_USE_RGA=1 时为 videoconvert 启用 RGA 加速。这是本案例最关键的误区:关闭 MPP RGA,不等于关闭 GStreamer Base 视频转换库的 RGA。

七、根本原因

根本原因由四个条件共同构成:

条件作用
特殊视频高度素材可见高度为 718,硬件解码缓冲按 720 行对齐。
网页流分支开启网页流后需要执行 NV12 到 I420 的格式转换。
Rockchip GStreamer 修改libgstvideo 增加了 RGA 加速路径。
RGA 1.10.1 缺陷无法安全处理该可见高度与 stride 组合,失败后产生内存破坏。

streamControl
        |
        v
重新创建 WebRTC pipeline
        |
        v
videoconvert
        |
        v
libgstvideo
        |
        v
GST_VIDEO_CONVERT_USE_RGA=1
        |
        v
librga.so.2 → RgaBlit()
        |
        v
Invalid argument → 内存破坏
        |
        v
Segmentation fault

这条链路把“网页流一启动就崩溃”与底层错误连接起来:控制消息只是触发器,真正的故障点是新增 WebRTC 视频分支中的格式转换。

八、修复方案

修复原则不是关闭 RK3588 的全部硬件能力,而是只关闭已证明不稳定的“视频格式转换 RGA 路径”。修复后仍保留 MPP 硬件解码、MPP 硬件编码和 HDMI 输出:

MPP Decoder
      |
      v
     NV12
      |\
      | +---- HDMI / kmssink
      |
      +---- CPU videoconvert
                    |
                    v
                  I420
                    |
                    v
              MPP H.264 Encoder

(一)应用层强制关闭 libgstvideo RGA

在 app/main.cpp 中,必须在 gst_init() 之前执行:

g_setenv("GST_VIDEO_CONVERT_USE_RGA", "0", TRUE);
gst_init(&argc, &argv);

应用启动时打印:

[GStreamer] CPU video conversion forced; libgstvideo RGA disabled

在应用内覆盖环境变量,可以防止用户直接运行二进制或外部 shell 环境意外设置为 1。

(二)启动脚本双重保护

scripts/use-gst-test.sh 和打包脚本生成的启动脚本均设置:

export GST_MPP_NO_RGA=1
export GST_VIDEO_CONVERT_USE_RGA=0
  • GST_MPP_NO_RGA=1:关闭 Rockchip MPP 插件内部的 RGA 转换。
  • GST_VIDEO_CONVERT_USE_RGA=0:关闭 libgstvideo 中的 RGA 转换。

(三)MPP 插件在编译期禁用 RGA

新增 scripts/build-rockchip-gstreamer-plugin.sh,使用:

-Drockchipmpp=enabled -Drga=disabled

脚本在安装插件前检查 ELF;如果插件仍直接依赖 librga,构建立即失败。

(四)保持解码器侧 NV12 隔离

网页流启用时,解码器到 tee 的入口固定为:

capsfilter caps=video/x-raw,format=NV12
  ! identity drop-allocation=true
  ! tee name=stream_video_tee

该处理避免 WebRTC 分支反向影响硬件解码器的输出格式和 allocation 协商。

(五)原子部署

scripts/upload.sh 先将播放器、插件和启动脚本上传为 /tmp/*.new, 全部成功后再用 mv 替换正式文件,并删除 GStreamer registry:

rm -f /root/Test/gst-test/cache/registry.bin

这样可以避免网络中断时留下半写入的可执行文件或共享库。

九、性能与功能影响

模块修复后行为影响
视频解码继续使用 mppvideodec 硬件解码。硬件解码能力保留。
物理 HDMI继续使用 NV12 和 kmssink。输出格式和画面质量不变。
网页流编码继续使用 mpph264enc 硬件编码。H.264 编码仍由硬件完成。
视频格式转换/缩放使用 GStreamer CPU 路径,不再调用 RGA。会增加一定 CPU 使用率,应在双路 1080p 网页流压力测试中持续观察。
音频HDMI ALSA、DEP 和 WebRTC Opus 路径不受此次修复直接影响。功能保持不变。

此修复不是将整个播放器改成软件解码或软件编码。只有原始视频的颜色格式转换和缩放改为 CPU 路径。

当前真实验证记录没有给出统一的 CPU 百分比,因此本文不虚构“1080p 增加 xx%”之类的数字。对 1080p 实时流,建议记录单路和双路 WebRTC 的 CPU 峰值与平均值;4K 场景则应单独进行压力测试,确认 CPU 转换是否满足帧率和温度要求。

十、构建与验证

(一)交叉编译

cd /home/cuijc/xiangmu/RK3588-GStreamer-Player
bash scripts/build.sh

scripts/build.sh 先构建无 RGA 的 Rockchip MPP 插件,再交叉编译播放器。

(二)部署

bash scripts/upload.sh

(三)核对文件哈希

开发机:

sha256sum build-rk3588/rk3588_gst_player \
  third_party/rk3588-sdk/target/usr/lib/gstreamer-1.0/libgstrockchipmpp.so \
  scripts/use-gst-test.sh

板端:

sha256sum /root/Test/gst-test/app/rk3588_gst_player \
  /root/Test/gst-test/lib/gstreamer-1.0/libgstrockchipmpp.so \
  /root/Test/gst-test/use-gst-test.sh

最终部署时应确认三项本地与板端哈希完全一致;本文记录的具体 SHA-256 放在附录 C,正文只保留核对方法。

(四)验证运行环境

/root/Test/gst-test/use-gst-test.sh env | \
  grep -E "GST_(VIDEO_CONVERT_USE_RGA|MPP_NO_RGA)="

预期:

GST_MPP_NO_RGA=1
GST_VIDEO_CONVERT_USE_RGA=0

(五)验证 MPP 插件

/root/Test/gst-test/use-gst-test.sh gst-inspect-1.0 mppvideodec
strings /root/Test/gst-test/lib/gstreamer-1.0/libgstrockchipmpp.so | \
  grep "RGA disabled at compile time"

gst-inspect 中的插件路径应为:

/root/Test/gst-test/lib/gstreamer-1.0/libgstrockchipmpp.so

(六)最终运行验证

用户按原流程启动播放器,在 HDMI 已经显示目标素材后发送 streamControl。验证结果:

网页实时流能够成功生成;物理 HDMI 继续播放;日志中不再出现 RgaBlit、RGA_BLIT fail 或 Segmentation fault; rk3588_gst_player 不再崩溃。

十一、建议复测方法

  1. 重启板子,确保清除上一次 RGA 内存破坏造成的系统异常。
  2. 使用标准脚本启动播放器:
    /root/Test/gst-test/use-gst-test.sh \
      /root/Test/gst-test/app/rk3588_gst_player
  3. 确认启动日志出现:
    [GStreamer] CPU video conversion forced; libgstvideo RGA disabled
  4. 等待 HDMI 显示目标素材。
  5. 发送 streamControl。
  6. 打开接口返回的 streamUrl,确认网页有视频和音频。
  7. 至少持续播放 10 分钟,观察 HDMI、网页流、DEP 音频和进程稳定性。
  8. 检查日志中不存在:
    RgaBlit
    RGA_BLIT fail
    Segmentation fault
    corrupted size
  9. 使用 top 记录单路和双路网页流下的 CPU 使用率。

十二、排障经验总结

  1. 看到 RGA 日志不能直接认定调用者是 MPP 插件;RGA 可能由 GStreamer 核心视频库间接调用。
  2. gst-inspect 的实际插件路径和 SHA-256 核对,是排除旧文件部署问题的必要步骤。
  3. readelf -d 用于区分直接依赖,ldd 会同时显示间接依赖,不能混为一谈。
  4. nm -D 和源码全文搜索可以从二进制符号追溯到真正调用点。
  5. 第一次修复未解决问题并不是插件未上传,而是只关闭了错误层级的 RGA 开关。
  6. 对会造成内存破坏的硬件加速路径,应在应用和启动环境两层关闭,不能只依赖外部 shell 配置。
  7. 媒体 pipeline 测试一旦出现 RGA 内存破坏,应先重启板子,再继续部署或验证,避免把系统异常误判为新代码问题。

经验总结:

  1. WebRTC 新增的不只是网络发送,还可能改变视频处理链路。
  2. HDMI 正常并不代表 WebRTC 的格式转换和编码路径正常。
  3. 看到 RGA 错误,不要默认认为是 MPP 在调用 RGA。
  4. 应结合日志、符号、动态依赖和源码搜索,定位真正的调用者。
  5. 硬件加速出现稳定性问题时,优先关闭单一路径,而不是放弃全部硬件能力。
Logo

火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。

更多推荐