docs/benchmarking.md.
Both lines climb because audio is a full mesh. The listener line climbs too: usedtx=0 keeps packets flowing while muted, so everyone in the room pays for the mesh, not only whoever is talking.
| Peers | Speakers | Speaker up | Listener up | Listener down | RTT | Loss | Room CPU | Room RSS |
|---|---|---|---|---|---|---|---|---|
| 2 | 1 | 48 kb/s | 35 kb/s | 48 kb/s | 1 ms | 0.0% | 30% | 1085 MiB |
| 3 | 1 | 97 kb/s | 86 kb/s | 100 kb/s | 1 ms | 0.0% | 60% | 1314 MiB |
| 4 | 1 | 145 kb/s | 137 kb/s | 152 kb/s | 1 ms | 0.0% | 107% | 1553 MiB |
| 6 | 1 | 220 kb/s | 239 kb/s | 254 kb/s | 3 ms | 0.0% | 217% | 2078 MiB |
| 6 | 6 | 320 kb/s | — | — | 2 ms | 0.0% | 173% | 2135 MiB |
Every peer runs in one browser here, so this is the whole room on one machine — never divide it by the peer count. CPU also carries real run-to-run variance; compare it only across repeats, and only between configurations measured back to back.
| Peers | Room CPU | Room RSS |
|---|---|---|
| 2 | 30% | 1085 MiB |
| 3 | 60% | 1314 MiB |
| 4 | 107% | 1553 MiB |
| 6 | 217% | 2078 MiB |
rnnoise is the desktop default, so this is a cost the product pays unless someone changes it. Same room size and the same speakers in every row — only the mode changes.
| Mode | Peers | Speaker up | Room CPU | Room RSS | JS heap |
|---|---|---|---|---|---|
| off | 3 | 102 kb/s | 53% | 1375 MiB | 4 MiB |
| browser | 3 | 102 kb/s | 60% | 1372 MiB | 4 MiB |
| rnnoise (default) | 3 | 102 kb/s | 73% | 1397 MiB | 4 MiB |
Under the p2p-only preference this harness pins, camera is a full mesh too — so a sharer encodes and uploads one stream per other peer, at roughly twenty times an Opus stream. The shipped default may route this through an SFU above two participants instead, and that topology is not measured here. This machine ran out of room. One browser was encoding every peer's camera at once, and when that happens the frame rate falls first — while the encoder still blames its own bitrate cap. Read the rows below as a ceiling of the harness, not of the app.
| Peers | Cameras | Sharer up | Receiver down | Picture sent | Room CPU | Room RSS |
|---|---|---|---|---|---|---|
| 2 | all (2) | 632 kb/s | 625 kb/s | 960×540 @ 30 fps (bandwidth) | 93% | 1399 MiB |
| 3 | all (3) | 1.01 Mb/s | 1.01 Mb/s | 853×480 @ 16 fps | 223% | 1799 MiB |
| 4 | 1 | 1.90 Mb/s | 631 kb/s | 853×480 @ 30 fps | 200% | 1857 MiB |
| 4 | all (4) | 1.75 Mb/s | 1.59 Mb/s | 853×480 @ 4 fps (bandwidth) | 337% | 2270 MiB |
Segmentation runs locally — MediaPipe in WebAssembly plus a WebGL composite — so it is paid in CPU and frame rate, never on the wire. Still running counts the sharing peers that still had the effect on at the end: the app turns a background off in silence both when the runtime cannot start and when the device cannot sustain it, so a 0 there means the row is not the cost of the effect.
| Background | Peers | Cameras | Still running | Sharer up | Picture sent | Room CPU |
|---|---|---|---|---|---|---|
| off (default) | 2 | 1 | 0/1 | 611 kb/s | 960×540 @ 30 fps | 80% |
| blur | 2 | 1 | 0/1 | 627 kb/s | 1280×720 @ 30 fps | 160% |
A screen is not a camera: its ceiling is 1.5 Mb/s rather than 600 kb/s, it is pinned to maintain-resolution so it drops frames rather than letters, and the peer-count downscale never touches it.
| Peers | Sharer up | Watcher down | Picture sent | Room CPU | Room RSS |
|---|---|---|---|---|---|
| 3 | 920 kb/s | 460 kb/s | 1280×720 @ 20 fps | 130% | 1558 MiB |