Files
MiladRTX--Glint/docs/API.md
T
MiladandClaude Opus 5.5 21d3ddbd70 Glint 1.0
Watch, hear and record a jailbroken PS5 on a Windows PC with low latency.

- app/: Windows app (C++17, Vulkan + Dear ImGui, static FFmpeg and curl)
- payload/: PS5 payload driving the console's H.264 encoder, HTTP API on
  port 7231 and a browser dashboard
- docs/, tests/, tools/: build and API docs, protocol smoke test, asset
  generators

Licensed under GPL-3.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 05:10:47 +01:00

6.6 KiB
Raw Blame History

Glint payload HTTP API (port 7231)

The payload on the PS5 serves the console's H.264 encoder output, its audio and a set of controls over HTTP. The desktop app and the browser dashboard are both clients of this API.

Transport: plain HTTP/1.1 over TCP. Every response sets Connection: close and Cache-Control: no-store (where applicable). The request line is parsed as %11s %255s; only the method and path are used. A request must be complete within 5 seconds (server-side receive timeout 1 s, header deadline 5 s, 4095-byte limit).

Routes

Method Path Response
GET / text/html; charset=utf-8, the dashboard page
GET /banner.png image/png, the banner image
GET /config.json stream configuration
GET /status.json live statistics
GET /frames framed H.264 stream (endless)
GET /audio framed PCM stream (endless)
GET /rtp/video?port=N RTP/AVC stream to the caller's IP on UDP port N
GET /recordings JSON array of recordings
GET /recordings/<name> recording download
POST /record/start arm recording on the next IDR
POST /record/stop close and finalize the recording
POST /video/keyframe request an IDR from the encoder
POST /capture/profile?height=H change capture height
POST /capture/restart restart the capture session
POST /stop stop streaming and release capture

Unknown routes return HTTP 200 with the body Unavailable route (text/plain).

Video framing (/frames)

Each frame is:

offset  size  field
0       4     payload length, big-endian (max 2,097,152)
4       4     presentation timestamp, big-endian, 90 kHz units
8       N     Annex-B access unit (00 00 00 01 / 00 00 01 start codes)

The first frame after a connection is a keyframe (the server requests one on connect). After that keyframes come from the encoder's own schedule, after a skip (below), or on POST /video/keyframe; the server does not force periodic ones, because each large IDR is a latency spike on a slow link (Wi-Fi). The PC recorder requests one every 2 s while it records. Timestamps are 32-bit and wrap; the dashboard unwraps the delta.

Ring: 64 slots. When a viewer falls more than 6 frames behind the server drops to the newest available IDR and requests another keyframe. Streaming clients are capped at 8; the 9th gets HTTP/1.1 503 Busy with an empty body.

Audio framing (/audio)

offset  size  field
0       4     payload length, big-endian (max 65,536)
4       4     timestamp, big-endian (console clock)
8       N     PCM samples

Codecs advertised in config.json:

  • pcm_f32le — 32-bit float, interleaved
  • pcm_s16le — signed 16-bit
  • pcm_s32le — signed 32-bit

Default console stream: pcm_f32le, 2 channels, 48000 Hz. The capture layer emits one packet per audio frame batch, typically 8192 bytes (1024 stereo float samples).

config.json

{
  "platform": "PS5",
  "width": 1920,
  "height": 1080,
  "fps": 60,
  "codec": "avc1.4d4032",
  "sps": "674d403291a01e0089f97016a020202800001f48000ea6044d0f1f50",
  "pps": "68ee3c80",
  "audio": {
    "codec": "pcm_f32le",
    "channels": 2,
    "rate": 48000,
    "bits": 32
  },
  "resolutions": [1080],
  "requested_height": 1080,
  "profile_changing": false,
  "profile_error": false
}
  • codec is avc1.%02x%02x%02x from sps[1..3] (profile, constraints, level).
  • sps/pps are lowercase hex without start codes.
  • audio is null when no audio channel is open.
  • Glint captures 1080p60 only (capture feature 12). On current firmware 1440p OpenVideo returns 0x81950008 (profile unsupported), and a failed 1440p attempt can leave the encoder in a state where Start returns 0x80c78000, so the payload opens 1080p directly without probing. See docs/CAPTURE-INTERNALS.md for the full error-code table.

status.json

{
  "version": "1.0",
  "backend": "h264",
  "width": 1920,
  "height": 1080,
  "frames": 648,
  "bytes": 1354059,
  "fps": 60.3,
  "kbps": 1008.8,
  "clients": 1,
  "recording": false,
  "recording_name": "",
  "recording_frames": 0,
  "recording_bytes": 0,
  "recording_seconds": 0.0,
  "recording_error": false,
  "audio_chunks": 508,
  "audio_bytes": 4161536,
  "audio_kbps": 3108.5,
  "audio_codec": "pcm_f32le"
}

fps and kbps are frames and bytes since the payload started divided by the time since the server last (re)started, so they overcount after an encoder reopen. The app shows its own rate over the last 2 s instead. clients counts every open request socket (including status polling).

Recording

  • Directory: /data/glint/recordings (created on demand).
  • Trigger: the first IDR after POST /record/start.
  • File name: glint-<unix-time>-<frame-count>.h264.
  • Contents: [BE u32 len][SPS] [BE u32 len][PPS] followed by Annex-B access units. A new SPS/PPS pair is written after a discontinuity.
  • POST /record/stop finalizes the file and fills recording_name, recording_frames, recording_bytes, recording_seconds.
  • A recording error sets recording_error and closes the file.
  • Listing entries: {"name":"...","bytes":123}.
  • Download names are restricted to [A-Za-z0-9.-], must end in .h264, must not start with ., must not contain .., and are at most 96 chars.

Profile changes

POST /capture/profile?height=H accepts only 1080 (anything else is 400).

  • 400 Unsupported resolution — bad height or trailing characters.
  • 409 Stop recording before changing resolution.
  • 409 A resolution change is already in progress.
  • 200 Resolution unchanged — same as current.
  • 200 Resolution change requested — capture loop reopens avcap2 and bumps the stream generation, which disconnects current viewers.

Response body shape: {"message":"..."} over HTTP/1.1 <code> Profile.

RTP

GET /rtp/video?port=N (accepted range 1024–65535):

  1. Sends HTTP/1.1 200 OK ... RTP\n.
  2. Opens a UDP socket to the caller's address with the requested port.
  3. Packetizes Annex-B NAL units as RTP/AVC (payload type 96, 90 kHz clock).
  4. SPS and PPS are prepended to every keyframe, each with a 4-byte start code.
  5. NALs larger than 1400 bytes use FU-A fragmentation; the marker bit is set on the last packet of each access unit.

Limits and concurrency

Resource Value
Client slots 16
Streaming slots (/frames, /audio, /rtp) 8
Video ring 64 entries
Audio ring 256 entries
Request header 4095 bytes
Header deadline 5 s
Send timeout 5 s
Receive timeout 1 s
Max video frame 2 MiB
Max audio packet 64 KiB