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