Skip to content

Transport Tiers

Score2 edited this page Aug 10, 2026 · 2 revisions

Transport Tiers

AudioHub classifies each peer's connection into one of three tiers. A tier describes how the bytes are actually travelling, and it exists for one reason:

When you read a latency number that has gone up, the cause should be sitting next to it.

The same "210 ms" means two different things. With nothing beside it, the only available conclusion is this software is bad. With "relayed over TCP" beside it, the conclusion becomes this network can only do this — and that one is true.


The three tiers

Tier 0 — direct (UDP)

TCP for control, UDP for media, both on port 47810. This is the intended path and covers everything reachable at layer 4 or below: same subnet, routed across subnets, or through an L3 VPN that presents a virtual NIC (WireGuard, Tailscale, and similar).

Media is sent outward from the initiator's socket and the peer replies to the same 5-tuple, so only one side's port needs to be reachable — the initiator may sit behind NAT or a strict outbound-only firewall.

Full experience, best latency and jitter. Tier 0 carries no badge in the UI. Marking every healthy peer "all fine" only trains you to ignore the place where we need a degradation to be noticed.

Tier 1 — media over TCP

Used when layer 4 is reachable but UDP is blocked. Control and media both run over TCP, on a second connection to the same address and port. The media datagrams are byte-for-byte identical to the UDP ones.

Nothing is removed: sharing and consuming, volume sync, per-peer and per-direction settings all keep identical semantics. What changes is timing.

Tier 2 — single-connection multiplex

The floor. Used when only an application-layer path exists — some VPNs and HTTP tunnels forward at layer 7, which destroys the source-address property and allows connections to be initiated in one direction only.

Whichever side can initiate opens one TCP or WebSocket connection, and control plus both media directions are multiplexed onto it. Highest latency, and the two directions interfere with each other. That interference is inherent to the shape, not a defect to be tuned away.


When AudioHub changes tier

Tier 0 → Tier 1 is automatic. The criterion is deliberately blunt: after a stream is accepted, if no media packet has arrived within 600 ms while the control channel is healthy, UDP is declared dead and the peer is told to switch.

Anything → Tier 2 is manual, and the address says so. You reach Tier 2 by entering a ws:// tunnel address for that peer instead of an IP[:port]. This is not laziness: Tier 2's precondition is a property of your tunnel, and from where AudioHub sits, "a peer we cannot dial" and "a peer that is switched off" are indistinguishable. An address that begins ws:// explains itself; a silent peer does not.

Any peer can be pinned to a specific tier by hand, including pinned back to Tier 0. Pinning Tier 1 while the peer pins Tier 0 simply means the relay never forms and audio keeps using the direct path.

Once degraded, the tier is remembered and the next connection starts there. Upgrading back to Tier 0 happens at the next stream open, never mid-stream.


The tunnel address

Each peer's detail page has a tunnel address field. It is the one place a ws:// address is stored; typing one into "add a peer" works for that connection and is forgotten on reconnect, because the service deliberately does not persist an address it was handed in passing.

Field contents What happens
Empty direct connection to the IP:port recorded at pairing time
ws://host[:port][/path] the connection is wrapped in a WebSocket, for tunnels that forward HTTP only

Three consequences worth having in front of you before you fill it in:

It overrides the tier picker for outbound connections. With an address set, anything this machine dials uses the single-connection multiplex, whatever the four buttons above say. The UI states this rather than leaving the picker looking effective; to get the picker back, clear the field.

Tier 2 does not require it. The multiplex is selectable on its own — without an address it multiplexes over plain TCP, with one it multiplexes inside a WebSocket. Both are Tier 2. The address is about what the tunnel will carry, not about unlocking the tier.

wss:// is not supported in this build. There is no TLS client compiled in. Point the field at the tunnel's cleartext entrance, or terminate TLS locally in front of it. The field says so rather than reporting the address as unparseable — "unsupported" and "malformed" send you looking in different places.

Pairing cannot be performed over a tunnel. Pair over IP:port first, then set the tunnel address; see Discovery and Pairing.


What degradation costs

The measured cost: ~0.6 s of silence

On a link where UDP is blocked, the automatic downgrade means roughly 0.6 seconds of silence before the first sound, because that is how long AudioHub waits before concluding UDP is dead. On a working link the cost is zero. This is also why the tier is persisted: without that, every connection would replay the same 0.6 s.

The structural cost: TCP retransmission versus the jitter buffer

This is the one that makes Tier 1 sound worse, and it is arithmetic rather than opinion.

A single TCP retransmission timeout is typically 200 ms or more — Linux's minimum RTO is 200 ms, Windows' is 300 ms. The default jitter buffer ceiling is 12 frames = 120 ms.

Any single retransmission is therefore longer than the entire buffer. It punches straight through and causes an underrun. No amount of tuning fixes this at a 120 ms ceiling.

AudioHub's response is a separate buffer profile for degraded links with the ceiling raised to 400 ms. Stated plainly: that figure is derived from the 200–300 ms RTO range as an order of magnitude, it is not a measured optimum.

Why we publish no latency target for Tier 1 or Tier 2

Two promises are made about degraded links — label them honestly and make them measurable — and deliberately no third one.

Latency on a degraded link is set by TCP retransmission, head-of-line blocking, and whatever buffering your tunnel does. None of that is under this project's control. A millisecond figure here would be a number we could not keep.

Knock-on effects worth knowing

  • The quality ladder's AUTO ceiling drops. On Tier 0, AUTO tops out at rung 2 (48 kHz · 16-bit). On Tier 1 and Tier 2 it tops out at rung 3 (32 kHz · 16-bit), because deep rungs suffer disproportionately on a degraded link and having sound at all comes first. The same AUTO setting therefore lands on different rungs on different tiers, by design.
  • The signals AUTO watches change. Under TCP, packet loss is reported as identically zero — TCP hides it. So on degraded links AUTO watches send-queue backlog instead of loss, and one-way delay spread instead of frame-to-frame jitter. TCP's failure mode is "pause, then deliver in a burst", and within a burst the frame-to-frame differences are near zero, so a conventional jitter estimate systematically understates the problem.
  • Frames that miss their budget are dropped on purpose. On a degraded link, a frame still queued past its 200 ms budget is discarded rather than sent. This is not a new source of loss: TCP erases the loss signal, and the receiver's concealment only works correctly if the gap is actually left there.

How degradation is presented

  • The tier is one state per peer, not per direction.
  • A degraded tier is top-level information, shown next to the latency number — not a badge in a card corner far from the number it explains.
  • The UI writes plain language: "relayed over TCP", "multiplexed over a tunnel". Internal identifiers like tier1 appear only in logs and the IPC layer.
  • "Not yet determined" and "determined to be direct" never render the same way. An undetermined connection shows a greyed dash. Tier 0 does not impersonate unknown, and unknown does not impersonate Tier 0.
  • Second-level pages keep the full detail: current tier, link address, send-queue backlog, frames dropped past budget. Promoting information to the top level does not mean moving it there.

Implementation status

Being specific, because "supported" and "shipped" are different words:

Capability Status
Tier 0 (UDP media) Working, the normal path
Tier 1 transport Implemented and tested by pinning the tier manually
Tier 0 → Tier 1 automatic downgrade Not yet implemented. The design is settled and the manual path exists; the automatic trigger does not
Remembering an automatic downgrade Follows from the above — there is nothing to remember yet
Tier 2 Implemented; never yet exercised between two machines

The 600 ms criterion described above is the specified behaviour, not a description of code you can run today.

Clone this wiki locally