Skip to main content

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#![cfg_attr(not(opus_codec_rust_packet_ops), allow(dead_code))]
6
7use crate::bindings::{
8    OPUS_BANDWIDTH_FULLBAND, OPUS_BANDWIDTH_MEDIUMBAND, OPUS_BANDWIDTH_NARROWBAND,
9    OPUS_BANDWIDTH_SUPERWIDEBAND, OPUS_BANDWIDTH_WIDEBAND, opus_multistream_packet_unpad,
10    opus_packet_get_bandwidth, opus_packet_get_nb_channels, opus_packet_get_nb_frames,
11    opus_packet_get_nb_samples, opus_packet_get_samples_per_frame, opus_packet_has_lbrr,
12    opus_packet_parse, opus_packet_unpad, opus_pcm_soft_clip,
13};
14#[cfg(not(opus_codec_rust_packet_ops))]
15use crate::bindings::{opus_multistream_packet_pad, opus_packet_pad};
16use crate::error::{Error, Result};
17use crate::types::{Bandwidth, Channels, SampleRate};
18
19mod layout;
20
21pub(crate) use layout::MAX_FRAMES_PER_PACKET;
22#[cfg(opus_codec_rust_packet_ops)]
23pub(crate) use layout::{
24    PacketPadding, PacketRepacketizerLayout, packet_repacketizer_layout, repacketize_frames,
25    repacketize_frames_range,
26};
27#[cfg(opus_codec_rust_packet_ops)]
28use layout::{multistream_last_stream_offset, pad_single_packet};
29
30/// Get bandwidth from a packet.
31///
32/// # Errors
33/// Returns `InvalidPacket` if the packet is malformed.
34pub fn packet_bandwidth(packet: &[u8]) -> Result<Bandwidth> {
35    if packet.is_empty() {
36        return Err(Error::BadArg);
37    }
38    let bw = unsafe { opus_packet_get_bandwidth(packet.as_ptr()) };
39    match bw {
40        x if x == OPUS_BANDWIDTH_NARROWBAND as i32 => Ok(Bandwidth::Narrowband),
41        x if x == OPUS_BANDWIDTH_MEDIUMBAND as i32 => Ok(Bandwidth::Mediumband),
42        x if x == OPUS_BANDWIDTH_WIDEBAND as i32 => Ok(Bandwidth::Wideband),
43        x if x == OPUS_BANDWIDTH_SUPERWIDEBAND as i32 => Ok(Bandwidth::SuperWideband),
44        x if x == OPUS_BANDWIDTH_FULLBAND as i32 => Ok(Bandwidth::Fullband),
45        _ => Err(Error::InvalidPacket),
46    }
47}
48
49/// Get channel count encoded by the packet.
50///
51/// # Errors
52/// Returns `InvalidPacket` if the packet is malformed.
53pub fn packet_channels(packet: &[u8]) -> Result<Channels> {
54    if packet.is_empty() {
55        return Err(Error::BadArg);
56    }
57    let ch = unsafe { opus_packet_get_nb_channels(packet.as_ptr()) };
58    match ch {
59        1 => Ok(Channels::Mono),
60        2 => Ok(Channels::Stereo),
61        _ => Err(Error::InvalidPacket),
62    }
63}
64
65/// Get number of frames in a packet.
66///
67/// # Errors
68/// Returns an error if the packet cannot be parsed.
69pub fn packet_frame_count(packet: &[u8]) -> Result<usize> {
70    if packet.is_empty() {
71        return Err(Error::BadArg);
72    }
73    let len_i32 = i32::try_from(packet.len()).map_err(|_| Error::BadArg)?;
74    let n = unsafe { opus_packet_get_nb_frames(packet.as_ptr(), len_i32) };
75    if n < 0 {
76        return Err(Error::from_code(n));
77    }
78    usize::try_from(n).map_err(|_| Error::InternalError)
79}
80
81/// Get total samples (per channel) in a packet at the given sample rate.
82///
83/// # Errors
84/// Returns an error if the packet cannot be parsed.
85pub fn packet_sample_count(packet: &[u8], sample_rate: SampleRate) -> Result<usize> {
86    if packet.is_empty() {
87        return Err(Error::BadArg);
88    }
89    let len_i32 = i32::try_from(packet.len()).map_err(|_| Error::BadArg)?;
90    let n = unsafe { opus_packet_get_nb_samples(packet.as_ptr(), len_i32, sample_rate.as_i32()) };
91    if n < 0 {
92        return Err(Error::from_code(n));
93    }
94    usize::try_from(n).map_err(|_| Error::InternalError)
95}
96
97/// Get the number of samples per frame for a packet at a given sample rate.
98///
99/// # Errors
100/// Returns [`Error::BadArg`] if `packet` is empty.
101pub fn packet_samples_per_frame(packet: &[u8], sample_rate: SampleRate) -> Result<usize> {
102    if packet.is_empty() {
103        return Err(Error::BadArg);
104    }
105    let n = unsafe { opus_packet_get_samples_per_frame(packet.as_ptr(), sample_rate.as_i32()) };
106    if n <= 0 {
107        return Err(Error::InvalidPacket);
108    }
109    usize::try_from(n).map_err(|_| Error::InternalError)
110}
111
112/// Check if packet has LBRR.
113///
114/// # Errors
115/// Returns an error if the packet cannot be parsed.
116pub fn packet_has_lbrr(packet: &[u8]) -> Result<bool> {
117    if packet.is_empty() {
118        return Err(Error::BadArg);
119    }
120    let len_i32 = i32::try_from(packet.len()).map_err(|_| Error::BadArg)?;
121    let v = unsafe { opus_packet_has_lbrr(packet.as_ptr(), len_i32) };
122    if v < 0 {
123        return Err(Error::from_code(v));
124    }
125    Ok(v != 0)
126}
127
128/// Apply libopus soft clipping to keep float PCM within [-1, 1].
129///
130/// The clipping state memory must be provided per-channel and preserved across calls
131/// for continuous processing. Initialize with zeros for a new stream.
132///
133/// # Errors
134/// Returns [`Error::BadArg`] when the PCM slice, frame size, or soft-clip memory
135/// do not match the provided channel configuration.
136pub fn soft_clip(
137    pcm: &mut [f32],
138    frame_size_per_ch: usize,
139    channels: i32,
140    softclip_mem: &mut [f32],
141) -> Result<()> {
142    if frame_size_per_ch == 0 {
143        return Err(Error::BadArg);
144    }
145    let channels_usize = usize::try_from(channels).map_err(|_| Error::BadArg)?;
146    if channels_usize == 0 {
147        return Err(Error::BadArg);
148    }
149    if softclip_mem.len() < channels_usize {
150        return Err(Error::BadArg);
151    }
152    let needed_samples = checked_soft_clip_sample_count(frame_size_per_ch, channels_usize)?;
153    if pcm.len() < needed_samples {
154        return Err(Error::BadArg);
155    }
156    let frame_i32 = i32::try_from(frame_size_per_ch).map_err(|_| Error::BadArg)?;
157    unsafe {
158        opus_pcm_soft_clip(
159            pcm.as_mut_ptr(),
160            frame_i32,
161            channels,
162            softclip_mem.as_mut_ptr(),
163        );
164    }
165    Ok(())
166}
167
168fn checked_soft_clip_sample_count(frame_size_per_ch: usize, channels: usize) -> Result<usize> {
169    let needed_samples = frame_size_per_ch
170        .checked_mul(channels)
171        .ok_or(Error::BadArg)?;
172    // opus_pcm_soft_clip() evaluates N*C and all sample offsets in signed
173    // C `int` arithmetic. A larger Rust slice would therefore still make the
174    // C implementation overflow before it accessed the full slice.
175    if needed_samples > i32::MAX as usize {
176        return Err(Error::BadArg);
177    }
178    Ok(needed_samples)
179}
180
181/// Parse a packet into caller-provided frame storage.
182///
183/// Returns `(toc, payload_offset, frame_count)`. The first `frame_count`
184/// entries in `frames` are replaced with slices that borrow from `packet`.
185///
186/// # Errors
187/// Returns [`Error::BufferTooSmall`] if `frames` cannot hold every parsed
188/// frame, or another error if the packet cannot be parsed.
189pub fn packet_parse_into<'packet>(
190    packet: &'packet [u8],
191    frames: &mut [&'packet [u8]],
192) -> Result<(u8, usize, usize)> {
193    if packet.is_empty() {
194        return Err(Error::BadArg);
195    }
196    let mut out_toc: u8 = 0;
197    let mut payload_offset: i32 = 0;
198    // libopus caps frames at MAX_FRAMES_PER_PACKET according to docs.
199    let mut frames_ptrs: [*const u8; MAX_FRAMES_PER_PACKET] =
200        [std::ptr::null(); MAX_FRAMES_PER_PACKET];
201    let mut sizes: [i16; MAX_FRAMES_PER_PACKET] = [0; MAX_FRAMES_PER_PACKET];
202    let len_i32 = i32::try_from(packet.len()).map_err(|_| Error::BadArg)?;
203    let n = unsafe {
204        opus_packet_parse(
205            packet.as_ptr(),
206            len_i32,
207            &raw mut out_toc,
208            frames_ptrs.as_mut_ptr().cast::<*const u8>(),
209            sizes.as_mut_ptr(),
210            &raw mut payload_offset,
211        )
212    };
213    if n < 0 {
214        return Err(Error::from_code(n));
215    }
216    let count = usize::try_from(n).map_err(|_| Error::InternalError)?;
217    if count > MAX_FRAMES_PER_PACKET {
218        return Err(Error::InternalError);
219    }
220    if count > frames.len() {
221        return Err(Error::BufferTooSmall);
222    }
223    let mut starts = [0usize; MAX_FRAMES_PER_PACKET];
224    let mut lengths = [0usize; MAX_FRAMES_PER_PACKET];
225    for i in 0..count {
226        let size = usize::try_from(sizes[i]).map_err(|_| Error::InternalError)?;
227        let ptr = frames_ptrs[i];
228        if ptr.is_null() {
229            return Err(Error::InvalidPacket);
230        }
231        let ptr_addr = ptr as usize;
232        let base_addr = packet.as_ptr() as usize;
233        if ptr_addr < base_addr {
234            return Err(Error::InvalidPacket);
235        }
236        // SAFETY: pointers are into `packet`; derive offset via pointer arithmetic
237        let start = ptr_addr - base_addr;
238        let end = start.checked_add(size).ok_or(Error::InternalError)?;
239        if end > packet.len() {
240            return Err(Error::InvalidPacket);
241        }
242        starts[i] = start;
243        lengths[i] = size;
244    }
245    let payload_offset = usize::try_from(payload_offset).map_err(|_| Error::InternalError)?;
246    if payload_offset > packet.len() {
247        return Err(Error::InvalidPacket);
248    }
249    for i in 0..count {
250        frames[i] = &packet[starts[i]..starts[i] + lengths[i]];
251    }
252    Ok((out_toc, payload_offset, count))
253}
254
255/// Parse packet into frame slices. Returns `(toc, payload_offset, frames)`.
256///
257/// Returned frame slices borrow from `packet` and are valid as long as
258/// `packet` lives. Use [`packet_parse_into`] to supply reusable storage and
259/// avoid allocating the returned vector.
260///
261/// # Errors
262/// Returns an error if the packet cannot be parsed.
263pub fn packet_parse(packet: &[u8]) -> Result<(u8, usize, Vec<&[u8]>)> {
264    let mut frames = [&[][..]; MAX_FRAMES_PER_PACKET];
265    let (toc, payload_offset, frame_count) = packet_parse_into(packet, &mut frames)?;
266    Ok((toc, payload_offset, frames[..frame_count].to_vec()))
267}
268
269/// Increase a packet's size by adding padding to reach `new_len`.
270///
271/// # Errors
272/// Returns [`Error::BadArg`] for invalid lengths or another error if padding fails.
273#[cfg(opus_codec_rust_packet_ops)]
274pub fn packet_pad(packet: &mut [u8], len: usize, new_len: usize) -> Result<()> {
275    if new_len < len || new_len > packet.len() {
276        return Err(Error::BadArg);
277    }
278    if len == 0 {
279        return Err(Error::BadArg);
280    }
281    if len == new_len {
282        return Ok(());
283    }
284    pad_single_packet(packet, len, new_len)
285}
286
287/// Increase a packet's size by adding padding to reach `new_len`.
288///
289/// # Errors
290/// Returns [`Error::BadArg`] for invalid lengths or a mapped libopus error if padding fails.
291#[cfg(not(opus_codec_rust_packet_ops))]
292pub fn packet_pad(packet: &mut [u8], len: usize, new_len: usize) -> Result<()> {
293    if new_len < len || new_len > packet.len() {
294        return Err(Error::BadArg);
295    }
296    if len == 0 {
297        return Err(Error::BadArg);
298    }
299    let len_i32 = i32::try_from(len).map_err(|_| Error::BadArg)?;
300    let new_len_i32 = i32::try_from(new_len).map_err(|_| Error::BadArg)?;
301    let r = unsafe { opus_packet_pad(packet.as_mut_ptr(), len_i32, new_len_i32) };
302    if r != 0 {
303        return Err(Error::from_code(r));
304    }
305    Ok(())
306}
307
308/// Remove padding from a packet; returns new length or error.
309///
310/// # Errors
311/// Returns [`Error::BadArg`] for invalid lengths or a mapped libopus error if unpadding fails.
312pub fn packet_unpad(packet: &mut [u8], len: usize) -> Result<usize> {
313    if len > packet.len() {
314        return Err(Error::BadArg);
315    }
316    if len == 0 {
317        return Err(Error::BadArg);
318    }
319    let len_i32 = i32::try_from(len).map_err(|_| Error::BadArg)?;
320    let n = unsafe { opus_packet_unpad(packet.as_mut_ptr(), len_i32) };
321    if n < 0 {
322        return Err(Error::from_code(n));
323    }
324    usize::try_from(n).map_err(|_| Error::InternalError)
325}
326
327/// Pad a multistream packet to `new_len` given `nb_streams`.
328///
329/// # Errors
330/// Returns [`Error::BadArg`] for invalid lengths or another error if padding fails.
331#[cfg(opus_codec_rust_packet_ops)]
332pub fn multistream_packet_pad(
333    packet: &mut [u8],
334    len: usize,
335    new_len: usize,
336    nb_streams: i32,
337) -> Result<()> {
338    if new_len < len || new_len > packet.len() {
339        return Err(Error::BadArg);
340    }
341    if len == 0 {
342        return Err(Error::BadArg);
343    }
344    // The public API requires at least one stream. Reject invalid counts
345    // before delegating to libopus' multistream packet walker.
346    if nb_streams < 1 {
347        return Err(Error::BadArg);
348    }
349    if len == new_len {
350        return Ok(());
351    }
352    let nb_streams = usize::try_from(nb_streams).map_err(|_| Error::BadArg)?;
353    let last_stream_offset = multistream_last_stream_offset(&packet[..len], nb_streams)?;
354    if last_stream_offset == len {
355        return Err(Error::BadArg);
356    }
357    let amount = new_len - len;
358    let last_len = len - last_stream_offset;
359    let last_new_len = last_len.checked_add(amount).ok_or(Error::BadArg)?;
360    pad_single_packet(&mut packet[last_stream_offset..], last_len, last_new_len)
361}
362
363/// Pad a multistream packet to `new_len` given `nb_streams`.
364///
365/// # Errors
366/// Returns [`Error::BadArg`] for invalid lengths or a mapped libopus error if padding fails.
367#[cfg(not(opus_codec_rust_packet_ops))]
368pub fn multistream_packet_pad(
369    packet: &mut [u8],
370    len: usize,
371    new_len: usize,
372    nb_streams: i32,
373) -> Result<()> {
374    if new_len < len || new_len > packet.len() {
375        return Err(Error::BadArg);
376    }
377    if len == 0 {
378        return Err(Error::BadArg);
379    }
380    if nb_streams < 1 {
381        return Err(Error::BadArg);
382    }
383    let len_i32 = i32::try_from(len).map_err(|_| Error::BadArg)?;
384    let new_len_i32 = i32::try_from(new_len).map_err(|_| Error::BadArg)?;
385    let r = unsafe {
386        opus_multistream_packet_pad(packet.as_mut_ptr(), len_i32, new_len_i32, nb_streams)
387    };
388    if r != 0 {
389        return Err(Error::from_code(r));
390    }
391    Ok(())
392}
393
394/// Remove padding from a multistream packet; returns new length.
395///
396/// # Errors
397/// Returns [`Error::BadArg`] for invalid lengths or a mapped libopus error if unpadding fails.
398pub fn multistream_packet_unpad(packet: &mut [u8], len: usize, nb_streams: i32) -> Result<usize> {
399    if len > packet.len() {
400        return Err(Error::BadArg);
401    }
402    if len == 0 {
403        return Err(Error::BadArg);
404    }
405    // The public API requires at least one stream. Reject invalid counts
406    // before delegating to libopus' multistream packet walker.
407    if nb_streams < 1 {
408        return Err(Error::BadArg);
409    }
410    let len_i32 = i32::try_from(len).map_err(|_| Error::BadArg)?;
411    let n = unsafe { opus_multistream_packet_unpad(packet.as_mut_ptr(), len_i32, nb_streams) };
412    if n < 0 {
413        return Err(Error::from_code(n));
414    }
415    usize::try_from(n).map_err(|_| Error::InternalError)
416}
417
418#[cfg(test)]
419mod tests {
420    use super::*;
421
422    #[test]
423    fn soft_clip_rejects_sample_products_that_overflow_c_int() {
424        let frame_size = i32::MAX as usize / 2 + 1;
425        assert_eq!(
426            checked_soft_clip_sample_count(frame_size, 2),
427            Err(Error::BadArg)
428        );
429        assert_eq!(
430            checked_soft_clip_sample_count(i32::MAX as usize, 1),
431            Ok(i32::MAX as usize)
432        );
433    }
434}