Skip to main content

snapcast_server/encoder/
pcm.rs

1//! PCM encoder — passthrough with WAV header.
2
3use anyhow::Result;
4use snapcast_proto::SampleFormat;
5
6use super::{EncodedChunk, Encoder};
7
8/// PCM passthrough encoder. Header is a 44-byte WAV header.
9pub struct PcmEncoder {
10    format: SampleFormat,
11    header: Vec<u8>,
12}
13
14impl PcmEncoder {
15    /// Create a new PCM encoder for the given sample format.
16    pub fn new(format: SampleFormat) -> Self {
17        let header = build_wav_header(format);
18        Self { format, header }
19    }
20}
21
22impl Encoder for PcmEncoder {
23    fn name(&self) -> &str {
24        "pcm"
25    }
26
27    fn header(&self) -> &[u8] {
28        &self.header
29    }
30
31    fn encode(&mut self, pcm: &[u8]) -> Result<EncodedChunk> {
32        let frame_size = self.format.frame_size() as usize;
33        let frames = if frame_size > 0 {
34            pcm.len() / frame_size
35        } else {
36            0
37        };
38        let duration_ms = self.format.frames_to_ms(frames);
39        tracing::trace!(codec = "pcm", input_bytes = pcm.len(), frames, "encode");
40        Ok(EncodedChunk {
41            data: pcm.to_vec(),
42            duration_ms,
43        })
44    }
45}
46
47fn build_wav_header(fmt: SampleFormat) -> Vec<u8> {
48    let mut h = vec![0u8; 44];
49    let channels = fmt.channels();
50    let rate = fmt.rate();
51    let bits = fmt.bits();
52    let block_align = channels * bits.div_ceil(8);
53    let byte_rate = rate * block_align as u32;
54
55    h[0..4].copy_from_slice(b"RIFF");
56    h[4..8].copy_from_slice(&36u32.to_le_bytes());
57    h[8..12].copy_from_slice(b"WAVE");
58    h[12..16].copy_from_slice(b"fmt ");
59    h[16..20].copy_from_slice(&16u32.to_le_bytes());
60    h[20..22].copy_from_slice(&1u16.to_le_bytes()); // PCM
61    h[22..24].copy_from_slice(&channels.to_le_bytes());
62    h[24..28].copy_from_slice(&rate.to_le_bytes());
63    h[28..32].copy_from_slice(&byte_rate.to_le_bytes());
64    h[32..34].copy_from_slice(&block_align.to_le_bytes());
65    h[34..36].copy_from_slice(&bits.to_le_bytes());
66    h[36..40].copy_from_slice(b"data");
67    h[40..44].copy_from_slice(&0u32.to_le_bytes());
68    h
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn pcm_passthrough() {
77        let fmt = SampleFormat::new(48000, 16, 2);
78        let mut enc = PcmEncoder::new(fmt);
79        assert_eq!(enc.name(), "pcm");
80        assert_eq!(enc.header().len(), 44);
81        assert_eq!(&enc.header()[0..4], b"RIFF");
82
83        let pcm = vec![0u8; 960 * 4]; // 960 frames, 4 bytes/frame
84        let result = enc.encode(&pcm).unwrap();
85        assert_eq!(result.data.len(), pcm.len());
86        assert!((result.duration_ms - 20.0).abs() < 0.01);
87    }
88}