Selkies, 全称 Selkies-GStreamer, 是一个开源的低延迟高性能 Linux 原生 GPU/CPU 加速 WebRTC HTML5 远程桌面流媒体平台, 适用于自托管 容器 Kubernetes 或 云/HPC 平台, 其原型由 Google 工程师开发1
Selkies 设计上就是为了高性能游戏或远程桌面串流设计的, 可以直接调用 GPU 硬件编解码, 相较于 noVNC 等传统 VNC 解决方案, 配置更简单.
同时, Selkies 支持免配置音频传输, 仅需保证后端兼容 PulseAudio (例如 PulseAudio 与 PipeWire-Pulse)
NOTESelkies 暂未支持 Wayland
安装并配置基于 X11 的 KDE 桌面环境
https://wiki.archlinux.org/title/KDE
https://archlinux.org/news/plasma-640-will-need-manual-intervention-if-you-are-on-x11/
使用如下指令安装基于 X11 的 KDE 桌面环境及其相关组件
sudo pacman -S plasma-meta plasma-x11-session konsole dolphin pipewire pipewire-pulse xorg-server xorg-xinit xf86-video-dummy polkit-kde-agent配置 KDE 的 X11 启动脚本
在如下路径写入如下内容
~/.xinitrc#!/usr/bin/sh
unset DBUS_SESSION_BUS_ADDRESSunset DBUS_SESSION_BUS_PID
export DESKTOP_SESSION=plasmaexport XDG_CURRENT_DESKTOP=KDEexport XDG_SESSION_TYPE=x11
if [ -x /usr/bin/dbus-run-session ]; then exec dbus-run-session -- startplasma-x11else exec dbus-launch --exit-with-session startplasma-x11fi若需调整系统音量, 请参阅 故障排除 > KDE 显示无音频设备导致无法调节系统音量
并配置可执行权限
chmod +x ~/.xinitrc配置 X11 虚拟显示器
在 X11 配置目录写入如下配置文件
/etc/X11/xorg.conf.d/20-dummy.confSection "Device" Identifier "DummyDevice" Driver "dummy" VideoRam 512000EndSection
Section "Monitor" Identifier "DummyMonitor" Modeline "1920x1080_60.00" 173.00 1920 2048 2248 2576 1080 1083 1088 1120 -hsync +vsync HorizSync 28.0-110.0 VertRefresh 43.0-90.0EndSection
Section "Screen" Identifier "DummyScreen" Device "DummyDevice" Monitor "DummyMonitor" DefaultDepth 24 SubSection "Display" Depth 24 Modes "1920x1080_60.00" EndSubSectionEndSection配置 X11 启动服务
为了保证 X11 Server 可以随系统自启动, 我们需要使用自定义 Systemd Unit
~/.config/systemd/user/headless-x11-kde.service[Unit]Description=Headless KDE Plasma X11 Session on :0After=network.target
[Service]Environment=DISPLAY=:0Environment=XDG_SESSION_TYPE=x11Environment=XAUTHORITY=%h/.Xauthority
ExecStart=/usr/bin/startx %h/.xinitrc -- :0 -config 20-dummy.conf
Restart=alwaysRestartSec=5s
StandardOutput=journalStandardError=journal
[Install]WantedBy=default.target加载并启用服务
systemctl --user daemon-reloadsystemctl --user enable --now headless-x11-kde.service启动 KDE 所需的 Pipewire 服务系列
systemctl --user enable --now pipewire.service pipewire-pulse.service wireplumber.service安装 Selkies
参考 Selkies 官方文档 的 Getting Started > Quick Start 页面下载并安装 Selkies
默认安装路径位于 <workpath>/selkies-gstreamer
配置 Selkies 的 Systemd Unit
在如下路径新建 Env 文件
~/selkies-gstreamer/selkies.env修改必要配置, 诸如端口号, 用户, 密码与编码器配置
SELKIES_ADDR=0.0.0.0SELKIES_PORT=<port>SELKIES_USER=<user>SELKIES_PASS=<password>SELKIES_ENCODER=av1encSELKIES_RESIZE=falseNOTE对于无法使用 GPU 的虚拟机或者 GPU 性能较弱设备 (如我使用的 Intel UHD Graphics P6302), 使用
x264enc等编码方式可能出现跳帧问题, 推荐使用svtav1enc或av1enc编码方式
在如下文件写入 Unit
~/.config/systemd/user/selkies.service[Unit]Description=Selkies GStreamer Service# 必须和上面定义的 KDE Unit 名称对应, `Wants` 同理After=headless-x11-kde.serviceWants=headless-x11-kde.service
[Service]Type=simple# 设置默认环境变量, 参考官方文档Environment=DISPLAY=:0Environment=PIPEWIRE_LATENCY=128/48000Environment=XDG_RUNTIME_DIR=%tEnvironment=PIPEWIRE_RUNTIME_DIR=%tEnvironment=PULSE_RUNTIME_PATH=%t/pulseEnvironment=PULSE_SERVER=unix:%t/pulse/native
EnvironmentFile=%h/selkies-gstreamer/selkies.env
ExecStart=%h/selkies-gstreamer/selkies-gstreamer-run \ --addr=${SELKIES_ADDR} \ --port=${SELKIES_PORT} \ --enable_https=false \ --basic_auth_user=${SELKIES_USER} \ --basic_auth_password=${SELKIES_PASS} \ --encoder=${SELKIES_ENCODER} \ --enable_resize=${SELKIES_RESIZE}
Restart=alwaysRestartSec=5s
StandardOutput=journalStandardError=journal
[Install]WantedBy=default.target加载并启用服务
systemctl --user daemon-reloadsystemctl --user enable --now selkies.service故障排除
Selkies 画面有显示但仍在画面中间显示 Waiting for stream.
发生该情况可能存在多种原因:
- 由于 Selkies 只接收到了视频画面, 无法获取到音频. 尝试检查 Pipewire 和 PulseAudio 等服务的状态.
- 客户端不支持该编解码格式, 导致解码时出现错误 (多数情况是客户端不支持 Opus 音频格式)
DBus 进程在 SSH 结束后退出
请确保开启了后台进程常驻
sudo loginctl enable-linger $USERKDE 显示无音频设备导致无法调节系统音量
我们可以使用 pactl 加载虚拟音频模块, 并将其设为系统默认输出
为了自动化地设置虚拟音频输出, 请在 ~/.xinitrc 的 exec 前添加如下内容
VIRTUAL_SINK_NAME="default"
if ! pactl list sinks short | awk '{print $2}' | grep -Fxq "$VIRTUAL_SINK_NAME"; then pactl load-module module-null-sink sink_name="$VIRTUAL_SINK_NAME" sink_properties=device.description="$VIRTUAL_SINK_NAME" >/dev/null 2>&1fi
pactl set-default-sink "$VIRTUAL_SINK_NAME" >/dev/null 2>&1Selkies 客户端网络环境无法访问 TURN/STUN Server 或者 443 端口, 导致无法建立连接
由于 Selkies 默认开启了 TURN/STUN Server, 无法访问的客户端会尝试连接并失败造成无法成功传输画面. 出现类似于如下日志
Status Log
[14:56:31] [webrtc] [ERROR] attempt to send data channel message before channel was openDebug Log
[14:44:12] [app] using TURN servers: turn:staticauth.openrelay.metered.ca:443?transport=udp一个可能的解决方法是禁用 Selkies 的全部 TURN/STUN Server, 全部流量直接通过反向代理或部署 Selkies 的服务器传输
在 Env 文件中写入如下内容
打开
~/selkies-gstreamer/selkies.env写入
SELKIES_TURN_HOST=NOTE若仍然无法加载, 请检查网络环境 UDP 连通性
针对 UDP 不佳网络环境的共存部署 KasmVNC
由于 WebRTC 很大程度上依赖 UDP, 并且在受限网络工况中表现不佳, 我们可以使用 KasmVNC 与 Selkies 同时部署的方式, 使得客户端可以选择一个合适的 WebUI 操控方案
安装 KasmVNC
安装 KasmVNC Server
yay -S kasmvncserver-bin openssl-1.1配置 WebUI 用户名密码
kasmvncpasswd -u <USER>若要配置与当前用户名称一样的用户名, 请使用
Terminal window kasmvncpasswd -u $USER
创建证书文件
由于 KasmVNC 强制要求配置 TLS 证书 (无论是否启用 HTTPS), 我们需要使用 OpenSSL 创建证书
openssl req -x509 -nodes -days 90 -newkey rsa:4096 \ -keyout ~/ssl-cert-snakeoil.key \ -out ~/ssl-cert-snakeoil.pem \ -subj "/C=CN/ST=Default/L=Default/O=KasmVNC/CN=${HOST:-default-host}"
chmod 644 ~/ssl-cert-snakeoil.pemchmod 600 ~/ssl-cert-snakeoil.key编辑 KasmVNC 配置文件
~/.vnc/kasmvnc.yaml写入
network: protocol: http interface: 0.0.0.0 websocket_port: <port> # edit use_ipv4: true use_ipv6: true udp: public_ip: auto port: auto payload_size: auto stun_server: auto ssl: pem_certificate: /home/<USER>/ssl-cert-snakeoil.pem # edit pem_key: /home/<USER>/ssl-cert-snakeoil.key # edit require_ssl: false若服务启动耗时较长, 或显示
Failed to get public IP, please specify it with -publicIP字样, 请修改配置文件的network.udp.public_ip字段, 或在启动kasmvncserver时使用-publicIP <public-ipaddr>指定 公网 IP 地址 (没有者尝试填写内网地址)
[可选] 启用 GPU 加速
GPU 加速请按需启用
找到设备上的 Render 设备, 对于拥有单个 GPU 的设备一般来说为 /dev/dri/renderD128, 同时拥有核显与独显的设备一般来说编号较大者为独显, 常见于 /dev/dri/renderD129
~/.vnc/kasmvnc.yaml添加
desktop: gpu: hw3d: true drinode: /dev/dri/renderD<id>使用不同方法共存部署
目前, 将 Selkies 与 KasmVNC 共存部署共有两种方法 (任选其一即可):
1.1 禁用 KDE 启动程序
由于 KasmVNC Server 会自己启动一个 X11 Session 调起 DE, 我们需要禁用先前定义的 headless-x11-kde.service
systemctl --user disable --now headless-x11-kde.service1.2 配置 DE 启动脚本
由于我们先前已经在 ~/.xinitrc 写入了启动脚本, 我们可以直接使用 startx 调用
在如下文件
~/.vnc/xstartup写入
#!/usr/bin/sh
exec ~/.xinitrcWARNING由于 KasmVNC 启动并接管了 X11 Session, 我们无法使其加载
20-dummy.conf, 导致 GPU 加速不可用 (显著表现为在 Session 内运行glxinfo -B无法找到渲染信息或显示软件渲染)这可以通过 使用 kasmxproxy 转发现有 X11 Display 解决
并授予可执行权限
chmod +x ~/.vnc/xstartup1.3 配置 KasmVNC 为 Systemd Unit
~/.config/systemd/user/kasmvnc.service写入
[Unit]Description=KasmVNC ServiceAfter=network.target
[Service]Type=simple
ExecStart=/usr/bin/vncserver :0 -fgExecStop=/usr/bin/vncserver -kill :0
Restart=on-failureRestartSec=5
[Install]WantedBy=default.target由于我们禁用了 headless-x11-kde.service, 需要将 Selkies Systemd Unit 的等待服务设置为 kasmvnc.service
~/.config/systemd/user/selkies.service修改
After=headless-x11-kde.serviceWants=headless-x11-kde.service为
After=kasmvnc.serviceWants=kasmvnc.service重载并启用服务
systemctl --user daemon-reloadsystemctl --user restart selkies.servicesystemctl --user enable --now kasmvnc.service2.1 使用 kasmxproxy 转发现有 X11 Display
先前的 headless-x11-kde.service 我们已经在 Display :0 启动了一个 X11 Session, 我们需要使用 kasmxproxy 转发这个 Display
2.2 配置 KasmVNC 与 kasmxproxy 为 Systemd Unit
对
vncserver使用-noxstartup选项以创建空白的 X11 Session, 不启动 DE
~/.config/systemd/user/kasmvnc.service写入
[Unit]Description=KasmVNC ServiceAfter=network.target
[Service]Type=simple# 不能使用 ExecStartPre, 因为 vncserver 启动不是立即的ExecStart=/usr/bin/sh -c '/usr/bin/vncserver :99 -noxstartup && /usr/bin/kasmxproxy -a :0 -v :99 -r -f ${KASM_FPS:-60}'ExecStop=/usr/bin/vncserver -kill :99
Restart=on-failureRestartSec=5
[Install]WantedBy=default.target如果需要自动修改远程显示器大小, 请携带
-r参数
参数配置请参阅 https://kasmweb.com/kasmvnc/docs/1.3.4/man/kasmxproxy.html#options
重载并启用服务
systemctl --user daemon-reloadsystemctl --user enable --now kasmvnc.service[可选] 反向代理配置
KasmVNC 使用 Websocket 作为长连接, 所以可以直接使用一般的现代化反向代理软件. 这里以 Caddy 为例
要使 Caddy 反向代理忽略 TLS 证书有效性, 请使用如下配置
host:port { reverse_proxy https://<url> { transport http { tls_insecure_skip_verify } }}