steam_audio_codec/
lib.rs

1pub use crate::error::SteamAudioError;
2use std::fmt::Debug;
3
4#[cfg(feature = "opus")]
5mod decoder;
6mod error;
7#[cfg(feature = "opus")]
8pub use decoder::SteamVoiceDecoder;
9
10/// The voice packet types seen in the voice data
11#[derive(Debug)]
12#[repr(u8)]
13enum PacketType {
14    Silence = 0,
15    OpusPlc = 6,
16    SampleRate = 11,
17}
18
19impl TryFrom<u8> for PacketType {
20    type Error = SteamAudioError;
21
22    fn try_from(value: u8) -> Result<Self, Self::Error> {
23        match value {
24            0 => Ok(Self::Silence),
25            6 => Ok(Self::OpusPlc),
26            11 => Ok(Self::SampleRate),
27            _ => Err(SteamAudioError::UnknownPacketType { ty: value }),
28        }
29    }
30}
31
32fn read_bytes<const N: usize>(data: &[u8]) -> Result<([u8; N], &[u8]), SteamAudioError> {
33    if data.len() < N {
34        Err(SteamAudioError::InsufficientData)
35    } else {
36        let (result, rest) = data.split_at(N);
37        Ok((result.try_into().unwrap(), rest))
38    }
39}
40
41fn read_u16(data: &[u8]) -> Result<(u16, &[u8]), SteamAudioError> {
42    let (bytes, data) = read_bytes(data)?;
43    Ok((u16::from_le_bytes(bytes), data))
44}
45
46/// A packet contained in the voice data
47#[derive(Debug)]
48pub enum Packet<'a> {
49    /// A number of samples of silence
50    Silence(u16),
51    /// Opus PLC data
52    OpusPlc(SteamOpusData<'a>),
53    /// The sample rate for the opus packets
54    SampleRate(u16),
55}
56
57impl<'a> Packet<'a> {
58    pub fn read(data: &'a [u8]) -> Result<(Self, &'a [u8]), SteamAudioError> {
59        let ty = PacketType::try_from(*data.first().ok_or(SteamAudioError::InsufficientData)?)?;
60        let data = &data[1..];
61
62        let (next, data) = read_u16(data)?;
63
64        Ok(match ty {
65            PacketType::Silence => (Packet::Silence(next), data),
66            PacketType::OpusPlc => {
67                if data.len() < next as usize {
68                    return Err(SteamAudioError::InsufficientData);
69                } else {
70                    let (result, data) = data.split_at(next as usize);
71                    (Packet::OpusPlc(SteamOpusData { data: result }), data)
72                }
73            }
74            PacketType::SampleRate => (Packet::SampleRate(next), data),
75        })
76    }
77}
78
79/// A light-parsed voice data wrapper
80///
81/// Each bit of voice data contains two or three smaller packets
82///
83/// - The sample rate
84/// - A number of silence samples since the last voice data
85/// - The opus voice data
86#[derive(Debug)]
87pub struct SteamVoiceData<'a> {
88    pub steam_id: u64,
89    packet_data: &'a [u8],
90}
91
92impl<'a> SteamVoiceData<'a> {
93    /// Parse the header of the voice data and validate the CRC checksum
94    pub fn new(data: &'a [u8]) -> Result<Self, SteamAudioError> {
95        let (data, crc_data) = data.split_at(data.len() - 4);
96        let expected_crc = u32::from_le_bytes(crc_data.try_into().unwrap());
97        let calculated_crc = crc32b(data);
98        if expected_crc != calculated_crc {
99            return Err(SteamAudioError::CrcMismatch {
100                actual: calculated_crc,
101                expected: expected_crc,
102            });
103        }
104
105        let (steam_id_bytes, data) = read_bytes(data)?;
106        let steam_id = u64::from_le_bytes(steam_id_bytes);
107        Ok(SteamVoiceData {
108            steam_id,
109            packet_data: data,
110        })
111    }
112
113    /// Get the packets contained in the data
114    pub fn packets(&self) -> impl Iterator<Item = Result<Packet<'a>, SteamAudioError>> {
115        SteamPacketIterator {
116            data: self.packet_data,
117        }
118    }
119}
120
121struct SteamPacketIterator<'a> {
122    data: &'a [u8],
123}
124
125impl Debug for SteamPacketIterator<'_> {
126    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127        f.debug_struct("SteamPacketIterator")
128            .field("data_length", &self.data.len())
129            .finish_non_exhaustive()
130    }
131}
132
133impl<'a> Iterator for SteamPacketIterator<'a> {
134    type Item = Result<Packet<'a>, SteamAudioError>;
135
136    fn next(&mut self) -> Option<Self::Item> {
137        if self.data.is_empty() {
138            None
139        } else {
140            match Packet::read(self.data) {
141                Ok((packet, rest)) => {
142                    self.data = rest;
143                    Some(Ok(packet))
144                }
145                Err(e) => Some(Err(e)),
146            }
147        }
148    }
149}
150
151fn crc32b(data: &[u8]) -> u32 {
152    let mut crc: u32 = 0xFFFFFFFF;
153    for &byte in data {
154        crc ^= byte as u32;
155        for _ in 0..8 {
156            let mask = (-((crc & 1) as i32)) as u32;
157            crc = (crc >> 1) ^ (0xEDB88320 & mask);
158        }
159    }
160    !crc
161}
162
163/// Raw opus data
164pub struct SteamOpusData<'a> {
165    data: &'a [u8],
166}
167
168impl<'a> SteamOpusData<'a> {
169    pub fn as_slice(&self) -> &'a [u8] {
170        self.data
171    }
172}
173
174impl Debug for SteamOpusData<'_> {
175    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176        f.debug_struct("SteamOpusData")
177            .field("data_length", &self.data.len())
178            .finish_non_exhaustive()
179    }
180}