Skip to content

Runtime bitrate endpoint + ABR capability negotiation (Foundation/Moonlight V+ compatible)#273

Open
AsafMah wants to merge 1 commit into
Nonary:masterfrom
AsafMah:feat/runtime-bitrate-abr
Open

Runtime bitrate endpoint + ABR capability negotiation (Foundation/Moonlight V+ compatible)#273
AsafMah wants to merge 1 commit into
Nonary:masterfrom
AsafMah:feat/runtime-bitrate-abr

Conversation

@AsafMah

@AsafMah AsafMah commented Jun 14, 2026

Copy link
Copy Markdown

Implements #272 — runtime (mid-stream) bitrate changes so Foundation / Moonlight V+ clients can adjust encoder bitrate without reconnecting.

Endpoints (HTTPS, paired client, View permission)

  • GET /bitrate?bitrate=<kbps> — apply a new encoder bitrate to the caller's active session. Clamped to max_bitrate and an absolute 500 Mbps ceiling. Returns <root status_code=200><bitrate>N</bitrate></root> (0 = failure) — the exact shape NvHTTP.setBitrate() parses (and its verifyResponseStatus requires the status_code attribute).
  • GET /api/abr/capabilities — returns {"supported":false,"version":1,"features":["runtime_bitrate"]}. Verified against AdaptiveBitrateService.kt: supported:false makes V+ run its local ABR controller, which drives the host through /bitrate. So both the manual slider and adaptive mode work. Server-side ABR decisioning (POST /api/abr, /feedback) is intentionally out of scope.

Apply mechanism

A per-session mail event (dynamic_bitrate), coalesced to the latest value on the encode thread:

  • NVENC reconfigures the live encoder via nvEncReconfigureEncoder using saved init params (ported from foundation-sunshine, adapted to Vibepollo's runtime-versioned NVENC structs / api::reconfigure_params_version). Seamless, no hitch.
  • avcodec encoders (AMD/Intel/software) report set_bitrate()==false and fall back to an encode-session rebuild via the existing reinit path. Correct, but a brief hitch per change. Backend-native AMF/QSV reconfigure is a possible follow-up (mirrors foundation staging NVENC first, AMF later).

Clamping/overflow: rejects <=0, clamps to max_bitrate (when set) and an absolute ceiling; set_bitrate() also rejects >800000 kbps so kbps*1000 cannot overflow the 32-bit rate-control fields.

Files

globals.h (event), stream.{h,cpp} (set_bitrate_for_sessions, per-session event), video.{h,cpp} (encode_session_t::set_bitrate, NVENC override, both encode paths), nvenc/nvenc_base.{h,cpp} (set_bitrate live reconfigure), nvhttp.cpp (routes).

Verification

  • Builds + links: compiled with gcc 16 (MSYS2 UCRT64), ninja sunshinesunshine.exe links cleanly (no undefined symbols from the new functions). NVENC reconfigure struct fields/version confirmed against the bundled ffnvcodec/nvEncodeAPI.h.
  • Client contract: response shapes verified against Moonlight V+ NvHTTP/AdaptiveBitrateService source.
  • Runtime-verified on a live Moonlight V+ stream (NVENC AV1, Windows). The live nvEncReconfigureEncoder path is exercised: as the client's bitrate control moved (e.g. 20000↔21000 kbps), the host applied each change in place — no encoder rebuild, no reconnect. Host log:
    Info: Client [waa] set runtime bitrate to 21000 kbps (1 session(s))
    Info: NvEnc: H.264/AV1 bitrate reconfigured to 21000 kbps
    Info: Applied runtime bitrate 21000 kbps (live)
    
    Upward changes force a single IDR by design (resetEncoder+forceIDR); no full reinit. The avcodec reinit-fallback path (AMD/Intel/SW) hasn't been exercised yet.

Client reference: qiin2333/moonlight-vplus (NvHTTP.setBitrate, getAbrCapabilities, AdaptiveBitrateService). Server reference: AlkaidLab/foundation-sunshine (#193 /bitrate + NVENC reconfigure, #571 /api/abr/*, ClassicOldSong#690 max_bitrate clamp).

Lets Foundation/Moonlight V+ clients change the encoder bitrate mid-stream
without reconnecting.

Endpoints (HTTPS, paired client, View permission):
- GET /bitrate?bitrate=<kbps>   apply a new encoder bitrate to the caller's
  active session. Clamped to config max_bitrate and an absolute 500 Mbps ceiling.
  Returns <root status_code=200><bitrate>N</bitrate></root> (0 = failure), the
  shape V+ NvHTTP.setBitrate() parses (and verifyResponseStatus requires the
  status_code attribute).
- GET /api/abr/capabilities     reports {supported:false,...}; verified against
  AdaptiveBitrateService.kt this makes V+ run its local ABR controller, which
  drives the host via /bitrate. Server-side ABR decisioning (/api/abr,
  /feedback) is intentionally out of scope.

Apply path: a per-session mail event (dynamic_bitrate), coalesced to the latest
value on the encode thread. NVENC reconfigures the live encoder via
nvEncReconfigureEncoder (no hitch; ported from foundation-sunshine, adapted to
Vibepollo's runtime-versioned NVENC structs). avcodec encoders (AMD/Intel/
software) report set_bitrate()==false and fall back to an encode-session rebuild
via the existing reinit path -- correct, but produces a brief hitch per change.

Not yet compiled (needs the MSYS2/MinGW toolchain); review/runtime test pending.
@Nonary

Nonary commented Jun 24, 2026

Copy link
Copy Markdown
Owner

Thanks for putting this together. The runtime bitrate path looks like the right direction for Moonlight V+ / Foundation-compatible clients, and I agree with keeping the paired-client /bitrate endpoint separate from the Web UI API surface.

I do want to call out one integration issue before this lands: any non-RTSP / Web UI-facing controls need to be registered through the normal API route layer, not only through the NvHTTP server.

Right now these endpoints are registered directly on the GameStream/NvHTTP server:

  • GET /bitrate
  • GET /api/abr/capabilities

That is fine for the Moonlight-compatible paired-client path, because those requests authenticate with the paired client certificate and View permission. But API-token route discovery is dynamic only for routes registered via register_api_route() in the Web UI/API server. Direct NvHTTP registrations will not be included in /api/token/routes, and they will not get the normal Web UI API behavior such as bearer-token auth and CSRF handling for mutating calls.

For the Web UI/admin/API-token surface, I think we should add a separate managed API route, for example:

  • POST /api/rtsp/sessions/{id}/bitrate
  • or POST /api/clients/{uuid}/bitrate

That route should be registered with register_api_route(), use the existing Web UI authentication path, and then call the same underlying stream bitrate helper. It should not rely on get_verified_cert(), since an API token/session cookie is an admin identity rather than a Moonlight paired-client identity.

The split I would like to see is:

  • Keep /bitrate?bitrate=<kbps> for Moonlight V+ / Foundation client compatibility.
  • Keep /api/abr/capabilities on NvHTTP only if it is strictly a Moonlight client discovery endpoint.
  • Add any Web UI/admin bitrate controls as confighttp.cpp API routes through register_api_route() so token scopes and route cataloging work correctly.

One other compatibility note: this PR does not make stock/legacy Moonlight clients client-driven adaptive bitrate clients. Stock Moonlight negotiates bitrate at stream startup and does not call this endpoint. Legacy clients can still benefit from server-driven runtime bitrate changes if the host changes encoder bitrate transparently, but any manual slider or client-side ABR behavior requires a client that knows how to call /bitrate, such as Moonlight V+.


This comment was AI generated, but has been peer reviewed by the repository owner.

@AsafMah

AsafMah commented Jun 25, 2026

Copy link
Copy Markdown
Author

Thanks — and agreed on the split.

To confirm scope: this PR is intentionally paired-client only. Both /bitrate and /api/abr/capabilities are Moonlight-client-facing — they authenticate with the paired client cert + View permission and exist purely for Moonlight V+ / Foundation compatibility. There's no Web UI/admin control surface here, so nothing bypasses the register_api_route() layer or expects token-scope cataloging. /api/abr/capabilities is strictly client ABR discovery (it returns supported:false so V+ runs its local controller against /bitrate).

For an admin/Web-UI bitrate control I'm happy to add a separate managed route in confighttp.cpp — e.g. POST /api/clients/{uuid}/bitrate via register_api_route(), using the Web UI auth path (no get_verified_cert()) and calling the same underlying stream::set_bitrate_for_sessions() helper that /bitrate already uses. That helper is deliberately decoupled from the HTTP layer, so both surfaces share one code path. Want that in this PR, or as a follow-up?

Fully agree on the compatibility note: stock Moonlight negotiates bitrate at startup and never calls this, so manual-slider / client-ABR behavior needs a client that knows the endpoint (V+). Legacy clients only benefit from host-driven changes.


This comment was drafted with an AI assistant and reviewed by me before posting.

@AsafMah

AsafMah commented Jun 25, 2026

Copy link
Copy Markdown
Author

Update: this is now runtime-verified (updated the description's Verification section). On Windows with NVENC (AV1), driving the bitrate from Moonlight V+ applies changes in place via the live nvEncReconfigureEncoder path — no encoder rebuild, no reconnect. Host log over a few seconds as the client control moved:

Info: Client [waa] set runtime bitrate to 21000 kbps (1 session(s))
Info: NvEnc: H.264/AV1 bitrate reconfigured to 21000 kbps
Info: Applied runtime bitrate 21000 kbps (live)
Info: Client [waa] set runtime bitrate to 20000 kbps (1 session(s))
Info: NvEnc: H.264/AV1 bitrate reconfigured to 20000 kbps
Info: Applied runtime bitrate 20000 kbps (live)

The avcodec reinit-fallback path (AMD/Intel/SW) still hasn't been exercised — if anyone runs one of those backends, the same flow should log Applied runtime bitrate … (sync) / Rebuilding encoder … instead.


This comment was drafted with an AI assistant and reviewed by me before posting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants