use anyhow::Result;
use snapcast_proto::SampleFormat;
use super::{EncodedChunk, Encoder};
use crate::AudioData;
pub struct PcmEncoder {
format: SampleFormat,
header: Vec<u8>,
warned: bool,
}
impl PcmEncoder {
pub fn new(format: SampleFormat) -> Self {
let header = build_wav_header(format);
Self {
format,
header,
warned: false,
}
}
}
impl Encoder for PcmEncoder {
fn name(&self) -> &str {
"pcm"
}
fn header(&self) -> &[u8] {
&self.header
}
fn encode(&mut self, input: &AudioData) -> Result<EncodedChunk> {
let data = match input {
AudioData::Pcm(pcm) => pcm.clone(),
AudioData::F32(samples) => {
if !self.warned {
self.warned = true;
tracing::warn!(
codec = "pcm",
bits = self.format.bits(),
"F32 input → {}-bit PCM quantization",
self.format.bits()
);
}
super::f32_to_pcm(samples, self.format.bits())
}
};
Ok(EncodedChunk { data })
}
}
fn build_wav_header(fmt: SampleFormat) -> Vec<u8> {
let mut h = vec![0u8; 44];
let channels = fmt.channels();
let rate = fmt.rate();
let bits = fmt.bits();
let block_align = channels * bits.div_ceil(8);
let byte_rate = rate * block_align as u32;
h[0..4].copy_from_slice(b"RIFF");
h[4..8].copy_from_slice(&36u32.to_le_bytes());
h[8..12].copy_from_slice(b"WAVE");
h[12..16].copy_from_slice(b"fmt ");
h[16..20].copy_from_slice(&16u32.to_le_bytes());
h[20..22].copy_from_slice(&1u16.to_le_bytes()); h[22..24].copy_from_slice(&channels.to_le_bytes());
h[24..28].copy_from_slice(&rate.to_le_bytes());
h[28..32].copy_from_slice(&byte_rate.to_le_bytes());
h[32..34].copy_from_slice(&block_align.to_le_bytes());
h[34..36].copy_from_slice(&bits.to_le_bytes());
h[36..40].copy_from_slice(b"data");
h[40..44].copy_from_slice(&0u32.to_le_bytes());
h
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pcm_passthrough() {
let fmt = SampleFormat::new(48000, 16, 2);
let mut enc = PcmEncoder::new(fmt);
assert_eq!(enc.name(), "pcm");
assert_eq!(enc.header().len(), 44);
assert_eq!(&enc.header()[0..4], b"RIFF");
let pcm = vec![0u8; 960 * 4];
let result = enc.encode(&AudioData::Pcm(pcm.clone())).unwrap();
assert_eq!(result.data.len(), pcm.len());
}
#[test]
fn f32_converts_to_pcm() {
let fmt = SampleFormat::new(48000, 16, 2);
let mut enc = PcmEncoder::new(fmt);
let samples = vec![0.0f32; 960];
let result = enc.encode(&AudioData::F32(samples)).unwrap();
assert_eq!(result.data.len(), 960 * 2); }
}