Skip to content

Latest commit

 

History

History
582 lines (457 loc) · 30.4 KB

File metadata and controls

582 lines (457 loc) · 30.4 KB

浏览器 VA-API 硬解接入指南(Chrome / Firefox)

实测环境:nabu(SD855 / Adreno 640)+ DroidSpaces Debian 13 trixie 容器 + msm_drm_drv_video.so(驱动内 V4L2 直通)。 浏览器侧的结论来自真机验证(验证日期 2026-08-26; 第二章第 4、5 节为 0.4.3 绿屏排查追加,2026-09-03; 第二章第 6 节为 0.4.4 seek / 切清晰度卡顿排查追加,2026-09-03), 非理论推导。

前置:驱动已按 README 部署完成,且 ffmpeg -hwaccel vaapi 解码正常。 若这一步不通,先解决后端,别碰浏览器。

⚠️ 0.4.1 更正:vainfo 现在可以正常用。 本文原写"它在本平台会挂住, 即使指定不存在的驱动名也一样"—— 那是 0.3.x daemon 架构下的现象 (vainfo 初始化时会去连 socket,daemon 没起来就卡在那里)。 0.4.0 起没有 daemon 与 socket,vainfo 直接打开 /dev/video32, 正常返回:

vainfo: Driver version: DroidSpaces V4L2 VA-API driver 0.4.1
      VAProfileH264ConstrainedBaseline: VAEntrypointVLD
      VAProfileH264Main               : VAEntrypointVLD
      VAProfileH264High               : VAEntrypointVLD
      VAProfileHEVCMain               : VAEntrypointVLD
      VAProfileVP9Profile0            : VAEntrypointVLD

但它只证明驱动能加载、profile 列表是静态声明的,不证明能出帧。 要验真实解码仍用下面的 ffmpeg 命令。


零、先确认后端真的在硬解

这是唯一一条必须先跑的检查,因为后面所有浏览器配置都建立在它成立的基础上。

LIBVA_DRIVER_NAME=msm_drm ffmpeg -hwaccel vaapi -hwaccel_output_format vaapi \
       -i test.mp4 -f null - 2>&1 | tail -1

⚠️ 必须带 -hwaccel_output_format vaapi。 只写 -hwaccel vaapi 时 ffmpeg 拿不到硬解会静默回落软解且不报错,日志里出现 hevc (native) 就是软解 —— 这样测什么配置都"正常",等于没测。

0.4.0 起不再需要选择端点

0.3.x 的驱动要在 Unix socket 与 TCP 之间选,因为解码走 容器 → socket → Android 侧 decode-daemon → MediaCodec。 那时有个大坑:Unix 端点默认 SO_RCVBUF 只有 224KB 装不下整帧 (720p NV12 是 1.38MB),导致吞吐塌陷到 0.92x 并静默回落软解, 需要给浏览器钉 DMD_ENDPOINT=tcp:20003 绕开。

这些全部不再适用。 0.4.0 的驱动在浏览器进程内直接打开 /dev/video32,没有 socket、没有 daemon、没有端点可选。 DMD_ENDPOINT 与 DMD_WANT_SHM 两个环境变量已无任何读者, 设了也不起作用 —— 如果你的 .desktop 文件里还留着它们, 可以删掉(留着无害,只是没用)。


一、Chrome / Chromium(必须 Wayland 模式)

1. 为什么必须 Wayland

VaapiVideoDecodeLinux 系 feature 的解码帧经 linux-dmabuf 协议零拷贝提交给 合成器。X11/XWayland 下没有这条协议,后果是:

dmd 日志特征 —— 握手成功但零流量:
[12] 握手成功: video/hevc 1280x720 帧回传=内联
[12] 会话结束: 收到 0 NALU, 回传 0 帧     ← 解码器创建后输出无门,空转自杀

驱动栈看似加载(GPU 进程 maps 里 libva/drv_video 都在),实际一帧不解。 X11 模式下此路不通,没有变通。

2. 必需启动参数(缺一不可)

google-chrome \
  --ozone-platform=wayland \
  --render-node-override=/dev/dri/renderD128 \
  --ignore-gpu-blocklist \
  --use-angle=vulkan \
  --enable-features="VaapiVideoDecodeLinux,VaapiVideoDecoder,VaapiVideoDecodeLinuxGL"
参数 原理
--ozone-platform=wayland 见上节,dmabuf 输出的前提
--render-node-override=/dev/dri/renderD128 核心。Chromium vaapi_wrapper.cc 的 PreSandboxInitialization() 只枚举 PCI 总线 DRM 设备,ARM 平台设备的 renderD128 会被 if (device->bustype != DRM_BUS_PCI) continue; 跳过。此开关走 LoadDrmFD() 分支绕过白名单
--ignore-gpu-blocklist ARM GPU 在 Chrome 的软件渲染黑名单里
--use-angle=vulkan ANGLE(WebGL 与部分 GL 呈现路径)走 Vulkan 后端。骁龙 8 Elite 上不给它就文字糊成一团并且重影;实测不影响硬解,所以保留
--enable-features=... Linux VA-API 解码总开关(DMABUF/GL 两路都开)

最后一条必须写成一项、多个 feature 用逗号分隔:--enable-features 出现两次时 不要依赖框架帮你合并,写全在一个列表里最稳。

⚠️ --enable-features 的列表里绝对不能有 Vulkan —— 那是"打开 Vulkan 图形 后端"的开关,实测会让 Chrome 完全不创建 VA-API 解码上下文(对照表见第 2.5 节)。 --use-angle=vulkan 是另一回事,只管 ANGLE 后端,照留。

注意:容器里通常需要 MESA_LOADER_DRIVER_OVERRIDE=msm 让 GL 栈认出 Adreno。

2.5 Vulkan:两个参数别混为一谈(0.4.7 实测纠正)

先说这条 GPU 进程日志,它不是故障:

ui/ozone/platform/wayland/gpu/wayland_surface_factory.cc:249] ERROR:
'--ozone-platform=wayland' is not compatible with Vulkan.
Consider switching to '--ozone-platform=x11' or disabling Vulkan

它给的两个建议里,--ozone-platform=x11 在两台实测机型上都不可用 (本地只有 wayland,X11 起不来),那条 ERROR 两台都会打,可以无视。 判据始终是视频与文字显示是否正常,外加驱动日志里有没有 CreateContext。

真正要分清的是这两项,它们名字像、作用相反:

项 管什么 对硬解的影响
--use-angle=vulkan 只切 ANGLE 的后端 无影响,实测照常建上下文;8 Elite 不给它还花屏
--enable-features=Vulkan(等于 chrome://flags 里的 "Vulkan") 打开 Vulkan 图形后端 硬解直接没有

骁龙 8 Elite + Chrome 151 + 本驱动,2026-09-21 逐项对照实测(同一素材、同一套 驱动,唯一变量是参数;判据 = 驱动日志里 CreateContext 次数):

--enable-features 额外的 --use-angle=vulkan CreateContext
三项 Vaapi… 无 1 ✓
三项 Vaapi… + Vulkan 无 0 ✗
三项 Vaapi… 有 1 ✓
三项 Vaapi… + Vulkan 有 0 ✗

踩了这个坑时的表现很阴:GPU 进程照样加载驱动、照样给每个 profile 建满 config (CreateConfig: profile=32 … 全都在),紧接着一个 vaTerminate, 一个 CreateContext 都没有。页面播放流畅、CPU 不降,看起来就是 "参数配了没生效"。所以别照搬 chrome://flags 教程里"把 Vulkan 打开"那一步。

本文早期版本(0.4.6 及之前)写的"显式开 Vulkan 更保险 / 8 Elite 必须开 Vulkan", 是把上面两项混成了一项,0.4.7 起按本表纠正。nabu / SD855 那一代还要额外去掉 --use-angle=vulkan(wayland 与 Vulkan 冲突),那是机型差异;而不进 --enable-features=Vulkan 是所有机型的共同要求,属 Chrome 侧行为,与本驱动无关。

另有一条与机型无关的实测结论仍然成立:命令行关不掉 Vulkan。Chrome 151 的二进制里 根本没有 --disable-vulkan 这个开关(只有 enable-vulkan 与 use-vulkan),而 Chromium 的 switch 不像 feature flag 那样自动生成 disable- 反面,传进去既不报错 也不生效,纯粹被忽略。实测下列写法全部无效,GPU 进程照样打印上面那条警告:

--disable-vulkan                            # 开关不存在,被忽略
--disable-features=Vulkan                   # 无效
--use-vulkan=disabled                       # 无效
--use-vulkan=disabled --disable-features=Vulkan,VulkanFromANGLE   # 仍然无效

也就是说,如果 chrome://flags 里那个 "Vulkan" 已经被开成 Enabled,命令行 拉不回来(选择存在 profile 的 Local State 里,跟着 profile 走),只能手动设回 Disabled。而正确配置本来就不需要开它 —— 显示要正常靠的是 --use-angle=vulkan。

nabu / SD855 上还有一层:那一代连 ANGLE 的 Vulkan 后端都不能用, --use-angle=vulkan 也要去掉(tools/configure-chrome-vaapi.sh 用 DMD_VULKAN=off 区分两种机型)。

3. 固化到桌面图标(幂等:重复执行不会叠加)

在容器终端里整段复制执行——已配置过的文件自动跳过,备份只在首次创建:

D=/usr/share/applications/google-chrome.desktop
if ! grep -q "render-node-override" "$D"; then
    [ -f "$D.bak" ] || sudo cp "$D" "$D.bak"
    # 骁龙 8 Elite 及更新的机型:保留 --use-angle=vulkan(不给就文字糊、重影)
    # nabu / SD855:删掉 --use-angle=vulkan(那代 wayland 与 Vulkan 冲突)
    # ⚠️ 两种情况都**不要**在 --enable-features 里加 Vulkan,加了就没有硬解(见 2.5 节)
    FLAGS="--ozone-platform=wayland --render-node-override=/dev/dri/renderD128 --ignore-gpu-blocklist --use-angle=vulkan --enable-features=VaapiVideoDecodeLinux,VaapiVideoDecoder,VaapiVideoDecodeLinuxGL"
    sudo sed -i \
      -e "s|^Exec=/usr/bin/google-chrome-stable $|Exec=/usr/bin/google-chrome-stable $FLAGS %U|" \
      -e "s|^Exec=/usr/bin/google-chrome-stable |Exec=/usr/bin/google-chrome-stable $FLAGS |" \
      "$D"
fi
grep -c "render-node-override" "$D"    # 每个 Exec 入口 1 次,不应随执行次数增长
grep -c "features=[^ ]*Vulkan" "$D"    # 必须是 0;非 0 就踩了 2.5 节那个坑

# 执行后每个 Exec= 行应形如(注意开头就是 --ozone-platform=wayland):
# Exec=/usr/bin/google-chrome-stable --ozone-platform=wayland --render-node-override=/dev/dri/renderD128 --ignore-gpu-blocklist --enable-features=VaapiVideoDecodeLinux,... %U

容器内没有 sudo 时,从宿主侧改同一个文件 /mnt/Droidspaces/<容器名>/usr/share/applications/google-chrome.desktop (宿主 root 直写容器 rootfs)。

只想临时试一次,不固化:把上面 FLAGS 的值接在 google-chrome 后面手动启动。

4. 验证三步

# ① GPU 进程加载了驱动栈(数字均应 >0)
for p in $(pgrep -f "type=gpu-process"); do
  grep -c libva /proc/$p/maps; grep -c drv_video /proc/$p/maps; done

# ② 驱动侧出现真实解码会话
#    0.4.0 起没有 daemon 日志了;驱动日志走 stderr,需要以 DMD_VA_LOG=1
#    启动浏览器才能看到(.desktop 里 Exec=env DMD_VA_LOG=1 ...)
#    期望看到(0.4.1 实测输出):
#      [dmd-va] init: ... vendor=DroidSpaces V4L2 VA-API driver 0.4.1
#      [v4l2] S_FMT(OUTPUT) OK: 1920x1088 sizeimage=16588800
#      [v4l2] 缓冲来源: /dev/ion mask=0x2000000(无 dma_heap: ...)
#      [v4l2] CAPTURE 就绪: 1920x1088(有效 1920x1088)stride=1920 slice_h=1088
#      [v4l2] 会话就绪: OUTPUT 8 x 16588800 字节, CAPTURE 24 x 3137536 字节
#      [v4l2] PORT_SETTINGS(SUFFICIENT): h=1088 w=1920 ← 固件解析出序列参数
#      [v4l2] 已发 SESSION_CONTINUE(事件 seq=0)      ← 有这两行才真在解码
#
#    ⚠️ 0.4.1 更正:本文原写"[v4l2] 收到 SOURCE_CHANGE ← 有这行才是固件
#    真的在解码"。那行**永远不会出现** —— msm_vidc 从不发标准
#    V4L2_EVENT_SOURCE_CHANGE,它发的是私有事件 0x08001002
#    (PORT_SETTINGS_CHANGED_SUFFICIENT)。拿 SOURCE_CHANGE 当判据会
#    误判成"没在硬解"。这不只是措辞问题:下游 msm_vidc 不是标准 V4L2
#    stateful 解码器,分辨率变更只经私有 PORT_SETTINGS_* 通知,
#    重配序列也与内核规范不同(见第二章第 6 节)。
#
#    ⚠️ 0.4.4 起 SESSION_CONTINUE 是**每个事件发一次**(带事件 seq),
#    不再是一个会话只发一次。日志里出现多行属正常,缺行才是问题。
#
#    只有 init 而没有"会话就绪",说明浏览器没把解码交给本驱动

# ③ 页面侧帧计数增长
video.getVideoPlaybackQuality().totalVideoFrames

H.264 实测:720p30 连续播放数小时级会话稳定(单会话 16900 帧)。 HEVC 实测:本地文件解码与渲染正常;在线流见"已知限制"。

5. 已知限制:anland 显示桥呈现反馈缺失

在 DroidSpaces 的 anland 合成桥上,Chrome 提交的帧存在 presentation feedback 断链,定量特征(向平台方报障时请附上):

  • getVideoPlaybackQuality().totalVideoFrames 持续增长、dropped≈0 (Chrome 认为一切正常)
  • 但 requestVideoFrameCallback 零回调(帧从未收到"已呈现"回执)
  • 五种组合全部复现:{Wayland, X11} × {硬解, 软解} ± dmabuf 开关
  • 用户观感:视频掉帧跳跃、间歇绿屏(YUV 平面错配的典型表现)
  • 同机 Firefox 播放同一视频完全正常 → 排除解码层,锁定显示桥

这是 anland 对 Chrome 提交协议的兼容缺陷,不是本项目解码管线的问题。 观看 HEVC 视频目前推荐 Firefox(见下节)。

另:X11 下即使不硬解(纯软解)也存在同类呈现异常,进一步佐证问题在 显示桥对 Chrome 提交模式的支持,与 VA-API 无关。


二、Firefox ESR(HEVC 观看推荐)

Firefox 的 VA-API 走 ffmpeg 后端 + WebRender 提交,实测同视频完美流畅, 是当前 HEVC 观看的首选。

1. 写入硬解配置(幂等:重复执行不会写重)

背景:Firefox 的配置写在所选 profile 目录的 user.js 文件里;而"选中的 profile"由 ~/.mozilla/firefox/installs.ini 的 Default= 决定(注意 profiles.ini 里老式 Default=1 标记无效,别看错文件)。下面的命令自动 定位真实 profile、逐条检查已存在则跳过,重复执行安全:

推荐直接用脚本,它会自动找到所有 profile 并幂等写入:

bash tools/configure-firefox-vaapi.sh            # 配置(Firefox 需先关闭)
bash tools/configure-firefox-vaapi.sh --verify   # 查状态
bash tools/configure-firefox-vaapi.sh --uninstall # 还原

⚠️ profile 路径不能写死。 不同安装方式放在不同地方,而且同一台机器上 可能同时存在多处 —— 本机实测 ~/.mozilla/firefox 与 ~/.config/mozilla/firefox 下各有两个真实 profile(都有 prefs.js)。 只写前者的话,Firefox 实际用的是后者时就会"配了但没生效", 而这种失败最难排查:页面能播、统计看着正常,实际静默走软解。 脚本会遍历这四处:

位置 对应安装方式
~/.mozilla/firefox apt / 官方 tarball(传统位置)
~/.config/mozilla/firefox 较新版本遵循 XDG 后的新位置
~/snap/firefox/common/.mozilla/firefox snap
~/.var/app/org.mozilla.firefox/.mozilla/firefox flatpak

FIREFOX_HOME 可指定只处理某一个,DMD_DRIVER_DIR 指定驱动目录。

手动配置的话,pref 清单如下(写进对应 profile 的 user.js,重启生效):

user_pref("media.hardware-video-decoding.enabled", true);
user_pref("media.hardware-video-decoding.force-enabled", true); // 绕过 gfxInfo 黑名单,不关看门狗
user_pref("media.ffmpeg.vaapi.enabled", true);
user_pref("media.hevc.enabled", true);
// 零拷贝:解码帧经 dmabuf 直进合成器。缺了它硬解照跑,但每帧要拷回内存
// 再做 CPU 软件转色,内容进程能吃掉半个到多个核心 ——
// 表现为"开了硬解 CPU 反而更高"。
user_pref("media.ffmpeg.vaapi.force-surface-zero-copy", 2);
// ⚠️ 唯一能关掉硬解性能看门狗的开关。见下方说明。
user_pref("media.ffmpeg.disable-software-fallback", true);
// 播放队列深度:不是可选项。Venus 固件的流水线滞后 4 个输入单元才吐首帧,
// Firefox 默认队列吸收不了这个抖动。1080p30 27Mbps 实测:
// 不加丢帧 14.25% / 顺序回退 20.75%,加上降到 0.89% / 4.04%
// (软解基线 0.5% / 1.85%,即与软解同量级)。
user_pref("media.video-queue.hw-accel-size", 10);
user_pref("media.video-queue.default-size", 10);
user_pref("media.video-queue.send-to-compositor-size", 6);

⚠️ media.vaapi-dmabuf-textures.enabled 已废弃 —— 本文此前推荐过它, 但 Firefox 154 的 libxul 里已无此 pref(实测),写了不起作用。 零拷贝现在由 media.ffmpeg.vaapi.force-surface-zero-copy 控制。

media.ffmpeg.disable-software-fallback 是关掉看门狗的唯一开关 (此前误把 force-enabled 当成这个开关)。Firefox 154 源码 FFmpegVideoDecoder.cpp:1662-1671:累计 16 帧 decodeTime > frameDuration 且该 pref 为 false,就返回 HW decoding is slow, switching back to SW decode。 force-enabled 只在 gfxPlatform.cpp:3029 绕过 gfxInfo 黑名单,看门狗 条件里没有任何对它的引用。B 站 1080p 每个解码器建立后立刻 PORT_SETTINGS(INSUFFICIENT) 重配,叠固件首帧滞后 4 个输入单元, 看门狗在解码器初期最敏感 → 杀解码器 → Seek 回同一 PTS → 重建 → 再重配 → 再误判,页面卡在加载。实测加上后 100s / 3min 的 HW slow、 FATAL、Seek 全 0;3min 配对 4645 帧、送入 3099 收到 3099。 代价:真正硬解损坏时不再自动退回软解。

media.hardware-video-decoding.force-enabled 仍建议开(绕过黑名单)。 media.gpu-process-decoding、media.rdd-ffmpeg.enabled 存在但非必需。

2. 关闭 RDD 沙箱(必需,幂等)

RDD(远程解码进程)的 seccomp 沙箱会拦截 /dev/dri 打开与 socket 连接, 容器环境下必须放开。已改过的文件会自动跳过:

F=/usr/share/applications/firefox-esr.desktop
if ! sudo grep -q "MOZ_DISABLE_RDD_SANDBOX" "$F"; then
    sudo cp "$F" "$F.bak"
    sudo sed -i 's|^Exec=/usr/lib/firefox-esr/firefox-esr |Exec=env MOZ_DISABLE_RDD_SANDBOX=1 /usr/lib/firefox-esr/firefox-esr |' "$F"
fi
grep -c "MOZ_DISABLE_RDD_SANDBOX" "$F"   # 输出 >=1 即成功

没有 sudo 就从宿主侧改 /mnt/Droidspaces/<容器名>/usr/share/applications/firefox-esr.desktop。 手动启动时同理:env MOZ_DISABLE_RDD_SANDBOX=1 firefox <地址>。 Wayland 原生模式(ESR 121+ 默认开启)无需额外变量。

3. 验证

# RDD 进程加载驱动栈(数字应 >0; 未播放视频时为 0,属正常,播一段再看)
grep -c drv_video "/proc/$(pgrep -f 'rdd$' | head -1)/maps"
  • about:support → 图形 → 应显示 VA-API 相关条目
  • 驱动日志(需 DMD_VA_LOG=1)出现 会话就绪 与 已发 SESSION_CONTINUE (0.4.0 起没有 daemon,本文旧版写的 daemon 日志 ... 收到 N NALU 已不适用)
  • 拖过进度条或切过清晰度后,还应能看到 重配:FLUSH_DONE 已收到 与 重配完成并已补发 SESSION_CONTINUE(0.4.4 起;只有 INSUFFICIENT 没有这两行,说明重配没走完,见第 6 节)
  • glxtest 报 "No GPUs detected via PCI" 是噪声,不影响 DMABUF 解码路径

4. Firefox 特有的三个坑(0.4.3 绿屏排查所得)

① 环境变量可能进不了 RDD 进程 —— 会让你误以为在测硬解。

Firefox 把解码放在单独的 RDD 进程里。用 nohup firefox 之类方式设置的 LIBVA_DRIVER_NAME / LIBVA_DRIVERS_PATH 未必传得进去,此时它静默走 CPU 软解,而你以为在测硬解 —— 排查中就因此白跑过一轮。

判据(软解时的表现):MOZ_LOG 里 VAAPI 与 DMABUF 出现 0 次, 解码器描述只有 ffmpeg video decoder (RDD remote)。可靠的核实方法是直接 查进程:

R=$(pgrep -f 'RDD Process' | head -1)
tr '\0' '\n' < /proc/$R/environ | grep -E 'LIBVA|MOZ_DISABLE_RDD'
grep -c drv_video /proc/$R/maps        # 应 >0
ls -l /proc/$R/fd | grep -c video32    # 硬解唯一通路,应 >0

0.4.3 起版本串带 git 短 hash,vainfo 能直接确认实际加载的是哪一版 .so, 不必靠文件时间戳猜(系统目录与 LIBVA_DRIVERS_PATH 常各有一份)。

② media.ffmpeg.vaapi.force-surface-zero-copy 是三态整数,且改它无法隔离问题。

它不是布尔。Firefox 源码 modules/libpref/init/StaticPrefList.yaml 的注释是 0 - force disable、1 - force enable、2 - default,默认 2。 注释首句 "Force to copy dmabuf video frames" 容易读反 —— 这个 pref 管的是 「是否强制零拷贝」。

关键在于设成 0 并不会让 Firefox 改走 CPU 下载路径。Linux 上 DMABufSurfaceYUV::CopyYUVDataImpl 仍然先 vaExportSurfaceHandle 把源 dmabuf 导入成 EGL 纹理,再 BlitTextureToTexture 拷到另一块纹理。 所以源描述有错时,设 0 照样绿屏 —— 这个对照实验区分不出 「驱动导出的几何错」和「Firefox 导入有问题」,不要用它下结论。

③ LD_PRELOAD 拦不到 Firefox 的 vaExportSurfaceHandle。

Firefox 经 libva 的 driver vtable 直接进入驱动回调,不走公共符号。 实测 RDD 进程确实加载了 hook 库(/proc/<pid>/maps 可见),但没有任何 记录产生。要拿它的真实入参只能在驱动内部打日志 —— 这也是 DMD_VA_LOG 会打印导出 flags 的原因。

实测 Firefox 传的是 flags=0x5,即 VA_EXPORT_SURFACE_SEPARATE_LAYERS | VA_EXPORT_SURFACE_READ_ONLY, 驱动返回两层:Y 用 DRM_FORMAT_R8、UV 用 DRM_FORMAT_GR88, 单个 dmabuf object,modifier 为 DRM_FORMAT_MOD_LINEAR。 另一个要点:Firefox 在解码之前就会导出 dmabuf 去建纹理,所以驱动的 export 路径不能等帧就绪(Chrome 同样如此)。

5. 判断画面是否真的正常(不必截图)

想客观确认「有没有绿屏」时会撞到两道墙:KWin 对未授权进程返回 org.kde.KWin.ScreenShot2.Error.NoAuthorized;XWayland 的 xwd -root 拿不到 Wayland 原生窗口内容(报 BadMatch)。

所以改用驱动内的探针 —— DMD_VA_LUMA=1 会在每次导出时抽样打印:

像素: surface=3 Y均值 29.3 UV均值 123.7 UV近零 2%
像素: surface=1 Y均值 0.0  UV均值 128.0 UV近零 0%   ← 黑帧

只看 Y 判不出绿屏,判据在 UV(NV12 的 UV=0 是最大色偏,转 RGB 就是纯绿):

画面 UV 均值
正常彩色 123.6 – 124.8
纯黑(起播前的合法初值) 128.0
纯绿(异常) 0.0,且 UV近零 > 80% 会标 ← 纯绿!

0.4.3 在 720p 上的 45 秒长播复测:924 次导出,纯绿 0、截断 0、错误 0, 送入 3863 单元收到 3863 帧。

6. seek / 切清晰度卡数秒:0.4.3 及更早的固定循环(0.4.4 已修)

症状:拖进度条或播放器自动切清晰度(ABR)后,画面卡住数秒才恢复, 而且会反复发生。0.4.4 修复;在旧版上这不是配置问题,换 pref 没用。

判据在驱动日志里,是一个每轮约 7 秒的固定循环(需 DMD_VA_LOG=1):

PORT_SETTINGS(INSUFFICIENT)
flush 触发: futile=0(recv=0 has_seq=0 pend=5) spent=2000/2000
SyncSurface: 等帧超时 5000 ms
会话已重建(codec=0 864x480)

7 秒 = 2 秒 flush 阈值 + 5 秒 SyncSurface 超时,然后重建会话。 recv=0 是关键 —— 送了料但一帧没回来,固件停在 in_reconfig 等重配。

根因:固件用 msm_vidc 私有事件 PORT_SETTINGS_CHANGED_INSUFFICIENT (V4L2_EVENT_MSM_VIDC_START+3)要求按新几何重配 CAPTURE,旧版收到后没有 真正重配。0.4.4 按厂商 OMX 的正规序列处理:先 FLUSH_CAPTURE 并等 FLUSH_DONE 把固件手里的输出缓冲全部收回,再 STREAMOFF(CAPTURE) + REQBUFS(CAPTURE,0),之后才释放 dma-buf,最后按新几何重配并 STREAMON(CAPTURE)。序列细节与内核取证见 verified-platform-facts.md §12。

0.4.4 上该看到的日志(一次成功的重配):

[v4l2] PORT_SETTINGS(INSUFFICIENT): h=480 w=856 ...
[v4l2] INSUFFICIENT:开始重配 CAPTURE 第 1 次(当前 1920x1088 ...)
[v4l2] 重配:FLUSH_DONE 已收到
[v4l2] CAPTURE 就绪: 856x480 ...
[v4l2] 重配完成并已补发 SESSION_CONTINUE

看到 INSUFFICIENT 却没有 重配完成,说明重配中断了(日志里会有 重配中收到 SYS_ERROR 或 CAPTURE 重配失败)—— 这时才是真的缺陷, 可带这段日志报障。

另一处易误判:已发 SESSION_CONTINUE 在 0.4.4 起是逐事件打印的 (带 事件 seq=),一次播放里出现多行属正常,不是重复发送的 bug。 反过来,浏览器 seek 会在同一 fd 上反复触发事件,漏发一次就永久卡在等帧。

实测(Firefox,H.264/HEVC,含 seek 与 856x480 ↔ 1920x1080 切换): SYS_ERROR 从数百次降到 0,2 秒 flush 空等 0、5 秒 SyncSurface 超时 0、 会话重建循环 0,9 次 INSUFFICIENT 全部重配成功(9/9),2977 帧配对, DestroyContext 送入/取回完全平衡(1080/1080、850/850、630/630)。

7. AV1 拖进度条:黑到下一个关键帧是预期行为(0.4.7 起)

症状:AV1 视频拖进度条后短暂黑屏/花屏,几秒后恢复;恢复后画面正常。

这不是缺陷。msm_vidc 是状态解码器,落到 GOP 中间时头几帧引用的是它 DPB 里根本不存在的帧。这类帧硬件既不出帧也不报错,于是驱动只能干等 SyncSurface 超时,而调用方(浏览器/ffmpeg)一收到同步错误就放弃整条码流 —— ffmpeg 侧实测 rc=251、输出 0 帧;浏览器里的等价表现应是"拖动后不再出画" (这条尚未在浏览器上复测,不要当实测结论用)。

0.4.7 起驱动在合成码流前先判一次引用可否解析,判不过的帧不提交、直接按 "就绪但内容为这张 surface 的残留"交付,流程继续往前推,下一个关键帧一到 就恢复正常,之后的像素与软解逐字节一致。

判据(DMD_VA_LOG=1):

EndPicture: AV1 surface=N 的参考帧不在 DPB(拖到 GOP 中间起解?),跳过提交并按空壳交付

出现这行属正常,条数应约等于"起点到下一个关键帧之间的帧数";不该再出现 SyncSurface: 等帧超时 5000 ms 或播放中止。GOP 越长,这段黑屏越久 —— 观感不好时是片源 GOP 的问题,不是解码器的。

实测(GOP=12 的 36 帧样本 + B 站 1080p60 真流 GOP=300,ffmpeg 路径): 从第 5 帧起解 43 帧全交付,其中 7 帧走空壳,关键帧之后与软解逐字节一致; 不卡死、不中止。浏览器端(Chrome / Firefox 拖进度条)尚未复测,装上新驱动后 请按上面的日志判据核对一遍。


三、实时监视:确认硬解"持续"在用

单次体检只能回答"此刻通不通"。播放视频时 CPU 曲线本身是脉冲状的 (解码按帧突发 + 播放器有帧缓冲),光看占用起伏无法判断硬解是否中断。

# 持续监视,Ctrl-C 退出;加 ADB=1 额外显示宿主侧 VPU 证据
ADB=1 bash tools/watch-decode.sh

# 只跑 60 秒
bash tools/watch-decode.sh 60

输出形如:

时刻     │ Firefox RDD          │ Chrome GPU      │ 宿主 VPU
16:50:47 │ ✅硬解 cpu=61        │ —— 无 GPU 进程   │ ✅解码中 36/5s
16:50:48 │ ✅硬解 cpu=77        │ —— 无 GPU 进程   │ ✅解码中 71/5s
16:50:49 │ ✅硬解 cpu=59        │ —— 无 GPU 进程   │ ✅解码中 127/5s

判读要点:看 ✅ 标记是否稳定,不要看 cpu 数值是否平滑。 数值在 59~95 之间起伏是正常的,只要 ✅硬解 一直在,硬解就没有中断。

其中「宿主 VPU」是最硬的证据 —— 宿主 omx@1.0-service(硬件解码服务) 的 CPU 消耗。实测空闲约 12 jiffies/5s、解码约 107 jiffies/5s,差约 9 倍, 阈值取 30 分界。它涨说明 VPU 真在出帧,与上层怎么统计无关。

四、快速体检脚本

一键检查 daemon 连通性、两个浏览器的驱动栈加载状态、最近解码会话流量。 无需 clone 整个仓库,直接下载运行:

# 方式一: 下载后执行(推荐,可先审阅内容)
curl -fsSLO https://raw.githubusercontent.com/Re-s/droidspaces-media-decode/v0.3.4/tools/check-browser-vaapi.sh
bash check-browser-vaapi.sh

# 方式二: 管道直跑
curl -fsSL https://raw.githubusercontent.com/Re-s/droidspaces-media-decode/v0.3.4/tools/check-browser-vaapi.sh | bash

直连 GitHub 失败时走系统代理,例如: curl -fsSL --proxy socks5h://127.0.0.1:1080 ...

链接锚定在 v0.3.4 标签上,脚本行为与本文档描述严格一致; 想要最新版把 URL 里的 v0.3.4 换成 master。

五、排障速查表

症状 根因 处置
会话建立成功但 0 帧 Chrome 跑在 X11,dmabuf 输出走不通 换 Wayland 模式
GPU 进程 maps 无 drv_video PCI 白名单跳过了平台设备 确认 --render-node-override
日志报 not compatible with Vulkan Chromium 的固定提示,两台机型的处置相反 按视频与文字是否正常决定:nabu 要关,骁龙 8 Elite 必须留(关了会文字糊、重影),见 §2.5
配置全对但文字糊成一团、画面重影 在骁龙 8 Elite 上把 Vulkan 关了 到 chrome://flags 把 Vulkan 恢复 Enabled(默认值)并重启,见 §2.5
Firefox 有进程不解码 RDD 沙箱拦设备 MOZ_DISABLE_RDD_SANDBOX=1
user.js 写了没生效 写错了 profile 查 installs.ini 的 Default
Chrome HEVC 在线流掉帧/绿屏 anland 呈现反馈缺失(平台 bug) 用 Firefox;或等平台修复
视频卡顿 + CPU 飙高,但"硬解已启用" 实际回落软解了(0.4.0 无端点问题,多为浏览器侧没走硬解路径) 按第零章的 ffmpeg 判据自查后端;再用验证三步确认浏览器进程加载了驱动
ffmpeg 日志 hevc (native) 根本没用硬解,静默回落软解了 测试命令要带 -hwaccel_output_format vaapi
拖进度条/切清晰度卡数秒并反复 INSUFFICIENT 重配没做(0.4.3 及更早) 升级到 0.4.4;判据见第二章第 6 节的 7 秒循环
AV1 拖动后黑几秒再恢复 起点落在 GOP 中间,头几帧引用缺失 —— 预期行为 不用处理,判据与原理见第二章第 7 节;若拖动后一直不出画,那是 0.4.7 之前的整条码流放弃缺陷,升级后按第 7 节的日志判据核对
驱动 init 了但一帧不出 该设备的 V4L2 解码会话起不来(已知有此类设备) 跑 vaapi-driver/tools/probe_device_support.c,看有没有事件到达(⚠️ 该探针只订阅标准 SOURCE_CHANGE,而 msm_vidc 只发私有 PORT_SETTINGS_*,所以"没有 SOURCE_CHANGE"本身不构成不可用的判据,见第二章第 6 节)