Capacity: players per server
How many players one bitwerk server can feed, by where the music is stored and how the server is connected. The short answer is the table below; the rest of the page shows where its numbers come from, which of them were measured and which are estimates.
bitwerk sets no limit of its own. Plan with at most 100 players per server and at most 100 rooms per group: more has not been tested on real hardware (the lab has two players), even where the network and the disk would carry more. The control traffic of 500 simulated players was measured (see Measurements).
Players per server
Section titled “Players per server”How many rooms can play at the same time, each a different track, before the storage or the network runs out. Rooms in one group share one read from the disk (see Groups), so for groups only the network rows count. All numbers are estimates with a safety margin (about half of what the hardware should manage), wired Ethernet for server and players, the server on a 1 Gbit/s port unless the row says otherwise.
| Music on … | CD quality FLAC 16/44.1 | Hi-res FLAC 24/96 | Hi-res FLAC 24/192 | Worst case: DSD256 or WAV 32/384 |
|---|---|---|---|---|
| USB 2.0 stick | 60 | 15 | 8 | 2 |
| USB 3 stick | > 100 | 55 | 25 | 7 |
| External 2.5″ USB hard disk | 30 | 8 | 4 | 1 |
| Internal 3.5″ hard disk (NAS) | 40 | 10 | 5 | 1 |
| Hard disk with read-ahead raised to 4 MiB (tip) | > 100 | 70 | 35 | 9 |
| SATA SSD | > 100 | > 100 | 70 | 15 |
| NVMe SSD | > 100 | > 100 | 70 | 15 |
| NVMe SSD, server on 2.5 Gbit/s | > 100 | > 100 | > 100 | 45 |
| Any storage, server on 100 Mbit/s | 50 | 10 | 7 | 1 |
”> 100” means the hardware would carry more, but 100 players per server is the tested recommendation. From the SATA SSD on, the 1 Gbit/s network is the limit, not the disk: an NVMe SSD adds nothing over a SATA SSD until the server gets a faster port.
A fast NVMe server with “cached” songs: bitwerk keeps no separate cache; every player streams the file from the library. Put the library on the NVMe SSD itself. Linux keeps recently read files in RAM (page cache) on its own, so a group, a track played again or the next track of a popular album often comes from RAM. With the library on SSD or NVMe the storage no longer limits anything; the server’s network port does (rows 6 to 8).
How bitwerk streams
Section titled “How bitwerk streams”Taken from the code, because it decides what the server has to do per player:
- No transcoding on the server.
GET /api/tracks/{id}/streamof the media-service serves the original file bytes, bit-perfect, with HTTPRange(server/media-service/src/http.rs,tower-httpServeFile). Decoding, resampling, DSD → PCM, ReplayGain and PEQ all run on the player (client/src/engine/). The server’s work per player is reading the file and sending it. - One stream per player, paced by its DAC. The player reads the stream into a decoder and a jitter buffer of
2 s (
HIFI_BUFFER_MS, playback starts at 500 ms) and stops reading while the buffer is full (client/src/audio/buffer.rs). So a stream runs at the file’s own bitrate on average, with a short burst at each track start or seek while the buffer fills (about 2 s of audio, plus what the TCP buffers hold). - Gapless and crossfade: the next track is opened 2 s (one buffer) before the current one ends, for a crossfade the crossfade length before. For that moment one player has two streams open.
- Groups: every member gets its own
Playand streams the file itself (docs/architecture.md, room groups). The control-server only schedules the start 1 s ahead (GROUP_START_LEAD) and relays the leader’s position once a second (SyncReport) to each follower; no audio passes through it. - Radio and Spotify do not touch the server at all: each player fetches the radio stream from the internet
itself (
client/src/radio/), Spotify Connect runs on the player (librespot). They load the internet line (about 0.1 to 0.35 Mbit/s per player, lossless radio about 1 Mbit/s), not the server. - Per track start the player asks the media-service for
playback-info(one database lookup) and the control-server sends a few small WebSocket messages; idle players ping every 2 s. This is noise next to the audio.
Bitrate per player
Section titled “Bitrate per player”The bitrate of the file is the bitrate on the network. Stereo throughout; plus about 5 % for TCP/IP and HTTP.
| Format | Uncompressed (WAV, AIFF, DSF) | Typical FLAC | Planned per player |
|---|---|---|---|
| MP3 320, AAC 256 | – | – | 0.4 Mbit/s |
| 16 bit / 44.1 kHz (CD) | 1.41 Mbit/s | 0.6 – 1.0 Mbit/s | 1.0 (FLAC), 1.5 (WAV) |
| 24 bit / 96 kHz | 4.61 Mbit/s | 2.5 – 3.5 Mbit/s | 3.5 (FLAC), 4.9 (WAV) |
| 24 bit / 192 kHz | 9.22 Mbit/s | 4.5 – 6.5 Mbit/s | 7 (FLAC), 9.7 (WAV) |
| 32 bit / 384 kHz | 24.58 Mbit/s | – | 26 |
| DSD64 (2.8 MHz) | 5.64 Mbit/s | – | 6 |
| DSD128 (5.6 MHz) | 11.29 Mbit/s | – | 12 |
| DSD256 (11.3 MHz) | 22.58 Mbit/s | – | 24 |
| DSD512 (22.6 MHz) | 45.16 Mbit/s | – | 48 |
Uncompressed rates are exact (bits × rate × 2 channels; DSD: 1 bit × rate × 2). FLAC rates are typical values for music; quiet acoustic recordings compress better, loud masters and noise worse. The table above plans FLAC with the upper end of the typical range and the worst case with 26 Mbit/s (WAV 32/384; DSD256 is a little less). DSD512 is half the players of the worst-case column.
Network
Section titled “Network”| Server port | Usable for audio (planned) | Why |
|---|---|---|
| 100 Mbit/s | 50 Mbit/s | half the link |
| 1 Gbit/s | 500 Mbit/s | half the link |
| 2.5 Gbit/s | 1,250 Mbit/s | half the link |
TCP carries about 94 % of a wired link. The plan uses half, because the same port also carries the bursts at track starts and seeks, the web UI and covers, uploads, the Samba share, backups and whatever else the house sends, and because a switch uplink between server and players may be shared. Every player’s own port is never the limit: even DSD512 needs 48 Mbit/s, which a Pi 3B’s 100 Mbit/s port carries.
Wi-Fi is only a caveat here, since bitwerk’s players are meant to be wired: all players on one access point share its airtime, real throughput is a fraction of the advertised rate (on 2.4 GHz often 20 to 50 Mbit/s for everyone together), and retransmissions come in bursts. The 2 s buffer absorbs short dropouts. As a rough plan, a few CD-quality players per 2.4 GHz access point, hi-res and DSD only wired or on a strong 5 GHz link.
Storage
Section titled “Storage”When many rooms play different tracks, the disk serves many files at once: each stream reads its file in order,
but the disk sees the streams interleaved, so it jumps between files. Linux reads ahead 128 KiB per jump by
default (read_ahead_kb); the media-service reads in 64 KiB chunks. What counts is how many such reads per
second the medium manages, not its sequential speed from the box.
| Medium | Estimated for many interleaved streams | Planned (≈ half) |
|---|---|---|
| USB 2.0 stick | 15 – 30 MB/s (USB 2.0 tops out near 35 MB/s; no seek time) | 60 Mbit/s |
| USB 3 stick | 50 – 150 MB/s (cheap sticks at the lower end) | 200 Mbit/s |
| microSD card (A1/A2, a Pi as server) | 20 – 40 MB/s | 80 Mbit/s |
| 2.5″ USB hard disk, 5400 rpm | about 55 seeks/s × 128 KiB ≈ 7 MB/s | 30 Mbit/s |
| 3.5″ hard disk, 7200 rpm | about 80 seeks/s × 128 KiB ≈ 10 MB/s | 40 Mbit/s |
| Hard disk, read-ahead 4 MiB | about 15 – 25 reads/s × 4 MiB ≈ 60 – 110 MB/s | 250 Mbit/s |
| SATA SSD | 300 – 500 MB/s | more than the 1 Gbit/s network |
| NVMe SSD | 1,000 MB/s and more | more than a 2.5 Gbit/s network |
These are estimates from typical device figures, not measurements on bitwerk’s lab hardware; the planned value is about half of the low end. Flash media have no seek time, so with default settings a good USB stick serves more parallel streams than a hard disk, although the disk is faster for one long copy. The disk’s own cache and command queueing help, so real hard disks should do somewhat better than the seek arithmetic.
Hard disks: raise the read-ahead
Section titled “Hard disks: raise the read-ahead”With 4 MiB read ahead per jump the hard disk spends most of its time reading instead of seeking, which gives it
about six times the parallel streams. It costs RAM only while streams run (4 MiB per stream). On the host, for a
disk sdb:
echo 4096 | sudo tee /sys/block/sdb/queue/read_ahead_kb# permanently, e.g. /etc/udev/rules.d/60-readahead.rules:# ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", ATTR{queue/read_ahead_kb}="4096"Groups and repeated tracks
Section titled “Groups and repeated tracks”A group of ten rooms streams ten copies over the network, but the disk reads the file once: the other nine come from the page cache. The storage rows count different tracks playing at the same time; with every room in one group, only the network rows matter. The same holds for a track several rooms play within minutes, as long as the server has RAM to spare (a 24/192 album is about 1 to 2 GB).
While the library is scanned
Section titled “While the library is scanned”The first import of a large library reads every file and measures its loudness (Benchmarks:
about 3.3 h for 100,000 tracks on 16 cores). On a hard disk or USB stick that competes with playback: plan with
half the players of the table while it runs, or lower SCAN_CONCURRENCY. Later scans only compare size and
modification time and cost nothing. There is no limit on the number of indexed files. Import and indexing
times by library size and drive: Performance.
CPU and the database
Section titled “CPU and the database”The server never decodes or transcodes, so its CPU is not the limit for playback. Measured (see below): the media-service needs about 0.2 to 0.5 of one CPU core per Gbit/s it streams, 300 CD-quality streams took 16 % of one core. A Raspberry Pi 4 or a small NAS CPU is several times slower per core than the machine measured; as an estimate, 500 Mbit/s of streams take one to two of a Pi 4’s four cores there, which still leaves room, but not much for a library scan at the same time.
The database is touched once per track start (playback-info) and by the web UI. Search and library views stay in
the tens of milliseconds at 100,000 tracks (Benchmarks), independent of the number of
players.
Bottlenecks in order
Section titled “Bottlenecks in order”- Storage, when many rooms play different tracks from a hard disk or a USB stick: the disk runs out of seeks long before the network is full.
- The server’s network port, with SSD or NVMe storage: 1 Gbit/s carries about 70 rooms of 24/192 FLAC.
- Wi-Fi, wherever a player is not wired.
- The internet line for radio and Spotify, because every player fetches its own stream (the server does not relay it).
- Server CPU only on small boards during a library scan; playback itself needs little.
- Sync groups add only control messages; the group’s start is scheduled 1 s ahead, and even 500 simulated members received it within 130 ms.
The players have limits of their own (DSD → PCM or resampling on a Pi 3, the DAC), but they do not depend on the number of players.
Measurements
Section titled “Measurements”Measured on 2026-10-20 in a cloud container: 4 vCPU (Intel Xeon, 2.8 GHz), 15 GB RAM, Postgres 16, release builds, everything over loopback.
Media-service streaming (release build of media-service, authentication off, 60 FLAC files of 16/44.1 and
24/192 in the page cache): N parallel HTTP clients (curl --limit-rate) each stream random tracks at a fixed
rate, like paced players; the media-service’s own CPU time from /proc/<pid>/stat.
| Clients × rate | Served | media-service CPU | Per Gbit/s |
|---|---|---|---|
| 300 × 1 Mbit/s (CD FLAC) | 306 Mbit/s | 16 % of one core | 0.52 cores |
| 100 × 6.2 Mbit/s (24/192 FLAC) | 618 Mbit/s | 19 % | 0.30 cores |
| 200 × 6.2 Mbit/s | 1,201 Mbit/s | 39 % | 0.32 cores |
| 40 × 26 Mbit/s (DSD256, 32/384) | 1,051 Mbit/s | 25 % | 0.24 cores |
| 8 unthrottled | 12,710 Mbit/s | 218 % | 0.17 cores |
Every client got its full rate. Not covered: a real network card (interrupts, offloads), the disk (the files were in RAM) and slower CPUs.
Control-server with many players (server/control-server/tests/scale_bench.rs, real WebSockets, in-memory
store; the time until the last simulated player has the message):
| Players | All rooms Play at once | Group start (scheduled Play at every member) | SyncReport to every follower |
|---|---|---|---|
| 50 | 5 ms | 3 ms | 2 ms |
| 200 | 42 ms | 33 ms | 5 ms |
| 500 | 151 ms | 128 ms | 10 ms |
The group start has 1,000 ms of lead, so a group of 500 still starts together on this machine; on a CPU four times slower it would be about half the lead, which is why groups are recommended up to 100 rooms. Joining 499 rooms one after the other took 1.3 s in total.
To repeat them:
cargo test --release -p control-server --test it scale_bench -- --ignored --nocapture # HIFI_BENCH_PLAYERS=500The streaming measurement ran a release media-service with AUTH_DISABLED=true and a throwaway database, then
N loops of curl -s -o /dev/null --limit-rate 750K http://127.0.0.1:8082/api/tracks/<id>/stream for 30 s.
Not yet measured: storage and network on real hardware. Open item: run the same streaming load on the lab
server (bitwerk, wired players) against its own disk, and replace the estimates of the storage table with what it
shows.