Skip to main content

ace_player/sync/
av_sync.rs

1//! AV synchronization — aligns video and audio streams.
2//!
3//! Uses tokio channels for race-condition-free frame timing.
4//! This is the "Fearless Concurrency" guarantee from the business proposal.
5use anyhow::Result;
6use tokio::sync::mpsc::Receiver;
7use crate::decoder::{audio::AudioFrame, video::VideoFrame};
8
9/// Run AV sync loop: consume video + audio frames and deliver at correct PTS.
10pub async fn run(
11    mut video_rx: Receiver<VideoFrame>,
12    mut audio_rx: Receiver<AudioFrame>,
13) -> Result<()> {
14    tracing::info!("AV sync started");
15
16    // Master clock: derive from audio PTS (audio drives sync, video follows)
17    let mut audio_sample_count: u64 = 0;
18    let sample_rate: u64 = 44100;
19
20    loop {
21        tokio::select! {
22            Some(video_frame) = video_rx.recv() => {
23                // Calculate current audio clock in ms
24                let audio_clock_ms = (audio_sample_count * 1000) / sample_rate;
25                let video_pts_ms = video_frame.pts_ms;
26
27                // Drop late frames, hold early frames
28                if video_pts_ms + 50 < audio_clock_ms {
29                    tracing::debug!("Dropping late video frame: pts={video_pts_ms}ms clock={audio_clock_ms}ms");
30                    continue;
31                }
32                if video_pts_ms > audio_clock_ms + 100 {
33                    // Video is ahead — brief yield
34                    tokio::time::sleep(tokio::time::Duration::from_millis(
35                        (video_pts_ms - audio_clock_ms).min(100)
36                    )).await;
37                }
38
39                // Render frame (send to output module)
40                crate::output::render_frame(&video_frame).await;
41            }
42
43            Some(audio_frame) = audio_rx.recv() => {
44                audio_sample_count += audio_frame.len() as u64 / 2; // stereo
45                crate::output::play_audio(&audio_frame).await;
46            }
47
48            else => break,
49        }
50    }
51
52    tracing::info!("AV sync completed");
53    Ok(())
54}