功能:支持真实 2K 串流与重启持久化并优化渲染
变更详情:真2K串流与重启持久化,优化客户端渲染和帧率配置 三端统一2560x1440,盒子使用297MHz HDMI时序并保留1080p回退;Windows脚本动态定位VDD恢复2K。串流默认2K20/30Mbps,使用GLES2,设置页支持20/24FPS及自定义值。保持Windows正常光标,记录NVENC阶段性实测和GPU重启后Code43未恢复的限制。
This commit is contained in:
1 parent
4f9565a768
commit
e47b01beec
11 files changed
+198
-36
No files matched your search
@@ -5,7 +5,7 @@
|
||||
#
|
||||
# host/port Sunshine 服务端地址与端口(当前联调环境为 VM 100)
|
||||
# app 应用名。用名称而不是 app id:Sunshine 重装或升级后 app id 会变。
|
||||
# width/height 分辨率,需与服务端显示模式匹配(当前虚拟显示器 1920x1080@60Hz)
|
||||
# width/height 分辨率,需与服务端显示模式匹配(当前虚拟显示器 2560x1440@30Hz)
|
||||
# fps 目标帧率
|
||||
# bitrate 码率,单位 Kbps
|
||||
# codec 视频编码:H.264 / HEVC / AV1 / auto
|
||||
@@ -13,19 +13,19 @@
|
||||
# RK3528 当前没有可用的 V4L2 request/m2m 设备,auto 会选到
|
||||
# h264_v4l2m2m 并报 "Could not find a valid device",故用 software。
|
||||
# vsync 1=开启垂直同步(--vsync),0=关闭(--no-vsync)
|
||||
# 2026-10-29 A/B 实测:关闭 vsync/frame-pacing 对队列与渲染延迟
|
||||
# 无改善(队列 47.2ms vs 基线 44.1ms),延迟瓶颈是服务端软编码
|
||||
# 帧率(~21.6FPS,每帧约46ms),保持开启以获得更平滑的画面。
|
||||
# 同步开关 A/B 未见明确延迟收益,保留开启以避免撕裂。
|
||||
# 连续动画确认服务端可发送约59FPS;当前2K软件呈现选择20FPS档,
|
||||
# NVENC重启故障及各档性能记录见 docs/moonlight-kiosk-20261028.md。
|
||||
# frame-pacing 1=开启帧率匀速(--frame-pacing),0=关闭(--no-frame-pacing)
|
||||
# performance-overlay 1=显示性能叠图(调试用)
|
||||
# hang-timeout 串流假死判定时间(秒),0 表示关闭该检测
|
||||
host=192.168.1.70
|
||||
port=47984
|
||||
app=Desktop
|
||||
width=1920
|
||||
height=1080
|
||||
fps=60
|
||||
bitrate=20000
|
||||
width=2560
|
||||
height=1440
|
||||
fps=20
|
||||
bitrate=30000
|
||||
codec=H.264
|
||||
decoder=software
|
||||
display-mode=fullscreen
|
||||
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
|
||||
export DISPLAY="${DISPLAY:-:0}"
|
||||
export XAUTHORITY="${XAUTHORITY:-/home/armbian/.Xauthority}"
|
||||
|
||||
# Match the local output to the 2K Moonlight stream.
|
||||
# This kernel rejects the monitor's 241.70 MHz timing but accepts the 297 MHz
|
||||
# PLL rate. Longer vertical blanking produces 2560x1440 at approximately 60 Hz.
|
||||
# HDMI can appear a few seconds after the desktop autostart entry runs.
|
||||
: >/tmp/vdi-display-mode.log
|
||||
mode="2560x1440_60_fixed297"
|
||||
for _ in $(seq 1 20); do
|
||||
if xrandr --query 2>>/tmp/vdi-display-mode.log | grep -q '^HDMI-1 connected'; then
|
||||
xrandr --newmode "$mode" 297.00 \
|
||||
2560 2608 2640 2720 1440 1443 1448 1820 \
|
||||
+HSync -VSync >>/tmp/vdi-display-mode.log 2>&1 || true
|
||||
xrandr --addmode HDMI-1 "$mode" >>/tmp/vdi-display-mode.log 2>&1 || true
|
||||
if xrandr --output HDMI-1 --mode "$mode" >>/tmp/vdi-display-mode.log 2>&1; then
|
||||
break
|
||||
fi
|
||||
xrandr --output HDMI-1 --mode 1920x1080 --rate 60 \
|
||||
>>/tmp/vdi-display-mode.log 2>&1 || true
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
exec /home/armbian/vdi-terminal/bin/vdi-kiosk-monitor -- \
|
||||
/home/armbian/vdi-terminal/bin/kiosk-shell
|
||||
@@ -30,9 +30,9 @@ struct DesktopInfo {
|
||||
// 连接参数(kUnassigned 时为空)
|
||||
QString endpoint; // 主机地址(IP / 域名 / VPN 隧道地址)
|
||||
QString app; // Sunshine 应用名
|
||||
int width = 1920;
|
||||
int height = 1080;
|
||||
int fps = 60;
|
||||
int width = 2560;
|
||||
int height = 1440;
|
||||
int fps = 20;
|
||||
};
|
||||
|
||||
// 一次会话的连接凭据;当前为本地构造,后续由平台 connect-ticket 接口下发
|
||||
@@ -40,9 +40,9 @@ struct ConnectionTicket {
|
||||
QString desktopId;
|
||||
QString endpoint;
|
||||
QString app;
|
||||
int width = 1920;
|
||||
int height = 1080;
|
||||
int fps = 60;
|
||||
int width = 2560;
|
||||
int height = 1440;
|
||||
int fps = 20;
|
||||
};
|
||||
|
||||
struct AuthResult {
|
||||
|
||||
@@ -63,6 +63,8 @@ DesktopSettingsDialog::DesktopSettingsDialog(std::vector<DesktopInfo> desktops,
|
||||
comboFps_ = new QComboBox(this);
|
||||
comboFps_->addItem(QStringLiteral("60"), 60);
|
||||
comboFps_->addItem(QStringLiteral("30"), 30);
|
||||
comboFps_->addItem(QStringLiteral("24"), 24);
|
||||
comboFps_->addItem(QStringLiteral("20"), 20);
|
||||
form->addRow(QStringLiteral("帧率"), comboFps_);
|
||||
|
||||
root->addLayout(form);
|
||||
@@ -109,7 +111,11 @@ void DesktopSettingsDialog::loadCurrent() {
|
||||
QStringLiteral("%1x%2").arg(d.width).arg(d.height));
|
||||
comboResolution_->setCurrentIndex(resIndex >= 0 ? resIndex : 0);
|
||||
int fpsIndex = comboFps_->findData(d.fps);
|
||||
comboFps_->setCurrentIndex(fpsIndex >= 0 ? fpsIndex : 0);
|
||||
if (fpsIndex < 0) {
|
||||
comboFps_->addItem(QString::number(d.fps), d.fps);
|
||||
fpsIndex = comboFps_->count() - 1;
|
||||
}
|
||||
comboFps_->setCurrentIndex(fpsIndex);
|
||||
loading_ = false;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -23,9 +23,9 @@ std::vector<DesktopInfo> defaultDesktops(const QString& migratedServer) {
|
||||
win.os = QStringLiteral("Windows 桌面");
|
||||
win.endpoint = migratedServer; // 兼容老盒子:迁移 client.conf 的 server
|
||||
win.app = QStringLiteral("Desktop");
|
||||
win.width = 1920;
|
||||
win.height = 1080;
|
||||
win.fps = 60;
|
||||
win.width = 2560;
|
||||
win.height = 1440;
|
||||
win.fps = 20;
|
||||
list.push_back(win);
|
||||
|
||||
DesktopInfo ubuntu;
|
||||
@@ -50,9 +50,9 @@ DesktopInfo fromJson(const QJsonObject& obj) {
|
||||
d.os = obj.value(QStringLiteral("os")).toString();
|
||||
d.endpoint = obj.value(QStringLiteral("endpoint")).toString();
|
||||
d.app = obj.value(QStringLiteral("app")).toString();
|
||||
d.width = obj.value(QStringLiteral("width")).toInt(1920);
|
||||
d.height = obj.value(QStringLiteral("height")).toInt(1080);
|
||||
d.fps = obj.value(QStringLiteral("fps")).toInt(60);
|
||||
d.width = obj.value(QStringLiteral("width")).toInt(2560);
|
||||
d.height = obj.value(QStringLiteral("height")).toInt(1440);
|
||||
d.fps = obj.value(QStringLiteral("fps")).toInt(20);
|
||||
d.state = d.endpoint.isEmpty() ? DesktopState::kUnassigned : DesktopState::kAvailable;
|
||||
return d;
|
||||
}
|
||||
|
||||
@@ -123,7 +123,12 @@ bool MoonlightSession::activateStreamWindow() {
|
||||
{QStringLiteral("search"), QStringLiteral("--onlyvisible"),
|
||||
QStringLiteral("--pid"), QString::number(process_->processId()),
|
||||
QStringLiteral("--class"), QStringLiteral("com.moonlight_stream.Moonlight")});
|
||||
if (!finder.waitForFinished(500) || finder.exitCode() != 0) {
|
||||
if (!finder.waitForFinished(500)) {
|
||||
finder.kill();
|
||||
finder.waitForFinished();
|
||||
return false;
|
||||
}
|
||||
if (finder.exitCode() != 0) {
|
||||
return false;
|
||||
}
|
||||
const QString windowId = QString::fromUtf8(finder.readAllStandardOutput())
|
||||
@@ -154,8 +159,10 @@ bool MoonlightSession::start(const ConnectionTicket& ticket, EventCallback callb
|
||||
process_->setProgram(QStringLiteral("moonlight-qt"));
|
||||
QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
|
||||
environment.insert(QStringLiteral("GL_IS_SLOW"), QStringLiteral("0"));
|
||||
environment.insert(QStringLiteral("SDL_RENDER_DRIVER"), QStringLiteral("opengles2"));
|
||||
process_->setProcessEnvironment(environment);
|
||||
|
||||
const int bitrateKbps = ticket.width >= 2560 ? 30000 : 20000;
|
||||
QStringList args;
|
||||
args << QStringLiteral("stream")
|
||||
<< ticket.endpoint
|
||||
@@ -163,7 +170,7 @@ bool MoonlightSession::start(const ConnectionTicket& ticket, EventCallback callb
|
||||
<< QStringLiteral("--resolution")
|
||||
<< QStringLiteral("%1x%2").arg(ticket.width).arg(ticket.height)
|
||||
<< QStringLiteral("--fps") << QString::number(ticket.fps)
|
||||
<< QStringLiteral("--bitrate") << QString::number(20000)
|
||||
<< QStringLiteral("--bitrate") << QString::number(bitrateKbps)
|
||||
<< QStringLiteral("--display-mode") << QStringLiteral("fullscreen")
|
||||
<< QStringLiteral("--video-codec") << options_.videoCodec
|
||||
<< QStringLiteral("--video-decoder") << options_.decoder
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Type=Application
|
||||
Name=VDI Cloud Desktop (Kiosk)
|
||||
Comment=产品唯一自启入口:看门狗守护 kiosk-shell(登录 -> 桌面列表 -> 会话)
|
||||
Exec=/home/armbian/vdi-terminal/bin/vdi-kiosk-monitor -- /home/armbian/vdi-terminal/bin/kiosk-shell
|
||||
Exec=/home/armbian/vdi-terminal/bin/vdi-kiosk-start
|
||||
Terminal=false
|
||||
X-GNOME-Autostart-enabled=true
|
||||
X-GNOME-Autostart-Delay=2
|
||||
Reference in new issue
Block a user