pub const FORMAT_LINEAR_PCM: u32 = u32::from_be_bytes(*b"lpcm");
pub const ASBD_SIZE: usize = 40;
pub mod flags {
pub const IS_FLOAT: u32 = 1 << 0;
pub const IS_BIG_ENDIAN: u32 = 1 << 1;
pub const IS_SIGNED_INTEGER: u32 = 1 << 2;
pub const IS_PACKED: u32 = 1 << 3;
pub const IS_ALIGNED_HIGH: u32 = 1 << 4;
pub const IS_NON_INTERLEAVED: u32 = 1 << 5;
pub const IS_NON_MIXABLE: u32 = 1 << 6;
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[non_exhaustive]
pub enum SampleFormat {
Int16,
Int24,
Int32,
Float32,
}
impl SampleFormat {
#[inline]
#[must_use]
pub const fn bits_per_channel(self) -> u32 {
match self {
Self::Int16 => 16,
Self::Int24 => 24,
Self::Int32 | Self::Float32 => 32,
}
}
#[inline]
#[must_use]
pub const fn bytes_per_channel(self) -> u32 {
match self {
Self::Int16 => 2,
Self::Int24 => 3,
Self::Int32 | Self::Float32 => 4,
}
}
#[inline]
#[must_use]
pub const fn format_flags(self) -> u32 {
match self {
Self::Float32 => flags::IS_FLOAT | flags::IS_PACKED,
Self::Int16 | Self::Int24 | Self::Int32 => flags::IS_SIGNED_INTEGER | flags::IS_PACKED,
}
}
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct StreamFormat {
sample_rate: f64,
format_id: u32,
format_flags: u32,
bytes_per_packet: u32,
frames_per_packet: u32,
bytes_per_frame: u32,
channels_per_frame: u32,
bits_per_channel: u32,
}
impl StreamFormat {
#[allow(clippy::too_many_arguments)]
#[inline]
#[must_use]
pub const fn from_raw(
sample_rate: f64,
format_id: u32,
format_flags: u32,
bytes_per_packet: u32,
frames_per_packet: u32,
bytes_per_frame: u32,
channels_per_frame: u32,
bits_per_channel: u32,
) -> Self {
Self {
sample_rate,
format_id,
format_flags,
bytes_per_packet,
frames_per_packet,
bytes_per_frame,
channels_per_frame,
bits_per_channel,
}
}
#[must_use]
pub const fn linear_pcm(sample: SampleFormat, sample_rate: f64, channels: u32) -> Self {
let bytes_per_frame = sample.bytes_per_channel() * channels;
Self {
sample_rate,
format_id: FORMAT_LINEAR_PCM,
format_flags: sample.format_flags(),
bytes_per_packet: bytes_per_frame,
frames_per_packet: 1,
bytes_per_frame,
channels_per_frame: channels,
bits_per_channel: sample.bits_per_channel(),
}
}
#[must_use]
pub const fn float32(sample_rate: f64, channels: u32) -> Self {
Self::linear_pcm(SampleFormat::Float32, sample_rate, channels)
}
#[must_use]
pub const fn int16(sample_rate: f64, channels: u32) -> Self {
Self::linear_pcm(SampleFormat::Int16, sample_rate, channels)
}
#[must_use]
pub const fn int24(sample_rate: f64, channels: u32) -> Self {
Self::linear_pcm(SampleFormat::Int24, sample_rate, channels)
}
#[must_use]
pub const fn int32(sample_rate: f64, channels: u32) -> Self {
Self::linear_pcm(SampleFormat::Int32, sample_rate, channels)
}
#[inline]
#[must_use]
pub const fn sample_rate(&self) -> f64 {
self.sample_rate
}
#[inline]
#[must_use]
pub const fn format_id(&self) -> u32 {
self.format_id
}
#[inline]
#[must_use]
pub const fn format_flags(&self) -> u32 {
self.format_flags
}
#[inline]
#[must_use]
pub const fn bytes_per_packet(&self) -> u32 {
self.bytes_per_packet
}
#[inline]
#[must_use]
pub const fn frames_per_packet(&self) -> u32 {
self.frames_per_packet
}
#[inline]
#[must_use]
pub const fn bytes_per_frame(&self) -> u32 {
self.bytes_per_frame
}
#[inline]
#[must_use]
pub const fn channels(&self) -> u32 {
self.channels_per_frame
}
#[inline]
#[must_use]
pub const fn bits_per_channel(&self) -> u32 {
self.bits_per_channel
}
#[inline]
#[must_use]
pub const fn is_linear_pcm(&self) -> bool {
self.format_id == FORMAT_LINEAR_PCM
}
#[inline]
#[must_use]
pub const fn is_float(&self) -> bool {
self.format_flags & flags::IS_FLOAT != 0
}
#[inline]
#[must_use]
pub const fn is_packed(&self) -> bool {
self.format_flags & flags::IS_PACKED != 0
}
#[inline]
#[must_use]
pub const fn is_non_interleaved(&self) -> bool {
self.format_flags & flags::IS_NON_INTERLEAVED != 0
}
#[must_use]
pub fn sample_format(&self) -> Option<SampleFormat> {
if !self.is_linear_pcm() || !self.is_packed() {
return None;
}
[
SampleFormat::Int16,
SampleFormat::Int24,
SampleFormat::Int32,
SampleFormat::Float32,
]
.into_iter()
.find(|candidate| {
candidate.bits_per_channel() == self.bits_per_channel
&& candidate.format_flags() == self.format_flags
})
}
#[inline]
#[must_use]
pub fn is_canonical(&self) -> bool {
self.sample_format() == Some(SampleFormat::Float32) && !self.is_non_interleaved()
}
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum FormatNegotiation {
Accept,
Suggest(StreamFormat),
Reject,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn float32_48k_stereo_has_expected_fields() {
let f = StreamFormat::float32(48_000.0, 2);
assert_eq!(f.sample_rate(), 48_000.0);
assert_eq!(f.format_id(), FORMAT_LINEAR_PCM);
assert!(f.is_linear_pcm());
assert!(f.is_float());
assert!(f.is_packed());
assert!(!f.is_non_interleaved());
assert_eq!(f.channels(), 2);
assert_eq!(f.bits_per_channel(), 32);
assert_eq!(f.bytes_per_frame(), 8);
assert_eq!(f.bytes_per_packet(), 8);
assert_eq!(f.frames_per_packet(), 1);
}
#[test]
fn int16_44k1_mono_has_expected_fields() {
let f = StreamFormat::int16(44_100.0, 1);
assert!(!f.is_float());
assert_eq!(f.bits_per_channel(), 16);
assert_eq!(f.bytes_per_frame(), 2);
}
#[test]
fn int24_packs_into_three_bytes() {
let f = StreamFormat::int24(48_000.0, 2);
assert_eq!(f.bits_per_channel(), 24);
assert_eq!(f.bytes_per_frame(), 6);
}
#[test]
fn int32_uses_four_byte_words() {
let f = StreamFormat::int32(96_000.0, 4);
assert_eq!(f.bits_per_channel(), 32);
assert_eq!(f.bytes_per_frame(), 16);
}
#[test]
fn sample_format_round_trips_through_constructors() {
let cases = [
(SampleFormat::Int16, StreamFormat::int16(48_000.0, 2)),
(SampleFormat::Int24, StreamFormat::int24(48_000.0, 2)),
(SampleFormat::Int32, StreamFormat::int32(48_000.0, 2)),
(SampleFormat::Float32, StreamFormat::float32(48_000.0, 2)),
];
for (sample, format) in cases {
assert_eq!(format.sample_format(), Some(sample));
}
}
#[test]
fn only_float32_interleaved_is_canonical() {
assert!(StreamFormat::float32(48_000.0, 2).is_canonical());
assert!(!StreamFormat::int16(48_000.0, 2).is_canonical());
assert!(!StreamFormat::int32(48_000.0, 2).is_canonical());
}
#[test]
fn non_interleaved_float32_is_not_canonical() {
let mut raw = StreamFormat::float32(48_000.0, 2);
raw = StreamFormat::from_raw(
raw.sample_rate(),
raw.format_id(),
raw.format_flags() | flags::IS_NON_INTERLEAVED,
raw.bytes_per_packet(),
raw.frames_per_packet(),
raw.bytes_per_frame(),
raw.channels(),
raw.bits_per_channel(),
);
assert!(raw.is_non_interleaved());
assert!(!raw.is_canonical());
}
#[test]
fn unknown_format_id_has_no_sample_format() {
let raw =
StreamFormat::from_raw(48_000.0, u32::from_be_bytes(*b"aac "), 0, 0, 1024, 0, 2, 0);
assert!(!raw.is_linear_pcm());
assert_eq!(raw.sample_format(), None);
assert!(!raw.is_canonical());
}
#[test]
fn format_linear_pcm_constant_is_lpcm() {
assert_eq!(FORMAT_LINEAR_PCM, 0x6C70_636D);
}
#[test]
fn sample_format_flag_sets_are_distinct() {
assert_ne!(
SampleFormat::Float32.format_flags(),
SampleFormat::Int32.format_flags()
);
assert!(SampleFormat::Float32.format_flags() & flags::IS_FLOAT != 0);
assert!(SampleFormat::Int16.format_flags() & flags::IS_SIGNED_INTEGER != 0);
}
#[test]
fn negotiation_variants_distinguish() {
let accept = FormatNegotiation::Accept;
let suggest = FormatNegotiation::Suggest(StreamFormat::float32(48_000.0, 2));
let reject = FormatNegotiation::Reject;
assert_eq!(accept, FormatNegotiation::Accept);
assert_ne!(accept, reject);
assert_ne!(suggest, accept);
assert_ne!(suggest, reject);
}
}