Skip to main content

snapcast_server/encoder/
mod.rs

1//! Audio encoders — PCM, FLAC, Opus, Vorbis.
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
16/// Result of encoding a PCM chunk.
17pub struct EncodedChunk {
18    /// Encoded audio data.
19    pub data: Vec<u8>,
20    /// Duration of the encoded audio in milliseconds.
21    pub duration_ms: f64,
22}
23
24/// Trait for audio encoders.
25pub trait Encoder {
26    /// Codec name (e.g. "flac", "pcm", "opus", "ogg").
27    fn name(&self) -> &str;
28
29    /// Codec header bytes sent to clients before audio data.
30    fn header(&self) -> &[u8];
31
32    /// Encode a PCM chunk. Returns encoded data + duration.
33    fn encode(&mut self, pcm: &[u8]) -> Result<EncodedChunk>;
34}
35
36/// Configuration for creating an encoder.
37#[derive(Debug, Clone)]
38pub struct EncoderConfig {
39    /// Codec name: "pcm", "flac", "opus", "ogg", "f32lz4".
40    pub codec: String,
41    /// Audio sample format.
42    pub format: SampleFormat,
43    /// Codec-specific options (e.g. FLAC compression level).
44    pub options: String,
45    /// Pre-shared key for f32lz4 encryption. `None` = no encryption.
46    #[cfg(feature = "encryption")]
47    pub encryption_psk: Option<String>,
48}
49
50impl EncoderConfig {
51    /// Create a minimal config for the given codec and format.
52    pub fn new(codec: &str, format: SampleFormat) -> Self {
53        Self {
54            codec: codec.into(),
55            format,
56            options: String::new(),
57            #[cfg(feature = "encryption")]
58            encryption_psk: None,
59        }
60    }
61}
62
63/// Create an encoder from config.
64pub fn create(config: &EncoderConfig) -> Result<Box<dyn Encoder>> {
65    #[allow(unused_variables)]
66    let EncoderConfig {
67        codec,
68        format,
69        options,
70        ..
71    } = config;
72    let format = *format;
73    match codec.as_str() {
74        "pcm" => Ok(Box::new(pcm::PcmEncoder::new(format))),
75        #[cfg(feature = "flac")]
76        "flac" => Ok(Box::new(flac::FlacEncoder::new(format, options)?)),
77        #[cfg(feature = "opus")]
78        "opus" => Ok(Box::new(opus::OpusEncoder::new(format, options)?)),
79        #[cfg(feature = "vorbis")]
80        "ogg" => Ok(Box::new(vorbis::VorbisEncoder::new(format, options)?)),
81        #[cfg(feature = "f32lz4")]
82        "f32lz4" => {
83            let enc = f32lz4::F32Lz4Encoder::new(format);
84            #[cfg(feature = "encryption")]
85            let enc = if let Some(ref key) = config.encryption_psk {
86                enc.with_encryption(key)
87            } else {
88                enc
89            };
90            Ok(Box::new(enc))
91        }
92        other => anyhow::bail!("unsupported codec: {other} (check enabled features)"),
93    }
94}