snapcast_server/encoder/
pcm.rs1use anyhow::Result;
4use snapcast_proto::SampleFormat;
5
6use super::{EncodedChunk, Encoder};
7use crate::AudioData;
8
9pub struct PcmEncoder {
11 format: SampleFormat,
12 header: Vec<u8>,
13 warned: bool,
14}
15
16impl PcmEncoder {
17 pub fn new(format: SampleFormat) -> Self {
19 let header = build_wav_header(format);
20 Self {
21 format,
22 header,
23 warned: false,
24 }
25 }
26}
27
28impl Encoder for PcmEncoder {
29 fn name(&self) -> &str {
30 "pcm"
31 }
32
33 fn header(&self) -> &[u8] {
34 &self.header
35 }
36
37 fn encode(&mut self, input: &AudioData) -> Result<EncodedChunk> {
38 let data = match input {
39 AudioData::Pcm(pcm) => pcm.clone(),
40 AudioData::F32(samples) => {
41 if !self.warned {
42 self.warned = true;
43 tracing::warn!(
44 codec = "pcm",
45 bits = self.format.bits(),
46 "F32 input → {}-bit PCM quantization",
47 self.format.bits()
48 );
49 }
50 super::f32_to_pcm(samples, self.format.bits())
51 }
52 };
53 Ok(EncodedChunk { data })
54 }
55}
56
57fn build_wav_header(fmt: SampleFormat) -> Vec<u8> {
58 let mut h = vec![0u8; 44];
59 let channels = fmt.channels();
60 let rate = fmt.rate();
61 let bits = fmt.bits();
62 let block_align = channels * bits.div_ceil(8);
63 let byte_rate = rate * block_align as u32;
64
65 h[0..4].copy_from_slice(b"RIFF");
66 h[4..8].copy_from_slice(&36u32.to_le_bytes());
67 h[8..12].copy_from_slice(b"WAVE");
68 h[12..16].copy_from_slice(b"fmt ");
69 h[16..20].copy_from_slice(&16u32.to_le_bytes());
70 h[20..22].copy_from_slice(&1u16.to_le_bytes()); h[22..24].copy_from_slice(&channels.to_le_bytes());
72 h[24..28].copy_from_slice(&rate.to_le_bytes());
73 h[28..32].copy_from_slice(&byte_rate.to_le_bytes());
74 h[32..34].copy_from_slice(&block_align.to_le_bytes());
75 h[34..36].copy_from_slice(&bits.to_le_bytes());
76 h[36..40].copy_from_slice(b"data");
77 h[40..44].copy_from_slice(&0u32.to_le_bytes());
78 h
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84
85 #[test]
86 fn pcm_passthrough() {
87 let fmt = SampleFormat::new(48000, 16, 2);
88 let mut enc = PcmEncoder::new(fmt);
89 assert_eq!(enc.name(), "pcm");
90 assert_eq!(enc.header().len(), 44);
91 assert_eq!(&enc.header()[0..4], b"RIFF");
92
93 let pcm = vec![0u8; 960 * 4];
94 let result = enc.encode(&AudioData::Pcm(pcm.clone())).unwrap();
95 assert_eq!(result.data.len(), pcm.len());
96 }
97
98 #[test]
99 fn f32_converts_to_pcm() {
100 let fmt = SampleFormat::new(48000, 16, 2);
101 let mut enc = PcmEncoder::new(fmt);
102 let samples = vec![0.0f32; 960];
103 let result = enc.encode(&AudioData::F32(samples)).unwrap();
104 assert_eq!(result.data.len(), 960 * 2); }
106}