Skip to content

fix: keep timestamps monotonic across device and graph changes#1251

Merged
roderickvd merged 5 commits into
masterfrom
fix/timestamp-buffer-depth
Jun 17, 2026
Merged

fix: keep timestamps monotonic across device and graph changes#1251
roderickvd merged 5 commits into
masterfrom
fix/timestamp-buffer-depth

Conversation

@roderickvd

Copy link
Copy Markdown
Member

#1246 and #1247 fixed monotonicity on WASAPI and PipeWire one at a time; this PR takes another pass to ensure no timestamp can regress on any host.

The problem is that a host's reported latency can jump between callbacks (a reroute, a graph reconnection, or a timestamp read that fails and falls back to now()). Add that to the timestamp and it can step backward, while StreamInstant is supposed to be strictly monotonic. As solution, on hosts and directions where this can occur, the data callbacks now run through a shared non-decreasing clamp.

While I was in here:

  • iOS and JACK weren't accounting for buffer depth at all, so now they do.
  • iOS also re-reads it on a route change.
  • ALSA: a sub-millisecond poll timeout truncated to 0, which is a non-blocking busy poll. Rounds up to 1ms now. Duration::ZERO still means 0 on purpose.
  • WASAPI: the frame/REFERENCE_TIME round-trip truncated both ways, so 512, 1024 and friends came back a frame short. Rounds now.

…ng poll

A nonzero timeout under 1ms truncated to 0, which ALSA reads as a non-blocking
poll. That busy-spins instead of waiting the short interval the caller asked
for. Round up to 1ms. An explicit Duration::ZERO still maps to 0, so a
deliberate non-blocking poll still works.
… one

A frame count and its 100ns duration aren't exact multiples. Truncating on
the way out and again on the way back drops common sizes (512, 1024, ...) by
a frame, so the supported range and default buffer size came back one short.
Round both halves of the round-trip.

Copilot AI 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.

Pull request overview

This PR aims to ensure StreamInstant timestamps remain monotonic across all hosts even when reported latency changes between callbacks (e.g., device reroutes, graph reconnects, transient timestamp read failures), by clamping callback timestamps to never regress.

Changes:

  • Add shared “non-decreasing” wrappers for input/output data callbacks and apply them across multiple backends where latency can vary.
  • Fix/adjust latency and buffer-depth accounting across hosts (notably iOS route changes + hardware latency, JACK port latency, macOS default output reroute depth refresh).
  • Address backend-specific time/buffer issues (ALSA poll timeout rounding; WASAPI buffer size round-trip rounding).

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/host/webaudio/mod.rs Wrap output callback with monotonic clamp to prevent regressions when outputLatency changes.
src/host/wasapi/device.rs Clamp output timestamps; fix buffer size ↔ duration conversions by rounding.
src/host/pulseaudio/mod.rs Wrap input/output callbacks with monotonic clamps to handle latency changes on source/sink switches.
src/host/pipewire/device.rs Wrap input/output callbacks with monotonic clamps to handle graph delay changes.
src/host/mod.rs Introduce shared non-decreasing clamp and callback wrapper helpers.
src/host/jack/stream.rs Include JACK port/hardware latency in timestamps (capture/playback).
src/host/jack/device.rs Apply monotonic clamps around JACK callbacks to prevent regressions on repatching/latency changes.
src/host/coreaudio/macos/mod.rs Extend default-output monitoring to refresh latency/buffer-depth on reroute.
src/host/coreaudio/macos/device.rs Apply monotonic clamp for playback; track/update buffer depth for DefaultOutput reroutes.
src/host/coreaudio/ios/session_event_manager.rs Refresh shared latency depth on route-change notifications.
src/host/coreaudio/ios/mod.rs Apply monotonic clamps; include hardware latency in buffer depth; round frame computations.
src/host/audioworklet/mod.rs Clamp playback timestamps; add polling of outputLatency on main thread and share via atomics.
src/host/asio/stream.rs Apply monotonic clamps for capture/playback on ASIO latency-change events.
src/host/alsa/mod.rs Apply monotonic clamps; round up sub-millisecond poll timeouts to avoid busy polling.
src/host/aaudio/mod.rs Apply monotonic clamps for capture/playback when timestamp reads transiently fail.
CHANGELOG.md Document monotonic timestamp fixes and backend-specific latency/buffer behavior changes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/host/pulseaudio/mod.rs Outdated
Comment thread src/host/audioworklet/mod.rs
Comment thread src/host/audioworklet/mod.rs

Copilot AI 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.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 5 comments.

Comment thread src/host/mod.rs
Comment thread src/host/coreaudio/macos/device.rs
Comment thread src/host/webaudio/mod.rs Outdated
Comment thread src/host/pipewire/device.rs
Comment thread src/host/pipewire/device.rs

Copilot AI 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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI 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.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.

Comment thread src/timestamp.rs Outdated

Copilot AI 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.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated 1 comment.

Comment thread src/timestamp.rs Outdated

Copilot AI 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.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated 1 comment.

Comment thread src/host/jack/stream.rs

Copilot AI 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.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated no new comments.

A host's reported latency can jump between callbacks: a device reroute, an
audio graph reconnection, or a timestamp read that fails and falls back to
now() with no latency offset. Folding that into the timestamp can walk the
derived instant backward. Wrap the data callbacks in a shared non-decreasing
clamp, applied only to the hosts that can actually regress.

iOS and JACK didn't account for buffer depth yet, so I added that here too:

- iOS: capture and playback now include hardware latency, and refresh it when
  the audio route changes.
- JACK: timestamps now include port latency.
@roderickvd roderickvd force-pushed the fix/timestamp-buffer-depth branch from 3e8f24e to b1cf416 Compare June 17, 2026 18:58
@roderickvd roderickvd merged commit 8a48ae6 into master Jun 17, 2026
32 checks passed
@roderickvd roderickvd deleted the fix/timestamp-buffer-depth branch June 17, 2026 19:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants