Skip to main content

ff_format/frame/audio/
core.rs

1//! Constructors, metadata accessors, and trait impls for [`AudioFrame`].
2
3use std::fmt;
4use std::time::Duration;
5
6use crate::error::FrameError;
7use crate::{SampleFormat, Timestamp};
8
9use super::AudioFrame;
10
11impl AudioFrame {
12    /// Creates a new audio frame with the specified parameters.
13    ///
14    /// # Arguments
15    ///
16    /// * `planes` - Audio sample data (1 plane for packed, channels for planar)
17    /// * `samples` - Number of samples per channel
18    /// * `channels` - Number of audio channels
19    /// * `sample_rate` - Sample rate in Hz
20    /// * `format` - Sample format
21    /// * `timestamp` - Presentation timestamp
22    ///
23    /// # Errors
24    ///
25    /// Returns [`FrameError::InvalidPlaneCount`] if the number of planes doesn't
26    /// match the format (1 for packed, channels for planar).
27    ///
28    /// # Examples
29    ///
30    /// ```
31    /// use ff_format::{AudioFrame, SampleFormat, Timestamp};
32    ///
33    /// // Create a mono F32 frame with 1024 samples
34    /// let samples = 1024;
35    /// let bytes_per_sample = 4; // F32
36    /// let data = vec![0u8; samples * bytes_per_sample];
37    ///
38    /// let frame = AudioFrame::new(
39    ///     vec![data],
40    ///     samples,
41    ///     1,
42    ///     48000,
43    ///     SampleFormat::F32,
44    ///     Timestamp::default(),
45    /// ).unwrap();
46    ///
47    /// assert_eq!(frame.samples(), 1024);
48    /// assert_eq!(frame.channels(), 1);
49    /// ```
50    pub fn new(
51        planes: Vec<Vec<u8>>,
52        samples: usize,
53        channels: u32,
54        sample_rate: u32,
55        format: SampleFormat,
56        timestamp: Timestamp,
57    ) -> Result<Self, FrameError> {
58        let expected_planes = if format.is_planar() {
59            channels as usize
60        } else {
61            1
62        };
63
64        if planes.len() != expected_planes {
65            return Err(FrameError::InvalidPlaneCount {
66                expected: expected_planes,
67                actual: planes.len(),
68            });
69        }
70
71        Ok(Self {
72            planes,
73            samples,
74            channels,
75            sample_rate,
76            format,
77            timestamp,
78        })
79    }
80
81    /// Creates an empty audio frame with the specified parameters.
82    ///
83    /// The frame will have properly sized planes filled with zeros.
84    ///
85    /// # Arguments
86    ///
87    /// * `samples` - Number of samples per channel
88    /// * `channels` - Number of audio channels
89    /// * `sample_rate` - Sample rate in Hz
90    /// * `format` - Sample format
91    ///
92    /// # Errors
93    ///
94    /// Returns [`FrameError::UnsupportedSampleFormat`] if the format is
95    /// [`SampleFormat::Other`], as the memory layout cannot be determined.
96    ///
97    /// # Examples
98    ///
99    /// ```
100    /// use ff_format::{AudioFrame, SampleFormat};
101    ///
102    /// // Create a stereo I16 frame
103    /// let frame = AudioFrame::empty(1024, 2, 44100, SampleFormat::I16).unwrap();
104    /// assert_eq!(frame.samples(), 1024);
105    /// assert_eq!(frame.channels(), 2);
106    /// assert_eq!(frame.num_planes(), 1); // Packed format
107    /// ```
108    pub fn empty(
109        samples: usize,
110        channels: u32,
111        sample_rate: u32,
112        format: SampleFormat,
113    ) -> Result<Self, FrameError> {
114        if matches!(format, SampleFormat::Other(_)) {
115            return Err(FrameError::UnsupportedSampleFormat(format));
116        }
117
118        let planes = Self::allocate_planes(samples, channels, format);
119
120        Ok(Self {
121            planes,
122            samples,
123            channels,
124            sample_rate,
125            format,
126            timestamp: Timestamp::default(),
127        })
128    }
129
130    /// Creates a silent audio frame with 1024 zero-filled samples.
131    ///
132    /// `pts_ms` is the presentation timestamp in milliseconds.
133    /// Both planar formats (one plane per channel) and packed formats (single
134    /// interleaved plane) are supported.
135    #[doc(hidden)]
136    #[must_use]
137    pub fn new_silent(sample_rate: u32, channels: u32, format: SampleFormat, pts_ms: i64) -> Self {
138        let samples = 1024usize;
139        let bps = format.bytes_per_sample();
140        let planes = if format.is_planar() {
141            (0..channels as usize)
142                .map(|_| vec![0u8; samples * bps])
143                .collect()
144        } else {
145            vec![vec![0u8; samples * channels as usize * bps]]
146        };
147        let timestamp = Timestamp::from_millis(pts_ms, crate::Rational::new(1, 1000));
148        Self {
149            planes,
150            samples,
151            channels,
152            sample_rate,
153            format,
154            timestamp,
155        }
156    }
157
158    /// Allocates planes for the given parameters.
159    pub(super) fn allocate_planes(
160        samples: usize,
161        channels: u32,
162        format: SampleFormat,
163    ) -> Vec<Vec<u8>> {
164        let bytes_per_sample = format.bytes_per_sample();
165
166        if format.is_planar() {
167            // Planar: one plane per channel
168            let plane_size = samples * bytes_per_sample;
169            (0..channels).map(|_| vec![0u8; plane_size]).collect()
170        } else {
171            // Packed: single plane with interleaved samples
172            let total_size = samples * channels as usize * bytes_per_sample;
173            vec![vec![0u8; total_size]]
174        }
175    }
176
177    // ==========================================================================
178    // Metadata Accessors
179    // ==========================================================================
180
181    /// Returns the number of samples per channel.
182    ///
183    /// # Examples
184    ///
185    /// ```
186    /// use ff_format::{AudioFrame, SampleFormat};
187    ///
188    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
189    /// assert_eq!(frame.samples(), 1024);
190    /// ```
191    #[must_use]
192    #[inline]
193    pub const fn samples(&self) -> usize {
194        self.samples
195    }
196
197    /// Returns the number of audio channels.
198    ///
199    /// The return type is `u32` to match `FFmpeg`'s `AVFrame::ch_layout.nb_channels`
200    /// and professional audio APIs (Core Audio, WASAPI, JACK, Dolby Atmos).
201    ///
202    /// # Integration
203    ///
204    /// Playback libraries such as `rodio` and `cpal` accept channel counts as
205    /// `u16`. Cast with `frame.channels() as u16`; the truncation is always safe
206    /// because no real-world format exceeds `u16::MAX` (65 535) channels.
207    ///
208    /// # Examples
209    ///
210    /// ```
211    /// use ff_format::{AudioFrame, SampleFormat};
212    ///
213    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
214    /// assert_eq!(frame.channels(), 2);
215    /// ```
216    #[must_use]
217    #[inline]
218    pub const fn channels(&self) -> u32 {
219        self.channels
220    }
221
222    /// Returns the sample rate in Hz.
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.sample_rate(), 48000);
231    /// ```
232    #[must_use]
233    #[inline]
234    pub const fn sample_rate(&self) -> u32 {
235        self.sample_rate
236    }
237
238    /// Returns the sample format.
239    ///
240    /// # Examples
241    ///
242    /// ```
243    /// use ff_format::{AudioFrame, SampleFormat};
244    ///
245    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
246    /// assert_eq!(frame.format(), SampleFormat::F32);
247    /// assert!(frame.format().is_float());
248    /// ```
249    #[must_use]
250    #[inline]
251    pub const fn format(&self) -> SampleFormat {
252        self.format
253    }
254
255    /// Returns the presentation timestamp.
256    ///
257    /// # Examples
258    ///
259    /// ```
260    /// use ff_format::{AudioFrame, SampleFormat, Timestamp, Rational};
261    ///
262    /// let ts = Timestamp::new(48000, Rational::new(1, 48000));
263    /// let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
264    /// frame.set_timestamp(ts);
265    /// assert_eq!(frame.timestamp(), ts);
266    /// ```
267    #[must_use]
268    #[inline]
269    pub const fn timestamp(&self) -> Timestamp {
270        self.timestamp
271    }
272
273    /// Sets the presentation timestamp.
274    ///
275    /// # Examples
276    ///
277    /// ```
278    /// use ff_format::{AudioFrame, SampleFormat, Timestamp, Rational};
279    ///
280    /// let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
281    /// let ts = Timestamp::new(48000, Rational::new(1, 48000));
282    /// frame.set_timestamp(ts);
283    /// assert_eq!(frame.timestamp(), ts);
284    /// ```
285    #[inline]
286    pub fn set_timestamp(&mut self, timestamp: Timestamp) {
287        self.timestamp = timestamp;
288    }
289
290    /// Returns the duration of this audio frame.
291    ///
292    /// The duration is calculated as `samples / sample_rate`.
293    ///
294    /// # Examples
295    ///
296    /// ```
297    /// use ff_format::{AudioFrame, SampleFormat};
298    ///
299    /// // 1024 samples at 48kHz = ~21.33ms
300    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
301    /// let duration = frame.duration();
302    /// assert!((duration.as_secs_f64() - 0.02133).abs() < 0.001);
303    ///
304    /// // 48000 samples at 48kHz = 1 second
305    /// let frame = AudioFrame::empty(48000, 2, 48000, SampleFormat::F32).unwrap();
306    /// assert_eq!(frame.duration().as_secs(), 1);
307    /// ```
308    #[must_use]
309    #[allow(clippy::cast_precision_loss)] // Audio frame sample counts are well within f64's precision
310    pub fn duration(&self) -> Duration {
311        if self.sample_rate == 0 {
312            log::warn!(
313                "duration unavailable, sample_rate is 0, returning zero \
314                 samples={} fallback=Duration::ZERO",
315                self.samples
316            );
317            return Duration::ZERO;
318        }
319        let secs = self.samples as f64 / f64::from(self.sample_rate);
320        Duration::from_secs_f64(secs)
321    }
322}
323
324impl fmt::Debug for AudioFrame {
325    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326        f.debug_struct("AudioFrame")
327            .field("samples", &self.samples)
328            .field("channels", &self.channels)
329            .field("sample_rate", &self.sample_rate)
330            .field("format", &self.format)
331            .field("timestamp", &self.timestamp)
332            .field("num_planes", &self.planes.len())
333            .field(
334                "plane_sizes",
335                &self.planes.iter().map(Vec::len).collect::<Vec<_>>(),
336            )
337            .finish()
338    }
339}
340
341impl fmt::Display for AudioFrame {
342    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343        let duration_ms = self.duration().as_secs_f64() * 1000.0;
344        write!(
345            f,
346            "AudioFrame({} samples, {}ch, {}Hz, {} @ {}, {:.2}ms)",
347            self.samples, self.channels, self.sample_rate, self.format, self.timestamp, duration_ms
348        )
349    }
350}
351
352impl Default for AudioFrame {
353    /// Returns a default empty mono F32 frame with 0 samples.
354    fn default() -> Self {
355        Self {
356            planes: vec![vec![]],
357            samples: 0,
358            channels: 1,
359            sample_rate: 48000,
360            format: SampleFormat::F32,
361            timestamp: Timestamp::default(),
362        }
363    }
364}
365
366#[cfg(test)]
367#[allow(clippy::unwrap_used, clippy::redundant_closure_for_method_calls)]
368mod tests {
369    use super::*;
370    use crate::Rational;
371
372    #[test]
373    fn test_new_packed_f32() {
374        let samples = 1024;
375        let channels = 2u32;
376        let bytes_per_sample = 4;
377        let data = vec![0u8; samples * channels as usize * bytes_per_sample];
378
379        let frame = AudioFrame::new(
380            vec![data],
381            samples,
382            channels,
383            48000,
384            SampleFormat::F32,
385            Timestamp::default(),
386        )
387        .unwrap();
388
389        assert_eq!(frame.samples(), 1024);
390        assert_eq!(frame.channels(), 2);
391        assert_eq!(frame.sample_rate(), 48000);
392        assert_eq!(frame.format(), SampleFormat::F32);
393        assert_eq!(frame.num_planes(), 1);
394    }
395
396    #[test]
397    fn test_new_planar_f32p() {
398        let samples = 1024;
399        let channels = 2u32;
400        let bytes_per_sample = 4;
401        let plane_size = samples * bytes_per_sample;
402
403        let planes = vec![vec![0u8; plane_size], vec![0u8; plane_size]];
404
405        let frame = AudioFrame::new(
406            planes,
407            samples,
408            channels,
409            48000,
410            SampleFormat::F32p,
411            Timestamp::default(),
412        )
413        .unwrap();
414
415        assert_eq!(frame.samples(), 1024);
416        assert_eq!(frame.channels(), 2);
417        assert_eq!(frame.format(), SampleFormat::F32p);
418        assert_eq!(frame.num_planes(), 2);
419    }
420
421    #[test]
422    fn test_new_invalid_plane_count_packed() {
423        // Packed format should have 1 plane, but we provide 2
424        let result = AudioFrame::new(
425            vec![vec![0u8; 100], vec![0u8; 100]],
426            100,
427            2,
428            48000,
429            SampleFormat::F32,
430            Timestamp::default(),
431        );
432
433        assert!(result.is_err());
434        assert_eq!(
435            result.unwrap_err(),
436            FrameError::InvalidPlaneCount {
437                expected: 1,
438                actual: 2
439            }
440        );
441    }
442
443    #[test]
444    fn test_new_invalid_plane_count_planar() {
445        // Planar format with 2 channels should have 2 planes, but we provide 1
446        let result = AudioFrame::new(
447            vec![vec![0u8; 100]],
448            100,
449            2,
450            48000,
451            SampleFormat::F32p,
452            Timestamp::default(),
453        );
454
455        assert!(result.is_err());
456        assert_eq!(
457            result.unwrap_err(),
458            FrameError::InvalidPlaneCount {
459                expected: 2,
460                actual: 1
461            }
462        );
463    }
464
465    #[test]
466    fn test_empty_packed() {
467        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
468
469        assert_eq!(frame.samples(), 1024);
470        assert_eq!(frame.channels(), 2);
471        assert_eq!(frame.sample_rate(), 48000);
472        assert_eq!(frame.format(), SampleFormat::F32);
473        assert_eq!(frame.num_planes(), 1);
474        assert_eq!(frame.total_size(), 1024 * 2 * 4);
475    }
476
477    #[test]
478    fn test_empty_planar() {
479        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
480
481        assert_eq!(frame.num_planes(), 2);
482        assert_eq!(frame.plane(0).map(|p| p.len()), Some(1024 * 4));
483        assert_eq!(frame.plane(1).map(|p| p.len()), Some(1024 * 4));
484        assert_eq!(frame.total_size(), 1024 * 2 * 4);
485    }
486
487    #[test]
488    fn test_empty_i16() {
489        let frame = AudioFrame::empty(1024, 2, 44100, SampleFormat::I16).unwrap();
490
491        assert_eq!(frame.bytes_per_sample(), 2);
492        assert_eq!(frame.total_size(), 1024 * 2 * 2);
493    }
494
495    #[test]
496    fn test_empty_other_format_error() {
497        let result = AudioFrame::empty(1024, 2, 48000, SampleFormat::Other(999));
498
499        assert!(result.is_err());
500        assert_eq!(
501            result.unwrap_err(),
502            FrameError::UnsupportedSampleFormat(SampleFormat::Other(999))
503        );
504    }
505
506    #[test]
507    fn test_default() {
508        let frame = AudioFrame::default();
509
510        assert_eq!(frame.samples(), 0);
511        assert_eq!(frame.channels(), 1);
512        assert_eq!(frame.sample_rate(), 48000);
513        assert_eq!(frame.format(), SampleFormat::F32);
514    }
515
516    #[test]
517    fn test_duration() {
518        // 1024 samples at 48kHz = 0.02133... seconds
519        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
520        let duration = frame.duration();
521        assert!((duration.as_secs_f64() - 0.021_333_333).abs() < 0.000_001);
522
523        // 48000 samples at 48kHz = 1 second
524        let frame = AudioFrame::empty(48000, 2, 48000, SampleFormat::F32).unwrap();
525        assert_eq!(frame.duration().as_secs(), 1);
526    }
527
528    #[test]
529    fn test_duration_zero_sample_rate() {
530        let frame = AudioFrame::new(
531            vec![vec![]],
532            0,
533            1,
534            0,
535            SampleFormat::F32,
536            Timestamp::default(),
537        )
538        .unwrap();
539
540        assert_eq!(frame.duration(), Duration::ZERO);
541    }
542
543    #[test]
544    fn test_set_timestamp() {
545        let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
546        let ts = Timestamp::new(48000, Rational::new(1, 48000));
547
548        frame.set_timestamp(ts);
549        assert_eq!(frame.timestamp(), ts);
550    }
551
552    #[test]
553    fn test_clone() {
554        let mut original = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
555        original.set_timestamp(Timestamp::new(1000, Rational::new(1, 1000)));
556
557        // Modify some data
558        if let Some(data) = original.plane_mut(0) {
559            data[0] = 42;
560        }
561
562        let cloned = original.clone();
563
564        // Verify metadata matches
565        assert_eq!(cloned.samples(), original.samples());
566        assert_eq!(cloned.channels(), original.channels());
567        assert_eq!(cloned.sample_rate(), original.sample_rate());
568        assert_eq!(cloned.format(), original.format());
569        assert_eq!(cloned.timestamp(), original.timestamp());
570
571        // Verify data was cloned
572        assert_eq!(cloned.plane(0).unwrap()[0], 42);
573
574        // Verify it's a deep clone
575        let mut cloned = cloned;
576        if let Some(data) = cloned.plane_mut(0) {
577            data[0] = 99;
578        }
579        assert_eq!(original.plane(0).unwrap()[0], 42);
580        assert_eq!(cloned.plane(0).unwrap()[0], 99);
581    }
582
583    #[test]
584    fn audio_frame_clone_should_have_identical_data() {
585        let samples = 512;
586        let channels = 2u32;
587        let bytes_per_sample = 4; // F32
588        let plane_data = vec![7u8; samples * bytes_per_sample];
589        let ts = Timestamp::new(500, Rational::new(1, 1000));
590
591        let original = AudioFrame::new(
592            vec![plane_data.clone()],
593            samples,
594            channels,
595            44100,
596            SampleFormat::F32,
597            ts,
598        )
599        .unwrap();
600
601        let clone = original.clone();
602
603        assert_eq!(clone.samples(), original.samples());
604        assert_eq!(clone.channels(), original.channels());
605        assert_eq!(clone.sample_rate(), original.sample_rate());
606        assert_eq!(clone.format(), original.format());
607        assert_eq!(clone.timestamp(), original.timestamp());
608        assert_eq!(clone.num_planes(), original.num_planes());
609        assert_eq!(clone.plane(0), original.plane(0));
610    }
611
612    #[test]
613    fn test_debug() {
614        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
615        let debug = format!("{frame:?}");
616        assert!(debug.contains("AudioFrame"));
617        assert!(debug.contains("1024"));
618        assert!(debug.contains("48000"));
619        assert!(debug.contains("F32"));
620    }
621
622    #[test]
623    fn test_display() {
624        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
625        let display = format!("{frame}");
626        assert!(display.contains("1024 samples"));
627        assert!(display.contains("2ch"));
628        assert!(display.contains("48000Hz"));
629        assert!(display.contains("flt")); // F32 displays as "flt"
630    }
631}