steam_audio_codec/
lib.rs

1pub use crate::error::SteamAudioError;
2use opus::{Channels, Decoder};
3use std::fmt::Debug;
4
5mod error;
6
7#[derive(Debug)]
8#[repr(u8)]
9enum PacketType {
10    Silence = 0,
11    OpusPlc = 6,
12    SampleRate = 11,
13}
14
15impl TryFrom<u8> for PacketType {
16    type Error = SteamAudioError;
17
18    fn try_from(value: u8) -> Result<Self, Self::Error> {
19        match value {
20            0 => Ok(Self::Silence),
21            6 => Ok(Self::OpusPlc),
22            11 => Ok(Self::SampleRate),
23            _ => Err(SteamAudioError::UnknownPacketType { ty: value }),
24        }
25    }
26}
27
28fn read_bytes<const N: usize>(data: &[u8]) -> Result<([u8; N], &[u8]), SteamAudioError> {
29    if data.len() < N {
30        Err(SteamAudioError::InsufficientData)
31    } else {
32        let (result, rest) = data.split_at(N);
33        Ok((result.try_into().unwrap(), rest))
34    }
35}
36
37fn read_u16(data: &[u8]) -> Result<(u16, &[u8]), SteamAudioError> {
38    let (bytes, data) = read_bytes(data)?;
39    Ok((u16::from_le_bytes(bytes), data))
40}
41
42#[derive(Debug)]
43pub enum Packet<'a> {
44    /// A number of samples of silence
45    Silence(u16),
46    /// Opus PLC data
47    OpusPlc(SteamOpusData<'a>),
48    /// The sample rate for the opus packets
49    SampleRate(u16),
50}
51
52impl<'a> Packet<'a> {
53    pub fn read(data: &'a [u8]) -> Result<(Self, &'a [u8]), SteamAudioError> {
54        let ty = PacketType::try_from(*data.first().ok_or(SteamAudioError::InsufficientData)?)?;
55        let data = &data[1..];
56
57        let (next, data) = read_u16(data)?;
58
59        Ok(match ty {
60            PacketType::Silence => (Packet::Silence(next), data),
61            PacketType::OpusPlc => {
62                if data.len() < next as usize {
63                    return Err(SteamAudioError::InsufficientData);
64                } else {
65                    let (result, data) = data.split_at(next as usize);
66                    (Packet::OpusPlc(SteamOpusData { data: result }), data)
67                }
68            }
69            PacketType::SampleRate => (Packet::SampleRate(next), data),
70        })
71    }
72}
73
74#[derive(Debug)]
75pub struct SteamVoiceData<'a> {
76    pub steam_id: u64,
77    packet_data: &'a [u8],
78}
79
80impl<'a> SteamVoiceData<'a> {
81    pub fn new(data: &'a [u8]) -> Result<Self, SteamAudioError> {
82        let (data, crc_data) = data.split_at(data.len() - 4);
83        let expected_crc = u32::from_le_bytes(crc_data.try_into().unwrap());
84        let calculated_crc = crc32b(data);
85        if expected_crc != calculated_crc {
86            return Err(SteamAudioError::CrcMismatch {
87                actual: calculated_crc,
88                expected: expected_crc,
89            });
90        }
91
92        let (steam_id_bytes, data) = read_bytes(data)?;
93        let steam_id = u64::from_le_bytes(steam_id_bytes);
94        Ok(SteamVoiceData {
95            steam_id,
96            packet_data: data,
97        })
98    }
99
100    /// Get the voice
101    pub fn packets(&self) -> impl Iterator<Item = Result<Packet<'a>, SteamAudioError>> {
102        SteamPacketIterator {
103            data: self.packet_data,
104        }
105    }
106}
107
108struct SteamPacketIterator<'a> {
109    data: &'a [u8],
110}
111
112impl Debug for SteamPacketIterator<'_> {
113    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114        f.debug_struct("SteamPacketIterator")
115            .field("data_length", &self.data.len())
116            .finish_non_exhaustive()
117    }
118}
119
120impl<'a> Iterator for SteamPacketIterator<'a> {
121    type Item = Result<Packet<'a>, SteamAudioError>;
122
123    fn next(&mut self) -> Option<Self::Item> {
124        if self.data.is_empty() {
125            None
126        } else {
127            match Packet::read(self.data) {
128                Ok((packet, rest)) => {
129                    self.data = rest;
130                    Some(Ok(packet))
131                }
132                Err(e) => Some(Err(e)),
133            }
134        }
135    }
136}
137
138fn crc32b(data: &[u8]) -> u32 {
139    let mut crc: u32 = 0xFFFFFFFF;
140    for &byte in data {
141        crc ^= byte as u32;
142        for _ in 0..8 {
143            let mask = (-((crc & 1) as i32)) as u32;
144            crc = (crc >> 1) ^ (0xEDB88320 & mask);
145        }
146    }
147    !crc
148}
149
150#[derive(Default)]
151pub struct SteamVoiceDecoder {
152    decoder: Option<Decoder>,
153    sample_rate: u16,
154    seq: u16,
155}
156
157pub struct SteamOpusData<'a> {
158    data: &'a [u8],
159}
160
161impl Debug for SteamOpusData<'_> {
162    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163        f.debug_struct("SteamOpusData")
164            .field("data_length", &self.data.len())
165            .finish_non_exhaustive()
166    }
167}
168
169impl SteamVoiceDecoder {
170    pub fn new() -> Self {
171        Self::default()
172    }
173
174    pub fn decode(
175        &mut self,
176        voice_data: SteamVoiceData,
177        output_buffer: &mut [i16],
178    ) -> Result<usize, SteamAudioError> {
179        let mut total = 0;
180        for packet in voice_data.packets() {
181            let packet = packet?;
182            match packet {
183                Packet::SampleRate(rate) => {
184                    if self.sample_rate != rate {
185                        self.decoder = Some(Decoder::new(rate as u32, Channels::Mono)?);
186                        self.sample_rate = rate;
187                    }
188                }
189                Packet::OpusPlc(opus) => {
190                    let count = self.decode_opus(opus.data, &mut output_buffer[total..])?;
191                    total += count;
192                    if total >= output_buffer.len() {
193                        return Err(SteamAudioError::InsufficientOutputBuffer);
194                    }
195                }
196                Packet::Silence(silence) => {
197                    total += silence as usize;
198                }
199            }
200        }
201        Ok(total)
202    }
203
204    fn decode_opus(
205        &mut self,
206        mut data: &[u8],
207        output_buffer: &mut [i16],
208    ) -> Result<usize, SteamAudioError> {
209        let mut total = 0;
210        let Some(decoder) = self.decoder.as_mut() else {
211            return Err(SteamAudioError::NoSampleRate);
212        };
213
214        while data.len() > 2 {
215            let (len, remainder) = read_u16(data)?;
216            data = remainder;
217            if len == u16::MAX {
218                decoder.reset_state()?;
219                self.seq = 0;
220                continue;
221            }
222            let (seq, remainder) = read_u16(data)?;
223            data = remainder;
224
225            if seq < self.seq {
226                decoder.reset_state()?;
227            } else {
228                let lost = seq - self.seq;
229                for _ in 0..lost {
230                    let count = decoder.decode(&[], &mut output_buffer[total..], false)?;
231                    total += count;
232                    if total >= output_buffer.len() {
233                        return Err(SteamAudioError::InsufficientOutputBuffer);
234                    }
235                }
236            }
237            let len = len as usize;
238
239            self.seq = seq + 1;
240
241            if data.len() < len {
242                return Err(SteamAudioError::InsufficientData);
243            }
244
245            let count = decoder.decode(&data[0..len], &mut output_buffer[total..], false)?;
246            data = &data[len..];
247            total += count;
248            if total >= output_buffer.len() {
249                return Err(SteamAudioError::InsufficientOutputBuffer);
250            }
251        }
252
253        Ok(total)
254    }
255}