opus_codec/
packet.rs

1//! Safe helpers around opus packet inspection and parsing
2
3#![allow(clippy::cast_possible_truncation)]
4#![allow(clippy::cast_possible_wrap)]
5
6use crate::bindings::{
7    OPUS_BANDWIDTH_FULLBAND, OPUS_BANDWIDTH_MEDIUMBAND, OPUS_BANDWIDTH_NARROWBAND,
8    OPUS_BANDWIDTH_SUPERWIDEBAND, OPUS_BANDWIDTH_WIDEBAND, opus_multistream_packet_pad,
9    opus_multistream_packet_unpad, opus_packet_get_bandwidth, opus_packet_get_nb_channels,
10    opus_packet_get_nb_frames, opus_packet_get_nb_samples, opus_packet_get_samples_per_frame,
11    opus_packet_has_lbrr, opus_packet_pad, opus_packet_parse, opus_packet_unpad,
12    opus_pcm_soft_clip,
13};
14use crate::error::{Error, Result};
15use crate::types::{Bandwidth, Channels, SampleRate};
16
17/// Get bandwidth from a packet.
18///
19/// # Errors
20/// Returns `InvalidPacket` if the packet is malformed.
21pub fn packet_bandwidth(packet: &[u8]) -> Result<Bandwidth> {
22    if packet.is_empty() {
23        return Err(Error::BadArg);
24    }
25    let bw = unsafe { opus_packet_get_bandwidth(packet.as_ptr()) };
26    match bw {
27        x if x == OPUS_BANDWIDTH_NARROWBAND as i32 => Ok(Bandwidth::Narrowband),
28        x if x == OPUS_BANDWIDTH_MEDIUMBAND as i32 => Ok(Bandwidth::Mediumband),
29        x if x == OPUS_BANDWIDTH_WIDEBAND as i32 => Ok(Bandwidth::Wideband),
30        x if x == OPUS_BANDWIDTH_SUPERWIDEBAND as i32 => Ok(Bandwidth::SuperWideband),
31        x if x == OPUS_BANDWIDTH_FULLBAND as i32 => Ok(Bandwidth::Fullband),
32        _ => Err(Error::InvalidPacket),
33    }
34}
35
36/// Get channel count encoded by the packet.
37///
38/// # Errors
39/// Returns `InvalidPacket` if the packet is malformed.
40pub fn packet_channels(packet: &[u8]) -> Result<Channels> {
41    if packet.is_empty() {
42        return Err(Error::BadArg);
43    }
44    let ch = unsafe { opus_packet_get_nb_channels(packet.as_ptr()) };
45    match ch {
46        1 => Ok(Channels::Mono),
47        2 => Ok(Channels::Stereo),
48        _ => Err(Error::InvalidPacket),
49    }
50}
51
52/// Get number of frames in a packet.
53///
54/// # Errors
55/// Returns an error if the packet cannot be parsed.
56pub fn packet_frame_count(packet: &[u8]) -> Result<usize> {
57    if packet.is_empty() {
58        return Err(Error::BadArg);
59    }
60    let len_i32 = i32::try_from(packet.len()).map_err(|_| Error::BadArg)?;
61    let n = unsafe { opus_packet_get_nb_frames(packet.as_ptr(), len_i32) };
62    if n < 0 {
63        return Err(Error::from_code(n));
64    }
65    usize::try_from(n).map_err(|_| Error::InternalError)
66}
67
68/// Get total samples (per channel) in a packet at the given sample rate.
69///
70/// # Errors
71/// Returns an error if the packet cannot be parsed.
72pub fn packet_sample_count(packet: &[u8], sample_rate: SampleRate) -> Result<usize> {
73    if packet.is_empty() {
74        return Err(Error::BadArg);
75    }
76    let len_i32 = i32::try_from(packet.len()).map_err(|_| Error::BadArg)?;
77    let n = unsafe { opus_packet_get_nb_samples(packet.as_ptr(), len_i32, sample_rate.as_i32()) };
78    if n < 0 {
79        return Err(Error::from_code(n));
80    }
81    usize::try_from(n).map_err(|_| Error::InternalError)
82}
83
84/// Get the number of samples per frame for a packet at a given sample rate.
85///
86/// # Errors
87/// Returns [`Error::BadArg`] if `packet` is empty.
88pub fn packet_samples_per_frame(packet: &[u8], sample_rate: SampleRate) -> Result<usize> {
89    if packet.is_empty() {
90        return Err(Error::BadArg);
91    }
92    let n = unsafe { opus_packet_get_samples_per_frame(packet.as_ptr(), sample_rate.as_i32()) };
93    if n <= 0 {
94        return Err(Error::InvalidPacket);
95    }
96    usize::try_from(n).map_err(|_| Error::InternalError)
97}
98
99/// Check if packet has LBRR.
100///
101/// # Errors
102/// Returns an error if the packet cannot be parsed.
103pub fn packet_has_lbrr(packet: &[u8]) -> Result<bool> {
104    if packet.is_empty() {
105        return Err(Error::BadArg);
106    }
107    let len_i32 = i32::try_from(packet.len()).map_err(|_| Error::BadArg)?;
108    let v = unsafe { opus_packet_has_lbrr(packet.as_ptr(), len_i32) };
109    if v < 0 {
110        return Err(Error::from_code(v));
111    }
112    Ok(v != 0)
113}
114
115/// Apply libopus soft clipping to keep float PCM within [-1, 1].
116///
117/// The clipping state memory must be provided per-channel and preserved across calls
118/// for continuous processing. Initialize with zeros for a new stream.
119///
120/// # Errors
121/// Returns [`Error::BadArg`] when the PCM slice, frame size, or soft-clip memory
122/// do not match the provided channel configuration.
123pub fn soft_clip(
124    pcm: &mut [f32],
125    frame_size_per_ch: usize,
126    channels: i32,
127    softclip_mem: &mut [f32],
128) -> Result<()> {
129    if frame_size_per_ch == 0 {
130        return Err(Error::BadArg);
131    }
132    let channels_usize = usize::try_from(channels).map_err(|_| Error::BadArg)?;
133    if channels_usize == 0 {
134        return Err(Error::BadArg);
135    }
136    if softclip_mem.len() < channels_usize {
137        return Err(Error::BadArg);
138    }
139    let needed_samples = frame_size_per_ch
140        .checked_mul(channels_usize)
141        .ok_or(Error::BadArg)?;
142    if pcm.len() < needed_samples {
143        return Err(Error::BadArg);
144    }
145    let frame_i32 = i32::try_from(frame_size_per_ch).map_err(|_| Error::BadArg)?;
146    unsafe {
147        opus_pcm_soft_clip(
148            pcm.as_mut_ptr(),
149            frame_i32,
150            channels,
151            softclip_mem.as_mut_ptr(),
152        );
153    }
154    Ok(())
155}
156
157/// Parse packet into frame pointers and sizes. Returns (toc, `payload_offset`, `frame_sizes`).
158/// Note: Returned frame slices borrow from `packet` and are valid as long as `packet` lives.
159///
160/// # Errors
161/// Returns an error if the packet cannot be parsed.
162pub fn packet_parse(packet: &[u8]) -> Result<(u8, usize, Vec<&[u8]>)> {
163    if packet.is_empty() {
164        return Err(Error::BadArg);
165    }
166    let mut out_toc: u8 = 0;
167    let mut payload_offset: i32 = 0;
168    // libopus caps frames at 48 according to docs
169    let mut frames_ptrs: [*const u8; 48] = [std::ptr::null(); 48];
170    let mut sizes: [i16; 48] = [0; 48];
171    let len_i32 = i32::try_from(packet.len()).map_err(|_| Error::BadArg)?;
172    let n = unsafe {
173        opus_packet_parse(
174            packet.as_ptr(),
175            len_i32,
176            &raw mut out_toc,
177            frames_ptrs.as_mut_ptr().cast::<*const u8>(),
178            sizes.as_mut_ptr(),
179            &raw mut payload_offset,
180        )
181    };
182    if n < 0 {
183        return Err(Error::from_code(n));
184    }
185    let count = usize::try_from(n).map_err(|_| Error::InternalError)?;
186    let mut frames = Vec::with_capacity(count);
187    for i in 0..count {
188        let size = usize::try_from(sizes[i]).map_err(|_| Error::InternalError)?;
189        let ptr = frames_ptrs[i];
190        if ptr.is_null() {
191            return Err(Error::InvalidPacket);
192        }
193        let ptr_addr = ptr as usize;
194        let base_addr = packet.as_ptr() as usize;
195        if ptr_addr < base_addr {
196            return Err(Error::InvalidPacket);
197        }
198        // SAFETY: pointers are into `packet`; derive offset via pointer arithmetic
199        let start = ptr_addr - base_addr;
200        let end = start + size;
201        if end > packet.len() {
202            return Err(Error::InvalidPacket);
203        }
204        frames.push(&packet[start..end]);
205    }
206    Ok((
207        out_toc,
208        usize::try_from(payload_offset).map_err(|_| Error::InternalError)?,
209        frames,
210    ))
211}
212
213/// Increase a packet's size by adding padding to reach `new_len`.
214///
215/// # Errors
216/// Returns [`Error::BadArg`] for invalid lengths or a mapped libopus error if padding fails.
217pub fn packet_pad(packet: &mut [u8], len: usize, new_len: usize) -> Result<()> {
218    if new_len < len || new_len > packet.len() {
219        return Err(Error::BadArg);
220    }
221    let len_i32 = i32::try_from(len).map_err(|_| Error::BadArg)?;
222    let new_len_i32 = i32::try_from(new_len).map_err(|_| Error::BadArg)?;
223    let r = unsafe { opus_packet_pad(packet.as_mut_ptr(), len_i32, new_len_i32) };
224    if r != 0 {
225        return Err(Error::from_code(r));
226    }
227    Ok(())
228}
229
230/// Remove padding from a packet; returns new length or error.
231///
232/// # Errors
233/// Returns [`Error::BadArg`] for invalid lengths or a mapped libopus error if unpadding fails.
234pub fn packet_unpad(packet: &mut [u8], len: usize) -> Result<usize> {
235    if len > packet.len() {
236        return Err(Error::BadArg);
237    }
238    let len_i32 = i32::try_from(len).map_err(|_| Error::BadArg)?;
239    let n = unsafe { opus_packet_unpad(packet.as_mut_ptr(), len_i32) };
240    if n < 0 {
241        return Err(Error::from_code(n));
242    }
243    usize::try_from(n).map_err(|_| Error::InternalError)
244}
245
246/// Pad a multistream packet to `new_len` given `nb_streams`.
247///
248/// # Errors
249/// Returns [`Error::BadArg`] for invalid lengths or a mapped libopus error if padding fails.
250pub fn multistream_packet_pad(
251    packet: &mut [u8],
252    len: usize,
253    new_len: usize,
254    nb_streams: i32,
255) -> Result<()> {
256    if new_len < len || new_len > packet.len() {
257        return Err(Error::BadArg);
258    }
259    let len_i32 = i32::try_from(len).map_err(|_| Error::BadArg)?;
260    let new_len_i32 = i32::try_from(new_len).map_err(|_| Error::BadArg)?;
261    let r = unsafe {
262        opus_multistream_packet_pad(packet.as_mut_ptr(), len_i32, new_len_i32, nb_streams)
263    };
264    if r != 0 {
265        return Err(Error::from_code(r));
266    }
267    Ok(())
268}
269
270/// Remove padding from a multistream packet; returns new length.
271///
272/// # Errors
273/// Returns [`Error::BadArg`] for invalid lengths or a mapped libopus error if unpadding fails.
274pub fn multistream_packet_unpad(packet: &mut [u8], len: usize, nb_streams: i32) -> Result<usize> {
275    if len > packet.len() {
276        return Err(Error::BadArg);
277    }
278    let len_i32 = i32::try_from(len).map_err(|_| Error::BadArg)?;
279    let n = unsafe { opus_multistream_packet_unpad(packet.as_mut_ptr(), len_i32, nb_streams) };
280    if n < 0 {
281        return Err(Error::from_code(n));
282    }
283    usize::try_from(n).map_err(|_| Error::InternalError)
284}