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::{
18  ffi::AVSampleFormat,
19  format::{Sample, sample::Type as SampleType},
20};
21
22/// Audio sample format identifier.
23#[repr(transparent)]
24#[derive(Copy, Clone, Eq, PartialEq, Hash)]
25pub struct SampleFormat(i32);
26
27impl SampleFormat {
28  /// Constructs a `SampleFormat` from the raw integer FFmpeg uses for
29  /// `AVCodecContext::sample_fmt` / `AVFrame::format` (audio).
30  #[inline]
31  pub const fn from_raw(raw: i32) -> Self {
32    Self(raw)
33  }
34
35  /// Returns the underlying integer.
36  #[inline]
37  pub const fn raw(self) -> i32 {
38    self.0
39  }
40
41  /// Returns `true` if this is a planar (one buffer per channel) format.
42  #[inline]
43  pub const fn is_planar(self) -> bool {
44    matches!(
45      self,
46      Self::U8P | Self::S16P | Self::S32P | Self::S64P | Self::FLTP | Self::DBLP,
47    )
48  }
49
50  /// Returns `true` if this is a packed (interleaved) format.
51  #[inline]
52  pub const fn is_packed(self) -> bool {
53    matches!(
54      self,
55      Self::U8 | Self::S16 | Self::S32 | Self::S64 | Self::FLT | Self::DBL,
56    )
57  }
58
59  /// Bytes per sample for known formats. `None` for [`Self::NONE`] or
60  /// values outside the closed set this newtype enumerates.
61  #[inline]
62  pub const fn bytes_per_sample(self) -> Option<u32> {
63    let bytes = match self {
64      Self::U8 | Self::U8P => 1,
65      Self::S16 | Self::S16P => 2,
66      Self::S32 | Self::S32P | Self::FLT | Self::FLTP => 4,
67      Self::S64 | Self::S64P | Self::DBL | Self::DBLP => 8,
68      _ => return None,
69    };
70    Some(bytes)
71  }
72
73  // --- Sentinel --------------------------------------------------------
74
75  /// Sentinel for "no format" / unset (`AV_SAMPLE_FMT_NONE`, `-1`).
76  pub const NONE: Self = Self(AVSampleFormat::AV_SAMPLE_FMT_NONE as i32);
77
78  // --- Packed (interleaved) --------------------------------------------
79
80  /// Unsigned 8-bit, packed.
81  pub const U8: Self = Self(AVSampleFormat::AV_SAMPLE_FMT_U8 as i32);
82  /// Signed 16-bit, packed.
83  pub const S16: Self = Self(AVSampleFormat::AV_SAMPLE_FMT_S16 as i32);
84  /// Signed 32-bit, packed.
85  pub const S32: Self = Self(AVSampleFormat::AV_SAMPLE_FMT_S32 as i32);
86  /// Signed 64-bit, packed.
87  pub const S64: Self = Self(AVSampleFormat::AV_SAMPLE_FMT_S64 as i32);
88  /// 32-bit float, packed.
89  pub const FLT: Self = Self(AVSampleFormat::AV_SAMPLE_FMT_FLT as i32);
90  /// 64-bit double, packed.
91  pub const DBL: Self = Self(AVSampleFormat::AV_SAMPLE_FMT_DBL as i32);
92
93  // --- Planar (one buffer per channel) ---------------------------------
94
95  /// Unsigned 8-bit, planar.
96  pub const U8P: Self = Self(AVSampleFormat::AV_SAMPLE_FMT_U8P as i32);
97  /// Signed 16-bit, planar.
98  pub const S16P: Self = Self(AVSampleFormat::AV_SAMPLE_FMT_S16P as i32);
99  /// Signed 32-bit, planar.
100  pub const S32P: Self = Self(AVSampleFormat::AV_SAMPLE_FMT_S32P as i32);
101  /// Signed 64-bit, planar.
102  pub const S64P: Self = Self(AVSampleFormat::AV_SAMPLE_FMT_S64P as i32);
103  /// 32-bit float, planar.
104  pub const FLTP: Self = Self(AVSampleFormat::AV_SAMPLE_FMT_FLTP as i32);
105  /// 64-bit double, planar.
106  pub const DBLP: Self = Self(AVSampleFormat::AV_SAMPLE_FMT_DBLP as i32);
107
108  /// The [`ffmpeg_next::format::Sample`] this identifier names, or
109  /// `None` for [`Self::NONE`] and for any integer outside the closed
110  /// set above.
111  ///
112  /// The inverse of [`Self::from_raw`], and the direction that has to
113  /// exist for this newtype to be usable as *input* to FFmpeg's safe
114  /// API — `swr_alloc_set_opts2` and `AVFrame` allocation both take the
115  /// format, and a raw integer read out of a container cannot be cast
116  /// back into the bindgen enum to get there. Matching against
117  /// compile-time constants is the way across, exactly as
118  /// [`crate::boundary::from_av_pixel_format`] comes the other way.
119  #[inline]
120  pub const fn to_ffmpeg(self) -> Option<Sample> {
121    Some(match self {
122      Self::U8 => Sample::U8(SampleType::Packed),
123      Self::S16 => Sample::I16(SampleType::Packed),
124      Self::S32 => Sample::I32(SampleType::Packed),
125      Self::S64 => Sample::I64(SampleType::Packed),
126      Self::FLT => Sample::F32(SampleType::Packed),
127      Self::DBL => Sample::F64(SampleType::Packed),
128      Self::U8P => Sample::U8(SampleType::Planar),
129      Self::S16P => Sample::I16(SampleType::Planar),
130      Self::S32P => Sample::I32(SampleType::Planar),
131      Self::S64P => Sample::I64(SampleType::Planar),
132      Self::FLTP => Sample::F32(SampleType::Planar),
133      Self::DBLP => Sample::F64(SampleType::Planar),
134      _ => return None,
135    })
136  }
137
138  /// The identifier for an [`ffmpeg_next::format::Sample`] — the safe
139  /// direction, since `Sample` is ffmpeg-next's own Rust enum and
140  /// converting it to `AVSampleFormat` constructs the bindgen enum from
141  /// a known constant rather than from foreign memory.
142  #[inline]
143  pub fn from_ffmpeg(value: Sample) -> Self {
144    let raw: AVSampleFormat = value.into();
145    Self::from_raw(raw as i32)
146  }
147}
148
149impl fmt::Debug for SampleFormat {
150  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151    let name = match *self {
152      Self::NONE => "NONE",
153      Self::U8 => "U8",
154      Self::S16 => "S16",
155      Self::S32 => "S32",
156      Self::S64 => "S64",
157      Self::FLT => "FLT",
158      Self::DBL => "DBL",
159      Self::U8P => "U8P",
160      Self::S16P => "S16P",
161      Self::S32P => "S32P",
162      Self::S64P => "S64P",
163      Self::FLTP => "FLTP",
164      Self::DBLP => "DBLP",
165      _ => return write!(f, "SampleFormat({})", self.0),
166    };
167    write!(f, "SampleFormat::{name}")
168  }
169}
170
171#[cfg(test)]
172mod tests {
173  use super::*;
174
175  #[test]
176  fn known_constants_match_av_values() {
177    assert_eq!(
178      SampleFormat::S16.raw(),
179      AVSampleFormat::AV_SAMPLE_FMT_S16 as i32
180    );
181    assert_eq!(
182      SampleFormat::FLTP.raw(),
183      AVSampleFormat::AV_SAMPLE_FMT_FLTP as i32
184    );
185    assert_eq!(SampleFormat::NONE.raw(), -1);
186  }
187
188  #[test]
189  fn planar_packed_partition_is_complete() {
190    let all_packed = [
191      SampleFormat::U8,
192      SampleFormat::S16,
193      SampleFormat::S32,
194      SampleFormat::S64,
195      SampleFormat::FLT,
196      SampleFormat::DBL,
197    ];
198    let all_planar = [
199      SampleFormat::U8P,
200      SampleFormat::S16P,
201      SampleFormat::S32P,
202      SampleFormat::S64P,
203      SampleFormat::FLTP,
204      SampleFormat::DBLP,
205    ];
206    for f in all_packed {
207      assert!(f.is_packed());
208      assert!(!f.is_planar());
209    }
210    for f in all_planar {
211      assert!(f.is_planar());
212      assert!(!f.is_packed());
213    }
214  }
215
216  #[test]
217  fn bytes_per_sample_matches_width() {
218    assert_eq!(SampleFormat::U8.bytes_per_sample(), Some(1));
219    assert_eq!(SampleFormat::S16.bytes_per_sample(), Some(2));
220    assert_eq!(SampleFormat::S32P.bytes_per_sample(), Some(4));
221    assert_eq!(SampleFormat::FLTP.bytes_per_sample(), Some(4));
222    assert_eq!(SampleFormat::DBL.bytes_per_sample(), Some(8));
223    assert_eq!(SampleFormat::NONE.bytes_per_sample(), None);
224    assert_eq!(SampleFormat::from_raw(99_999).bytes_per_sample(), None);
225  }
226
227  #[test]
228  fn debug_uses_name_for_known_formats() {
229    assert_eq!(format!("{:?}", SampleFormat::S16), "SampleFormat::S16");
230    assert_eq!(format!("{:?}", SampleFormat::FLTP), "SampleFormat::FLTP");
231  }
232
233  #[test]
234  fn debug_falls_back_to_raw_for_unknown() {
235    assert_eq!(
236      format!("{:?}", SampleFormat::from_raw(99_999)),
237      "SampleFormat(99999)"
238    );
239  }
240}