From 0cd634ef3f44f31e7254d100fb0f899489871ebe Mon Sep 17 00:00:00 2001 From: Roderick van Domburg Date: Wed, 10 Jun 2026 00:06:55 +0200 Subject: [PATCH 1/2] fix(pipewire): ensure monotonic timestamps --- CHANGELOG.md | 7 +++++++ src/host/pipewire/stream.rs | 8 ++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 834b4269b..66e6d2a0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Fixed + +- **PipeWire**: Fix non-monotonic timestamps on unusual clock readings. + ## [0.18.1] - 2026-06-07 ### Fixed @@ -1285,6 +1291,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Initial commit. +[Unreleased]: https://github.com/RustAudio/cpal/compare/v0.18.1...HEAD [0.18.1]: https://github.com/RustAudio/cpal/compare/v0.18.0...v0.18.1 [0.18.0]: https://github.com/RustAudio/cpal/compare/v0.17.3...v0.18.0 [0.17.3]: https://github.com/RustAudio/cpal/compare/v0.17.2...v0.17.3 diff --git a/src/host/pipewire/stream.rs b/src/host/pipewire/stream.rs index fece9be11..2af75a2d9 100644 --- a/src/host/pipewire/stream.rs +++ b/src/host/pipewire/stream.rs @@ -306,8 +306,8 @@ where Some(t) => { let delay_ns = t.delay() * 1_000_000_000i64 / t.rate().denom as i64; ( - StreamInstant::from_nanos(t.now() as u64), - StreamInstant::from_nanos((t.now() - delay_ns.max(0)) as u64), + StreamInstant::from_nanos(t.now().max(0) as u64), + StreamInstant::from_nanos((t.now() - delay_ns.max(0)).max(0) as u64), ) } None => { @@ -345,8 +345,8 @@ where Some(t) => { let delay_ns = t.delay() * 1_000_000_000i64 / t.rate().denom as i64; ( - StreamInstant::from_nanos(t.now() as u64), - StreamInstant::from_nanos((t.now() + delay_ns.max(0)) as u64), + StreamInstant::from_nanos(t.now().max(0) as u64), + StreamInstant::from_nanos((t.now() + delay_ns.max(0)).max(0) as u64), ) } None => { From bfccb60f6b20703822fcbf9f8beac455dcd833af Mon Sep 17 00:00:00 2001 From: Roderick van Domburg Date: Wed, 10 Jun 2026 21:18:37 +0200 Subject: [PATCH 2/2] fix: address review points --- CHANGELOG.md | 7 ------- src/host/pipewire/stream.rs | 16 ++++++++++------ 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66e6d2a0a..834b4269b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,6 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] - -### Fixed - -- **PipeWire**: Fix non-monotonic timestamps on unusual clock readings. - ## [0.18.1] - 2026-06-07 ### Fixed @@ -1291,7 +1285,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Initial commit. -[Unreleased]: https://github.com/RustAudio/cpal/compare/v0.18.1...HEAD [0.18.1]: https://github.com/RustAudio/cpal/compare/v0.18.0...v0.18.1 [0.18.0]: https://github.com/RustAudio/cpal/compare/v0.17.3...v0.18.0 [0.17.3]: https://github.com/RustAudio/cpal/compare/v0.17.2...v0.17.3 diff --git a/src/host/pipewire/stream.rs b/src/host/pipewire/stream.rs index 2af75a2d9..94b3cbc18 100644 --- a/src/host/pipewire/stream.rs +++ b/src/host/pipewire/stream.rs @@ -304,10 +304,12 @@ where let (callback, capture) = match pw_stream_time(stream) { Some(t) => { - let delay_ns = t.delay() * 1_000_000_000i64 / t.rate().denom as i64; + // `pw_stream_time` guarantees `now > 0` and `denom != 0`. + let now = t.now() as u64; + let delay_ns = (t.delay() * 1_000_000_000i64 / t.rate().denom as i64).max(0) as u64; ( - StreamInstant::from_nanos(t.now().max(0) as u64), - StreamInstant::from_nanos((t.now() - delay_ns.max(0)).max(0) as u64), + StreamInstant::from_nanos(now), + StreamInstant::from_nanos(now.saturating_sub(delay_ns)), ) } None => { @@ -343,10 +345,12 @@ where let (callback, playback) = match pw_stream_time(stream) { Some(t) => { - let delay_ns = t.delay() * 1_000_000_000i64 / t.rate().denom as i64; + // `pw_stream_time` guarantees `now > 0` and `denom != 0`. + let now = t.now() as u64; + let delay_ns = (t.delay() * 1_000_000_000i64 / t.rate().denom as i64).max(0) as u64; ( - StreamInstant::from_nanos(t.now().max(0) as u64), - StreamInstant::from_nanos((t.now() + delay_ns.max(0)).max(0) as u64), + StreamInstant::from_nanos(now), + StreamInstant::from_nanos(now.saturating_add(delay_ns)), ) } None => {