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_nb_frames(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_nb_samples(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        if size == 0 {
190            continue;
191        }
192        let ptr = frames_ptrs[i];
193        if ptr.is_null() {
194            return Err(Error::InvalidPacket);
195        }
196        let ptr_addr = ptr as usize;
197        let base_addr = packet.as_ptr() as usize;
198        if ptr_addr < base_addr {
199            return Err(Error::InvalidPacket);
200        }
201        // SAFETY: pointers are into `packet`; derive offset via pointer arithmetic
202        let start = ptr_addr - base_addr;
203        let end = start + size;
204        if end > packet.len() {
205            return Err(Error::InvalidPacket);
206        }
207        frames.push(&packet[start..end]);
208    }
209    Ok((
210        out_toc,
211        usize::try_from(payload_offset).map_err(|_| Error::InternalError)?,
212        frames,
213    ))
214}
215
216/// Increase a packet's size by adding padding to reach `new_len`.
217///
218/// # Errors
219/// Returns [`Error::BadArg`] for invalid lengths or a mapped libopus error if padding fails.
220pub fn packet_pad(packet: &mut [u8], len: usize, new_len: usize) -> Result<()> {
221    if new_len < len || new_len > packet.len() {
222        return Err(Error::BadArg);
223    }
224    let len_i32 = i32::try_from(len).map_err(|_| Error::BadArg)?;
225    let new_len_i32 = i32::try_from(new_len).map_err(|_| Error::BadArg)?;
226    let r = unsafe { opus_packet_pad(packet.as_mut_ptr(), len_i32, new_len_i32) };
227    if r != 0 {
228        return Err(Error::from_code(r));
229    }
230    Ok(())
231}
232
233/// Remove padding from a packet; returns new length or error.
234///
235/// # Errors
236/// Returns [`Error::BadArg`] for invalid lengths or a mapped libopus error if unpadding fails.
237pub fn packet_unpad(packet: &mut [u8], len: usize) -> Result<usize> {
238    if len > packet.len() {
239        return Err(Error::BadArg);
240    }
241    let len_i32 = i32::try_from(len).map_err(|_| Error::BadArg)?;
242    let n = unsafe { opus_packet_unpad(packet.as_mut_ptr(), len_i32) };
243    if n < 0 {
244        return Err(Error::from_code(n));
245    }
246    usize::try_from(n).map_err(|_| Error::InternalError)
247}
248
249/// Pad a multistream packet to `new_len` given `nb_streams`.
250///
251/// # Errors
252/// Returns [`Error::BadArg`] for invalid lengths or a mapped libopus error if padding fails.
253pub fn multistream_packet_pad(
254    packet: &mut [u8],
255    len: usize,
256    new_len: usize,
257    nb_streams: i32,
258) -> Result<()> {
259    if new_len < len || new_len > packet.len() {
260        return Err(Error::BadArg);
261    }
262    let len_i32 = i32::try_from(len).map_err(|_| Error::BadArg)?;
263    let new_len_i32 = i32::try_from(new_len).map_err(|_| Error::BadArg)?;
264    let r = unsafe {
265        opus_multistream_packet_pad(packet.as_mut_ptr(), len_i32, new_len_i32, nb_streams)
266    };
267    if r != 0 {
268        return Err(Error::from_code(r));
269    }
270    Ok(())
271}
272
273/// Remove padding from a multistream packet; returns new length.
274///
275/// # Errors
276/// Returns [`Error::BadArg`] for invalid lengths or a mapped libopus error if unpadding fails.
277pub fn multistream_packet_unpad(packet: &mut [u8], len: usize, nb_streams: i32) -> Result<usize> {
278    if len > packet.len() {
279        return Err(Error::BadArg);
280    }
281    let len_i32 = i32::try_from(len).map_err(|_| Error::BadArg)?;
282    let n = unsafe { opus_multistream_packet_unpad(packet.as_mut_ptr(), len_i32, nb_streams) };
283    if n < 0 {
284        return Err(Error::from_code(n));
285    }
286    usize::try_from(n).map_err(|_| Error::InternalError)
287}