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};
use crate::AudioData;
pub struct VorbisEncoder {
format: SampleFormat,
header: Vec<u8>,
warned: bool,
}
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,
warned: false,
})
}
}
impl Encoder for VorbisEncoder {
fn name(&self) -> &str {
"ogg"
}
fn header(&self) -> &[u8] {
&self.header
}
fn encode(&mut self, input: &AudioData) -> Result<EncodedChunk> {
let channels = self.format.channels() as usize;
if channels == 0 {
bail!("channels must be > 0");
}
let channel_bufs: Vec<Vec<f32>> = match input {
AudioData::F32(samples) => {
let frames = samples.len() / channels;
let mut bufs = vec![Vec::with_capacity(frames); channels];
for (i, &s) in samples.iter().enumerate() {
bufs[i % channels].push(s);
}
bufs
}
AudioData::Pcm(pcm) => {
if !self.warned {
self.warned = true;
tracing::warn!(
codec = "vorbis",
"PCM input requires scaling to f32 — consider sending F32 directly"
);
}
let sample_size = self.format.sample_size() as usize;
let total_samples = pcm.len() / sample_size;
let frames = total_samples / channels;
let mut bufs = vec![Vec::with_capacity(frames); channels];
let scale = match sample_size {
2 => 1.0 / 32768.0,
4 => 1.0 / 2_147_483_648.0,
_ => bail!("unsupported sample size: {sample_size}"),
};
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;
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;
bufs[i % channels].push(s);
}
}
_ => unreachable!(),
}
bufs
}
};
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()?;
Ok(EncodedChunk {
data: output.into_inner(),
})
}
}