Rockbox open source high quality audio player as a Music Player Daemon
mpris rockbox mpd libadwaita audio rust zig deno
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Increase AirPlay RTCP sync rate and add offset

+28 -9
+6 -4
crates/airplay/src/lib.rs
··· 43 43 44 44 self.pacing.advance(); 45 45 46 - // RTCP NTP sync every ~44 frames (~0.35 s) 47 - if self.pacing.frames_sent % 44 == 0 { 46 + // RTCP NTP sync every ~10 frames (~80 ms) for tighter multi-room alignment. 47 + // NTP offset = time until next_ts plays, so receivers anchor on the exact deadline. 48 + if self.pacing.frames_sent % 10 == 0 { 48 49 let current_ts = self.pacing.rtptime.wrapping_sub(FRAME_SAMPLES as u32); 49 50 let next_ts = self.pacing.rtptime; 51 + let offset_us = self.pacing.us_until_next_frame(); 50 52 for rx in &self.receivers { 51 - rx.send_sync(current_ts, next_ts, false); 53 + rx.send_sync(current_ts, next_ts, false, offset_us); 52 54 } 53 55 } 54 56 ··· 59 61 fn send_initial_sync(&self) { 60 62 let ts = self.pacing.initial_rtptime; 61 63 for rx in &self.receivers { 62 - rx.send_sync(ts, ts, true); 64 + rx.send_sync(ts, ts, true, 0); 63 65 } 64 66 tracing::debug!( 65 67 "sent initial sync ts={} to {} receiver(s)",
+22 -5
crates/airplay/src/rtp.rs
··· 93 93 } 94 94 95 95 /// Send an RTCP NTP sync packet on the ctrl socket. 96 - pub fn send_sync(&self, current_ts: u32, next_ts: u32, first: bool) { 97 - let now = SystemTime::now() 96 + /// 97 + /// `ntp_offset_us` is how many microseconds in the future `next_ts` will be played. 98 + /// Pass 0 for the initial sync; pass the remaining pacing time for periodic syncs. 99 + pub fn send_sync(&self, current_ts: u32, next_ts: u32, first: bool, ntp_offset_us: u64) { 100 + let wall = SystemTime::now() 98 101 .duration_since(UNIX_EPOCH) 99 - .unwrap_or_default(); 100 - let ntp_sec = now.as_secs() as u32 + NTP_EPOCH_DELTA; 101 - let ntp_frac = ((now.subsec_nanos() as u64 * (1u64 << 32)) / 1_000_000_000) as u32; 102 + .unwrap_or_default() 103 + + Duration::from_micros(ntp_offset_us); 104 + let ntp_sec = wall.as_secs() as u32 + NTP_EPOCH_DELTA; 105 + let ntp_frac = ((wall.subsec_nanos() as u64 * (1u64 << 32)) / 1_000_000_000) as u32; 102 106 103 107 let mut pkt = [0u8; 20]; 104 108 pkt[0] = if first { 0x90 } else { 0x80 }; ··· 179 183 let now = Instant::now(); 180 184 if expected > now { 181 185 std::thread::sleep(expected - now); 186 + } 187 + } 188 + 189 + /// Microseconds until the current `frames_sent` deadline (i.e. when `rtptime` plays). 190 + /// Returns 0 if we're already past the deadline. 191 + pub fn us_until_next_frame(&self) -> u64 { 192 + let Some(start) = self.stream_start else { return 0 }; 193 + let deadline = start + Duration::from_micros(self.frames_sent * FRAME_DURATION_US); 194 + let now = Instant::now(); 195 + if deadline > now { 196 + (deadline - now).as_micros() as u64 197 + } else { 198 + 0 182 199 } 183 200 } 184 201