-
Notifications
You must be signed in to change notification settings - Fork 0
Volume
Across an AudioHub peer link there are two things that could be kept in step: the position of the slider, or the loudness it produces. They cannot both be kept in step. This page shows why, and says which one AudioHub keeps.
AudioHub keeps the position. Move a control to 50 % and the peer's device lands on 50 % too — and at that percentage the two machines are not equally loud. The difference is measured below, and it is what 1.0.0 does; it is not a bug that has been fixed. (The one place even the percentages diverge is a Windows virtual device — see Mode B.)
AirPlay is governed by a different contract; see AirPlay volume is a separate contract.
| On the wire | one number in 0..1, plus an optional mute flag |
| Where it starts | Mode A: the volume slider on the peer card (and the macOS menu-bar slider). Mode B: the system's own volume control on the AudioHub virtual device |
| How the receiving side reads it | as a slider position — it is written straight to the default output device's scalar volume property: kAudioDevicePropertyVolumeScalar on macOS, SetMasterVolumeLevelScalar on Windows |
| What the audio stream carries | nothing volume-related; samples are sent at full scale (one exception) |
No decibel conversion happens between the two machines. Both platforms expose a native dB property for a real device, and the shipped code does not use it — which is exactly why loudness is not preserved.
The mute flag is optional and separate. Dragging the slider in AudioHub sends the level only and leaves the peer's mute control alone, so a slider drag can never unmute a machine somebody deliberately muted; the mute button sends it explicitly. A virtual device in Mode B reports both together, because the system control it mirrors carries both.
Same slider position, two machines, real hardware on both ends — macOS built-in speakers against a Windows output endpoint:
| Slider | macOS | Windows | Difference | Perceived |
|---|---|---|---|---|
| 0 % | −63.5 dB | −96.0 dB | −32.5 dB | quieter |
| 10 % | −43.4 dB | −34.8 dB | +8.7 dB | ~1.8× louder |
| 25 % | −31.8 dB | −21.0 dB | +10.8 dB | ~2.1× louder |
| 50 % | −18.6 dB | −10.5 dB | +8.1 dB | ~1.8× louder |
| 75 % | −8.5 dB | −4.4 dB | +4.1 dB | ~1.3× louder |
| 90 % | −3.3 dB | −1.6 dB | +1.7 dB | ~1.1× louder |
| 100 % | 0 dB | 0 dB | 0 dB | equal |
Only 0 % and 100 % line up. Everything between them differs, and the worst error falls in the 10–50 % range people actually use — where Windows is roughly twice as loud as macOS at the same slider position.
Practically: after you drag a slider, check the far end by ear, not by reading the percentage back.
A tempting framing is macOS uses one taper, Windows uses another. The measurements do not support it. Three different mappings across the four devices measured, two of them on macOS:
| Device | Mapping | At 50 % |
|---|---|---|
| macOS built-in speakers | dB = −63.5 · (1 − √s) |
−18.6 dB |
| AudioHub's own macOS virtual device | dB = −63.5 · (1 − √s) |
−18.6 dB |
| A third-party macOS virtual cable |
dB = −64 + 64·s (dB-linear) |
−32.0 dB |
| Windows software endpoint | amplitude = s^1.745 |
−10.5 dB |
Square root, dB-linear, and a power law: the curvature does not even point the same way. Windows is internally inconsistent too — an endpoint with hardware volume follows a further curve of its own.
AudioHub's macOS virtual device is the one row here that is a deliberate choice rather than a vendor's: it is set point-for-point to Apple's built-in taper, so that a volume key press on it steps by the same number of decibels as a volume key press on the built-in output.
One structural difference is worth noting: the Windows mapping is independent of the device's dB range, while the macOS mapping scales with it. These are two design philosophies, not two parameterisations of one idea.
Let T_ours be the taper of the virtual device AudioHub controls, and T_peer
the taper of the peer's real device, which the hardware vendor and the OS
control.
-
Equal loudness requires
T_ours(s₁) = T_peer(s₂). -
Equal slider numbers requires
s₁ = s₂.
Both hold simultaneously if and only if T_ours ≡ T_peer. Measurement has
already shown T_peer is several different functions across several devices, so
no single T_ours can equal all of them. This is not an engineering problem
where a better implementation gets you both; one has to be chosen.
Equal loudness would mean putting an amplitude on the wire and landing it through each platform's native dB property, which is a design that exists on paper and is not in 1.0.0. Until it ships, the numbers agree and the loudness does not.
Which of the three sets of rules below applies depends on the mode this machine is in, and the modes are mutually exclusive — see Operating Modes for what each one does and where the picker is.
The mode itself, including what appears in the system device list and what happens when the driver is missing, is described in Mode B · Virtual devices.
There is no volume slider on the peer card in Mode B — the control is the system's own. Adjust the AudioHub virtual speaker in Sound settings, with the volume keys, or from any app's volume UI, and the value is written to the peer's real default output device.
The virtual device never scales samples; it is a control node only. Whatever attenuation you hear happens on the peer's device, at the peer's taper.
The two virtual devices differ in one way that shows on screen. The macOS virtual device publishes the slider position directly, so its percentage matches the peer's. The Windows virtual device carries volume as decibels internally — that is what a kernel-streaming volume node holds — so both directions pass through a dB conversion and the two percentages will not line up. Measured on a real Windows endpoint, a peer sitting at 50 % lands at about 67 % on the Windows side. Treat the exact figure as an expectation rather than a guarantee: it has never been read back through the AudioHub driver itself.
The mode itself, including the capture backends and the one-peer-at-a-time rule, is described in Mode A · Driverless.
Two independent things, and they are easy to confuse:
| Where | Default | What it does | |
|---|---|---|---|
| Peer volume slider | the peer's card (and the macOS menu-bar slider) | — | drives the peer's real output device |
| Sync volume | Settings › Options | off | makes this machine's system output follow the peer's reading |
| Mute locally | Settings › Options | off | mutes this machine's output once, when a speaker path is established |
The Options card only shows these while Mode A is the selected mode.
Sync volume puts the peer in charge: on connect this machine adopts the peer's value, and if both change at once the peer's value wins.
Mute follows volume — with one exception. If Mute locally is also on, only the volume level syncs and mute stays local; otherwise the one-shot mute would travel down the sync link and silence the peer as well. The app states this under the switch when both are on.
That local mute is a one-time action, not a maintained state. It fires once when the speaker path comes up. Nothing re-applies it, so if you unmute afterwards that is taken to mean you want this machine audible too, until the next path is established. It is skipped entirely on a capture backend that reads the stream after the output volume — Windows Device loopback — because muting there would take the mirror being sent to the peer down with it.
Share mode is the default on a fresh install, so this is the section that applies until someone changes the mode.
Nothing on this page applies to the serving side. A machine in Share mode owns the output device the peer is driving; it has no outbound speaker stream of its own and no peer volume control. The Stats tab labels its own device's reading Local volume rather than Peer volume for exactly this reason.
Some outputs expose no volume control whatsoever. The usual case is a macOS aggregate or multi-output device built in Audio MIDI Setup: it has no master volume, so there is nothing on it for a slider to drive.
In Mode B, AudioHub takes the volume over. The value stops being sent to the peer and becomes a software gain applied on the sending side, so the wire then carries audio that already has volume in it. Two things you will see:
- the line under the peer's volume slider reads "Peer volume unsupported · Using local software gain.";
- the level starts at 100 % the moment the fallback engages. That is not a jump in loudness — at that instant nothing anywhere is attenuating, so 100 % is the honest reading. Adopting the peer's reported value instead would mute the link, because a device with no volume reports 0.
While the fallback is in force the slider is a straight multiplier: 50 % is half the amplitude, or −6 dB. That is a much shallower taper than any real device's — the same 50 % is about 12.6 dB louder than it would be through a Mac's built-in output. Expect to sit lower on the slider than you are used to.
Mute is folded into the same gain, and both ride a 20 ms ramp, so neither pops. Any gain below unity also switches on TPDF dither for the 16-bit quality rungs, because attenuating in 16 bits throws away resolution; the 32-bit-float and 24-bit rungs are already wide enough and get none. At exactly 100 % the wire is bit-for-bit identical to the normal full-scale path.
In Mode A there is no fallback. Mode A has no virtual device to take the volume over, so against such a peer the card's slider is greyed out and the Sync volume switch does nothing in either direction. The peer's reported "0" is an absence, not a level, and following it would write silence onto a working speaker.
The stream itself carries unmodified, full-scale audio. Volume is a control-plane property applied at the endpoints, not something mixed into the samples in transit.
The software-gain fallback above is the one exception, and the interface says so rather than leaving a slider that silently does nothing.
AirPlay receiving has its own product and protocol semantics, and they are
deliberately not the peer-link semantics above. For the receiver itself —
turning it on, the password, and where the sound goes — see
Share Protocols. Its -30..0 dB volume field
is treated as a linear slider coordinate, not as acoustic gain, and is
mapped directly onto the receiving system's slider:
- sender to receiver:
system_scalar = (clamp(airplay_db, -30, 0) + 30) / 30; - receiver to sender:
airplay_db = -30 + 30 * clamp(system_scalar, 0, 1).
-144 is the mute value, and the two directions treat it differently:
-
Inbound
-144both mutes this machine's output and moves its visible slider to zero. Apple's own sender uses-144for the visible zero-percent endpoint, not only for a separate mute button, so leaving a 50 % slider behind would break the direct-agreement contract. AudioHub reports failure if the slider does not actually reach zero. -
A mute performed here on the receiver is reflected back as
-144without disturbing the local slider, so an explicit mute/unmute cycle returns to where it was.
An unmuted scalar of 0 maps to -30, which is not mute.
Do not reason about AirPlay volume through the peer-link table on this page, and do not expect it to run through the endpoint's native dB taper. Converting it once would put a client's 0 % at roughly 10 % on the receiver and collapse the bottom of the range into silence.
| Symptom | Likely cause | What to do |
|---|---|---|
| Slider and mute button are greyed out, note reads "Peer volume unsupported" | the peer's default output has no volume control, and you are in Mode A | switch the peer's default output to a device that has a volume control, or use Mode B, where AudioHub takes the volume over |
| Note reads "Peer volume unsupported · Using local software gain" | Mode B fallback is in force | working as intended; the slider is real and drives a send-side gain, starting at 100 % |
| Slider moves, far end does not | the peer accepted the value but its device refused the write | this is only recorded in the service log; check the peer's output device directly |
| Sync volume is on and this machine's output never follows | the peer's device reports no adjustable volume, so the switch is inert | see the first row — there is no software-gain fallback in Mode A |
| Percentages agree but one machine is much louder | expected — see the measured table | set by ear, not by percentage |
| Note reads "Sync off" or "Loading volume…" | the session is not carrying volume yet, or no reading has arrived | wait for the session to establish; if it persists, reopen the speaker path |
If you go looking at this yourself: on macOS, the Core Audio properties that translate between scalar and decibels do not agree with the device's own behaviour. On built-in speakers the translation property reports a linear curve that contradicts the device's actual state by more than 15 dB, and on virtual devices it simply returns an error.
Trust only set-then-read-back. Every figure on this page was obtained that way, on real hardware on both platforms.
One path has not been measured end to end: what the Windows audio engine derives from the AudioHub virtual device's own volume node. The driver's half of that conversion — decibels to and from a 16.16 scalar — is pinned by tests against the formula, but the engine's half has only been measured on an ordinary Windows endpoint, never on one fed by the installed AudioHub driver.
Start here
Using AudioHub
- Window and tray
- Operating Modes
- Virtual Driver
- Discovery and Pairing
- Peer detail page
- Share Protocols (Sharing tab)
- Web Access
How it behaves
Reference
- Settings Reference
- Platform Notes
- Firewall and network
- CLI Reference
- Versions and Compatibility
- Glossary
- Licenses and Attribution
Troubleshooting and removal
This wiki describes AudioHub 1.0.0.