- 音视频
【免费下载链接】foundation-sunshine
Sunshine fork: an enhanced sunshine, a self-hosted game streaming host for Moonlight with HDR10/HDR Vivid, virtual displays, advanced audio, optimized encoders, and a modern control panel.
本文围绕 Sunshine(一个基于 Moonlight 协议的自托管游戏流媒体主机)中自适应码率(Adaptive Bitrate,ABR)决策引擎的 LLM 提示词模板展开,讲解src/assets/abr_prompt.md这份模板如何将网络指标与前台应用识别转化为最优编码码率决策,并结合 src/abr.cpp、src/nvhttp/abr_api.cpp 等源码揭示其完整工作链路。读完本文,你将掌握 ABR 模板的占位符机制、按应用类型分类的码率目标设定方法、视觉复杂度修正规则、网络调整规则,以及该模板在 Sunshine 服务端如何被加载、填充、提交给 LLM 并解析回码率动作。
一、ABR 提示词模板的定位:LLM 决策层的"大脑"
在 Sunshine 的 ABR 架构中,码率控制被设计为两层结构(见 src/abr.h 的文件头注释):
- 实时回退层(Fallback):基于丢包等网络阈值做即时反应,始终生效,每 3 秒限频一次;
- 事件驱动 LLM 层:在应用切换、网络恢复等事件发生时,异步调用配置好的 LLM API,为当前游戏/应用推荐一个最优目标码率。
src/assets/abr_prompt.md正是第二层 LLM 决策所依赖的提示词模板。它通过{{PLACEHOLDER}}占位符接收会话运行时状态,指导 LLM 完成"识别应用 → 按交互类型定基码率 → 按视觉复杂度修正 → 结合网络反馈微调 → 输出 JSON 动作"的完整推理链。
模板的加载逻辑位于 src/abr.cpp 的load_prompt_template():搜索顺序为配置目录(用户覆盖)→ assets 目录(捆绑默认),首次成功加载后缓存。具体而言,它会先尝试读取config::sunshine.config_file所在目录下的abr_prompt.md(即用户可通过放置同名文件覆盖默认提示词),若不存在则回退到编译期常量SUNSHINE_ASSETS_DIR下的捆绑版本。两份都不存在时,日志会输出警告 "ABR: prompt template not found, LLM decisions will be unavailable",LLM 层将静默失效而回退层继续工作。
二、模板占位符:会话状态如何注入提示词
build_llm_prompt()(src/abr.cpp)负责用运行时状态替换模板中的所有{{key}}占位符,替换逻辑由replace_placeholders()实现(循环查找{{key}}并替换)。模板中的占位符与填充来源对应如下:
| 占位符 | 填充内容 | 填充来源 |
|---|---|---|
{{FOREGROUND_TITLE}} | 前台窗口标题 | 运行时detect_foreground_app();缺省回退到配置的 app_name,再回退到 "Unknown" |
{{FOREGROUND_EXE}} | 前台进程可执行文件名 | 同上 |
{{MODE}} | ABR 运行模式 | balanced/quality/lowLatency字符串 |
{{CURRENT_BITRATE}} | 当前码率(Kbps) | 会话状态current_bitrate_kbps |
{{MIN_BITRATE}}/{{MAX_BITRATE}} | 允许码率范围(Kbps) | 会话配置min_bitrate_kbps/max_bitrate_kbps |
{{RECENT_FEEDBACK}} | 最近网络反馈列表(最新在前) | 滚动窗口内最近 10 条反馈,每条格式为- [Ns ago] loss=..%, rtt=..ms, fps=.., dropped=.., bitrate=..Kbps |
{{FPS_RANGE}} | FPS/赛车类目标区间 | int(max*0.8)-max |
{{ACTION_RANGE}} | 动作/冒险类目标区间 | int(max*0.6)-int(max*0.8) |
{{STRATEGY_RANGE}} | 策略/回合制目标区间 | int(max*0.4)-int(max*0.6) |
{{DESKTOP_RANGE}} | 桌面/生产力目标区间 | int(max*0.2)-int(max*0.3) |
注意:范围类占位符并非模板中的静态文本,而是依据会话的max_bitrate_kbps动态计算后填入,因此模板中的-> {{FPS_RANGE}}等描述在实际请求中会变成具体的数值区间(如-> 40000-50000)。
前台应用识别是"应用感知码率"的关键前提。detect_foreground_app()(src/abr.cpp)在 Windows 平台通过GetForegroundWindow()获取前台窗口,再经GetWindowThreadProcessId与QueryFullProcessImageNameW取得进程名,并统一转为 UTF-8。其设计动机(源码注释明确说明):当用户通过 Steam/Epic 等启动器进入游戏时,配置中的app_name往往只是 "Steam Big Picture",而该函数能拿到真正活跃的游戏窗口标题与进程名。该检测以 10 秒为间隔限频(FG_DETECT_INTERVAL_SECONDS),仅在 PID 变化时才更新标题/进程并置app_changed标志,从而驱动 LLM 重新评估。
三、Step 1:按交互类型确定基础码率目标
模板首先要求 LLM 根据 Active Window 与 Process 名称识别正在运行的应用,并在允许范围内按交互类型确定基础目标(基码率)。这是模板中最核心的应用感知规则:
| 交互类型 | 目标区间(占最大码率比例) | 示例应用(模板原文) |
|---|---|---|
| 快节奏 FPS / 竞速(Fast-paced FPS/Racing) | 80%–100% | CS2、Forza、Apex |
| 动作 / 冒险(Action/Adventure) | 60%–80% | Elden Ring、GTA V |
| 策略 / 回合制(Strategy/Turn-based) | 40%–60% | Civilization、XCOM |
| 桌面 / 生产力(Desktop/Productivity) | 20%–30% | explorer.exe、chrome、浏览器 |
其设计逻辑是:第一人称射击与竞速游戏画面快速运动、信息量大,对码率敏感,应尽量贴近上限;动作冒险居中;策略与回合制画面相对静态;桌面场景对画质要求最低,应把带宽让给网络余量。模板同时强调"IMPORTANT: If current bitrate differs significantly from the adjusted target, you MUST adjust toward it",即当前码率与修正后目标差异显著时,LLM 必须朝目标方向调整。
四、Step 2:按视觉复杂度修正基础区间
在基础区间之上,模板要求 LLM 结合渲染风格做二次修正,这是"画质-码率"权衡的精细化规则:
- 动漫 / cel-shaded 风格(Genshin Impact、Honkai、Persona):降低 10%–20%——大面积平涂色与重复纹理压缩效率高,无需高码率;
- 像素艺术 / 2D(Terraria、Stardew Valley、复古游戏):降低 20%–30%——极端可压缩;
- 写实 / 高细节(RDR2、Flight Simulator、Forza):保持区间上沿——复杂纹理压缩率低;
- 黑暗 / 恐怖场景(Resident Evil、Dead Space):保持中等——暗部渐变在低码率下易出现色带伪影;
- 未知应用:使用未修正的基础目标。
该规则本质是让 LLM 综合"运动复杂度 + 纹理复杂度"两维信息,避免对可压缩性强的画面浪费码率、对难压缩的画面过度压缩。
五、调整规则:稳定性与网络适应约束
模板为 LLM 的最终决策设定了硬性约束,防止码率剧烈抖动或在网络恶化时决策失当:
- 单次决策最大变化量:当前码率的 15%(稳定性考虑);
- 网络丢包 > 5%:覆盖最大变化量限制,强制降低 25%–35%;
- 网络丢包持续 2%–5%:降低 10%–20%;
- 网络稳定且当前码率 ≠ 目标:每步最多向目标调整 10%;
- 任何情况下不得超出
[{{MIN_BITRATE}}, {{MAX_BITRATE}}]允许范围。
最后一条在服务端有双重保障:parse_llm_response()在解析出码率后会执行std::clamp(bitrate, state.config.min_bitrate_kbps, state.config.max_bitrate_kbps)强制收敛到会话允许范围(src/abr.cpp);同时,即便 LLM 决策为 0(模板语义"仅当当前码率处于类型目标 5% 以内且网络稳定时才输出 0"),服务端也只把结果当作"不立即动作"处理。
六、响应格式与容错解析
模板严格要求 LLM 只输出 JSON:{"bitrate": <integer_kbps>, "reason": "<reason>"},并明令禁止<think>标签、markdown 代码块、注释及 JSON 前后的任何文本。bitrate置 0 仅在"当前码率处于类型适切目标 5% 以内且网络稳定"时允许。
现实中的 LLM(尤其是 DeepSeek-R1、QwQ 等推理模型)常不遵守此约束,因此服务端实现了多级容错解析(parse_llm_response(),src/abr.cpp):
- 先直接尝试
json::accept()校验原始 content; - 失败则调用
strip_reasoning_blocks()大小写不敏感地剥除<think>...</think>块(未闭合的<think>起始标签也会被清除); - 仍失败则用
extract_first_json_object()手动扫描字符串,正确处理字符串转义与花括号嵌套,提取第一个完整 JSON 对象; - 全部失败时给出诊断原因:
llm_parse_error: no JSON object in content。
针对真实场景的回归问题——推理模型在max_tokens耗尽(finish_reason == "length")时<think>块被截断、JSON 尚未产出——代码会返回llm_truncated: reasoning exceeded max_tokens,并在日志中提示增大ai_config.json的max_tokens(默认 1024,已预留约 600–800 推理 token 余量)。
解析成功后,若新码率与会话当前码率差异 ≥ 2%(current_bitrate_kbps / 50)才产生立即动作;差异过小则 reason 记为no_change: delta too small。LLM 的推荐值始终被记录为target_bitrate_kbps,作为回退层探测性升码(probe-up)的天花板。
这些容错行为均有单元测试覆盖(位于 src/abr.cpp 的SUNSHINE_TESTS段):ExtractsJsonAfterThinkBlock、ExtractsFirstJsonObjectFromMixedContent、ReportsMissingJsonWhenContentHasOnlyReasoning、DetectsTruncatedReasoningWhenFinishReasonLength、PreservesExplicitMaxBitrateWhenMinIsOmitted。
七、LLM 层的触发与执行链路
LLM 调用是事件驱动的,而非周期性轮询。process_feedback()(src/abr.cpp)每个反馈周期按以下阶段执行:
- Phase 1 前台检测(限频 10s):PID 变化 → 更新前台应用并置
app_changed = true; - Phase 2 回退决策(限频 3s):丢包 > 5% 时不受限频立即紧急降码率(
emergency_drop,降至当前 70%);丢包 2%–5% 时moderate_drop(降至 90%);丢包 < 0.5% 且连续 5 个稳定周期时probe_up(升至 105%)。探测升码一旦存在 LLM 目标,则取min(probe_bitrate, llm_target_bitrate_kbps),已到或超过目标则停止探测; - Phase 3 LLM 触发:仅当
(app_changed || network_recovered) && !llm_in_flight && confighttp::isAiEnabled() && 提示词模板非空时,且距上次调用 ≥ 10 秒(LLM_MIN_INTERVAL_SECONDS),才构建提示词并派发后台线程执行。
后台llm_worker()(src/abr.cpp)通过confighttp::processAiChat()以 OpenAI 兼容格式调用 LLM:请求体包含 system 提示(默认 "You are a streaming bitrate optimizer. Respond with a single valid JSON object only...",可从ai_config.json覆盖)、用户消息(即本模板填充后的完整提示词)、temperature(ABR 层默认 0.1)与max_tokens(默认 1024)。worker 使用generation计数器防陈旧结果:会话被清理或重建后,旧 worker 的结果直接丢弃。
network_recovered的判定采用边沿触发——stable_ticks首次达到 5 且无持续高丢包时置位一次,配合app_changed共同决定是否重新咨询 LLM。
八、AI 能力开关:ai_config.json 与相关 REST 端点
模板要真正生效,还需要 AI 代理处于启用状态。confighttp::isAiEnabled()(src/confighttp.cpp)要求ai_config.json中enabled为真、apiBase非空,且(若提供商要求 key)apiKey已配置。该文件与sunshine.conf同目录,默认值为{"enabled": false, "provider": "openai", "apiBase": "https://api.openai.com/v1", "model": "gpt-4.1-mini", "compatibility": "openai-chat", "temperature": 0.3, "max_tokens": 2048}。API key 不落盘于明文 JSON——首次读取到遗留明文 key 时会自动迁移到安全凭据存储ai_llm_credential.bin(见 src/confighttp.cpp)。
ABR 的对外能力通过三条 HTTPS 路由暴露(注册于 src/nvhttp.cpp),客户端(Moonlight)通过源 IP 关联活动会话:
GET /api/abr/capabilities:返回supported、version、features(含llm_ai、game_aware、fallback_threshold、bitrate_cap)、llmEnabled(即isAiEnabled())与hostMaxBitrate;POST /api/abr(configure):接收enabled、mode(balanced/quality/lowLatency)、minBitrate、maxBitrate,校验非负且 min ≤ max,再用apply_host_bitrate_cap()以主机配置video.max_bitrate封顶;随后调用abr::enable()启动会话级 ABR;POST /api/abr/feedback:接收packetLoss、rttMs、decodeFps、droppedFrames、currentBitrate,调用abr::process_feedback(),若有新码率则通过stream::session::change_dynamic_param_for_client()以动态 BITRATE 参数实时作用于活动流,并把newBitrate、bitrateApplied、reason返回给客户端。
abr::enable()中的模式预设(src/abr.cpp)值得一提:仅对未显式配置的边界套用预设值,从而保留客户端或主机设定的 maxBitrate 上限——quality模式为max(5000, initial/2)至min(150000, initial*1.5),lowLatency为2000至initial*1.2,balanced为max(3000, initial*0.3)至min(150000, initial*2),并处理初始码率极低时的区间反转。
九、自定义模板与调优建议
由于load_prompt_template()优先加载配置目录下的abr_prompt.md,用户可以:
- 在
sunshine.conf同目录放置自定义abr_prompt.md,覆盖仓库内 src/assets/abr_prompt.md 的默认模板——例如新增应用分类、调整各区间比例或收紧单步变化量; - 若使用 DeepSeek-R1、QwQ 等"长思维链"推理模型,请在
ai_config.json中调大max_tokens,避免<think>块截断导致解析失败(日志会给出llm_truncated: reasoning exceeded max_tokens提示); - 保持
temperature较低(ABR 层默认 0.1),使决策更稳定可复现; - 会话键是客户端的设备显示名(device display name),源码注释明确提醒:同名设备的多会话并发会共享同一 ABR 状态而产生交叉污染——正常配对设备名称各异,单会话部署不受影响。
综上,src/assets/abr_prompt.md虽只是数百行提示词文本,却是 Sunshine 将 LLM 能力与游戏流媒体码率控制结合的关键接口:它把"应用识别 + 画质分类 + 网络容忍度"编码为结构化决策规则,再经由 src/abr.cpp 的加载、填充、触发、解析链路落到实时的编码器码率调节上,与始终在线的阈值回退层互补,构成一套完整、可观测、可自定义的自适应码率闭环。
- 音视频
【免费下载链接】foundation-sunshine
Sunshine fork: an enhanced sunshine, a self-hosted game streaming host for Moonlight with HDR10/HDR Vivid, virtual displays, advanced audio, optimized encoders, and a modern control panel.
相关推荐
Ollama项目模板引擎深度解析:构建高效LLM提示词
Ollama项目模板引擎深度解析:构建高效LLM提示词 什么是Ollama模板引擎 Ollama项目内置了一个基于Go模板引擎的强大提示词构建系统,专门为大型语
人工智能大模型模型推理服务本地部署探索Sunshine:自托管游戏流媒体服务器的深度解析
探索Sunshine:自托管游戏流媒体服务器的深度解析 项目概览 Sunshine作为一款开源的自托管游戏流媒体服务器,专为Moonlight客户端打造,致力于
音视频后端从卡顿到流畅:Shaka Player自适应码率切换(ABR)算法深度解析
从卡顿到流畅:Shaka Player自适应码率切换(ABR)算法深度解析 你是否曾经历过视频播放时频繁缓冲、画质忽高忽低的尴尬?在流媒体传输中,网络带宽的波动
前端音视频
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考