mirror of
https://github.com/jmelovich/Steam-Link-PS5.git
synced 2026-10-19 02:10:53 -05:00
Prepare the repository for release
Clean up the development scaffolding and document the project for sharing. Removed: - patches/ihslib/0050-diagnostics-window-state.patch, a temporary packet-window tracing patch used to diagnose the stalls. - The per-second diagnostic logging added while chasing the stutter (present cadence, arrival-gap histogram, decode-cost histogram). The operational lines stay: state, session config, video/audio start, per-5s stats, video-out mode, rumble/LED, and the stream-size-change notice. - PLAN.md, an internal planning document that also carried a developer's LAN address. No tracked file now contains a host-specific address or path. Kept and brought current: - PINS.md now documents every patch in patches/ihslib/ (0015, 0016, 0018, 0019 and 0020 were missing) and no longer claims there are only two of them. - README.md rewritten: it still said 'Status: planning. Nothing builds yet.' It now leads with what works, states the console and firmware actually tested (CFI-2015, 13.20), and gives a per-feature verified / implemented-but-unverified / not-implemented breakdown rather than implying everything is proven. Build, install and usage instructions, the settings reference, the two host-side gotchas that look like client bugs (a sleeping PC display stops Steam capturing; Wayland hosts need 'steam -pipewire'), and the licensing position. No functional change; verified the title still builds, deploys and streams.
This commit is contained in:
1 parent
3e3a60e664
commit
50d4b17a19
5 files changed
+162
-448
No files matched your search
@@ -7,7 +7,7 @@ commit the submodule change here together with a note below.
|
||||
|
||||
| Component | Where | Revision | Why this one |
|
||||
| --- | --- | --- | --- |
|
||||
| [ihslib](https://github.com/scaronni/ihslib) branch `auth` | `third_party/ihslib` | `f68de5f0` (2026-10-04) | Upstream [mariotaku/ihslib](https://github.com/mariotaku/ihslib) master `1881b9a0` plus two commits implementing Steam's 2026 PIN-masked X25519 pairing (`crypto: add SHA-256, X25519 and Steam's key exchange`, `client: pair with the PIN-masked key exchange`). Without them current Steam answers `AuthorizationFailed` once the PIN is typed. Switch back to upstream once they are merged. `patches/ihslib/` carries two small robustness patches on top (see below). |
|
||||
| [ihslib](https://github.com/scaronni/ihslib) branch `auth` | `third_party/ihslib` | `f68de5f0` (2026-10-04) | Upstream [mariotaku/ihslib](https://github.com/mariotaku/ihslib) master `1881b9a0` plus two commits implementing Steam's 2026 PIN-masked X25519 pairing (`crypto: add SHA-256, X25519 and Steam's key exchange`, `client: pair with the PIN-masked key exchange`). Without them current Steam answers `AuthorizationFailed` once the PIN is typed. Switch back to upstream once they are merged. `patches/ihslib/` carries the patches listed below on top. |
|
||||
| [protobuf-c](https://github.com/protobuf-c/protobuf-c) | `third_party/protobuf-c` | v1.5.2 | ihslib's generated sources need ≥ 1.3.0; 1.5.2 is what they were regenerated with. |
|
||||
| [mbedtls](https://github.com/Mbed-TLS/mbedtls) | `third_party/mbedtls` | v3.6.4 | The revision ProsperoLight proved on the console; 4.x drops APIs ihslib uses (`mbedtls_md_hmac`). Built with `platform/ps5/mbedtls_ps5_config.h`. |
|
||||
| [opus](https://github.com/xiph/opus) | `third_party/opus` | v1.5.2 | Same as ProsperoLight. Intrinsics off (the SDK's toolchain has no runtime CPU dispatch we trust). |
|
||||
@@ -66,5 +66,30 @@ commit the submodule change here together with a note below.
|
||||
- `0013-send-acks-redundantly-on-lossy-uplink.patch`: send each ACK three times so reliable
|
||||
control handshakes (start input reports, keyframe/config delivery) survive a lossy uplink
|
||||
where single ACKs are lost and the host otherwise gives up.
|
||||
- `0015-control-recover-out-of-sequence-message.patch`: a control message whose encryption
|
||||
counter skipped ahead was thrown away even though it had already decrypted and passed its
|
||||
HMAC. Losing one such message cost either "start input reports" (the pad connects but sends
|
||||
no input) or a stream-start message (a black screen), at random, on roughly 1 in 12 startups.
|
||||
Resync the counter *and* deliver the message - a forward skip can never be a replay.
|
||||
- `0016-control-window-hold-the-startup-burst.patch`: the control frame window held 128 slots,
|
||||
too few for Steam's Big Picture startup burst while one lost reliable packet healed, so it
|
||||
overflowed and disconnected before the video channel opened. Raised to 2048; the host's
|
||||
resend then drains the backlog in order.
|
||||
- `0018-video-keep-decoding-through-small-gap.patch`: any forward frame-sequence gap - even a
|
||||
single frame - requested a keyframe and then dropped *every* frame until a full IDR arrived.
|
||||
On a good link that fired about once a second, each time freezing the picture for the
|
||||
keyframe round trip. Now a small gap asks for a keyframe once (coalesced, backed off from a
|
||||
flat 200 ms to 500 ms) and keeps decoding, so a one-frame loss is a brief artifact the next
|
||||
keyframe wipes rather than a freeze. Only a large desync still hard-waits; the decoder's own
|
||||
reset path remains the backstop.
|
||||
- `0019-video-soft-partial-stall.patch`: the same over-reaction on the other path - a frame
|
||||
missing a fragment hard-waited for a keyframe, measured at 200-640 ms per event. Drop the
|
||||
stuck partials, ask once, keep decoding.
|
||||
- `0020-negotiation-advertise-display-refresh.patch`: the negotiation hard-coded 59.94 Hz as
|
||||
both the available video mode's refresh and the client's maximum framerate, so a user who
|
||||
chose 120 Hz in Steam's Remote Play settings was silently vetoed (the host streams at the
|
||||
lower of the two) and the resulting disagreement showed up as doubled latency and artifacts.
|
||||
`IHS_SessionConfig` gains `refreshRateNum/Den`; the app measures its real refresh rate and
|
||||
advertises that, leaving the host's settings in charge.
|
||||
|
||||
All are candidates for upstream.
|
||||
@@ -1,276 +0,0 @@
|
||||
# steamlink-ps5: plan
|
||||
|
||||
A native Steam Remote Play client for a jailbroken PS5, as a homebrew title. Target
|
||||
experience: the TV Steam Link app. Turn it on, it finds the PC running Steam, pair once
|
||||
with a PIN, Big Picture Mode streams to the TV, the DualSense controllers just work.
|
||||
|
||||
This document records the research (October 2026), the decision, the architecture, the
|
||||
build plan and the milestones. Nothing is built yet.
|
||||
|
||||
## 1. Decision in one paragraph
|
||||
|
||||
There is no open-source Steam Link client to port: Valve's client is proprietary, and its
|
||||
Linux binary cannot run on the PS5 (a title may make no system call and the console is
|
||||
FreeBSD-derived, not Linux). The protocol, however, has been reverse engineered:
|
||||
**[ihslib](https://github.com/mariotaku/ihslib)** (C, LGPL-3.0) implements discovery,
|
||||
pairing and streaming of Steam Remote Play, is actively maintained (last commit
|
||||
2026-10-05, Ghidra-driven work against the real Steam client), and already powers a
|
||||
Switch homebrew client ([nsteamlink](https://github.com/scaronni/nsteamlink), hardware
|
||||
NVDEC) and a Raspberry Pi 5 client ([plume](https://github.com/beudbeud/plume)). On the
|
||||
PS5 side, **[ProsperoLight](https://github.com/blackbearreloaded/ProsperoLight)**
|
||||
(GPL-3.0-or-later, a Moonlight client for PS5, last commit 2026-10-07) has already solved
|
||||
everything hardware-specific: VideoDec2 hardware H.264/HEVC decode presented through AGC
|
||||
without CPU copies, Opus audio through AudioOut, up to four DualSense controllers through
|
||||
ScePad, an OpenGL launcher UI, and a Make-based build with FTP deploy built on
|
||||
[ps5-native-app-boilerplate](https://github.com/blackbearreloaded/ps5-native-app-boilerplate).
|
||||
|
||||
**So: do not port ihsplay (the SDL2/LVGL webOS front end, stale since mid-2024). Write a
|
||||
new, thin PS5 application that uses ihslib for the protocol and ProsperoLight's
|
||||
platform code for the console.** The protocol is 100 % done elsewhere, the PS5 media
|
||||
path is about 70 % done elsewhere, and our code is the glue: a ScePad HID provider for
|
||||
ihslib, a pthread backend, a VideoDec2 video sink, an Opus-to-AudioOut sink, and the
|
||||
screens.
|
||||
|
||||
## 2. What exists (research findings)
|
||||
|
||||
| Project | What it is | Verdict for us |
|
||||
| --- | --- | --- |
|
||||
| [ValveSoftware/steamlink-sdk](https://github.com/ValveSoftware/steamlink-sdk) | SDK for apps *on* the Steam Link box; the streaming client is a closed binary | Not usable. Confirms no official source. |
|
||||
| [mariotaku/ihslib](https://github.com/mariotaku/ihslib) | Steam Remote Play protocol library: UDP discovery (port 27036), PIN authorization, streaming request, session with control/stats/data channels, AES-CBC control crypto, H.264 and HEVC frame reassembly, Opus audio, mouse/keyboard/touch input, HID gamepad channel (incl. `IHS_CONTROLLER_TYPE_PS5`), microphone, frame stats. Deps: protobuf-c runtime, mbedtls (or OpenSSL), a threads backend (SDL2 upstream). LGPL-3.0, active. | **The protocol core.** |
|
||||
| [mariotaku/ihsplay](https://github.com/mariotaku/ihsplay) | SDL2 + LVGL + `ss4s` media-sink client for webOS/RPi, GPL-3.0 | Reference for UI flow and stream lifecycle only; last commit June 2024, SDL-bound. Not ported. |
|
||||
| [beudbeud/ihslib `plume`](https://github.com/beudbeud/ihslib/tree/plume) | Fork: +25 fixes (retransmit deadlocks, control-channel serialization, HID bugs), **pure POSIX thread backend** (`ihs_thread_posix.c`), no SDL | Source of the pthread backend; cherry-pick what upstream lacks. |
|
||||
| [scaronni/nsteamlink](https://github.com/scaronni/nsteamlink) | Switch client on ihslib (kxn fork, +72 over upstream incl. a cumulative-ACK fix that cured 20 s input stalls), FFmpeg/NVDEC, SDL2. GPL-3.0. Excellent RE docs (`docs/STEAMLINK_PROTOCOL_RE.md`, `OFFICIAL_INPUT_RE.md`, `STEAM_REMOTE_PLAY_AUTH.md`) | **The closest precedent**: console + hardware decoder + ihslib. Its docs are our protocol reference; its author now upstreams to mariotaku. |
|
||||
| [blackbearreloaded/ProsperoLight](https://github.com/blackbearreloaded/ProsperoLight) | Native Moonlight (Sunshine) client for PS5: VideoDec2 + AGC zero-copy, 1080p–2160p at 30–120 FPS, HEVC Main10 HDR, Opus → AudioOut stereo/5.1, 4 DualSense + USB keyboard/mouse, OpenGL launcher (ps5-homebrew-ui), self-update, `make deploy PS5_HOST=`. GPL-3.0-or-later, active | **The PS5 platform donor**: VideoDec2 structs and link stubs, AGC presenter, AudioOut, ScePad, sockets→sceNet adapter, mbedtls PS5 config and entropy, build/deploy tooling. |
|
||||
| [blackbearreloaded/ps5-native-app-boilerplate](https://github.com/blackbearreloaded/ps5-native-app-boilerplate) | Template ProsperoLight is built on: Make + `tools/build.sh`, downloads the public ps5-payload-dev SDK v0.42 and zlib, clean-room `libc.prx`, ELF→FSELF converter, `make init/deploy/undeploy`, param.json validation | **Our project skeleton.** |
|
||||
| [blackbearreloaded/ps5-hardware-video-decoding-research](https://github.com/blackbearreloaded/ps5-hardware-video-decoding-research) | Measurements: HEVC 4K60 decode 5.5 ms, 16.7 ms access-unit-to-flip; H.264 High and HEVC Main at 1080p/1440p/2160p; AV1 not available | Sets expectations for latency and bitrate. |
|
||||
| [blackbearreloaded/ps5-homebrew-ui](https://github.com/blackbearreloaded/ps5-homebrew-ui) + [ps5-opengl](https://github.com/blackbearreloaded/ps5-opengl) | OpenGL UI kit (~100 widgets, themes, finished screens, PC preview) on a Mesa port; prebuilt SDK downloaded by script | Launcher UI (milestone 5). |
|
||||
| `../rpcs3-PS5` (local) | RPCS3 as a PS5 title: lavavex PS5_PayloadSDK fork + PS5_Vulkan RADV link recipe, `tools/deploy.sh` (curl FTP), `ps5/ps5_pad_handler.cpp`, `ps5_audio_backend.cpp` | Deploy-script shape and the libc-gap lessons. Its Vulkan/RADV toolchain is far heavier than we need: a streaming client needs no Vulkan. |
|
||||
| Thalium, [Bug hunting in Steam Remote Play](https://blog.thalium.re/posts/achieving-remote-code-execution-in-steam-remote-play/) | Protocol write-up: 32 channels, AES-CBC with IV = HMAC-MD5, codec negotiation | Background reading. |
|
||||
|
||||
Protocol facts that shape the design (from ihslib's headers and nsteamlink's RE docs):
|
||||
|
||||
- Pairing is Steam-Link-style: **the client shows a PIN and the user types it into Steam
|
||||
on the PC** (Settings → Remote Play → Pair Steam Link). No keyboard needed on the console.
|
||||
The client identity (`deviceId` + `secretKey` → device token) must persist, or Steam
|
||||
asks to pair again.
|
||||
- The host sends complete video frames; ihslib reassembles fragments and emits one
|
||||
Annex-B access unit per `submit` callback (keyframe flag included, SPS/PPS in
|
||||
`codecData`). That is exactly what VideoDec2 consumes, and what ProsperoLight already
|
||||
feeds it from Moonlight.
|
||||
- Audio is Opus 48 kHz (channel count requested by the client).
|
||||
- Gamepads go through the HID channel: the client enumerates "devices" with vid/pid and
|
||||
streams 48-byte input reports (6 axes, 16 buttons, gyro, accel, touch), optionally as
|
||||
deltas; the host sends rumble back as HID writes. ihslib's SDL provider is the template
|
||||
for a ScePad provider (~1,500 lines of SDL code, most of it enumeration plumbing).
|
||||
- Timestamps on the wire are 16.16 fixed-point seconds; the session handles it.
|
||||
|
||||
## 3. Alternatives considered
|
||||
|
||||
- **Moonlight/Sunshine instead of Steam Remote Play.** ProsperoLight already exists and
|
||||
streams Steam Big Picture fine *if* the PC runs Sunshine. It is the fallback if the
|
||||
Steam protocol turns out to be a moving target, but it is not what was asked for:
|
||||
Remote Play needs nothing installed on the PC beyond Steam.
|
||||
- **Port ihsplay wholesale.** It would drag in SDL2 (no PS5 port), LVGL and the `ss4s`
|
||||
sink layer, all for a UI we would replace anyway. Its `app/backend/stream/*` files are
|
||||
worth reading for lifecycle ordering, nothing more.
|
||||
- **Port nsteamlink.** Same SDL2 problem, plus Switch-specific NVDEC and libnx code. Its
|
||||
protocol-level fixes matter; its application does not.
|
||||
- **rpcs3-PS5's toolchain (lavavex SDK fork + PS5_Vulkan).** Needed there because RPCS3
|
||||
renders with Vulkan. A streaming client presents decoded surfaces through AGC directly,
|
||||
which the public ps5-payload-dev SDK plus ProsperoLight's link stubs already cover.
|
||||
Avoiding the RADV build keeps a first build at minutes rather than tens of minutes.
|
||||
|
||||
## 4. Architecture
|
||||
|
||||
```
|
||||
Steam on the PC (Remote Play enabled)
|
||||
| UDP 27036 discovery / auth | UDP session: control, stats, data channels
|
||||
───────────────┼─────────────────────────────────────┼──────────────────────────────────
|
||||
v v
|
||||
ihslib client ihslib session
|
||||
(discover, PIN authorize, (crypto, reassembly, retransmit, stats)
|
||||
streaming request) callbacks: video audio input/HID
|
||||
| | | ^
|
||||
v v v |
|
||||
launcher (host list, VideoDec2 sink Opus → ScePad HID provider
|
||||
PIN screen, settings) H.264/HEVC AU AudioOut (4 users, rumble,
|
||||
| → GPU surface touchpad = mouse)
|
||||
v | |
|
||||
ps5-homebrew-ui (OpenGL) AGC presenter → VideoOut scePadReadState
|
||||
or CPU text UI until M5 (zero-copy, flip on vblank) (4 ms poll thread)
|
||||
```
|
||||
|
||||
Components and where each comes from:
|
||||
|
||||
| Component | Source | Our work |
|
||||
| --- | --- | --- |
|
||||
| Protocol (`ihslib`) | submodule, pinned to upstream `master` | A `src/platform/ihs_thread_posix.c` (pthread; 75 lines, or lifted from the `plume` fork), a small patch set under `patches/ihslib/` for anything PS5-specific, cherry-picked fixes from the nsteamlink fork that upstream lacks. Build it from a file list, not its CMake (pkg-config is a liability on the cross toolchain). |
|
||||
| protobuf-c runtime | submodule, one `.c` | None. |
|
||||
| mbedtls | submodule | ProsperoLight's `mbedtls_ps5_config.h` and `ps5_entropy.c` (`sysctl kern.rng_pseudo`; `/dev/urandom` is also available). ihslib needs AES-CBC/ECB, RSA-OAEP (authorization), CTR-DRBG. |
|
||||
| Opus | submodule | None (decoder only). |
|
||||
| Video sink | ProsperoLight `moonlight_stream.cpp` (VideoDec2 structs, create/decode/flush/reset, decoder memory, compute queue) and `native_agc_present.cpp` (1,840 lines) | Extract into `src/video/videodec2.cpp` + `src/video/agc_present.cpp` with a codec-agnostic `submit(access_unit, keyframe)` interface; keep decode and present on separate threads as ProsperoLight does. Report `IHS_SessionReportVideoFrameStage/Complete` so Steam's quality control sees decode time. |
|
||||
| Audio sink | ProsperoLight's AudioOut thread; `../rpcs3-PS5` `ps5_audio_backend.cpp` (same port API) | `src/audio/opus_audioout.cpp`: Opus → S16 stereo → `sceAudioOutOutput`, bounded backlog, drop on overrun. 5.1 later via `audioChannelCount`. |
|
||||
| Input | ihslib `src/hid/sdl/*` as the template; ProsperoLight `ps5_dualsense.cpp` for ScePad (users, slots, rumble, motion, touch) | `src/hid/ps5/*`: an `IHS_HIDProvider` whose devices are the signed-in users' pads, producing the 48-byte report from `ScePadData`; `write` → `scePadSetVibration`. Touchpad-as-mouse and PS+Options overlay chord via `IHS_SessionSendMouse*`. Text entry via `sceImeDialog` → `IHS_SessionSendText`. |
|
||||
| Launcher UI | ps5-homebrew-ui + ps5-opengl (prebuilt SDK download) | Screens: hosts (auto-discovered, state), pairing (PIN display with countdown), settings, in-stream overlay (stats, disconnect, quit game), errors. Until M5, a CPU-rendered text screen from the boilerplate skeleton is enough to drive the stream. |
|
||||
| Network | ProsperoLight `platform/ps5/ps5_sockets.c` (BSD API over `sceNet*`, epoll) | None expected. No DNS is needed (IPs only), which sidesteps the console's broken `getaddrinfo`. |
|
||||
| Storage, logging | ProsperoLight `app_storage.cpp` shape; rpcs3's `/data/<app>` convention | `/data/steamlink-ps5/` (ShadowMount+ 1.7beta4+ mounts `/data` into the sandbox; the dev console has it) with `/app0/` fallback (writable nullfs of the title folder). Log file + `sceKernelDebugOutText` so klog shows it. Pairing identity, hosts and settings as a small text/JSON file. |
|
||||
| Build, package, deploy | ps5-native-app-boilerplate `tools/`, `tooling/native/`, `runtime/` | Import as-is; add `tools/build-deps.sh`. |
|
||||
|
||||
Threads at run time: ihslib client thread, session receive thread (+ its timers), video
|
||||
decode thread, present thread (vblank), audio output thread, pad poll thread (4 ms),
|
||||
UI/main thread. The console gives a title 13 usable cores; the SDK reports 16, so size
|
||||
nothing from `sysconf`. Give every thread an explicit stack (256 KB+); the default may
|
||||
not match what the library expects (rpcs3-PS5 wraps `pthread_create` for this).
|
||||
|
||||
Lifecycle, following ProsperoLight: the launcher and the stream never own the display at
|
||||
the same time. Launcher tears down (EGL display, its pad handle, its audio port) →
|
||||
stream opens VideoOut, pads, AudioOut → stream ends → launcher is rebuilt. Returning
|
||||
from HFR/HDR modes waits for the TV to settle.
|
||||
|
||||
## 5. Build system (deliberately plain)
|
||||
|
||||
One `make`, from a fresh clone, on Arch or Debian/Ubuntu/WSL:
|
||||
|
||||
```bash
|
||||
git clone --recursive <repo>
|
||||
cd steamlink-ps5
|
||||
make doctor # checks clang/lld 18, python3, curl, wget, unzip
|
||||
make # → dist/<TITLE_ID>/
|
||||
PS5_HOST=192.168.0.192 make deploy
|
||||
```
|
||||
|
||||
- `Makefile` and `tools/build.sh` come from the boilerplate: they fetch and verify the
|
||||
pinned public ps5-payload-dev SDK and zlib into `.deps/`, compile every file under
|
||||
`src/` with the SDK's clang, link with `prospero-lld` plus `--wrap`/`--defsym` bindings,
|
||||
convert the ELF to a development FSELF with the repository-owned converter, generate the
|
||||
clean-room `runtime/libc.prx`, and assemble `dist/<TITLE_ID>/{eboot.bin,sce_sys,sce_module}`.
|
||||
- `tools/build-deps.sh` (modelled on ProsperoLight's `build-stream-deps.sh`) builds
|
||||
`build/deps/lib{ihslib,protobuf-c,mbedcrypto,mbedtls,mbedx509,opus}.a` once with the
|
||||
same cross compiler. ihslib and protobuf-c are compiled from explicit file lists;
|
||||
mbedtls and opus through their own CMake with the SDK's `toolchain/prospero.cmake`,
|
||||
with pkg-config disabled (`PKG_CONFIG_LIBDIR=/nonexistent`).
|
||||
- `tools/deploy.sh` (already in this repo, adapted from rpcs3-PS5): curl FTP upload of
|
||||
`dist/<TITLE_ID>` to `/data/homebrew/`, `eboot.bin` and `param.json` last, size check
|
||||
afterwards. `tools/ps5sh.py` for `launch`/`kill`.
|
||||
- Third-party code is git submodules pinned by commit, listed in `PINS.md` with the
|
||||
reason for each pin, as rpcs3-PS5 does. Patches live in `patches/<project>/` and are
|
||||
applied by `tools/build-deps.sh`, never committed into the submodule.
|
||||
- No CMake at the top level, no host package discovery, nothing installed globally.
|
||||
Toolchain note: the boilerplate insists on LLVM 18 (`llvm-config-18`); on Arch that is
|
||||
the `llvm18`/`clang18`/`lld18` packages (to verify in M0), not the system clang.
|
||||
- Optional later: `make ffpfsc` (compressed image) and a GitHub Actions build, both
|
||||
already written in the boilerplate.
|
||||
|
||||
Proposed layout:
|
||||
|
||||
```
|
||||
Makefile PINS.md PLAN.md README.md LICENSE
|
||||
sce_sys/ param.json, icon0.png (pic0/pic1/snd0 stay out of git)
|
||||
src/
|
||||
main.cpp storage, log, launcher ↔ stream handover
|
||||
app/ hosts registry, pairing identity, settings (plain files)
|
||||
ui/ screens (text UI first, ps5-homebrew-ui from M5)
|
||||
stream/ session owner: ihslib callbacks → sinks, stats, overlay chords
|
||||
video/ videodec2.cpp, agc_present.cpp (from ProsperoLight)
|
||||
audio/ opus_audioout.cpp
|
||||
hid/ps5/ IHS_HIDProvider for ScePad (template: ihslib/src/hid/sdl)
|
||||
platform/ ps5_sockets.c, ps5_entropy.c, ihs_thread_posix.c, runtime shims
|
||||
patches/ ihslib/*.patch
|
||||
third_party/ ihslib, protobuf-c, mbedtls, opus, ps5-homebrew-ui (submodules)
|
||||
tooling/ runtime/ boilerplate's converter, CRT, libc.prx builder (imported)
|
||||
tools/ build.sh, build-deps.sh, deploy.sh, ps5sh.py, doctor.sh
|
||||
vendor/ps5/sdk/stubs/ link stubs for VideoDec2, VideoOut, AGC driver (from ProsperoLight)
|
||||
docs/
|
||||
```
|
||||
|
||||
## 6. Milestones
|
||||
|
||||
Each milestone ends with something that runs on the console; the dev console is the
|
||||
PS5 at `192.168.0.192` (firmware 13.20, etaHEN, ftpsrv 2121, shsrv 2323, klogsrv 3232,
|
||||
ShadowMount+ 1.7beta4). Pick a title ID now (`PPSA99350` unless homebrew.page wants
|
||||
another; rpcs3 is `PPSA99300`, ProsperoLight `PPSA99002`).
|
||||
|
||||
**M0 — Skeleton that launches (½–1 day).** Import the boilerplate (`tools/`, `tooling/`,
|
||||
`runtime/`, `Makefile`, `sce_sys/`), `make init TITLE_ID=… APP_NAME=…`, `make`, `make
|
||||
deploy`, title on the home screen, a line in klog and a file under `/data/steamlink-ps5/`.
|
||||
Decide the Arch LLVM 18 question. Done when the edit-deploy-launch-log loop takes under a
|
||||
minute.
|
||||
|
||||
**M1 — Protocol on the console (2–4 days).** Cross-compile protobuf-c, mbedtls, opus,
|
||||
ihslib with the pthread backend; port `ps5_sockets.c` and `ps5_entropy.c`. A headless
|
||||
flow driven by the text UI: broadcast discovery finds the Steam PC, the screen shows a
|
||||
PIN, the user pairs in Steam, the identity persists, a streaming request succeeds, the
|
||||
session connects, and the log counts video and audio frames with their sizes and codec.
|
||||
Validates the three protocol risks (broadcast in the sandbox, socket receive buffer size,
|
||||
Steam version compatibility) before any media code exists.
|
||||
|
||||
**M2 — Video (3–5 days).** VideoDec2 sink for H.264 at 1080p60, AGC presentation,
|
||||
keyframe wait on loss, `setCaptureSize` honoured, frame-stage reporting. Then HEVC
|
||||
(`enableHevc`) and 1440p/2160p request sizes. Measure access-unit-to-flip with the
|
||||
ProsperoLight method; expect ~17 ms.
|
||||
|
||||
**M3 — Audio (1–2 days).** Opus → AudioOut stereo, bounded backlog, clean start/stop with
|
||||
the session. 5.1 is a setting later.
|
||||
|
||||
**M4 — Input (3–5 days).** ScePad HID provider for player 1 (buttons, sticks, triggers,
|
||||
gyro/accel), then hot-plug and 4 users, rumble via HID writes, touchpad as mouse,
|
||||
PS-button chord for the overlay, `sceImeDialog` for text. This is the milestone with the
|
||||
most protocol uncertainty: nsteamlink's log shows several report-format revisions
|
||||
(48-byte SDL layout vs host-requested lengths, "Generic Gamepad" announcement). Start from
|
||||
upstream ihslib's SDL layout, verify against Steam's controller settings page, and adopt
|
||||
nsteamlink's findings as needed. Done when a game plays end to end with two controllers.
|
||||
|
||||
**M5 — Launcher (3–5 days).** ps5-homebrew-ui screens: hosts list with live state,
|
||||
pairing with countdown, settings (resolution cap, HEVC, audio channels, stream interface:
|
||||
Big Picture / desktop / recent games), in-stream overlay (stats, disconnect, stop game),
|
||||
error states and reconnect. Auto-connect to the last host on launch so the TV flow is one
|
||||
button.
|
||||
|
||||
**M6 — Polish (ongoing).** Quality control feedback (`setTargetBitrate` etc. are host→
|
||||
client in this protocol: honour them), stall recovery, HDR (Main10) if Steam offers it,
|
||||
120 Hz, USB keyboard/mouse passthrough (ProsperoLight has it), microphone (ihslib has the
|
||||
channel), self-update kit.
|
||||
|
||||
**M7 — Release.** README with a real build matrix, `PINS.md`, GitHub Actions build,
|
||||
homebrew.page listing. Unlike rpcs3-PS5 (GPL-2-only RPCS3 + GPL-3 SDK), every component
|
||||
here is GPL-3-compatible and the runtime shim is clean-room, so built titles can be
|
||||
shared the way ProsperoLight's are.
|
||||
|
||||
Rough total to a playable client (M0–M4): two to three weeks of focused work; the
|
||||
launcher adds one more.
|
||||
|
||||
## 7. Risks and unknowns
|
||||
|
||||
| Risk | Likelihood | Mitigation |
|
||||
| --- | --- | --- |
|
||||
| Steam changes the protocol; ihslib lags | Medium, ongoing | Pin ihslib; nsteamlink's author upstreams fixes; keep Moonlight (ProsperoLight) as the documented fallback for users. |
|
||||
| UDP broadcast discovery blocked in the title sandbox | Low–medium | ProsperoLight does mDNS multicast from a title, so sockets are fine; verify `SO_BROADCAST` in M1; manual IP entry is a feature anyway. |
|
||||
| sceNet caps `SO_RCVBUF` well below the 4 MB ihslib asks for (nsteamlink hit ~42 KB on libnx) | Medium | Measure in M1 (`ps5_sockets.c` already logs requested vs actual); if capped, drain the socket on a dedicated thread into a userspace ring. |
|
||||
| Input report format mismatch (host ignores or misreads the pad) | Medium | See M4; two independent implementations to compare against (upstream SDL provider, nsteamlink's revisions). |
|
||||
| VideoDec2 is not in the public SDK | None in practice | ProsperoLight's link stubs and struct definitions (GPL-3) are proven on 6.02 and 12.70; the dev console is 13.20, which runs ProsperoLight-era homebrew. |
|
||||
| Decoder bitrate ceiling at 4K HEVC (~80 Mb/s measured) | Known | Steam's default quality tiers sit far below; expose a resolution cap. |
|
||||
| Thread stacks and core count | Known | Explicit stack sizes; never size pools from `sysconf`. |
|
||||
| `/data` absent on older ShadowMount+ | Known | `/app0` fallback; log the chosen path at startup. |
|
||||
| Trademark | — | Name the app something that is not "Steam Link" (e.g. "Prospero Remote Play"); the repo name is fine. Unofficial, personal-use client. |
|
||||
|
||||
## 8. Day-to-day loop
|
||||
|
||||
```bash
|
||||
make # or: make deps once, then make
|
||||
tools/ps5sh.py "$PS5_HOST" kill # stop the running title
|
||||
PS5_HOST=… make deploy # or tools/deploy.sh
|
||||
tools/ps5sh.py "$PS5_HOST" "launch PPSA99350"
|
||||
setsid nohup ~/.claude/skills/ps5-console/scripts/ps5klog.sh "$PS5_HOST" /tmp/klog.txt &
|
||||
tail -F /tmp/klog.txt | grep -E "\[SLP5\]|signal:|proc name:|reason:"
|
||||
curl -s "ftp://$PS5_HOST:2121/data/steamlink-ps5/log.txt"
|
||||
```
|
||||
|
||||
Keep a Steam PC on the LAN with Remote Play enabled and a controller-friendly game; use
|
||||
Steam's own `logs/streaming_log.txt` on the PC as the second half of every investigation,
|
||||
as nsteamlink's docs do.
|
||||
|
||||
## 9. Open questions for the owner
|
||||
|
||||
1. Title ID and display name (`PPSA99350`, "Prospero Remote Play"?).
|
||||
2. Text UI through M4 and the OpenGL kit from M5 (recommended), or the kit from M0?
|
||||
3. Any interest in the Moonlight fallback being mentioned in the README, or Steam-only?
|
||||
@@ -1,26 +1,145 @@
|
||||
# steamlink-ps5
|
||||
# Steam Remote Play for PS5
|
||||
|
||||
A native Steam Remote Play ("Steam Link") client for a jailbroken PS5, built as a
|
||||
homebrew title. The goal is the TV Steam Link experience: the console finds the PC
|
||||
running Steam, pairs with a PIN, and streams Big Picture Mode with DualSense
|
||||
controllers, hardware video decoding and low latency.
|
||||
A native Steam Remote Play ("Steam Link") client for jailbroken PS5 consoles. The console
|
||||
finds your PC running Steam, pairs with a PIN, and streams Big Picture Mode with hardware
|
||||
video decoding and the DualSense — the TV Steam Link experience, on the PS5.
|
||||
|
||||
**Status: planning.** Read [PLAN.md](PLAN.md) for the research, the chosen
|
||||
architecture and the milestones. Nothing builds yet.
|
||||
**Status: working.** Streams 1080p60 with audio and controller input, and is playable.
|
||||
Not affiliated with Valve or Sony.
|
||||
|
||||
## Planned workflow
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **Tested on** | PS5 CFI-2015, firmware **13.20**, etaHEN + ShadowMount+ 1.7beta4 |
|
||||
| **Video** | H.264, hardware decode (VideoDec2), up to 1080p60, scaled to fit |
|
||||
| **Audio** | Opus → stereo |
|
||||
| **Controller** | DualSense over ScePad, seen by Steam as a real PS5 controller |
|
||||
| **Build host** | Linux or WSL; the PS5 payload SDK is fetched for you |
|
||||
|
||||
## Features
|
||||
|
||||
| Feature | State |
|
||||
| --- | --- |
|
||||
| LAN host discovery, PIN pairing, persisted pairing key | Verified on hardware |
|
||||
| H.264 hardware decode, any stream resolution scaled to fit the screen | Verified |
|
||||
| Mid-stream resolution changes | Verified |
|
||||
| Opus stereo audio | Verified |
|
||||
| DualSense input, reported to Steam as a PS5 controller (VID 054C / PID 0CE6) | Verified |
|
||||
| Options + touchpad click → Steam menu; hold 1.5 s → leave the stream | Verified |
|
||||
| Auto-connect to the last PC, settings UI, smoothing (jitter) buffer | Verified |
|
||||
| Rumble and lightbar | Implemented, **unverified** — Steam's Identify/LED controls don't reach a streamed pad, so it needs a game to exercise |
|
||||
| Gyro / accelerometer | Implemented, unverified |
|
||||
| HEVC, 5.1 audio, up to 4 controllers | Implemented, unverified (H.264 / stereo / one pad are what's been used) |
|
||||
| 120 Hz output | Implemented, **untested** — no 120 Hz display available; see below |
|
||||
| Keyboard, mouse, microphone, HDR | Not implemented |
|
||||
| Touchpad as a trackpad | Not possible over Remote Play — the host exposes no touchpad for a streamed controller. The touchpad *click* works as a bindable button |
|
||||
|
||||
### About 120 Hz
|
||||
|
||||
The client measures its real display refresh rate and advertises it during negotiation, so
|
||||
the host streams at the rate the console can actually show (nothing is hard-coded — Steam's
|
||||
own Remote Play settings decide, same as for bitrate). It also asks the console for 120 Hz
|
||||
output at startup, which needs `attribute3 = 0x80040` in `sce_sys/param.json` (set) plus a
|
||||
120 Hz panel and the system's **Enable 120 Hz Output** setting. On a 60 Hz display the
|
||||
console declines with `UNAVAILABLE_OUTPUT_MODE` and the app stays at 60 Hz — the intended
|
||||
fallback. The 120 Hz path has never run on a 120 Hz display, so treat it as unproven.
|
||||
|
||||
## Requirements
|
||||
|
||||
**Console** — a jailbroken PS5 (developed on firmware 13.20) with:
|
||||
- a payload loader (etaHEN or equivalent)
|
||||
- `ftpsrv` on port 2121 — used to deploy
|
||||
- `ShadowMount+` **1.7beta4 or newer** — it mounts `/data` into the title sandbox (the app
|
||||
needs it for its settings and log) and registers homebrew on the home screen
|
||||
- `shsrv` on port 2323 — optional, only for the command-line dev loop
|
||||
|
||||
**PC** — Steam with Remote Play enabled, on the same network.
|
||||
|
||||
**Build host** — Linux or WSL with `git`, `make`, `python3`, `curl`.
|
||||
|
||||
## Building
|
||||
|
||||
```bash
|
||||
git clone --recursive <this repo>
|
||||
make # dist/<TITLE_ID>/ (eboot.bin, sce_sys/, sce_module/)
|
||||
PS5_HOST=192.168.x.x make deploy # upload over FTP (ftpsrv, port 2121)
|
||||
git clone --recursive https://github.com/<you>/steamlink-ps5
|
||||
cd steamlink-ps5
|
||||
make doctor # checks the host for what the build needs, changes nothing
|
||||
make # fetches the SDK on first run, then builds
|
||||
```
|
||||
|
||||
Produces `dist/PPSA99350/` (`eboot.bin` + `sce_sys/`) and `dist/PPSA99350.zip`.
|
||||
|
||||
## Installing
|
||||
|
||||
```bash
|
||||
PS5_HOST=192.168.x.x make deploy
|
||||
```
|
||||
|
||||
Uploads the title to `/data/homebrew/PPSA99350/` over FTP. ShadowMount+ picks it up within
|
||||
about 15 seconds and it appears on the home screen. Copying `dist/PPSA99350/` there by hand
|
||||
works just as well.
|
||||
|
||||
For the edit-build-test loop (needs `shsrv`), `tools/relaunch.sh` stops the title, uploads,
|
||||
relaunches it and prints the log:
|
||||
|
||||
```bash
|
||||
PS5_HOST=192.168.x.x tools/relaunch.sh
|
||||
```
|
||||
|
||||
## Using it
|
||||
|
||||
1. On the PC: Steam → Settings → Remote Play → enable it.
|
||||
2. Launch **Steam Remote Play** on the console. It finds the PC by itself.
|
||||
3. First time only: Steam shows a PIN — type it on the console.
|
||||
4. It connects and streams Big Picture.
|
||||
|
||||
While streaming, **Options + touchpad click** opens the Steam menu (the PS button belongs to
|
||||
the console, so it can't be forwarded). Holding both for 1.5 s leaves the stream.
|
||||
|
||||
Press Options on the launcher for settings:
|
||||
|
||||
| Setting | Default | Meaning |
|
||||
| --- | --- | --- |
|
||||
| Resolution | 1080p | cap requested of the host |
|
||||
| Bitrate cap | AUTO | advertises 100 Mbit so Steam's own bandwidth setting is the limit |
|
||||
| Quality | Fast | host encoder preset |
|
||||
| HEVC | Off | offer HEVC instead of H.264 (unverified) |
|
||||
| Auto connect | On | reconnect to the last PC without asking |
|
||||
| Smoothing buffer | 2 frames | frames held before display to absorb network jitter, ~17 ms each. 0 = lowest latency, more stutter |
|
||||
|
||||
The console writes `/data/steamlink-ps5/log.txt` (readable over FTP) and mirrors it to the
|
||||
kernel log.
|
||||
|
||||
## Known issues and host-side gotchas
|
||||
|
||||
- **Keep the PC's display awake.** When it sleeps, Steam stops capturing and the stream goes
|
||||
black or hitches badly. This looks exactly like a client bug and isn't one.
|
||||
- **Linux/Wayland hosts need `steam -pipewire`.** Without it Steam captures a black frame.
|
||||
Start Steam with that flag (and click *Share* on the monitor picker).
|
||||
- Games see the forwarded pad as an XInput device; only Steam Input sees a DualSense. That is
|
||||
how Remote Play forwards controllers, not a limitation of this client.
|
||||
- On a weak Wi-Fi link, packet loss shows up as brief artifacts rather than freezes — the
|
||||
client deliberately keeps decoding through small losses instead of blanking.
|
||||
|
||||
## How it works
|
||||
|
||||
- **Protocol** — [ihslib](https://github.com/mariotaku/ihslib), built from a patched copy;
|
||||
`patches/ihslib/` holds the changes and `PINS.md` explains every pinned revision.
|
||||
- **Video** — VideoDec2 hardware decode → CPU NV12→RGBA (4 threads) → tiled blit to VideoOut,
|
||||
behind a small frame-pacing buffer.
|
||||
- **Audio** — Opus → `sceAudioOut`.
|
||||
- **Input** — ScePad behind an ihslib HID provider that advertises the DualSense VID/PID.
|
||||
|
||||
`docs/` covers the native tooling and PS5 platform notes.
|
||||
|
||||
## Credits
|
||||
|
||||
[ihslib](https://github.com/mariotaku/ihslib) (protocol) and its
|
||||
[`auth` branch](https://github.com/scaronni/ihslib) for current Steam pairing;
|
||||
[ProsperoLight](https://github.com/blackbearreloaded/ProsperoLight) and
|
||||
[ps5-native-app-boilerplate](https://github.com/blackbearreloaded/ps5-native-app-boilerplate)
|
||||
for the PS5 platform layer and build system; protobuf-c, Mbed-TLS, Opus and TLSF.
|
||||
|
||||
## Licence
|
||||
|
||||
GPL-3.0-or-later for this repository's own code. Protocol code comes from
|
||||
[ihslib](https://github.com/mariotaku/ihslib) (LGPL-3.0); PS5 platform code is
|
||||
adapted from [ProsperoLight](https://github.com/blackbearreloaded/ProsperoLight)
|
||||
and [ps5-native-app-boilerplate](https://github.com/blackbearreloaded/ps5-native-app-boilerplate)
|
||||
(GPL-3.0-or-later). No Sony SDK, firmware, keys or Valve code are included.
|
||||
Unofficial: not affiliated with Valve or Sony.
|
||||
GPL-3.0-or-later for this repository's own code; see [LICENSE](LICENSE) and
|
||||
[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md). No Sony SDK, firmware or keys, and no Valve
|
||||
code, are included or required. Build it for your own console.
|
||||
@@ -1,66 +0,0 @@
|
||||
From: steamlink-ps5
|
||||
Subject: TEMP diagnostics - log data channel window state and add-branch counts
|
||||
|
||||
diff -ru a/src/session/channels/ch_data.c b/src/session/channels/ch_data.c
|
||||
--- a/src/session/channels/ch_data.c 2026-10-09 17:06:31.727083938 -0700
|
||||
+++ b/src/session/channels/ch_data.c 2026-10-09 17:06:31.740381074 -0700
|
||||
@@ -132,8 +132,21 @@
|
||||
return;
|
||||
}
|
||||
IHS_SessionLog(channel->base.session, IHS_LogLevelInfo, "Data", "%s channel started", channelName);
|
||||
+ extern unsigned long g_slp_add_accept, g_slp_add_old, g_slp_add_far, g_slp_add_overflow, g_slp_add_dup;
|
||||
+ void IHS_SlpWindowDebug(const IHS_SessionPacketsWindow *, int *, int *, int *, unsigned *);
|
||||
+ uint64_t slpLastLog = 0;
|
||||
while (!channel->interrupted) {
|
||||
IHS_MutexLock(channel->windowLock);
|
||||
+ uint64_t slpNow = IHS_TimerNow();
|
||||
+ if (slpNow - slpLastLog >= 1000) {
|
||||
+ slpLastLog = slpNow;
|
||||
+ int sz, hp, tp; unsigned tid;
|
||||
+ IHS_SlpWindowDebug(channel->window, &sz, &hp, &tp, &tid);
|
||||
+ IHS_SessionLog(channel->base.session, IHS_LogLevelInfo, "WIN",
|
||||
+ "%s size=%d head=%d tail=%d tailid=%u add[ok=%lu old=%lu far=%lu ovf=%lu dup=%lu]",
|
||||
+ channelName, sz, hp, tp, tid, g_slp_add_accept, g_slp_add_old, g_slp_add_far,
|
||||
+ g_slp_add_overflow, g_slp_add_dup);
|
||||
+ }
|
||||
uint16_t discarded = IHS_SessionPacketsWindowDiscard(channel->window, DISCARD_DIFF);
|
||||
if (discarded > 0) {
|
||||
IHS_SessionLog(channel->base.session, IHS_LogLevelDebug, channelName, "Discarded %u packets", discarded);
|
||||
diff -ru a/src/session/window.c b/src/session/window.c
|
||||
--- a/src/session/window.c 2026-10-09 17:06:31.727198132 -0700
|
||||
+++ b/src/session/window.c 2026-10-09 17:06:31.740315966 -0700
|
||||
@@ -88,6 +88,15 @@
|
||||
window->tail.pos = -1;
|
||||
}
|
||||
|
||||
+unsigned long g_slp_add_accept = 0, g_slp_add_old = 0, g_slp_add_far = 0, g_slp_add_overflow = 0, g_slp_add_dup = 0;
|
||||
+
|
||||
+void IHS_SlpWindowDebug(const IHS_SessionPacketsWindow *window, int *size, int *headpos, int *tailpos, unsigned *tailid) {
|
||||
+ *size = IHS_SessionPacketsWindowSize(window);
|
||||
+ *headpos = window->head.pos;
|
||||
+ *tailpos = window->tail.pos;
|
||||
+ *tailid = window->tail.id;
|
||||
+}
|
||||
+
|
||||
bool IHS_SessionPacketsWindowAdd(IHS_SessionPacketsWindow *window, IHS_SessionPacket *packet) {
|
||||
/* Calculate distance of 2 items */
|
||||
int tailOffset = window->tail.pos < 0 ? 1 : (int16_t) (packet->header.packetId - window->tail.id);
|
||||
@@ -107,6 +116,7 @@
|
||||
* asks the host for a keyframe.
|
||||
*/
|
||||
if (tailOffset > window->capacity) {
|
||||
+ g_slp_add_far++;
|
||||
return false;
|
||||
}
|
||||
/* Large offset means overflow, abort processing and hangup. Keep one slot free: a ring with
|
||||
@@ -118,8 +128,10 @@
|
||||
IHS_SessionWindowItem *tailPkt = &window->data[writePos];
|
||||
/* Ignore if the slot is used */
|
||||
if (FrameItemIsUsed(tailPkt)) {
|
||||
+ g_slp_add_dup++;
|
||||
return true;
|
||||
}
|
||||
+ g_slp_add_accept++;
|
||||
FrameItemUsePacket(tailPkt, packet);
|
||||
|
||||
/* Only do incremental update */
|
||||
@@ -275,41 +275,6 @@ void draw_stream(ps5::demo::Canvas &canvas) noexcept
|
||||
if (slp::video::latest(frame) && frame.width > 0 && frame.height > 0)
|
||||
{
|
||||
status.have_picture = true;
|
||||
// (diag) presentation cadence: at 60 Hz vsync, how many vblanks show a NEW decoded
|
||||
// frame vs repeat the last one (a judder stall) vs skip past 2+ (arrival clumped).
|
||||
// Smooth 60 fps = ~60 new/s, 0 repeat, 0 skip. repeats+skips/s is the judder rate.
|
||||
{
|
||||
static std::uint64_t d_t0 = 0, d_last = 0, d_vbl = 0, d_new = 0, d_rep = 0, d_skip = 0, d_drop = 0;
|
||||
const std::uint64_t t = now_ms();
|
||||
if (d_t0 == 0)
|
||||
d_t0 = t;
|
||||
++d_vbl;
|
||||
if (d_last != 0)
|
||||
{
|
||||
const std::uint64_t delta = frame.serial - d_last;
|
||||
if (delta == 0)
|
||||
++d_rep;
|
||||
else
|
||||
{
|
||||
++d_new;
|
||||
if (delta >= 2)
|
||||
{
|
||||
++d_skip;
|
||||
d_drop += delta - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
d_last = frame.serial;
|
||||
if (t - d_t0 >= 1000)
|
||||
{
|
||||
slp::log("present: %llu vbl/s new=%llu repeat=%llu skip=%llu dropped=%llu",
|
||||
static_cast<unsigned long long>(d_vbl), static_cast<unsigned long long>(d_new),
|
||||
static_cast<unsigned long long>(d_rep), static_cast<unsigned long long>(d_skip),
|
||||
static_cast<unsigned long long>(d_drop));
|
||||
d_t0 = t;
|
||||
d_vbl = d_new = d_rep = d_skip = d_drop = 0;
|
||||
}
|
||||
}
|
||||
// Scale the decoded frame to fill 1920x1080, preserving aspect (letterbox).
|
||||
// The host streams at its own aspect (e.g. 1280x536), so a 1:1 copy would
|
||||
// leave most of the screen black; scaling makes any resolution fill.
|
||||
@@ -704,38 +669,6 @@ IHS_StreamVideoSubmitResult on_video_submit(IHS_Session *session, IHS_Buffer *da
|
||||
{
|
||||
status.video_frames.fetch_add(1);
|
||||
status.video_bytes.fetch_add(data->size);
|
||||
// (diag) arrival jitter: gap between consecutive decoded-frame deliveries. At 60 fps
|
||||
// the ideal is a steady ~16-17 ms. Bin per second: on-time (<=20ms), late (20-33ms, a
|
||||
// missed vblank), hitch (>33ms). A steady source shows almost all on-time; many late/
|
||||
// hitch = network arrival jitter, which with no buffer becomes visible judder.
|
||||
{
|
||||
static std::uint64_t a_t0 = 0, a_last = 0, a_on = 0, a_late = 0, a_hitch = 0, a_max = 0;
|
||||
const std::uint64_t t = now_ms();
|
||||
if (a_t0 == 0)
|
||||
a_t0 = t;
|
||||
if (a_last != 0)
|
||||
{
|
||||
const std::uint64_t gap = t - a_last;
|
||||
if (gap > a_max)
|
||||
a_max = gap;
|
||||
if (gap <= 20)
|
||||
++a_on;
|
||||
else if (gap <= 33)
|
||||
++a_late;
|
||||
else
|
||||
++a_hitch;
|
||||
}
|
||||
a_last = t;
|
||||
if (t - a_t0 >= 1000)
|
||||
{
|
||||
slp::log("arrival: on_time=%llu late=%llu hitch=%llu maxgap=%llums",
|
||||
static_cast<unsigned long long>(a_on), static_cast<unsigned long long>(a_late),
|
||||
static_cast<unsigned long long>(a_hitch), static_cast<unsigned long long>(a_max));
|
||||
a_t0 = t;
|
||||
a_on = a_late = a_hitch = 0;
|
||||
a_max = 0;
|
||||
}
|
||||
}
|
||||
const bool keyframe = (flags & IHS_StreamVideoFrameKeyFrame) != 0;
|
||||
const std::uint16_t frame_id = IHS_SessionLastSubmittedVideoFrameId(session);
|
||||
const std::uint64_t started = now_ms();
|
||||
@@ -750,27 +683,6 @@ IHS_StreamVideoSubmitResult on_video_submit(IHS_Session *session, IHS_Buffer *da
|
||||
g_submit_finished_ms = now_ms();
|
||||
if (took > 40)
|
||||
g_submit_slow.fetch_add(1);
|
||||
// (diag) decode+convert cost per frame: at 120 fps the whole budget is 8.3 ms, so the
|
||||
// old ">40 ms = slow" counter says nothing about 120 Hz headroom. Report avg/max/s.
|
||||
{
|
||||
static std::uint64_t c_t0 = 0, c_sum = 0, c_max = 0, c_n = 0, c_over8 = 0;
|
||||
if (c_t0 == 0)
|
||||
c_t0 = started;
|
||||
c_sum += took;
|
||||
++c_n;
|
||||
if (took > c_max)
|
||||
c_max = took;
|
||||
if (took > 8)
|
||||
++c_over8;
|
||||
if (started - c_t0 >= 1000)
|
||||
{
|
||||
slp::log("decode: avg=%.1fms max=%llums over8ms=%llu/%llu", c_n ? static_cast<double>(c_sum) / c_n : 0.0,
|
||||
static_cast<unsigned long long>(c_max), static_cast<unsigned long long>(c_over8),
|
||||
static_cast<unsigned long long>(c_n));
|
||||
c_t0 = started;
|
||||
c_sum = c_max = c_n = c_over8 = 0;
|
||||
}
|
||||
}
|
||||
if (ok)
|
||||
return IHS_StreamVideoSubmitOK;
|
||||
// A failed decode means we wait for a keyframe. Reporting it makes ihslib reset
|
||||
|
||||
Reference in new issue
Block a user