use crate::error::Result;
use super::decode::{bad, read_u16, read_u32};
use super::format::{FORMAT_EXTENSIBLE, FmtFields};
pub(super) fn parse_fmt(bytes: &[u8], body: usize, size: usize) -> Result<FmtFields> {
if size < 16 {
return Err(bad(format!(
"'fmt ' chunk is {size} bytes, the minimum is 16"
)));
}
let field = |off: usize| -> Result<u16> {
read_u16(bytes, body + off)
.ok_or_else(|| bad(format!("'fmt ' chunk is truncated at offset {off}")))
};
let mut format_tag = field(0)?;
let mut channel_mask = 0;
let channels = field(2)?;
let sample_rate = read_u32(bytes, body + 4)
.ok_or_else(|| bad("'fmt ' chunk is truncated at the sample rate".to_string()))?;
let byte_rate = read_u32(bytes, body + 8)
.ok_or_else(|| bad("'fmt ' chunk is truncated at the byte rate".to_string()))?;
let block_align = field(12)?;
let bits_per_sample = field(14)?;
if format_tag == FORMAT_EXTENSIBLE {
if size < 40 {
return Err(bad(format!(
"WAVE_FORMAT_EXTENSIBLE 'fmt ' chunk is {size} bytes, the minimum is 40"
)));
}
let cb_size = field(16)?;
if cb_size < 22 {
return Err(bad(format!(
"WAVE_FORMAT_EXTENSIBLE cbSize is {cb_size}, the minimum is 22"
)));
}
channel_mask = read_u32(bytes, body + 20)
.ok_or_else(|| bad("'fmt ' chunk is truncated at the channel mask".to_string()))?;
format_tag = field(24)?;
if format_tag == FORMAT_EXTENSIBLE {
return Err(bad(
"WAVE_FORMAT_EXTENSIBLE SubFormat GUID is itself 0xFFFE".to_string(),
));
}
}
Ok(FmtFields {
format_tag,
channels,
sample_rate,
byte_rate: sample_rate
.checked_mul(block_align as u32)
.unwrap_or(byte_rate),
block_align,
bits_per_sample,
channel_mask,
})
}
pub(crate) fn mask_layout(mask: u32, channels: u16) -> Option<u64> {
(mask != 0 && mask.count_ones() == u32::from(channels)).then_some(u64::from(mask))
}
#[cfg(feature = "decode")]
pub(crate) fn wav_layout(bytes: &[u8]) -> Option<u64> {
if bytes.get(0..4) != Some(b"RIFF") || bytes.get(8..12) != Some(b"WAVE") {
return None;
}
let mut pos = 12usize;
while let Some(size) = read_u32(bytes, pos + 4) {
let size = size as usize;
let body = pos + 8;
if bytes.get(pos..pos + 4) == Some(b"fmt ") {
let f = parse_fmt(bytes, body, size).ok()?;
return mask_layout(f.channel_mask, f.channels);
}
pos = body.checked_add(size + (size & 1))?;
}
None
}