snapcast_client/decoder/
opus.rs1use anyhow::{Result, bail};
10use opus_decoder::OpusDecoder as OpusDec;
11use snapcast_proto::SampleFormat;
12use snapcast_proto::message::codec_header::CodecHeader;
13
14use crate::decoder::Decoder;
15
16const OPUS_ID: u32 = 0x4F50_5553;
17const MAX_FRAME_SIZE: usize = 2880;
18
19fn parse_opus_header(payload: &[u8]) -> Result<SampleFormat> {
21 if payload.len() < 12 {
22 bail!(
23 "Opus header too small ({} bytes, need >= 12)",
24 payload.len()
25 );
26 }
27 let id = u32::from_le_bytes(payload[0..4].try_into().unwrap());
28 if id != OPUS_ID {
29 bail!("not an Opus header (expected 0x{OPUS_ID:08X}, got 0x{id:08X})");
30 }
31 let rate = u32::from_le_bytes(payload[4..8].try_into().unwrap());
32 let bits = u16::from_le_bytes(payload[8..10].try_into().unwrap());
33 let channels = u16::from_le_bytes(payload[10..12].try_into().unwrap());
34 Ok(SampleFormat::new(rate, bits, channels))
35}
36
37pub struct OpusDecoder {
39 decoder: OpusDec,
40 sample_format: SampleFormat,
41 pcm_buf: Vec<i16>,
42}
43
44impl Decoder for OpusDecoder {
45 fn set_header(&mut self, header: &CodecHeader) -> Result<SampleFormat> {
46 let new = create(header)?;
47 *self = new;
48 Ok(self.sample_format)
49 }
50
51 fn decode(&mut self, data: &mut Vec<u8>) -> Result<bool> {
52 if data.is_empty() {
53 return Ok(false);
54 }
55 tracing::trace!(codec = "opus", input_bytes = data.len(), "decode");
56 let decoded_samples = match self
57 .decoder
58 .decode(data.as_slice(), &mut self.pcm_buf, false)
59 {
60 Ok(n) => n,
61 Err(e) => {
62 tracing::warn!(codec = "opus", error = %e, "decode failed");
63 return Ok(false);
64 }
65 };
66 let total_samples = decoded_samples * self.sample_format.channels() as usize;
67 let mut out = Vec::with_capacity(total_samples * 2);
68 for &s in &self.pcm_buf[..total_samples] {
69 out.extend_from_slice(&s.to_le_bytes());
70 }
71 *data = out;
72 Ok(true)
73 }
74}
75
76pub fn create(header: &CodecHeader) -> Result<OpusDecoder> {
78 let sf = parse_opus_header(&header.payload)?;
79 let dec = OpusDec::new(sf.rate(), sf.channels() as usize)
80 .map_err(|e| anyhow::anyhow!("failed to create Opus decoder: {e}"))?;
81 Ok(OpusDecoder {
82 decoder: dec,
83 sample_format: sf,
84 pcm_buf: vec![0i16; MAX_FRAME_SIZE * sf.channels() as usize],
85 })
86}
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 fn opus_header(rate: u32, bits: u16, channels: u16) -> Vec<u8> {
92 let mut h = Vec::new();
93 h.extend_from_slice(&OPUS_ID.to_le_bytes());
94 h.extend_from_slice(&rate.to_le_bytes());
95 h.extend_from_slice(&bits.to_le_bytes());
96 h.extend_from_slice(&channels.to_le_bytes());
97 h
98 }
99
100 #[test]
101 fn parse_header_48000_16_2() {
102 let sf = parse_opus_header(&opus_header(48000, 16, 2)).unwrap();
103 assert_eq!(sf.rate(), 48000);
104 assert_eq!(sf.bits(), 16);
105 assert_eq!(sf.channels(), 2);
106 }
107
108 #[test]
109 fn parse_header_too_small() {
110 assert!(parse_opus_header(&[0; 8]).is_err());
111 }
112
113 #[test]
114 fn parse_header_bad_magic() {
115 let mut h = opus_header(48000, 16, 2);
116 h[0] = 0xFF;
117 assert!(parse_opus_header(&h).is_err());
118 }
119
120 #[test]
121 fn create_decoder_48000_16_2() {
122 let header = CodecHeader {
123 codec: "opus".into(),
124 payload: opus_header(48000, 16, 2),
125 };
126 let dec = create(&header);
127 assert!(dec.is_ok());
128 }
129}