NetDesk 1.5.0 — source release
This commit is contained in:
commit
ec4955682f
227 files changed
+80669
No files matched your search
@@ -0,0 +1,94 @@
|
||||
//! Remote audio playback — "Play remote computer sound".
|
||||
//!
|
||||
//! The host streams its system audio as Opus (CHAN_AUDIO `0xa1`), one 20 ms
|
||||
//! frame at 48 kHz stereo per packet — exactly what the web viewer decodes with
|
||||
//! WebCodecs. Here we decode each Opus packet with libopus (via the vendored
|
||||
//! `netdesk-opus` crate) and play the PCM through the default output device with
|
||||
//! `cpal` (WASAPI on Windows). A short ring buffer bridges the decode side (net
|
||||
//! thread) and the audio callback (cpal's own thread) and caps latency.
|
||||
//!
|
||||
//! Developed by Akronlogic.
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||
use netdesk_opus::Decoder;
|
||||
|
||||
const SR: u32 = 48_000;
|
||||
const CH: usize = 2;
|
||||
|
||||
/// Decodes the host's Opus audio and plays it. Keep the instance alive for the
|
||||
/// whole session — dropping it stops the output stream.
|
||||
pub struct AudioPlayer {
|
||||
_stream: cpal::Stream, // keep alive: dropping stops playback
|
||||
decoder: Decoder,
|
||||
ring: Arc<Mutex<VecDeque<f32>>>,
|
||||
}
|
||||
|
||||
impl AudioPlayer {
|
||||
/// Open the default output device at 48 kHz stereo and start playing silence.
|
||||
pub fn new() -> anyhow::Result<Self> {
|
||||
let host = cpal::default_host();
|
||||
let device = host
|
||||
.default_output_device()
|
||||
.ok_or_else(|| anyhow::anyhow!("no default audio output device"))?;
|
||||
|
||||
let config = cpal::StreamConfig {
|
||||
channels: CH as u16,
|
||||
sample_rate: cpal::SampleRate(SR),
|
||||
buffer_size: cpal::BufferSize::Default,
|
||||
};
|
||||
|
||||
let ring: Arc<Mutex<VecDeque<f32>>> =
|
||||
Arc::new(Mutex::new(VecDeque::with_capacity(SR as usize)));
|
||||
let ring_cb = ring.clone();
|
||||
|
||||
let stream = device.build_output_stream(
|
||||
&config,
|
||||
move |out: &mut [f32], _: &cpal::OutputCallbackInfo| match ring_cb.lock() {
|
||||
Ok(mut r) => {
|
||||
for s in out.iter_mut() {
|
||||
*s = r.pop_front().unwrap_or(0.0);
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
for s in out.iter_mut() {
|
||||
*s = 0.0;
|
||||
}
|
||||
}
|
||||
},
|
||||
move |err| eprintln!("[audio] output stream error: {err}"),
|
||||
None,
|
||||
)?;
|
||||
stream.play()?;
|
||||
|
||||
let decoder = Decoder::new(SR, CH as u32)?;
|
||||
Ok(Self {
|
||||
_stream: stream,
|
||||
decoder,
|
||||
ring,
|
||||
})
|
||||
}
|
||||
|
||||
/// Decode one Opus packet and enqueue the resulting PCM for playback.
|
||||
/// Silently drops malformed/undecodable packets (best-effort, like the web
|
||||
/// viewer).
|
||||
pub fn push_opus(&mut self, data: &[u8]) {
|
||||
let pcm = match self.decoder.decode(data) {
|
||||
Ok(p) => p, // interleaved i16, stereo
|
||||
Err(_) => return,
|
||||
};
|
||||
if let Ok(mut r) = self.ring.lock() {
|
||||
for &s in &pcm {
|
||||
r.push_back(s as f32 / 32768.0);
|
||||
}
|
||||
// Cap buffered audio to ~400 ms so playback can't drift far behind
|
||||
// the video (drop the oldest samples on overrun).
|
||||
let cap = SR as usize * CH * 2 / 5;
|
||||
while r.len() > cap {
|
||||
r.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user