use std::io::Cursor;
use std::num::{NonZeroU8, NonZeroU32};
use anyhow::{Result, bail};
use snapcast_proto::SampleFormat;
use vorbis_rs::VorbisEncoderBuilder;
use super::{EncodedChunk, Encoder};
pub struct VorbisEncoder {
format: SampleFormat,
header: Vec<u8>,
}
impl VorbisEncoder {
pub fn new(format: SampleFormat, _options: &str) -> Result<Self> {
let mut header_buf = Cursor::new(Vec::new());
let channels = NonZeroU8::new(format.channels() as u8)
.ok_or_else(|| anyhow::anyhow!("channels must be > 0"))?;
let rate = NonZeroU32::new(format.rate())
.ok_or_else(|| anyhow::anyhow!("sample rate must be > 0"))?;
let enc = VorbisEncoderBuilder::new(rate, channels, &mut header_buf)?.build()?;
enc.finish()?;
let header = header_buf.into_inner();
Ok(Self { format, header })
}
}
impl Encoder for VorbisEncoder {
fn name(&self) -> &str {
"ogg"
}
fn header(&self) -> &[u8] {
&self.header
}
fn encode(&mut self, pcm: &[u8]) -> Result<EncodedChunk> {
let channels = self.format.channels() as usize;
let sample_size = self.format.sample_size() as usize;
let total_samples = pcm.len() / sample_size;
let frames = total_samples / channels;
tracing::trace!(codec = "vorbis", input_bytes = pcm.len(), frames, "encode");
if channels == 0 {
tracing::warn!(codec = "vorbis", "channels must be > 0");
bail!("channels must be > 0");
}
let scale = match sample_size {
1 => 1.0 / 128.0,
2 => 1.0 / 32768.0,
4 => 1.0 / 2_147_483_648.0,
_ => {
tracing::warn!(codec = "vorbis", sample_size, "unsupported sample size");
bail!("unsupported sample size: {sample_size}");
}
};
let mut channel_bufs: Vec<Vec<f32>> = vec![Vec::with_capacity(frames); channels];
match sample_size {
2 => {
for (i, chunk) in pcm.chunks_exact(2).enumerate() {
let s = i16::from_le_bytes([chunk[0], chunk[1]]) as f32 * scale;
channel_bufs[i % channels].push(s);
}
}
4 => {
for (i, chunk) in pcm.chunks_exact(4).enumerate() {
let s =
i32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]) as f32 * scale;
channel_bufs[i % channels].push(s);
}
}
_ => {
for (i, &b) in pcm.iter().enumerate() {
channel_bufs[i % channels].push(b as i8 as f32 * scale);
}
}
}
let channel_refs: Vec<&[f32]> = channel_bufs.iter().map(|v| v.as_slice()).collect();
let mut output = Cursor::new(Vec::new());
let ch = NonZeroU8::new(channels as u8).ok_or_else(|| anyhow::anyhow!("zero channels"))?;
let rate = NonZeroU32::new(self.format.rate())
.ok_or_else(|| anyhow::anyhow!("zero sample rate"))?;
let mut enc = VorbisEncoderBuilder::new(rate, ch, &mut output)?.build()?;
enc.encode_audio_block(channel_refs)?;
enc.finish()?;
let duration_ms = self.format.frames_to_ms(frames);
Ok(EncodedChunk {
data: output.into_inner(),
duration_ms,
})
}
}