bevy_talk 0.19.0

Voice chat while in game
Documentation
use bevy_ecs::resource::Resource;
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use std::{
    sync::{
        Arc, Mutex,
        mpsc::{Receiver, SyncSender, sync_channel},
    },
    thread::JoinHandle,
};

#[derive(Resource)]
pub(crate) struct AudioPiper {
    mic: Arc<Mutex<Receiver<Vec<f32>>>>,
    sink: SyncSender<Vec<f32>>,
    _ih: JoinHandle<()>,
    _oh: JoinHandle<()>,
}

const BUF: usize = 100;
const LEN: usize = 600;

impl AudioPiper {
    pub(crate) fn new() -> Self {
        let (mic, ih) = mic_source();
        let (sink, oh) = out_sink();
        Self {
            mic: Arc::new(Mutex::new(mic)),
            sink,
            _ih: ih,
            _oh: oh,
        }
    }

    pub(crate) fn sink(&self, data: &[f32]) {
        for d in data.chunks(LEN) {
            let _ = self.sink.send(d.to_vec());
        }
    }

    pub(crate) fn read(&self) -> Vec<f32> {
        let mut res = vec![];
        let Ok(c) = self.mic.lock() else {
            return res;
        };
        while let Ok(v) = c.try_recv() {
            res.extend_from_slice(&v);
        }
        res
    }
}

fn mic_source() -> (Receiver<Vec<f32>>, JoinHandle<()>) {
    let (tx, rx) = sync_channel(BUF);
    let h = std::thread::spawn(move || {
        let dev = cpal::default_host().default_input_device().unwrap();
        let mut conf = dev
            .supported_input_configs()
            .unwrap()
            .next()
            .unwrap()
            .with_max_sample_rate()
            .config();
        conf.buffer_size = cpal::BufferSize::Fixed(LEN as u32);
        let is = dev
            .build_input_stream(
                &conf,
                move |data: &[f32], _: &cpal::InputCallbackInfo| {
                    let _ = tx.try_send(data.to_vec());
                },
                move |_err| {},
                None,
            )
            .unwrap();
        is.play().unwrap();
        loop {
            std::thread::yield_now();
        }
    });
    (rx, h)
}

fn out_sink() -> (SyncSender<Vec<f32>>, JoinHandle<()>) {
    let (tx, rx) = sync_channel::<Vec<f32>>(BUF);
    let h = std::thread::spawn(move || {
        let output_dev = cpal::default_host().default_output_device().unwrap();
        let mut output_conf = output_dev
            .supported_output_configs()
            .unwrap()
            .next()
            .unwrap()
            .with_max_sample_rate()
            .config();
        output_conf.buffer_size = cpal::BufferSize::Fixed(LEN as u32);
        let os = output_dev
            .build_output_stream(
                &output_conf,
                move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
                    if let Ok(d) = rx.recv() {
                        let l = d.len().min(data.len());
                        data[0..l].copy_from_slice(&d[..l]);
                    }
                },
                move |_err| {},
                None,
            )
            .unwrap();
        os.play().unwrap();
        loop {
            std::thread::yield_now();
        }
    });
    (tx, h)
}

#[cfg(test)]
mod test {
    use std::time::{Duration, Instant};

    use super::*;

    #[test]
    fn test() {
        let start = Instant::now();
        let (input, _is) = mic_source();
        let (output, _os) = out_sink();
        while let Ok(d) = input.recv()
            && start.elapsed() < Duration::from_secs(20)
        {
            let _ = output.send(d);
        }
    }
}