Step 1 of nescore's 2026-10-16 media timing spec. Media carried no
send time and audio no time at all, so every layer had to infer lateness
from gaps between arrivals.
- clock: one sender clock, CLOCK_MONOTONIC in u64 microseconds, shared
by every process that stamps media. Off unix the client uses its own
process clock, which is all a receiver needs.
- Datagram headers (and PyroWave's) carry sent_us, stamped by neshub at
hand-off to QUIC. Keyframe streams open with it too.
- Video carries captured_us (from nescapture's present time) in place of
milliseconds since the encoder started. Audio carries its first
sample's position on the PipeWire graph clock, which neswire already
read to fill missed cycles. The IPC stamp is a u64 carrying either.
- delay: microseconds. The baseline no longer expires on a 30 s timer:
clock skew is measured as the least-squares slope of each second's
minimum one-way delay, and only a path change resets the baseline.
observe() returns each unit's queueing delay.
- ReceiverReport carries video and audio queueing delay summaries for
the rate control that will consume them.
- Shared encode/decode helpers for video and audio payloads, so the hub
and receivers stop hand-building byte offsets.
No compatibility kept. STREAM_VERSION is 4. nescore needs a pin and the
client side (step 2) before it can talk to this.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The guest's audio stream ran about 3.5% short of realtime: 193 packets a
second where 200 were due, with no network loss at all. The client plays
at its own 48 kHz, so its buffer drained and underran every few seconds,
idle or loaded.
The graph was running 128-frame (2.67 ms) cycles. The 256 floor for VMs
matched on `cpu.vm.name`, which PipeWire reads from DMI and nesbox does
not provide, so it never applied, and neswire's 240-frame request was
rounded down to a power of two. A busy guest missed ~20 of those cycles a
second (pw-top ERR on neswire), and each missed cycle's audio never
reached neswire while the graph clock moved on.
- PipeWire is fixed at 240 frames, power-of-two rounding off: one cycle,
one 5 ms Opus packet, twice the headroom of before.
- neswire reads the graph position each cycle and fills exactly the
frames a missed cycle skipped, as silence, so the stream stays at
48 kHz whatever the scheduler does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
All three came across with their standalone repos and none had been
touched since the transport changed.
nescapture claimed to packetize with Reed-Solomon FEC and stream RTP/UDP
to a Moonlight client. It sends encoded frames to neshub over a Unix
socket. packetizer.rs, control.rs and shard_batch.rs do not exist, and
neither does ARCHITECTURE.md. Nine documented environment variables are
not read by anything -- NESCAPTURE_RTP_HOST was listed as *required* --
and five that are read were undocumented, including the one that says
where frames go. Someone following that quick start would have set a
required variable that does nothing and got no output.
Documented the three sockets, since nescapture binds one and connects to
two and that was written down nowhere.
neswire documented --rtp-addr and --channels against a gstreamer pipeline.
It has --ipc-path, --channels, --packet-duration-ms and
--bitrate-per-channel, and hub-stub exists precisely so it can be tested
without a hub. Kept the reason hub-stub decodes rather than counts bytes:
Opus codes silence at ~3 kbps, so a dead sink looks alive on a meter.
nescope was mostly right. It called the capture layer "vkcapture", listed
seven of fifteen modules and five of nine flags, and its TODO list was
three items that neshub and nescapture now do. Added compositor mode,
which is the shape a real session uses and was undocumented.
All three licence lines were "TBD" or "See project repository".
Six comments across nescope, neswire and nescapture still named
nestri-guest-hub, plus one still naming nestri-protocol. Deferred from the
import commits so the rename would read as one change rather than six
unexplained edits inside otherwise-verbatim trees.
Comments only. Realigned an ASCII box in encode.rs that the shorter name
knocked crooked.
Captures a session's audio and hands it to the transport over a local socket.
Third component in, imported as a tree from `nestrilabs/neswire` on the same
terms as the previous two.
Wired to the workspace, `nesprotocol` by path. 4 tests pass.
`bin/hub-stub.rs` is a stand-in for the transport's listener, which is what lets
this be developed and tested without the rest of a box existing. It names the
transport by its old name, and is left for the rename commit along with the two
in the compositor.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>