ff_format/frame/audio.rs
1//! Audio frame type.
2//!
3//! This module provides [`AudioFrame`] for working with decoded audio frames.
4//!
5//! # Examples
6//!
7//! ```
8//! use ff_format::{AudioFrame, SampleFormat, Rational, Timestamp};
9//!
10//! // Create a stereo F32 audio frame with 1024 samples
11//! let channels = 2u32;
12//! let samples = 1024usize;
13//! let sample_rate = 48000u32;
14//!
15//! let frame = AudioFrame::empty(
16//! samples,
17//! channels,
18//! sample_rate,
19//! SampleFormat::F32,
20//! ).unwrap();
21//!
22//! assert_eq!(frame.samples(), 1024);
23//! assert_eq!(frame.channels(), 2);
24//! assert_eq!(frame.sample_rate(), 48000);
25//! assert!(!frame.format().is_planar());
26//! ```
27
28use std::fmt;
29use std::time::Duration;
30
31use crate::error::FrameError;
32use crate::{SampleFormat, Timestamp};
33
34/// A decoded audio frame.
35///
36/// This structure holds audio sample data and metadata for a segment of audio.
37/// It supports both packed (interleaved) formats where all channels are
38/// interleaved in a single buffer, and planar formats where each channel
39/// is stored in a separate buffer.
40///
41/// # Memory Layout
42///
43/// For packed (interleaved) formats (I16, F32, etc.):
44/// - Single plane containing interleaved samples: L R L R L R ...
45/// - Total size: `samples * channels * bytes_per_sample`
46///
47/// For planar formats (I16p, F32p, etc.):
48/// - One plane per channel
49/// - Each plane size: `samples * bytes_per_sample`
50///
51/// # Examples
52///
53/// ```
54/// use ff_format::{AudioFrame, SampleFormat, Timestamp, Rational};
55///
56/// // Create a stereo F32 frame with 1024 samples at 48kHz
57/// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
58///
59/// assert_eq!(frame.samples(), 1024);
60/// assert_eq!(frame.channels(), 2);
61/// assert_eq!(frame.sample_rate(), 48000);
62/// assert_eq!(frame.format(), SampleFormat::F32);
63///
64/// // Duration of this frame: 1024 / 48000 ≈ 21.33ms
65/// let duration = frame.duration();
66/// assert!((duration.as_secs_f64() - 0.02133).abs() < 0.001);
67/// ```
68#[derive(Clone)]
69pub struct AudioFrame {
70 /// Sample data for each plane (1 for packed, channels for planar)
71 planes: Vec<Vec<u8>>,
72 /// Number of samples per channel
73 samples: usize,
74 /// Number of audio channels
75 channels: u32,
76 /// Sample rate in Hz
77 sample_rate: u32,
78 /// Sample format
79 format: SampleFormat,
80 /// Presentation timestamp
81 timestamp: Timestamp,
82}
83
84impl AudioFrame {
85 /// Creates a new audio frame with the specified parameters.
86 ///
87 /// # Arguments
88 ///
89 /// * `planes` - Audio sample data (1 plane for packed, channels for planar)
90 /// * `samples` - Number of samples per channel
91 /// * `channels` - Number of audio channels
92 /// * `sample_rate` - Sample rate in Hz
93 /// * `format` - Sample format
94 /// * `timestamp` - Presentation timestamp
95 ///
96 /// # Errors
97 ///
98 /// Returns [`FrameError::InvalidPlaneCount`] if the number of planes doesn't
99 /// match the format (1 for packed, channels for planar).
100 ///
101 /// # Examples
102 ///
103 /// ```
104 /// use ff_format::{AudioFrame, SampleFormat, Timestamp};
105 ///
106 /// // Create a mono F32 frame with 1024 samples
107 /// let samples = 1024;
108 /// let bytes_per_sample = 4; // F32
109 /// let data = vec![0u8; samples * bytes_per_sample];
110 ///
111 /// let frame = AudioFrame::new(
112 /// vec![data],
113 /// samples,
114 /// 1,
115 /// 48000,
116 /// SampleFormat::F32,
117 /// Timestamp::default(),
118 /// ).unwrap();
119 ///
120 /// assert_eq!(frame.samples(), 1024);
121 /// assert_eq!(frame.channels(), 1);
122 /// ```
123 pub fn new(
124 planes: Vec<Vec<u8>>,
125 samples: usize,
126 channels: u32,
127 sample_rate: u32,
128 format: SampleFormat,
129 timestamp: Timestamp,
130 ) -> Result<Self, FrameError> {
131 let expected_planes = if format.is_planar() {
132 channels as usize
133 } else {
134 1
135 };
136
137 if planes.len() != expected_planes {
138 return Err(FrameError::InvalidPlaneCount {
139 expected: expected_planes,
140 actual: planes.len(),
141 });
142 }
143
144 Ok(Self {
145 planes,
146 samples,
147 channels,
148 sample_rate,
149 format,
150 timestamp,
151 })
152 }
153
154 /// Creates an empty audio frame with the specified parameters.
155 ///
156 /// The frame will have properly sized planes filled with zeros.
157 ///
158 /// # Arguments
159 ///
160 /// * `samples` - Number of samples per channel
161 /// * `channels` - Number of audio channels
162 /// * `sample_rate` - Sample rate in Hz
163 /// * `format` - Sample format
164 ///
165 /// # Errors
166 ///
167 /// Returns [`FrameError::UnsupportedSampleFormat`] if the format is
168 /// [`SampleFormat::Other`], as the memory layout cannot be determined.
169 ///
170 /// # Examples
171 ///
172 /// ```
173 /// use ff_format::{AudioFrame, SampleFormat};
174 ///
175 /// // Create a stereo I16 frame
176 /// let frame = AudioFrame::empty(1024, 2, 44100, SampleFormat::I16).unwrap();
177 /// assert_eq!(frame.samples(), 1024);
178 /// assert_eq!(frame.channels(), 2);
179 /// assert_eq!(frame.num_planes(), 1); // Packed format
180 /// ```
181 pub fn empty(
182 samples: usize,
183 channels: u32,
184 sample_rate: u32,
185 format: SampleFormat,
186 ) -> Result<Self, FrameError> {
187 if matches!(format, SampleFormat::Other(_)) {
188 return Err(FrameError::UnsupportedSampleFormat(format));
189 }
190
191 let planes = Self::allocate_planes(samples, channels, format);
192
193 Ok(Self {
194 planes,
195 samples,
196 channels,
197 sample_rate,
198 format,
199 timestamp: Timestamp::default(),
200 })
201 }
202
203 /// Creates a silent audio frame with 1024 zero-filled samples.
204 ///
205 /// `pts_ms` is the presentation timestamp in milliseconds.
206 /// Both planar formats (one plane per channel) and packed formats (single
207 /// interleaved plane) are supported.
208 #[doc(hidden)]
209 #[must_use]
210 pub fn new_silent(sample_rate: u32, channels: u32, format: SampleFormat, pts_ms: i64) -> Self {
211 let samples = 1024usize;
212 let bps = format.bytes_per_sample();
213 let planes = if format.is_planar() {
214 (0..channels as usize)
215 .map(|_| vec![0u8; samples * bps])
216 .collect()
217 } else {
218 vec![vec![0u8; samples * channels as usize * bps]]
219 };
220 let timestamp = Timestamp::from_millis(pts_ms, crate::Rational::new(1, 1000));
221 Self {
222 planes,
223 samples,
224 channels,
225 sample_rate,
226 format,
227 timestamp,
228 }
229 }
230
231 /// Allocates planes for the given parameters.
232 fn allocate_planes(samples: usize, channels: u32, format: SampleFormat) -> Vec<Vec<u8>> {
233 let bytes_per_sample = format.bytes_per_sample();
234
235 if format.is_planar() {
236 // Planar: one plane per channel
237 let plane_size = samples * bytes_per_sample;
238 (0..channels).map(|_| vec![0u8; plane_size]).collect()
239 } else {
240 // Packed: single plane with interleaved samples
241 let total_size = samples * channels as usize * bytes_per_sample;
242 vec![vec![0u8; total_size]]
243 }
244 }
245
246 // ==========================================================================
247 // Metadata Accessors
248 // ==========================================================================
249
250 /// Returns the number of samples per channel.
251 ///
252 /// # Examples
253 ///
254 /// ```
255 /// use ff_format::{AudioFrame, SampleFormat};
256 ///
257 /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
258 /// assert_eq!(frame.samples(), 1024);
259 /// ```
260 #[must_use]
261 #[inline]
262 pub const fn samples(&self) -> usize {
263 self.samples
264 }
265
266 /// Returns the number of audio channels.
267 ///
268 /// The return type is `u32` to match `FFmpeg`'s `AVFrame::ch_layout.nb_channels`
269 /// and professional audio APIs (Core Audio, WASAPI, JACK, Dolby Atmos).
270 ///
271 /// # Integration
272 ///
273 /// Playback libraries such as `rodio` and `cpal` accept channel counts as
274 /// `u16`. Cast with `frame.channels() as u16`; the truncation is always safe
275 /// because no real-world format exceeds `u16::MAX` (65 535) channels.
276 ///
277 /// # Examples
278 ///
279 /// ```
280 /// use ff_format::{AudioFrame, SampleFormat};
281 ///
282 /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
283 /// assert_eq!(frame.channels(), 2);
284 /// ```
285 #[must_use]
286 #[inline]
287 pub const fn channels(&self) -> u32 {
288 self.channels
289 }
290
291 /// Returns the sample rate in Hz.
292 ///
293 /// # Examples
294 ///
295 /// ```
296 /// use ff_format::{AudioFrame, SampleFormat};
297 ///
298 /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
299 /// assert_eq!(frame.sample_rate(), 48000);
300 /// ```
301 #[must_use]
302 #[inline]
303 pub const fn sample_rate(&self) -> u32 {
304 self.sample_rate
305 }
306
307 /// Returns the sample format.
308 ///
309 /// # Examples
310 ///
311 /// ```
312 /// use ff_format::{AudioFrame, SampleFormat};
313 ///
314 /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
315 /// assert_eq!(frame.format(), SampleFormat::F32);
316 /// assert!(frame.format().is_float());
317 /// ```
318 #[must_use]
319 #[inline]
320 pub const fn format(&self) -> SampleFormat {
321 self.format
322 }
323
324 /// Returns the presentation timestamp.
325 ///
326 /// # Examples
327 ///
328 /// ```
329 /// use ff_format::{AudioFrame, SampleFormat, Timestamp, Rational};
330 ///
331 /// let ts = Timestamp::new(48000, Rational::new(1, 48000));
332 /// let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
333 /// frame.set_timestamp(ts);
334 /// assert_eq!(frame.timestamp(), ts);
335 /// ```
336 #[must_use]
337 #[inline]
338 pub const fn timestamp(&self) -> Timestamp {
339 self.timestamp
340 }
341
342 /// Sets the presentation timestamp.
343 ///
344 /// # Examples
345 ///
346 /// ```
347 /// use ff_format::{AudioFrame, SampleFormat, Timestamp, Rational};
348 ///
349 /// let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
350 /// let ts = Timestamp::new(48000, Rational::new(1, 48000));
351 /// frame.set_timestamp(ts);
352 /// assert_eq!(frame.timestamp(), ts);
353 /// ```
354 #[inline]
355 pub fn set_timestamp(&mut self, timestamp: Timestamp) {
356 self.timestamp = timestamp;
357 }
358
359 /// Returns the duration of this audio frame.
360 ///
361 /// The duration is calculated as `samples / sample_rate`.
362 ///
363 /// # Examples
364 ///
365 /// ```
366 /// use ff_format::{AudioFrame, SampleFormat};
367 ///
368 /// // 1024 samples at 48kHz = ~21.33ms
369 /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
370 /// let duration = frame.duration();
371 /// assert!((duration.as_secs_f64() - 0.02133).abs() < 0.001);
372 ///
373 /// // 48000 samples at 48kHz = 1 second
374 /// let frame = AudioFrame::empty(48000, 2, 48000, SampleFormat::F32).unwrap();
375 /// assert_eq!(frame.duration().as_secs(), 1);
376 /// ```
377 #[must_use]
378 #[allow(clippy::cast_precision_loss)] // Audio frame sample counts are well within f64's precision
379 pub fn duration(&self) -> Duration {
380 if self.sample_rate == 0 {
381 log::warn!(
382 "duration unavailable, sample_rate is 0, returning zero \
383 samples={} fallback=Duration::ZERO",
384 self.samples
385 );
386 return Duration::ZERO;
387 }
388 let secs = self.samples as f64 / f64::from(self.sample_rate);
389 Duration::from_secs_f64(secs)
390 }
391
392 // ==========================================================================
393 // Plane Data Access
394 // ==========================================================================
395
396 /// Returns the number of planes in this frame.
397 ///
398 /// - Packed formats: 1 plane (interleaved channels)
399 /// - Planar formats: 1 plane per channel
400 ///
401 /// # Examples
402 ///
403 /// ```
404 /// use ff_format::{AudioFrame, SampleFormat};
405 ///
406 /// // Packed format - 1 plane
407 /// let packed = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
408 /// assert_eq!(packed.num_planes(), 1);
409 ///
410 /// // Planar format - 1 plane per channel
411 /// let planar = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
412 /// assert_eq!(planar.num_planes(), 2);
413 /// ```
414 #[must_use]
415 #[inline]
416 pub fn num_planes(&self) -> usize {
417 self.planes.len()
418 }
419
420 /// Returns a slice of all plane data.
421 ///
422 /// # Examples
423 ///
424 /// ```
425 /// use ff_format::{AudioFrame, SampleFormat};
426 ///
427 /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
428 /// let planes = frame.planes();
429 /// assert_eq!(planes.len(), 2);
430 /// ```
431 #[must_use]
432 #[inline]
433 pub fn planes(&self) -> &[Vec<u8>] {
434 &self.planes
435 }
436
437 /// Returns the data for a specific plane, or `None` if the index is out of bounds.
438 ///
439 /// For packed formats, use `plane(0)`. For planar formats, use `plane(channel_index)`.
440 ///
441 /// # Arguments
442 ///
443 /// * `index` - The plane index (0 for packed, channel index for planar)
444 ///
445 /// # Examples
446 ///
447 /// ```
448 /// use ff_format::{AudioFrame, SampleFormat};
449 ///
450 /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
451 ///
452 /// // Access left channel (plane 0)
453 /// assert!(frame.plane(0).is_some());
454 ///
455 /// // Access right channel (plane 1)
456 /// assert!(frame.plane(1).is_some());
457 ///
458 /// // No third channel
459 /// assert!(frame.plane(2).is_none());
460 /// ```
461 #[must_use]
462 #[inline]
463 pub fn plane(&self, index: usize) -> Option<&[u8]> {
464 self.planes.get(index).map(Vec::as_slice)
465 }
466
467 /// Returns mutable access to a specific plane's data.
468 ///
469 /// # Arguments
470 ///
471 /// * `index` - The plane index
472 ///
473 /// # Examples
474 ///
475 /// ```
476 /// use ff_format::{AudioFrame, SampleFormat};
477 ///
478 /// let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
479 /// if let Some(data) = frame.plane_mut(0) {
480 /// // Modify left channel
481 /// data[0] = 128;
482 /// }
483 /// ```
484 #[must_use]
485 #[inline]
486 pub fn plane_mut(&mut self, index: usize) -> Option<&mut [u8]> {
487 self.planes.get_mut(index).map(Vec::as_mut_slice)
488 }
489
490 /// Returns the channel data for planar formats.
491 ///
492 /// This is an alias for [`plane()`](Self::plane) that's more semantically
493 /// meaningful for audio data.
494 ///
495 /// # Arguments
496 ///
497 /// * `channel` - The channel index (0 = left, 1 = right, etc.)
498 ///
499 /// # Examples
500 ///
501 /// ```
502 /// use ff_format::{AudioFrame, SampleFormat};
503 ///
504 /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
505 ///
506 /// // Get left channel data
507 /// let left = frame.channel(0).unwrap();
508 /// assert_eq!(left.len(), 1024 * 4); // 1024 samples * 4 bytes
509 /// ```
510 #[must_use]
511 #[inline]
512 pub fn channel(&self, channel: usize) -> Option<&[u8]> {
513 self.plane(channel)
514 }
515
516 /// Returns mutable access to channel data for planar formats.
517 ///
518 /// # Arguments
519 ///
520 /// * `channel` - The channel index
521 #[must_use]
522 #[inline]
523 pub fn channel_mut(&mut self, channel: usize) -> Option<&mut [u8]> {
524 self.plane_mut(channel)
525 }
526
527 // ==========================================================================
528 // Contiguous Data Access
529 // ==========================================================================
530
531 /// Returns the raw sample data as a contiguous byte slice.
532 ///
533 /// For packed formats (e.g. [`SampleFormat::F32`], [`SampleFormat::I16`]), this returns
534 /// the interleaved sample bytes. For planar formats (e.g. [`SampleFormat::F32p`],
535 /// [`SampleFormat::I16p`]), this returns an empty slice — use [`channel()`](Self::channel)
536 /// or [`channel_as_f32()`](Self::channel_as_f32) to access individual channel planes instead.
537 ///
538 /// # Examples
539 ///
540 /// ```
541 /// use ff_format::{AudioFrame, SampleFormat};
542 ///
543 /// // Packed format - returns interleaved sample bytes
544 /// let packed = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
545 /// assert_eq!(packed.data().len(), 1024 * 2 * 4);
546 ///
547 /// // Planar format - returns empty slice; use channel() instead
548 /// let planar = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
549 /// assert!(planar.data().is_empty());
550 /// let left = planar.channel(0).unwrap();
551 /// assert_eq!(left.len(), 1024 * 4);
552 /// ```
553 #[must_use]
554 #[inline]
555 pub fn data(&self) -> &[u8] {
556 if self.format.is_packed() && self.planes.len() == 1 {
557 &self.planes[0]
558 } else {
559 &[]
560 }
561 }
562
563 /// Returns mutable access to the raw sample data.
564 ///
565 /// For packed formats, returns the interleaved sample bytes as a mutable slice.
566 /// For planar formats, returns an empty mutable slice — use
567 /// [`channel_mut()`](Self::channel_mut) to modify individual channel planes instead.
568 ///
569 /// # Examples
570 ///
571 /// ```
572 /// use ff_format::{AudioFrame, SampleFormat};
573 ///
574 /// let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
575 /// let data = frame.data_mut();
576 /// data[0] = 128;
577 /// ```
578 #[must_use]
579 #[inline]
580 pub fn data_mut(&mut self) -> &mut [u8] {
581 if self.format.is_packed() && self.planes.len() == 1 {
582 &mut self.planes[0]
583 } else {
584 &mut []
585 }
586 }
587
588 // ==========================================================================
589 // Typed Sample Access
590 // ==========================================================================
591 //
592 // These methods provide zero-copy typed access to audio sample data.
593 // They use unsafe code to reinterpret byte buffers as typed slices.
594 //
595 // SAFETY: The data buffers are allocated with proper size and the
596 // underlying Vec<u8> is guaranteed to be properly aligned for the
597 // platform's requirements. We verify format matches before casting.
598
599 /// Returns the sample data as an f32 slice.
600 ///
601 /// This only works if the format is [`SampleFormat::F32`] (packed).
602 /// For planar F32p format, use [`channel_as_f32()`](Self::channel_as_f32).
603 ///
604 /// # Safety Note
605 ///
606 /// This method reinterprets the raw bytes as f32 values. It requires
607 /// proper alignment and format matching.
608 ///
609 /// # Examples
610 ///
611 /// ```
612 /// use ff_format::{AudioFrame, SampleFormat};
613 ///
614 /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
615 /// if let Some(samples) = frame.as_f32() {
616 /// assert_eq!(samples.len(), 1024 * 2); // samples * channels
617 /// }
618 /// ```
619 #[must_use]
620 #[allow(unsafe_code, clippy::cast_ptr_alignment)]
621 pub fn as_f32(&self) -> Option<&[f32]> {
622 if self.format != SampleFormat::F32 {
623 return None;
624 }
625
626 let bytes = self.data();
627 if bytes.is_empty() {
628 return None;
629 }
630 // SAFETY: We verified the format is F32, and the data was allocated
631 // for F32 samples. Vec<u8> is aligned to at least 1 byte, but in practice
632 // most allocators align to at least 8/16 bytes which is sufficient for f32.
633 let ptr = bytes.as_ptr().cast::<f32>();
634 let len = bytes.len() / std::mem::size_of::<f32>();
635 Some(unsafe { std::slice::from_raw_parts(ptr, len) })
636 }
637
638 /// Returns mutable access to sample data as an f32 slice.
639 ///
640 /// Only works for [`SampleFormat::F32`] (packed).
641 #[must_use]
642 #[allow(unsafe_code, clippy::cast_ptr_alignment)]
643 pub fn as_f32_mut(&mut self) -> Option<&mut [f32]> {
644 if self.format != SampleFormat::F32 {
645 return None;
646 }
647
648 let bytes = self.data_mut();
649 if bytes.is_empty() {
650 return None;
651 }
652 let ptr = bytes.as_mut_ptr().cast::<f32>();
653 let len = bytes.len() / std::mem::size_of::<f32>();
654 Some(unsafe { std::slice::from_raw_parts_mut(ptr, len) })
655 }
656
657 /// Returns the sample data as an i16 slice.
658 ///
659 /// This only works if the format is [`SampleFormat::I16`] (packed).
660 /// For planar I16p format, use [`channel_as_i16()`](Self::channel_as_i16).
661 ///
662 /// # Examples
663 ///
664 /// ```
665 /// use ff_format::{AudioFrame, SampleFormat};
666 ///
667 /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::I16).unwrap();
668 /// if let Some(samples) = frame.as_i16() {
669 /// assert_eq!(samples.len(), 1024 * 2);
670 /// }
671 /// ```
672 #[must_use]
673 #[allow(unsafe_code, clippy::cast_ptr_alignment)]
674 pub fn as_i16(&self) -> Option<&[i16]> {
675 if self.format != SampleFormat::I16 {
676 return None;
677 }
678
679 let bytes = self.data();
680 if bytes.is_empty() {
681 return None;
682 }
683 let ptr = bytes.as_ptr().cast::<i16>();
684 let len = bytes.len() / std::mem::size_of::<i16>();
685 Some(unsafe { std::slice::from_raw_parts(ptr, len) })
686 }
687
688 /// Returns mutable access to sample data as an i16 slice.
689 ///
690 /// Only works for [`SampleFormat::I16`] (packed).
691 #[must_use]
692 #[allow(unsafe_code, clippy::cast_ptr_alignment)]
693 pub fn as_i16_mut(&mut self) -> Option<&mut [i16]> {
694 if self.format != SampleFormat::I16 {
695 return None;
696 }
697
698 let bytes = self.data_mut();
699 if bytes.is_empty() {
700 return None;
701 }
702 let ptr = bytes.as_mut_ptr().cast::<i16>();
703 let len = bytes.len() / std::mem::size_of::<i16>();
704 Some(unsafe { std::slice::from_raw_parts_mut(ptr, len) })
705 }
706
707 /// Returns a specific channel's data as an f32 slice.
708 ///
709 /// Works for planar F32p format.
710 ///
711 /// # Arguments
712 ///
713 /// * `channel` - The channel index
714 ///
715 /// # Examples
716 ///
717 /// ```
718 /// use ff_format::{AudioFrame, SampleFormat};
719 ///
720 /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
721 /// if let Some(left) = frame.channel_as_f32(0) {
722 /// assert_eq!(left.len(), 1024);
723 /// }
724 /// ```
725 #[must_use]
726 #[allow(unsafe_code, clippy::cast_ptr_alignment)]
727 pub fn channel_as_f32(&self, channel: usize) -> Option<&[f32]> {
728 if self.format != SampleFormat::F32p {
729 return None;
730 }
731
732 self.channel(channel).map(|bytes| {
733 let ptr = bytes.as_ptr().cast::<f32>();
734 let len = bytes.len() / std::mem::size_of::<f32>();
735 unsafe { std::slice::from_raw_parts(ptr, len) }
736 })
737 }
738
739 /// Returns mutable access to a channel's data as an f32 slice.
740 ///
741 /// Works for planar F32p format.
742 #[must_use]
743 #[allow(unsafe_code, clippy::cast_ptr_alignment)]
744 pub fn channel_as_f32_mut(&mut self, channel: usize) -> Option<&mut [f32]> {
745 if self.format != SampleFormat::F32p {
746 return None;
747 }
748
749 self.channel_mut(channel).map(|bytes| {
750 let ptr = bytes.as_mut_ptr().cast::<f32>();
751 let len = bytes.len() / std::mem::size_of::<f32>();
752 unsafe { std::slice::from_raw_parts_mut(ptr, len) }
753 })
754 }
755
756 /// Returns a specific channel's data as an i16 slice.
757 ///
758 /// Works for planar I16p format.
759 ///
760 /// # Arguments
761 ///
762 /// * `channel` - The channel index
763 ///
764 /// # Examples
765 ///
766 /// ```
767 /// use ff_format::{AudioFrame, SampleFormat};
768 ///
769 /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::I16p).unwrap();
770 /// if let Some(left) = frame.channel_as_i16(0) {
771 /// assert_eq!(left.len(), 1024);
772 /// }
773 /// ```
774 #[must_use]
775 #[allow(unsafe_code, clippy::cast_ptr_alignment)]
776 pub fn channel_as_i16(&self, channel: usize) -> Option<&[i16]> {
777 if self.format != SampleFormat::I16p {
778 return None;
779 }
780
781 self.channel(channel).map(|bytes| {
782 let ptr = bytes.as_ptr().cast::<i16>();
783 let len = bytes.len() / std::mem::size_of::<i16>();
784 unsafe { std::slice::from_raw_parts(ptr, len) }
785 })
786 }
787
788 /// Returns mutable access to a channel's data as an i16 slice.
789 ///
790 /// Works for planar I16p format.
791 #[must_use]
792 #[allow(unsafe_code, clippy::cast_ptr_alignment)]
793 pub fn channel_as_i16_mut(&mut self, channel: usize) -> Option<&mut [i16]> {
794 if self.format != SampleFormat::I16p {
795 return None;
796 }
797
798 self.channel_mut(channel).map(|bytes| {
799 let ptr = bytes.as_mut_ptr().cast::<i16>();
800 let len = bytes.len() / std::mem::size_of::<i16>();
801 unsafe { std::slice::from_raw_parts_mut(ptr, len) }
802 })
803 }
804
805 // ==========================================================================
806 // Utility Methods
807 // ==========================================================================
808
809 /// Returns the total size in bytes of all sample data.
810 ///
811 /// # Examples
812 ///
813 /// ```
814 /// use ff_format::{AudioFrame, SampleFormat};
815 ///
816 /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
817 /// assert_eq!(frame.total_size(), 1024 * 2 * 4);
818 /// ```
819 #[must_use]
820 pub fn total_size(&self) -> usize {
821 self.planes.iter().map(Vec::len).sum()
822 }
823
824 /// Returns the size in bytes of a single sample (one channel).
825 ///
826 /// # Examples
827 ///
828 /// ```
829 /// use ff_format::{AudioFrame, SampleFormat};
830 ///
831 /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
832 /// assert_eq!(frame.bytes_per_sample(), 4);
833 ///
834 /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::I16).unwrap();
835 /// assert_eq!(frame.bytes_per_sample(), 2);
836 /// ```
837 #[must_use]
838 #[inline]
839 pub fn bytes_per_sample(&self) -> usize {
840 self.format.bytes_per_sample()
841 }
842
843 /// Returns the total number of samples across all channels (`samples * channels`).
844 ///
845 /// # Examples
846 ///
847 /// ```
848 /// use ff_format::{AudioFrame, SampleFormat};
849 ///
850 /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
851 /// assert_eq!(frame.sample_count(), 2048);
852 /// ```
853 #[must_use]
854 #[inline]
855 pub fn sample_count(&self) -> usize {
856 self.samples * self.channels as usize
857 }
858
859 // ==========================================================================
860 // PCM Conversion
861 // ==========================================================================
862
863 /// Converts the audio frame to interleaved 32-bit float PCM.
864 ///
865 /// All [`SampleFormat`] variants are supported. Planar formats are transposed
866 /// to interleaved layout (L0 R0 L1 R1 ...). Returns an empty `Vec` for
867 /// [`SampleFormat::Other`].
868 ///
869 /// # Scaling
870 ///
871 /// | Source format | Normalization |
872 /// |---|---|
873 /// | U8 | `(sample − 128) / 128.0` → `[−1.0, 1.0]` |
874 /// | I16 | `sample / 32767.0` → `[−1.0, 1.0]` |
875 /// | I32 | `sample / 2147483647.0` → `[−1.0, 1.0]` |
876 /// | F32 | identity |
877 /// | F64 | narrowed to f32 (`as f32`) |
878 ///
879 /// # Examples
880 ///
881 /// ```
882 /// use ff_format::{AudioFrame, SampleFormat};
883 ///
884 /// let frame = AudioFrame::empty(4, 2, 48000, SampleFormat::F32p).unwrap();
885 /// let pcm = frame.to_f32_interleaved();
886 /// assert_eq!(pcm.len(), 8); // 4 samples × 2 channels
887 /// ```
888 #[must_use]
889 #[allow(
890 clippy::cast_possible_truncation,
891 clippy::cast_precision_loss,
892 clippy::too_many_lines
893 )]
894 pub fn to_f32_interleaved(&self) -> Vec<f32> {
895 let total = self.sample_count();
896 if total == 0 {
897 return Vec::new();
898 }
899
900 match self.format {
901 SampleFormat::F32 => self.as_f32().map(<[f32]>::to_vec).unwrap_or_default(),
902 SampleFormat::F32p => {
903 let mut out = vec![0f32; total];
904 let ch_count = self.channels as usize;
905 for ch in 0..ch_count {
906 if let Some(plane) = self.channel_as_f32(ch) {
907 for (i, &s) in plane.iter().enumerate() {
908 out[i * ch_count + ch] = s;
909 }
910 }
911 }
912 out
913 }
914 SampleFormat::F64 => {
915 let bytes = self.data();
916 bytes
917 .chunks_exact(8)
918 .map(|b| {
919 f64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]) as f32
920 })
921 .collect()
922 }
923 SampleFormat::F64p => {
924 let mut out = vec![0f32; total];
925 let ch_count = self.channels as usize;
926 for ch in 0..ch_count {
927 if let Some(bytes) = self.channel(ch) {
928 for (i, b) in bytes.chunks_exact(8).enumerate() {
929 out[i * ch_count + ch] = f64::from_le_bytes([
930 b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
931 ]) as f32;
932 }
933 }
934 }
935 out
936 }
937 SampleFormat::I16 => {
938 let bytes = self.data();
939 bytes
940 .chunks_exact(2)
941 .map(|b| f32::from(i16::from_le_bytes([b[0], b[1]])) / f32::from(i16::MAX))
942 .collect()
943 }
944 SampleFormat::I16p => {
945 let mut out = vec![0f32; total];
946 let ch_count = self.channels as usize;
947 for ch in 0..ch_count {
948 if let Some(bytes) = self.channel(ch) {
949 for (i, b) in bytes.chunks_exact(2).enumerate() {
950 out[i * ch_count + ch] =
951 f32::from(i16::from_le_bytes([b[0], b[1]])) / f32::from(i16::MAX);
952 }
953 }
954 }
955 out
956 }
957 SampleFormat::I32 => {
958 let bytes = self.data();
959 bytes
960 .chunks_exact(4)
961 .map(|b| i32::from_le_bytes([b[0], b[1], b[2], b[3]]) as f32 / i32::MAX as f32)
962 .collect()
963 }
964 SampleFormat::I32p => {
965 let mut out = vec![0f32; total];
966 let ch_count = self.channels as usize;
967 for ch in 0..ch_count {
968 if let Some(bytes) = self.channel(ch) {
969 for (i, b) in bytes.chunks_exact(4).enumerate() {
970 out[i * ch_count + ch] = i32::from_le_bytes([b[0], b[1], b[2], b[3]])
971 as f32
972 / i32::MAX as f32;
973 }
974 }
975 }
976 out
977 }
978 SampleFormat::U8 => {
979 let bytes = self.data();
980 bytes
981 .iter()
982 .map(|&b| (f32::from(b) - 128.0) / 128.0)
983 .collect()
984 }
985 SampleFormat::U8p => {
986 let mut out = vec![0f32; total];
987 let ch_count = self.channels as usize;
988 for ch in 0..ch_count {
989 if let Some(bytes) = self.channel(ch) {
990 for (i, &b) in bytes.iter().enumerate() {
991 out[i * ch_count + ch] = (f32::from(b) - 128.0) / 128.0;
992 }
993 }
994 }
995 out
996 }
997 SampleFormat::Other(_) => Vec::new(),
998 }
999 }
1000
1001 /// Converts the audio frame to interleaved 16-bit signed integer PCM.
1002 ///
1003 /// Suitable for use with `rodio::buffer::SamplesBuffer<i16>`. All
1004 /// [`SampleFormat`] variants are supported. Returns an empty `Vec` for
1005 /// [`SampleFormat::Other`].
1006 ///
1007 /// # Scaling
1008 ///
1009 /// | Source format | Conversion |
1010 /// |---|---|
1011 /// | I16 | identity |
1012 /// | I32 | `sample >> 16` (high 16 bits) |
1013 /// | U8 | `(sample − 128) << 8` |
1014 /// | F32/F64 | `clamp(−1, 1) × 32767`, truncated |
1015 ///
1016 /// # Examples
1017 ///
1018 /// ```
1019 /// use ff_format::{AudioFrame, SampleFormat};
1020 ///
1021 /// let frame = AudioFrame::empty(4, 2, 48000, SampleFormat::I16p).unwrap();
1022 /// let pcm = frame.to_i16_interleaved();
1023 /// assert_eq!(pcm.len(), 8);
1024 /// ```
1025 #[must_use]
1026 #[allow(clippy::cast_possible_truncation, clippy::too_many_lines)] // float→i16 and i32→i16 are intentional truncations
1027 pub fn to_i16_interleaved(&self) -> Vec<i16> {
1028 let total = self.sample_count();
1029 if total == 0 {
1030 return Vec::new();
1031 }
1032
1033 match self.format {
1034 SampleFormat::I16 => self.as_i16().map(<[i16]>::to_vec).unwrap_or_default(),
1035 SampleFormat::I16p => {
1036 let mut out = vec![0i16; total];
1037 let ch_count = self.channels as usize;
1038 for ch in 0..ch_count {
1039 if let Some(plane) = self.channel_as_i16(ch) {
1040 for (i, &s) in plane.iter().enumerate() {
1041 out[i * ch_count + ch] = s;
1042 }
1043 }
1044 }
1045 out
1046 }
1047 SampleFormat::F32 => {
1048 let bytes = self.data();
1049 bytes
1050 .chunks_exact(4)
1051 .map(|b| {
1052 let s = f32::from_le_bytes([b[0], b[1], b[2], b[3]]);
1053 (s.clamp(-1.0, 1.0) * f32::from(i16::MAX)) as i16
1054 })
1055 .collect()
1056 }
1057 SampleFormat::F32p => {
1058 let mut out = vec![0i16; total];
1059 let ch_count = self.channels as usize;
1060 for ch in 0..ch_count {
1061 if let Some(bytes) = self.channel(ch) {
1062 for (i, b) in bytes.chunks_exact(4).enumerate() {
1063 let s = f32::from_le_bytes([b[0], b[1], b[2], b[3]]);
1064 out[i * ch_count + ch] =
1065 (s.clamp(-1.0, 1.0) * f32::from(i16::MAX)) as i16;
1066 }
1067 }
1068 }
1069 out
1070 }
1071 SampleFormat::F64 => {
1072 let bytes = self.data();
1073 bytes
1074 .chunks_exact(8)
1075 .map(|b| {
1076 let s =
1077 f64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]);
1078 (s.clamp(-1.0, 1.0) * f64::from(i16::MAX)) as i16
1079 })
1080 .collect()
1081 }
1082 SampleFormat::F64p => {
1083 let mut out = vec![0i16; total];
1084 let ch_count = self.channels as usize;
1085 for ch in 0..ch_count {
1086 if let Some(bytes) = self.channel(ch) {
1087 for (i, b) in bytes.chunks_exact(8).enumerate() {
1088 let s = f64::from_le_bytes([
1089 b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
1090 ]);
1091 out[i * ch_count + ch] =
1092 (s.clamp(-1.0, 1.0) * f64::from(i16::MAX)) as i16;
1093 }
1094 }
1095 }
1096 out
1097 }
1098 SampleFormat::I32 => {
1099 let bytes = self.data();
1100 bytes
1101 .chunks_exact(4)
1102 .map(|b| (i32::from_le_bytes([b[0], b[1], b[2], b[3]]) >> 16) as i16)
1103 .collect()
1104 }
1105 SampleFormat::I32p => {
1106 let mut out = vec![0i16; total];
1107 let ch_count = self.channels as usize;
1108 for ch in 0..ch_count {
1109 if let Some(bytes) = self.channel(ch) {
1110 for (i, b) in bytes.chunks_exact(4).enumerate() {
1111 out[i * ch_count + ch] =
1112 (i32::from_le_bytes([b[0], b[1], b[2], b[3]]) >> 16) as i16;
1113 }
1114 }
1115 }
1116 out
1117 }
1118 SampleFormat::U8 => {
1119 let bytes = self.data();
1120 bytes.iter().map(|&b| (i16::from(b) - 128) << 8).collect()
1121 }
1122 SampleFormat::U8p => {
1123 let mut out = vec![0i16; total];
1124 let ch_count = self.channels as usize;
1125 for ch in 0..ch_count {
1126 if let Some(bytes) = self.channel(ch) {
1127 for (i, &b) in bytes.iter().enumerate() {
1128 out[i * ch_count + ch] = (i16::from(b) - 128) << 8;
1129 }
1130 }
1131 }
1132 out
1133 }
1134 SampleFormat::Other(_) => Vec::new(),
1135 }
1136 }
1137}
1138
1139impl fmt::Debug for AudioFrame {
1140 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1141 f.debug_struct("AudioFrame")
1142 .field("samples", &self.samples)
1143 .field("channels", &self.channels)
1144 .field("sample_rate", &self.sample_rate)
1145 .field("format", &self.format)
1146 .field("timestamp", &self.timestamp)
1147 .field("num_planes", &self.planes.len())
1148 .field(
1149 "plane_sizes",
1150 &self.planes.iter().map(Vec::len).collect::<Vec<_>>(),
1151 )
1152 .finish()
1153 }
1154}
1155
1156impl fmt::Display for AudioFrame {
1157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1158 let duration_ms = self.duration().as_secs_f64() * 1000.0;
1159 write!(
1160 f,
1161 "AudioFrame({} samples, {}ch, {}Hz, {} @ {}, {:.2}ms)",
1162 self.samples, self.channels, self.sample_rate, self.format, self.timestamp, duration_ms
1163 )
1164 }
1165}
1166
1167impl Default for AudioFrame {
1168 /// Returns a default empty mono F32 frame with 0 samples.
1169 fn default() -> Self {
1170 Self {
1171 planes: vec![vec![]],
1172 samples: 0,
1173 channels: 1,
1174 sample_rate: 48000,
1175 format: SampleFormat::F32,
1176 timestamp: Timestamp::default(),
1177 }
1178 }
1179}
1180
1181#[cfg(test)]
1182#[allow(clippy::unwrap_used, clippy::redundant_closure_for_method_calls)]
1183mod tests {
1184 use super::*;
1185 use crate::Rational;
1186
1187 // ==========================================================================
1188 // Construction Tests
1189 // ==========================================================================
1190
1191 #[test]
1192 fn test_new_packed_f32() {
1193 let samples = 1024;
1194 let channels = 2u32;
1195 let bytes_per_sample = 4;
1196 let data = vec![0u8; samples * channels as usize * bytes_per_sample];
1197
1198 let frame = AudioFrame::new(
1199 vec![data],
1200 samples,
1201 channels,
1202 48000,
1203 SampleFormat::F32,
1204 Timestamp::default(),
1205 )
1206 .unwrap();
1207
1208 assert_eq!(frame.samples(), 1024);
1209 assert_eq!(frame.channels(), 2);
1210 assert_eq!(frame.sample_rate(), 48000);
1211 assert_eq!(frame.format(), SampleFormat::F32);
1212 assert_eq!(frame.num_planes(), 1);
1213 }
1214
1215 #[test]
1216 fn test_new_planar_f32p() {
1217 let samples = 1024;
1218 let channels = 2u32;
1219 let bytes_per_sample = 4;
1220 let plane_size = samples * bytes_per_sample;
1221
1222 let planes = vec![vec![0u8; plane_size], vec![0u8; plane_size]];
1223
1224 let frame = AudioFrame::new(
1225 planes,
1226 samples,
1227 channels,
1228 48000,
1229 SampleFormat::F32p,
1230 Timestamp::default(),
1231 )
1232 .unwrap();
1233
1234 assert_eq!(frame.samples(), 1024);
1235 assert_eq!(frame.channels(), 2);
1236 assert_eq!(frame.format(), SampleFormat::F32p);
1237 assert_eq!(frame.num_planes(), 2);
1238 }
1239
1240 #[test]
1241 fn test_new_invalid_plane_count_packed() {
1242 // Packed format should have 1 plane, but we provide 2
1243 let result = AudioFrame::new(
1244 vec![vec![0u8; 100], vec![0u8; 100]],
1245 100,
1246 2,
1247 48000,
1248 SampleFormat::F32,
1249 Timestamp::default(),
1250 );
1251
1252 assert!(result.is_err());
1253 assert_eq!(
1254 result.unwrap_err(),
1255 FrameError::InvalidPlaneCount {
1256 expected: 1,
1257 actual: 2
1258 }
1259 );
1260 }
1261
1262 #[test]
1263 fn test_new_invalid_plane_count_planar() {
1264 // Planar format with 2 channels should have 2 planes, but we provide 1
1265 let result = AudioFrame::new(
1266 vec![vec![0u8; 100]],
1267 100,
1268 2,
1269 48000,
1270 SampleFormat::F32p,
1271 Timestamp::default(),
1272 );
1273
1274 assert!(result.is_err());
1275 assert_eq!(
1276 result.unwrap_err(),
1277 FrameError::InvalidPlaneCount {
1278 expected: 2,
1279 actual: 1
1280 }
1281 );
1282 }
1283
1284 #[test]
1285 fn test_empty_packed() {
1286 let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1287
1288 assert_eq!(frame.samples(), 1024);
1289 assert_eq!(frame.channels(), 2);
1290 assert_eq!(frame.sample_rate(), 48000);
1291 assert_eq!(frame.format(), SampleFormat::F32);
1292 assert_eq!(frame.num_planes(), 1);
1293 assert_eq!(frame.total_size(), 1024 * 2 * 4);
1294 }
1295
1296 #[test]
1297 fn test_empty_planar() {
1298 let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
1299
1300 assert_eq!(frame.num_planes(), 2);
1301 assert_eq!(frame.plane(0).map(|p| p.len()), Some(1024 * 4));
1302 assert_eq!(frame.plane(1).map(|p| p.len()), Some(1024 * 4));
1303 assert_eq!(frame.total_size(), 1024 * 2 * 4);
1304 }
1305
1306 #[test]
1307 fn test_empty_i16() {
1308 let frame = AudioFrame::empty(1024, 2, 44100, SampleFormat::I16).unwrap();
1309
1310 assert_eq!(frame.bytes_per_sample(), 2);
1311 assert_eq!(frame.total_size(), 1024 * 2 * 2);
1312 }
1313
1314 #[test]
1315 fn test_empty_other_format_error() {
1316 let result = AudioFrame::empty(1024, 2, 48000, SampleFormat::Other(999));
1317
1318 assert!(result.is_err());
1319 assert_eq!(
1320 result.unwrap_err(),
1321 FrameError::UnsupportedSampleFormat(SampleFormat::Other(999))
1322 );
1323 }
1324
1325 #[test]
1326 fn test_default() {
1327 let frame = AudioFrame::default();
1328
1329 assert_eq!(frame.samples(), 0);
1330 assert_eq!(frame.channels(), 1);
1331 assert_eq!(frame.sample_rate(), 48000);
1332 assert_eq!(frame.format(), SampleFormat::F32);
1333 }
1334
1335 // ==========================================================================
1336 // Metadata Tests
1337 // ==========================================================================
1338
1339 #[test]
1340 fn test_duration() {
1341 // 1024 samples at 48kHz = 0.02133... seconds
1342 let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1343 let duration = frame.duration();
1344 assert!((duration.as_secs_f64() - 0.021_333_333).abs() < 0.000_001);
1345
1346 // 48000 samples at 48kHz = 1 second
1347 let frame = AudioFrame::empty(48000, 2, 48000, SampleFormat::F32).unwrap();
1348 assert_eq!(frame.duration().as_secs(), 1);
1349 }
1350
1351 #[test]
1352 fn test_duration_zero_sample_rate() {
1353 let frame = AudioFrame::new(
1354 vec![vec![]],
1355 0,
1356 1,
1357 0,
1358 SampleFormat::F32,
1359 Timestamp::default(),
1360 )
1361 .unwrap();
1362
1363 assert_eq!(frame.duration(), Duration::ZERO);
1364 }
1365
1366 #[test]
1367 fn test_set_timestamp() {
1368 let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1369 let ts = Timestamp::new(48000, Rational::new(1, 48000));
1370
1371 frame.set_timestamp(ts);
1372 assert_eq!(frame.timestamp(), ts);
1373 }
1374
1375 // ==========================================================================
1376 // Plane Access Tests
1377 // ==========================================================================
1378
1379 #[test]
1380 fn test_plane_access_packed() {
1381 let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1382
1383 assert!(frame.plane(0).is_some());
1384 assert!(frame.plane(1).is_none());
1385 }
1386
1387 #[test]
1388 fn test_plane_access_planar() {
1389 let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
1390
1391 assert!(frame.plane(0).is_some());
1392 assert!(frame.plane(1).is_some());
1393 assert!(frame.plane(2).is_none());
1394 }
1395
1396 #[test]
1397 fn test_plane_mut_access() {
1398 let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1399
1400 if let Some(data) = frame.plane_mut(0) {
1401 data[0] = 255;
1402 }
1403
1404 assert_eq!(frame.plane(0).unwrap()[0], 255);
1405 }
1406
1407 #[test]
1408 fn test_channel_access() {
1409 let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
1410
1411 let left = frame.channel(0).unwrap();
1412 let right = frame.channel(1).unwrap();
1413
1414 assert_eq!(left.len(), 1024 * 4);
1415 assert_eq!(right.len(), 1024 * 4);
1416 }
1417
1418 // ==========================================================================
1419 // Data Access Tests
1420 // ==========================================================================
1421
1422 #[test]
1423 fn test_data_packed() {
1424 let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1425 assert!(!frame.data().is_empty());
1426 assert_eq!(frame.data().len(), 1024 * 2 * 4);
1427 }
1428
1429 #[test]
1430 fn test_data_planar_returns_none() {
1431 let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
1432 assert!(frame.data().is_empty());
1433 }
1434
1435 #[test]
1436 fn test_data_mut() {
1437 let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1438
1439 frame.data_mut()[0] = 123;
1440
1441 assert_eq!(frame.data()[0], 123);
1442 }
1443
1444 // ==========================================================================
1445 // Typed Access Tests
1446 // ==========================================================================
1447
1448 #[test]
1449 fn test_as_f32() {
1450 let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1451 let samples = frame.as_f32().unwrap();
1452 assert_eq!(samples.len(), 1024 * 2);
1453 }
1454
1455 #[test]
1456 fn test_as_f32_wrong_format() {
1457 let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::I16).unwrap();
1458 assert!(frame.as_f32().is_none());
1459 }
1460
1461 #[test]
1462 fn test_as_f32_mut() {
1463 let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1464
1465 if let Some(samples) = frame.as_f32_mut() {
1466 samples[0] = 1.0;
1467 samples[1] = -1.0;
1468 }
1469
1470 let samples = frame.as_f32().unwrap();
1471 assert!((samples[0] - 1.0).abs() < f32::EPSILON);
1472 assert!((samples[1] - (-1.0)).abs() < f32::EPSILON);
1473 }
1474
1475 #[test]
1476 fn test_as_i16() {
1477 let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::I16).unwrap();
1478 let samples = frame.as_i16().unwrap();
1479 assert_eq!(samples.len(), 1024 * 2);
1480 }
1481
1482 #[test]
1483 fn test_as_i16_wrong_format() {
1484 let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1485 assert!(frame.as_i16().is_none());
1486 }
1487
1488 #[test]
1489 fn test_channel_as_f32() {
1490 let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
1491
1492 let left = frame.channel_as_f32(0).unwrap();
1493 let right = frame.channel_as_f32(1).unwrap();
1494
1495 assert_eq!(left.len(), 1024);
1496 assert_eq!(right.len(), 1024);
1497 }
1498
1499 #[test]
1500 fn test_channel_as_f32_wrong_format() {
1501 let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1502 assert!(frame.channel_as_f32(0).is_none()); // F32 is packed, not F32p
1503 }
1504
1505 #[test]
1506 fn test_channel_as_f32_mut() {
1507 let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
1508
1509 if let Some(left) = frame.channel_as_f32_mut(0) {
1510 left[0] = 0.5;
1511 }
1512
1513 assert!((frame.channel_as_f32(0).unwrap()[0] - 0.5).abs() < f32::EPSILON);
1514 }
1515
1516 #[test]
1517 fn test_channel_as_i16() {
1518 let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::I16p).unwrap();
1519
1520 let left = frame.channel_as_i16(0).unwrap();
1521 assert_eq!(left.len(), 1024);
1522 }
1523
1524 #[test]
1525 fn test_channel_as_i16_mut() {
1526 let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::I16p).unwrap();
1527
1528 if let Some(left) = frame.channel_as_i16_mut(0) {
1529 left[0] = 32767;
1530 }
1531
1532 assert_eq!(frame.channel_as_i16(0).unwrap()[0], 32767);
1533 }
1534
1535 // ==========================================================================
1536 // Utility Tests
1537 // ==========================================================================
1538
1539 #[test]
1540 fn test_total_size() {
1541 // Packed stereo F32: 1024 samples * 2 channels * 4 bytes
1542 let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1543 assert_eq!(frame.total_size(), 1024 * 2 * 4);
1544
1545 // Planar stereo F32p: 2 planes * 1024 samples * 4 bytes
1546 let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
1547 assert_eq!(frame.total_size(), 1024 * 4 * 2);
1548 }
1549
1550 #[test]
1551 fn test_bytes_per_sample() {
1552 assert_eq!(
1553 AudioFrame::empty(1024, 2, 48000, SampleFormat::U8)
1554 .unwrap()
1555 .bytes_per_sample(),
1556 1
1557 );
1558 assert_eq!(
1559 AudioFrame::empty(1024, 2, 48000, SampleFormat::I16)
1560 .unwrap()
1561 .bytes_per_sample(),
1562 2
1563 );
1564 assert_eq!(
1565 AudioFrame::empty(1024, 2, 48000, SampleFormat::F32)
1566 .unwrap()
1567 .bytes_per_sample(),
1568 4
1569 );
1570 assert_eq!(
1571 AudioFrame::empty(1024, 2, 48000, SampleFormat::F64)
1572 .unwrap()
1573 .bytes_per_sample(),
1574 8
1575 );
1576 }
1577
1578 // ==========================================================================
1579 // Clone Tests
1580 // ==========================================================================
1581
1582 #[test]
1583 fn test_clone() {
1584 let mut original = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1585 original.set_timestamp(Timestamp::new(1000, Rational::new(1, 1000)));
1586
1587 // Modify some data
1588 if let Some(data) = original.plane_mut(0) {
1589 data[0] = 42;
1590 }
1591
1592 let cloned = original.clone();
1593
1594 // Verify metadata matches
1595 assert_eq!(cloned.samples(), original.samples());
1596 assert_eq!(cloned.channels(), original.channels());
1597 assert_eq!(cloned.sample_rate(), original.sample_rate());
1598 assert_eq!(cloned.format(), original.format());
1599 assert_eq!(cloned.timestamp(), original.timestamp());
1600
1601 // Verify data was cloned
1602 assert_eq!(cloned.plane(0).unwrap()[0], 42);
1603
1604 // Verify it's a deep clone
1605 let mut cloned = cloned;
1606 if let Some(data) = cloned.plane_mut(0) {
1607 data[0] = 99;
1608 }
1609 assert_eq!(original.plane(0).unwrap()[0], 42);
1610 assert_eq!(cloned.plane(0).unwrap()[0], 99);
1611 }
1612
1613 // ==========================================================================
1614 // Display/Debug Tests
1615 // ==========================================================================
1616
1617 #[test]
1618 fn test_debug() {
1619 let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1620 let debug = format!("{frame:?}");
1621 assert!(debug.contains("AudioFrame"));
1622 assert!(debug.contains("1024"));
1623 assert!(debug.contains("48000"));
1624 assert!(debug.contains("F32"));
1625 }
1626
1627 #[test]
1628 fn test_display() {
1629 let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1630 let display = format!("{frame}");
1631 assert!(display.contains("1024 samples"));
1632 assert!(display.contains("2ch"));
1633 assert!(display.contains("48000Hz"));
1634 assert!(display.contains("flt")); // F32 displays as "flt"
1635 }
1636
1637 // ==========================================================================
1638 // All Sample Formats Tests
1639 // ==========================================================================
1640
1641 #[test]
1642 fn test_all_packed_formats() {
1643 let formats = [
1644 SampleFormat::U8,
1645 SampleFormat::I16,
1646 SampleFormat::I32,
1647 SampleFormat::F32,
1648 SampleFormat::F64,
1649 ];
1650
1651 for format in formats {
1652 let frame = AudioFrame::empty(1024, 2, 48000, format).unwrap();
1653 assert_eq!(frame.num_planes(), 1);
1654 assert!(!frame.data().is_empty());
1655 }
1656 }
1657
1658 #[test]
1659 fn test_all_planar_formats() {
1660 let formats = [
1661 SampleFormat::U8p,
1662 SampleFormat::I16p,
1663 SampleFormat::I32p,
1664 SampleFormat::F32p,
1665 SampleFormat::F64p,
1666 ];
1667
1668 for format in formats {
1669 let frame = AudioFrame::empty(1024, 2, 48000, format).unwrap();
1670 assert_eq!(frame.num_planes(), 2);
1671 assert!(frame.data().is_empty());
1672 assert!(frame.channel(0).is_some());
1673 assert!(frame.channel(1).is_some());
1674 }
1675 }
1676
1677 #[test]
1678 fn test_mono_planar() {
1679 let frame = AudioFrame::empty(1024, 1, 48000, SampleFormat::F32p).unwrap();
1680 assert_eq!(frame.num_planes(), 1);
1681 assert_eq!(frame.plane(0).map(|p| p.len()), Some(1024 * 4));
1682 }
1683
1684 #[test]
1685 fn test_surround_planar() {
1686 // 5.1 surround sound
1687 let frame = AudioFrame::empty(1024, 6, 48000, SampleFormat::F32p).unwrap();
1688 assert_eq!(frame.num_planes(), 6);
1689 for i in 0..6 {
1690 assert!(frame.plane(i).is_some());
1691 }
1692 assert!(frame.plane(6).is_none());
1693 }
1694
1695 // ==========================================================================
1696 // data() / data_mut() Tests
1697 // ==========================================================================
1698
1699 #[test]
1700 fn data_packed_should_return_sample_bytes() {
1701 let frame = AudioFrame::empty(4, 2, 48000, SampleFormat::F32).unwrap();
1702 // 4 samples * 2 channels * 4 bytes = 32
1703 assert_eq!(frame.data().len(), 32);
1704 }
1705
1706 #[test]
1707 fn data_planar_should_return_empty_slice() {
1708 let frame = AudioFrame::empty(4, 2, 48000, SampleFormat::F32p).unwrap();
1709 assert!(frame.data().is_empty());
1710 }
1711
1712 #[test]
1713 fn data_mut_packed_should_allow_mutation() {
1714 let mut frame = AudioFrame::empty(4, 1, 48000, SampleFormat::I16).unwrap();
1715 frame.data_mut()[0] = 0x42;
1716 frame.data_mut()[1] = 0x00;
1717 assert_eq!(frame.data()[0], 0x42);
1718 }
1719
1720 #[test]
1721 fn data_mut_planar_should_return_empty_slice() {
1722 let mut frame = AudioFrame::empty(4, 2, 48000, SampleFormat::I16p).unwrap();
1723 assert!(frame.data_mut().is_empty());
1724 }
1725
1726 // ==========================================================================
1727 // PCM Conversion Tests
1728 // ==========================================================================
1729
1730 #[test]
1731 fn sample_count_should_return_samples_times_channels() {
1732 let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1733 assert_eq!(frame.sample_count(), 2048);
1734
1735 let mono = AudioFrame::empty(512, 1, 44100, SampleFormat::I16).unwrap();
1736 assert_eq!(mono.sample_count(), 512);
1737 }
1738
1739 #[test]
1740 fn to_f32_interleaved_f32p_should_transpose_to_interleaved() {
1741 // Stereo F32p: L=[1.0, 2.0], R=[3.0, 4.0]
1742 // Expected interleaved: [1.0, 3.0, 2.0, 4.0]
1743 let left: Vec<u8> = [1.0f32, 2.0f32]
1744 .iter()
1745 .flat_map(|f| f.to_le_bytes())
1746 .collect();
1747 let right: Vec<u8> = [3.0f32, 4.0f32]
1748 .iter()
1749 .flat_map(|f| f.to_le_bytes())
1750 .collect();
1751
1752 let frame = AudioFrame::new(
1753 vec![left, right],
1754 2,
1755 2,
1756 48000,
1757 SampleFormat::F32p,
1758 Timestamp::default(),
1759 )
1760 .unwrap();
1761
1762 let pcm = frame.to_f32_interleaved();
1763 assert_eq!(pcm.len(), 4);
1764 assert!((pcm[0] - 1.0).abs() < f32::EPSILON); // L0
1765 assert!((pcm[1] - 3.0).abs() < f32::EPSILON); // R0
1766 assert!((pcm[2] - 2.0).abs() < f32::EPSILON); // L1
1767 assert!((pcm[3] - 4.0).abs() < f32::EPSILON); // R1
1768 }
1769
1770 #[test]
1771 fn to_f32_interleaved_i16p_should_scale_to_minus_one_to_one() {
1772 // i16::MAX → ~1.0, i16::MIN → ~-1.0, 0 → 0.0
1773 let make_i16_bytes = |v: i16| v.to_le_bytes().to_vec();
1774
1775 let left: Vec<u8> = [i16::MAX, 0i16]
1776 .iter()
1777 .flat_map(|&v| make_i16_bytes(v))
1778 .collect();
1779 let right: Vec<u8> = [i16::MIN, 0i16]
1780 .iter()
1781 .flat_map(|&v| make_i16_bytes(v))
1782 .collect();
1783
1784 let frame = AudioFrame::new(
1785 vec![left, right],
1786 2,
1787 2,
1788 48000,
1789 SampleFormat::I16p,
1790 Timestamp::default(),
1791 )
1792 .unwrap();
1793
1794 let pcm = frame.to_f32_interleaved();
1795 // i16 is asymmetric: MIN=-32768, MAX=32767, so MIN/MAX ≈ -1.00003
1796 // Values should be very close to [-1.0, 1.0]
1797 for &s in &pcm {
1798 assert!(s >= -1.001 && s <= 1.001, "out of range: {s}");
1799 }
1800 assert!((pcm[0] - 1.0).abs() < 0.0001); // i16::MAX → ~1.0
1801 assert!((pcm[1] - (-1.0)).abs() < 0.001); // i16::MIN → ~-1.00003
1802 }
1803
1804 #[test]
1805 fn to_f32_interleaved_unknown_should_return_empty() {
1806 // AudioFrame::new() (unlike empty()) accepts Other(_): Other is treated
1807 // as packed so expected_planes = 1.
1808 let frame = AudioFrame::new(
1809 vec![vec![0u8; 16]],
1810 4,
1811 1,
1812 48000,
1813 SampleFormat::Other(999),
1814 Timestamp::default(),
1815 )
1816 .unwrap();
1817 assert_eq!(frame.to_f32_interleaved(), Vec::<f32>::new());
1818 }
1819
1820 #[test]
1821 fn to_i16_interleaved_i16p_should_transpose_to_interleaved() {
1822 let left: Vec<u8> = [100i16, 200i16]
1823 .iter()
1824 .flat_map(|v| v.to_le_bytes())
1825 .collect();
1826 let right: Vec<u8> = [300i16, 400i16]
1827 .iter()
1828 .flat_map(|v| v.to_le_bytes())
1829 .collect();
1830
1831 let frame = AudioFrame::new(
1832 vec![left, right],
1833 2,
1834 2,
1835 48000,
1836 SampleFormat::I16p,
1837 Timestamp::default(),
1838 )
1839 .unwrap();
1840
1841 let pcm = frame.to_i16_interleaved();
1842 assert_eq!(pcm, vec![100, 300, 200, 400]);
1843 }
1844
1845 #[test]
1846 fn to_i16_interleaved_f32_should_scale_and_clamp() {
1847 // 1.0 → i16::MAX, -1.0 → -i16::MAX, 2.0 → clamped to i16::MAX
1848 let samples: &[f32] = &[1.0, -1.0, 2.0, -2.0];
1849 let bytes: Vec<u8> = samples.iter().flat_map(|f| f.to_le_bytes()).collect();
1850
1851 let frame = AudioFrame::new(
1852 vec![bytes],
1853 4,
1854 1,
1855 48000,
1856 SampleFormat::F32,
1857 Timestamp::default(),
1858 )
1859 .unwrap();
1860
1861 let pcm = frame.to_i16_interleaved();
1862 assert_eq!(pcm.len(), 4);
1863 assert_eq!(pcm[0], i16::MAX);
1864 assert_eq!(pcm[1], -i16::MAX);
1865 // Clamped values
1866 assert_eq!(pcm[2], i16::MAX);
1867 assert_eq!(pcm[3], -i16::MAX);
1868 }
1869}