1pub const FORMAT_LINEAR_PCM: u32 = u32::from_be_bytes(*b"lpcm");
25
26pub const ASBD_SIZE: usize = 40;
31
32pub mod flags {
35 pub const IS_FLOAT: u32 = 1 << 0;
38 pub const IS_BIG_ENDIAN: u32 = 1 << 1;
42 pub const IS_SIGNED_INTEGER: u32 = 1 << 2;
45 pub const IS_PACKED: u32 = 1 << 3;
48 pub const IS_ALIGNED_HIGH: u32 = 1 << 4;
51 pub const IS_NON_INTERLEAVED: u32 = 1 << 5;
54 pub const IS_NON_MIXABLE: u32 = 1 << 6;
57}
58
59#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
67#[non_exhaustive]
68pub enum SampleFormat {
69 Int16,
71 Int24,
73 Int32,
75 Float32,
78}
79
80impl SampleFormat {
81 #[inline]
83 #[must_use]
84 pub const fn bits_per_channel(self) -> u32 {
85 match self {
86 Self::Int16 => 16,
87 Self::Int24 => 24,
88 Self::Int32 | Self::Float32 => 32,
89 }
90 }
91
92 #[inline]
94 #[must_use]
95 pub const fn bytes_per_channel(self) -> u32 {
96 match self {
97 Self::Int16 => 2,
98 Self::Int24 => 3,
99 Self::Int32 | Self::Float32 => 4,
100 }
101 }
102
103 #[inline]
106 #[must_use]
107 pub const fn format_flags(self) -> u32 {
108 match self {
109 Self::Float32 => flags::IS_FLOAT | flags::IS_PACKED,
110 Self::Int16 | Self::Int24 | Self::Int32 => flags::IS_SIGNED_INTEGER | flags::IS_PACKED,
111 }
112 }
113}
114
115#[derive(Copy, Clone, PartialEq, Debug)]
123pub struct StreamFormat {
124 sample_rate: f64,
125 format_id: u32,
126 format_flags: u32,
127 bytes_per_packet: u32,
128 frames_per_packet: u32,
129 bytes_per_frame: u32,
130 channels_per_frame: u32,
131 bits_per_channel: u32,
132}
133
134impl StreamFormat {
135 #[allow(clippy::too_many_arguments)]
144 #[inline]
145 #[must_use]
146 pub const fn from_raw(
147 sample_rate: f64,
148 format_id: u32,
149 format_flags: u32,
150 bytes_per_packet: u32,
151 frames_per_packet: u32,
152 bytes_per_frame: u32,
153 channels_per_frame: u32,
154 bits_per_channel: u32,
155 ) -> Self {
156 Self {
157 sample_rate,
158 format_id,
159 format_flags,
160 bytes_per_packet,
161 frames_per_packet,
162 bytes_per_frame,
163 channels_per_frame,
164 bits_per_channel,
165 }
166 }
167
168 #[must_use]
171 pub const fn linear_pcm(sample: SampleFormat, sample_rate: f64, channels: u32) -> Self {
172 let bytes_per_frame = sample.bytes_per_channel() * channels;
173 Self {
174 sample_rate,
175 format_id: FORMAT_LINEAR_PCM,
176 format_flags: sample.format_flags(),
177 bytes_per_packet: bytes_per_frame,
179 frames_per_packet: 1,
180 bytes_per_frame,
181 channels_per_frame: channels,
182 bits_per_channel: sample.bits_per_channel(),
183 }
184 }
185
186 #[must_use]
189 pub const fn float32(sample_rate: f64, channels: u32) -> Self {
190 Self::linear_pcm(SampleFormat::Float32, sample_rate, channels)
191 }
192
193 #[must_use]
195 pub const fn int16(sample_rate: f64, channels: u32) -> Self {
196 Self::linear_pcm(SampleFormat::Int16, sample_rate, channels)
197 }
198
199 #[must_use]
201 pub const fn int24(sample_rate: f64, channels: u32) -> Self {
202 Self::linear_pcm(SampleFormat::Int24, sample_rate, channels)
203 }
204
205 #[must_use]
207 pub const fn int32(sample_rate: f64, channels: u32) -> Self {
208 Self::linear_pcm(SampleFormat::Int32, sample_rate, channels)
209 }
210
211 #[inline]
213 #[must_use]
214 pub const fn sample_rate(&self) -> f64 {
215 self.sample_rate
216 }
217
218 #[inline]
221 #[must_use]
222 pub const fn format_id(&self) -> u32 {
223 self.format_id
224 }
225
226 #[inline]
228 #[must_use]
229 pub const fn format_flags(&self) -> u32 {
230 self.format_flags
231 }
232
233 #[inline]
235 #[must_use]
236 pub const fn bytes_per_packet(&self) -> u32 {
237 self.bytes_per_packet
238 }
239
240 #[inline]
242 #[must_use]
243 pub const fn frames_per_packet(&self) -> u32 {
244 self.frames_per_packet
245 }
246
247 #[inline]
249 #[must_use]
250 pub const fn bytes_per_frame(&self) -> u32 {
251 self.bytes_per_frame
252 }
253
254 #[inline]
256 #[must_use]
257 pub const fn channels(&self) -> u32 {
258 self.channels_per_frame
259 }
260
261 #[inline]
263 #[must_use]
264 pub const fn bits_per_channel(&self) -> u32 {
265 self.bits_per_channel
266 }
267
268 #[inline]
271 #[must_use]
272 pub const fn is_linear_pcm(&self) -> bool {
273 self.format_id == FORMAT_LINEAR_PCM
274 }
275
276 #[inline]
278 #[must_use]
279 pub const fn is_float(&self) -> bool {
280 self.format_flags & flags::IS_FLOAT != 0
281 }
282
283 #[inline]
285 #[must_use]
286 pub const fn is_packed(&self) -> bool {
287 self.format_flags & flags::IS_PACKED != 0
288 }
289
290 #[inline]
292 #[must_use]
293 pub const fn is_non_interleaved(&self) -> bool {
294 self.format_flags & flags::IS_NON_INTERLEAVED != 0
295 }
296
297 #[must_use]
300 pub fn sample_format(&self) -> Option<SampleFormat> {
301 if !self.is_linear_pcm() || !self.is_packed() {
302 return None;
303 }
304 [
305 SampleFormat::Int16,
306 SampleFormat::Int24,
307 SampleFormat::Int32,
308 SampleFormat::Float32,
309 ]
310 .into_iter()
311 .find(|candidate| {
312 candidate.bits_per_channel() == self.bits_per_channel
313 && candidate.format_flags() == self.format_flags
314 })
315 }
316
317 #[inline]
320 #[must_use]
321 pub fn is_canonical(&self) -> bool {
322 self.sample_format() == Some(SampleFormat::Float32) && !self.is_non_interleaved()
323 }
324}
325
326#[derive(Copy, Clone, PartialEq, Debug)]
340pub enum FormatNegotiation {
341 Accept,
343 Suggest(StreamFormat),
345 Reject,
348}
349
350#[cfg(test)]
351mod tests {
352 use super::*;
353
354 #[test]
355 fn float32_48k_stereo_has_expected_fields() {
356 let f = StreamFormat::float32(48_000.0, 2);
357 assert_eq!(f.sample_rate(), 48_000.0);
358 assert_eq!(f.format_id(), FORMAT_LINEAR_PCM);
359 assert!(f.is_linear_pcm());
360 assert!(f.is_float());
361 assert!(f.is_packed());
362 assert!(!f.is_non_interleaved());
363 assert_eq!(f.channels(), 2);
364 assert_eq!(f.bits_per_channel(), 32);
365 assert_eq!(f.bytes_per_frame(), 8);
366 assert_eq!(f.bytes_per_packet(), 8);
367 assert_eq!(f.frames_per_packet(), 1);
368 }
369
370 #[test]
371 fn int16_44k1_mono_has_expected_fields() {
372 let f = StreamFormat::int16(44_100.0, 1);
373 assert!(!f.is_float());
374 assert_eq!(f.bits_per_channel(), 16);
375 assert_eq!(f.bytes_per_frame(), 2);
376 }
377
378 #[test]
379 fn int24_packs_into_three_bytes() {
380 let f = StreamFormat::int24(48_000.0, 2);
381 assert_eq!(f.bits_per_channel(), 24);
382 assert_eq!(f.bytes_per_frame(), 6);
383 }
384
385 #[test]
386 fn int32_uses_four_byte_words() {
387 let f = StreamFormat::int32(96_000.0, 4);
388 assert_eq!(f.bits_per_channel(), 32);
389 assert_eq!(f.bytes_per_frame(), 16);
390 }
391
392 #[test]
393 fn sample_format_round_trips_through_constructors() {
394 let cases = [
395 (SampleFormat::Int16, StreamFormat::int16(48_000.0, 2)),
396 (SampleFormat::Int24, StreamFormat::int24(48_000.0, 2)),
397 (SampleFormat::Int32, StreamFormat::int32(48_000.0, 2)),
398 (SampleFormat::Float32, StreamFormat::float32(48_000.0, 2)),
399 ];
400 for (sample, format) in cases {
401 assert_eq!(format.sample_format(), Some(sample));
402 }
403 }
404
405 #[test]
406 fn only_float32_interleaved_is_canonical() {
407 assert!(StreamFormat::float32(48_000.0, 2).is_canonical());
408 assert!(!StreamFormat::int16(48_000.0, 2).is_canonical());
409 assert!(!StreamFormat::int32(48_000.0, 2).is_canonical());
410 }
411
412 #[test]
413 fn non_interleaved_float32_is_not_canonical() {
414 let mut raw = StreamFormat::float32(48_000.0, 2);
415 raw = StreamFormat::from_raw(
416 raw.sample_rate(),
417 raw.format_id(),
418 raw.format_flags() | flags::IS_NON_INTERLEAVED,
419 raw.bytes_per_packet(),
420 raw.frames_per_packet(),
421 raw.bytes_per_frame(),
422 raw.channels(),
423 raw.bits_per_channel(),
424 );
425 assert!(raw.is_non_interleaved());
426 assert!(!raw.is_canonical());
427 }
428
429 #[test]
430 fn unknown_format_id_has_no_sample_format() {
431 let raw =
432 StreamFormat::from_raw(48_000.0, u32::from_be_bytes(*b"aac "), 0, 0, 1024, 0, 2, 0);
433 assert!(!raw.is_linear_pcm());
434 assert_eq!(raw.sample_format(), None);
435 assert!(!raw.is_canonical());
436 }
437
438 #[test]
439 fn format_linear_pcm_constant_is_lpcm() {
440 assert_eq!(FORMAT_LINEAR_PCM, 0x6C70_636D);
441 }
442
443 #[test]
444 fn sample_format_flag_sets_are_distinct() {
445 assert_ne!(
446 SampleFormat::Float32.format_flags(),
447 SampleFormat::Int32.format_flags()
448 );
449 assert!(SampleFormat::Float32.format_flags() & flags::IS_FLOAT != 0);
450 assert!(SampleFormat::Int16.format_flags() & flags::IS_SIGNED_INTEGER != 0);
451 }
452
453 #[test]
454 fn negotiation_variants_distinguish() {
455 let accept = FormatNegotiation::Accept;
456 let suggest = FormatNegotiation::Suggest(StreamFormat::float32(48_000.0, 2));
457 let reject = FormatNegotiation::Reject;
458 assert_eq!(accept, FormatNegotiation::Accept);
459 assert_ne!(accept, reject);
460 assert_ne!(suggest, accept);
461 assert_ne!(suggest, reject);
462 }
463}