Skip to main content

snapcast_server/encoder/
mod.rs

1//! Audio encoders — PCM, FLAC, Opus, Vorbis, F32LZ4.
2
3#[cfg(feature = "f32lz4")]
4pub mod f32lz4;
5#[cfg(feature = "flac")]
6pub mod flac;
7#[cfg(feature = "opus")]
8pub mod opus;
9pub mod pcm;
10#[cfg(feature = "vorbis")]
11pub mod vorbis;
12
13use anyhow::Result;
14use snapcast_proto::SampleFormat;
15
16use crate::AudioData;
17
18/// Result of encoding an audio chunk.
19pub(crate) struct EncodedChunk {
20    /// Encoded audio data.
21    pub data: Vec<u8>,
22}
23
24/// Trait for audio encoders.
25///
26/// Each encoder accepts [`AudioData`] (F32 or Pcm) and handles conversion
27/// internally. This keeps format-specific logic in the encoder, not the caller.
28pub(crate) trait Encoder: Send {
29    /// Codec name (e.g. "flac", "pcm", "opus", "ogg", "f32lz4").
30    fn name(&self) -> &str;
31
32    /// Codec header bytes sent to clients before audio data.
33    fn header(&self) -> &[u8];
34
35    /// Encode an audio chunk. Accepts F32 or Pcm input.
36    fn encode(&mut self, input: &AudioData) -> Result<EncodedChunk>;
37}
38
39/// Configuration for creating an encoder.
40#[derive(Debug, Clone)]
41pub(crate) struct EncoderConfig {
42    /// Codec name: "pcm", "flac", "opus", "ogg", "f32lz4".
43    pub codec: String,
44    /// Audio sample format.
45    pub format: SampleFormat,
46    /// Codec-specific options (e.g. FLAC compression level).
47    pub options: String,
48    /// Pre-shared key for f32lz4 encryption. `None` = no encryption.
49    #[cfg(feature = "encryption")]
50    pub encryption_psk: Option<String>,
51}
52
53/// Create an encoder from config.
54pub(crate) fn create(config: &EncoderConfig) -> Result<Box<dyn Encoder>> {
55    #[allow(unused_variables)]
56    let EncoderConfig {
57        codec,
58        format,
59        options,
60        ..
61    } = config;
62    let format = *format;
63    match codec.as_str() {
64        "pcm" => Ok(Box::new(pcm::PcmEncoder::new(format))),
65        #[cfg(feature = "flac")]
66        "flac" => Ok(Box::new(flac::FlacEncoder::new(format, options)?)),
67        #[cfg(feature = "opus")]
68        "opus" => Ok(Box::new(opus::OpusEncoder::new(format, options)?)),
69        #[cfg(feature = "vorbis")]
70        "ogg" => Ok(Box::new(vorbis::VorbisEncoder::new(format, options)?)),
71        #[cfg(feature = "f32lz4")]
72        "f32lz4" => {
73            let enc = f32lz4::F32Lz4Encoder::new(format);
74            #[cfg(feature = "encryption")]
75            let enc = if let Some(ref key) = config.encryption_psk {
76                enc.with_encryption(key)
77            } else {
78                enc
79            };
80            Ok(Box::new(enc))
81        }
82        other => anyhow::bail!("unsupported codec: {other} (check enabled features)"),
83    }
84}
85
86/// Convert f32 samples to PCM bytes at the given bit depth.
87/// Shared helper for encoders that need integer PCM input.
88pub(crate) fn f32_to_pcm(samples: &[f32], bits: u16) -> Vec<u8> {
89    match bits {
90        16 => {
91            let mut buf = Vec::with_capacity(samples.len() * 2);
92            for &s in samples {
93                let i = (s.clamp(-1.0, 1.0) * i16::MAX as f32) as i16;
94                buf.extend_from_slice(&i.to_le_bytes());
95            }
96            buf
97        }
98        24 => {
99            let mut buf = Vec::with_capacity(samples.len() * 3);
100            for &s in samples {
101                let i = (s.clamp(-1.0, 1.0) * 8_388_607.0) as i32;
102                let bytes = i.to_le_bytes();
103                buf.extend_from_slice(&bytes[..3]);
104            }
105            buf
106        }
107        32 => {
108            let mut buf = Vec::with_capacity(samples.len() * 4);
109            for &s in samples {
110                let i = (s.clamp(-1.0, 1.0) * i32::MAX as f32) as i32;
111                buf.extend_from_slice(&i.to_le_bytes());
112            }
113            buf
114        }
115        _ => f32_to_pcm(samples, 16),
116    }
117}
118
119/// Convert PCM bytes to f32 samples at the given bit depth.
120/// Shared helper for encoders that need f32 input.
121#[cfg(feature = "f32lz4")]
122pub(crate) fn pcm_to_f32(pcm: &[u8], bits: u16) -> Vec<f32> {
123    match bits {
124        16 => pcm
125            .chunks_exact(2)
126            .map(|c| i16::from_le_bytes([c[0], c[1]]) as f32 / i16::MAX as f32)
127            .collect(),
128        24 => pcm
129            .chunks_exact(3)
130            .map(|c| {
131                let i =
132                    i32::from_le_bytes([c[0], c[1], c[2], if c[2] & 0x80 != 0 { 0xFF } else { 0 }]);
133                i as f32 / 8_388_607.0
134            })
135            .collect(),
136        32 => pcm
137            .chunks_exact(4)
138            .map(|c| i32::from_le_bytes([c[0], c[1], c[2], c[3]]) as f32 / i32::MAX as f32)
139            .collect(),
140        _ => pcm_to_f32(pcm, 16),
141    }
142}