use crate::error::Result;
use crate::macros::decode_err;
use crate::properties::FileProperties;
use std::time::Duration;
use byteorder::{LittleEndian, ReadBytesExt};
const PCM: u16 = 0x0001;
const IEEE_FLOAT: u16 = 0x0003;
const EXTENSIBLE: u16 = 0xFFFE;
#[allow(missing_docs, non_camel_case_types)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum WavFormat {
PCM,
IEEE_FLOAT,
Other(u16),
}
impl Default for WavFormat {
fn default() -> Self {
Self::Other(0)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
#[non_exhaustive]
pub struct WavProperties {
pub(crate) format: WavFormat,
pub(crate) duration: Duration,
pub(crate) overall_bitrate: u32,
pub(crate) audio_bitrate: u32,
pub(crate) sample_rate: u32,
pub(crate) bit_depth: u8,
pub(crate) channels: u8,
}
impl From<WavProperties> for FileProperties {
fn from(input: WavProperties) -> Self {
Self {
duration: input.duration,
overall_bitrate: Some(input.overall_bitrate),
audio_bitrate: Some(input.audio_bitrate),
sample_rate: Some(input.sample_rate),
bit_depth: Some(input.bit_depth),
channels: Some(input.channels),
}
}
}
impl WavProperties {
pub fn duration(&self) -> Duration {
self.duration
}
pub fn overall_bitrate(&self) -> u32 {
self.overall_bitrate
}
pub fn bitrate(&self) -> u32 {
self.audio_bitrate
}
pub fn sample_rate(&self) -> u32 {
self.sample_rate
}
pub fn bit_depth(&self) -> u8 {
self.bit_depth
}
pub fn channels(&self) -> u8 {
self.channels
}
pub fn format(&self) -> &WavFormat {
&self.format
}
}
pub(super) fn read_properties(
fmt: &mut &[u8],
mut total_samples: u32,
stream_len: u32,
file_length: u64,
) -> Result<WavProperties> {
let mut format_tag = fmt.read_u16::<LittleEndian>()?;
let channels = fmt.read_u16::<LittleEndian>()? as u8;
if channels == 0 {
decode_err!(@BAIL WAV, "File contains 0 channels");
}
let sample_rate = fmt.read_u32::<LittleEndian>()?;
let bytes_per_second = fmt.read_u32::<LittleEndian>()?;
let block_align = fmt.read_u16::<LittleEndian>()?;
let bits_per_sample = fmt.read_u16::<LittleEndian>()?;
let bytes_per_sample = block_align / u16::from(channels);
let mut bit_depth = if bits_per_sample > 0 {
bits_per_sample as u8
} else {
(bytes_per_sample * 8) as u8
};
if format_tag == EXTENSIBLE {
if fmt.len() + 16 < 40 {
decode_err!(@BAIL WAV, "Extensible format identified, invalid \"fmt \" chunk size found (< 40)");
}
let _cb_size = fmt.read_u16::<LittleEndian>()?;
let valid_bits_per_sample = fmt.read_u16::<LittleEndian>()?;
let _channel_mask = fmt.read_u32::<LittleEndian>()?;
if valid_bits_per_sample > 0 {
bit_depth = valid_bits_per_sample as u8;
}
format_tag = fmt.read_u16::<LittleEndian>()?;
}
let non_pcm = format_tag != PCM && format_tag != IEEE_FLOAT;
if non_pcm && total_samples == 0 {
decode_err!(@BAIL WAV, "Non-PCM format identified, no \"fact\" chunk found");
}
if bits_per_sample > 0 {
total_samples = stream_len / u32::from(u16::from(channels) * ((bits_per_sample + 7) / 8))
} else if !non_pcm {
total_samples = 0
}
let (duration, overall_bitrate, audio_bitrate) = if sample_rate > 0 && total_samples > 0 {
let length = (u64::from(total_samples) * 1000) / u64::from(sample_rate);
if length == 0 {
(Duration::ZERO, 0, 0)
} else {
let overall_bitrate = ((file_length * 8) / length) as u32;
let audio_bitrate = (u64::from(stream_len * 8) / length) as u32;
(
Duration::from_millis(length),
overall_bitrate,
audio_bitrate,
)
}
} else if bytes_per_second > 0 {
let length = (u64::from(stream_len) * 1000) / u64::from(bytes_per_second);
let overall_bitrate = ((file_length * 8) / length) as u32;
let audio_bitrate = (bytes_per_second * 8) / 1000;
(
Duration::from_millis(length),
overall_bitrate,
audio_bitrate,
)
} else {
(Duration::ZERO, 0, 0)
};
Ok(WavProperties {
format: match format_tag {
PCM => WavFormat::PCM,
IEEE_FLOAT => WavFormat::IEEE_FLOAT,
other => WavFormat::Other(other),
},
duration,
overall_bitrate,
audio_bitrate,
sample_rate,
bit_depth,
channels,
})
}