Skip to main content

mediadecode_ffmpeg/
sample_format.rs

1//! `SampleFormat` newtype around FFmpeg's `AVSampleFormat` discriminant.
2//!
3//! Same safety stance as [`crate::pix_fmt::PixelFormat`] — never cast an
4//! arbitrary `i32` back into the bindgen `AVSampleFormat` enum (UB when
5//! the value isn't in the build's discriminant set). Wrap the integer in
6//! `SampleFormat` and dispatch on it via the associated constants below.
7//!
8//! Each format is one of:
9//! - **Packed** (interleaved samples for multi-channel audio in one
10//!   plane): `U8`, `S16`, `S32`, `S64`, `FLT`, `DBL`.
11//! - **Planar** (one sample buffer per channel): `U8P`, `S16P`, `S32P`,
12//!   `S64P`, `FLTP`, `DBLP`. The corresponding `AudioFrame` exposes one
13//!   `Plane` per channel rather than a single interleaved buffer.
14
15use core::fmt;
16
17use ffmpeg_next::ffi::AVSampleFormat;
18
19/// Audio sample format identifier.
20#[repr(transparent)]
21#[derive(Copy, Clone, Eq, PartialEq, Hash)]
22pub struct SampleFormat(i32);
23
24impl SampleFormat {
25  /// Constructs a `SampleFormat` from the raw integer FFmpeg uses for
26  /// `AVCodecContext::sample_fmt` / `AVFrame::format` (audio).
27  #[inline]
28  pub const fn from_raw(raw: i32) -> Self {
29    Self(raw)
30  }
31
32  /// Returns the underlying integer.
33  #[inline]
34  pub const fn raw(self) -> i32 {
35    self.0
36  }
37
38  /// Returns `true` if this is a planar (one buffer per channel) format.
39  #[inline]
40  pub const fn is_planar(self) -> bool {
41    matches!(
42      self,
43      Self::U8P | Self::S16P | Self::S32P | Self::S64P | Self::FLTP | Self::DBLP,
44    )
45  }
46
47  /// Returns `true` if this is a packed (interleaved) format.
48  #[inline]
49  pub const fn is_packed(self) -> bool {
50    matches!(
51      self,
52      Self::U8 | Self::S16 | Self::S32 | Self::S64 | Self::FLT | Self::DBL,
53    )
54  }
55
56  /// Bytes per sample for known formats. `None` for [`Self::NONE`] or
57  /// values outside the closed set this newtype enumerates.
58  #[inline]
59  pub const fn bytes_per_sample(self) -> Option<u32> {
60    let bytes = match self {
61      Self::U8 | Self::U8P => 1,
62      Self::S16 | Self::S16P => 2,
63      Self::S32 | Self::S32P | Self::FLT | Self::FLTP => 4,
64      Self::S64 | Self::S64P | Self::DBL | Self::DBLP => 8,
65      _ => return None,
66    };
67    Some(bytes)
68  }
69
70  // --- Sentinel --------------------------------------------------------
71
72  /// Sentinel for "no format" / unset (`AV_SAMPLE_FMT_NONE`, `-1`).
73  pub const NONE: Self = Self(AVSampleFormat::AV_SAMPLE_FMT_NONE as i32);
74
75  // --- Packed (interleaved) --------------------------------------------
76
77  /// Unsigned 8-bit, packed.
78  pub const U8: Self = Self(AVSampleFormat::AV_SAMPLE_FMT_U8 as i32);
79  /// Signed 16-bit, packed.
80  pub const S16: Self = Self(AVSampleFormat::AV_SAMPLE_FMT_S16 as i32);
81  /// Signed 32-bit, packed.
82  pub const S32: Self = Self(AVSampleFormat::AV_SAMPLE_FMT_S32 as i32);
83  /// Signed 64-bit, packed.
84  pub const S64: Self = Self(AVSampleFormat::AV_SAMPLE_FMT_S64 as i32);
85  /// 32-bit float, packed.
86  pub const FLT: Self = Self(AVSampleFormat::AV_SAMPLE_FMT_FLT as i32);
87  /// 64-bit double, packed.
88  pub const DBL: Self = Self(AVSampleFormat::AV_SAMPLE_FMT_DBL as i32);
89
90  // --- Planar (one buffer per channel) ---------------------------------
91
92  /// Unsigned 8-bit, planar.
93  pub const U8P: Self = Self(AVSampleFormat::AV_SAMPLE_FMT_U8P as i32);
94  /// Signed 16-bit, planar.
95  pub const S16P: Self = Self(AVSampleFormat::AV_SAMPLE_FMT_S16P as i32);
96  /// Signed 32-bit, planar.
97  pub const S32P: Self = Self(AVSampleFormat::AV_SAMPLE_FMT_S32P as i32);
98  /// Signed 64-bit, planar.
99  pub const S64P: Self = Self(AVSampleFormat::AV_SAMPLE_FMT_S64P as i32);
100  /// 32-bit float, planar.
101  pub const FLTP: Self = Self(AVSampleFormat::AV_SAMPLE_FMT_FLTP as i32);
102  /// 64-bit double, planar.
103  pub const DBLP: Self = Self(AVSampleFormat::AV_SAMPLE_FMT_DBLP as i32);
104}
105
106impl fmt::Debug for SampleFormat {
107  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108    let name = match *self {
109      Self::NONE => "NONE",
110      Self::U8 => "U8",
111      Self::S16 => "S16",
112      Self::S32 => "S32",
113      Self::S64 => "S64",
114      Self::FLT => "FLT",
115      Self::DBL => "DBL",
116      Self::U8P => "U8P",
117      Self::S16P => "S16P",
118      Self::S32P => "S32P",
119      Self::S64P => "S64P",
120      Self::FLTP => "FLTP",
121      Self::DBLP => "DBLP",
122      _ => return write!(f, "SampleFormat({})", self.0),
123    };
124    write!(f, "SampleFormat::{name}")
125  }
126}
127
128#[cfg(test)]
129mod tests {
130  use super::*;
131
132  #[test]
133  fn known_constants_match_av_values() {
134    assert_eq!(
135      SampleFormat::S16.raw(),
136      AVSampleFormat::AV_SAMPLE_FMT_S16 as i32
137    );
138    assert_eq!(
139      SampleFormat::FLTP.raw(),
140      AVSampleFormat::AV_SAMPLE_FMT_FLTP as i32
141    );
142    assert_eq!(SampleFormat::NONE.raw(), -1);
143  }
144
145  #[test]
146  fn planar_packed_partition_is_complete() {
147    let all_packed = [
148      SampleFormat::U8,
149      SampleFormat::S16,
150      SampleFormat::S32,
151      SampleFormat::S64,
152      SampleFormat::FLT,
153      SampleFormat::DBL,
154    ];
155    let all_planar = [
156      SampleFormat::U8P,
157      SampleFormat::S16P,
158      SampleFormat::S32P,
159      SampleFormat::S64P,
160      SampleFormat::FLTP,
161      SampleFormat::DBLP,
162    ];
163    for f in all_packed {
164      assert!(f.is_packed());
165      assert!(!f.is_planar());
166    }
167    for f in all_planar {
168      assert!(f.is_planar());
169      assert!(!f.is_packed());
170    }
171  }
172
173  #[test]
174  fn bytes_per_sample_matches_width() {
175    assert_eq!(SampleFormat::U8.bytes_per_sample(), Some(1));
176    assert_eq!(SampleFormat::S16.bytes_per_sample(), Some(2));
177    assert_eq!(SampleFormat::S32P.bytes_per_sample(), Some(4));
178    assert_eq!(SampleFormat::FLTP.bytes_per_sample(), Some(4));
179    assert_eq!(SampleFormat::DBL.bytes_per_sample(), Some(8));
180    assert_eq!(SampleFormat::NONE.bytes_per_sample(), None);
181    assert_eq!(SampleFormat::from_raw(99_999).bytes_per_sample(), None);
182  }
183
184  #[test]
185  fn debug_uses_name_for_known_formats() {
186    assert_eq!(format!("{:?}", SampleFormat::S16), "SampleFormat::S16");
187    assert_eq!(format!("{:?}", SampleFormat::FLTP), "SampleFormat::FLTP");
188  }
189
190  #[test]
191  fn debug_falls_back_to_raw_for_unknown() {
192    assert_eq!(
193      format!("{:?}", SampleFormat::from_raw(99_999)),
194      "SampleFormat(99999)"
195    );
196  }
197}