Cinnamon version: 6.4
cinnamon-settings-daemon version: 6.4.3
OS: FreeBSD 15.0-RELEASE-p9
PulseAudio version: 17.0
Audio backend: module-oss (OSS)
Hardware: Mac Pro 5,1, Realtek ALC889A (multiple PCM devices)
Issue
On FreeBSD, pressing the volume up media key (XF86AudioRaiseVolume) works only once. After the first press, subsequent presses have no effect until volume down is pressed. Volume down works reliably every time. The issue does not occur on Linux.
The root cause is a misalignment between the volume step grid used by csd-media-keys and the volume resolution available through PulseAudio's OSS backend.
Related PR: #460 (tested and working locally)
Steps to Reproduce
- Set volume to 50% (e.g.
pactl set-sink-volume @DEFAULT_SINK@ 50%).
- Press XF86AudioRaiseVolume. Volume increases to ~54%.
- Press XF86AudioRaiseVolume again. Volume stays at 54%.
- Press XF86AudioRaiseVolume any number of additional times. Volume remains at 54%.
- Press XF86AudioLowerVolume. Volume decreases to ~49%.
- Press XF86AudioRaiseVolume. Volume increases to ~54% again, then gets stuck.
This was confirmed by simulating key events via XTest and monitoring both PulseAudio (pactl get-sink-volume) and the OSS mixer (mixer -d pcm9) simultaneously. PulseAudio receives change events for every key press, confirming csd-media-keys is sending volume commands -- but the actual volume does not change after the first press.
Expected Behaviour
Each press of volume up should increase the volume by approximately 5%, consistently, regardless of how many times it is pressed in succession.
Root Cause Analysis
The bug is caused by an interaction between three components:
-
OSS has only 101 discrete volume levels (0-100). PulseAudio's module-oss maps its internal 0-65536 range to these 101 steps. Each OSS step corresponds to ~655 PA units.
-
csd-media-keys uses grid-aligned stepping. In do_sound_action() (plugins/media-keys/csd-media-keys-manager.c), volume up computes:
new_vol_pa = MIN(old_vol_pa / vol_step_pa * vol_step_pa + vol_step_pa, max_vol_pa);
With VOLUME_STEP = 5, vol_step_pa = 65536 * 5 / 100 = 3276. This snaps to a grid of multiples of 3276.
-
PulseAudio truncates when mapping to OSS levels. When PA sets volume 36036 (the grid target for "55%"), OSS receives 36036 / 65536 = 0.54987..., which truncates to level 54 (not 55). The readback value becomes 35389 (54%).
The result is a cycle:
- From 35389 (54%):
floor(35389 / 3276) * 3276 + 3276 = 10 * 3276 + 3276 = 36036
- PA sets 36036 -> OSS truncates to level 54 -> readback is 35389
- Next press computes the same 36036 -> same truncation -> stuck forever
Volume down is not affected because both the floor-rounding in csd-media-keys and the truncation in OSS go in the same direction (downward), so the volume always decreases.
Why this only affects FreeBSD
On Linux, PulseAudio uses ALSA, which has fine-grained volume resolution (65536+ steps). The grid-aligned math lands precisely on the intended value with no truncation. On FreeBSD, PulseAudio uses module-oss, which has only 101 steps. The coarse quantization creates step boundaries that the Cinnamon volume grid (multiples of 3276) systematically falls just short of.
Verified by direct PA volume tests:
Set 36036 (54.99%) -> got 35389 (54.00%) # 9 units short of boundary
Set 36045 (55.00%) -> got 36044 (55.00%) # just barely makes it
Set 36044 (55.00%) -> got 35389 (54.00%) # 1 unit short, truncated
Proposed Fix
On FreeBSD only (#ifdef __FreeBSD__), replace the grid-aligned stepping with simple addition/subtraction in do_sound_action() (plugins/media-keys/csd-media-keys-manager.c):
// Volume up - before (grid-aligned, gets stuck on OSS):
new_vol_pa = MIN(old_vol_pa / vol_step_pa * vol_step_pa + vol_step_pa, max_vol_pa);
// Volume up - after (simple addition, works on OSS):
new_vol_pa = MIN(old_vol_pa + vol_step_pa, max_vol_pa);
// Volume down - before:
new_vol_pa = (old_vol_pa / vol_step_pa * vol_step_pa) - vol_step_pa;
// Volume down - after:
new_vol_pa = old_vol_pa - vol_step_pa;
This matches what pactl set-sink-volume +5% does internally, which was confirmed to work correctly on FreeBSD. The grid-alignment logic is preserved on Linux where ALSA's fine-grained volume resolution (65536+ steps) does not suffer from this truncation issue.
The alternative fix would be in PulseAudio's module-oss to round to nearest instead of truncating when converting PA volumes to OSS levels, but the csd-media-keys fix is simpler and self-contained.
Related Issues
Issue
On FreeBSD, pressing the volume up media key (XF86AudioRaiseVolume) works only once. After the first press, subsequent presses have no effect until volume down is pressed. Volume down works reliably every time. The issue does not occur on Linux.
The root cause is a misalignment between the volume step grid used by
csd-media-keysand the volume resolution available through PulseAudio's OSS backend.Related PR: #460 (tested and working locally)
Steps to Reproduce
pactl set-sink-volume @DEFAULT_SINK@ 50%).This was confirmed by simulating key events via XTest and monitoring both PulseAudio (
pactl get-sink-volume) and the OSS mixer (mixer -d pcm9) simultaneously. PulseAudio receiveschangeevents for every key press, confirmingcsd-media-keysis sending volume commands -- but the actual volume does not change after the first press.Expected Behaviour
Each press of volume up should increase the volume by approximately 5%, consistently, regardless of how many times it is pressed in succession.
Root Cause Analysis
The bug is caused by an interaction between three components:
OSS has only 101 discrete volume levels (0-100). PulseAudio's
module-ossmaps its internal 0-65536 range to these 101 steps. Each OSS step corresponds to ~655 PA units.csd-media-keysuses grid-aligned stepping. Indo_sound_action()(plugins/media-keys/csd-media-keys-manager.c), volume up computes:With
VOLUME_STEP = 5,vol_step_pa = 65536 * 5 / 100 = 3276. This snaps to a grid of multiples of 3276.PulseAudio truncates when mapping to OSS levels. When PA sets volume 36036 (the grid target for "55%"), OSS receives
36036 / 65536 = 0.54987..., which truncates to level 54 (not 55). The readback value becomes 35389 (54%).The result is a cycle:
floor(35389 / 3276) * 3276 + 3276 = 10 * 3276 + 3276 = 36036Volume down is not affected because both the floor-rounding in
csd-media-keysand the truncation in OSS go in the same direction (downward), so the volume always decreases.Why this only affects FreeBSD
On Linux, PulseAudio uses ALSA, which has fine-grained volume resolution (65536+ steps). The grid-aligned math lands precisely on the intended value with no truncation. On FreeBSD, PulseAudio uses
module-oss, which has only 101 steps. The coarse quantization creates step boundaries that the Cinnamon volume grid (multiples of 3276) systematically falls just short of.Verified by direct PA volume tests:
Proposed Fix
On FreeBSD only (
#ifdef __FreeBSD__), replace the grid-aligned stepping with simple addition/subtraction indo_sound_action()(plugins/media-keys/csd-media-keys-manager.c):This matches what
pactl set-sink-volume +5%does internally, which was confirmed to work correctly on FreeBSD. The grid-alignment logic is preserved on Linux where ALSA's fine-grained volume resolution (65536+ steps) does not suffer from this truncation issue.The alternative fix would be in PulseAudio's
module-ossto round to nearest instead of truncating when converting PA volumes to OSS levels, but thecsd-media-keysfix is simpler and self-contained.Related Issues