如何把 OpenWrt 软路由变成 Waydroid 电视盒子
- 0. 这是一个怎样的 Setup ?
- 0.5 部署前先看:最容易把路由器弄断网的坑
- 坑 1:不要一开始就给 Fedora LXC
/sys:rw 和桥接网卡
- 坑 2:不要用
lxc.net.0.type = none 共享 OpenWrt 宿主网络
- 坑 3:OpenWrt LXC 的
common.conf 可能触发 cgroup2 BPF 设备过滤
- 坑 4:Waydroid 内层 Android LXC 需要可写 cgroup2 子树
- 坑 5:OpenWrt 的
/tmp 是 tmpfs,必须在 Android 层禁用蓝牙
- 1. 变量约定
- 2. 整体部署流程
- 3. OpenWrt 宿主内核准备
- 3.1 必要能力
- 3.1.1 binderfs 挂载和检查
- 3.2 OpenWrt 内核配置
- 3.3 重要坑:OpenWrt 的 DMA-BUF debloat patch
- 3.4 构建内核
- 3.5 部署内核,并修改 GRUB 配置
- 3.5.1 准备部署包
- 3.5.2 复制 kernel 和 modules 到 OpenWrt
- 3.5.3 不要覆盖原始
OpenWrt 启动项
- 3.5.4 编辑
/boot/grub/grub.cfg
- 3.5.5 重启前检查
- 3.5.6 重启并验证
- 3.6 OpenWrt 宿主运行时前置检查:LXC 工具、GPU 固件和设备节点
- 4. 创建外层 Fedora 44 LXC
- 4.1 rootfs 创建说明
- 4.2 外层 LXC 配置:分两阶段写,先安全初始化,再正式运行
- 4.3 验证外层容器设备
- 4.4 给 Fedora 一个最小可用网络:不用 NetworkManager/networkd
- 5. Fedora LXC 内安装基础软件
- 6. 安装 upstream Waydroid / libgbinder / gbinder-python
- 7. 安装 WayDroid-ATV 镜像
- 7.1 版本选择:新版 A16 vs 旧版 A13
- 7.2 方法一:用
waydroid init 接 WayDroid-ATV OTA server(推荐)
- 7.3 方法二:手动下载 zip 并安装
- 7.4 全新初始化时的清理边界
- 7.5 配置 Waydroid 使用正确协议和镜像目录
- 7.6 初始化后立刻禁用 Android 蓝牙
- 8. 配置 Fedora 图形会话:systemd user + LXQt + KWin
- 9. 配置桥接网络
- 10. Waydroid 内层 LXC:config_nodes、cgroup2 子树和
cgroup:rw
- 11. 启动
- 11.1 OpenWrt 启动外层 Fedora
- 11.2 Fedora 内启动 Waydroid 和桌面会话
- 11.3 如果
waydroid-autostart.service 和 sunshine-wayland.service 是 inactive
- 12. 验证
- 13. Bonus:Android media / Codec2 / VAAPI / P010 支持(可选)
- 13.5 Bonus 验证:视频播放验证(可选)
- 14. 可选:OpenWrt 4G swap
- 14.5 最容易漏的部署细节复盘
- 15. 常见问题
- 15.0 OpenWrt
/tmp 被写满或内存上涨
- 15.1
waydroid status 显示 IP UNKNOWN
- 15.2 Android 提示 Missing DMA-BUF support
- 15.3
waydroidplatform 不通或 waydroid app list 卡住
- 15.4 Waydroid inner LXC 因 AppArmor 配置失败
- 15.5
lxc.hook.post-stop = /dev/null 导致 status 126
- 15.6 Android 早期日志出现
/dev/hw_random 缺失
- 15.7 NetworkManager / systemd-networkd 抢网络
- 16. 最小检查清单
- 17. 不建议做的事
- 18. Debug 指引
家里没有多余的电视盒子了,电视的性能也不是很好,怎么办呢?刚好软路由上有个 HDMI,于是...
向您介绍:LineageOS in LXC(Waydroid) on Fedora in LXC on OpenWrt,无须虚拟化,直接跑在 OpenWrt 的 kernel 和 sysfs 上的 Android!
断断续续折腾了5天,烧掉了$200+的偷啃,终于和一大坨Agent一起把这玩意给搞出来啦。其实基础安装,包括编译调试 OpenWrt 内核在内也就花了两天,后面的时间都是修视频硬件解码,给上游 Waydroid-ATV 提 pr 啥的。
此次部署涉及编译 OpenWrt 内核,编译 Android Vendor,编译 ffmpeg 组件,编译 Waydroid 上游组件等,还有一大堆的配置文件测试,没有 AI 的协助是绝对做不到的(传统人力可能得要一个人一个月的时间?特别感谢凌莞宝给的偷啃!)。整理出了一大堆文档,索性把这篇教程也让 Agent 来写啦,希望你能看懂。建议扔给你的AI来部署,我们已经把坑替你踩好了。
整理日期:2026-05-23
适用目标:在一台 OpenWrt x86_64 主机上,通过“OpenWrt 宿主 + Fedora LXC + Waydroid Android LXC”的三层结构运行 Waydroid ATV,并启用 Intel iGPU、DMA-BUF、桥接网络、KWin/LXQt 图形会话和 Android Codec2/VAAPI 视频解码。
本文来自我们已经跑通的 OpenWrt/Fedora/Waydroid 实机部署记录。为了给其他人复用,文中不使用 openwrt.lan、固定内网地址、固定用户目录作为硬要求,而是改成变量。你可以按自己的机器替换。
0. 这是一个怎样的 Setup ?
把 Android TV 放进 Waydroid 的 Android LXC,再把 Waydroid 放进一个 Fedora privileged LXC,最底下仍然使用 OpenWrt 宿主的 Linux kernel 和硬件设备。
整体结构是这样:
OpenWrt x86_64 宿主
└── Fedora 44 privileged LXC
└── Waydroid Android LXC / WayDroid-ATV
它不是传统虚拟机,也不是在 Docker 里硬塞 Android。Android 这一层会直接使用宿主内核提供的 binder、cgroup、DMA-BUF、DRM render node、input、audio 等能力;Fedora 这一层负责提供更完整、对 Waydroid 更友好的 userspace。
三层各自负责的事情大概是:
第一层:OpenWrt 宿主
- 跑真正的 Linux kernel。
- 提供 Waydroid 需要的内核能力,例如 binder/binderfs、cgroup2、DMA-BUF heap。
- 提供 GPU/DRM 设备,例如
/dev/dri/renderD128。 - 提供输入、声音等设备,例如
/dev/input、/dev/uinput、/dev/snd。 - 准备 GPU firmware。OpenWrt 通常没有完整 firmware,这一步后面会单独写。
- 提供外层 Fedora LXC 使用的桥接网络,例如接到 OpenWrt 的
br-lan。
第二层:Fedora privileged LXC
- 运行
systemd、Waydroid、LXC、KWin Wayland、LXQt 等组件。 - 提供 Wayland 图形会话,让 Waydroid 的 Android 画面有地方显示。
- 提供
libgbinder、Waydroid helper、Mesa/VAAPI 等 Linux userspace 组件。 - 管理普通用户会话,例如本文使用的
user 用户,以及它所属的 video、audio、input、render、seat 等组。 - 作为 Android 内层 LXC 的父环境,负责把必要设备、cgroup 子树和 binder 节点交给 Waydroid。
第三层:Waydroid Android LXC / WayDroid-ATV
- 运行 Android TV,也就是 LineageOS / WayDroid-ATV。
- 使用
binder、hwbinder、vndbinder 完成 Android IPC。 - 使用
/dev/dma_heap/system 和 DRM render node 参与图形缓冲区分配、渲染和显示。 - 通过桥接网络获得一个和 LAN 同网段的地址。
- 后面可选启用 Android Codec2 / VAAPI / P010 等视频解码能力。
基础部署目标很朴素:先把 Android TV 桌面跑起来,让网络、显示、输入、声音都能工作。至于视频硬解、Codec2、VAAPI、P010 这些内容,本文会放到后面的 Bonus 部分;它们很有价值,但不应该挡在第一次跑通前面。
本文后续命令会默认使用这几个名字,你可以按自己的机器替换:
外层 Fedora LXC 名称:waydroid
外层 Fedora 普通用户:user
外层 Fedora 图形会话:systemd user session + LXQt Wayland + KWin Wayland
Android 镜像:WayDroid-ATV Android 16 / Lineage 23.0 / waydroid_tv_x86_64
0.5 部署前先看:最容易把路由器弄断网的坑
请先看这一节,再动 LXC 配置。我们早期踩过的最大坑不是 Waydroid 本身,而是外层 Fedora LXC 里 systemd-networkd / systemd-udevd 配合可写 /sys 后,可能会改动网络设备状态或尝试按 Fedora 规则重命名网卡。
坑 1:不要一开始就给 Fedora LXC /sys:rw 和桥接网卡
建议第一次启动 Fedora LXC 时先用安全配置:
# 第一次启动只用于进容器做系统服务屏蔽,不要先桥接到 LAN
lxc.net.0.type = empty
# 第一次启动保留 /sys 只读
lxc.mount.auto = proc:mixed sys:ro cgroup:mixed
这样做的目的很具体:
- Fedora 容器第一次启动时,即使
systemd-networkd 或 systemd-udevd 自动运行,也不容易改 OpenWrt 宿主的网卡状态。 empty 网络命名空间不会共享 OpenWrt 宿主网络,也不会立刻桥接到 br-lan。/sys:ro 可以降低容器内 udev 写 sysfs、改设备状态、触发网卡重命名的风险。
第一次启动后,进入 Fedora 容器:
lxc-start -n waydroid
lxc-attach -n waydroid -- /bin/bash
在 Fedora 容器内先屏蔽这些服务:
systemctl mask systemd-networkd.service systemd-networkd.socket systemd-networkd-wait-online.service 2>/dev/null || true
systemctl disable --now systemd-networkd.service systemd-networkd.socket systemd-networkd-wait-online.service 2>/dev/null || true
systemctl mask systemd-resolved.service firewalld.service NetworkManager.service wpa_supplicant.service 2>/dev/null || true
systemctl disable --now systemd-resolved.service firewalld.service NetworkManager.service wpa_supplicant.service 2>/dev/null || true
再处理 udev 的网卡自动重命名。建议至少写入一个 “保持内核原名” 的 .link 文件,并屏蔽默认 link 规则:
mkdir -p /etc/systemd/network
cat > /etc/systemd/network/10-keep-kernel-ifnames.link <<'EOF'
[Match]
OriginalName=*
[Link]
NamePolicy=keep
MACAddressPolicy=none
EOF
ln -sfn /dev/null /etc/systemd/network/99-default.link
如果你的目标是最保守的路由器环境,可以先屏蔽 systemd-udevd,等确认不会重命名网卡之后,再按图形会话需要决定是否恢复 udev 数据库:
systemctl mask systemd-udevd.service systemd-udevd-control.socket systemd-udevd-kernel.socket 2>/dev/null || true
systemctl disable --now systemd-udevd.service systemd-udevd-control.socket systemd-udevd-kernel.socket 2>/dev/null || true
注意边界:KWin/Weston/libinput/libudev 有时需要 udev 数据库来识别 DRM 和输入设备。如果后面图形会话缺 udev tags,可以在确认网卡不会被重命名后,再有控制地恢复 udev,并只触发需要的设备,例如 DRM/input。不要在 /sys:rw、网络还没保护好的阶段直接让 udev 全量扫描。
完成上面步骤后,退出 Fedora,停止外层 LXC:
exit
lxc-stop -n waydroid
然后再把外层 LXC 改成正式运行配置:
lxc.net.0.type = veth
lxc.net.0.veth.pair = lxc-waydroid
lxc.net.0.link = br-lan
lxc.net.0.flags = up
lxc.net.0.hwaddr = 00:16:3e:59:af:02
lxc.mount.auto = proc:mixed sys:rw cgroup:mixed
这一步顺序很重要:先在容器内禁用 networkd / 自动网卡重命名,再给桥接网络和 /sys:rw。
坑 2:不要用 lxc.net.0.type = none 共享 OpenWrt 宿主网络
我们早期试过 lxc.net.0.type = none。这个设置会让 Fedora 容器共享 OpenWrt 宿主网络命名空间。结果是 Fedora 里的网络管理服务有机会直接影响 OpenWrt 宿主网卡,路由器可能失联。
安全选择是:
- 首次保护阶段用
empty。 - 正式运行阶段用
veth 接 OpenWrt 的 br-lan。 - 不要用
none。
坑 3:OpenWrt LXC 的 common.conf 可能触发 cgroup2 BPF 设备过滤
OpenWrt 的 LXC common.conf 里可能有:
lxc.cgroup2.devices.deny = a
这会让 LXC 尝试加载 cgroup2 device BPF 程序。有些 OpenWrt 内核没有对应 BPF 支持,启动会失败,日志类似:
Failed to load bpf program
Failed to setup cgroup2 device controller limits
我们验证过的处理方式是:外层 Fedora LXC 不 include common.conf,自己写最小配置,并且不要写 cgroup2 device deny 规则。当前方案依赖 privileged LXC 和明确的设备 bind mount。
坑 4:Waydroid 内层 Android LXC 需要可写 cgroup2 子树
这是另一个非常关键的坑。Waydroid 内层 LXC 不是普通进程,它还要在 Android 里让 init / libprocessgroup / lmkd 创建和管理 cgroup。
如果内层 LXC 只有:
lxc.mount.auto = cgroup:ro sys:ro proc
可能出现这些现象:
libprocessgroup: Failed to make and chown /sys/fs/cgroup/uid_1000: Read-only file system
libprocessgroup: Failed to open /dev/cpuset/foreground/tasks: No such file or directory
lmkd: Old kill strategy can only be used with v1 cgroup hierarchy
critical process 'lmkd' exited ... before boot completed
Android 容器启动几秒后退出
我们日志里确认过的关键要求是:Waydroid 内层 Android LXC 必须运行在明确的 cgroup2 子树中,并且 Android 看到的 cgroup 挂载必须是可写的,也就是给内层 LXC cgroup:rw:
lxc.mount.auto = cgroup:rw sys:ro proc
lxc.cgroup.dir.container = waydroid-android
lxc.cgroup.dir.monitor = waydroid-android-monitor
lxc.cgroup.dir.monitor.pivot = waydroid-android-monitor-pivot
lxc.cgroup.dir.container.inner = payload
lxc.hook.pre-start = /var/lib/waydroid/lxc/waydroid/waydroid-cgroup2-delegate.sh
waydroid-cgroup2-delegate.sh 的作用是打开 /sys/fs/cgroup 的可用 controllers,并让 LXC 在 waydroid-android/payload 这个 leaf 里运行 Android。这样 Android 可以在自己的 cgroup 子树下面创建 uid_* / pid_* 子目录。
这不是为了“美化日志”,而是 Android 进程管理需要的运行条件。后文会给出完整脚本。
坑 5:OpenWrt 的 /tmp 是 tmpfs,必须在 Android 层禁用蓝牙
OpenWrt 的 /tmp 通常是 tmpfs,也就是占用内存。Waydroid / Android 启动失败、服务反复崩溃或 logcat 长时间写入 /tmp 时,可能直接吃掉路由器内存。
我们遇到的一个具体风险是:Waydroid-ATV 的蓝牙栈在当前环境里不可靠,蓝牙相关服务可能反复崩溃并刷日志。如果日志写到 OpenWrt/Fedora 的 tmpfs,内存会被慢慢吃满,最后可能导致路由器重启。
部署时请明确做两件事:
- 不要把长时间日志写到 OpenWrt
/tmp。 长日志请写到持久存储,例如构建机本地目录、外接磁盘,或 OpenWrt rootfs 上确认不是 tmpfs 的目录。 - 在 Android 层禁用蓝牙。 不要通过加载 OpenWrt Bluetooth 模块来“修蓝牙”。我们之前的记录里,Bluetooth kernel 模块尝试导致过 boot loop 风险。
Android 启动后执行:
waydroid shell -- sh -c '
setprop persist.bluetooth.disabled true
settings put global bluetooth_on 0
settings put global ble_scan_always_enabled 0
cmd bluetooth_manager disable 2>/dev/null || true
pm disable-user --user 0 com.android.bluetooth 2>/dev/null || true
pm disable com.android.bluetooth 2>/dev/null || true
'
验证:
waydroid shell -- sh -c '
getprop persist.bluetooth.disabled
cmd package list packages -d | grep -i bluetooth || true
service list | grep -i bluetooth || true
'
期望至少看到:
persist.bluetooth.disabled=true
package:com.android.bluetooth
service list 里可能仍有 bluetooth_manager binder service,某些 vendor bluetooth service 也可能存在;关键是 Android framework 层的 com.android.bluetooth 已被禁用,蓝牙不再作为可用功能反复启动。
1. 变量约定
请先把这些变量换成你自己的环境。
# OpenWrt 宿主
OPENWRT_HOST="你的OpenWrt主机名或IP"
# 外层 Fedora LXC
FEDORA_LXC_NAME="waydroid"
FEDORA_ROOTFS="/srv/lxc/waydroid/rootfs"
FEDORA_LXC_CONFIG="/srv/lxc/waydroid/config"
FEDORA_USER="user"
FEDORA_UID="1000"
# OpenWrt LAN
OPENWRT_LAN_BRIDGE="br-lan"
OPENWRT_LAN_GATEWAY="192.168.1.1"
FEDORA_LAN_ADDR="192.168.1.237/24" # 示例,按你的 LAN 改
ANDROID_LAN_ADDR="由 DHCP 分配" # 不建议硬编码
# Waydroid 镜像存放位置,位于 Fedora LXC 内
WAYDROID_IMAGE_DIR="/etc/waydroid-extra/images"
# 本地 Android 源码树。如果你只使用现成镜像和产物,可以跳过源码构建章节。
ANDROID_SRC="/path/to/android-waydroid-atv23"
后文命令如果写死了 /srv/lxc/waydroid、user、192.168.1.1,请按这里的变量替换。
2. 整体部署流程
建议按这个顺序做:
- 准备 OpenWrt 内核:binder、binderfs、cgroup、PSI、DMA-BUF heaps。
- 创建外层 Fedora 44 privileged LXC,并透传设备。
- 在 Fedora LXC 中安装基础软件、Waydroid userspace、libgbinder、gbinder-python。
- 如果 OpenWrt 缺 GPU firmware,从 Fedora/Linux 的 firmware 包复制对应 firmware 到 OpenWrt,再重启验证 GPU 驱动。
- 安装 WayDroid-ATV Android 16 镜像。
- 配置 Fedora 图形会话:systemd user session + LXQt Wayland + KWin。
- 配置网络:外层 Fedora 和内层 Android 桥接到 OpenWrt LAN。
- 配置 Waydroid 内层 LXC 的
config_nodes 和 cgroup2 子树。 - 启动并验证三层状态。
- 可选 Bonus:需要视频硬解调优时,再部署 Android media / Codec2 / VAAPI / P010 支持,并用 Android 播放器看日志验证。
3. OpenWrt 宿主内核准备
3.1 必要能力
OpenWrt 宿主必须提供:
Android binder / binderfs
cgroup / cgroup v1 compatibility pieces used by this setup
PSI
DMA-BUF core
DMA-BUF system heap
UDMABUF
DRM render node, usually /dev/dri/renderD128
部署后必须能看到:
ls -ld /dev/dri /dev/dma_heap /dev/dma_heap/system 2>/dev/null || true
grep -E 'dma_heap|udmabuf' /proc/misc /proc/devices 2>/dev/null || true
期望至少有:
/dev/dri
/dev/dma_heap
/dev/dma_heap/system
/proc/devices 里有 dma_heap
/proc/misc 里有 udmabuf
如果 /dev/dma_heap/system 不存在,不要去改 Android XML、Magisk 模块、build.prop 来隐藏提示。正确方向是修 OpenWrt 内核。
3.1.1 binderfs 挂载和检查
Waydroid 需要 binder、hwbinder、vndbinder。常见位置是:
/dev/binderfs/binder
/dev/binderfs/hwbinder
/dev/binderfs/vndbinder
OpenWrt 宿主要先具备 binder / binderfs 内核能力。可以检查:
grep -E 'binder' /proc/filesystems /proc/misc /proc/devices 2>/dev/null || true
ls -ld /dev/binderfs 2>/dev/null || true
ls -l /dev/binderfs/binder-control /dev/binderfs/binder /dev/binderfs/hwbinder /dev/binderfs/vndbinder 2>/dev/null || true
mount | grep binder || true
如果 /dev/binderfs 不存在,但内核已经启用了 CONFIG_ANDROID_BINDERFS=y,可以手动挂载做诊断:
mkdir -p /dev/binderfs
mount -t binder binder /dev/binderfs 2>/dev/null || true
ls -l /dev/binderfs 2>/dev/null || true
如果挂载后只有 binder-control,没有 binder、hwbinder、vndbinder,新版 Waydroid 的 driver helper 通常会尝试通过 binder-control 创建节点,并把它们链接到 /dev/。
注意边界:OpenWrt 宿主需要有 binder/binderfs 内核能力,但不一定要长期把 binderfs 挂在宿主 /dev/binderfs。我们当前远程环境里,外层 LXC 配置没有显式 bind /dev/binderfs,Fedora 容器内仍然由 Waydroid helper 建出了:
/dev/binderfs/binder
/dev/binderfs/hwbinder
/dev/binderfs/vndbinder
/dev/binder -> /dev/binderfs/binder
/dev/hwbinder -> /dev/binderfs/hwbinder
/dev/vndbinder -> /dev/binderfs/vndbinder
所以最终请至少确认 Fedora 外层容器里能看到这些设备:
lxc-attach -n waydroid -- /bin/bash -lc 'ls -l /dev/binderfs/binder /dev/binderfs/hwbinder /dev/binderfs/vndbinder /dev/binder /dev/hwbinder /dev/vndbinder 2>/dev/null || true'
如果 Fedora 里没有 /dev/binderfs,请先看 Waydroid container 日志。可以选择让 Waydroid helper 在 Fedora 里挂载 binderfs,也可以在 OpenWrt 宿主挂载后通过第 4.2 节的 optional bind mount 传进 Fedora。不要把“OpenWrt 宿主当前没挂 /dev/binderfs”单独当成失败;关键是 Fedora 和内层 Android 最终能访问 binder/hwbinder/vndbinder。
3.2 OpenWrt 内核配置
在 OpenWrt source tree 中,x86 通用配置类似:
target/linux/x86/config-6.12
target/linux/x86/64/config-6.12
我们验证过的关键配置如下。
target/linux/x86/config-6.12:
CONFIG_ANDROID=y
CONFIG_ANDROID_BINDER_IPC=y
CONFIG_ANDROID_BINDERFS=y
CONFIG_ANDROID_BINDER_DEVICES="binder,hwbinder,vndbinder"
CONFIG_PSI=y
CONFIG_MEMCG_V1=y
CONFIG_CPUSETS_V1=y
CONFIG_CGROUP_FREEZER=y
CONFIG_CGROUP_DEVICE=y
CONFIG_CGROUP_NET_PRIO=y
CONFIG_CGROUP_PERF=y
target/linux/x86/64/config-6.12:
CONFIG_DMA_SHARED_BUFFER=y
CONFIG_DMABUF_HEAPS=y
CONFIG_DMABUF_HEAPS_SYSTEM=y
CONFIG_UDMABUF=y
CONFIG_IA32_EMULATION=y
如果你的 OpenWrt kernel 版本不是 6.12,请按对应版本的 config 文件修改。本文只验证过 6.12 系列。
3.3 重要坑:OpenWrt 的 DMA-BUF debloat patch
OpenWrt 有一个 patch 可能影响 DMA-BUF heaps:
target/linux/generic/hack-6.12/904-debloat_dma_buf.patch
我们遇到过这样的情况:
- 内核配置已经有
CONFIG_DMABUF_HEAPS_SYSTEM=y。 - 但是
/dev/dma_heap/system 仍然没有出现。 - 追查后发现
system_heap.o 没有链接进最终内核。 - 原因是这个 patch 改写了
drivers/dma-buf/heaps/Makefile。
验证通过的处理方式是:保留 OpenWrt 对 DMA_SHARED_BUFFER 的模块化改动,但不要改写:
drivers/dma-buf/heaps/Makefile
也就是说,904-debloat_dma_buf.patch 里不应再包含类似这一段:
---- a/drivers/dma-buf/heaps/Makefile
-+++ b/drivers/dma-buf/heaps/Makefile
-@@ -1,3 +1,3 @@
- # SPDX-License-Identifier: GPL-2.0
--obj-$(CONFIG_DMABUF_HEAPS_SYSTEM) += system_heap.o
--obj-$(CONFIG_DMABUF_HEAPS_CMA) += cma_heap.o
-+dma-buf-objs-$(CONFIG_DMABUF_HEAPS_SYSTEM) += system_heap.o
-+dma-buf-objs-$(CONFIG_DMABUF_HEAPS_CMA) += cma_heap.o
修改后重新编译内核,再确认 /dev/dma_heap/system。
3.4 构建内核
示例命令:
cd /path/to/openwrt-source
make target/linux/clean
make -j"$(nproc)" target/linux/compile V=s
我们为了避免和系统原模块目录混用,给 Waydroid 内核使用过单独 release 名称,例如:
6.12.74-waydroid-v10
这需要 OpenWrt build system 额外支持 WAYDROID_KERNEL_RELEASE。如果你没有做这个本地改动,就不要直接照抄这个变量。核心原则是:
运行中的 uname -r 必须和 /lib/modules/<uname -r> 匹配。
3.5 部署内核,并修改 GRUB 配置
这一节以常见 OpenWrt x86_64 安装为例。我们验证过的机器是:
/boot 是单独 vfat 分区
GRUB 配置文件:/boot/grub/grub.cfg
内核文件:/boot/vmlinuz 或 /boot/vmlinuz-自定义名字
模块目录:/lib/modules/<uname -r>
你的机器可能略有不同。请先确认:
mount | grep ' /boot '
ls -lah /boot /boot/grub 2>/dev/null || true
sed -n '1,220p' /boot/grub/grub.cfg 2>/dev/null || true
cat /proc/cmdline
如果 GRUB 配置不在 /boot/grub/grub.cfg,请按你机器实际文件改。下面以 /boot/grub/grub.cfg 为例。
3.5.1 准备部署包
假设你已经在 OpenWrt build tree 编译出:
vmlinuz-waydroid
/lib/modules/<WAYDROID_KERNEL_RELEASE>/
推荐给 Waydroid 内核一个明确名字,例如:
WAYDROID_KERNEL_RELEASE=6.12.74-waydroid-v10
/boot/vmlinuz-waydroid-v10
/lib/modules/6.12.74-waydroid-v10/
核心原则:
GRUB 启动的 kernel 文件、uname -r、/lib/modules/<uname -r> 必须互相匹配。
3.5.2 复制 kernel 和 modules 到 OpenWrt
在 OpenWrt 上先备份:
TS=$(date +%Y%m%d-%H%M%S)
mkdir -p /root/kernel-backup-$TS
cp -a /boot/grub/grub.cfg /root/kernel-backup-$TS/grub.cfg 2>/dev/null || true
cp -a /boot/vmlinuz /root/kernel-backup-$TS/vmlinuz.original 2>/dev/null || true
如果你用 scp 从构建机传文件,示例:
# 在构建机执行,按你的实际文件位置替换
scp /path/to/vmlinuz-waydroid-v10 root@<OPENWRT_HOST>:/boot/vmlinuz-waydroid-v10
scp -r /path/to/lib/modules/6.12.74-waydroid-v10 root@<OPENWRT_HOST>:/lib/modules/
在 OpenWrt 上确认:
ls -lah /boot/vmlinuz-waydroid-v10
ls -ld /lib/modules/6.12.74-waydroid-v10
3.5.3 不要覆盖原始 OpenWrt 启动项
不要直接把原来的 menuentry "OpenWrt" 改没。推荐做法是新增一个 OpenWrt-Waydroid 启动项,并保留原始 OpenWrt 和 OpenWrt (failsafe)。
先取当前 root 参数。最稳妥的方法是看现有 grub.cfg 里的 root=...:
grep -o 'root=[^ ]*' /boot/grub/grub.cfg | head -1
也可以看当前内核命令行:
cat /proc/cmdline
常见形式类似:
root=PARTUUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
3.5.4 编辑 /boot/grub/grub.cfg
示例 GRUB 配置:
serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1 --rtscts=off
terminal_input console serial; terminal_output console serial
set default="0"
set timeout="3"
search -l kernel -s root
menuentry "OpenWrt-Waydroid" {
linux /boot/vmlinuz-waydroid-v10 root=PARTUUID=替换成你的ROOT_PARTUUID rootwait console=tty1 console=ttyS0,115200n8 noinitrd
}
menuentry "OpenWrt-original" {
linux /boot/vmlinuz root=PARTUUID=替换成你的ROOT_PARTUUID rootwait console=tty1 console=ttyS0,115200n8 noinitrd
}
menuentry "OpenWrt-original-failsafe" {
linux /boot/vmlinuz failsafe=true root=PARTUUID=替换成你的ROOT_PARTUUID rootwait console=tty1 console=ttyS0,115200n8 noinitrd
}
注意:
search -l kernel -s root 是我们实机上的 OpenWrt x86 GRUB 写法。它依赖 /boot 分区 label 是 kernel。如果你的 /boot 分区 label 不同,请按原始 grub.cfg 保留原来的 search 行。root=PARTUUID=... 请从原始 grub.cfg 复制,不要猜。set timeout="3" 比 1 更适合调试,给你一点时间在串口/显示器上选择回退项。cgroup_enable=memory cgroup_memory=1 在我们后来的 6.12 / cgroup2 环境里不是关键项,内核还会提示 unknown kernel command line parameters。通用教程不建议新增它们。若你原配置已有,先不要随意删除;但新 Waydroid entry 可以不加。
可以用脚本方式追加 entry,但手工编辑前请备份:
cp -a /boot/grub/grub.cfg /boot/grub/grub.cfg.pre-waydroid-$(date +%Y%m%d-%H%M%S)
vi /boot/grub/grub.cfg
sync
3.5.5 重启前检查
重启前确认:
ls -lah /boot/vmlinuz-waydroid-v10
ls -ld /lib/modules/6.12.74-waydroid-v10
grep -nA8 -B2 'OpenWrt-Waydroid' /boot/grub/grub.cfg
sync
如果是远程路由器,请确保你有回退手段:
- 串口
- HDMI + 键盘
- IPMI / 远程 KVM
- 或确认 GRUB 保留原始启动项且 timeout 足够选择
不要在没有回退手段时盲目改默认启动项。
3.5.6 重启并验证
重启:
reboot
回来后验证:
uname -r
ls -ld /lib/modules/$(uname -r)
ls -ld /dev/dri /dev/dma_heap /dev/dma_heap/system 2>/dev/null || true
grep -E 'dma_heap|udmabuf' /proc/misc /proc/devices 2>/dev/null || true
cat /proc/cmdline
期望:
uname -r = 你的 WAYDROID_KERNEL_RELEASE
/lib/modules/$(uname -r) 存在
/dev/dri 存在
/dev/dma_heap/system 存在
注意:/proc/cmdline 里的 BOOT_IMAGE=/boot/xxx 只是 GRUB 加载的文件名。远程实机里曾出现过文件名还像旧名字、但 uname -r 已经是新 release 的情况。判断是否部署成功,请优先看 uname -r、/lib/modules/$(uname -r)、/dev/dma_heap/system、GPU firmware/DRM 初始化日志,不要只靠 kernel 文件名。
如果启动失败,请从 GRUB 选择原始 OpenWrt-original 或 failsafe 项回退,再检查:
- kernel 文件名是否写错
root=PARTUUID=... 是否复制错/lib/modules/<uname -r> 是否缺失- OpenWrt DMA-BUF patch 是否导致
/dev/dma_heap/system 没出现
3.6 OpenWrt 宿主运行时前置检查:LXC 工具、GPU 固件和设备节点
内核启动后,请先把这些运行时前置项查完,再创建 Fedora LXC。它们不是 Android 配置问题,漏掉时后面会表现成 KWin、Waydroid 或镜像挂载失败。
3.6.1 LXC 工具和容器存放目录
确认 OpenWrt 上有 LXC 工具:
command -v lxc-start
command -v lxc-attach
command -v lxc-stop
command -v lxc-info
如果没有,请按你的 OpenWrt 包管理器安装。新 OpenWrt 可能是 apk,旧环境可能是 opkg。包名会随发行版变化,请用包搜索确认;我们记录里用过的方向是安装 LXC 主程序、模板和 start/attach/stop/info 这些命令。
本文示例把外层 Fedora LXC 放在:
/srv/lxc/waydroid
有些 OpenWrt 的 LXC 默认容器目录不是 /srv/lxc。如果你执行:
lxc-start -n waydroid
提示找不到容器,请不要改 rootfs。先显式指定 LXC 容器目录:
lxc-start -P /srv/lxc -n waydroid
lxc-attach -P /srv/lxc -n waydroid -- /bin/bash
lxc-info -P /srv/lxc -n waydroid
lxc-stop -P /srv/lxc -n waydroid
后文为了简洁,仍写成 lxc-start -n waydroid。如果你的机器需要 -P /srv/lxc,请所有外层 LXC 命令都加上它。
3.6.2 GPU firmware 不要漏:OpenWrt 通常没有完整 firmware
OpenWrt 系统通常不会自带完整的 GPU firmware。内核里有 DRM/GPU 驱动,不等于 GPU 可以正常初始化。这里不要只理解成 Intel i915;不同机器要按实际 GPU 和 dmesg 里请求的文件来处理。
常见例子:
Intel 核显:/lib/firmware/i915/...
AMD GPU: /lib/firmware/amdgpu/...
其他 GPU:按内核日志里 request 的 firmware 文件名准备
我们这台实机是 Intel iGPU,所以后面的具体命令以 i915 为例。通用原则是:在 Fedora LXC 或另一台 Linux 机器里安装对应 firmware 包,把 GPU 驱动实际请求的 firmware 文件复制到 OpenWrt 宿主的 /lib/firmware/...,然后重启 OpenWrt 让 GPU 驱动重新初始化。
现场观察到的事实是:
Fedora LXC:
linux-firmware-20260410-1.fc44.noarch
intel-gpu-firmware-20260410-1.fc44.noarch
/lib/firmware/i915/*.bin.xz
OpenWrt 宿主:
/lib/firmware/i915/*.bin
其中 tgl_guc_70.bin 是后来补进去的 firmware
我们早期遇到过 i915 GuC firmware 加载失败,GPU 被内核标记为 wedged。Fedora 里 Mesa/Iris 可能会报一个容易误导的错误:
Kernel is too old (4.16+ required) or unusable for Iris
当时宿主 dmesg 里的关键事实是类似:
i915 ... GuC firmware i915/tgl_guc_70.bin: fetch failed
i915 ... GuC initialization failed
i915 ... Failed to initialize GPU, declaring it wedged
所以部署时请检查 OpenWrt 宿主:
dmesg | grep -i -E 'drm|gpu|i915|amdgpu|nouveau|xe|guc|huc|dmc|firmware|wedged|reset' | tail -160
ls -lah /lib/firmware 2>/dev/null | head -80
ls -lah /lib/firmware/i915 /lib/firmware/amdgpu 2>/dev/null | head -120 || true
ls -ld /dev/dri /dev/dri/renderD128 2>/dev/null || true
如果看到 firmware 缺失、GPU wedged、GPU reset 失败,或 DRM render node 没有出现,请先补齐对应 GPU firmware,再重启 OpenWrt。不要先去改 Waydroid、Mesa 或 Android MediaCodec。
从 Fedora LXC 复制 GPU firmware 到 OpenWrt
如果你已经有 Fedora LXC,请先进入 Fedora 安装 firmware 包。Intel 机器可以安装:
# Fedora LXC 内,Intel 例子
dnf install -y linux-firmware intel-gpu-firmware
rpm -q linux-firmware intel-gpu-firmware
find /lib/firmware/i915 /usr/lib/firmware/i915 -maxdepth 1 -type f 2>/dev/null | grep -E 'guc|huc|dmc' | head -80
如果是 AMD 或其它 GPU,请安装 Fedora 中对应的 firmware 包,并按 dmesg 请求的目录复制,例如 amdgpu/。本文没有验证非 Intel GPU,不能把 i915 文件名照搬到其它硬件。
然后回到 OpenWrt 宿主,从 Fedora rootfs 复制并解压。下面命令假设 Fedora rootfs 是 /srv/lxc/waydroid/rootfs,并以 Intel i915 为例:
# OpenWrt 宿主执行,Intel i915 例子
FEDORA_ROOTFS=/srv/lxc/waydroid/rootfs
FW_SUBDIR=i915
mkdir -p /lib/firmware/$FW_SUBDIR
# 优先复制压缩的 Fedora firmware,并在 OpenWrt 上解压成内核会请求的 .bin 文件。
# BusyBox xz/unxz 不一定安装;如果没有 unxz,可以在 Fedora 里先解压后再复制。
for srcdir in "$FEDORA_ROOTFS/lib/firmware/$FW_SUBDIR" "$FEDORA_ROOTFS/usr/lib/firmware/$FW_SUBDIR"; do
[ -d "$srcdir" ] || continue
find "$srcdir" -maxdepth 1 -type f \( -name '*.bin.xz' -o -name '*.bin' \) -print | while read -r f; do
base=$(basename "$f")
case "$base" in
*.bin.xz)
out=/lib/firmware/$FW_SUBDIR/${base%.xz}
if command -v unxz >/dev/null 2>&1; then
unxz -c "$f" > "$out"
elif command -v xz >/dev/null 2>&1; then
xz -dc "$f" > "$out"
else
echo "missing xz/unxz, cannot decompress $f" >&2
fi
;;
*.bin)
cp -f "$f" /lib/firmware/$FW_SUBDIR/
;;
esac
done
done
ls -lah /lib/firmware/$FW_SUBDIR | head -80
sync
如果你的 GPU 不是 Intel,请把 FW_SUBDIR=i915 换成你的 GPU firmware 目录,例如 FW_SUBDIR=amdgpu,并确认文件名和 dmesg 请求的一致。
如果 OpenWrt 没有 xz / unxz,也可以在 Fedora LXC 内先解压到临时目录,再从 OpenWrt 宿主复制解压后的 .bin。Intel 例子:
# Fedora LXC 内,Intel i915 例子
mkdir -p /root/gpu-fw-unpacked/i915
find /lib/firmware/i915 /usr/lib/firmware/i915 -maxdepth 1 -type f -name '*.bin.xz' 2>/dev/null | while read -r f; do
xz -dc "$f" > /root/gpu-fw-unpacked/i915/$(basename "${f%.xz}")
done
# OpenWrt 宿主
mkdir -p /lib/firmware/i915
cp -f /srv/lxc/waydroid/rootfs/root/gpu-fw-unpacked/i915/*.bin /lib/firmware/i915/
sync
完成后重启 OpenWrt,让 GPU 驱动在启动时重新加载 firmware:
reboot
重启后看 dmesg。我们当前 Intel 实机上曾观察到类似这些成功事实:
i915 ... Finished loading DMC firmware i915/adlp_dmc.bin
i915 ... GT0: GuC firmware i915/tgl_guc_70.bin version 70.49.4
i915 ... GT0: GUC: submission enabled
i915 ... GT0: GUC: SLPC enabled
i915 ... GT0: GUC: RC enabled
注意边界:当前 Intel 实机日志里曾还有 tgl_huc.bin 获取失败,但 GuC 已启用,GPU 没有 wedged,Waydroid 图形和后续验证可以继续。其他 GPU 请以你的 dmesg 为准。
如果你的 Fedora LXC 还没创建,也可以先用另一台 Fedora/Linux 机器安装对应 firmware 包,把 GPU 驱动请求的 firmware 文件复制到 OpenWrt。关键不是来源必须是 Fedora LXC,而是 OpenWrt 宿主的 /lib/firmware/... 里要有 GPU 驱动实际请求的 firmware。
3.6.3 loop、uinput、input、snd 设备
Waydroid 镜像挂载需要 loop 设备;输入和音频需要 /dev/uinput、/dev/input、/dev/snd。请在 OpenWrt 宿主先检查:
ls -l /dev/loop-control /dev/loop0 2>/dev/null || true
ls -ld /dev/uinput /dev/input /dev/snd 2>/dev/null || true
如果缺 loop 设备,先加载或安装 OpenWrt 的 loop 内核模块:
modprobe loop 2>/dev/null || true
ls -l /dev/loop-control /dev/loop0 2>/dev/null || true
如果缺输入设备,检查 uinput、evdev、HID 相关模块:
modprobe uinput 2>/dev/null || true
modprobe evdev 2>/dev/null || true
ls -ld /dev/uinput /dev/input 2>/dev/null || true
如果需要 HDMI/声卡音频,检查 ALSA/HDA 相关模块:
modprobe snd-hda-intel 2>/dev/null || true
ls -ld /dev/snd 2>/dev/null || true
包名同样按你的 OpenWrt 包源搜索。我们记录里出现过的方向包括 kmod-loop、kmod-uinput、kmod-input-evdev、kmod-hid、kmod-usb-hid、kmod-sound-hda-intel、kmod-sound-hda-codec-hdmi。这些名字只作为搜索提示,不保证每个 OpenWrt 分支完全一致。
4. 创建外层 Fedora 44 LXC
4.1 rootfs 创建说明
我们的记录没有完整保存最初创建 Fedora 44 rootfs 的每一条命令。所以本文不把某个 rootfs 生成命令写成唯一答案。
如果你的 OpenWrt LXC download template 可用,通用做法通常类似下面这样。请先用 --list 确认模板里确实有 Fedora 44,再执行创建:
# 可选:查看 download template 支持的 Fedora 版本
lxc-create -n list-test -t download -- --list | grep -i fedora || true
# 示例:创建 Fedora 44 amd64 rootfs
lxc-create -P /srv/lxc -n waydroid -t download -- --dist fedora --release 44 --arch amd64
如果你的 OpenWrt 没有 lxc-create、没有 download template,或模板源不可用,请使用你现场可用的标准方式准备 Fedora 44 rootfs。最终只要放到类似:
/srv/lxc/waydroid/rootfs
最低要求:
架构:x86_64
发行版:Fedora 44
容器类型:privileged LXC
init:systemd
网络:有 eth0,可以接 OpenWrt br-lan
创建完成后,从 OpenWrt 进入 Fedora:
lxc-start -n waydroid
lxc-attach -n waydroid -- /bin/bash
cat /etc/fedora-release
期望:
Fedora release 44
4.2 外层 LXC 配置:分两阶段写,先安全初始化,再正式运行
请不要第一次启动就直接使用桥接网络和 /sys:rw。建议分两阶段。
阶段 A:首次启动安全配置
编辑 OpenWrt 上的:
/srv/lxc/waydroid/config
首次启动建议用这个最小安全配置:
lxc.arch = x86_64
lxc.rootfs.path = dir:/srv/lxc/waydroid/rootfs
lxc.uts.name = waydroid
lxc.tty.max = 2
lxc.pty.max = 1024
# 当前验证的是 privileged 容器。
lxc.seccomp.profile =
lxc.cap.drop =
# 首次启动不要桥接,不要共享宿主网络。
lxc.net.0.type = empty
# 首次启动保留 /sys 只读,先避免 Fedora udev/networkd 改动宿主网络设备。
lxc.mount.auto = proc:mixed sys:ro cgroup:mixed
# 设备透传可以先写好。
lxc.mount.entry = /dev/dri dev/dri none bind,create=dir 0 0
lxc.mount.entry = /dev/dma_heap dev/dma_heap none bind,create=dir,optional 0 0
lxc.mount.entry = /dev/uinput dev/uinput none bind,create=file 0 0
lxc.mount.entry = /dev/input dev/input none bind,create=dir 0 0
lxc.mount.entry = /dev/snd dev/snd none bind,create=dir 0 0
lxc.mount.entry = tmpfs dev/shm tmpfs create=dir,mode=01777,nosuid,nodev 0 0
# binderfs:可选。当前远程环境主要由 Fedora 内的 Waydroid helper 挂载/创建 binderfs。
# 如果你选择在 OpenWrt 宿主挂载 binderfs,也可以用这行 optional bind 传入 Fedora。
lxc.mount.entry = /dev/binderfs dev/binderfs none bind,create=dir,optional 0 0
# loop 设备:Waydroid 镜像挂载需要。
lxc.mount.entry = /dev/loop-control dev/loop-control none bind,create=file 0 0
lxc.mount.entry = /dev/loop0 dev/loop0 none bind,create=file 0 0
lxc.mount.entry = /dev/loop1 dev/loop1 none bind,create=file 0 0
lxc.mount.entry = /dev/loop2 dev/loop2 none bind,create=file 0 0
lxc.mount.entry = /dev/loop3 dev/loop3 none bind,create=file 0 0
# 给内层 Waydroid LXC 使用的辅助挂载点。
lxc.mount.entry = proc dev/.lxc/proc proc create=dir,optional 0 0
lxc.mount.entry = sys dev/.lxc/sys sysfs create=dir,optional 0 0
启动并进入容器:
lxc-start -n waydroid
lxc-attach -n waydroid -- /bin/bash
在 Fedora 里做三件事:
# 1. 禁用会接管网络的服务
systemctl mask systemd-networkd.service systemd-networkd.socket systemd-networkd-wait-online.service 2>/dev/null || true
systemctl disable --now systemd-networkd.service systemd-networkd.socket systemd-networkd-wait-online.service 2>/dev/null || true
systemctl mask systemd-resolved.service firewalld.service NetworkManager.service wpa_supplicant.service 2>/dev/null || true
systemctl disable --now systemd-resolved.service firewalld.service NetworkManager.service wpa_supplicant.service 2>/dev/null || true
# 2. 禁用网卡自动重命名
mkdir -p /etc/systemd/network
cat > /etc/systemd/network/10-keep-kernel-ifnames.link <<'EOF'
[Match]
OriginalName=*
[Link]
NamePolicy=keep
MACAddressPolicy=none
EOF
ln -sfn /dev/null /etc/systemd/network/99-default.link
# 3. 如果你要最保守,先屏蔽 udev。后面图形会话需要 udev 数据库时,再有控制地恢复。
systemctl mask systemd-udevd.service systemd-udevd-control.socket systemd-udevd-kernel.socket 2>/dev/null || true
systemctl disable --now systemd-udevd.service systemd-udevd-control.socket systemd-udevd-kernel.socket 2>/dev/null || true
退出并停止容器:
exit
lxc-stop -n waydroid
阶段 B:正式运行配置
完成阶段 A 后,再把外层 LXC 改成正式运行配置:
lxc.arch = x86_64
lxc.rootfs.path = dir:/srv/lxc/waydroid/rootfs
lxc.uts.name = waydroid
lxc.tty.max = 2
lxc.pty.max = 1024
lxc.seccomp.profile =
lxc.cap.drop =
# 正式运行:用 veth 接 OpenWrt 的 br-lan。不要用 none 共享宿主网络。
lxc.net.0.type = veth
lxc.net.0.veth.pair = lxc-waydroid
lxc.net.0.link = br-lan
lxc.net.0.flags = up
lxc.net.0.hwaddr = 00:16:3e:59:af:02
# 正式运行:Waydroid / nested LXC 需要可写 sysfs/cgroup 视图。
lxc.mount.auto = proc:mixed sys:rw cgroup:mixed
lxc.mount.entry = /dev/dri dev/dri none bind,create=dir 0 0
lxc.mount.entry = /dev/dma_heap dev/dma_heap none bind,create=dir,optional 0 0
lxc.mount.entry = /dev/uinput dev/uinput none bind,create=file 0 0
lxc.mount.entry = /dev/input dev/input none bind,create=dir 0 0
lxc.mount.entry = /dev/snd dev/snd none bind,create=dir 0 0
lxc.mount.entry = tmpfs dev/shm tmpfs create=dir,mode=01777,nosuid,nodev 0 0
# binderfs:可选。当前远程环境主要由 Fedora 内的 Waydroid helper 挂载/创建 binderfs。
# 如果你选择在 OpenWrt 宿主挂载 binderfs,也可以用这行 optional bind 传入 Fedora。
lxc.mount.entry = /dev/binderfs dev/binderfs none bind,create=dir,optional 0 0
lxc.mount.entry = /dev/loop-control dev/loop-control none bind,create=file 0 0
lxc.mount.entry = /dev/loop0 dev/loop0 none bind,create=file 0 0
lxc.mount.entry = /dev/loop1 dev/loop1 none bind,create=file 0 0
lxc.mount.entry = /dev/loop2 dev/loop2 none bind,create=file 0 0
lxc.mount.entry = /dev/loop3 dev/loop3 none bind,create=file 0 0
lxc.mount.entry = proc dev/.lxc/proc proc create=dir,optional 0 0
lxc.mount.entry = sys dev/.lxc/sys sysfs create=dir,optional 0 0
如果你的 OpenWrt 主机没有 /dev/dma_heap/system,先回到第 3 节修内核。
4.3 验证外层容器设备
启动 Fedora LXC:
lxc-start -n waydroid
lxc-attach -n waydroid -- /bin/bash -lc '
cat /etc/fedora-release
ls -ld /dev/dri /dev/dma_heap /dev/dma_heap/system /dev/uinput /dev/input /dev/snd /dev/binderfs 2>/dev/null || true
ls -l /dev/binderfs/binder /dev/binderfs/hwbinder /dev/binderfs/vndbinder 2>/dev/null || true
'
必须看到 /dev/dri 和 /dev/dma_heap/system。/dev/binderfs 可能要等 Waydroid container helper 运行后才出现;如果后面 Waydroid 报 binder service manager 不出现,再确认 Fedora 里是否已经有 /dev/binderfs/binder、/dev/binderfs/hwbinder、/dev/binderfs/vndbinder。
4.4 给 Fedora 一个最小可用网络:不用 NetworkManager/networkd
这一节很容易漏。前面我们已经把 NetworkManager / systemd-networkd / systemd-resolved 都 mask 掉了,所以 Fedora LXC 不会自己给 eth0 配 IP。可是第 5 节马上要 dnf install,这时 Fedora 必须先能联网。
最简单的做法是先手动配置一次:
# Fedora LXC 内
ip link set lo up || true
ip link set eth0 up
ip addr replace 192.168.1.237/24 dev eth0 # 按你的 FEDORA_LAN_ADDR 改
ip route replace default via 192.168.1.1 dev eth0
rm -f /etc/resolv.conf
echo 'nameserver 192.168.1.1' > /etc/resolv.conf
chmod 0644 /etc/resolv.conf
ping -c 2 192.168.1.1
curl -I https://mirrors.fedoraproject.org/ 2>/dev/null | head || true
我们当前远程环境还保留了一个很小的 systemd oneshot,用来在启动早期做同样的安全网络初始化。它不启用 NetworkManager,也不启用 systemd-networkd。你可以按自己的 LAN 改成变量化版本:
文件:
/usr/local/bin/waydroid-lxc-network-safe.sh
内容:
#!/bin/bash
set -euo pipefail
PHY=${WAYDROID_PARENT_IF:-eth0}
ADDR=${WAYDROID_LXC_IPV4:-192.168.1.237/24}
GW=${WAYDROID_LXC_GW:-192.168.1.1}
DNS=${WAYDROID_LXC_DNS:-$GW}
ip link set lo up || true
ip link set "$PHY" up
if [ -n "$ADDR" ]; then
ip addr replace "$ADDR" dev "$PHY"
fi
if [ -n "$GW" ]; then
ip route replace default via "$GW" dev "$PHY" || true
fi
rm -f /etc/resolv.conf
printf 'nameserver %s\n' "$DNS" > /etc/resolv.conf
chmod 0644 /etc/resolv.conf
授权并写入 service:
chmod 0755 /usr/local/bin/waydroid-lxc-network-safe.sh
cat > /etc/systemd/system/waydroid-lxc-network-safe.service <<'EOF'
[Unit]
Description=Minimal safe network setup for Waydroid Fedora LXC
DefaultDependencies=no
After=local-fs.target
Before=network-pre.target network.target waydroid-container.service
Wants=network-pre.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/waydroid-lxc-network-safe.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
mkdir -p /etc/systemd/system/waydroid-lxc-network-safe.service.d
cat > /etc/systemd/system/waydroid-lxc-network-safe.service.d/10-lan.conf <<EOF
[Service]
Environment=WAYDROID_LXC_IPV4=192.168.1.237/24
Environment=WAYDROID_LXC_GW=192.168.1.1
Environment=WAYDROID_LXC_DNS=192.168.1.1
EOF
systemctl daemon-reload
systemctl enable --now waydroid-lxc-network-safe.service
再给 waydroid-container.service 加一个 drop-in,确保手动或自动启动 Waydroid container 时,都会先跑这个最小网络 service:
mkdir -p /etc/systemd/system/waydroid-container.service.d
cat > /etc/systemd/system/waydroid-container.service.d/20-after-lxc-network-safe.conf <<'EOF'
[Unit]
Wants=waydroid-lxc-network-safe.service
After=waydroid-lxc-network-safe.service
EOF
systemctl daemon-reload
如果后面要启用第 9 节的 waydroid-bridge-setup.service,它会把 eth0 放进 br0,并把地址迁到 br0。这个早期 service 的作用只是保证 Fedora 在没有网络管理器时也能先安装包、解析 DNS,并且让 Waydroid container 启动前网络状态是可预期的。
5. Fedora LXC 内安装基础软件
进入 Fedora:
lxc-attach -n waydroid -- /bin/bash
安装工具和图形/媒体相关包。包名来自我们当前 Fedora 44 实机中已经存在的组件。
先启用 RPM Fusion。原因是 Fedora 默认仓库里的 VAAPI / Mesa / 编解码包会避开一部分有专利或授权限制的编解码能力;我们当前实机启用了 RPM Fusion free/nonfree,并安装了 mesa-va-drivers-freeworld 等包。
dnf install -y \
https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm \
https://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm
dnf repolist --enabled | grep -Ei 'rpmfusion|fedora|updates' || true
然后安装基础软件和图形/媒体包:
dnf install -y \
git curl unzip lzip make gcc gcc-c++ \
python3 python3-devel python3-setuptools python3-Cython python3-pip python3-virtualenv \
lxc lxc-templates lxcfs \
android-tools \
linux-firmware intel-gpu-firmware \
mesa-dri-drivers mesa-libgbm mesa-libEGL mesa-libGL mesa-vulkan-drivers mesa-va-drivers-freeworld \
libva libva-utils libva-intel-media-driver intel-media-driver intel-mediasdk \
ffmpeg-free \
dbus dbus-broker dbus-daemon dbus-tools dbus-x11 \
pipewire pipewire-pulseaudio pipewire-utils wireplumber \
lxqt-session lxqt-wayland-session lxqt-sway-session lxqt-panel lxqt-notificationd lxqt-policykit lxqt-runner lxqt-powermanagement pcmanfm-qt qterminal \
kwin plasma-workspace-common xdg-desktop-portal xdg-desktop-portal-kde \
seatd acl Sunshine
如果某个包名在你的 Fedora 仓库里不同,请用 Fedora 的包搜索确认。不要启用 NetworkManager / firewalld 来解决这个部署里的网络问题。
关于 VAAPI 解码器:
mesa-va-drivers-freeworld 是 Fedora 上常见的“更完整但不那么 free”的 Mesa VAAPI 包,主要影响 Mesa VAAPI 驱动暴露的编解码能力。- Intel 实机还需要
intel-media-driver / libva-intel-media-driver,当前现场 vainfo 使用的是 /usr/lib64/dri-nonfree/iHD_drv_video.so。 - 不同 GPU 要按实际驱动选择包。Intel iGPU 看 iHD/i965,AMD 通常看 Mesa VAAPI/freeworld,不能把 Intel 包照搬成唯一答案。
安装后建议验证:
rpm -qa | grep -Ei 'rpmfusion|libva|vaapi|intel-media|mesa.*va|ffmpeg|openh264' | sort
vainfo --display drm --device /dev/dri/renderD128 2>&1 | tee /root/vainfo-drm.log
grep -E 'VAProfileH264|VAProfileHEVC|VAProfileVP9|VAProfileAV1' /root/vainfo-drm.log || true
我们当前实机能看到 VAProfileHEVCMain10、VAProfileVP9Profile2、VAProfileAV1Profile0。这些事实只说明 Fedora/VAAPI 层暴露了对应 profile;Android MediaCodec/Codec2 是否使用它,还要看后面 Bonus 章节的 Android logcat。
这里安装 linux-firmware / intel-gpu-firmware 不只是给 Fedora 自己用。OpenWrt 宿主通常没有完整 GPU firmware,前面第 3.6.2 节会用 Fedora rootfs 里的 firmware 文件作为来源,复制并解压到 OpenWrt 的 /lib/firmware/...。intel-gpu-firmware 是当前 Intel 实机的例子;其它 GPU 请安装对应 firmware 包。
禁用不需要的网络管理服务:
systemctl mask NetworkManager systemd-networkd systemd-resolved firewalld wpa_supplicant 2>/dev/null || true
systemctl disable --now NetworkManager systemd-networkd systemd-resolved firewalld wpa_supplicant 2>/dev/null || true
验证:
for s in NetworkManager systemd-networkd systemd-resolved firewalld wpa_supplicant; do
printf '%s: ' "$s"
systemctl is-active "$s" 2>/dev/null || true
done
期望都是 inactive。
6. 安装 upstream Waydroid / libgbinder / gbinder-python
Fedora 包里的 Waydroid/libgbinder 版本记录为:
waydroid RPM metadata: 1.6.2
libgbinder RPM metadata: 1.1.43
python3-gbinder RPM metadata: 1.3.0
但 Android 16 / API 36 需要 Waydroid 正确使用:
binder_protocol = aidl3
service_manager_protocol = aidl6
我们验证过用 upstream source 覆盖安装。有效 commits:
Waydroid: 73fed11b465138130eb7067c9645db81b32ac929
libgbinder: 680bedcc335586502980f9a28614416352e6f369
gbinder-python: 86b8feba4cacd0952b010d1c3af6a29a0c146ced
在 Fedora LXC 内执行:
systemctl --user -M user@ stop waydroid-full-lxqt.service 2>/dev/null || true
waydroid session stop 2>/dev/null || true
systemctl stop waydroid-container.service 2>/dev/null || true
mkdir -p /usr/local/src
git clone https://github.com/waydroid/waydroid.git /usr/local/src/waydroid-upstream || true
cd /usr/local/src/waydroid-upstream
git fetch --prune origin
git checkout 73fed11b465138130eb7067c9645db81b32ac929
make install PREFIX=/usr USE_SYSTEMD=1 USE_DBUS_ACTIVATION=1
systemctl daemon-reload
git clone https://github.com/mer-hybris/libgbinder.git /usr/local/src/libgbinder-upstream || true
cd /usr/local/src/libgbinder-upstream
git fetch --prune origin --tags
git checkout 680bedcc335586502980f9a28614416352e6f369
make clean || true
make -j"$(nproc)" release pkgconfig
make install-dev PREFIX=/usr LIBDIR=/usr/lib64
ldconfig
git clone https://github.com/waydroid/gbinder-python.git /usr/local/src/gbinder-python-upstream || true
cd /usr/local/src/gbinder-python-upstream
git fetch --prune origin --tags
git checkout 86b8feba4cacd0952b010d1c3af6a29a0c146ced
python3 setup.py build_ext --inplace
python3 setup.py install
检查:
python3 - <<'PY'
import gbinder
print(gbinder.__file__)
PY
pkg-config --modversion libgbinder
期望类似:
/usr/local/lib64/python3.14/site-packages/gbinder.cpython-314-x86_64-linux-gnu.so
1.1.45
7. 安装 WayDroid-ATV 镜像
WayDroid-ATV 已经内置了 arm 转译层。不再需要手动运行脚本安装啦!
WayDroid-ATV 现在有比较人类友好的部署方式。推荐优先使用 Waydroid 命令从 WayDroid-ATV OTA server 初始化和下载镜像;手动下载 zip 只作为备用。
7.1 版本选择:新版 A16 vs 旧版 A13
推荐版本:
WayDroid-ATV Android 16 / Lineage 23.0 / waydroid_tv_x86_64
OTA: https://waydroid-atv.github.io/ota/a16-tv/system
https://waydroid-atv.github.io/ota/a16-tv/vendor
新版优点:
- 支持当前我们验证过的 VAAPI / Codec2 方向。
- 适合继续做 HEVC Main10 / P010 / Android media 测试。
新版注意点:
- 需要比较新的 Waydroid / libgbinder / gbinder-python。Fedora 包里的 Waydroid 1.6.2 metadata 不够说明实际能力。请按第 6 节拉 upstream source 自己编译安装。
- 如果
waydroid app list 卡住、waydroidplatform 不通,优先检查第 6 节的 service_manager_protocol = aidl6 和 libgbinder 版本。
旧版测试用镜像:
WayDroid-ATV Android 13 release 20260106
https://github.com/WayDroid-ATV/waydroid-androidtv-builds/releases/tag/20260106
system: lineage-20.0-20260105-GAPPS-waydroid_tv_x86_64-system.zip
vendor: lineage-20.0-20260105-MAINLINE-waydroid_tv_x86_64-vendor.zip
旧版适合用来验证“OpenWrt + Fedora LXC + Waydroid 能不能基本跑通”。但旧版不适合拿来验证当前视频硬解方案:它不具备我们后来验证的最新版 VAAPI / Codec2 / P010 行为。
7.2 方法一:用 waydroid init 接 WayDroid-ATV OTA server(推荐)
在 Fedora LXC 内执行。若你是 root,不需要 sudo;如果是普通用户,请按 Waydroid 常规方式加 sudo。
GApps 版本:
waydroid init -f \
-c https://waydroid-atv.github.io/ota/a16-tv/system \
-v https://waydroid-atv.github.io/ota/a16-tv/vendor \
-r lineage \
-s GAPPS
waydroid upgrade
如果你想要非 GApps build,把 GAPPS 换成 VANILLA:
waydroid init -f \
-c https://waydroid-atv.github.io/ota/a16-tv/system \
-v https://waydroid-atv.github.io/ota/a16-tv/vendor \
-r lineage \
-s VANILLA
waydroid upgrade
下载完成后确认:
sed -n '1,160p' /var/lib/waydroid/waydroid.cfg
ls -lah /var/lib/waydroid/images /etc/waydroid-extra/images 2>/dev/null || true
如果你希望固定使用 /etc/waydroid-extra/images,请确认:
images_path = /etc/waydroid-extra/images
否则按 waydroid.cfg 实际 images_path 继续,不要猜。
7.3 方法二:手动下载 zip 并安装
如果 OTA 下载不可用,可以手动下载 SourceForge 上的:
lineage-23.0-xxxxxxx-GAPPS-waydroid_tv_xxxxx-system.zip
lineage-23.0-xxxxxxx-MAINLINE-waydroid_tv_xxxxx-vendor.zip
如果想要非 GApps build,把 GAPPS 换成 VANILLA。
解压出:
system.img
vendor.img
复制到 Fedora LXC 内:
mkdir -p /etc/waydroid-extra/images/
cp /path/to/system.img /etc/waydroid-extra/images/system.img
cp /path/to/vendor.img /etc/waydroid-extra/images/vendor.img
然后重新初始化:
waydroid init -f
我们的早期 A16 实机记录使用过 release 20260403,zip sha256 是:
system zip: ddf5995f9104fe7164ad326def8bbed5cbf6340b55da5012e240b5855c44953b
vendor zip: 8b8b176c7d3b34719889f3c891d86b98af8dbbb2f737a970e89302fa3d0cf077
这只是我们当时验证的版本,不代表你部署时必须固定到这个日期。通用部署建议优先使用 7.2 的 OTA server。
7.4 全新初始化时的清理边界
如果是全新初始化,可以停止 Waydroid 后清理旧数据:
waydroid session stop 2>/dev/null || true
systemctl stop waydroid-container.service 2>/dev/null || true
rm -rf /home/user/.local/share/waydroid
rm -rf /var/lib/waydroid/overlay /var/lib/waydroid/overlay_rw /var/lib/waydroid/lxc
如果你是在维护已有环境,不要直接删除这些目录。overlay_rw/vendor 里可能有已经部署的 media/Codec2 修复文件。
7.5 配置 Waydroid 使用正确协议和镜像目录
确认 /var/lib/waydroid/waydroid.cfg 有:
[waydroid]
arch = x86_64
vendor_type = MAINLINE
mount_overlays = True
binder = binder
vndbinder = vndbinder
hwbinder = hwbinder
binder_protocol = aidl3
service_manager_protocol = aidl6
images_path 可以是 OTA 初始化生成的位置,也可以是:
images_path = /etc/waydroid-extra/images
请以你实际文件位置为准。
7.6 初始化后立刻禁用 Android 蓝牙
第一次能进入 Android shell 后,请按第 0.5 节禁用蓝牙:
waydroid shell -- sh -c '
setprop persist.bluetooth.disabled true
settings put global bluetooth_on 0
settings put global ble_scan_always_enabled 0
cmd bluetooth_manager disable 2>/dev/null || true
pm disable-user --user 0 com.android.bluetooth 2>/dev/null || true
pm disable com.android.bluetooth 2>/dev/null || true
'
不要等系统长时间运行后再处理。这个问题和 OpenWrt /tmp 是 tmpfs 叠在一起时,可能会变成内存压力和重启问题。
8. 配置 Fedora 图形会话:systemd user + LXQt + KWin
8.1 创建桌面用户,并加入图形/音频/输入需要的 groups
当前验证过的桌面用户状态是:
uid=1000(user) gid=1000(user) groups=1000(user),10(wheel),39(video),63(audio),104(input),105(render),992(seat)
这些 groups 很重要:
video / render:访问 DRM/KMS、render node。audio:访问 ALSA/PipeWire 相关音频设备。input:访问输入设备。seat:配合 seatd / 桌面会话管理座席设备。wheel:方便需要管理员权限时使用。
不同 Fedora rootfs 里的 group 数字可能不同,不要手写 gid。请按 group 名称添加。
如果还没有 user:
useradd -m -u 1000 user 2>/dev/null || true
确保必要 groups 存在,并把用户加入:
for g in wheel video audio input render seat; do
getent group "$g" >/dev/null || groupadd "$g"
usermod -aG "$g" user
done
loginctl enable-linger user
id user
期望 id user 至少包含:
wheel video audio input render seat
音频设备 ACL:
setfacl -m u:user:rw /dev/snd/* 2>/dev/null || true
8.2 写入 waydroid-full-lxqt.service
文件:
/home/user/.config/systemd/user/waydroid-full-lxqt.service
创建目录:
mkdir -p /home/user/.config/systemd/user
chown -R user:user /home/user/.config
写入:
[Unit]
Description=Full LXQt Wayland desktop session for Waydroid
After=dbus.socket pipewire.socket pipewire-pulse.socket
Wants=dbus.socket pipewire.socket pipewire-pulse.socket graphical-session.target
BindsTo=graphical-session.target
Before=graphical-session.target
[Service]
Type=simple
UnsetEnvironment=WLR_LIBINPUT_NO_DEVICES WAYLAND_DISPLAY DISPLAY SWAYSOCK I3SOCK
Environment=XDG_RUNTIME_DIR=/run/user/1000
Environment=XDG_SESSION_TYPE=wayland
Environment=LIBSEAT_BACKEND=noop
Environment=QT_QPA_PLATFORM=wayland
ExecStart=/usr/sbin/startlxqtwayland
Restart=on-failure
RestartSec=2s
[Install]
WantedBy=default.target
启用:
runuser -u user -- env \
XDG_RUNTIME_DIR=/run/user/1000 \
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus \
systemctl --user daemon-reload
runuser -u user -- env \
XDG_RUNTIME_DIR=/run/user/1000 \
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus \
systemctl --user enable --now waydroid-full-lxqt.service
8.3 Waydroid UI 启动脚本
当前验证过的最终方式是:waydroid-autostart-session.sh 和 sunshine 作为 LXQt 会话内进程运行,而不是作为单独 enabled user service 运行。
写入:
/usr/local/bin/waydroid-autostart-session.sh
内容:
#!/bin/bash
set -euo pipefail
if [ -z "${XDG_RUNTIME_DIR:-}" ] || [ -z "${DBUS_SESSION_BUS_ADDRESS:-}" ]; then
echo "XDG desktop session environment is incomplete" >&2
exit 1
fi
for _ in $(seq 1 90); do
[ -S "$XDG_RUNTIME_DIR/bus" ] && [ -n "${WAYLAND_DISPLAY:-}" ] && [ -S "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ] && break
sleep 1
done
if [ -z "${WAYLAND_DISPLAY:-}" ] || [ ! -S "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ]; then
echo "Wayland session environment is not ready" >&2
exit 1
fi
while true; do
if ! systemctl is-active --quiet waydroid-container.service; then
systemctl start waydroid-container.service || true
fi
if waydroid status 2>/dev/null | grep -Eq 'Session:[[:space:]]*RUNNING'; then
sleep 30
continue
fi
/usr/local/bin/waydroid-show-full-ui-after-start.sh >/tmp/waydroid-show-full-ui-after-start.log 2>&1 &
/usr/sbin/waydroid session start || true
sleep 5
done
写入:
/usr/local/bin/waydroid-show-full-ui-after-start.sh
内容:
#!/bin/bash
set -euo pipefail
if [ -z "${XDG_RUNTIME_DIR:-}" ] || [ -z "${DBUS_SESSION_BUS_ADDRESS:-}" ] || [ -z "${WAYLAND_DISPLAY:-}" ]; then
echo "XDG desktop session environment is incomplete" >&2
exit 1
fi
for _ in $(seq 1 90); do
if waydroid status 2>/dev/null | grep -Eq 'Container:[[:space:]]*RUNNING'; then
break
fi
sleep 1
done
sleep 5
/usr/sbin/waydroid show-full-ui || true
授权:
chmod 0755 /usr/local/bin/waydroid-autostart-session.sh /usr/local/bin/waydroid-show-full-ui-after-start.sh
你需要让 LXQt session 自己启动:
/usr/local/bin/waydroid-autostart-session.sh
/usr/sbin/sunshine
我们当前实机是由 LXQt session 拉起它们。不同 LXQt 配置可以用 autostart desktop 文件、LXQt session 设置或等效方式。重点是不要手动伪造不完整的 XDG session。
8.4 总启动脚本
写入:
/usr/local/bin/start-waydroid.sh
内容:
#!/bin/bash
set -euo pipefail
loginctl enable-linger user >/dev/null 2>&1 || true
systemctl daemon-reload
systemctl enable --now waydroid-bridge-setup.service waydroid-container.service
# 用户所属 groups 和设备权限请在第 8.1 节部署时一次性设置。
# 启动脚本不要每次反复 usermod / setfacl。
runuser -u user -- env \
XDG_RUNTIME_DIR=/run/user/1000 \
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus \
systemctl --user daemon-reload
runuser -u user -- env \
XDG_RUNTIME_DIR=/run/user/1000 \
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus \
systemctl --user enable --now waydroid-full-lxqt.service
runuser -u user -- env \
XDG_RUNTIME_DIR=/run/user/1000 \
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus \
systemctl --user --no-pager status waydroid-full-lxqt.service
授权:
chmod 0755 /usr/local/bin/start-waydroid.sh
9. 配置桥接网络
目标:让 Android 直接出现在 OpenWrt LAN 中,而不是使用默认 waydroid0 NAT。
最终结构:
OpenWrt br-lan
└── 外层 Fedora eth0
└── Fedora 内 br0
├── Fedora 自己的 IPv4 地址
└── Waydroid Android veth
└── Android eth0 通过 DHCP 获取 LAN 地址
9.1 Fedora 内 bridge setup 脚本
写入:
/usr/local/sbin/waydroid-bridge-setup
内容:
#!/usr/bin/env bash
set -euo pipefail
BR=${WAYDROID_BRIDGE:-br0}
PHY=${WAYDROID_PARENT_IF:-eth0}
DEFAULT_IPV4=${WAYDROID_BRIDGE_IPV4:-}
DEFAULT_GW=${WAYDROID_BRIDGE_GW:-}
if ! ip link show "$PHY" >/dev/null 2>&1; then
echo "parent interface $PHY does not exist" >&2
exit 1
fi
PHY_MAC=$(cat /sys/class/net/$PHY/address)
PHY_MTU=$(cat /sys/class/net/$PHY/mtu)
CURRENT4=$(ip -o -4 addr show dev "$BR" scope global 2>/dev/null | awk 'NR==1{print $4}' || true)
PHY4=$(ip -o -4 addr show dev "$PHY" scope global 2>/dev/null | awk 'NR==1{print $4}' || true)
GW4=$(ip -4 route show default dev "$BR" 2>/dev/null | awk 'NR==1{for(i=1;i<=NF;i++) if($i=="via") print $(i+1)}' || true)
PHY_GW4=$(ip -4 route show default dev "$PHY" 2>/dev/null | awk 'NR==1{for(i=1;i<=NF;i++) if($i=="via") print $(i+1)}' || true)
if ! ip link show "$BR" >/dev/null 2>&1; then
ip link add name "$BR" type bridge
fi
ip link set dev "$BR" address "$PHY_MAC" || true
ip link set dev "$BR" mtu "$PHY_MTU" || true
ip link set dev "$BR" type bridge stp_state 0 forward_delay 0 || true
ip link set dev "$BR" up
if [ "$(basename "$(readlink -f /sys/class/net/$PHY/master 2>/dev/null || true)" || true)" != "$BR" ]; then
ip addr flush dev "$PHY" || true
ip link set dev "$PHY" master "$BR"
fi
ip link set dev "$PHY" up
ADDR="${CURRENT4:-${PHY4:-$DEFAULT_IPV4}}"
GW="${GW4:-${PHY_GW4:-$DEFAULT_GW}}"
if [ -n "$ADDR" ] && ! ip -4 addr show dev "$BR" | grep -q "${ADDR%/*}"; then
ip addr add "$ADDR" dev "$BR" 2>/dev/null || true
fi
if [ -n "$GW" ]; then
ip route replace default via "$GW" dev "$BR" 2>/dev/null || true
fi
DNS="${WAYDROID_BRIDGE_DNS:-$GW}"
if [ -n "$DNS" ]; then
rm -f /etc/resolv.conf
printf 'nameserver %s\n' "$DNS" > /etc/resolv.conf
chmod 0644 /etc/resolv.conf
fi
ip link set dev "$BR" promisc on || true
授权:
chmod 0755 /usr/local/sbin/waydroid-bridge-setup
9.2 systemd 服务
写入:
/etc/systemd/system/waydroid-bridge-setup.service
内容:
[Unit]
Description=Prepare br0 bridge for bridged Waydroid Android container
DefaultDependencies=no
After=local-fs.target waydroid-lxc-network-safe.service
Before=waydroid-container.service
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/waydroid-bridge-setup
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
按你的 LAN 写入 drop-in:
mkdir -p /etc/systemd/system/waydroid-bridge-setup.service.d
cat > /etc/systemd/system/waydroid-bridge-setup.service.d/10-lan.conf <<EOF2
[Service]
Environment=WAYDROID_BRIDGE_IPV4=${FEDORA_LAN_ADDR:-192.168.1.237/24}
Environment=WAYDROID_BRIDGE_GW=${OPENWRT_LAN_GATEWAY:-192.168.1.1}
Environment=WAYDROID_BRIDGE_DNS=${OPENWRT_LAN_GATEWAY:-192.168.1.1}
EOF2
启用:
systemctl daemon-reload
systemctl enable --now waydroid-bridge-setup.service
9.3 修改 Waydroid inner LXC 网络模板
需要把 Waydroid inner LXC 从默认 waydroid0 改到 br0。涉及文件:
/var/lib/waydroid/lxc/waydroid/config
/usr/lib/waydroid/data/configs/config_3
/usr/lib/waydroid/data/configs/config_1
你要确认这些文件里的 Waydroid 网络配置使用:
lxc.net.0.type = veth
lxc.net.0.link = br0
lxc.net.0.flags = up
lxc.net.0.mtu = 1450
不同 Waydroid 版本模板内容可能略有不同。修改前请备份:
TS=$(date +%Y%m%d-%H%M%S)
mkdir -p /root/waydroid-bridge-backup-$TS
cp -a /var/lib/waydroid/lxc/waydroid/config /root/waydroid-bridge-backup-$TS/ 2>/dev/null || true
cp -a /usr/lib/waydroid/data/configs/config_1 /root/waydroid-bridge-backup-$TS/ 2>/dev/null || true
cp -a /usr/lib/waydroid/data/configs/config_3 /root/waydroid-bridge-backup-$TS/ 2>/dev/null || true
验证:
ip -br addr
bridge link
waydroid shell -- sh -c 'ip addr show eth0; dumpsys ethernet | head -80'
waydroid status 仍可能显示 IP address: UNKNOWN。桥接模式下请以 Android eth0 和 OpenWrt DHCP lease 为准。
10. Waydroid 内层 LXC:config_nodes、cgroup2 子树和 cgroup:rw
10.1 自动生成的 config_nodes:不要漏 DMA-BUF 和 binderfs
Waydroid 会根据 Fedora 外层容器里能看到的设备生成内层 Android LXC 的设备挂载,文件是:
/var/lib/waydroid/lxc/waydroid/config_nodes
当前记录里,/dev/dma_heap/system 是由 Waydroid helper 扫描 /dev/dma_heap/* 后自动写入 config_nodes 的。外层 Fedora 看不到 /dev/dma_heap/system 时,内层 Android 通常也看不到它。
请在启动 Waydroid 前后检查:
sed -n '1,220p' /var/lib/waydroid/lxc/waydroid/config_nodes 2>/dev/null || true
grep -E 'dma_heap|binder|hwbinder|vndbinder|dri|uinput|snd' /var/lib/waydroid/lxc/waydroid/config_nodes 2>/dev/null || true
期望至少能看到类似:
lxc.mount.entry = /dev/dri/renderD128 dev/dri/renderD128 none bind,create=file,optional 0 0
lxc.mount.entry = /dev/dma_heap/system dev/dma_heap/system none bind,create=file,optional 0 0
lxc.mount.entry = /dev/binderfs/binder dev/binder none bind,create=file,optional 0 0
lxc.mount.entry = /dev/binderfs/hwbinder dev/hwbinder none bind,create=file,optional 0 0
lxc.mount.entry = /dev/binderfs/vndbinder dev/vndbinder none bind,create=file,optional 0 0
不同 Waydroid 版本生成的顺序和附加参数可能不同,不要只按行号判断。重点是:
- Android 内层要能看到
/dev/dri/renderD128。 - Android 内层要能看到
/dev/dma_heap/system。 - Android 内层要通过
/dev/binder、/dev/hwbinder、/dev/vndbinder 使用宿主 binderfs 对应设备。
如果你是在修复宿主内核后才出现 /dev/dma_heap/system,请重启 Waydroid container/session,让 config_nodes 重新生成:
waydroid session stop 2>/dev/null || true
systemctl restart waydroid-container.service
sed -n '1,220p' /var/lib/waydroid/lxc/waydroid/config_nodes 2>/dev/null || true
如果你曾经手动删除过 config_nodes,也先用 Waydroid 自己的 container start 流程重新生成。不要把缺设备的问题误判成 Android media、SurfaceFlinger 或播放器问题。
10.2 cgroup2 子树和 cgroup:rw
这一节非常重要。外层 Fedora LXC 能启动,不代表 Waydroid 的内层 Android LXC 能稳定启动。Android 里面的 init、libprocessgroup 和 lmkd 需要在 cgroup2 上创建自己的 uid_* / pid_* 子目录;所以内层 LXC 必须在一个可委托的 cgroup2 子树里运行,并看到可写 cgroup。
如果内层 Waydroid LXC 仍然是只读 cgroup:
lxc.mount.auto = cgroup:ro sys:ro proc
遇到 Android 启动几秒退出、libprocessgroup 报只读、lmkd 反复重启时,请先修这里。
10.2.1 修改 Waydroid LXC config 模板
需要同时改当前生成文件和模板文件:
/var/lib/waydroid/lxc/waydroid/config
/usr/lib/waydroid/data/configs/config_base
请注意:waydroid init -f、waydroid upgrade、包升级、或某些重新生成 LXC 配置的操作,可能把 /var/lib/waydroid/lxc/waydroid/config 和 /usr/lib/waydroid/data/configs/config_base 改回默认值。我们远程环境里就观察到过运行态又回到:
lxc.mount.auto = cgroup:ro sys:ro proc
lxc.hook.post-stop = /dev/null
Android 当时仍能运行,但宿主 dmesg 里有不少 libprocessgroup 找不到 /sys/fs/cgroup/system/uid_*、/dev/blkio、/dev/cpuset 的噪音。通用部署建议仍按下面的 cgroup:rw 和明确 cgroup2 子树做,配置完后一定重新 grep 确认。
把:
lxc.mount.auto = cgroup:ro sys:ro proc
改成:
lxc.mount.auto = cgroup:rw sys:ro proc
并加入:
# Keep Android in an explicitly delegated cgroup2 subtree.
# The payload runs in a leaf so Android init can create uid_* and process-group children below it.
lxc.cgroup.dir.container = waydroid-android
lxc.cgroup.dir.monitor = waydroid-android-monitor
lxc.cgroup.dir.monitor.pivot = waydroid-android-monitor-pivot
lxc.cgroup.dir.container.inner = payload
lxc.hook.pre-start = /var/lib/waydroid/lxc/waydroid/waydroid-cgroup2-delegate.sh
同时检查是否还有这行:
lxc.hook.post-stop = /dev/null
我们的早期日志里,这行会让 LXC 在 Android 退出时尝试执行 /dev/null,并报:
Script exec /dev/null ... Permission denied
Script exited with status 126
Failed to run lxc.hook.post-stop
当前稳定配置里已经把它注释掉:
# lxc.hook.post-stop disabled
如果你看到同样的 status 126,请删除或注释 lxc.hook.post-stop = /dev/null。这个报错不一定是 Android 崩溃根因,但会干扰判断。
10.2.2 写入 pre-start delegation 脚本
文件:
/var/lib/waydroid/lxc/waydroid/waydroid-cgroup2-delegate.sh
内容:
#!/bin/sh
set -eu
LOG=/var/lib/waydroid/cgroup2-delegate.log
ROOT=/sys/fs/cgroup
CTR="$ROOT/waydroid-android"
PAYLOAD="$CTR/payload"
MONITOR="$ROOT/waydroid-android-monitor"
PIVOT="$ROOT/waydroid-android-monitor-pivot"
{
echo "=== $(date -Is) pre-start cgroup2 delegation ==="
echo "self=$(cat /proc/self/cgroup 2>/dev/null || true)"
findmnt -n -T "$ROOT" 2>/dev/null || true
if [ ! -f "$ROOT/cgroup.controllers" ]; then
echo "no cgroup2 controllers file at $ROOT"
exit 0
fi
# LXC should create final monitor/payload cgroups itself.
# Remove stale empty dirs from previous failed tests, but do not pre-create leaf cgroups.
for stale in "$PAYLOAD" "$CTR" "$MONITOR" "$PIVOT"; do
if [ -d "$stale" ]; then
rmdir "$stale" 2>/dev/null || true
fi
done
# Open every available cgroup2 controller at the root level.
if [ -f "$ROOT/cgroup.controllers" ] && [ -w "$ROOT/cgroup.subtree_control" ]; then
for controller in $(cat "$ROOT/cgroup.controllers"); do
echo "+$controller" > "$ROOT/cgroup.subtree_control" 2>/dev/null || true
done
fi
echo "$ROOT controllers=$(cat "$ROOT/cgroup.controllers" 2>/dev/null || true)"
echo "$ROOT subtree=$(cat "$ROOT/cgroup.subtree_control" 2>/dev/null || true)"
echo "$ROOT procs=$(wc -l < "$ROOT/cgroup.procs" 2>/dev/null || echo '?')"
echo "planned container=$CTR inner=payload monitor=$MONITOR pivot=$PIVOT"
} >> "$LOG" 2>&1
exit 0
授权:
chmod 0755 /var/lib/waydroid/lxc/waydroid/waydroid-cgroup2-delegate.sh
10.2.3 验证 cgroup 子树
启动 Waydroid 后先确认配置没有被重新生成覆盖:
grep -nE 'lxc.mount.auto|lxc.cgroup.dir|pre-start|post-stop' /var/lib/waydroid/lxc/waydroid/config /usr/lib/waydroid/data/configs/config_base 2>/dev/null || true
再检查 cgroup 子树:
sed -n '1,160p' /var/lib/waydroid/cgroup2-delegate.log 2>/dev/null || true
find /sys/fs/cgroup -maxdepth 3 -type d | grep -E 'waydroid-android|payload|monitor' || true
for d in /sys/fs/cgroup/waydroid-android /sys/fs/cgroup/waydroid-android/payload; do
[ -d "$d" ] || continue
echo "-- $d --"
echo "controllers=$(cat "$d/cgroup.controllers" 2>/dev/null || true)"
echo "subtree=$(cat "$d/cgroup.subtree_control" 2>/dev/null || true)"
echo "procs=$(cat "$d/cgroup.procs" 2>/dev/null | tr '\n' ' ' || true)"
done
期望能看到类似:
/sys/fs/cgroup controllers=cpuset cpu io memory pids rdma
/sys/fs/cgroup subtree=cpuset cpu io memory pids rdma
/sys/fs/cgroup/waydroid-android
/sys/fs/cgroup/waydroid-android/payload
如果 Android 日志里还有:
libprocessgroup: Failed to make and chown /sys/fs/cgroup/uid_...: Read-only file system
说明内层 Waydroid LXC 仍然没有拿到可写 cgroup2 子树。先修这一节,不要先去改播放器或 media 配置。
10.2.4 lmkd 要使用 cgroup2 / PSI 策略
如果 lmkd 报:
Old kill strategy can only be used with v1 cgroup hierarchy
Kernel does not support memory pressure events or in-kernel low memory killer
critical process 'lmkd' exited ... before boot completed
需要让 Android 使用 PSI / cgroup2 策略。早期稳定配置用过这些 vendor properties:
ro.lmk.use_new_strategy=true
ro.lmk.use_psi=true
ro.lmk.use_minfree_levels=false
ro.lmk.kill_heaviest_task=true
ro.lmk.psi_partial_stall_ms=70
ro.lmk.psi_complete_stall_ms=700
persist.device_config.lmkd_native.use_psi=true
这些属性是否需要放入当前镜像,要看你使用的 Android build。原则是:不要让 lmkd 走旧的 v1 memcg / vmpressure 策略。
完成 10.2 后,再重启 Waydroid:
waydroid session stop || true
systemctl restart waydroid-container.service
/usr/local/bin/start-waydroid.sh
11. 启动
11.1 OpenWrt 启动外层 Fedora
lxc-start -n waydroid
进入 Fedora:
lxc-attach -n waydroid -- /bin/bash
11.2 Fedora 内启动 Waydroid 和桌面会话
/usr/local/bin/start-waydroid.sh
检查:
systemctl is-active waydroid-container.service
systemctl --user -M user@ is-active waydroid-full-lxqt.service
waydroid status
期望:
active
active
Session: RUNNING
Container: RUNNING
11.3 如果 waydroid-autostart.service 和 sunshine-wayland.service 是 inactive
这不一定是错。我们当前验证过的状态是:
waydroid-full-lxqt.service active。sunshine 是 LXQt session 下的进程。waydroid-autostart-session.sh 是 LXQt session 下的进程。- 单独的
waydroid-autostart.service / sunshine-wayland.service 可以 disabled / inactive。
检查进程:
ps -eo user,pid,ppid,stat,cmd | grep -E 'kwin|lxqt|waydroid|sunshine' | grep -v grep
ls -la /run/user/1000 | grep wayland
期望看到:
kwin_wayland ... --socket wayland-0
lxqt-session
sunshine
waydroid-autostart-session.sh
wayland-0
12. 验证
12.1 宿主验证
在 OpenWrt:
uname -r
ls -ld /dev/dri /dev/dma_heap /dev/dma_heap/system 2>/dev/null || true
12.2 Fedora 验证
在 Fedora:
cat /etc/fedora-release
ls -ld /dev/dri /dev/dma_heap /dev/dma_heap/system 2>/dev/null || true
ip -br addr
bridge link
systemctl is-active waydroid-container.service
waydroid status
12.3 Android 验证
waydroid shell -- sh -c '
getprop ro.build.version.release
getprop ro.lineage.version
getprop sys.boot_completed
ls -ld /dev/dma_heap /dev/dma_heap/system /dev/dri /dev/dri/renderD128 /dev/binder /dev/hwbinder /dev/vndbinder 2>/dev/null
service check activity
service check window
service check display
service check SurfaceFlinger
service check waydroidplatform
'
期望:
16
23.0-20260403-UNOFFICIAL-WayDroidATV-waydroid_tv_x86_64
1
Service activity: found
Service window: found
Service display: found
Service SurfaceFlinger: found
Service waydroidplatform: found
13. Bonus:Android media / Codec2 / VAAPI / P010 支持(可选)
如果你只是想先把 Waydroid ATV 跑起来,可以跳过第 13 节,直接看第 14 节或按第 16 节做最终检查。需要调视频硬解、HEVC Main10/P010 时,再回来读这一节。
13.1 默认镜像能启动,不等于 Main10/P010 完整
Bonus 方向里,我们后来额外修过 stagefright-plugins 和 waydroid-init,用于改善 Android MediaCodec/Codec2 下 HEVC Main10 / P010 / VAAPI 行为。
关键目标:
- HEVC Main10 时,
c2.ffmpeg.hevc.decoder 能动态输出 P010。 - VAAPI hardware frame download 不把 P010 错误降成 8-bit。
- HEVC decoder capability 暴露 Main10 profile。
waydroid-init 暴露 host codec profiles。- 全局
debug.ffmpeg-codec2.pixel_format 保持 YUV_420,避免 H.264 / VP9 被全局强制 P010。
13.2 如果你有 Android 源码树
源码树目标:
WayDroid-ATV Android 16 tree
lunch target: lineage_waydroid_tv_x86_64 bp2a userdebug
构建命令:
cd /path/to/android-waydroid-atv23
export ANDROID_USE_GAPPS=false
export ALLOW_MISSING_DEPENDENCIES=true
export SOONG_ALLOW_MISSING_DEPENDENCIES=true
export DISABLE_DEXPREOPT_CHECK=true
source build/envsetup.sh
lunch lineage_waydroid_tv_x86_64 bp2a userdebug
m -j8 android.hardware.media.c2-ffmpeg-service media_codecs_ffmpeg_c2.xml android.hardware.media.c2-ffmpeg.policy
m -j8 waydroid-init
产物:
out/target/product/waydroid_tv_x86_64/vendor/bin/hw/android.hardware.media.c2-ffmpeg-service
out/target/product/waydroid_tv_x86_64/vendor/etc/media_codecs_ffmpeg_c2.xml
out/target/product/waydroid_tv_x86_64/vendor/etc/seccomp_policy/android.hardware.media.c2-ffmpeg.policy
out/target/product/waydroid_tv_x86_64/vendor/etc/vintf/manifest/manifest_media_c2_ffmpeg.xml
out/target/product/waydroid_tv_x86_64/vendor/bin/waydroid-init
out/target/product/waydroid_tv_x86_64/vendor/etc/init/waydroid-init.rc
相关上游工作:
stagefright-plugins branch: [main10-p010-codec2](https://github.com/WayDroid-ATV/android_external_stagefright-plugins/pull/1)
waydroid-init branch: [auto-p010-for-main10](https://github.com/WayDroid-ATV/android_vendor_waydroid_init/pull/1)
13.3 部署到 vendor overlay
不要直接修改 vendor.img。使用 Waydroid vendor overlay upperdir:
/var/lib/waydroid/overlay_rw/vendor
在 Fedora LXC 中备份:
TS=$(date +%Y%m%d-%H%M%S)
BACKUP=/root/waydroid-media-backup-$TS
mkdir -p "$BACKUP"
cp -a /var/lib/waydroid/overlay_rw/vendor "$BACKUP/vendor-overlay-rw" 2>/dev/null || true
复制产物到:
/var/lib/waydroid/overlay_rw/vendor/bin/hw/android.hardware.media.c2-ffmpeg-service
/var/lib/waydroid/overlay_rw/vendor/etc/media_codecs_ffmpeg_c2.xml
/var/lib/waydroid/overlay_rw/vendor/etc/seccomp_policy/android.hardware.media.c2-ffmpeg.policy
/var/lib/waydroid/overlay_rw/vendor/etc/vintf/manifest/manifest_media_c2_ffmpeg.xml
/var/lib/waydroid/overlay_rw/vendor/bin/waydroid-init
授权:
chmod 0755 /var/lib/waydroid/overlay_rw/vendor/bin/hw/android.hardware.media.c2-ffmpeg-service
chmod 0755 /var/lib/waydroid/overlay_rw/vendor/bin/waydroid-init
部署完这些文件后,请确认第 10 节的 Waydroid inner LXC config_nodes 和 cgroup2 配置已经完成,再重启 Waydroid。
13.4 期望属性
进入 Android:
waydroid shell -- sh -c '
getprop debug.ffmpeg-codec2.pixel_format
getprop debug.ffmpeg-codec2.hwaccel.drm
getprop ro.waydroid.hwcodecs
getprop ro.waydroid.hwcodec_profiles
getprop ro.hardware.gralloc
getprop ro.hardware.egl
getprop ro.hardware.vulkan
getprop media.sf.hwaccel
'
期望:
YUV_420
1
MPG2H264VP80HEVCVP90AV10
HEVCMain10,VP9Profile2,AV1Profile0
minigbm
mesa
intel
1
注意:debug.ffmpeg-codec2.pixel_format=YUV_420 是当前安全设计,不是错误。HEVC Main10 由 codec component 根据 VAAPI frame context 动态切换到 P010。
13.5 Bonus 验证:视频播放验证(可选)
如果你只是要先跑起 Waydroid ATV,可以先跳过本节。这里验证的是 Android MediaCodec / Codec2 / C2FFMPEG / VAAPI / P010 这一组可选视频能力。
13.5.1 推荐测试方式
要验证 Android MediaCodec / Codec2 / C2FFMPEG / VAAPI,不要只看“某个播放器能播放”。请看 logcat。
用一个 2160p HEVC Main10 样片。我们测试过的样片事实是:
codec_name=hevc
profile=Main 10
width=3840
height=2160
pix_fmt=yuv420p10le
用 Next Player 或其它会调用 Android MediaCodec 的播放器播放。
同时抓日志:
waydroid logcat | grep -E 'CCodec|Codec2|C2FFMPEG|HWACCEL|MediaCodec|pixel-format|color-format'
关键日志应该类似:
allocate(c2.ffmpeg.hevc.decoder)
Available Codec2 services: "ffmpeg" "software"
createComponent: c2.ffmpeg.hevc.decoder
ffmpeg_hwaccel_init: ... [hevc], hw device = vaapi
outputFrame: pixel format changed - 0x36
raw.pixel-format.value = 54
android._color-format = 54
playbackState=PLAYING
playbackState=STOPPED
其中:
0x36 / 54 = HAL_PIXEL_FORMAT_YCBCR_P010 / YCBCR_P010
13.5.2 已观察到的结论边界
已经观察到:
- H.264 2160p 样片没有被强制 P010,可以播放到结束。
- VP9 Profile 0 1080p 样片没有被强制 P010,可以播放到结束。
- HEVC Main10 2160p 样片会动态切到 P010,可以播放到结束。
- Emby HEVC Main10 用户测试改善明显。
不能由这些事实推出:
- 所有 HDR 都正常。
- 所有 Dolby Vision 都正常。
- Emby AV1 HDR 已解决。
- VLC 能播放就说明 Android MediaCodec 正常。
VLC 可以播放某个文件,只能说明 VLC 可以播放这个文件。VLC 可能使用自己的 ffmpeg 解码和 GLES 输出,不一定经过 Android MediaCodec / Codec2。
14. 可选:OpenWrt 4G swap
如果机器内存压力大,可以在 OpenWrt 宿主加 swap。我们用过 4G:
dd if=/dev/zero of=/swapfile bs=1M count=4096 conv=fsync
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
BusyBox 注意:
swapon --show 可能不支持。dd status=progress 可能不支持。
检查:
cat /proc/swaps
free
如果 OpenWrt 没有 /etc/init.d/fstab,可以写一个简单 init 脚本开机 swapon /swapfile。脚本只应在 /swapfile 存在且没有激活时执行 swapon。
14.5 最容易漏的部署细节复盘
按已经跑通的实机重新核对后,下面这些点最容易在通用部署时漏掉:
- Fedora 断开 NetworkManager/networkd 后,要先有最小网络配置。 可以用第 4.4 节的
waydroid-lxc-network-safe.service,先给 Fedora 的 eth0 配 LAN IP、默认网关和 /etc/resolv.conf。否则第 5 节 dnf install 很容易没网。 - binderfs 不一定由 OpenWrt 宿主直接 bind 进 Fedora。 也可以由 Fedora 内的 Waydroid helper 创建
/dev/binderfs/* 和 /dev/binder symlink。判断成功与否看 Fedora 和 Android 最终是否能访问 binder/hwbinder/vndbinder。 - Waydroid LXC config 可能被重新生成。
waydroid init -f、waydroid upgrade 或包升级后,lxc.mount.auto 可能回到 cgroup:ro,lxc.hook.post-stop = /dev/null 也可能回来。按第 10.2 节修完后,重启 Waydroid 前后都要 grep 确认。 waydroid status 的 IP address: UNKNOWN 不代表 Android 没网。 桥接模式下请用 lxc-info -P /var/lib/waydroid/lxc -n waydroid、Android ip addr show eth0、OpenWrt DHCP lease 来判断真实网络。- OpenWrt swap 建议作为低内存机器的可选项。 内存压力大的路由器可以按第 14 节加 swap,避免日志、图形会话、Android 服务叠加后太容易触发内存压力。
- 蓝牙要在 Android 层禁用并验证。 期望看到
persist.bluetooth.disabled=true、bluetooth_on=0、com.android.bluetooth 在 disabled packages 里。service list 仍可能显示 bluetooth_manager,这不等于蓝牙 app 又启用了。 - Bonus media overlay 不是“先跑起来”的必要步骤。
/var/lib/waydroid/overlay_rw/vendor 里的 android.hardware.media.c2-ffmpeg-service、waydroid-init、ffmpeg libs 和 media XML 属于第 13 节视频硬解方向。基础部署先跳过也可以。
15. 常见问题
15.0 OpenWrt /tmp 被写满或内存上涨
先确认是不是日志写到了 tmpfs:
# OpenWrt
mount | grep ' /tmp '
df -h /tmp
free
如果 /tmp 是 tmpfs,请不要把长时间 waydroid logcat、Android tombstone 收集、LXC debug log 写到 /tmp。把日志写到持久存储。
同时检查 Android 蓝牙是否已禁用:
waydroid shell -- sh -c '
getprop persist.bluetooth.disabled
cmd package list packages -d | grep -i bluetooth || true
'
如果没有禁用,执行第 0.5 节的蓝牙禁用命令。
15.1 waydroid status 显示 IP UNKNOWN
桥接模式下这可能正常。Waydroid helper 仍可能读取旧的 waydroid0 dnsmasq lease。
请用这些命令判断真实网络:
ip -br addr
bridge link
waydroid shell -- sh -c 'ip addr show eth0; dumpsys ethernet | head -80'
15.2 Android 提示 Missing DMA-BUF support
依次检查三层:
# OpenWrt
ls -ld /dev/dma_heap /dev/dma_heap/system /dev/dri
# Fedora
lxc-attach -n waydroid -- /bin/bash -lc 'ls -ld /dev/dma_heap /dev/dma_heap/system /dev/dri'
# Android
lxc-attach -n waydroid -- /bin/bash -lc 'waydroid shell -- sh -c "ls -ld /dev/dma_heap /dev/dma_heap/system /dev/dri"'
如果 OpenWrt 没有 /dev/dma_heap/system,回到内核章节。不要改 Android 文件掩盖。
15.3 waydroidplatform 不通或 waydroid app list 卡住
检查:
sed -n '1,120p' /var/lib/waydroid/waydroid.cfg
python3 - <<'PY'
import gbinder
print(gbinder.__file__)
PY
pkg-config --modversion libgbinder
需要:
binder_protocol = aidl3
service_manager_protocol = aidl6
gbinder 来自 /usr/local/lib64/python3.14/site-packages
libgbinder 1.1.45
15.4 Waydroid inner LXC 因 AppArmor 配置失败
如果日志出现:
Built without AppArmor support
Invalid argument
检查 Waydroid inner LXC config,删除不兼容行:
lxc.apparmor.profile = unconfined
15.5 lxc.hook.post-stop = /dev/null 导致 status 126
如果手动启动内层 Android LXC 时看到:
Script exec /dev/null ... Permission denied
Script exited with status 126
Failed to run lxc.hook.post-stop
请检查:
grep -n 'post-stop' /var/lib/waydroid/lxc/waydroid/config /usr/lib/waydroid/data/configs/config_base 2>/dev/null || true
把下面这行删除或注释:
lxc.hook.post-stop = /dev/null
当前稳定配置是注释掉它。这个问题可能不是 Android 退出的根因,但会让 LXC 日志多一个失败点。
15.6 Android 早期日志出现 /dev/hw_random 缺失
早期 Android 13 调试记录里出现过:
prng_seeder: Hanging forever because setup failed: Unable to open hwrng /dev/hw_random
当时还试过把 /dev/random、/dev/urandom 绑定进 Android,结果 Android init 自己创建 /dev/random、/dev/urandom 时发生冲突:
init: mknod("/dev/random"...) failed File exists
init: mknod("/dev/urandom"...) failed File exists
Init encountered errors starting first stage
所以请不要默认把 /dev/random 和 /dev/urandom 写进 config_nodes。
如果你用旧镜像,且明确看到 /dev/hw_random 这一条错误,可以只做临时诊断:
lxc.mount.entry = /dev/urandom dev/hw_random none bind,create=file,optional 0 0
这不是当前 Android 16 ATV 部署的默认步骤。当前更重要的是先确认第 10.2 节的 cgroup2 子树、cgroup:rw、lmkd 策略,以及第 10.1 节的 config_nodes 设备挂载。
15.7 NetworkManager / systemd-networkd 抢网络
当前方案不用它们。保持:
NetworkManager inactive
systemd-networkd inactive
systemd-resolved inactive
firewalld inactive
wpa_supplicant inactive
网络由 OpenWrt 外层 LXC 配置、Fedora 内 waydroid-bridge-setup 和 Android DHCP 完成。
16. 最小检查清单
部署结束后,按这个清单检查:
# OpenWrt
uname -r
ls -ld /dev/dri /dev/dma_heap /dev/dma_heap/system /dev/binderfs 2>/dev/null || true
ls -l /dev/binderfs/binder /dev/binderfs/hwbinder /dev/binderfs/vndbinder 2>/dev/null || true
dmesg | grep -i -E "drm|gpu|i915|amdgpu|nouveau|xe|guc|huc|firmware|wedged|reset" | tail -60 || true
# Fedora
lxc-attach -n waydroid -- /bin/bash -lc '
cat /etc/fedora-release
systemctl is-active waydroid-container.service || true
systemctl --user -M user@ is-active waydroid-full-lxqt.service || true
waydroid status || true
ip -br addr
bridge link || true
ls -ld /dev/dri /dev/dma_heap /dev/dma_heap/system /dev/binderfs 2>/dev/null || true
ls -l /dev/binderfs/binder /dev/binderfs/hwbinder /dev/binderfs/vndbinder 2>/dev/null || true
grep -E "dma_heap|binder|hwbinder|vndbinder|dri" /var/lib/waydroid/lxc/waydroid/config_nodes 2>/dev/null || true
for s in NetworkManager systemd-networkd systemd-resolved firewalld wpa_supplicant; do printf "%s: " "$s"; systemctl is-active "$s" 2>/dev/null || true; done
'
# Android
lxc-attach -n waydroid -- /bin/bash -lc '
waydroid shell -- sh -c "
getprop ro.build.version.release
getprop ro.lineage.version
getprop sys.boot_completed
getprop debug.ffmpeg-codec2.pixel_format
getprop debug.ffmpeg-codec2.hwaccel.drm
getprop ro.waydroid.hwcodec_profiles
getprop ro.hardware.gralloc
getprop ro.hardware.egl
getprop ro.hardware.vulkan
getprop media.sf.hwaccel
ls -ld /dev/dma_heap /dev/dma_heap/system /dev/dri /dev/dri/renderD128 /dev/binder /dev/hwbinder /dev/vndbinder 2>/dev/null
service check activity
service check window
service check display
service check SurfaceFlinger
service check waydroidplatform
"
'
期望重点:
OpenWrt 有 /dev/dma_heap/system,且 GPU 驱动没有 firmware 缺失或 wedged/reset 失败日志
Fedora 能看到 /dev/binderfs 和 /dev/dma_heap/system
config_nodes 里有 /dev/dma_heap/system、binder/hwbinder/vndbinder、/dev/dri/renderD128
内层 Waydroid LXC 使用 cgroup:rw,并运行在 waydroid-android/payload cgroup2 子树中
Fedora 44
Waydroid Session RUNNING / Container RUNNING
Android 16 / Lineage 23.0-20260403
sys.boot_completed=1
YUV_420
hwaccel.drm=1
HEVCMain10,VP9Profile2,AV1Profile0
minigbm / mesa / intel
SurfaceFlinger found
waydroidplatform found
17. 不建议做的事
请不要:
- 为了隐藏 DMA-BUF 提示去改 Android framework、build.prop 或 Magisk 模块。
- 随便启用 NetworkManager、systemd-networkd、systemd-resolved、firewalld。
- 随便加载 Bluetooth 模块。我们的记录里 Bluetooth 模块尝试导致过 boot loop。
- 默认给 Android inner LXC 绑定
/dev/random、/dev/urandom。Android init 会自己创建设备节点,乱绑可能让 first stage init 失败。 - 保留
lxc.hook.post-stop = /dev/null,如果它在你的 LXC 版本里导致 status 126。 - 只看 VLC 播放结果就判断 Android MediaCodec 正常。
- 删除整个
/var/lib/waydroid/overlay_rw/vendor,除非你确认里面没有需要保留的部署产物。 - 在不知道原因时把 H.264、VP9、HEVC 全局强制成 P010。
18. Debug 指引
如果部署失败,请优先收集:
OpenWrt: uname -r, dmesg, /dev/dma_heap, /dev/binderfs, GPU firmware 检查
Fedora: journalctl -u waydroid-container.service
Fedora user: journalctl --user -u waydroid-full-lxqt.service
Waydroid: waydroid status, waydroid logcat
Android: getprop, service check, /dev/dma_heap, /dev/dri, /dev/binder, /dev/hwbinder, /dev/vndbinder
LXC: /srv/lxc/waydroid/config, /var/lib/waydroid/lxc/waydroid/config, config_nodes
你可能还希望使用 netconsole 等方式在软路由 kernel panic 时抓取到错误。或者至少给它接个键盘。
- 0. 这是一个怎样的 Setup ?
- 0.5 部署前先看:最容易把路由器弄断网的坑
- 坑 1:不要一开始就给 Fedora LXC
/sys:rw和桥接网卡 - 坑 2:不要用
lxc.net.0.type = none共享 OpenWrt 宿主网络 - 坑 3:OpenWrt LXC 的
common.conf可能触发 cgroup2 BPF 设备过滤 - 坑 4:Waydroid 内层 Android LXC 需要可写 cgroup2 子树
- 坑 5:OpenWrt 的
/tmp是 tmpfs,必须在 Android 层禁用蓝牙 - 1. 变量约定
- 2. 整体部署流程
- 3. OpenWrt 宿主内核准备
- 3.1 必要能力
- 3.1.1 binderfs 挂载和检查
- 3.2 OpenWrt 内核配置
- 3.3 重要坑:OpenWrt 的 DMA-BUF debloat patch
- 3.4 构建内核
- 3.5 部署内核,并修改 GRUB 配置
- 3.5.1 准备部署包
- 3.5.2 复制 kernel 和 modules 到 OpenWrt
- 3.5.3 不要覆盖原始
OpenWrt启动项 - 3.5.4 编辑
/boot/grub/grub.cfg - 3.5.5 重启前检查
- 3.5.6 重启并验证
- 3.6 OpenWrt 宿主运行时前置检查:LXC 工具、GPU 固件和设备节点
- 4. 创建外层 Fedora 44 LXC
- 4.1 rootfs 创建说明
- 4.2 外层 LXC 配置:分两阶段写,先安全初始化,再正式运行
- 4.3 验证外层容器设备
- 4.4 给 Fedora 一个最小可用网络:不用 NetworkManager/networkd
- 5. Fedora LXC 内安装基础软件
- 6. 安装 upstream Waydroid / libgbinder / gbinder-python
- 7. 安装 WayDroid-ATV 镜像
- 7.1 版本选择:新版 A16 vs 旧版 A13
- 7.2 方法一:用
waydroid init接 WayDroid-ATV OTA server(推荐) - 7.3 方法二:手动下载 zip 并安装
- 7.4 全新初始化时的清理边界
- 7.5 配置 Waydroid 使用正确协议和镜像目录
- 7.6 初始化后立刻禁用 Android 蓝牙
- 8. 配置 Fedora 图形会话:systemd user + LXQt + KWin
- 9. 配置桥接网络
- 10. Waydroid 内层 LXC:config_nodes、cgroup2 子树和
cgroup:rw - 11. 启动
- 11.1 OpenWrt 启动外层 Fedora
- 11.2 Fedora 内启动 Waydroid 和桌面会话
- 11.3 如果
waydroid-autostart.service和sunshine-wayland.service是 inactive - 12. 验证
- 13. Bonus:Android media / Codec2 / VAAPI / P010 支持(可选)
- 13.5 Bonus 验证:视频播放验证(可选)
- 14. 可选:OpenWrt 4G swap
- 14.5 最容易漏的部署细节复盘
- 15. 常见问题
- 15.0 OpenWrt
/tmp被写满或内存上涨 - 15.1
waydroid status显示 IP UNKNOWN - 15.2 Android 提示 Missing DMA-BUF support
- 15.3
waydroidplatform不通或waydroid app list卡住 - 15.4 Waydroid inner LXC 因 AppArmor 配置失败
- 15.5
lxc.hook.post-stop = /dev/null导致 status 126 - 15.6 Android 早期日志出现
/dev/hw_random缺失 - 15.7 NetworkManager / systemd-networkd 抢网络
- 16. 最小检查清单
- 17. 不建议做的事
- 18. Debug 指引
家里没有多余的电视盒子了,电视的性能也不是很好,怎么办呢?刚好软路由上有个 HDMI,于是...
向您介绍:LineageOS in LXC(Waydroid) on Fedora in LXC on OpenWrt,无须虚拟化,直接跑在 OpenWrt 的 kernel 和 sysfs 上的 Android!
断断续续折腾了5天,烧掉了$200+的偷啃,终于和一大坨Agent一起把这玩意给搞出来啦。其实基础安装,包括编译调试 OpenWrt 内核在内也就花了两天,后面的时间都是修视频硬件解码,给上游 Waydroid-ATV 提 pr 啥的。
此次部署涉及编译 OpenWrt 内核,编译 Android Vendor,编译 ffmpeg 组件,编译 Waydroid 上游组件等,还有一大堆的配置文件测试,没有 AI 的协助是绝对做不到的(传统人力可能得要一个人一个月的时间?特别感谢凌莞宝给的偷啃!)。整理出了一大堆文档,索性把这篇教程也让 Agent 来写啦,希望你能看懂。建议扔给你的AI来部署,我们已经把坑替你踩好了。
整理日期:2026-05-23
适用目标:在一台 OpenWrt x86_64 主机上,通过“OpenWrt 宿主 + Fedora LXC + Waydroid Android LXC”的三层结构运行 Waydroid ATV,并启用 Intel iGPU、DMA-BUF、桥接网络、KWin/LXQt 图形会话和 Android Codec2/VAAPI 视频解码。
本文来自我们已经跑通的 OpenWrt/Fedora/Waydroid 实机部署记录。为了给其他人复用,文中不使用 openwrt.lan、固定内网地址、固定用户目录作为硬要求,而是改成变量。你可以按自己的机器替换。
0. 这是一个怎样的 Setup ?
把 Android TV 放进 Waydroid 的 Android LXC,再把 Waydroid 放进一个 Fedora privileged LXC,最底下仍然使用 OpenWrt 宿主的 Linux kernel 和硬件设备。
整体结构是这样:
OpenWrt x86_64 宿主
└── Fedora 44 privileged LXC
└── Waydroid Android LXC / WayDroid-ATV它不是传统虚拟机,也不是在 Docker 里硬塞 Android。Android 这一层会直接使用宿主内核提供的 binder、cgroup、DMA-BUF、DRM render node、input、audio 等能力;Fedora 这一层负责提供更完整、对 Waydroid 更友好的 userspace。
三层各自负责的事情大概是:
第一层:OpenWrt 宿主
- 跑真正的 Linux kernel。
- 提供 Waydroid 需要的内核能力,例如 binder/binderfs、cgroup2、DMA-BUF heap。
- 提供 GPU/DRM 设备,例如
/dev/dri/renderD128。 - 提供输入、声音等设备,例如
/dev/input、/dev/uinput、/dev/snd。 - 准备 GPU firmware。OpenWrt 通常没有完整 firmware,这一步后面会单独写。
- 提供外层 Fedora LXC 使用的桥接网络,例如接到 OpenWrt 的
br-lan。
第二层:Fedora privileged LXC
- 运行
systemd、Waydroid、LXC、KWin Wayland、LXQt 等组件。 - 提供 Wayland 图形会话,让 Waydroid 的 Android 画面有地方显示。
- 提供
libgbinder、Waydroid helper、Mesa/VAAPI 等 Linux userspace 组件。 - 管理普通用户会话,例如本文使用的
user用户,以及它所属的video、audio、input、render、seat等组。 - 作为 Android 内层 LXC 的父环境,负责把必要设备、cgroup 子树和 binder 节点交给 Waydroid。
第三层:Waydroid Android LXC / WayDroid-ATV
- 运行 Android TV,也就是 LineageOS / WayDroid-ATV。
- 使用
binder、hwbinder、vndbinder完成 Android IPC。 - 使用
/dev/dma_heap/system和 DRM render node 参与图形缓冲区分配、渲染和显示。 - 通过桥接网络获得一个和 LAN 同网段的地址。
- 后面可选启用 Android Codec2 / VAAPI / P010 等视频解码能力。
基础部署目标很朴素:先把 Android TV 桌面跑起来,让网络、显示、输入、声音都能工作。至于视频硬解、Codec2、VAAPI、P010 这些内容,本文会放到后面的 Bonus 部分;它们很有价值,但不应该挡在第一次跑通前面。
本文后续命令会默认使用这几个名字,你可以按自己的机器替换:
外层 Fedora LXC 名称:waydroid
外层 Fedora 普通用户:user
外层 Fedora 图形会话:systemd user session + LXQt Wayland + KWin Wayland
Android 镜像:WayDroid-ATV Android 16 / Lineage 23.0 / waydroid_tv_x86_640.5 部署前先看:最容易把路由器弄断网的坑
请先看这一节,再动 LXC 配置。我们早期踩过的最大坑不是 Waydroid 本身,而是外层 Fedora LXC 里 systemd-networkd / systemd-udevd 配合可写 /sys 后,可能会改动网络设备状态或尝试按 Fedora 规则重命名网卡。
坑 1:不要一开始就给 Fedora LXC /sys:rw 和桥接网卡
建议第一次启动 Fedora LXC 时先用安全配置:
# 第一次启动只用于进容器做系统服务屏蔽,不要先桥接到 LAN
lxc.net.0.type = empty
# 第一次启动保留 /sys 只读
lxc.mount.auto = proc:mixed sys:ro cgroup:mixed这样做的目的很具体:
- Fedora 容器第一次启动时,即使
systemd-networkd或systemd-udevd自动运行,也不容易改 OpenWrt 宿主的网卡状态。 empty网络命名空间不会共享 OpenWrt 宿主网络,也不会立刻桥接到br-lan。/sys:ro可以降低容器内 udev 写 sysfs、改设备状态、触发网卡重命名的风险。
第一次启动后,进入 Fedora 容器:
lxc-start -n waydroid
lxc-attach -n waydroid -- /bin/bash在 Fedora 容器内先屏蔽这些服务:
systemctl mask systemd-networkd.service systemd-networkd.socket systemd-networkd-wait-online.service 2>/dev/null || true
systemctl disable --now systemd-networkd.service systemd-networkd.socket systemd-networkd-wait-online.service 2>/dev/null || true
systemctl mask systemd-resolved.service firewalld.service NetworkManager.service wpa_supplicant.service 2>/dev/null || true
systemctl disable --now systemd-resolved.service firewalld.service NetworkManager.service wpa_supplicant.service 2>/dev/null || true再处理 udev 的网卡自动重命名。建议至少写入一个 “保持内核原名” 的 .link 文件,并屏蔽默认 link 规则:
mkdir -p /etc/systemd/network
cat > /etc/systemd/network/10-keep-kernel-ifnames.link <<'EOF'
[Match]
OriginalName=*
[Link]
NamePolicy=keep
MACAddressPolicy=none
EOF
ln -sfn /dev/null /etc/systemd/network/99-default.link如果你的目标是最保守的路由器环境,可以先屏蔽 systemd-udevd,等确认不会重命名网卡之后,再按图形会话需要决定是否恢复 udev 数据库:
systemctl mask systemd-udevd.service systemd-udevd-control.socket systemd-udevd-kernel.socket 2>/dev/null || true
systemctl disable --now systemd-udevd.service systemd-udevd-control.socket systemd-udevd-kernel.socket 2>/dev/null || true注意边界:KWin/Weston/libinput/libudev 有时需要 udev 数据库来识别 DRM 和输入设备。如果后面图形会话缺 udev tags,可以在确认网卡不会被重命名后,再有控制地恢复 udev,并只触发需要的设备,例如 DRM/input。不要在 /sys:rw、网络还没保护好的阶段直接让 udev 全量扫描。
完成上面步骤后,退出 Fedora,停止外层 LXC:
exit
lxc-stop -n waydroid然后再把外层 LXC 改成正式运行配置:
lxc.net.0.type = veth
lxc.net.0.veth.pair = lxc-waydroid
lxc.net.0.link = br-lan
lxc.net.0.flags = up
lxc.net.0.hwaddr = 00:16:3e:59:af:02
lxc.mount.auto = proc:mixed sys:rw cgroup:mixed这一步顺序很重要:先在容器内禁用 networkd / 自动网卡重命名,再给桥接网络和 /sys:rw。
坑 2:不要用 lxc.net.0.type = none 共享 OpenWrt 宿主网络
我们早期试过 lxc.net.0.type = none。这个设置会让 Fedora 容器共享 OpenWrt 宿主网络命名空间。结果是 Fedora 里的网络管理服务有机会直接影响 OpenWrt 宿主网卡,路由器可能失联。
安全选择是:
- 首次保护阶段用
empty。 - 正式运行阶段用
veth接 OpenWrt 的br-lan。 - 不要用
none。
坑 3:OpenWrt LXC 的 common.conf 可能触发 cgroup2 BPF 设备过滤
OpenWrt 的 LXC common.conf 里可能有:
lxc.cgroup2.devices.deny = a这会让 LXC 尝试加载 cgroup2 device BPF 程序。有些 OpenWrt 内核没有对应 BPF 支持,启动会失败,日志类似:
Failed to load bpf program
Failed to setup cgroup2 device controller limits我们验证过的处理方式是:外层 Fedora LXC 不 include common.conf,自己写最小配置,并且不要写 cgroup2 device deny 规则。当前方案依赖 privileged LXC 和明确的设备 bind mount。
坑 4:Waydroid 内层 Android LXC 需要可写 cgroup2 子树
这是另一个非常关键的坑。Waydroid 内层 LXC 不是普通进程,它还要在 Android 里让 init / libprocessgroup / lmkd 创建和管理 cgroup。
如果内层 LXC 只有:
lxc.mount.auto = cgroup:ro sys:ro proc可能出现这些现象:
libprocessgroup: Failed to make and chown /sys/fs/cgroup/uid_1000: Read-only file system
libprocessgroup: Failed to open /dev/cpuset/foreground/tasks: No such file or directory
lmkd: Old kill strategy can only be used with v1 cgroup hierarchy
critical process 'lmkd' exited ... before boot completed
Android 容器启动几秒后退出我们日志里确认过的关键要求是:Waydroid 内层 Android LXC 必须运行在明确的 cgroup2 子树中,并且 Android 看到的 cgroup 挂载必须是可写的,也就是给内层 LXC cgroup:rw:
lxc.mount.auto = cgroup:rw sys:ro proc
lxc.cgroup.dir.container = waydroid-android
lxc.cgroup.dir.monitor = waydroid-android-monitor
lxc.cgroup.dir.monitor.pivot = waydroid-android-monitor-pivot
lxc.cgroup.dir.container.inner = payload
lxc.hook.pre-start = /var/lib/waydroid/lxc/waydroid/waydroid-cgroup2-delegate.shwaydroid-cgroup2-delegate.sh 的作用是打开 /sys/fs/cgroup 的可用 controllers,并让 LXC 在 waydroid-android/payload 这个 leaf 里运行 Android。这样 Android 可以在自己的 cgroup 子树下面创建 uid_* / pid_* 子目录。
这不是为了“美化日志”,而是 Android 进程管理需要的运行条件。后文会给出完整脚本。
坑 5:OpenWrt 的 /tmp 是 tmpfs,必须在 Android 层禁用蓝牙
OpenWrt 的 /tmp 通常是 tmpfs,也就是占用内存。Waydroid / Android 启动失败、服务反复崩溃或 logcat 长时间写入 /tmp 时,可能直接吃掉路由器内存。
我们遇到的一个具体风险是:Waydroid-ATV 的蓝牙栈在当前环境里不可靠,蓝牙相关服务可能反复崩溃并刷日志。如果日志写到 OpenWrt/Fedora 的 tmpfs,内存会被慢慢吃满,最后可能导致路由器重启。
部署时请明确做两件事:
- 不要把长时间日志写到 OpenWrt
/tmp。 长日志请写到持久存储,例如构建机本地目录、外接磁盘,或 OpenWrt rootfs 上确认不是 tmpfs 的目录。 - 在 Android 层禁用蓝牙。 不要通过加载 OpenWrt Bluetooth 模块来“修蓝牙”。我们之前的记录里,Bluetooth kernel 模块尝试导致过 boot loop 风险。
Android 启动后执行:
waydroid shell -- sh -c '
setprop persist.bluetooth.disabled true
settings put global bluetooth_on 0
settings put global ble_scan_always_enabled 0
cmd bluetooth_manager disable 2>/dev/null || true
pm disable-user --user 0 com.android.bluetooth 2>/dev/null || true
pm disable com.android.bluetooth 2>/dev/null || true
'验证:
waydroid shell -- sh -c '
getprop persist.bluetooth.disabled
cmd package list packages -d | grep -i bluetooth || true
service list | grep -i bluetooth || true
'期望至少看到:
persist.bluetooth.disabled=true
package:com.android.bluetoothservice list 里可能仍有 bluetooth_manager binder service,某些 vendor bluetooth service 也可能存在;关键是 Android framework 层的 com.android.bluetooth 已被禁用,蓝牙不再作为可用功能反复启动。
1. 变量约定
请先把这些变量换成你自己的环境。
# OpenWrt 宿主
OPENWRT_HOST="你的OpenWrt主机名或IP"
# 外层 Fedora LXC
FEDORA_LXC_NAME="waydroid"
FEDORA_ROOTFS="/srv/lxc/waydroid/rootfs"
FEDORA_LXC_CONFIG="/srv/lxc/waydroid/config"
FEDORA_USER="user"
FEDORA_UID="1000"
# OpenWrt LAN
OPENWRT_LAN_BRIDGE="br-lan"
OPENWRT_LAN_GATEWAY="192.168.1.1"
FEDORA_LAN_ADDR="192.168.1.237/24" # 示例,按你的 LAN 改
ANDROID_LAN_ADDR="由 DHCP 分配" # 不建议硬编码
# Waydroid 镜像存放位置,位于 Fedora LXC 内
WAYDROID_IMAGE_DIR="/etc/waydroid-extra/images"
# 本地 Android 源码树。如果你只使用现成镜像和产物,可以跳过源码构建章节。
ANDROID_SRC="/path/to/android-waydroid-atv23"后文命令如果写死了 /srv/lxc/waydroid、user、192.168.1.1,请按这里的变量替换。
2. 整体部署流程
建议按这个顺序做:
- 准备 OpenWrt 内核:binder、binderfs、cgroup、PSI、DMA-BUF heaps。
- 创建外层 Fedora 44 privileged LXC,并透传设备。
- 在 Fedora LXC 中安装基础软件、Waydroid userspace、libgbinder、gbinder-python。
- 如果 OpenWrt 缺 GPU firmware,从 Fedora/Linux 的 firmware 包复制对应 firmware 到 OpenWrt,再重启验证 GPU 驱动。
- 安装 WayDroid-ATV Android 16 镜像。
- 配置 Fedora 图形会话:systemd user session + LXQt Wayland + KWin。
- 配置网络:外层 Fedora 和内层 Android 桥接到 OpenWrt LAN。
- 配置 Waydroid 内层 LXC 的
config_nodes和 cgroup2 子树。 - 启动并验证三层状态。
- 可选 Bonus:需要视频硬解调优时,再部署 Android media / Codec2 / VAAPI / P010 支持,并用 Android 播放器看日志验证。
3. OpenWrt 宿主内核准备
3.1 必要能力
OpenWrt 宿主必须提供:
Android binder / binderfs
cgroup / cgroup v1 compatibility pieces used by this setup
PSI
DMA-BUF core
DMA-BUF system heap
UDMABUF
DRM render node, usually /dev/dri/renderD128部署后必须能看到:
ls -ld /dev/dri /dev/dma_heap /dev/dma_heap/system 2>/dev/null || true
grep -E 'dma_heap|udmabuf' /proc/misc /proc/devices 2>/dev/null || true期望至少有:
/dev/dri
/dev/dma_heap
/dev/dma_heap/system
/proc/devices 里有 dma_heap
/proc/misc 里有 udmabuf如果 /dev/dma_heap/system 不存在,不要去改 Android XML、Magisk 模块、build.prop 来隐藏提示。正确方向是修 OpenWrt 内核。
3.1.1 binderfs 挂载和检查
Waydroid 需要 binder、hwbinder、vndbinder。常见位置是:
/dev/binderfs/binder
/dev/binderfs/hwbinder
/dev/binderfs/vndbinderOpenWrt 宿主要先具备 binder / binderfs 内核能力。可以检查:
grep -E 'binder' /proc/filesystems /proc/misc /proc/devices 2>/dev/null || true
ls -ld /dev/binderfs 2>/dev/null || true
ls -l /dev/binderfs/binder-control /dev/binderfs/binder /dev/binderfs/hwbinder /dev/binderfs/vndbinder 2>/dev/null || true
mount | grep binder || true如果 /dev/binderfs 不存在,但内核已经启用了 CONFIG_ANDROID_BINDERFS=y,可以手动挂载做诊断:
mkdir -p /dev/binderfs
mount -t binder binder /dev/binderfs 2>/dev/null || true
ls -l /dev/binderfs 2>/dev/null || true如果挂载后只有 binder-control,没有 binder、hwbinder、vndbinder,新版 Waydroid 的 driver helper 通常会尝试通过 binder-control 创建节点,并把它们链接到 /dev/。
注意边界:OpenWrt 宿主需要有 binder/binderfs 内核能力,但不一定要长期把 binderfs 挂在宿主 /dev/binderfs。我们当前远程环境里,外层 LXC 配置没有显式 bind /dev/binderfs,Fedora 容器内仍然由 Waydroid helper 建出了:
/dev/binderfs/binder
/dev/binderfs/hwbinder
/dev/binderfs/vndbinder
/dev/binder -> /dev/binderfs/binder
/dev/hwbinder -> /dev/binderfs/hwbinder
/dev/vndbinder -> /dev/binderfs/vndbinder所以最终请至少确认 Fedora 外层容器里能看到这些设备:
lxc-attach -n waydroid -- /bin/bash -lc 'ls -l /dev/binderfs/binder /dev/binderfs/hwbinder /dev/binderfs/vndbinder /dev/binder /dev/hwbinder /dev/vndbinder 2>/dev/null || true'如果 Fedora 里没有 /dev/binderfs,请先看 Waydroid container 日志。可以选择让 Waydroid helper 在 Fedora 里挂载 binderfs,也可以在 OpenWrt 宿主挂载后通过第 4.2 节的 optional bind mount 传进 Fedora。不要把“OpenWrt 宿主当前没挂 /dev/binderfs”单独当成失败;关键是 Fedora 和内层 Android 最终能访问 binder/hwbinder/vndbinder。
3.2 OpenWrt 内核配置
在 OpenWrt source tree 中,x86 通用配置类似:
target/linux/x86/config-6.12
target/linux/x86/64/config-6.12我们验证过的关键配置如下。
target/linux/x86/config-6.12:
CONFIG_ANDROID=y
CONFIG_ANDROID_BINDER_IPC=y
CONFIG_ANDROID_BINDERFS=y
CONFIG_ANDROID_BINDER_DEVICES="binder,hwbinder,vndbinder"
CONFIG_PSI=y
CONFIG_MEMCG_V1=y
CONFIG_CPUSETS_V1=y
CONFIG_CGROUP_FREEZER=y
CONFIG_CGROUP_DEVICE=y
CONFIG_CGROUP_NET_PRIO=y
CONFIG_CGROUP_PERF=ytarget/linux/x86/64/config-6.12:
CONFIG_DMA_SHARED_BUFFER=y
CONFIG_DMABUF_HEAPS=y
CONFIG_DMABUF_HEAPS_SYSTEM=y
CONFIG_UDMABUF=y
CONFIG_IA32_EMULATION=y如果你的 OpenWrt kernel 版本不是 6.12,请按对应版本的 config 文件修改。本文只验证过 6.12 系列。
3.3 重要坑:OpenWrt 的 DMA-BUF debloat patch
OpenWrt 有一个 patch 可能影响 DMA-BUF heaps:
target/linux/generic/hack-6.12/904-debloat_dma_buf.patch我们遇到过这样的情况:
- 内核配置已经有
CONFIG_DMABUF_HEAPS_SYSTEM=y。 - 但是
/dev/dma_heap/system仍然没有出现。 - 追查后发现
system_heap.o没有链接进最终内核。 - 原因是这个 patch 改写了
drivers/dma-buf/heaps/Makefile。
验证通过的处理方式是:保留 OpenWrt 对 DMA_SHARED_BUFFER 的模块化改动,但不要改写:
drivers/dma-buf/heaps/Makefile也就是说,904-debloat_dma_buf.patch 里不应再包含类似这一段:
---- a/drivers/dma-buf/heaps/Makefile
-+++ b/drivers/dma-buf/heaps/Makefile
-@@ -1,3 +1,3 @@
- # SPDX-License-Identifier: GPL-2.0
--obj-$(CONFIG_DMABUF_HEAPS_SYSTEM) += system_heap.o
--obj-$(CONFIG_DMABUF_HEAPS_CMA) += cma_heap.o
-+dma-buf-objs-$(CONFIG_DMABUF_HEAPS_SYSTEM) += system_heap.o
-+dma-buf-objs-$(CONFIG_DMABUF_HEAPS_CMA) += cma_heap.o修改后重新编译内核,再确认 /dev/dma_heap/system。
3.4 构建内核
示例命令:
cd /path/to/openwrt-source
make target/linux/clean
make -j"$(nproc)" target/linux/compile V=s我们为了避免和系统原模块目录混用,给 Waydroid 内核使用过单独 release 名称,例如:
6.12.74-waydroid-v10这需要 OpenWrt build system 额外支持 WAYDROID_KERNEL_RELEASE。如果你没有做这个本地改动,就不要直接照抄这个变量。核心原则是:
运行中的 uname -r 必须和 /lib/modules/<uname -r> 匹配。3.5 部署内核,并修改 GRUB 配置
这一节以常见 OpenWrt x86_64 安装为例。我们验证过的机器是:
/boot 是单独 vfat 分区
GRUB 配置文件:/boot/grub/grub.cfg
内核文件:/boot/vmlinuz 或 /boot/vmlinuz-自定义名字
模块目录:/lib/modules/<uname -r>你的机器可能略有不同。请先确认:
mount | grep ' /boot '
ls -lah /boot /boot/grub 2>/dev/null || true
sed -n '1,220p' /boot/grub/grub.cfg 2>/dev/null || true
cat /proc/cmdline如果 GRUB 配置不在 /boot/grub/grub.cfg,请按你机器实际文件改。下面以 /boot/grub/grub.cfg 为例。
3.5.1 准备部署包
假设你已经在 OpenWrt build tree 编译出:
vmlinuz-waydroid
/lib/modules/<WAYDROID_KERNEL_RELEASE>/推荐给 Waydroid 内核一个明确名字,例如:
WAYDROID_KERNEL_RELEASE=6.12.74-waydroid-v10
/boot/vmlinuz-waydroid-v10
/lib/modules/6.12.74-waydroid-v10/核心原则:
GRUB 启动的 kernel 文件、uname -r、/lib/modules/<uname -r> 必须互相匹配。3.5.2 复制 kernel 和 modules 到 OpenWrt
在 OpenWrt 上先备份:
TS=$(date +%Y%m%d-%H%M%S)
mkdir -p /root/kernel-backup-$TS
cp -a /boot/grub/grub.cfg /root/kernel-backup-$TS/grub.cfg 2>/dev/null || true
cp -a /boot/vmlinuz /root/kernel-backup-$TS/vmlinuz.original 2>/dev/null || true如果你用 scp 从构建机传文件,示例:
# 在构建机执行,按你的实际文件位置替换
scp /path/to/vmlinuz-waydroid-v10 root@<OPENWRT_HOST>:/boot/vmlinuz-waydroid-v10
scp -r /path/to/lib/modules/6.12.74-waydroid-v10 root@<OPENWRT_HOST>:/lib/modules/在 OpenWrt 上确认:
ls -lah /boot/vmlinuz-waydroid-v10
ls -ld /lib/modules/6.12.74-waydroid-v103.5.3 不要覆盖原始 OpenWrt 启动项
不要直接把原来的 menuentry "OpenWrt" 改没。推荐做法是新增一个 OpenWrt-Waydroid 启动项,并保留原始 OpenWrt 和 OpenWrt (failsafe)。
先取当前 root 参数。最稳妥的方法是看现有 grub.cfg 里的 root=...:
grep -o 'root=[^ ]*' /boot/grub/grub.cfg | head -1也可以看当前内核命令行:
cat /proc/cmdline常见形式类似:
root=PARTUUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx3.5.4 编辑 /boot/grub/grub.cfg
示例 GRUB 配置:
serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1 --rtscts=off
terminal_input console serial; terminal_output console serial
set default="0"
set timeout="3"
search -l kernel -s root
menuentry "OpenWrt-Waydroid" {
linux /boot/vmlinuz-waydroid-v10 root=PARTUUID=替换成你的ROOT_PARTUUID rootwait console=tty1 console=ttyS0,115200n8 noinitrd
}
menuentry "OpenWrt-original" {
linux /boot/vmlinuz root=PARTUUID=替换成你的ROOT_PARTUUID rootwait console=tty1 console=ttyS0,115200n8 noinitrd
}
menuentry "OpenWrt-original-failsafe" {
linux /boot/vmlinuz failsafe=true root=PARTUUID=替换成你的ROOT_PARTUUID rootwait console=tty1 console=ttyS0,115200n8 noinitrd
}注意:
search -l kernel -s root是我们实机上的 OpenWrt x86 GRUB 写法。它依赖/boot分区 label 是kernel。如果你的/boot分区 label 不同,请按原始grub.cfg保留原来的search行。root=PARTUUID=...请从原始grub.cfg复制,不要猜。set timeout="3"比1更适合调试,给你一点时间在串口/显示器上选择回退项。cgroup_enable=memory cgroup_memory=1在我们后来的 6.12 / cgroup2 环境里不是关键项,内核还会提示 unknown kernel command line parameters。通用教程不建议新增它们。若你原配置已有,先不要随意删除;但新 Waydroid entry 可以不加。
可以用脚本方式追加 entry,但手工编辑前请备份:
cp -a /boot/grub/grub.cfg /boot/grub/grub.cfg.pre-waydroid-$(date +%Y%m%d-%H%M%S)
vi /boot/grub/grub.cfg
sync3.5.5 重启前检查
重启前确认:
ls -lah /boot/vmlinuz-waydroid-v10
ls -ld /lib/modules/6.12.74-waydroid-v10
grep -nA8 -B2 'OpenWrt-Waydroid' /boot/grub/grub.cfg
sync如果是远程路由器,请确保你有回退手段:
- 串口
- HDMI + 键盘
- IPMI / 远程 KVM
- 或确认 GRUB 保留原始启动项且 timeout 足够选择
不要在没有回退手段时盲目改默认启动项。
3.5.6 重启并验证
重启:
reboot回来后验证:
uname -r
ls -ld /lib/modules/$(uname -r)
ls -ld /dev/dri /dev/dma_heap /dev/dma_heap/system 2>/dev/null || true
grep -E 'dma_heap|udmabuf' /proc/misc /proc/devices 2>/dev/null || true
cat /proc/cmdline期望:
uname -r = 你的 WAYDROID_KERNEL_RELEASE
/lib/modules/$(uname -r) 存在
/dev/dri 存在
/dev/dma_heap/system 存在注意:/proc/cmdline 里的 BOOT_IMAGE=/boot/xxx 只是 GRUB 加载的文件名。远程实机里曾出现过文件名还像旧名字、但 uname -r 已经是新 release 的情况。判断是否部署成功,请优先看 uname -r、/lib/modules/$(uname -r)、/dev/dma_heap/system、GPU firmware/DRM 初始化日志,不要只靠 kernel 文件名。
如果启动失败,请从 GRUB 选择原始 OpenWrt-original 或 failsafe 项回退,再检查:
- kernel 文件名是否写错
root=PARTUUID=...是否复制错/lib/modules/<uname -r>是否缺失- OpenWrt DMA-BUF patch 是否导致
/dev/dma_heap/system没出现
3.6 OpenWrt 宿主运行时前置检查:LXC 工具、GPU 固件和设备节点
内核启动后,请先把这些运行时前置项查完,再创建 Fedora LXC。它们不是 Android 配置问题,漏掉时后面会表现成 KWin、Waydroid 或镜像挂载失败。
3.6.1 LXC 工具和容器存放目录
确认 OpenWrt 上有 LXC 工具:
command -v lxc-start
command -v lxc-attach
command -v lxc-stop
command -v lxc-info如果没有,请按你的 OpenWrt 包管理器安装。新 OpenWrt 可能是 apk,旧环境可能是 opkg。包名会随发行版变化,请用包搜索确认;我们记录里用过的方向是安装 LXC 主程序、模板和 start/attach/stop/info 这些命令。
本文示例把外层 Fedora LXC 放在:
/srv/lxc/waydroid有些 OpenWrt 的 LXC 默认容器目录不是 /srv/lxc。如果你执行:
lxc-start -n waydroid提示找不到容器,请不要改 rootfs。先显式指定 LXC 容器目录:
lxc-start -P /srv/lxc -n waydroid
lxc-attach -P /srv/lxc -n waydroid -- /bin/bash
lxc-info -P /srv/lxc -n waydroid
lxc-stop -P /srv/lxc -n waydroid后文为了简洁,仍写成 lxc-start -n waydroid。如果你的机器需要 -P /srv/lxc,请所有外层 LXC 命令都加上它。
3.6.2 GPU firmware 不要漏:OpenWrt 通常没有完整 firmware
OpenWrt 系统通常不会自带完整的 GPU firmware。内核里有 DRM/GPU 驱动,不等于 GPU 可以正常初始化。这里不要只理解成 Intel i915;不同机器要按实际 GPU 和 dmesg 里请求的文件来处理。
常见例子:
Intel 核显:/lib/firmware/i915/...
AMD GPU: /lib/firmware/amdgpu/...
其他 GPU:按内核日志里 request 的 firmware 文件名准备我们这台实机是 Intel iGPU,所以后面的具体命令以 i915 为例。通用原则是:在 Fedora LXC 或另一台 Linux 机器里安装对应 firmware 包,把 GPU 驱动实际请求的 firmware 文件复制到 OpenWrt 宿主的 /lib/firmware/...,然后重启 OpenWrt 让 GPU 驱动重新初始化。
现场观察到的事实是:
Fedora LXC:
linux-firmware-20260410-1.fc44.noarch
intel-gpu-firmware-20260410-1.fc44.noarch
/lib/firmware/i915/*.bin.xz
OpenWrt 宿主:
/lib/firmware/i915/*.bin
其中 tgl_guc_70.bin 是后来补进去的 firmware我们早期遇到过 i915 GuC firmware 加载失败,GPU 被内核标记为 wedged。Fedora 里 Mesa/Iris 可能会报一个容易误导的错误:
Kernel is too old (4.16+ required) or unusable for Iris当时宿主 dmesg 里的关键事实是类似:
i915 ... GuC firmware i915/tgl_guc_70.bin: fetch failed
i915 ... GuC initialization failed
i915 ... Failed to initialize GPU, declaring it wedged所以部署时请检查 OpenWrt 宿主:
dmesg | grep -i -E 'drm|gpu|i915|amdgpu|nouveau|xe|guc|huc|dmc|firmware|wedged|reset' | tail -160
ls -lah /lib/firmware 2>/dev/null | head -80
ls -lah /lib/firmware/i915 /lib/firmware/amdgpu 2>/dev/null | head -120 || true
ls -ld /dev/dri /dev/dri/renderD128 2>/dev/null || true如果看到 firmware 缺失、GPU wedged、GPU reset 失败,或 DRM render node 没有出现,请先补齐对应 GPU firmware,再重启 OpenWrt。不要先去改 Waydroid、Mesa 或 Android MediaCodec。
从 Fedora LXC 复制 GPU firmware 到 OpenWrt
如果你已经有 Fedora LXC,请先进入 Fedora 安装 firmware 包。Intel 机器可以安装:
# Fedora LXC 内,Intel 例子
dnf install -y linux-firmware intel-gpu-firmware
rpm -q linux-firmware intel-gpu-firmware
find /lib/firmware/i915 /usr/lib/firmware/i915 -maxdepth 1 -type f 2>/dev/null | grep -E 'guc|huc|dmc' | head -80如果是 AMD 或其它 GPU,请安装 Fedora 中对应的 firmware 包,并按 dmesg 请求的目录复制,例如 amdgpu/。本文没有验证非 Intel GPU,不能把 i915 文件名照搬到其它硬件。
然后回到 OpenWrt 宿主,从 Fedora rootfs 复制并解压。下面命令假设 Fedora rootfs 是 /srv/lxc/waydroid/rootfs,并以 Intel i915 为例:
# OpenWrt 宿主执行,Intel i915 例子
FEDORA_ROOTFS=/srv/lxc/waydroid/rootfs
FW_SUBDIR=i915
mkdir -p /lib/firmware/$FW_SUBDIR
# 优先复制压缩的 Fedora firmware,并在 OpenWrt 上解压成内核会请求的 .bin 文件。
# BusyBox xz/unxz 不一定安装;如果没有 unxz,可以在 Fedora 里先解压后再复制。
for srcdir in "$FEDORA_ROOTFS/lib/firmware/$FW_SUBDIR" "$FEDORA_ROOTFS/usr/lib/firmware/$FW_SUBDIR"; do
[ -d "$srcdir" ] || continue
find "$srcdir" -maxdepth 1 -type f \( -name '*.bin.xz' -o -name '*.bin' \) -print | while read -r f; do
base=$(basename "$f")
case "$base" in
*.bin.xz)
out=/lib/firmware/$FW_SUBDIR/${base%.xz}
if command -v unxz >/dev/null 2>&1; then
unxz -c "$f" > "$out"
elif command -v xz >/dev/null 2>&1; then
xz -dc "$f" > "$out"
else
echo "missing xz/unxz, cannot decompress $f" >&2
fi
;;
*.bin)
cp -f "$f" /lib/firmware/$FW_SUBDIR/
;;
esac
done
done
ls -lah /lib/firmware/$FW_SUBDIR | head -80
sync如果你的 GPU 不是 Intel,请把 FW_SUBDIR=i915 换成你的 GPU firmware 目录,例如 FW_SUBDIR=amdgpu,并确认文件名和 dmesg 请求的一致。
如果 OpenWrt 没有 xz / unxz,也可以在 Fedora LXC 内先解压到临时目录,再从 OpenWrt 宿主复制解压后的 .bin。Intel 例子:
# Fedora LXC 内,Intel i915 例子
mkdir -p /root/gpu-fw-unpacked/i915
find /lib/firmware/i915 /usr/lib/firmware/i915 -maxdepth 1 -type f -name '*.bin.xz' 2>/dev/null | while read -r f; do
xz -dc "$f" > /root/gpu-fw-unpacked/i915/$(basename "${f%.xz}")
done
# OpenWrt 宿主
mkdir -p /lib/firmware/i915
cp -f /srv/lxc/waydroid/rootfs/root/gpu-fw-unpacked/i915/*.bin /lib/firmware/i915/
sync完成后重启 OpenWrt,让 GPU 驱动在启动时重新加载 firmware:
reboot重启后看 dmesg。我们当前 Intel 实机上曾观察到类似这些成功事实:
i915 ... Finished loading DMC firmware i915/adlp_dmc.bin
i915 ... GT0: GuC firmware i915/tgl_guc_70.bin version 70.49.4
i915 ... GT0: GUC: submission enabled
i915 ... GT0: GUC: SLPC enabled
i915 ... GT0: GUC: RC enabled注意边界:当前 Intel 实机日志里曾还有 tgl_huc.bin 获取失败,但 GuC 已启用,GPU 没有 wedged,Waydroid 图形和后续验证可以继续。其他 GPU 请以你的 dmesg 为准。
如果你的 Fedora LXC 还没创建,也可以先用另一台 Fedora/Linux 机器安装对应 firmware 包,把 GPU 驱动请求的 firmware 文件复制到 OpenWrt。关键不是来源必须是 Fedora LXC,而是 OpenWrt 宿主的 /lib/firmware/... 里要有 GPU 驱动实际请求的 firmware。
3.6.3 loop、uinput、input、snd 设备
Waydroid 镜像挂载需要 loop 设备;输入和音频需要 /dev/uinput、/dev/input、/dev/snd。请在 OpenWrt 宿主先检查:
ls -l /dev/loop-control /dev/loop0 2>/dev/null || true
ls -ld /dev/uinput /dev/input /dev/snd 2>/dev/null || true如果缺 loop 设备,先加载或安装 OpenWrt 的 loop 内核模块:
modprobe loop 2>/dev/null || true
ls -l /dev/loop-control /dev/loop0 2>/dev/null || true如果缺输入设备,检查 uinput、evdev、HID 相关模块:
modprobe uinput 2>/dev/null || true
modprobe evdev 2>/dev/null || true
ls -ld /dev/uinput /dev/input 2>/dev/null || true如果需要 HDMI/声卡音频,检查 ALSA/HDA 相关模块:
modprobe snd-hda-intel 2>/dev/null || true
ls -ld /dev/snd 2>/dev/null || true包名同样按你的 OpenWrt 包源搜索。我们记录里出现过的方向包括 kmod-loop、kmod-uinput、kmod-input-evdev、kmod-hid、kmod-usb-hid、kmod-sound-hda-intel、kmod-sound-hda-codec-hdmi。这些名字只作为搜索提示,不保证每个 OpenWrt 分支完全一致。
4. 创建外层 Fedora 44 LXC
4.1 rootfs 创建说明
我们的记录没有完整保存最初创建 Fedora 44 rootfs 的每一条命令。所以本文不把某个 rootfs 生成命令写成唯一答案。
如果你的 OpenWrt LXC download template 可用,通用做法通常类似下面这样。请先用 --list 确认模板里确实有 Fedora 44,再执行创建:
# 可选:查看 download template 支持的 Fedora 版本
lxc-create -n list-test -t download -- --list | grep -i fedora || true
# 示例:创建 Fedora 44 amd64 rootfs
lxc-create -P /srv/lxc -n waydroid -t download -- --dist fedora --release 44 --arch amd64如果你的 OpenWrt 没有 lxc-create、没有 download template,或模板源不可用,请使用你现场可用的标准方式准备 Fedora 44 rootfs。最终只要放到类似:
/srv/lxc/waydroid/rootfs最低要求:
架构:x86_64
发行版:Fedora 44
容器类型:privileged LXC
init:systemd
网络:有 eth0,可以接 OpenWrt br-lan创建完成后,从 OpenWrt 进入 Fedora:
lxc-start -n waydroid
lxc-attach -n waydroid -- /bin/bash
cat /etc/fedora-release期望:
Fedora release 444.2 外层 LXC 配置:分两阶段写,先安全初始化,再正式运行
请不要第一次启动就直接使用桥接网络和 /sys:rw。建议分两阶段。
阶段 A:首次启动安全配置
编辑 OpenWrt 上的:
/srv/lxc/waydroid/config首次启动建议用这个最小安全配置:
lxc.arch = x86_64
lxc.rootfs.path = dir:/srv/lxc/waydroid/rootfs
lxc.uts.name = waydroid
lxc.tty.max = 2
lxc.pty.max = 1024
# 当前验证的是 privileged 容器。
lxc.seccomp.profile =
lxc.cap.drop =
# 首次启动不要桥接,不要共享宿主网络。
lxc.net.0.type = empty
# 首次启动保留 /sys 只读,先避免 Fedora udev/networkd 改动宿主网络设备。
lxc.mount.auto = proc:mixed sys:ro cgroup:mixed
# 设备透传可以先写好。
lxc.mount.entry = /dev/dri dev/dri none bind,create=dir 0 0
lxc.mount.entry = /dev/dma_heap dev/dma_heap none bind,create=dir,optional 0 0
lxc.mount.entry = /dev/uinput dev/uinput none bind,create=file 0 0
lxc.mount.entry = /dev/input dev/input none bind,create=dir 0 0
lxc.mount.entry = /dev/snd dev/snd none bind,create=dir 0 0
lxc.mount.entry = tmpfs dev/shm tmpfs create=dir,mode=01777,nosuid,nodev 0 0
# binderfs:可选。当前远程环境主要由 Fedora 内的 Waydroid helper 挂载/创建 binderfs。
# 如果你选择在 OpenWrt 宿主挂载 binderfs,也可以用这行 optional bind 传入 Fedora。
lxc.mount.entry = /dev/binderfs dev/binderfs none bind,create=dir,optional 0 0
# loop 设备:Waydroid 镜像挂载需要。
lxc.mount.entry = /dev/loop-control dev/loop-control none bind,create=file 0 0
lxc.mount.entry = /dev/loop0 dev/loop0 none bind,create=file 0 0
lxc.mount.entry = /dev/loop1 dev/loop1 none bind,create=file 0 0
lxc.mount.entry = /dev/loop2 dev/loop2 none bind,create=file 0 0
lxc.mount.entry = /dev/loop3 dev/loop3 none bind,create=file 0 0
# 给内层 Waydroid LXC 使用的辅助挂载点。
lxc.mount.entry = proc dev/.lxc/proc proc create=dir,optional 0 0
lxc.mount.entry = sys dev/.lxc/sys sysfs create=dir,optional 0 0启动并进入容器:
lxc-start -n waydroid
lxc-attach -n waydroid -- /bin/bash在 Fedora 里做三件事:
# 1. 禁用会接管网络的服务
systemctl mask systemd-networkd.service systemd-networkd.socket systemd-networkd-wait-online.service 2>/dev/null || true
systemctl disable --now systemd-networkd.service systemd-networkd.socket systemd-networkd-wait-online.service 2>/dev/null || true
systemctl mask systemd-resolved.service firewalld.service NetworkManager.service wpa_supplicant.service 2>/dev/null || true
systemctl disable --now systemd-resolved.service firewalld.service NetworkManager.service wpa_supplicant.service 2>/dev/null || true
# 2. 禁用网卡自动重命名
mkdir -p /etc/systemd/network
cat > /etc/systemd/network/10-keep-kernel-ifnames.link <<'EOF'
[Match]
OriginalName=*
[Link]
NamePolicy=keep
MACAddressPolicy=none
EOF
ln -sfn /dev/null /etc/systemd/network/99-default.link
# 3. 如果你要最保守,先屏蔽 udev。后面图形会话需要 udev 数据库时,再有控制地恢复。
systemctl mask systemd-udevd.service systemd-udevd-control.socket systemd-udevd-kernel.socket 2>/dev/null || true
systemctl disable --now systemd-udevd.service systemd-udevd-control.socket systemd-udevd-kernel.socket 2>/dev/null || true退出并停止容器:
exit
lxc-stop -n waydroid阶段 B:正式运行配置
完成阶段 A 后,再把外层 LXC 改成正式运行配置:
lxc.arch = x86_64
lxc.rootfs.path = dir:/srv/lxc/waydroid/rootfs
lxc.uts.name = waydroid
lxc.tty.max = 2
lxc.pty.max = 1024
lxc.seccomp.profile =
lxc.cap.drop =
# 正式运行:用 veth 接 OpenWrt 的 br-lan。不要用 none 共享宿主网络。
lxc.net.0.type = veth
lxc.net.0.veth.pair = lxc-waydroid
lxc.net.0.link = br-lan
lxc.net.0.flags = up
lxc.net.0.hwaddr = 00:16:3e:59:af:02
# 正式运行:Waydroid / nested LXC 需要可写 sysfs/cgroup 视图。
lxc.mount.auto = proc:mixed sys:rw cgroup:mixed
lxc.mount.entry = /dev/dri dev/dri none bind,create=dir 0 0
lxc.mount.entry = /dev/dma_heap dev/dma_heap none bind,create=dir,optional 0 0
lxc.mount.entry = /dev/uinput dev/uinput none bind,create=file 0 0
lxc.mount.entry = /dev/input dev/input none bind,create=dir 0 0
lxc.mount.entry = /dev/snd dev/snd none bind,create=dir 0 0
lxc.mount.entry = tmpfs dev/shm tmpfs create=dir,mode=01777,nosuid,nodev 0 0
# binderfs:可选。当前远程环境主要由 Fedora 内的 Waydroid helper 挂载/创建 binderfs。
# 如果你选择在 OpenWrt 宿主挂载 binderfs,也可以用这行 optional bind 传入 Fedora。
lxc.mount.entry = /dev/binderfs dev/binderfs none bind,create=dir,optional 0 0
lxc.mount.entry = /dev/loop-control dev/loop-control none bind,create=file 0 0
lxc.mount.entry = /dev/loop0 dev/loop0 none bind,create=file 0 0
lxc.mount.entry = /dev/loop1 dev/loop1 none bind,create=file 0 0
lxc.mount.entry = /dev/loop2 dev/loop2 none bind,create=file 0 0
lxc.mount.entry = /dev/loop3 dev/loop3 none bind,create=file 0 0
lxc.mount.entry = proc dev/.lxc/proc proc create=dir,optional 0 0
lxc.mount.entry = sys dev/.lxc/sys sysfs create=dir,optional 0 0如果你的 OpenWrt 主机没有 /dev/dma_heap/system,先回到第 3 节修内核。
4.3 验证外层容器设备
启动 Fedora LXC:
lxc-start -n waydroid
lxc-attach -n waydroid -- /bin/bash -lc '
cat /etc/fedora-release
ls -ld /dev/dri /dev/dma_heap /dev/dma_heap/system /dev/uinput /dev/input /dev/snd /dev/binderfs 2>/dev/null || true
ls -l /dev/binderfs/binder /dev/binderfs/hwbinder /dev/binderfs/vndbinder 2>/dev/null || true
'必须看到 /dev/dri 和 /dev/dma_heap/system。/dev/binderfs 可能要等 Waydroid container helper 运行后才出现;如果后面 Waydroid 报 binder service manager 不出现,再确认 Fedora 里是否已经有 /dev/binderfs/binder、/dev/binderfs/hwbinder、/dev/binderfs/vndbinder。
4.4 给 Fedora 一个最小可用网络:不用 NetworkManager/networkd
这一节很容易漏。前面我们已经把 NetworkManager / systemd-networkd / systemd-resolved 都 mask 掉了,所以 Fedora LXC 不会自己给 eth0 配 IP。可是第 5 节马上要 dnf install,这时 Fedora 必须先能联网。
最简单的做法是先手动配置一次:
# Fedora LXC 内
ip link set lo up || true
ip link set eth0 up
ip addr replace 192.168.1.237/24 dev eth0 # 按你的 FEDORA_LAN_ADDR 改
ip route replace default via 192.168.1.1 dev eth0
rm -f /etc/resolv.conf
echo 'nameserver 192.168.1.1' > /etc/resolv.conf
chmod 0644 /etc/resolv.conf
ping -c 2 192.168.1.1
curl -I https://mirrors.fedoraproject.org/ 2>/dev/null | head || true我们当前远程环境还保留了一个很小的 systemd oneshot,用来在启动早期做同样的安全网络初始化。它不启用 NetworkManager,也不启用 systemd-networkd。你可以按自己的 LAN 改成变量化版本:
文件:
/usr/local/bin/waydroid-lxc-network-safe.sh内容:
#!/bin/bash
set -euo pipefail
PHY=${WAYDROID_PARENT_IF:-eth0}
ADDR=${WAYDROID_LXC_IPV4:-192.168.1.237/24}
GW=${WAYDROID_LXC_GW:-192.168.1.1}
DNS=${WAYDROID_LXC_DNS:-$GW}
ip link set lo up || true
ip link set "$PHY" up
if [ -n "$ADDR" ]; then
ip addr replace "$ADDR" dev "$PHY"
fi
if [ -n "$GW" ]; then
ip route replace default via "$GW" dev "$PHY" || true
fi
rm -f /etc/resolv.conf
printf 'nameserver %s\n' "$DNS" > /etc/resolv.conf
chmod 0644 /etc/resolv.conf授权并写入 service:
chmod 0755 /usr/local/bin/waydroid-lxc-network-safe.sh
cat > /etc/systemd/system/waydroid-lxc-network-safe.service <<'EOF'
[Unit]
Description=Minimal safe network setup for Waydroid Fedora LXC
DefaultDependencies=no
After=local-fs.target
Before=network-pre.target network.target waydroid-container.service
Wants=network-pre.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/waydroid-lxc-network-safe.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
mkdir -p /etc/systemd/system/waydroid-lxc-network-safe.service.d
cat > /etc/systemd/system/waydroid-lxc-network-safe.service.d/10-lan.conf <<EOF
[Service]
Environment=WAYDROID_LXC_IPV4=192.168.1.237/24
Environment=WAYDROID_LXC_GW=192.168.1.1
Environment=WAYDROID_LXC_DNS=192.168.1.1
EOF
systemctl daemon-reload
systemctl enable --now waydroid-lxc-network-safe.service再给 waydroid-container.service 加一个 drop-in,确保手动或自动启动 Waydroid container 时,都会先跑这个最小网络 service:
mkdir -p /etc/systemd/system/waydroid-container.service.d
cat > /etc/systemd/system/waydroid-container.service.d/20-after-lxc-network-safe.conf <<'EOF'
[Unit]
Wants=waydroid-lxc-network-safe.service
After=waydroid-lxc-network-safe.service
EOF
systemctl daemon-reload如果后面要启用第 9 节的 waydroid-bridge-setup.service,它会把 eth0 放进 br0,并把地址迁到 br0。这个早期 service 的作用只是保证 Fedora 在没有网络管理器时也能先安装包、解析 DNS,并且让 Waydroid container 启动前网络状态是可预期的。
5. Fedora LXC 内安装基础软件
进入 Fedora:
lxc-attach -n waydroid -- /bin/bash安装工具和图形/媒体相关包。包名来自我们当前 Fedora 44 实机中已经存在的组件。
先启用 RPM Fusion。原因是 Fedora 默认仓库里的 VAAPI / Mesa / 编解码包会避开一部分有专利或授权限制的编解码能力;我们当前实机启用了 RPM Fusion free/nonfree,并安装了 mesa-va-drivers-freeworld 等包。
dnf install -y \
https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm \
https://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm
dnf repolist --enabled | grep -Ei 'rpmfusion|fedora|updates' || true然后安装基础软件和图形/媒体包:
dnf install -y \
git curl unzip lzip make gcc gcc-c++ \
python3 python3-devel python3-setuptools python3-Cython python3-pip python3-virtualenv \
lxc lxc-templates lxcfs \
android-tools \
linux-firmware intel-gpu-firmware \
mesa-dri-drivers mesa-libgbm mesa-libEGL mesa-libGL mesa-vulkan-drivers mesa-va-drivers-freeworld \
libva libva-utils libva-intel-media-driver intel-media-driver intel-mediasdk \
ffmpeg-free \
dbus dbus-broker dbus-daemon dbus-tools dbus-x11 \
pipewire pipewire-pulseaudio pipewire-utils wireplumber \
lxqt-session lxqt-wayland-session lxqt-sway-session lxqt-panel lxqt-notificationd lxqt-policykit lxqt-runner lxqt-powermanagement pcmanfm-qt qterminal \
kwin plasma-workspace-common xdg-desktop-portal xdg-desktop-portal-kde \
seatd acl Sunshine如果某个包名在你的 Fedora 仓库里不同,请用 Fedora 的包搜索确认。不要启用 NetworkManager / firewalld 来解决这个部署里的网络问题。
关于 VAAPI 解码器:
mesa-va-drivers-freeworld是 Fedora 上常见的“更完整但不那么 free”的 Mesa VAAPI 包,主要影响 Mesa VAAPI 驱动暴露的编解码能力。- Intel 实机还需要
intel-media-driver/libva-intel-media-driver,当前现场vainfo使用的是/usr/lib64/dri-nonfree/iHD_drv_video.so。 - 不同 GPU 要按实际驱动选择包。Intel iGPU 看 iHD/i965,AMD 通常看 Mesa VAAPI/freeworld,不能把 Intel 包照搬成唯一答案。
安装后建议验证:
rpm -qa | grep -Ei 'rpmfusion|libva|vaapi|intel-media|mesa.*va|ffmpeg|openh264' | sort
vainfo --display drm --device /dev/dri/renderD128 2>&1 | tee /root/vainfo-drm.log
grep -E 'VAProfileH264|VAProfileHEVC|VAProfileVP9|VAProfileAV1' /root/vainfo-drm.log || true我们当前实机能看到 VAProfileHEVCMain10、VAProfileVP9Profile2、VAProfileAV1Profile0。这些事实只说明 Fedora/VAAPI 层暴露了对应 profile;Android MediaCodec/Codec2 是否使用它,还要看后面 Bonus 章节的 Android logcat。
这里安装 linux-firmware / intel-gpu-firmware 不只是给 Fedora 自己用。OpenWrt 宿主通常没有完整 GPU firmware,前面第 3.6.2 节会用 Fedora rootfs 里的 firmware 文件作为来源,复制并解压到 OpenWrt 的 /lib/firmware/...。intel-gpu-firmware 是当前 Intel 实机的例子;其它 GPU 请安装对应 firmware 包。
禁用不需要的网络管理服务:
systemctl mask NetworkManager systemd-networkd systemd-resolved firewalld wpa_supplicant 2>/dev/null || true
systemctl disable --now NetworkManager systemd-networkd systemd-resolved firewalld wpa_supplicant 2>/dev/null || true验证:
for s in NetworkManager systemd-networkd systemd-resolved firewalld wpa_supplicant; do
printf '%s: ' "$s"
systemctl is-active "$s" 2>/dev/null || true
done期望都是 inactive。
6. 安装 upstream Waydroid / libgbinder / gbinder-python
Fedora 包里的 Waydroid/libgbinder 版本记录为:
waydroid RPM metadata: 1.6.2
libgbinder RPM metadata: 1.1.43
python3-gbinder RPM metadata: 1.3.0但 Android 16 / API 36 需要 Waydroid 正确使用:
binder_protocol = aidl3
service_manager_protocol = aidl6我们验证过用 upstream source 覆盖安装。有效 commits:
Waydroid: 73fed11b465138130eb7067c9645db81b32ac929
libgbinder: 680bedcc335586502980f9a28614416352e6f369
gbinder-python: 86b8feba4cacd0952b010d1c3af6a29a0c146ced在 Fedora LXC 内执行:
systemctl --user -M user@ stop waydroid-full-lxqt.service 2>/dev/null || true
waydroid session stop 2>/dev/null || true
systemctl stop waydroid-container.service 2>/dev/null || true
mkdir -p /usr/local/src
git clone https://github.com/waydroid/waydroid.git /usr/local/src/waydroid-upstream || true
cd /usr/local/src/waydroid-upstream
git fetch --prune origin
git checkout 73fed11b465138130eb7067c9645db81b32ac929
make install PREFIX=/usr USE_SYSTEMD=1 USE_DBUS_ACTIVATION=1
systemctl daemon-reload
git clone https://github.com/mer-hybris/libgbinder.git /usr/local/src/libgbinder-upstream || true
cd /usr/local/src/libgbinder-upstream
git fetch --prune origin --tags
git checkout 680bedcc335586502980f9a28614416352e6f369
make clean || true
make -j"$(nproc)" release pkgconfig
make install-dev PREFIX=/usr LIBDIR=/usr/lib64
ldconfig
git clone https://github.com/waydroid/gbinder-python.git /usr/local/src/gbinder-python-upstream || true
cd /usr/local/src/gbinder-python-upstream
git fetch --prune origin --tags
git checkout 86b8feba4cacd0952b010d1c3af6a29a0c146ced
python3 setup.py build_ext --inplace
python3 setup.py install检查:
python3 - <<'PY'
import gbinder
print(gbinder.__file__)
PY
pkg-config --modversion libgbinder期望类似:
/usr/local/lib64/python3.14/site-packages/gbinder.cpython-314-x86_64-linux-gnu.so
1.1.457. 安装 WayDroid-ATV 镜像
WayDroid-ATV 已经内置了 arm 转译层。不再需要手动运行脚本安装啦!
WayDroid-ATV 现在有比较人类友好的部署方式。推荐优先使用 Waydroid 命令从 WayDroid-ATV OTA server 初始化和下载镜像;手动下载 zip 只作为备用。
7.1 版本选择:新版 A16 vs 旧版 A13
推荐版本:
WayDroid-ATV Android 16 / Lineage 23.0 / waydroid_tv_x86_64
OTA: https://waydroid-atv.github.io/ota/a16-tv/system
https://waydroid-atv.github.io/ota/a16-tv/vendor新版优点:
- 支持当前我们验证过的 VAAPI / Codec2 方向。
- 适合继续做 HEVC Main10 / P010 / Android media 测试。
新版注意点:
- 需要比较新的 Waydroid / libgbinder / gbinder-python。Fedora 包里的 Waydroid 1.6.2 metadata 不够说明实际能力。请按第 6 节拉 upstream source 自己编译安装。
- 如果
waydroid app list卡住、waydroidplatform不通,优先检查第 6 节的service_manager_protocol = aidl6和 libgbinder 版本。
旧版测试用镜像:
WayDroid-ATV Android 13 release 20260106
https://github.com/WayDroid-ATV/waydroid-androidtv-builds/releases/tag/20260106
system: lineage-20.0-20260105-GAPPS-waydroid_tv_x86_64-system.zip
vendor: lineage-20.0-20260105-MAINLINE-waydroid_tv_x86_64-vendor.zip旧版适合用来验证“OpenWrt + Fedora LXC + Waydroid 能不能基本跑通”。但旧版不适合拿来验证当前视频硬解方案:它不具备我们后来验证的最新版 VAAPI / Codec2 / P010 行为。
7.2 方法一:用 waydroid init 接 WayDroid-ATV OTA server(推荐)
在 Fedora LXC 内执行。若你是 root,不需要 sudo;如果是普通用户,请按 Waydroid 常规方式加 sudo。
GApps 版本:
waydroid init -f \
-c https://waydroid-atv.github.io/ota/a16-tv/system \
-v https://waydroid-atv.github.io/ota/a16-tv/vendor \
-r lineage \
-s GAPPS
waydroid upgrade如果你想要非 GApps build,把 GAPPS 换成 VANILLA:
waydroid init -f \
-c https://waydroid-atv.github.io/ota/a16-tv/system \
-v https://waydroid-atv.github.io/ota/a16-tv/vendor \
-r lineage \
-s VANILLA
waydroid upgrade下载完成后确认:
sed -n '1,160p' /var/lib/waydroid/waydroid.cfg
ls -lah /var/lib/waydroid/images /etc/waydroid-extra/images 2>/dev/null || true如果你希望固定使用 /etc/waydroid-extra/images,请确认:
images_path = /etc/waydroid-extra/images否则按 waydroid.cfg 实际 images_path 继续,不要猜。
7.3 方法二:手动下载 zip 并安装
如果 OTA 下载不可用,可以手动下载 SourceForge 上的:
lineage-23.0-xxxxxxx-GAPPS-waydroid_tv_xxxxx-system.zip
lineage-23.0-xxxxxxx-MAINLINE-waydroid_tv_xxxxx-vendor.zip如果想要非 GApps build,把 GAPPS 换成 VANILLA。
解压出:
system.img
vendor.img复制到 Fedora LXC 内:
mkdir -p /etc/waydroid-extra/images/
cp /path/to/system.img /etc/waydroid-extra/images/system.img
cp /path/to/vendor.img /etc/waydroid-extra/images/vendor.img然后重新初始化:
waydroid init -f我们的早期 A16 实机记录使用过 release 20260403,zip sha256 是:
system zip: ddf5995f9104fe7164ad326def8bbed5cbf6340b55da5012e240b5855c44953b
vendor zip: 8b8b176c7d3b34719889f3c891d86b98af8dbbb2f737a970e89302fa3d0cf077这只是我们当时验证的版本,不代表你部署时必须固定到这个日期。通用部署建议优先使用 7.2 的 OTA server。
7.4 全新初始化时的清理边界
如果是全新初始化,可以停止 Waydroid 后清理旧数据:
waydroid session stop 2>/dev/null || true
systemctl stop waydroid-container.service 2>/dev/null || true
rm -rf /home/user/.local/share/waydroid
rm -rf /var/lib/waydroid/overlay /var/lib/waydroid/overlay_rw /var/lib/waydroid/lxc如果你是在维护已有环境,不要直接删除这些目录。overlay_rw/vendor 里可能有已经部署的 media/Codec2 修复文件。
7.5 配置 Waydroid 使用正确协议和镜像目录
确认 /var/lib/waydroid/waydroid.cfg 有:
[waydroid]
arch = x86_64
vendor_type = MAINLINE
mount_overlays = True
binder = binder
vndbinder = vndbinder
hwbinder = hwbinder
binder_protocol = aidl3
service_manager_protocol = aidl6images_path 可以是 OTA 初始化生成的位置,也可以是:
images_path = /etc/waydroid-extra/images请以你实际文件位置为准。
7.6 初始化后立刻禁用 Android 蓝牙
第一次能进入 Android shell 后,请按第 0.5 节禁用蓝牙:
waydroid shell -- sh -c '
setprop persist.bluetooth.disabled true
settings put global bluetooth_on 0
settings put global ble_scan_always_enabled 0
cmd bluetooth_manager disable 2>/dev/null || true
pm disable-user --user 0 com.android.bluetooth 2>/dev/null || true
pm disable com.android.bluetooth 2>/dev/null || true
'不要等系统长时间运行后再处理。这个问题和 OpenWrt /tmp 是 tmpfs 叠在一起时,可能会变成内存压力和重启问题。
8. 配置 Fedora 图形会话:systemd user + LXQt + KWin
8.1 创建桌面用户,并加入图形/音频/输入需要的 groups
当前验证过的桌面用户状态是:
uid=1000(user) gid=1000(user) groups=1000(user),10(wheel),39(video),63(audio),104(input),105(render),992(seat)这些 groups 很重要:
video/render:访问 DRM/KMS、render node。audio:访问 ALSA/PipeWire 相关音频设备。input:访问输入设备。seat:配合 seatd / 桌面会话管理座席设备。wheel:方便需要管理员权限时使用。
不同 Fedora rootfs 里的 group 数字可能不同,不要手写 gid。请按 group 名称添加。
如果还没有 user:
useradd -m -u 1000 user 2>/dev/null || true确保必要 groups 存在,并把用户加入:
for g in wheel video audio input render seat; do
getent group "$g" >/dev/null || groupadd "$g"
usermod -aG "$g" user
done
loginctl enable-linger user
id user期望 id user 至少包含:
wheel video audio input render seat音频设备 ACL:
setfacl -m u:user:rw /dev/snd/* 2>/dev/null || true8.2 写入 waydroid-full-lxqt.service
文件:
/home/user/.config/systemd/user/waydroid-full-lxqt.service创建目录:
mkdir -p /home/user/.config/systemd/user
chown -R user:user /home/user/.config写入:
[Unit]
Description=Full LXQt Wayland desktop session for Waydroid
After=dbus.socket pipewire.socket pipewire-pulse.socket
Wants=dbus.socket pipewire.socket pipewire-pulse.socket graphical-session.target
BindsTo=graphical-session.target
Before=graphical-session.target
[Service]
Type=simple
UnsetEnvironment=WLR_LIBINPUT_NO_DEVICES WAYLAND_DISPLAY DISPLAY SWAYSOCK I3SOCK
Environment=XDG_RUNTIME_DIR=/run/user/1000
Environment=XDG_SESSION_TYPE=wayland
Environment=LIBSEAT_BACKEND=noop
Environment=QT_QPA_PLATFORM=wayland
ExecStart=/usr/sbin/startlxqtwayland
Restart=on-failure
RestartSec=2s
[Install]
WantedBy=default.target启用:
runuser -u user -- env \
XDG_RUNTIME_DIR=/run/user/1000 \
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus \
systemctl --user daemon-reload
runuser -u user -- env \
XDG_RUNTIME_DIR=/run/user/1000 \
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus \
systemctl --user enable --now waydroid-full-lxqt.service8.3 Waydroid UI 启动脚本
当前验证过的最终方式是:waydroid-autostart-session.sh 和 sunshine 作为 LXQt 会话内进程运行,而不是作为单独 enabled user service 运行。
写入:
/usr/local/bin/waydroid-autostart-session.sh内容:
#!/bin/bash
set -euo pipefail
if [ -z "${XDG_RUNTIME_DIR:-}" ] || [ -z "${DBUS_SESSION_BUS_ADDRESS:-}" ]; then
echo "XDG desktop session environment is incomplete" >&2
exit 1
fi
for _ in $(seq 1 90); do
[ -S "$XDG_RUNTIME_DIR/bus" ] && [ -n "${WAYLAND_DISPLAY:-}" ] && [ -S "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ] && break
sleep 1
done
if [ -z "${WAYLAND_DISPLAY:-}" ] || [ ! -S "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ]; then
echo "Wayland session environment is not ready" >&2
exit 1
fi
while true; do
if ! systemctl is-active --quiet waydroid-container.service; then
systemctl start waydroid-container.service || true
fi
if waydroid status 2>/dev/null | grep -Eq 'Session:[[:space:]]*RUNNING'; then
sleep 30
continue
fi
/usr/local/bin/waydroid-show-full-ui-after-start.sh >/tmp/waydroid-show-full-ui-after-start.log 2>&1 &
/usr/sbin/waydroid session start || true
sleep 5
done写入:
/usr/local/bin/waydroid-show-full-ui-after-start.sh内容:
#!/bin/bash
set -euo pipefail
if [ -z "${XDG_RUNTIME_DIR:-}" ] || [ -z "${DBUS_SESSION_BUS_ADDRESS:-}" ] || [ -z "${WAYLAND_DISPLAY:-}" ]; then
echo "XDG desktop session environment is incomplete" >&2
exit 1
fi
for _ in $(seq 1 90); do
if waydroid status 2>/dev/null | grep -Eq 'Container:[[:space:]]*RUNNING'; then
break
fi
sleep 1
done
sleep 5
/usr/sbin/waydroid show-full-ui || true授权:
chmod 0755 /usr/local/bin/waydroid-autostart-session.sh /usr/local/bin/waydroid-show-full-ui-after-start.sh你需要让 LXQt session 自己启动:
/usr/local/bin/waydroid-autostart-session.sh
/usr/sbin/sunshine我们当前实机是由 LXQt session 拉起它们。不同 LXQt 配置可以用 autostart desktop 文件、LXQt session 设置或等效方式。重点是不要手动伪造不完整的 XDG session。
8.4 总启动脚本
写入:
/usr/local/bin/start-waydroid.sh内容:
#!/bin/bash
set -euo pipefail
loginctl enable-linger user >/dev/null 2>&1 || true
systemctl daemon-reload
systemctl enable --now waydroid-bridge-setup.service waydroid-container.service
# 用户所属 groups 和设备权限请在第 8.1 节部署时一次性设置。
# 启动脚本不要每次反复 usermod / setfacl。
runuser -u user -- env \
XDG_RUNTIME_DIR=/run/user/1000 \
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus \
systemctl --user daemon-reload
runuser -u user -- env \
XDG_RUNTIME_DIR=/run/user/1000 \
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus \
systemctl --user enable --now waydroid-full-lxqt.service
runuser -u user -- env \
XDG_RUNTIME_DIR=/run/user/1000 \
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus \
systemctl --user --no-pager status waydroid-full-lxqt.service授权:
chmod 0755 /usr/local/bin/start-waydroid.sh9. 配置桥接网络
目标:让 Android 直接出现在 OpenWrt LAN 中,而不是使用默认 waydroid0 NAT。
最终结构:
OpenWrt br-lan
└── 外层 Fedora eth0
└── Fedora 内 br0
├── Fedora 自己的 IPv4 地址
└── Waydroid Android veth
└── Android eth0 通过 DHCP 获取 LAN 地址9.1 Fedora 内 bridge setup 脚本
写入:
/usr/local/sbin/waydroid-bridge-setup内容:
#!/usr/bin/env bash
set -euo pipefail
BR=${WAYDROID_BRIDGE:-br0}
PHY=${WAYDROID_PARENT_IF:-eth0}
DEFAULT_IPV4=${WAYDROID_BRIDGE_IPV4:-}
DEFAULT_GW=${WAYDROID_BRIDGE_GW:-}
if ! ip link show "$PHY" >/dev/null 2>&1; then
echo "parent interface $PHY does not exist" >&2
exit 1
fi
PHY_MAC=$(cat /sys/class/net/$PHY/address)
PHY_MTU=$(cat /sys/class/net/$PHY/mtu)
CURRENT4=$(ip -o -4 addr show dev "$BR" scope global 2>/dev/null | awk 'NR==1{print $4}' || true)
PHY4=$(ip -o -4 addr show dev "$PHY" scope global 2>/dev/null | awk 'NR==1{print $4}' || true)
GW4=$(ip -4 route show default dev "$BR" 2>/dev/null | awk 'NR==1{for(i=1;i<=NF;i++) if($i=="via") print $(i+1)}' || true)
PHY_GW4=$(ip -4 route show default dev "$PHY" 2>/dev/null | awk 'NR==1{for(i=1;i<=NF;i++) if($i=="via") print $(i+1)}' || true)
if ! ip link show "$BR" >/dev/null 2>&1; then
ip link add name "$BR" type bridge
fi
ip link set dev "$BR" address "$PHY_MAC" || true
ip link set dev "$BR" mtu "$PHY_MTU" || true
ip link set dev "$BR" type bridge stp_state 0 forward_delay 0 || true
ip link set dev "$BR" up
if [ "$(basename "$(readlink -f /sys/class/net/$PHY/master 2>/dev/null || true)" || true)" != "$BR" ]; then
ip addr flush dev "$PHY" || true
ip link set dev "$PHY" master "$BR"
fi
ip link set dev "$PHY" up
ADDR="${CURRENT4:-${PHY4:-$DEFAULT_IPV4}}"
GW="${GW4:-${PHY_GW4:-$DEFAULT_GW}}"
if [ -n "$ADDR" ] && ! ip -4 addr show dev "$BR" | grep -q "${ADDR%/*}"; then
ip addr add "$ADDR" dev "$BR" 2>/dev/null || true
fi
if [ -n "$GW" ]; then
ip route replace default via "$GW" dev "$BR" 2>/dev/null || true
fi
DNS="${WAYDROID_BRIDGE_DNS:-$GW}"
if [ -n "$DNS" ]; then
rm -f /etc/resolv.conf
printf 'nameserver %s\n' "$DNS" > /etc/resolv.conf
chmod 0644 /etc/resolv.conf
fi
ip link set dev "$BR" promisc on || true授权:
chmod 0755 /usr/local/sbin/waydroid-bridge-setup9.2 systemd 服务
写入:
/etc/systemd/system/waydroid-bridge-setup.service内容:
[Unit]
Description=Prepare br0 bridge for bridged Waydroid Android container
DefaultDependencies=no
After=local-fs.target waydroid-lxc-network-safe.service
Before=waydroid-container.service
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/waydroid-bridge-setup
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target按你的 LAN 写入 drop-in:
mkdir -p /etc/systemd/system/waydroid-bridge-setup.service.d
cat > /etc/systemd/system/waydroid-bridge-setup.service.d/10-lan.conf <<EOF2
[Service]
Environment=WAYDROID_BRIDGE_IPV4=${FEDORA_LAN_ADDR:-192.168.1.237/24}
Environment=WAYDROID_BRIDGE_GW=${OPENWRT_LAN_GATEWAY:-192.168.1.1}
Environment=WAYDROID_BRIDGE_DNS=${OPENWRT_LAN_GATEWAY:-192.168.1.1}
EOF2启用:
systemctl daemon-reload
systemctl enable --now waydroid-bridge-setup.service9.3 修改 Waydroid inner LXC 网络模板
需要把 Waydroid inner LXC 从默认 waydroid0 改到 br0。涉及文件:
/var/lib/waydroid/lxc/waydroid/config
/usr/lib/waydroid/data/configs/config_3
/usr/lib/waydroid/data/configs/config_1你要确认这些文件里的 Waydroid 网络配置使用:
lxc.net.0.type = veth
lxc.net.0.link = br0
lxc.net.0.flags = up
lxc.net.0.mtu = 1450不同 Waydroid 版本模板内容可能略有不同。修改前请备份:
TS=$(date +%Y%m%d-%H%M%S)
mkdir -p /root/waydroid-bridge-backup-$TS
cp -a /var/lib/waydroid/lxc/waydroid/config /root/waydroid-bridge-backup-$TS/ 2>/dev/null || true
cp -a /usr/lib/waydroid/data/configs/config_1 /root/waydroid-bridge-backup-$TS/ 2>/dev/null || true
cp -a /usr/lib/waydroid/data/configs/config_3 /root/waydroid-bridge-backup-$TS/ 2>/dev/null || true验证:
ip -br addr
bridge link
waydroid shell -- sh -c 'ip addr show eth0; dumpsys ethernet | head -80'waydroid status 仍可能显示 IP address: UNKNOWN。桥接模式下请以 Android eth0 和 OpenWrt DHCP lease 为准。
10. Waydroid 内层 LXC:config_nodes、cgroup2 子树和 cgroup:rw
10.1 自动生成的 config_nodes:不要漏 DMA-BUF 和 binderfs
Waydroid 会根据 Fedora 外层容器里能看到的设备生成内层 Android LXC 的设备挂载,文件是:
/var/lib/waydroid/lxc/waydroid/config_nodes当前记录里,/dev/dma_heap/system 是由 Waydroid helper 扫描 /dev/dma_heap/* 后自动写入 config_nodes 的。外层 Fedora 看不到 /dev/dma_heap/system 时,内层 Android 通常也看不到它。
请在启动 Waydroid 前后检查:
sed -n '1,220p' /var/lib/waydroid/lxc/waydroid/config_nodes 2>/dev/null || true
grep -E 'dma_heap|binder|hwbinder|vndbinder|dri|uinput|snd' /var/lib/waydroid/lxc/waydroid/config_nodes 2>/dev/null || true期望至少能看到类似:
lxc.mount.entry = /dev/dri/renderD128 dev/dri/renderD128 none bind,create=file,optional 0 0
lxc.mount.entry = /dev/dma_heap/system dev/dma_heap/system none bind,create=file,optional 0 0
lxc.mount.entry = /dev/binderfs/binder dev/binder none bind,create=file,optional 0 0
lxc.mount.entry = /dev/binderfs/hwbinder dev/hwbinder none bind,create=file,optional 0 0
lxc.mount.entry = /dev/binderfs/vndbinder dev/vndbinder none bind,create=file,optional 0 0不同 Waydroid 版本生成的顺序和附加参数可能不同,不要只按行号判断。重点是:
- Android 内层要能看到
/dev/dri/renderD128。 - Android 内层要能看到
/dev/dma_heap/system。 - Android 内层要通过
/dev/binder、/dev/hwbinder、/dev/vndbinder使用宿主 binderfs 对应设备。
如果你是在修复宿主内核后才出现 /dev/dma_heap/system,请重启 Waydroid container/session,让 config_nodes 重新生成:
waydroid session stop 2>/dev/null || true
systemctl restart waydroid-container.service
sed -n '1,220p' /var/lib/waydroid/lxc/waydroid/config_nodes 2>/dev/null || true如果你曾经手动删除过 config_nodes,也先用 Waydroid 自己的 container start 流程重新生成。不要把缺设备的问题误判成 Android media、SurfaceFlinger 或播放器问题。
10.2 cgroup2 子树和 cgroup:rw
这一节非常重要。外层 Fedora LXC 能启动,不代表 Waydroid 的内层 Android LXC 能稳定启动。Android 里面的 init、libprocessgroup 和 lmkd 需要在 cgroup2 上创建自己的 uid_* / pid_* 子目录;所以内层 LXC 必须在一个可委托的 cgroup2 子树里运行,并看到可写 cgroup。
如果内层 Waydroid LXC 仍然是只读 cgroup:
lxc.mount.auto = cgroup:ro sys:ro proc遇到 Android 启动几秒退出、libprocessgroup 报只读、lmkd 反复重启时,请先修这里。
10.2.1 修改 Waydroid LXC config 模板
需要同时改当前生成文件和模板文件:
/var/lib/waydroid/lxc/waydroid/config
/usr/lib/waydroid/data/configs/config_base请注意:waydroid init -f、waydroid upgrade、包升级、或某些重新生成 LXC 配置的操作,可能把 /var/lib/waydroid/lxc/waydroid/config 和 /usr/lib/waydroid/data/configs/config_base 改回默认值。我们远程环境里就观察到过运行态又回到:
lxc.mount.auto = cgroup:ro sys:ro proc
lxc.hook.post-stop = /dev/nullAndroid 当时仍能运行,但宿主 dmesg 里有不少 libprocessgroup 找不到 /sys/fs/cgroup/system/uid_*、/dev/blkio、/dev/cpuset 的噪音。通用部署建议仍按下面的 cgroup:rw 和明确 cgroup2 子树做,配置完后一定重新 grep 确认。
把:
lxc.mount.auto = cgroup:ro sys:ro proc改成:
lxc.mount.auto = cgroup:rw sys:ro proc并加入:
# Keep Android in an explicitly delegated cgroup2 subtree.
# The payload runs in a leaf so Android init can create uid_* and process-group children below it.
lxc.cgroup.dir.container = waydroid-android
lxc.cgroup.dir.monitor = waydroid-android-monitor
lxc.cgroup.dir.monitor.pivot = waydroid-android-monitor-pivot
lxc.cgroup.dir.container.inner = payload
lxc.hook.pre-start = /var/lib/waydroid/lxc/waydroid/waydroid-cgroup2-delegate.sh同时检查是否还有这行:
lxc.hook.post-stop = /dev/null我们的早期日志里,这行会让 LXC 在 Android 退出时尝试执行 /dev/null,并报:
Script exec /dev/null ... Permission denied
Script exited with status 126
Failed to run lxc.hook.post-stop当前稳定配置里已经把它注释掉:
# lxc.hook.post-stop disabled如果你看到同样的 status 126,请删除或注释 lxc.hook.post-stop = /dev/null。这个报错不一定是 Android 崩溃根因,但会干扰判断。
10.2.2 写入 pre-start delegation 脚本
文件:
/var/lib/waydroid/lxc/waydroid/waydroid-cgroup2-delegate.sh内容:
#!/bin/sh
set -eu
LOG=/var/lib/waydroid/cgroup2-delegate.log
ROOT=/sys/fs/cgroup
CTR="$ROOT/waydroid-android"
PAYLOAD="$CTR/payload"
MONITOR="$ROOT/waydroid-android-monitor"
PIVOT="$ROOT/waydroid-android-monitor-pivot"
{
echo "=== $(date -Is) pre-start cgroup2 delegation ==="
echo "self=$(cat /proc/self/cgroup 2>/dev/null || true)"
findmnt -n -T "$ROOT" 2>/dev/null || true
if [ ! -f "$ROOT/cgroup.controllers" ]; then
echo "no cgroup2 controllers file at $ROOT"
exit 0
fi
# LXC should create final monitor/payload cgroups itself.
# Remove stale empty dirs from previous failed tests, but do not pre-create leaf cgroups.
for stale in "$PAYLOAD" "$CTR" "$MONITOR" "$PIVOT"; do
if [ -d "$stale" ]; then
rmdir "$stale" 2>/dev/null || true
fi
done
# Open every available cgroup2 controller at the root level.
if [ -f "$ROOT/cgroup.controllers" ] && [ -w "$ROOT/cgroup.subtree_control" ]; then
for controller in $(cat "$ROOT/cgroup.controllers"); do
echo "+$controller" > "$ROOT/cgroup.subtree_control" 2>/dev/null || true
done
fi
echo "$ROOT controllers=$(cat "$ROOT/cgroup.controllers" 2>/dev/null || true)"
echo "$ROOT subtree=$(cat "$ROOT/cgroup.subtree_control" 2>/dev/null || true)"
echo "$ROOT procs=$(wc -l < "$ROOT/cgroup.procs" 2>/dev/null || echo '?')"
echo "planned container=$CTR inner=payload monitor=$MONITOR pivot=$PIVOT"
} >> "$LOG" 2>&1
exit 0授权:
chmod 0755 /var/lib/waydroid/lxc/waydroid/waydroid-cgroup2-delegate.sh10.2.3 验证 cgroup 子树
启动 Waydroid 后先确认配置没有被重新生成覆盖:
grep -nE 'lxc.mount.auto|lxc.cgroup.dir|pre-start|post-stop' /var/lib/waydroid/lxc/waydroid/config /usr/lib/waydroid/data/configs/config_base 2>/dev/null || true再检查 cgroup 子树:
sed -n '1,160p' /var/lib/waydroid/cgroup2-delegate.log 2>/dev/null || true
find /sys/fs/cgroup -maxdepth 3 -type d | grep -E 'waydroid-android|payload|monitor' || true
for d in /sys/fs/cgroup/waydroid-android /sys/fs/cgroup/waydroid-android/payload; do
[ -d "$d" ] || continue
echo "-- $d --"
echo "controllers=$(cat "$d/cgroup.controllers" 2>/dev/null || true)"
echo "subtree=$(cat "$d/cgroup.subtree_control" 2>/dev/null || true)"
echo "procs=$(cat "$d/cgroup.procs" 2>/dev/null | tr '\n' ' ' || true)"
done期望能看到类似:
/sys/fs/cgroup controllers=cpuset cpu io memory pids rdma
/sys/fs/cgroup subtree=cpuset cpu io memory pids rdma
/sys/fs/cgroup/waydroid-android
/sys/fs/cgroup/waydroid-android/payload如果 Android 日志里还有:
libprocessgroup: Failed to make and chown /sys/fs/cgroup/uid_...: Read-only file system说明内层 Waydroid LXC 仍然没有拿到可写 cgroup2 子树。先修这一节,不要先去改播放器或 media 配置。
10.2.4 lmkd 要使用 cgroup2 / PSI 策略
如果 lmkd 报:
Old kill strategy can only be used with v1 cgroup hierarchy
Kernel does not support memory pressure events or in-kernel low memory killer
critical process 'lmkd' exited ... before boot completed需要让 Android 使用 PSI / cgroup2 策略。早期稳定配置用过这些 vendor properties:
ro.lmk.use_new_strategy=true
ro.lmk.use_psi=true
ro.lmk.use_minfree_levels=false
ro.lmk.kill_heaviest_task=true
ro.lmk.psi_partial_stall_ms=70
ro.lmk.psi_complete_stall_ms=700
persist.device_config.lmkd_native.use_psi=true这些属性是否需要放入当前镜像,要看你使用的 Android build。原则是:不要让 lmkd 走旧的 v1 memcg / vmpressure 策略。
完成 10.2 后,再重启 Waydroid:
waydroid session stop || true
systemctl restart waydroid-container.service
/usr/local/bin/start-waydroid.sh11. 启动
11.1 OpenWrt 启动外层 Fedora
lxc-start -n waydroid进入 Fedora:
lxc-attach -n waydroid -- /bin/bash11.2 Fedora 内启动 Waydroid 和桌面会话
/usr/local/bin/start-waydroid.sh检查:
systemctl is-active waydroid-container.service
systemctl --user -M user@ is-active waydroid-full-lxqt.service
waydroid status期望:
active
active
Session: RUNNING
Container: RUNNING11.3 如果 waydroid-autostart.service 和 sunshine-wayland.service 是 inactive
这不一定是错。我们当前验证过的状态是:
waydroid-full-lxqt.serviceactive。sunshine是 LXQt session 下的进程。waydroid-autostart-session.sh是 LXQt session 下的进程。- 单独的
waydroid-autostart.service/sunshine-wayland.service可以 disabled / inactive。
检查进程:
ps -eo user,pid,ppid,stat,cmd | grep -E 'kwin|lxqt|waydroid|sunshine' | grep -v grep
ls -la /run/user/1000 | grep wayland期望看到:
kwin_wayland ... --socket wayland-0
lxqt-session
sunshine
waydroid-autostart-session.sh
wayland-012. 验证
12.1 宿主验证
在 OpenWrt:
uname -r
ls -ld /dev/dri /dev/dma_heap /dev/dma_heap/system 2>/dev/null || true12.2 Fedora 验证
在 Fedora:
cat /etc/fedora-release
ls -ld /dev/dri /dev/dma_heap /dev/dma_heap/system 2>/dev/null || true
ip -br addr
bridge link
systemctl is-active waydroid-container.service
waydroid status12.3 Android 验证
waydroid shell -- sh -c '
getprop ro.build.version.release
getprop ro.lineage.version
getprop sys.boot_completed
ls -ld /dev/dma_heap /dev/dma_heap/system /dev/dri /dev/dri/renderD128 /dev/binder /dev/hwbinder /dev/vndbinder 2>/dev/null
service check activity
service check window
service check display
service check SurfaceFlinger
service check waydroidplatform
'期望:
16
23.0-20260403-UNOFFICIAL-WayDroidATV-waydroid_tv_x86_64
1
Service activity: found
Service window: found
Service display: found
Service SurfaceFlinger: found
Service waydroidplatform: found13. Bonus:Android media / Codec2 / VAAPI / P010 支持(可选)
如果你只是想先把 Waydroid ATV 跑起来,可以跳过第 13 节,直接看第 14 节或按第 16 节做最终检查。需要调视频硬解、HEVC Main10/P010 时,再回来读这一节。
13.1 默认镜像能启动,不等于 Main10/P010 完整
Bonus 方向里,我们后来额外修过 stagefright-plugins 和 waydroid-init,用于改善 Android MediaCodec/Codec2 下 HEVC Main10 / P010 / VAAPI 行为。
关键目标:
- HEVC Main10 时,
c2.ffmpeg.hevc.decoder能动态输出 P010。 - VAAPI hardware frame download 不把 P010 错误降成 8-bit。
- HEVC decoder capability 暴露 Main10 profile。
waydroid-init暴露 host codec profiles。- 全局
debug.ffmpeg-codec2.pixel_format保持YUV_420,避免 H.264 / VP9 被全局强制 P010。
13.2 如果你有 Android 源码树
源码树目标:
WayDroid-ATV Android 16 tree
lunch target: lineage_waydroid_tv_x86_64 bp2a userdebug构建命令:
cd /path/to/android-waydroid-atv23
export ANDROID_USE_GAPPS=false
export ALLOW_MISSING_DEPENDENCIES=true
export SOONG_ALLOW_MISSING_DEPENDENCIES=true
export DISABLE_DEXPREOPT_CHECK=true
source build/envsetup.sh
lunch lineage_waydroid_tv_x86_64 bp2a userdebug
m -j8 android.hardware.media.c2-ffmpeg-service media_codecs_ffmpeg_c2.xml android.hardware.media.c2-ffmpeg.policy
m -j8 waydroid-init产物:
out/target/product/waydroid_tv_x86_64/vendor/bin/hw/android.hardware.media.c2-ffmpeg-service
out/target/product/waydroid_tv_x86_64/vendor/etc/media_codecs_ffmpeg_c2.xml
out/target/product/waydroid_tv_x86_64/vendor/etc/seccomp_policy/android.hardware.media.c2-ffmpeg.policy
out/target/product/waydroid_tv_x86_64/vendor/etc/vintf/manifest/manifest_media_c2_ffmpeg.xml
out/target/product/waydroid_tv_x86_64/vendor/bin/waydroid-init
out/target/product/waydroid_tv_x86_64/vendor/etc/init/waydroid-init.rc相关上游工作:
stagefright-plugins branch: [main10-p010-codec2](https://github.com/WayDroid-ATV/android_external_stagefright-plugins/pull/1)
waydroid-init branch: [auto-p010-for-main10](https://github.com/WayDroid-ATV/android_vendor_waydroid_init/pull/1)13.3 部署到 vendor overlay
不要直接修改 vendor.img。使用 Waydroid vendor overlay upperdir:
/var/lib/waydroid/overlay_rw/vendor在 Fedora LXC 中备份:
TS=$(date +%Y%m%d-%H%M%S)
BACKUP=/root/waydroid-media-backup-$TS
mkdir -p "$BACKUP"
cp -a /var/lib/waydroid/overlay_rw/vendor "$BACKUP/vendor-overlay-rw" 2>/dev/null || true复制产物到:
/var/lib/waydroid/overlay_rw/vendor/bin/hw/android.hardware.media.c2-ffmpeg-service
/var/lib/waydroid/overlay_rw/vendor/etc/media_codecs_ffmpeg_c2.xml
/var/lib/waydroid/overlay_rw/vendor/etc/seccomp_policy/android.hardware.media.c2-ffmpeg.policy
/var/lib/waydroid/overlay_rw/vendor/etc/vintf/manifest/manifest_media_c2_ffmpeg.xml
/var/lib/waydroid/overlay_rw/vendor/bin/waydroid-init授权:
chmod 0755 /var/lib/waydroid/overlay_rw/vendor/bin/hw/android.hardware.media.c2-ffmpeg-service
chmod 0755 /var/lib/waydroid/overlay_rw/vendor/bin/waydroid-init部署完这些文件后,请确认第 10 节的 Waydroid inner LXC config_nodes 和 cgroup2 配置已经完成,再重启 Waydroid。
13.4 期望属性
进入 Android:
waydroid shell -- sh -c '
getprop debug.ffmpeg-codec2.pixel_format
getprop debug.ffmpeg-codec2.hwaccel.drm
getprop ro.waydroid.hwcodecs
getprop ro.waydroid.hwcodec_profiles
getprop ro.hardware.gralloc
getprop ro.hardware.egl
getprop ro.hardware.vulkan
getprop media.sf.hwaccel
'期望:
YUV_420
1
MPG2H264VP80HEVCVP90AV10
HEVCMain10,VP9Profile2,AV1Profile0
minigbm
mesa
intel
1注意:debug.ffmpeg-codec2.pixel_format=YUV_420 是当前安全设计,不是错误。HEVC Main10 由 codec component 根据 VAAPI frame context 动态切换到 P010。
13.5 Bonus 验证:视频播放验证(可选)
如果你只是要先跑起 Waydroid ATV,可以先跳过本节。这里验证的是 Android MediaCodec / Codec2 / C2FFMPEG / VAAPI / P010 这一组可选视频能力。
13.5.1 推荐测试方式
要验证 Android MediaCodec / Codec2 / C2FFMPEG / VAAPI,不要只看“某个播放器能播放”。请看 logcat。
用一个 2160p HEVC Main10 样片。我们测试过的样片事实是:
codec_name=hevc
profile=Main 10
width=3840
height=2160
pix_fmt=yuv420p10le用 Next Player 或其它会调用 Android MediaCodec 的播放器播放。
同时抓日志:
waydroid logcat | grep -E 'CCodec|Codec2|C2FFMPEG|HWACCEL|MediaCodec|pixel-format|color-format'关键日志应该类似:
allocate(c2.ffmpeg.hevc.decoder)
Available Codec2 services: "ffmpeg" "software"
createComponent: c2.ffmpeg.hevc.decoder
ffmpeg_hwaccel_init: ... [hevc], hw device = vaapi
outputFrame: pixel format changed - 0x36
raw.pixel-format.value = 54
android._color-format = 54
playbackState=PLAYING
playbackState=STOPPED其中:
0x36 / 54 = HAL_PIXEL_FORMAT_YCBCR_P010 / YCBCR_P01013.5.2 已观察到的结论边界
已经观察到:
- H.264 2160p 样片没有被强制 P010,可以播放到结束。
- VP9 Profile 0 1080p 样片没有被强制 P010,可以播放到结束。
- HEVC Main10 2160p 样片会动态切到 P010,可以播放到结束。
- Emby HEVC Main10 用户测试改善明显。
不能由这些事实推出:
- 所有 HDR 都正常。
- 所有 Dolby Vision 都正常。
- Emby AV1 HDR 已解决。
- VLC 能播放就说明 Android MediaCodec 正常。
VLC 可以播放某个文件,只能说明 VLC 可以播放这个文件。VLC 可能使用自己的 ffmpeg 解码和 GLES 输出,不一定经过 Android MediaCodec / Codec2。
14. 可选:OpenWrt 4G swap
如果机器内存压力大,可以在 OpenWrt 宿主加 swap。我们用过 4G:
dd if=/dev/zero of=/swapfile bs=1M count=4096 conv=fsync
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfileBusyBox 注意:
swapon --show可能不支持。dd status=progress可能不支持。
检查:
cat /proc/swaps
free如果 OpenWrt 没有 /etc/init.d/fstab,可以写一个简单 init 脚本开机 swapon /swapfile。脚本只应在 /swapfile 存在且没有激活时执行 swapon。
14.5 最容易漏的部署细节复盘
按已经跑通的实机重新核对后,下面这些点最容易在通用部署时漏掉:
- Fedora 断开 NetworkManager/networkd 后,要先有最小网络配置。 可以用第 4.4 节的
waydroid-lxc-network-safe.service,先给 Fedora 的eth0配 LAN IP、默认网关和/etc/resolv.conf。否则第 5 节dnf install很容易没网。 - binderfs 不一定由 OpenWrt 宿主直接 bind 进 Fedora。 也可以由 Fedora 内的 Waydroid helper 创建
/dev/binderfs/*和/dev/bindersymlink。判断成功与否看 Fedora 和 Android 最终是否能访问 binder/hwbinder/vndbinder。 - Waydroid LXC config 可能被重新生成。
waydroid init -f、waydroid upgrade或包升级后,lxc.mount.auto可能回到cgroup:ro,lxc.hook.post-stop = /dev/null也可能回来。按第 10.2 节修完后,重启 Waydroid 前后都要grep确认。 waydroid status的IP address: UNKNOWN不代表 Android 没网。 桥接模式下请用lxc-info -P /var/lib/waydroid/lxc -n waydroid、Androidip addr show eth0、OpenWrt DHCP lease 来判断真实网络。- OpenWrt swap 建议作为低内存机器的可选项。 内存压力大的路由器可以按第 14 节加 swap,避免日志、图形会话、Android 服务叠加后太容易触发内存压力。
- 蓝牙要在 Android 层禁用并验证。 期望看到
persist.bluetooth.disabled=true、bluetooth_on=0、com.android.bluetooth在 disabled packages 里。service list仍可能显示bluetooth_manager,这不等于蓝牙 app 又启用了。 - Bonus media overlay 不是“先跑起来”的必要步骤。
/var/lib/waydroid/overlay_rw/vendor里的android.hardware.media.c2-ffmpeg-service、waydroid-init、ffmpeg libs 和 media XML 属于第 13 节视频硬解方向。基础部署先跳过也可以。
15. 常见问题
15.0 OpenWrt /tmp 被写满或内存上涨
先确认是不是日志写到了 tmpfs:
# OpenWrt
mount | grep ' /tmp '
df -h /tmp
free如果 /tmp 是 tmpfs,请不要把长时间 waydroid logcat、Android tombstone 收集、LXC debug log 写到 /tmp。把日志写到持久存储。
同时检查 Android 蓝牙是否已禁用:
waydroid shell -- sh -c '
getprop persist.bluetooth.disabled
cmd package list packages -d | grep -i bluetooth || true
'如果没有禁用,执行第 0.5 节的蓝牙禁用命令。
15.1 waydroid status 显示 IP UNKNOWN
桥接模式下这可能正常。Waydroid helper 仍可能读取旧的 waydroid0 dnsmasq lease。
请用这些命令判断真实网络:
ip -br addr
bridge link
waydroid shell -- sh -c 'ip addr show eth0; dumpsys ethernet | head -80'15.2 Android 提示 Missing DMA-BUF support
依次检查三层:
# OpenWrt
ls -ld /dev/dma_heap /dev/dma_heap/system /dev/dri
# Fedora
lxc-attach -n waydroid -- /bin/bash -lc 'ls -ld /dev/dma_heap /dev/dma_heap/system /dev/dri'
# Android
lxc-attach -n waydroid -- /bin/bash -lc 'waydroid shell -- sh -c "ls -ld /dev/dma_heap /dev/dma_heap/system /dev/dri"'如果 OpenWrt 没有 /dev/dma_heap/system,回到内核章节。不要改 Android 文件掩盖。
15.3 waydroidplatform 不通或 waydroid app list 卡住
检查:
sed -n '1,120p' /var/lib/waydroid/waydroid.cfg
python3 - <<'PY'
import gbinder
print(gbinder.__file__)
PY
pkg-config --modversion libgbinder需要:
binder_protocol = aidl3
service_manager_protocol = aidl6
gbinder 来自 /usr/local/lib64/python3.14/site-packages
libgbinder 1.1.4515.4 Waydroid inner LXC 因 AppArmor 配置失败
如果日志出现:
Built without AppArmor support
Invalid argument检查 Waydroid inner LXC config,删除不兼容行:
lxc.apparmor.profile = unconfined15.5 lxc.hook.post-stop = /dev/null 导致 status 126
如果手动启动内层 Android LXC 时看到:
Script exec /dev/null ... Permission denied
Script exited with status 126
Failed to run lxc.hook.post-stop请检查:
grep -n 'post-stop' /var/lib/waydroid/lxc/waydroid/config /usr/lib/waydroid/data/configs/config_base 2>/dev/null || true把下面这行删除或注释:
lxc.hook.post-stop = /dev/null当前稳定配置是注释掉它。这个问题可能不是 Android 退出的根因,但会让 LXC 日志多一个失败点。
15.6 Android 早期日志出现 /dev/hw_random 缺失
早期 Android 13 调试记录里出现过:
prng_seeder: Hanging forever because setup failed: Unable to open hwrng /dev/hw_random当时还试过把 /dev/random、/dev/urandom 绑定进 Android,结果 Android init 自己创建 /dev/random、/dev/urandom 时发生冲突:
init: mknod("/dev/random"...) failed File exists
init: mknod("/dev/urandom"...) failed File exists
Init encountered errors starting first stage所以请不要默认把 /dev/random 和 /dev/urandom 写进 config_nodes。
如果你用旧镜像,且明确看到 /dev/hw_random 这一条错误,可以只做临时诊断:
lxc.mount.entry = /dev/urandom dev/hw_random none bind,create=file,optional 0 0这不是当前 Android 16 ATV 部署的默认步骤。当前更重要的是先确认第 10.2 节的 cgroup2 子树、cgroup:rw、lmkd 策略,以及第 10.1 节的 config_nodes 设备挂载。
15.7 NetworkManager / systemd-networkd 抢网络
当前方案不用它们。保持:
NetworkManager inactive
systemd-networkd inactive
systemd-resolved inactive
firewalld inactive
wpa_supplicant inactive网络由 OpenWrt 外层 LXC 配置、Fedora 内 waydroid-bridge-setup 和 Android DHCP 完成。
16. 最小检查清单
部署结束后,按这个清单检查:
# OpenWrt
uname -r
ls -ld /dev/dri /dev/dma_heap /dev/dma_heap/system /dev/binderfs 2>/dev/null || true
ls -l /dev/binderfs/binder /dev/binderfs/hwbinder /dev/binderfs/vndbinder 2>/dev/null || true
dmesg | grep -i -E "drm|gpu|i915|amdgpu|nouveau|xe|guc|huc|firmware|wedged|reset" | tail -60 || true
# Fedora
lxc-attach -n waydroid -- /bin/bash -lc '
cat /etc/fedora-release
systemctl is-active waydroid-container.service || true
systemctl --user -M user@ is-active waydroid-full-lxqt.service || true
waydroid status || true
ip -br addr
bridge link || true
ls -ld /dev/dri /dev/dma_heap /dev/dma_heap/system /dev/binderfs 2>/dev/null || true
ls -l /dev/binderfs/binder /dev/binderfs/hwbinder /dev/binderfs/vndbinder 2>/dev/null || true
grep -E "dma_heap|binder|hwbinder|vndbinder|dri" /var/lib/waydroid/lxc/waydroid/config_nodes 2>/dev/null || true
for s in NetworkManager systemd-networkd systemd-resolved firewalld wpa_supplicant; do printf "%s: " "$s"; systemctl is-active "$s" 2>/dev/null || true; done
'
# Android
lxc-attach -n waydroid -- /bin/bash -lc '
waydroid shell -- sh -c "
getprop ro.build.version.release
getprop ro.lineage.version
getprop sys.boot_completed
getprop debug.ffmpeg-codec2.pixel_format
getprop debug.ffmpeg-codec2.hwaccel.drm
getprop ro.waydroid.hwcodec_profiles
getprop ro.hardware.gralloc
getprop ro.hardware.egl
getprop ro.hardware.vulkan
getprop media.sf.hwaccel
ls -ld /dev/dma_heap /dev/dma_heap/system /dev/dri /dev/dri/renderD128 /dev/binder /dev/hwbinder /dev/vndbinder 2>/dev/null
service check activity
service check window
service check display
service check SurfaceFlinger
service check waydroidplatform
"
'期望重点:
OpenWrt 有 /dev/dma_heap/system,且 GPU 驱动没有 firmware 缺失或 wedged/reset 失败日志
Fedora 能看到 /dev/binderfs 和 /dev/dma_heap/system
config_nodes 里有 /dev/dma_heap/system、binder/hwbinder/vndbinder、/dev/dri/renderD128
内层 Waydroid LXC 使用 cgroup:rw,并运行在 waydroid-android/payload cgroup2 子树中
Fedora 44
Waydroid Session RUNNING / Container RUNNING
Android 16 / Lineage 23.0-20260403
sys.boot_completed=1
YUV_420
hwaccel.drm=1
HEVCMain10,VP9Profile2,AV1Profile0
minigbm / mesa / intel
SurfaceFlinger found
waydroidplatform found17. 不建议做的事
请不要:
- 为了隐藏 DMA-BUF 提示去改 Android framework、build.prop 或 Magisk 模块。
- 随便启用 NetworkManager、systemd-networkd、systemd-resolved、firewalld。
- 随便加载 Bluetooth 模块。我们的记录里 Bluetooth 模块尝试导致过 boot loop。
- 默认给 Android inner LXC 绑定
/dev/random、/dev/urandom。Android init 会自己创建设备节点,乱绑可能让 first stage init 失败。 - 保留
lxc.hook.post-stop = /dev/null,如果它在你的 LXC 版本里导致 status 126。 - 只看 VLC 播放结果就判断 Android MediaCodec 正常。
- 删除整个
/var/lib/waydroid/overlay_rw/vendor,除非你确认里面没有需要保留的部署产物。 - 在不知道原因时把 H.264、VP9、HEVC 全局强制成 P010。
18. Debug 指引
如果部署失败,请优先收集:
OpenWrt: uname -r, dmesg, /dev/dma_heap, /dev/binderfs, GPU firmware 检查
Fedora: journalctl -u waydroid-container.service
Fedora user: journalctl --user -u waydroid-full-lxqt.service
Waydroid: waydroid status, waydroid logcat
Android: getprop, service check, /dev/dma_heap, /dev/dri, /dev/binder, /dev/hwbinder, /dev/vndbinder
LXC: /srv/lxc/waydroid/config, /var/lib/waydroid/lxc/waydroid/config, config_nodes你可能还希望使用 netconsole 等方式在软路由 kernel panic 时抓取到错误。或者至少给它接个键盘。
This work is licensed under
Creative Commons Attribution-NonCommercial 4.0 International
![]()
![]()
![]()