teamtalk 6.0.0

TeamTalk SDK for Rust
Documentation
use super::*;

pub(super) struct AudioBlockGuard<'a> {
    client: &'a Client,
    ptr: *mut ffi::AudioBlock,
}

impl<'a> AudioBlockGuard<'a> {
    pub(super) fn new(client: &'a Client, ptr: *mut ffi::AudioBlock) -> Self {
        Self { client, ptr }
    }

    pub(super) fn ptr(&self) -> *mut ffi::AudioBlock {
        self.ptr
    }
}

impl Drop for AudioBlockGuard<'_> {
    fn drop(&mut self) {
        unsafe {
            let _ = self.client.release_user_audio_block(self.ptr);
        }
    }
}

pub(super) struct UserTrack {
    writer: TrackWriter,
    samples_written: u64,
    sample_rate: Option<i32>,
    channels: Option<i32>,
}

impl UserTrack {
    pub(super) fn new(
        folder: &str,
        file_vars: &str,
        format: RecordingSampleFormat,
        user_id: UserId,
        user: Option<User>,
        default_sample_rate: Option<i32>,
        default_channels: Option<i32>,
    ) -> Result<Self> {
        let username = user
            .map(|u| u.username)
            .unwrap_or_else(|| "unknown".to_string());
        let filename = sanitized_filename(render_vars(file_vars, user_id, &username));
        let path = Path::new(folder).join(filename);
        let mut writer = TrackWriter::new(path, format)?;
        let sample_rate = default_sample_rate;
        let channels = default_channels;
        if let (Some(rate), Some(ch)) = (sample_rate, channels) {
            writer.init(rate, ch)?;
        }
        Ok(Self {
            writer,
            samples_written: 0,
            sample_rate,
            channels,
        })
    }

    pub(super) fn ensure_format(&mut self, sample_rate: i32, channels: i32) -> Result<()> {
        if self.sample_rate.is_none() {
            self.sample_rate = Some(sample_rate);
            self.channels = Some(channels);
            self.writer.init(sample_rate, channels)?;
        }
        Ok(())
    }

    pub(super) fn pad_to(&mut self, elapsed: Duration) -> Result<()> {
        let sample_rate = match self.sample_rate {
            Some(rate) => rate,
            None => return Ok(()),
        };
        let channels = self.channels.unwrap_or(1) as u64;
        let target_samples = (elapsed.as_secs_f64() * sample_rate as f64) as u64;
        if target_samples > self.samples_written {
            let missing = target_samples - self.samples_written;
            self.writer.write_silence(missing, channels)?;
            self.samples_written = target_samples;
        }
        Ok(())
    }

    pub(super) fn write_block(&mut self, block: &AudioBlockView<'_>) -> Result<()> {
        self.writer.write_pcm(block.data)?;
        self.samples_written = self.samples_written.saturating_add(block.samples as u64);
        Ok(())
    }
}

enum TrackWriter {
    Pcm(File),
    Wav(WavWriter),
}

impl TrackWriter {
    fn new(path: PathBuf, format: RecordingSampleFormat) -> Result<Self> {
        let path = unique_recording_path(&path);
        let file = OpenOptions::new()
            .create_new(true)
            .write(true)
            .open(path)
            .map_err(|e| Error::IoError {
                message: e.to_string(),
            })?;
        Ok(match format {
            RecordingSampleFormat::PcmS16Le => TrackWriter::Pcm(file),
            RecordingSampleFormat::WavS16Le => TrackWriter::Wav(WavWriter::new(file)),
        })
    }

    fn init(&mut self, sample_rate: i32, channels: i32) -> Result<()> {
        match self {
            TrackWriter::Pcm(_) => Ok(()),
            TrackWriter::Wav(writer) => writer.init(sample_rate, channels),
        }
    }

    fn write_pcm(&mut self, data: &[i16]) -> Result<()> {
        let bytes = unsafe {
            std::slice::from_raw_parts(data.as_ptr() as *const u8, std::mem::size_of_val(data))
        };
        match self {
            TrackWriter::Pcm(file) => file.write_all(bytes).map_err(|e| Error::IoError {
                message: e.to_string(),
            }),
            TrackWriter::Wav(writer) => writer.write(bytes),
        }
    }

    fn write_silence(&mut self, samples: u64, channels: u64) -> Result<()> {
        const SILENCE_CHUNK_SAMPLES: usize = 48_000;
        let mut remaining = samples.saturating_mul(channels);
        while remaining > 0 {
            let chunk = remaining.min(SILENCE_CHUNK_SAMPLES as u64) as usize;
            let buf = vec![0i16; chunk];
            self.write_pcm(&buf)?;
            remaining -= chunk as u64;
        }
        Ok(())
    }
}

struct WavWriter {
    file: File,
    data_bytes: u32,
    sample_rate: i32,
    channels: i32,
}

impl WavWriter {
    fn new(file: File) -> Self {
        Self {
            file,
            data_bytes: 0,
            sample_rate: 0,
            channels: 0,
        }
    }

    fn init(&mut self, sample_rate: i32, channels: i32) -> Result<()> {
        self.sample_rate = sample_rate;
        self.channels = channels;
        self.write_header(0)
    }

    fn write(&mut self, data: &[u8]) -> Result<()> {
        self.file.write_all(data).map_err(|e| Error::IoError {
            message: e.to_string(),
        })?;
        self.data_bytes = self.data_bytes.saturating_add(data.len() as u32);
        Ok(())
    }

    fn write_header(&mut self, data_bytes: u32) -> Result<()> {
        let byte_rate = self.sample_rate as u32 * self.channels as u32 * 2;
        let block_align = self.channels as u16 * 2;
        let mut header = Vec::with_capacity(44);
        header.extend_from_slice(b"RIFF");
        header.extend_from_slice(&(36 + data_bytes).to_le_bytes());
        header.extend_from_slice(b"WAVEfmt ");
        header.extend_from_slice(&(16u32).to_le_bytes());
        header.extend_from_slice(&(1u16).to_le_bytes());
        header.extend_from_slice(&(self.channels as u16).to_le_bytes());
        header.extend_from_slice(&(self.sample_rate as u32).to_le_bytes());
        header.extend_from_slice(&byte_rate.to_le_bytes());
        header.extend_from_slice(&block_align.to_le_bytes());
        header.extend_from_slice(&(16u16).to_le_bytes());
        header.extend_from_slice(b"data");
        header.extend_from_slice(&data_bytes.to_le_bytes());
        self.file.write_all(&header).map_err(|e| Error::IoError {
            message: e.to_string(),
        })
    }
}

impl Drop for WavWriter {
    fn drop(&mut self) {
        let _ = self.file.seek(SeekFrom::Start(0));
        let _ = self.write_header(self.data_bytes);
    }
}