lavende-core 0.1.0

Core in-process Discord voice connection and playback engine
Documentation
pub mod format {
use crate::common::types::AudioFormat;
pub fn detect_format(header: &[u8]) -> AudioFormat {
    if header.len() < 4 {
        return AudioFormat::Unknown;
    }
    if header.starts_with(&[0x1A, 0x45, 0xDF, 0xA3]) {
        return AudioFormat::Webm;
    }
    if header.len() >= 8 && &header[4..8] == b"ftyp" {
        return AudioFormat::Mp4;
    }
    if header.starts_with(b"OggS") {
        return AudioFormat::Ogg;
    }
    if header.starts_with(b"fLaC") {
        return AudioFormat::Flac;
    }
    if header.starts_with(b"RIFF") && header.len() >= 12 && &header[8..12] == b"WAVE" {
        return AudioFormat::Wav;
    }
    if header.starts_with(b"ID3") {
        return AudioFormat::Mp3;
    }
    if header[0] == 0xFF {
        let b1 = header[1];
        let is_sync = (b1 & 0xE0) == 0xE0;
        let layer = (b1 >> 1) & 0x03;
        if is_sync && layer != 0 {
            return AudioFormat::Mp3;
        }
    }
    AudioFormat::Unknown
}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn detect_webm() {
        let hdr = [0x1A, 0x45, 0xDF, 0xA3, 0x00, 0x00, 0x00, 0x00];
        assert_eq!(detect_format(&hdr), AudioFormat::Webm);
    }
    #[test]
    fn detect_mp4() {
        let hdr = b"\x00\x00\x00\x1Cftypisom";
        assert_eq!(detect_format(hdr), AudioFormat::Mp4);
    }
    #[test]
    fn detect_ogg() {
        assert_eq!(detect_format(b"OggS\x00"), AudioFormat::Ogg);
    }
    #[test]
    fn detect_unknown() {
        assert_eq!(
            detect_format(&[0x00, 0x00, 0x00, 0x00]),
            AudioFormat::Unknown
        );
    }
    #[test]
    fn adts_not_mistaken_for_mp3() {
        let adts = [0xFF, 0xF1, 0x50, 0x80];
        assert_eq!(detect_format(&adts), AudioFormat::Unknown);
    }
    #[test]
    fn mp3_sync_word() {
        let mp3 = [0xFF, 0xFB, 0x90, 0x00];
        assert_eq!(detect_format(&mp3), AudioFormat::Mp3);
    }
}
}
pub mod webm_opus {
use symphonia::core::{
    codecs::CODEC_TYPE_OPUS,
    errors::Error,
    formats::{FormatOptions, FormatReader},
    io::{MediaSource, MediaSourceStream},
    meta::MetadataOptions,
    probe::Hint,
};
pub struct WebmOpusDemuxer {
    format: Box<dyn FormatReader>,
    track_id: u32,
}
impl WebmOpusDemuxer {
    pub fn open(source: Box<dyn MediaSource>) -> Result<Option<Self>, Error> {
        let mss = MediaSourceStream::new(source, Default::default());
        let mut hint = Hint::new();
        hint.with_extension("webm");
        let probed = symphonia::default::get_probe().format(
            &hint,
            mss,
            &FormatOptions::default(),
            &MetadataOptions::default(),
        )?;
        let format = probed.format;
        let track_id = format
            .tracks()
            .iter()
            .find(|t| t.codec_params.codec == CODEC_TYPE_OPUS)
            .map(|t| t.id);
        Ok(track_id.map(|track_id| Self { format, track_id }))
    }
    pub fn next_packet(&mut self) -> Result<Option<Vec<u8>>, Error> {
        loop {
            let packet = match self.format.next_packet() {
                Ok(p) => p,
                Err(Error::IoError(e)) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
                    return Ok(None);
                }
                Err(e) => return Err(e),
            };
            if packet.track_id() != self.track_id {
                continue;
            }
            return Ok(Some(packet.data.into_vec()));
        }
    }
}
}
pub use format::detect_format;
use symphonia::core::{
    codecs::{CODEC_TYPE_NULL, Decoder, DecoderOptions},
    errors::Error,
    formats::{FormatOptions, FormatReader},
    io::{MediaSource, MediaSourceStream},
    meta::MetadataOptions,
    probe::Hint,
};
pub use webm_opus::WebmOpusDemuxer;
use crate::audio::constants::{MIXER_CHANNELS, TARGET_SAMPLE_RATE};
pub use crate::common::types::AudioFormat;
pub enum DemuxResult {
    Transcode {
        format: Box<dyn FormatReader>,
        track_id: u32,
        decoder: Box<dyn Decoder>,
        sample_rate: u32,
        channels: usize,
    },
}
pub fn open_format(
    source: Box<dyn MediaSource>,
    kind: Option<AudioFormat>,
) -> Result<DemuxResult, Error> {
    let mss = MediaSourceStream::new(source, Default::default());
    let mut hint = Hint::new();
    if let Some(k) = &kind {
        hint.with_extension(k.as_ext());
    }
    let probed = symphonia::default::get_probe().format(
        &hint,
        mss,
        &FormatOptions::default(),
        &MetadataOptions::default(),
    )?;
    let format = probed.format;
    let track = format
        .tracks()
        .iter()
        .find(|t| t.codec_params.codec != CODEC_TYPE_NULL)
        .ok_or_else(|| {
            Error::IoError(std::io::Error::new(
                std::io::ErrorKind::NotFound,
                "no audio track found",
            ))
        })?;
    let track_id = track.id;
    let codec = track.codec_params.codec;
    let sample_rate = track.codec_params.sample_rate.unwrap_or(TARGET_SAMPLE_RATE);
    let channels = track
        .codec_params
        .channels
        .map(|c| c.count())
        .unwrap_or(MIXER_CHANNELS);
    let decoder: Box<dyn Decoder> = if codec == symphonia::core::codecs::CODEC_TYPE_OPUS {
        Box::new(
            crate::audio::codec::opus_decoder::OpusCodecDecoder::try_new(
                &track.codec_params,
                &DecoderOptions::default(),
            )?,
        )
    } else {
        symphonia::default::get_codecs().make(&track.codec_params, &DecoderOptions::default())?
    };
    Ok(DemuxResult::Transcode {
        format,
        track_id,
        decoder,
        sample_rate,
        channels,
    })
}