Skip to content

Read the host clock from CLOCK_MONOTONIC_RAW on Linux - #208

Merged
kahrendt merged 1 commit into
mainfrom
fix/use-raw-monotonic-clock-on-linux
Oct 17, 2026
Merged

kahrendt merged 1 commit into
mainfrom
fix/use-raw-monotonic-clock-on-linux

Conversation

@chrisuthe

Copy link
Copy Markdown
Member

On Linux, std::chrono::steady_clock is CLOCK_MONOTONIC, which NTP slews. aiosendspin timestamps on CLOCK_MONOTONIC_RAW, so while the host's time daemon corrects the system clock, the offset the time filter measures moves at the slew rate. That rate changes faster than the filter's drift model follows, the sync error crosses the hard sync threshold, and playback hard-corrects.

platform_time_us() now reads CLOCK_MONOTONIC_RAW on Linux and keeps steady_clock on other hosts. ESP-IDF is unchanged.

Notes

  • New SendspinClient::now_us() returns the client clock, so consumers stamp notify_audio_played() and write_audio() times in the library's clock domain. The examples and the integration guide use it.
  • aiosendspin, sendspin-rs and SendspinKit already use the raw clock on Linux.
  • sendspin-cli stamps its sinks with its own steady_clock helpers; it moves to now_us() once this is tagged.

Testing

  • Ubuntu 24.04 x86_64: -DENABLE_WERROR=ON build with the examples and PortAudio; 707/707 under ASan/UBSan, 696/696 without Opus, 707/707 under TSan; script/clang-tidy.sh and clang-format 18.1.8 clean.
  • No new test: on a host whose time daemon is not slewing, the two clocks read alike, so a test cannot fail when the change is reverted.
  • Home Assistant OS, sendspin-cli v0.4.0 and aiosendspin 10.1.0 on the same host. systemd-timesyncd's hourly initial sync moved CLOCK_MONOTONIC 31 ms against CLOCK_MONOTONIC_RAW within 75 s. With this change the measured offset stayed within 75 µs of zero through it; without it the same event moved the measured offset 51 ms in a minute and the player lost sync repeatedly.
  • With -o pulse the player can still lose sync after such a correction: PulseAudio's own timing runs on CLOCK_MONOTONIC, which this change does not reach.

On Linux, `std::chrono::steady_clock` is `CLOCK_MONOTONIC`, which NTP
slews. aiosendspin timestamps on `CLOCK_MONOTONIC_RAW`, so while the
host's time daemon corrects the system clock, the offset the time filter
measures moves at the slew rate. That rate changes faster than the
filter's drift model follows, the sync error crosses the hard sync
threshold, and playback hard-corrects.

`platform_time_us()` now reads `CLOCK_MONOTONIC_RAW` on Linux and keeps
`steady_clock` on other hosts. ESP-IDF is unchanged.

`SendspinClient::now_us()` exposes the client clock, so consumers stamp
`notify_audio_played()` and `write_audio()` times in the library's clock
domain. The examples and the integration guide use it.

@kahrendt kahrendt left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I marked it as a breaking change as anyone who built a linux client that directly used the wrong timestamp needs to migrate to the helper.

@kahrendt
kahrendt merged commit 90cbd80 into main Oct 17, 2026
16 checks passed
@kahrendt
kahrendt deleted the fix/use-raw-monotonic-clock-on-linux branch October 17, 2026 10:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants