feat(ui): make stream framerate and bitrate configurable

Merged
AL
15h ago
+463-52

Following up on the Reddit thread where you said making these configurable made sense — the defaults are deliberately left alone so lower-end chips and wifi cards keep the behaviour they have now.

Based on feat/geforce-now, so it targets that branch rather than master.

What does this change?

Adds two rows to the settings screen, TARGET FRAMERATE (30/60) and MAX BITRATE (4/6/8/12/16/20 Mbps), so the GeForce NOW stream profile can be changed on device instead of being fixed at build time.

  • persistent_settings.zig — stores frames_per_second and max_bitrate_kbps with save/parse entries. Unknown keys are still skipped, so existing settings.tsv files load unchanged and remain readable by older builds.
  • handheld_ui.zig / handheld_ui.h — exposes both values to C through accessors mirroring the existing stream_width / stream_height pattern.
  • geforce_now/session_client.zig — reads the framerate when the client is created and uses it for stream-mode selection.
  • geforce_now/webrtc_session.zig — reads the bitrate ceiling at both request sites (requestBitrate and the NVST SDP answer), replacing the fixed maximum_bitrate_kbps constant, which is removed as it had no remaining references.
  • settings_view.zig — the two rows, plus row spacing adjusted from 48px to 46px so six rows fit above the footer.

Defaults are unchanged: 30 fps and 6000 kbps, matching the previous hard-coded values. Nothing changes for existing users unless they opt in. The Xbox path is untouched.

Two behaviours worth flagging for review:

  • Both values are negotiated at stream start, so a change applies to the next stream, not the running one. The settings screen states this.
  • sdp_protocol.zig derives the opening bid as max(4000, ceiling/4). With ceilings at or below 16000 that stays 4000 as before; at 20000 it becomes 5000. Happy to cap the choices at 16000 if you'd rather the initial request never move.

How did you test it?

Built with tools/bootstrap.sh && tools/zig.sh build release on an x86_64 Linux host and deployed to an Anbernic RG35XX-H (H700) running muOS 2508.2.

  • tools/zig.sh build test — passes
  • tools/zig.sh build fmt-check — passes
  • tools/zig.sh build release — passes

The 60 fps / 12 Mbps figures below come from a build carrying those values directly — the same profile the new settings select — captured with GREENOVERCAST_DEBUG=1 over several minutes of Control:

[118s] frames=7111/6852 source=1024x768 decoder=2 backpressure=0 decode_errors=0/0/0 queue=0/0
[119s] frames=7171/6912 source=1024x768 decoder=2 backpressure=0 decode_errors=0/0/0 queue=0/0
[120s] frames=7232/6972 source=1024x768 decoder=2 backpressure=0 decode_errors=0/0/0 queue=0/0

60 frames/second, cedar-h616 hardware decode, no decode errors, no backpressure, no queue growth. Also verified settings persist across restarts and that a pre-existing settings.tsv without the new keys still loads.

I have not tested on Rockchip/MPP hardware or the Miyoo Flip — only the H700 above.

Review follow-up (cubic)

All four cubic findings were valid. They're fixed as separate commits, following the focused fix(<scope>): pattern from #3:

  • 9e42628 fix(gfn): selectStreamMode overwrote the configured rate with the 30 fps constant and matched entitled resolutions against it. So in 52900e5, choosing 60 in settings still requested 30. The 60 fps log above came from a build with the value hard-coded, which skips that path. That's why it didn't show up. New test: stream mode selection keeps the configured frame rate (fails when the fix is reverted).
  • dbed9b6 fix(ui): persisted framerate/bitrate are only accepted when they're one of the offered choices; otherwise the default stays. The choice lists moved into persistent_settings.zig so the view and the parser share them. New test covers 0, 1 and u32 max.
  • bbf4d56 fix(gfn): the bitrate ceiling is clamped to 4000-20000 kbps once, before both the NVST answer and requestBitrate, and widened to u64 before the bps conversion.

Re-run after the fixes: build test, build fmt-check and build release all pass, and nm -D --undefined-only webrtc_stream | grep -c __aarch64_ is 0. The fixed build is deployed to the RG35XX-H with settings at 60 fps / 16 Mbps. Confirmed on device: the session requested 1024x768@60 and averaged 60.15 fps over about 4.5 minutes with no decode errors. Details and a log excerpt are in this comment.

AI disclosure

Per CONTRIBUTING: this was written with AI assistance (Claude). I reviewed the diff, built it, and verified the behaviour on hardware myself. Happy to adjust anything — naming, the bitrate choices, or the row ordering.


Summary by cubic

Makes GeForce NOW stream framerate and bitrate configurable from the settings screen, keeping the previous defaults of 30 FPS and 6 Mbps so existing behavior is unchanged.

  • Adds TARGET FRAMERATE (30/60 FPS) and MAX BITRATE (4/6/8/12/16/20 Mbps) rows to the GeForce NOW settings screen; D-pad left/right adjust the values, with bitrate cycling through the choices both ways.
  • Changes apply when the next stream starts; the running stream is unaffected.
  • The configured framerate now carries into stream mode selection instead of being reset to 30 FPS.
  • Preferences are validated against the offered choices before a stream starts, and the bitrate ceiling is clamped to 4000-20000 kbps before negotiation.
  • Settings files without these keys, or with values outside the offered choices, fall back to the defaults.
  • Xbox streaming is untouched.

Summary by CodeRabbit

  • New Features
    • GeForce NOW settings let you choose 30 or 60 FPS and a maximum bitrate of 4,000–20,000 Kbps from available options.
    • Your choices are saved and apply to the next stream. Streams use the selected frame rate when an eligible mode is available and respect the configured bitrate limit. Defaults are 30 FPS and 6,000 Kbps.
    • A notice indicates that quality changes take effect on the next stream.