1pub const WAVE_FORMAT_PCM: u16 = 0x0001;
23pub const WAVE_FORMAT_IEEE_FLOAT: u16 = 0x0003;
25pub const WAVE_FORMAT_EXTENSIBLE: u16 = 0xFFFE;
28
29#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
37pub struct Format {
38 format_tag: u16,
44 channels: u16,
45 samples_per_sec: u32,
46 avg_bytes_per_sec: u32,
47 block_align: u16,
48 bits_per_sample: u16,
49 valid_bits_per_sample: u16,
53 channel_mask: u32,
56 extensible: bool,
62}
63
64impl Format {
65 #[must_use]
73 pub const fn from_raw(
74 format_tag: u16,
75 channels: u16,
76 samples_per_sec: u32,
77 bits_per_sample: u16,
78 ) -> Self {
79 let block_align = channels * (bits_per_sample / 8);
80 let avg_bytes_per_sec = samples_per_sec * block_align as u32;
81 Self {
82 format_tag,
83 channels,
84 samples_per_sec,
85 avg_bytes_per_sec,
86 block_align,
87 bits_per_sample,
88 valid_bits_per_sample: 0,
89 channel_mask: 0,
90 extensible: false,
91 }
92 }
93
94 #[must_use]
96 pub const fn pcm_int16(sample_rate: u32, channels: u16) -> Self {
97 Self::from_raw(WAVE_FORMAT_PCM, channels, sample_rate, 16)
98 }
99
100 #[must_use]
103 pub const fn pcm_int24(sample_rate: u32, channels: u16) -> Self {
104 Self::from_raw(WAVE_FORMAT_PCM, channels, sample_rate, 24)
105 }
106
107 #[must_use]
109 pub const fn pcm_int32(sample_rate: u32, channels: u16) -> Self {
110 Self::from_raw(WAVE_FORMAT_PCM, channels, sample_rate, 32)
111 }
112
113 #[must_use]
117 pub const fn pcm_float32(sample_rate: u32, channels: u16) -> Self {
118 Self::from_raw(WAVE_FORMAT_IEEE_FLOAT, channels, sample_rate, 32)
119 }
120
121 #[must_use]
123 pub const fn pcm_float64(sample_rate: u32, channels: u16) -> Self {
124 Self::from_raw(WAVE_FORMAT_IEEE_FLOAT, channels, sample_rate, 64)
125 }
126
127 #[inline]
130 #[must_use]
131 pub const fn format_tag(&self) -> u16 {
132 self.format_tag
133 }
134
135 #[inline]
137 #[must_use]
138 pub const fn channels(&self) -> u16 {
139 self.channels
140 }
141
142 #[inline]
144 #[must_use]
145 pub const fn sample_rate(&self) -> u32 {
146 self.samples_per_sec
147 }
148
149 #[inline]
151 #[must_use]
152 pub const fn bits_per_sample(&self) -> u16 {
153 self.bits_per_sample
154 }
155
156 #[inline]
159 #[must_use]
160 pub const fn block_align(&self) -> u16 {
161 self.block_align
162 }
163
164 #[inline]
166 #[must_use]
167 pub const fn avg_bytes_per_sec(&self) -> u32 {
168 self.avg_bytes_per_sec
169 }
170
171 #[inline]
173 #[must_use]
174 pub const fn is_float(&self) -> bool {
175 self.format_tag == WAVE_FORMAT_IEEE_FLOAT
176 }
177
178 #[inline]
180 #[must_use]
181 pub const fn is_int_pcm(&self) -> bool {
182 self.format_tag == WAVE_FORMAT_PCM
183 }
184
185 #[inline]
191 #[must_use]
192 pub const fn is_extensible(&self) -> bool {
193 self.extensible
194 }
195
196 #[inline]
200 #[must_use]
201 pub const fn channel_mask(&self) -> u32 {
202 self.channel_mask
203 }
204
205 #[inline]
210 #[must_use]
211 pub const fn valid_bits_per_sample(&self) -> u16 {
212 self.valid_bits_per_sample
213 }
214
215 #[inline]
226 #[must_use]
227 pub const fn with_extensible(mut self) -> Self {
228 self.extensible = true;
229 if self.channel_mask == 0 {
230 self.channel_mask = default_channel_mask(self.channels);
231 }
232 if self.valid_bits_per_sample == 0 {
233 self.valid_bits_per_sample = self.bits_per_sample;
234 }
235 self
236 }
237
238 #[inline]
241 #[must_use]
242 pub const fn with_channel_mask(mut self, mask: u32) -> Self {
243 self.channel_mask = mask;
244 self
245 }
246
247 #[inline]
250 #[must_use]
251 pub const fn with_valid_bits_per_sample(mut self, bits: u16) -> Self {
252 self.valid_bits_per_sample = bits;
253 self
254 }
255}
256
257#[must_use]
263pub const fn default_channel_mask(channels: u16) -> u32 {
264 const SPEAKER_FRONT_LEFT: u32 = 0x1;
266 const SPEAKER_FRONT_RIGHT: u32 = 0x2;
267 const SPEAKER_FRONT_CENTER: u32 = 0x4;
268 const SPEAKER_LOW_FREQUENCY: u32 = 0x8;
269 const SPEAKER_BACK_LEFT: u32 = 0x10;
270 const SPEAKER_BACK_RIGHT: u32 = 0x20;
271 const SPEAKER_SIDE_LEFT: u32 = 0x200;
272 const SPEAKER_SIDE_RIGHT: u32 = 0x400;
273 match channels {
274 1 => SPEAKER_FRONT_CENTER,
275 2 => SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT,
276 6 => {
278 SPEAKER_FRONT_LEFT
279 | SPEAKER_FRONT_RIGHT
280 | SPEAKER_FRONT_CENTER
281 | SPEAKER_LOW_FREQUENCY
282 | SPEAKER_BACK_LEFT
283 | SPEAKER_BACK_RIGHT
284 }
285 8 => {
287 SPEAKER_FRONT_LEFT
288 | SPEAKER_FRONT_RIGHT
289 | SPEAKER_FRONT_CENTER
290 | SPEAKER_LOW_FREQUENCY
291 | SPEAKER_BACK_LEFT
292 | SPEAKER_BACK_RIGHT
293 | SPEAKER_SIDE_LEFT
294 | SPEAKER_SIDE_RIGHT
295 }
296 _ => 0,
297 }
298}
299
300#[cfg(windows)]
301impl Format {
302 #[must_use]
315 pub fn from_waveformatex(wf: &windows::Win32::Media::Audio::WAVEFORMATEX) -> Self {
316 Self {
317 format_tag: { wf.wFormatTag },
318 channels: { wf.nChannels },
319 samples_per_sec: { wf.nSamplesPerSec },
320 avg_bytes_per_sec: { wf.nAvgBytesPerSec },
321 block_align: { wf.nBlockAlign },
322 bits_per_sample: { wf.wBitsPerSample },
323 valid_bits_per_sample: 0,
324 channel_mask: 0,
325 extensible: false,
326 }
327 }
328
329 #[must_use]
336 pub fn to_waveformatex(&self) -> windows::Win32::Media::Audio::WAVEFORMATEX {
337 windows::Win32::Media::Audio::WAVEFORMATEX {
338 wFormatTag: if self.extensible {
339 WAVE_FORMAT_EXTENSIBLE
340 } else {
341 self.format_tag
342 },
343 nChannels: self.channels,
344 nSamplesPerSec: self.samples_per_sec,
345 nAvgBytesPerSec: self.avg_bytes_per_sec,
346 nBlockAlign: self.block_align,
347 wBitsPerSample: self.bits_per_sample,
348 cbSize: if self.extensible { 22 } else { 0 },
349 }
350 }
351
352 #[must_use]
362 pub fn from_waveformatextensible(
363 wfx: &windows::Win32::Media::Audio::WAVEFORMATEXTENSIBLE,
364 ) -> Self {
365 let base = wfx.Format;
366 let valid_bits = unsafe { wfx.Samples.wValidBitsPerSample };
370 const KSDATAFORMAT_SUBTYPE_IEEE_FLOAT: windows_core::GUID =
374 windows_core::GUID::from_u128(0x00000003_0000_0010_8000_00aa00389b71);
375 let sub: windows_core::GUID = wfx.SubFormat;
376 let logical_tag = if sub == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT {
377 WAVE_FORMAT_IEEE_FLOAT
378 } else {
379 WAVE_FORMAT_PCM
380 };
381 Self {
382 format_tag: logical_tag,
383 channels: { base.nChannels },
384 samples_per_sec: { base.nSamplesPerSec },
385 avg_bytes_per_sec: { base.nAvgBytesPerSec },
386 block_align: { base.nBlockAlign },
387 bits_per_sample: { base.wBitsPerSample },
388 valid_bits_per_sample: valid_bits,
389 channel_mask: { wfx.dwChannelMask },
390 extensible: true,
391 }
392 }
393
394 #[must_use]
404 pub fn to_waveformatextensible(&self) -> windows::Win32::Media::Audio::WAVEFORMATEXTENSIBLE {
405 use windows::Win32::Media::Audio::{WAVEFORMATEXTENSIBLE, WAVEFORMATEXTENSIBLE_0};
406 let base = windows::Win32::Media::Audio::WAVEFORMATEX {
407 wFormatTag: WAVE_FORMAT_EXTENSIBLE,
411 nChannels: self.channels,
412 nSamplesPerSec: self.samples_per_sec,
413 nAvgBytesPerSec: self.avg_bytes_per_sec,
414 nBlockAlign: self.block_align,
415 wBitsPerSample: self.bits_per_sample,
416 cbSize: 22,
419 };
420 let samples = WAVEFORMATEXTENSIBLE_0 {
421 wValidBitsPerSample: if self.valid_bits_per_sample == 0 {
422 self.bits_per_sample
423 } else {
424 self.valid_bits_per_sample
425 },
426 };
427 WAVEFORMATEXTENSIBLE {
428 Format: base,
429 Samples: samples,
430 dwChannelMask: self.channel_mask,
431 SubFormat: self.sub_format_guid(),
432 }
433 }
434
435 fn sub_format_guid(&self) -> windows_core::GUID {
442 const KSDATAFORMAT_SUBTYPE_IEEE_FLOAT: windows_core::GUID =
447 windows_core::GUID::from_u128(0x00000003_0000_0010_8000_00aa00389b71);
448 if self.is_int_pcm() {
449 windows::Win32::Media::KernelStreaming::KSDATAFORMAT_SUBTYPE_PCM
450 } else {
451 KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
452 }
453 }
454
455 #[must_use]
467 pub unsafe fn from_waveformatex_ptr(
468 wf: *const windows::Win32::Media::Audio::WAVEFORMATEX,
469 ) -> Self {
470 let base = unsafe { &*wf };
472 if { base.cbSize } >= 22 && { base.wFormatTag } == WAVE_FORMAT_EXTENSIBLE {
473 let wfx = wf as *const windows::Win32::Media::Audio::WAVEFORMATEXTENSIBLE;
478 Self::from_waveformatextensible(unsafe { &*wfx })
479 } else {
480 Self::from_waveformatex(base)
481 }
482 }
483}
484
485#[derive(Copy, Clone, PartialEq, Eq, Debug)]
497pub enum FormatNegotiation {
498 Accept,
500 Suggest(Format),
502 Reject,
504}
505
506#[cfg(test)]
507mod tests {
508 use super::*;
509
510 #[test]
511 fn pcm_float32_48k_mono_has_expected_fields() {
512 let f = Format::pcm_float32(48_000, 1);
513 assert_eq!(f.format_tag(), WAVE_FORMAT_IEEE_FLOAT);
514 assert_eq!(f.channels(), 1);
515 assert_eq!(f.sample_rate(), 48_000);
516 assert_eq!(f.bits_per_sample(), 32);
517 assert_eq!(f.block_align(), 4);
518 assert_eq!(f.avg_bytes_per_sec(), 48_000 * 4);
519 assert!(f.is_float());
520 assert!(!f.is_int_pcm());
521 }
522
523 #[test]
524 fn pcm_int16_44k1_stereo_has_expected_fields() {
525 let f = Format::pcm_int16(44_100, 2);
526 assert_eq!(f.format_tag(), WAVE_FORMAT_PCM);
527 assert_eq!(f.channels(), 2);
528 assert_eq!(f.sample_rate(), 44_100);
529 assert_eq!(f.bits_per_sample(), 16);
530 assert_eq!(f.block_align(), 4);
531 assert_eq!(f.avg_bytes_per_sec(), 44_100 * 4);
532 assert!(f.is_int_pcm());
533 assert!(!f.is_float());
534 }
535
536 #[test]
537 fn pcm_int24_48k_mono_block_align_is_3() {
538 let f = Format::pcm_int24(48_000, 1);
539 assert_eq!(f.block_align(), 3);
540 assert_eq!(f.avg_bytes_per_sec(), 48_000 * 3);
541 }
542
543 #[test]
544 fn pcm_float64_48k_5_1_block_align_is_48() {
545 let f = Format::pcm_float64(48_000, 6);
546 assert_eq!(f.block_align(), 48);
547 assert_eq!(f.avg_bytes_per_sec(), 48_000 * 48);
548 }
549
550 #[test]
551 fn negotiation_variants_distinguish() {
552 let a = FormatNegotiation::Accept;
553 let s = FormatNegotiation::Suggest(Format::pcm_float32(48_000, 1));
554 let r = FormatNegotiation::Reject;
555 assert_ne!(a, s);
556 assert_ne!(s, r);
557 assert_ne!(a, r);
558 }
559}
560
561#[cfg(all(test, windows))]
562mod windows_conv_tests {
563 use super::*;
564 use windows::Win32::Media::Audio::WAVEFORMATEX;
565
566 #[test]
567 fn windows_waveformatex_is_18_bytes_packed_one() {
568 assert_eq!(core::mem::size_of::<WAVEFORMATEX>(), 18);
572 assert_eq!(core::mem::align_of::<WAVEFORMATEX>(), 1);
573 }
574
575 #[test]
576 fn pcm_float32_48k_mono_round_trips() {
577 let f = Format::pcm_float32(48_000, 1);
578 let wf = f.to_waveformatex();
579 assert_eq!({ wf.wFormatTag }, WAVE_FORMAT_IEEE_FLOAT);
580 assert_eq!({ wf.nChannels }, 1);
581 assert_eq!({ wf.nSamplesPerSec }, 48_000);
582 assert_eq!({ wf.nAvgBytesPerSec }, 48_000 * 4);
583 assert_eq!({ wf.nBlockAlign }, 4);
584 assert_eq!({ wf.wBitsPerSample }, 32);
585 assert_eq!({ wf.cbSize }, 0);
586
587 let f2 = Format::from_waveformatex(&wf);
588 assert_eq!(f, f2);
589 }
590
591 #[test]
592 fn every_typed_constructor_round_trips() {
593 for f in [
594 Format::pcm_int16(44_100, 2),
595 Format::pcm_int24(48_000, 1),
596 Format::pcm_int32(96_000, 4),
597 Format::pcm_float32(48_000, 1),
598 Format::pcm_float64(192_000, 8),
599 ] {
600 let wf = f.to_waveformatex();
601 let f2 = Format::from_waveformatex(&wf);
602 assert_eq!(f, f2, "round-trip failed for {f:?}");
603 }
604 }
605
606 #[test]
607 fn from_waveformatex_preserves_all_base_fields() {
608 let wf = WAVEFORMATEX {
609 wFormatTag: WAVE_FORMAT_PCM,
610 nChannels: 2,
611 nSamplesPerSec: 44_100,
612 nAvgBytesPerSec: 44_100 * 4,
613 nBlockAlign: 4,
614 wBitsPerSample: 16,
615 cbSize: 0,
616 };
617 let f = Format::from_waveformatex(&wf);
618 assert_eq!(f.format_tag(), WAVE_FORMAT_PCM);
619 assert_eq!(f.channels(), 2);
620 assert_eq!(f.sample_rate(), 44_100);
621 assert_eq!(f.avg_bytes_per_sec(), 44_100 * 4);
622 assert_eq!(f.block_align(), 4);
623 assert_eq!(f.bits_per_sample(), 16);
624 }
625
626 #[test]
627 fn from_waveformatex_ignores_cbsize() {
628 let wf = WAVEFORMATEX {
632 wFormatTag: WAVE_FORMAT_EXTENSIBLE,
633 nChannels: 6,
634 nSamplesPerSec: 48_000,
635 nAvgBytesPerSec: 48_000 * 24,
636 nBlockAlign: 24,
637 wBitsPerSample: 32,
638 cbSize: 22, };
640 let f = Format::from_waveformatex(&wf);
641 assert_eq!(f.format_tag(), WAVE_FORMAT_EXTENSIBLE);
642 assert_eq!(f.channels(), 6);
643 assert_eq!(f.sample_rate(), 48_000);
644 assert_eq!({ f.to_waveformatex().cbSize }, 0);
647 }
648
649 #[test]
650 fn waveformatextensible_is_40_bytes_packed_one() {
651 assert_eq!(
654 core::mem::size_of::<windows::Win32::Media::Audio::WAVEFORMATEXTENSIBLE>(),
655 40
656 );
657 assert_eq!(
658 core::mem::align_of::<windows::Win32::Media::Audio::WAVEFORMATEXTENSIBLE>(),
659 1
660 );
661 }
662
663 #[test]
664 fn to_waveformatextensible_sets_wire_tag_and_subformat_for_float32() {
665 let f = Format::pcm_float32(48_000, 8).with_extensible();
666 let wfx = f.to_waveformatextensible();
667 assert_eq!({ wfx.Format.wFormatTag }, WAVE_FORMAT_EXTENSIBLE);
670 assert_eq!({ wfx.Format.cbSize }, 22);
671 assert_eq!({ wfx.Format.nChannels }, 8);
672 const KSDATAFORMAT_SUBTYPE_IEEE_FLOAT: windows_core::GUID =
674 windows_core::GUID::from_u128(0x00000003_0000_0010_8000_00aa00389b71);
675 assert_eq!({ wfx.SubFormat }, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT);
676 assert_eq!(unsafe { wfx.Samples.wValidBitsPerSample }, 32);
678 assert!({ wfx.dwChannelMask } != 0);
680 }
681
682 #[test]
683 fn to_waveformatextensible_sets_pcm_subformat_for_int16() {
684 let f = Format::pcm_int16(48_000, 2).with_extensible();
685 let wfx = f.to_waveformatextensible();
686 assert_eq!(
687 { wfx.SubFormat },
688 windows::Win32::Media::KernelStreaming::KSDATAFORMAT_SUBTYPE_PCM
689 );
690 }
691
692 #[test]
693 fn extensible_round_trips_through_waveformatextensible() {
694 let original = Format::pcm_float32(48_000, 6)
695 .with_extensible()
696 .with_valid_bits_per_sample(24);
697 let wfx = original.to_waveformatextensible();
698 let parsed = Format::from_waveformatextensible(&wfx);
699 assert!(parsed.is_extensible());
701 assert_eq!(parsed.format_tag(), WAVE_FORMAT_IEEE_FLOAT);
702 assert_eq!(parsed.channels(), original.channels());
703 assert_eq!(parsed.sample_rate(), original.sample_rate());
704 assert_eq!(parsed.channel_mask(), original.channel_mask());
705 assert_eq!(parsed.valid_bits_per_sample(), 24);
706 }
707
708 #[test]
709 fn from_waveformatex_ptr_picks_extensible_when_cbsize_22() {
710 let f = Format::pcm_float32(48_000, 8).with_extensible();
711 let wfx = f.to_waveformatextensible();
712 let prefix: *const windows::Win32::Media::Audio::WAVEFORMATEX =
715 core::ptr::addr_of!(wfx.Format);
716 let parsed = unsafe { Format::from_waveformatex_ptr(prefix) };
719 assert!(parsed.is_extensible());
720 assert_eq!(parsed.channels(), 8);
721 assert_eq!(parsed.format_tag(), WAVE_FORMAT_IEEE_FLOAT);
722 }
723
724 #[test]
725 fn from_waveformatex_ptr_keeps_base_when_cbsize_zero() {
726 let f = Format::pcm_float32(48_000, 2);
727 let wf = f.to_waveformatex();
728 let ptr: *const windows::Win32::Media::Audio::WAVEFORMATEX = &wf;
729 let parsed = unsafe { Format::from_waveformatex_ptr(ptr) };
731 assert!(!parsed.is_extensible());
732 assert_eq!(parsed.format_tag(), WAVE_FORMAT_IEEE_FLOAT);
733 }
734
735 #[test]
736 fn default_channel_mask_returns_known_layouts() {
737 assert_eq!(default_channel_mask(1), 0x4); assert_eq!(default_channel_mask(2), 0x3); assert_eq!(default_channel_mask(6), 0x3F); assert_eq!(default_channel_mask(8), 0x63F); assert_eq!(default_channel_mask(3), 0); }
743}