6de09fd822
Disconnect/reconnect now works reliably. Previously each stream spawned its own
portal+PipeWire (and PipeWire audio) capture threads and never stopped them, so a
reconnect opened a SECOND screencast session that conflicted with the leaked
first one ("no PipeWire frame within 10s" → black screen on reconnect).
- The screen capturer and audio capturer are now persistent, held in AppState and
reused across streams (created on the first stream). One screencast session for
the host's lifetime → no conflict, and instant reconnect (no re-handshake).
Verified live: 3 stream cycles, 1 create + 2 "reusing capturer", clean every time.
- Capturer::set_active gates the (5K, ~1.3 GB/s) de-pad copy to active streams, so
the persistent video capturer is nearly free while idle between streams.
- AudioCapturer::drain discards buffered chunks on reuse so the client never hears
stale audio captured while idle.
- stream.rs / gamestream/audio.rs split into a borrow-the-capturer wrapper + the
encode/send body, so the capturer is always returned to its slot on exit.
This holds whether the client reconnects via /resume (Moonlight's "running →
play/continue") or a fresh /launch — both re-run RTSP PLAY → a new stream cycle.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
37 lines
1.5 KiB
Rust
37 lines
1.5 KiB
Rust
//! Desktop audio capture for the GameStream audio stream. On Linux: a PipeWire stream that
|
|
//! records the default sink's monitor (i.e. everything playing out of the system), delivered
|
|
//! as interleaved `f32` stereo PCM at 48 kHz. The audio data plane (`gamestream::audio`)
|
|
//! reframes this into fixed Opus frames, encodes, and sends it.
|
|
|
|
use anyhow::Result;
|
|
|
|
/// Opus/GameStream audio is 48 kHz stereo.
|
|
pub const SAMPLE_RATE: u32 = 48_000;
|
|
pub const CHANNELS: usize = 2;
|
|
|
|
/// Produces interleaved `f32` stereo PCM (L,R,L,R,…) at [`SAMPLE_RATE`]. Lives on its own
|
|
/// thread; never blocks the capture loop (drops if the consumer falls behind).
|
|
pub trait AudioCapturer: Send {
|
|
/// Block until the next chunk of interleaved samples is available (variable size). The
|
|
/// caller reframes into fixed Opus frames.
|
|
fn next_chunk(&mut self) -> Result<Vec<f32>>;
|
|
|
|
/// Discard any buffered chunks (called when a persistent capturer is reused for a new
|
|
/// stream, so the client doesn't hear stale audio captured while idle). Default: no-op.
|
|
fn drain(&mut self) {}
|
|
}
|
|
|
|
/// Open a live capturer for the default sink monitor (system output) via PipeWire.
|
|
#[cfg(target_os = "linux")]
|
|
pub fn open_audio_capture() -> Result<Box<dyn AudioCapturer>> {
|
|
linux::PwAudioCapturer::open().map(|c| Box::new(c) as Box<dyn AudioCapturer>)
|
|
}
|
|
|
|
#[cfg(not(target_os = "linux"))]
|
|
pub fn open_audio_capture() -> Result<Box<dyn AudioCapturer>> {
|
|
anyhow::bail!("audio capture requires Linux + PipeWire")
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
mod linux;
|