RustAudio / cpal

Cross-platform audio I/O library in pure Rust
Apache License 2.0
2.66k stars 352 forks source link

First `OutputStreamTimestamp` very odd. #710

Open bspot opened 1 year ago

bspot commented 1 year ago

On Linux / ALSA (via PulseAudio), the first time the audio callback is called after the stream is started, the timestamp contains nonsensical values:

fn main() {
    // Open audio device
    let host = cpal::default_host();
    println!("Host is \"{:?}\"", host.id());

    let device = host.default_output_device().ok_or_else(|| "No output device").unwrap();
    println!("Device is {:?}", device.name().unwrap());

    // Create stream
    let _stream = device
        .build_output_stream(
            &cpal::StreamConfig {
                channels: 1,
                sample_rate: cpal::SampleRate(44100),
                buffer_size: cpal::BufferSize::Default,
            },
            move |data: &mut [f32], info: &cpal::OutputCallbackInfo| {
                println!("{:?}", info.timestamp());
                data.fill(0.0);
            },
            |err| {
                println!("{:?}", err);
            },
        )
        .unwrap();

    // Wait 
    loop {
        thread::sleep(Duration::from_secs(1));
    }
}

Output:

Host is "Alsa"
Device is "default"
OutputStreamTimestamp { callback: StreamInstant { secs: 1105516, nanos: 585731676 }, playback: StreamInstant { secs: 1105516, nanos: 585731676 } }
OutputStreamTimestamp { callback: StreamInstant { secs: 0, nanos: 28177 }, playback: StreamInstant { secs: 0, nanos: 100028177 } }
OutputStreamTimestamp { callback: StreamInstant { secs: 0, nanos: 25960575 }, playback: StreamInstant { secs: 0, nanos: 166504792 } }
OutputStreamTimestamp { callback: StreamInstant { secs: 0, nanos: 56707430 }, playback: StreamInstant { secs: 0, nanos: 192081579 } }
OutputStreamTimestamp { callback: StreamInstant { secs: 0, nanos: 82378008 }, playback: StreamInstant { secs: 0, nanos: 222740819 } }
OutputStreamTimestamp { callback: StreamInstant { secs: 0, nanos: 107205332 }, playback: StreamInstant { secs: 0, nanos: 248293767 } }
OutputStreamTimestamp { callback: StreamInstant { secs: 0, nanos: 132064702 }, playback: StreamInstant { secs: 0, nanos: 273493273 } }
OutputStreamTimestamp { callback: StreamInstant { secs: 0, nanos: 162067172 }, playback: StreamInstant { secs: 0, nanos: 298665811 } }
OutputStreamTimestamp { callback: StreamInstant { secs: 0, nanos: 190642728 }, playback: StreamInstant { secs: 0, nanos: 328012342 } }
OutputStreamTimestamp { callback: StreamInstant { secs: 0, nanos: 215397391 }, playback: StreamInstant { secs: 0, nanos: 356825962 } }
OutputStreamTimestamp { callback: StreamInstant { secs: 0, nanos: 250041818 }, playback: StreamInstant { secs: 0, nanos: 381810525 } }
OutputStreamTimestamp { callback: StreamInstant { secs: 0, nanos: 279423110 }, playback: StreamInstant { secs: 0, nanos: 416248506 } }
OutputStreamTimestamp { callback: StreamInstant { secs: 0, nanos: 313084664 }, playback: StreamInstant { secs: 0, nanos: 445601670 } }
OutputStreamTimestamp { callback: StreamInstant { secs: 0, nanos: 341173837 }, playback: StreamInstant { secs: 0, nanos: 478951614 } }
OutputStreamTimestamp { callback: StreamInstant { secs: 0, nanos: 370250724 }, playback: StreamInstant { secs: 0, nanos: 507030769 } }
OutputStreamTimestamp { callback: StreamInstant { secs: 0, nanos: 404757068 }, playback: StreamInstant { secs: 0, nanos: 536389721 } }
FreeFull commented 1 year ago

Notably, StreamInstant is meant to be monotonic, but the first value breaks that guarantee.

hugovdm commented 1 year ago

I'm seeing this problem too, but it also looks like it isn't entirely nonsensical:

So it would seem there's some system epoch which is leaking into the first playback callback when using ALSA, after which the epoch is reset to ~now.

I've not analyzed in depth, just peeked at src/host/alsa/mod.rs - and find myself wondering if it might e.g. be a bug in creation_instant handling?

akulisch commented 12 months ago

For me it seems to be the system’s uptime for the first sample (checked with cat /proc/uptime). After that it is the time since playback started (exactly like in the issue example).