use std::io::{Read, Seek, SeekFrom};
use crate::input::EncodePcmStream;
use crate::metadata::{FXMD_CHUNK_ID, FxmdChunkPolicy, Metadata, MetadataDraft};
use crate::{
error::{Error, Result},
pcm::{
PcmEnvelope, PcmSpec, PcmStream, container_bits_from_valid_bits, is_supported_channel_mask,
ordinary_channel_mask,
},
};
const PCM_SUBFORMAT_GUID: [u8; 16] = [
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71, ];
const EXTENSIBLE_FMT_CHUNK_SIZE: u32 = 40;
const RF64_PLACEHOLDER_SIZE: u32 = 0xFFFF_FFFF;
const W64_RIFF_GUID: [u8; 16] = [
0x72, 0x69, 0x66, 0x66, 0x2e, 0x91, 0xcf, 0x11, 0xa5, 0xd6, 0x28, 0xdb, 0x04, 0xc1, 0x00, 0x00,
];
const W64_CHUNK_GUID_SUFFIX: [u8; 12] = [
0xf3, 0xac, 0xd3, 0x11, 0x8c, 0xd1, 0x00, 0xc0, 0x4f, 0x8e, 0xdb, 0x8a,
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WavReaderOptions {
pub capture_fxmd: bool,
pub strict_fxmd_validation: bool,
}
impl Default for WavReaderOptions {
fn default() -> Self {
Self {
capture_fxmd: true,
strict_fxmd_validation: true,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct FormatChunk {
format_tag: u16,
channels: u16,
sample_rate: u32,
byte_rate: u32,
block_align: u16,
container_bits_per_sample: u16,
valid_bits_per_sample: u16,
channel_mask: u32,
}
#[derive(Debug)]
pub struct WavPcmStreamBuilder<R> {
reader: R,
sample_rate: Option<u32>,
channels: Option<u8>,
valid_bits_per_sample: Option<u8>,
container_bits_per_sample: Option<u8>,
total_samples: Option<u64>,
channel_mask: Option<u32>,
}
#[derive(Debug, Clone)]
pub struct WavReader<R> {
reader: R,
spec: PcmSpec,
metadata: Metadata,
envelope: PcmEnvelope,
}
impl<R: Read + Seek> WavReader<R> {
pub fn new(reader: R) -> Result<Self> {
Self::with_reader_options(reader, WavReaderOptions::default())
}
pub fn with_options(
reader: R,
capture_fxmd: bool,
strict_fxmd_validation: bool,
) -> Result<Self> {
Self::with_reader_options(
reader,
WavReaderOptions {
capture_fxmd,
strict_fxmd_validation,
},
)
}
pub fn with_reader_options(mut reader: R, options: WavReaderOptions) -> Result<Self> {
let layout = parse_wav_layout(
&mut reader,
true,
FxmdChunkPolicy {
capture: options.capture_fxmd,
strict: options.strict_fxmd_validation,
},
)?;
reader.seek(SeekFrom::Start(layout.data_offset))?;
let spec = PcmSpec {
sample_rate: layout.format.sample_rate,
channels: layout.format.channels as u8,
bits_per_sample: layout.envelope.valid_bits_per_sample as u8,
total_samples: layout.total_samples,
bytes_per_sample: layout.envelope.container_bits_per_sample / 8,
channel_mask: layout.envelope.channel_mask,
};
let mut metadata = layout.metadata;
if should_preserve_channel_mask(layout.format.channels, layout.envelope.channel_mask) {
metadata.set_channel_mask_for_channels(
layout.format.channels,
layout.envelope.channel_mask,
);
}
Ok(Self {
reader,
spec,
metadata,
envelope: layout.envelope,
})
}
#[must_use]
pub fn spec(&self) -> PcmSpec {
self.spec
}
#[must_use]
pub fn metadata(&self) -> &Metadata {
&self.metadata
}
pub fn into_source(self) -> crate::input::EncodeSource<impl crate::input::EncodePcmStream> {
let (metadata, stream) = self.into_session_parts();
crate::input::EncodeSource::new(metadata, stream)
}
pub(crate) fn into_pcm_stream(self) -> WavPcmStream<R> {
self.into_session_parts().1
}
pub(crate) fn into_session_parts(self) -> (Metadata, WavPcmStream<R>) {
let Self {
reader,
spec,
metadata,
envelope,
} = self;
(
metadata,
WavPcmStream::from_parts(reader, spec, envelope)
.expect("validated WAV reader state remains constructible"),
)
}
}
#[derive(Debug, Clone)]
pub struct WavPcmStream<R> {
reader: R,
spec: PcmSpec,
envelope: PcmEnvelope,
remaining_frames: u64,
frame_bytes: usize,
#[cfg(feature = "progress")]
input_bytes_processed: u64,
last_chunk_bytes: Vec<u8>,
}
impl<R: Read + Seek> WavPcmStream<R> {
pub fn builder(reader: R) -> WavPcmStreamBuilder<R> {
WavPcmStreamBuilder::new(reader)
}
fn from_parts(reader: R, spec: PcmSpec, envelope: PcmEnvelope) -> Result<Self> {
let frame_bytes = usize::from(envelope.channels)
.checked_mul(usize::from(envelope.container_bits_per_sample / 8))
.ok_or_else(|| Error::UnsupportedPcmContainer("PCM frame size overflows".into()))?;
Ok(Self {
reader,
spec,
envelope,
remaining_frames: spec.total_samples,
frame_bytes,
#[cfg(feature = "progress")]
input_bytes_processed: 0,
last_chunk_bytes: Vec::new(),
})
}
}
impl<R> WavPcmStreamBuilder<R> {
pub fn new(reader: R) -> Self {
Self {
reader,
sample_rate: None,
channels: None,
valid_bits_per_sample: None,
container_bits_per_sample: None,
total_samples: None,
channel_mask: None,
}
}
#[must_use]
pub fn sample_rate(mut self, sample_rate: u32) -> Self {
self.sample_rate = Some(sample_rate);
self
}
#[must_use]
pub fn channels(mut self, channels: u8) -> Self {
self.channels = Some(channels);
self
}
#[must_use]
pub fn valid_bits_per_sample(mut self, valid_bits_per_sample: u8) -> Self {
self.valid_bits_per_sample = Some(valid_bits_per_sample);
self
}
#[must_use]
pub fn container_bits_per_sample(mut self, container_bits_per_sample: u8) -> Self {
self.container_bits_per_sample = Some(container_bits_per_sample);
self
}
#[must_use]
pub fn total_samples(mut self, total_samples: u64) -> Self {
self.total_samples = Some(total_samples);
self
}
#[must_use]
pub fn channel_mask(mut self, channel_mask: u32) -> Self {
self.channel_mask = Some(channel_mask);
self
}
}
impl<R: Read + Seek> WavPcmStreamBuilder<R> {
pub fn build(self) -> Result<WavPcmStream<R>> {
let sample_rate = required_builder_field("sample_rate", self.sample_rate)?;
let channels = required_builder_field("channels", self.channels)?;
let valid_bits_per_sample =
required_builder_field("valid_bits_per_sample", self.valid_bits_per_sample)?;
let total_samples = required_builder_field("total_samples", self.total_samples)?;
let container_bits_per_sample = self.container_bits_per_sample.unwrap_or_else(|| {
u8::try_from(container_bits_from_valid_bits(u16::from(
valid_bits_per_sample,
)))
.expect("byte-aligned container width fits in u8")
});
let channels_u16 = u16::from(channels);
let channel_mask = match self.channel_mask {
Some(mask) => mask,
None => ordinary_channel_mask(channels_u16).ok_or_else(|| {
Error::UnsupportedPcmContainer(format!(
"no ordinary channel mask exists for {channels} channels"
))
})?,
};
let bytes_per_sample = u16::from(container_bits_per_sample / 8);
let block_align = channels_u16.checked_mul(bytes_per_sample).ok_or_else(|| {
Error::UnsupportedPcmContainer("fmt block alignment overflows".into())
})?;
let byte_rate = sample_rate
.checked_mul(u32::from(block_align))
.ok_or_else(|| Error::UnsupportedPcmContainer("fmt byte rate overflows".into()))?;
let ordinary_mask = ordinary_channel_mask(channels_u16);
let uses_extensible = channels > 2
|| valid_bits_per_sample != container_bits_per_sample
|| ordinary_mask != Some(channel_mask);
let format = FormatChunk {
format_tag: if uses_extensible { 0xFFFE } else { 1 },
channels: channels_u16,
sample_rate,
byte_rate,
block_align,
container_bits_per_sample: u16::from(container_bits_per_sample),
valid_bits_per_sample: u16::from(valid_bits_per_sample),
channel_mask,
};
let envelope = validate_format(format)?;
let spec = PcmSpec {
sample_rate,
channels,
bits_per_sample: valid_bits_per_sample,
total_samples,
bytes_per_sample,
channel_mask: envelope.channel_mask,
};
WavPcmStream::from_parts(self.reader, spec, envelope)
}
}
impl<R: Read + Seek> crate::input::EncodePcmStream for WavPcmStream<R> {
fn spec(&self) -> PcmSpec {
self.spec
}
fn read_chunk(&mut self, max_frames: usize, output: &mut Vec<i32>) -> Result<usize> {
let frames = self.remaining_frames.min(max_frames as u64) as usize;
if frames == 0 {
return Ok(0);
}
let byte_len = frames
.checked_mul(self.frame_bytes)
.ok_or_else(|| Error::UnsupportedPcmContainer("PCM chunk size overflows".into()))?;
self.last_chunk_bytes.clear();
self.last_chunk_bytes.resize(byte_len, 0);
self.reader.read_exact(&mut self.last_chunk_bytes)?;
decode_samples_into(&self.last_chunk_bytes, self.envelope, output)?;
self.remaining_frames -= frames as u64;
#[cfg(feature = "progress")]
{
self.input_bytes_processed = self.input_bytes_processed.saturating_add(byte_len as u64);
}
Ok(frames)
}
#[cfg(feature = "progress")]
fn input_bytes_processed(&self) -> u64 {
self.input_bytes_processed
}
fn update_streaminfo_md5(
&mut self,
md5: &mut crate::md5::StreaminfoMd5,
samples: &[i32],
) -> Result<()> {
if self.envelope.container_bits_per_sample == 8 {
md5.update_samples(samples)
} else {
md5.update_bytes(&self.last_chunk_bytes);
Ok(())
}
}
}
pub fn read_wav<R: Read + Seek>(mut reader: R) -> Result<PcmStream> {
let reader = WavReader::with_reader_options(
&mut reader,
WavReaderOptions {
capture_fxmd: false,
strict_fxmd_validation: false,
},
)?;
let spec = reader.spec();
let mut stream = reader.into_pcm_stream();
let mut samples = Vec::new();
while stream.read_chunk(4_096, &mut samples)? != 0 {}
Ok(PcmStream { spec, samples })
}
pub(crate) fn inspect_total_samples<R: Read + Seek>(mut reader: R) -> Result<u64> {
Ok(parse_wav_layout(&mut reader, false, FxmdChunkPolicy::IGNORE)?.total_samples)
}
fn required_builder_field<T>(name: &str, value: Option<T>) -> Result<T> {
value.ok_or_else(|| {
Error::UnsupportedPcmContainer(format!(
"direct WAV stream construction requires `{name}` to be set"
))
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct ParsedWavLayout {
format: FormatChunk,
envelope: PcmEnvelope,
data_offset: u64,
data_size: u64,
total_samples: u64,
metadata: Metadata,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Ds64Chunk {
riff_size: u64,
data_size: u64,
sample_count: u64,
}
fn parse_wav_layout<R: Read + Seek>(
reader: &mut R,
capture_metadata: bool,
fxmd_policy: FxmdChunkPolicy,
) -> Result<ParsedWavLayout> {
let mut header = [0u8; 16];
reader.read_exact(&mut header[..4])?;
match &header[..4] {
b"RIFF" | b"RF64" => {
return parse_riff_layout_from_header(
reader,
header[..4].try_into().expect("fixed header"),
capture_metadata,
fxmd_policy,
);
}
_ => {}
}
match reader.read_exact(&mut header[4..]) {
Ok(()) => {}
Err(error) if error.kind() == std::io::ErrorKind::UnexpectedEof => {
return Err(Error::InvalidPcmContainer("expected RIFF header"));
}
Err(error) => return Err(error.into()),
}
if header == W64_RIFF_GUID {
return parse_w64_layout(reader, capture_metadata, fxmd_policy);
}
Err(Error::InvalidPcmContainer("expected RIFF header"))
}
fn parse_riff_layout_from_header<R: Read + Seek>(
reader: &mut R,
file_kind: [u8; 4],
capture_metadata: bool,
fxmd_policy: FxmdChunkPolicy,
) -> Result<ParsedWavLayout> {
let is_rf64 = file_kind == *b"RF64";
let _riff_size = read_u32_le(reader)?;
let mut chunk_id = [0u8; 4];
reader.read_exact(&mut chunk_id)?;
if &chunk_id != b"WAVE" {
return Err(Error::InvalidPcmContainer("expected WAVE signature"));
}
let mut ds64 = None;
let mut format = None;
let mut data_offset = None;
let mut data_size = None;
let mut metadata_draft = MetadataDraft::default();
loop {
let mut chunk_header = [0u8; 8];
match reader.read_exact(&mut chunk_header) {
Ok(()) => {}
Err(error) if error.kind() == std::io::ErrorKind::UnexpectedEof => break,
Err(error) => return Err(error.into()),
}
let chunk_size =
u32::from_le_bytes(chunk_header[4..8].try_into().expect("fixed chunk header"));
let chunk_start = reader.stream_position()?;
let chunk_id: [u8; 4] = chunk_header[..4].try_into().expect("fixed chunk id");
match &chunk_id {
b"ds64" if is_rf64 => {
ds64 = Some(read_ds64_chunk(reader, chunk_size)?);
}
b"fmt " => {
format = Some(read_format_chunk(reader, chunk_size)?);
}
b"data" => {
data_offset = Some(chunk_start);
let resolved_size = if is_rf64 && chunk_size == RF64_PLACEHOLDER_SIZE {
ds64.ok_or(Error::InvalidPcmContainer(
"RF64 data chunk is missing ds64 metadata",
))?
.data_size
} else {
u64::from(chunk_size)
};
data_size = Some(resolved_size);
seek_forward(reader, chunk_start, resolved_size)?;
}
id if capture_metadata && is_captured_metadata_chunk(*id) => {
if chunk_size == RF64_PLACEHOLDER_SIZE {
return Err(Error::UnsupportedPcmContainer(
"oversized RF64 metadata chunks are not supported yet".into(),
));
}
let payload = read_chunk_payload(reader, chunk_size)?;
metadata_draft.ingest_chunk(chunk_id, &payload, fxmd_policy)?;
}
_ => {
if chunk_size == RF64_PLACEHOLDER_SIZE {
return Err(Error::UnsupportedPcmContainer(
"oversized RF64 non-audio chunks are not supported yet".into(),
));
}
reader.seek(SeekFrom::Current(i64::from(chunk_size)))?;
}
}
let padding_size = if &chunk_id == b"data" {
data_size.unwrap_or(u64::from(chunk_size))
} else {
u64::from(chunk_size)
};
if padding_size % 2 != 0 {
reader.seek(SeekFrom::Current(1))?;
}
}
let format = format.ok_or(Error::InvalidPcmContainer("missing fmt chunk"))?;
let data_offset = data_offset.ok_or(Error::InvalidPcmContainer("missing data chunk"))?;
let data_size = data_size.ok_or(Error::InvalidPcmContainer("missing data size"))?;
let envelope = validate_format(format)?;
let expected_block_align = envelope.channels * (envelope.container_bits_per_sample / 8);
if format.block_align != expected_block_align {
return Err(Error::InvalidPcmContainer(
"fmt block alignment does not match channels * bytes/sample",
));
}
let block_align = u64::from(format.block_align);
if block_align == 0 {
return Err(Error::InvalidPcmContainer(
"fmt block alignment must be non-zero",
));
}
if data_size % block_align != 0 {
return Err(Error::InvalidPcmContainer(
"data chunk is not aligned to the sample frame size",
));
}
let total_samples = data_size / u64::from(format.block_align);
if let Some(ds64) = ds64 {
if ds64.sample_count != 0 && ds64.sample_count != total_samples {
return Err(Error::InvalidPcmContainer(
"RF64 ds64 sample count does not match PCM payload size",
));
}
if ds64.riff_size < data_offset {
return Err(Error::InvalidPcmContainer(
"RF64 ds64 RIFF size is truncated",
));
}
}
Ok(ParsedWavLayout {
format,
envelope,
data_offset,
data_size,
total_samples,
metadata: if capture_metadata {
metadata_draft.finish(total_samples)
} else {
Metadata::default()
},
})
}
fn parse_w64_layout<R: Read + Seek>(
reader: &mut R,
capture_metadata: bool,
fxmd_policy: FxmdChunkPolicy,
) -> Result<ParsedWavLayout> {
let _file_size = read_u64_le(reader)?;
let mut wave_guid = [0u8; 16];
reader.read_exact(&mut wave_guid)?;
if wave_guid != w64_chunk_guid(*b"wave") {
return Err(Error::InvalidPcmContainer("expected Wave64 wave signature"));
}
let mut format = None;
let mut data_offset = None;
let mut data_size = None;
let mut metadata_draft = MetadataDraft::default();
loop {
let mut chunk_guid = [0u8; 16];
match reader.read_exact(&mut chunk_guid) {
Ok(()) => {}
Err(error) if error.kind() == std::io::ErrorKind::UnexpectedEof => break,
Err(error) => return Err(error.into()),
}
let chunk_size = read_u64_le(reader)?;
if chunk_size < 24 {
return Err(Error::InvalidPcmContainer(
"Wave64 chunk is smaller than its header",
));
}
let payload_size = chunk_size - 24;
let chunk_start = reader.stream_position()?;
if chunk_guid == w64_chunk_guid(*b"fmt ") {
let format_chunk_size = u32::try_from(payload_size).map_err(|_| {
Error::UnsupportedPcmContainer("Wave64 fmt chunk is too large".into())
})?;
format = Some(read_format_chunk(reader, format_chunk_size)?);
} else if chunk_guid == w64_chunk_guid(*b"data") {
data_offset = Some(chunk_start);
data_size = Some(payload_size);
seek_forward(reader, chunk_start, payload_size)?;
} else if capture_metadata && w64_metadata_chunk_id(chunk_guid).is_some() {
let chunk_size = u32::try_from(payload_size).map_err(|_| {
Error::UnsupportedPcmContainer("Wave64 metadata chunk is too large".into())
})?;
let payload = read_chunk_payload(reader, chunk_size)?;
metadata_draft.ingest_chunk(
w64_metadata_chunk_id(chunk_guid).expect("checked above"),
&payload,
fxmd_policy,
)?;
} else {
seek_forward(reader, chunk_start, payload_size)?;
}
let padding = (8 - (payload_size % 8)) % 8;
if padding != 0 {
reader.seek(SeekFrom::Current(
i64::try_from(padding).expect("padding fits i64"),
))?;
}
}
let format = format.ok_or(Error::InvalidPcmContainer("missing fmt chunk"))?;
let data_offset = data_offset.ok_or(Error::InvalidPcmContainer("missing data chunk"))?;
let data_size = data_size.ok_or(Error::InvalidPcmContainer("missing data size"))?;
let envelope = validate_format(format)?;
let expected_block_align = envelope.channels * (envelope.container_bits_per_sample / 8);
if format.block_align != expected_block_align {
return Err(Error::InvalidPcmContainer(
"fmt block alignment does not match channels * bytes/sample",
));
}
let block_align = u64::from(format.block_align);
if block_align == 0 {
return Err(Error::InvalidPcmContainer(
"fmt block alignment must be non-zero",
));
}
if data_size % block_align != 0 {
return Err(Error::InvalidPcmContainer(
"data chunk is not aligned to the sample frame size",
));
}
let total_samples = data_size / block_align;
Ok(ParsedWavLayout {
format,
envelope,
data_offset,
data_size,
total_samples,
metadata: if capture_metadata {
metadata_draft.finish(total_samples)
} else {
Metadata::default()
},
})
}
fn read_format_chunk<R: Read>(reader: &mut R, chunk_size: u32) -> Result<FormatChunk> {
if chunk_size < 16 {
return Err(Error::InvalidPcmContainer("fmt chunk is too short"));
}
let format_tag = read_u16_le(reader)?;
let channels = read_u16_le(reader)?;
let sample_rate = read_u32_le(reader)?;
let byte_rate = read_u32_le(reader)?;
let block_align = read_u16_le(reader)?;
let container_bits_per_sample = read_u16_le(reader)?;
if format_tag == 0xFFFE {
if chunk_size < EXTENSIBLE_FMT_CHUNK_SIZE {
return Err(Error::InvalidPcmContainer(
"WAVEFORMATEXTENSIBLE fmt chunk is too short",
));
}
let cb_size = read_u16_le(reader)?;
if cb_size < 22 {
return Err(Error::InvalidPcmContainer(
"WAVEFORMATEXTENSIBLE extension is too short",
));
}
let valid_bits_per_sample = read_u16_le(reader)?;
let channel_mask = read_u32_le(reader)?;
let mut subformat = [0u8; 16];
reader.read_exact(&mut subformat)?;
if subformat != PCM_SUBFORMAT_GUID {
return Err(Error::UnsupportedPcmContainer(
"only WAVEFORMATEXTENSIBLE PCM subformat is supported".into(),
));
}
let extra_bytes = usize::from(cb_size.saturating_sub(22))
+ (chunk_size as usize).saturating_sub(EXTENSIBLE_FMT_CHUNK_SIZE as usize);
if extra_bytes > 0 {
let mut discard = vec![0u8; extra_bytes];
reader.read_exact(&mut discard)?;
}
Ok(FormatChunk {
format_tag,
channels,
sample_rate,
byte_rate,
block_align,
container_bits_per_sample,
valid_bits_per_sample,
channel_mask,
})
} else {
let mut discard = vec![0u8; (chunk_size - 16) as usize];
reader.read_exact(&mut discard)?;
Ok(FormatChunk {
format_tag,
channels,
sample_rate,
byte_rate,
block_align,
container_bits_per_sample,
valid_bits_per_sample: container_bits_per_sample,
channel_mask: ordinary_channel_mask(channels).unwrap_or(0),
})
}
}
fn validate_format(format: FormatChunk) -> Result<PcmEnvelope> {
if format.sample_rate == 0 {
return Err(Error::UnsupportedPcmContainer(
"sample rate 0 is not allowed".into(),
));
}
let container_bits_per_sample = format.container_bits_per_sample;
if !matches!(container_bits_per_sample, 8 | 16 | 24 | 32) {
return Err(Error::UnsupportedPcmContainer(format!(
"only byte-aligned PCM containers are supported, found {container_bits_per_sample} bits/sample"
)));
}
let envelope = if format.format_tag == 1 {
if !(1..=2).contains(&format.channels) {
return Err(Error::UnsupportedPcmContainer(format!(
"canonical PCM tag 1 only supports exact mono/stereo (1..2 channel) cases, found {} channels",
format.channels
)));
}
if format.valid_bits_per_sample != container_bits_per_sample {
return Err(Error::UnsupportedPcmContainer(
"canonical PCM requires valid bits to match container bits".into(),
));
}
PcmEnvelope {
channels: format.channels,
valid_bits_per_sample: format.valid_bits_per_sample,
container_bits_per_sample,
channel_mask: ordinary_channel_mask(format.channels).ok_or_else(|| {
Error::UnsupportedPcmContainer(format!(
"no ordinary mask exists for {} channels",
format.channels
))
})?,
}
} else if format.format_tag == 0xFFFE {
if !(1..=8).contains(&format.channels) {
return Err(Error::UnsupportedPcmContainer(format!(
"WAVEFORMATEXTENSIBLE input only supports 1..8 channel layouts, found {} channels",
format.channels
)));
}
if format.valid_bits_per_sample < 4 || format.valid_bits_per_sample > 32 {
return Err(Error::UnsupportedPcmContainer(format!(
"valid bits must be in the FLAC-native 4..32 range, found {}",
format.valid_bits_per_sample
)));
}
if format.valid_bits_per_sample > container_bits_per_sample {
return Err(Error::UnsupportedPcmContainer(format!(
"valid bits cannot exceed container bits ({} > {})",
format.valid_bits_per_sample, container_bits_per_sample
)));
}
if !is_supported_channel_mask(format.channels, format.channel_mask) {
return Err(Error::UnsupportedPcmContainer(format!(
"channel mask {:#010x} is not supported for {} channels",
format.channel_mask, format.channels
)));
}
PcmEnvelope {
channels: format.channels,
valid_bits_per_sample: format.valid_bits_per_sample,
container_bits_per_sample,
channel_mask: format.channel_mask,
}
} else {
return Err(Error::UnsupportedPcmContainer(format!(
"only PCM format tag 1 and WAVEFORMATEXTENSIBLE PCM are supported, found {}",
format.format_tag
)));
};
let expected_byte_rate =
format.sample_rate * u32::from(format.channels) * u32::from(container_bits_per_sample / 8);
if format.byte_rate != expected_byte_rate {
return Err(Error::InvalidPcmContainer(
"fmt byte rate does not match the PCM payload shape",
));
}
Ok(envelope)
}
fn should_preserve_channel_mask(channels: u16, mask: u32) -> bool {
ordinary_channel_mask(channels) != Some(mask)
}
fn is_captured_metadata_chunk(chunk_id: [u8; 4]) -> bool {
matches!(&chunk_id, b"LIST" | b"cue " | &FXMD_CHUNK_ID)
}
fn decode_samples_into(data: &[u8], envelope: PcmEnvelope, output: &mut Vec<i32>) -> Result<()> {
let shift = envelope
.container_bits_per_sample
.checked_sub(envelope.valid_bits_per_sample)
.ok_or(Error::InvalidPcmContainer(
"valid bits cannot exceed container bits for decoding",
))? as u32;
match envelope.container_bits_per_sample {
8 => {
let bias = 1i32 << (envelope.valid_bits_per_sample - 1);
output.reserve(data.len());
for &byte in data {
output.push((i32::from(byte) >> shift) - bias);
}
Ok(())
}
16 => {
let sample_count = data.len() / 2;
output.reserve(sample_count);
for chunk in data.chunks_exact(2) {
let value = i16::from_le_bytes([chunk[0], chunk[1]]) as i32;
output.push(if shift == 0 { value } else { value >> shift });
}
Ok(())
}
24 => {
let sample_count = data.len() / 3;
output.reserve(sample_count);
for chunk in data.chunks_exact(3) {
let mut value =
i32::from(chunk[0]) | (i32::from(chunk[1]) << 8) | (i32::from(chunk[2]) << 16);
if value & 0x0080_0000 != 0 {
value |= !0x00ff_ffff;
}
output.push(if shift == 0 { value } else { value >> shift });
}
Ok(())
}
32 => {
let sample_count = data.len() / 4;
output.reserve(sample_count);
for chunk in data.chunks_exact(4) {
let value = i32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
output.push(if shift == 0 { value } else { value >> shift });
}
Ok(())
}
_ => Err(Error::UnsupportedPcmContainer(format!(
"unsupported container bits/sample for decoder: {}",
envelope.container_bits_per_sample
))),
}
}
fn read_chunk_payload<R: Read>(reader: &mut R, chunk_size: u32) -> Result<Vec<u8>> {
let mut payload = vec![0u8; chunk_size as usize];
reader.read_exact(&mut payload)?;
Ok(payload)
}
fn read_ds64_chunk<R: Read>(reader: &mut R, chunk_size: u32) -> Result<Ds64Chunk> {
if chunk_size < 28 {
return Err(Error::InvalidPcmContainer("RF64 ds64 chunk is too short"));
}
let chunk = Ds64Chunk {
riff_size: read_u64_le(reader)?,
data_size: read_u64_le(reader)?,
sample_count: read_u64_le(reader)?,
};
let table_length = read_u32_le(reader)?;
let extra_bytes = (chunk_size - 28) as usize;
if extra_bytes > 0 {
let mut discard = vec![0u8; extra_bytes];
reader.read_exact(&mut discard)?;
}
if table_length != 0 {
return Err(Error::UnsupportedPcmContainer(
"RF64 ds64 table entries are not supported yet".into(),
));
}
Ok(chunk)
}
fn read_u16_le<R: Read>(reader: &mut R) -> Result<u16> {
let mut bytes = [0u8; 2];
reader.read_exact(&mut bytes)?;
Ok(u16::from_le_bytes(bytes))
}
fn read_u32_le<R: Read>(reader: &mut R) -> Result<u32> {
let mut bytes = [0u8; 4];
reader.read_exact(&mut bytes)?;
Ok(u32::from_le_bytes(bytes))
}
fn read_u64_le<R: Read>(reader: &mut R) -> Result<u64> {
let mut bytes = [0u8; 8];
reader.read_exact(&mut bytes)?;
Ok(u64::from_le_bytes(bytes))
}
fn seek_forward<R: Seek>(reader: &mut R, chunk_start: u64, chunk_size: u64) -> Result<()> {
let target = chunk_start
.checked_add(chunk_size)
.ok_or(Error::InvalidPcmContainer(
"chunk length overflows the file cursor",
))?;
reader.seek(SeekFrom::Start(target))?;
Ok(())
}
fn w64_chunk_guid(chunk_id: [u8; 4]) -> [u8; 16] {
let mut guid = [0u8; 16];
guid[..4].copy_from_slice(&chunk_id);
guid[4..].copy_from_slice(&W64_CHUNK_GUID_SUFFIX);
guid
}
fn w64_metadata_chunk_id(guid: [u8; 16]) -> Option<[u8; 4]> {
if guid[4..] != W64_CHUNK_GUID_SUFFIX {
return None;
}
let chunk_id: [u8; 4] = guid[..4].try_into().expect("fixed chunk id");
if is_captured_metadata_chunk(chunk_id) {
Some(chunk_id)
} else {
None
}
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use crate::{
config::EncoderConfig,
input::{EncodePcmStream, PcmReaderOptions, read_pcm_reader_with_options},
metadata::FlacMetadataBlock,
};
use super::{PcmSpec, PcmStream, ordinary_channel_mask, read_wav};
struct ParsedForEncode {
wav: PcmStream,
metadata: crate::metadata::Metadata,
}
fn parse_wav_for_encode_with_config(wav: Vec<u8>, config: &EncoderConfig) -> ParsedForEncode {
let reader = read_pcm_reader_with_options(
Cursor::new(wav),
PcmReaderOptions {
capture_fxmd: config.capture_fxmd(),
strict_fxmd_validation: config.strict_fxmd_validation(),
},
)
.unwrap();
let metadata = reader.metadata().clone();
let spec = reader.spec();
let mut stream = reader.into_pcm_stream();
let mut samples = Vec::new();
while stream.read_chunk(4_096, &mut samples).unwrap() != 0 {}
ParsedForEncode {
wav: PcmStream { spec, samples },
metadata,
}
}
fn pcm_wav_bytes(
bits_per_sample: u16,
channels: u16,
sample_rate: u32,
samples: &[i32],
) -> Vec<u8> {
let bytes_per_sample = usize::from(bits_per_sample / 8);
let block_align = usize::from(channels) * bytes_per_sample;
let data_bytes = samples.len() * bytes_per_sample;
let riff_size = 4 + (8 + 16) + (8 + data_bytes);
let mut bytes = Vec::with_capacity(12 + 8 + 16 + 8 + data_bytes);
bytes.extend_from_slice(b"RIFF");
bytes.extend_from_slice(&(riff_size as u32).to_le_bytes());
bytes.extend_from_slice(b"WAVE");
bytes.extend_from_slice(b"fmt ");
bytes.extend_from_slice(&16u32.to_le_bytes());
bytes.extend_from_slice(&1u16.to_le_bytes());
bytes.extend_from_slice(&channels.to_le_bytes());
bytes.extend_from_slice(&sample_rate.to_le_bytes());
bytes.extend_from_slice(&(sample_rate * block_align as u32).to_le_bytes());
bytes.extend_from_slice(&(block_align as u16).to_le_bytes());
bytes.extend_from_slice(&bits_per_sample.to_le_bytes());
bytes.extend_from_slice(b"data");
bytes.extend_from_slice(&(data_bytes as u32).to_le_bytes());
match bits_per_sample {
16 => {
for &sample in samples {
bytes.extend_from_slice(&(sample as i16).to_le_bytes());
}
}
24 => {
for &sample in samples {
let value = sample as u32;
bytes.extend_from_slice(&[
(value & 0xff) as u8,
((value >> 8) & 0xff) as u8,
((value >> 16) & 0xff) as u8,
]);
}
}
_ => unreachable!(),
}
bytes
}
fn extensible_pcm_wav_bytes(
valid_bits_per_sample: u16,
container_bits_per_sample: u16,
channels: u16,
sample_rate: u32,
channel_mask: u32,
samples: &[i32],
) -> Vec<u8> {
let bytes_per_sample = usize::from(container_bits_per_sample / 8);
let block_align = usize::from(channels) * bytes_per_sample;
let data_bytes = samples.len() * bytes_per_sample;
let riff_size = 4 + (8 + super::EXTENSIBLE_FMT_CHUNK_SIZE as usize) + (8 + data_bytes);
let mut bytes =
Vec::with_capacity(12 + 8 + super::EXTENSIBLE_FMT_CHUNK_SIZE as usize + 8 + data_bytes);
bytes.extend_from_slice(b"RIFF");
bytes.extend_from_slice(&(riff_size as u32).to_le_bytes());
bytes.extend_from_slice(b"WAVE");
bytes.extend_from_slice(b"fmt ");
bytes.extend_from_slice(&super::EXTENSIBLE_FMT_CHUNK_SIZE.to_le_bytes());
bytes.extend_from_slice(&0xFFFEu16.to_le_bytes());
bytes.extend_from_slice(&channels.to_le_bytes());
bytes.extend_from_slice(&sample_rate.to_le_bytes());
bytes.extend_from_slice(&(sample_rate * block_align as u32).to_le_bytes());
bytes.extend_from_slice(&(block_align as u16).to_le_bytes());
bytes.extend_from_slice(&container_bits_per_sample.to_le_bytes());
bytes.extend_from_slice(&22u16.to_le_bytes());
bytes.extend_from_slice(&valid_bits_per_sample.to_le_bytes());
bytes.extend_from_slice(&channel_mask.to_le_bytes());
bytes.extend_from_slice(&super::PCM_SUBFORMAT_GUID);
bytes.extend_from_slice(b"data");
bytes.extend_from_slice(&(data_bytes as u32).to_le_bytes());
match container_bits_per_sample {
16 => {
for &sample in samples {
bytes.extend_from_slice(&(sample as i16).to_le_bytes());
}
}
24 => {
for &sample in samples {
let value = sample as u32;
bytes.extend_from_slice(&[
(value & 0xff) as u8,
((value >> 8) & 0xff) as u8,
((value >> 16) & 0xff) as u8,
]);
}
}
_ => unreachable!(),
}
bytes
}
fn append_chunk(bytes: &mut Vec<u8>, id: &[u8; 4], payload: &[u8]) {
bytes.extend_from_slice(id);
bytes.extend_from_slice(&(payload.len() as u32).to_le_bytes());
bytes.extend_from_slice(payload);
if !payload.len().is_multiple_of(2) {
bytes.push(0);
}
}
fn update_riff_size(bytes: &mut [u8]) {
let riff_size = (bytes.len() - 8) as u32;
bytes[4..8].copy_from_slice(&riff_size.to_le_bytes());
}
fn with_chunks(mut wav: Vec<u8>, chunks: &[([u8; 4], Vec<u8>)]) -> Vec<u8> {
let data_chunk_offset = wav
.windows(4)
.position(|window| window == b"data")
.expect("data chunk present");
let mut suffix = wav.split_off(data_chunk_offset);
for (id, payload) in chunks {
append_chunk(&mut wav, id, payload);
}
wav.append(&mut suffix);
update_riff_size(&mut wav);
wav
}
fn info_list_chunk(entries: &[([u8; 4], &[u8])]) -> Vec<u8> {
let mut payload = b"INFO".to_vec();
for (id, value) in entries {
append_chunk(&mut payload, id, value);
}
payload
}
fn cue_chunk(offsets: &[u32]) -> Vec<u8> {
let mut payload = Vec::new();
payload.extend_from_slice(&(offsets.len() as u32).to_le_bytes());
for (index, offset) in offsets.iter().enumerate() {
payload.extend_from_slice(&(index as u32).to_le_bytes());
payload.extend_from_slice(&0u32.to_le_bytes());
payload.extend_from_slice(b"data");
payload.extend_from_slice(&0u32.to_le_bytes());
payload.extend_from_slice(&0u32.to_le_bytes());
payload.extend_from_slice(&offset.to_le_bytes());
}
payload
}
fn invalid_fxmd_chunk() -> Vec<u8> {
vec![0x66, 0x78, 0x6d]
}
fn rf64_from_wav_bytes(wav: &[u8], sample_frames: u64) -> Vec<u8> {
assert_eq!(&wav[..4], b"RIFF");
let mut body = wav[12..].to_vec();
let data_chunk_offset = body
.windows(4)
.position(|window| window == b"data")
.expect("data chunk present");
let data_size_offset = data_chunk_offset + 4;
let data_size = u32::from_le_bytes(
body[data_size_offset..data_size_offset + 4]
.try_into()
.unwrap(),
) as u64;
body[data_size_offset..data_size_offset + 4].copy_from_slice(&u32::MAX.to_le_bytes());
let mut bytes = Vec::with_capacity(wav.len() + 36);
bytes.extend_from_slice(b"RF64");
bytes.extend_from_slice(&u32::MAX.to_le_bytes());
bytes.extend_from_slice(b"WAVE");
bytes.extend_from_slice(b"ds64");
bytes.extend_from_slice(&28u32.to_le_bytes());
let riff_size = (wav.len() as u64 - 8) + 36;
bytes.extend_from_slice(&riff_size.to_le_bytes());
bytes.extend_from_slice(&data_size.to_le_bytes());
bytes.extend_from_slice(&sample_frames.to_le_bytes());
bytes.extend_from_slice(&0u32.to_le_bytes());
bytes.extend_from_slice(&body);
bytes
}
fn w64_from_wav_bytes(wav: &[u8]) -> Vec<u8> {
assert_eq!(&wav[..4], b"RIFF");
assert_eq!(&wav[8..12], b"WAVE");
let mut body = &wav[12..];
let mut chunks = Vec::new();
while !body.is_empty() {
let chunk_id: [u8; 4] = body[..4].try_into().unwrap();
let chunk_size = u32::from_le_bytes(body[4..8].try_into().unwrap()) as usize;
let payload = body[8..8 + chunk_size].to_vec();
let padded_len = 8 + chunk_size + (chunk_size % 2);
chunks.push((chunk_id, payload));
body = &body[padded_len..];
}
let mut bytes = Vec::with_capacity(wav.len() + (chunks.len() * 16));
bytes.extend_from_slice(&super::W64_RIFF_GUID);
bytes.extend_from_slice(&0u64.to_le_bytes());
bytes.extend_from_slice(&super::w64_chunk_guid(*b"wave"));
for (chunk_id, payload) in chunks {
bytes.extend_from_slice(&super::w64_chunk_guid(chunk_id));
bytes.extend_from_slice(&((payload.len() + 24) as u64).to_le_bytes());
bytes.extend_from_slice(&payload);
let padding = (8 - (payload.len() % 8)) % 8;
if padding != 0 {
bytes.resize(bytes.len() + padding, 0);
}
}
let total_size = bytes.len() as u64;
bytes[16..24].copy_from_slice(&total_size.to_le_bytes());
bytes
}
fn fxmd_chunk(version: u16, flags: u16) -> Vec<u8> {
let mut payload = Vec::new();
payload.extend_from_slice(b"fxmd");
payload.extend_from_slice(&version.to_le_bytes());
payload.extend_from_slice(&flags.to_le_bytes());
payload.extend_from_slice(&1u32.to_le_bytes());
payload.extend_from_slice(&1u32.to_le_bytes());
payload.extend_from_slice(&1u32.to_le_bytes());
payload.push(0);
payload.push(1); payload.push(0);
payload.extend_from_slice(&0u16.to_le_bytes());
payload.extend_from_slice(&0u32.to_le_bytes());
payload.extend_from_slice(&0u32.to_le_bytes());
payload
}
#[test]
fn parses_16bit_pcm_wav() {
let samples = [0, -1_000, 1_000, -2_000];
let wav = read_wav(Cursor::new(pcm_wav_bytes(16, 2, 44_100, &samples))).unwrap();
assert_eq!(
wav,
PcmStream {
spec: PcmSpec {
sample_rate: 44_100,
channels: 2,
bits_per_sample: 16,
total_samples: 2,
bytes_per_sample: 2,
channel_mask: ordinary_channel_mask(2u16).unwrap(),
},
samples: samples.to_vec(),
}
);
}
#[test]
fn parses_rf64_pcm_wav() {
let samples = [0, -1_000, 1_000, -2_000];
let wav = pcm_wav_bytes(16, 2, 44_100, &samples);
let rf64 = rf64_from_wav_bytes(&wav, 2);
let parsed = read_wav(Cursor::new(rf64)).unwrap();
assert_eq!(parsed.spec.total_samples, 2);
assert_eq!(parsed.spec.channels, 2);
assert_eq!(parsed.spec.bits_per_sample, 16);
assert_eq!(parsed.samples, samples);
}
#[test]
fn rejects_rf64_without_ds64() {
let wav = pcm_wav_bytes(16, 1, 44_100, &[0, 1, 2, 3]);
let mut rf64 = rf64_from_wav_bytes(&wav, 4);
rf64.drain(12..48);
let error = read_wav(Cursor::new(rf64)).unwrap_err();
assert!(error.to_string().contains("ds64"));
}
#[test]
fn parses_w64_pcm_wav() {
let samples = [0, -1_000, 1_000, -2_000];
let wav = pcm_wav_bytes(16, 2, 44_100, &samples);
let parsed = read_wav(Cursor::new(w64_from_wav_bytes(&wav))).unwrap();
assert_eq!(parsed.spec.total_samples, 2);
assert_eq!(parsed.spec.channels, 2);
assert_eq!(parsed.spec.bits_per_sample, 16);
assert_eq!(parsed.samples, samples);
}
#[test]
fn rejects_non_pcm_format_tag() {
let mut bytes = pcm_wav_bytes(16, 1, 44_100, &[0, 1, 2, 3]);
bytes[20] = 3;
let error = read_wav(Cursor::new(bytes)).unwrap_err();
assert!(error.to_string().contains("only PCM"));
}
#[test]
fn rejects_non_extensible_multichannel_pcm_tag1_input() {
let error = read_wav(Cursor::new(pcm_wav_bytes(16, 3, 44_100, &[0; 9]))).unwrap_err();
assert!(error.to_string().contains("exact mono/stereo"));
}
#[test]
fn accepts_non_ordinary_extensible_channel_masks() {
let wav = read_wav(Cursor::new(extensible_pcm_wav_bytes(
16,
16,
4,
48_000,
0x0001_2104,
&[1, 2, 3, 4, 5, 6, 7, 8],
)))
.unwrap();
assert_eq!(wav.spec.channel_mask, 0x0001_2104);
assert_eq!(wav.spec.channels, 4);
}
#[test]
fn accepts_zero_extensible_channel_mask() {
let wav = read_wav(Cursor::new(extensible_pcm_wav_bytes(
16,
16,
2,
44_100,
0,
&[1, -1, 2, -2],
)))
.unwrap();
assert_eq!(wav.spec.channel_mask, 0);
assert_eq!(wav.spec.channels, 2);
}
#[test]
fn rejects_extensible_channel_masks_with_unsupported_speaker_bits() {
let error = read_wav(Cursor::new(extensible_pcm_wav_bytes(
16,
16,
4,
48_000,
0x0004_0000,
&[0; 8],
)))
.unwrap_err();
assert!(error.to_string().contains("channel mask"));
}
#[test]
fn rejects_zero_sample_rate() {
let mut bytes = pcm_wav_bytes(16, 1, 44_100, &[0, 1, 2, 3]);
bytes[24..28].copy_from_slice(&0u32.to_le_bytes());
bytes[28..32].copy_from_slice(&0u32.to_le_bytes());
let error = read_wav(Cursor::new(bytes)).unwrap_err();
assert!(error.to_string().contains("sample rate 0"));
}
#[test]
fn rejects_zero_block_align_without_panicking() {
let mut bytes = pcm_wav_bytes(16, 1, 44_100, &[0, 1, 2, 3]);
bytes[32..34].copy_from_slice(&0u16.to_le_bytes());
let error = read_wav(Cursor::new(bytes)).unwrap_err();
assert!(error.to_string().contains("block alignment"));
}
#[test]
fn read_wav_remains_audio_only_when_metadata_chunks_exist() {
let wav = with_chunks(
pcm_wav_bytes(16, 1, 44_100, &[0, 1, 2, 3]),
&[(*b"LIST", info_list_chunk(&[(*b"IART", b"Example Artist")]))],
);
let parsed = read_wav(Cursor::new(wav)).unwrap();
assert_eq!(parsed.spec.total_samples, 4);
assert_eq!(parsed.samples, vec![0, 1, 2, 3]);
}
#[cfg(feature = "wav")]
#[test]
fn read_wav_for_encode_captures_info_and_cue_metadata() {
let wav = with_chunks(
pcm_wav_bytes(16, 1, 44_100, &[0, 1, 2, 3]),
&[
(*b"LIST", info_list_chunk(&[(*b"IART", b"Example Artist")])),
(*b"cue ", cue_chunk(&[0, 2])),
],
);
let parsed = parse_wav_for_encode_with_config(wav, &EncoderConfig::default());
let blocks = parsed.metadata.flac_blocks(parsed.wav.spec.total_samples);
assert_eq!(blocks.len(), 2);
assert!(matches!(&blocks[0], FlacMetadataBlock::VorbisComment(_)));
assert!(matches!(&blocks[1], FlacMetadataBlock::CueSheet(_)));
}
#[cfg(feature = "wav")]
#[test]
fn ignores_malformed_metadata_chunks_without_rejecting_audio() {
let mut malformed_list = b"INFO".to_vec();
malformed_list.extend_from_slice(b"IART");
malformed_list.extend_from_slice(&99u32.to_le_bytes());
malformed_list.extend_from_slice(b"too-short");
let wav = with_chunks(
pcm_wav_bytes(16, 1, 44_100, &[0, 1, 2, 3]),
&[(*b"LIST", malformed_list)],
);
let parsed = parse_wav_for_encode_with_config(wav, &EncoderConfig::default());
assert!(
parsed
.metadata
.flac_blocks(parsed.wav.spec.total_samples)
.is_empty()
);
assert_eq!(parsed.wav.samples, vec![0, 1, 2, 3]);
}
#[cfg(feature = "wav")]
#[test]
fn read_wav_for_encode_leniently_ignores_invalid_fxmd_chunks() {
let wav = with_chunks(
pcm_wav_bytes(16, 1, 44_100, &[0, 1, 2, 3]),
&[
(*b"fxmd", invalid_fxmd_chunk()),
(*b"LIST", info_list_chunk(&[(*b"IART", b"Example Artist")])),
(*b"cue ", cue_chunk(&[0, 2])),
],
);
let parsed = parse_wav_for_encode_with_config(
wav,
&EncoderConfig::default().with_strict_fxmd_validation(false),
);
let blocks = parsed.metadata.flac_blocks(parsed.wav.spec.total_samples);
assert_eq!(blocks.len(), 2);
assert!(matches!(&blocks[0], FlacMetadataBlock::VorbisComment(_)));
assert!(matches!(&blocks[1], FlacMetadataBlock::CueSheet(_)));
}
#[cfg(feature = "wav")]
#[test]
fn read_wav_for_encode_can_ignore_fxmd_chunks_entirely() {
let wav = with_chunks(
pcm_wav_bytes(16, 1, 44_100, &[0, 1, 2, 3]),
&[
(*b"fxmd", invalid_fxmd_chunk()),
(*b"LIST", info_list_chunk(&[(*b"IART", b"Example Artist")])),
(*b"cue ", cue_chunk(&[0, 2])),
],
);
let parsed = parse_wav_for_encode_with_config(
wav,
&EncoderConfig::default()
.with_capture_fxmd(false)
.with_strict_fxmd_validation(false),
);
let blocks = parsed.metadata.flac_blocks(parsed.wav.spec.total_samples);
assert_eq!(blocks.len(), 2);
assert!(matches!(&blocks[0], FlacMetadataBlock::VorbisComment(_)));
assert!(matches!(&blocks[1], FlacMetadataBlock::CueSheet(_)));
}
#[cfg(feature = "wav")]
#[test]
fn read_wav_for_encode_rejects_unsupported_fxmd_header_flags_by_default() {
let wav = with_chunks(
pcm_wav_bytes(16, 1, 44_100, &[0, 1, 2, 3]),
&[(*b"fxmd", fxmd_chunk(1, 0))],
);
let result = read_pcm_reader_with_options(
Cursor::new(wav),
PcmReaderOptions {
capture_fxmd: true,
strict_fxmd_validation: true,
},
);
let error = match result {
Ok(_) => panic!("unsupported fxmd flags should fail"),
Err(error) => error,
};
assert!(error.to_string().contains("flags are unsupported"));
}
#[cfg(feature = "wav")]
#[test]
fn read_wav_for_encode_leniently_ignores_unsupported_fxmd_header_flags() {
let wav = with_chunks(
pcm_wav_bytes(16, 1, 44_100, &[0, 1, 2, 3]),
&[
(*b"fxmd", fxmd_chunk(1, 0)),
(*b"LIST", info_list_chunk(&[(*b"IART", b"Example Artist")])),
],
);
let parsed = parse_wav_for_encode_with_config(
wav,
&EncoderConfig::default().with_strict_fxmd_validation(false),
);
let blocks = parsed.metadata.flac_blocks(parsed.wav.spec.total_samples);
assert_eq!(blocks.len(), 1);
assert!(matches!(&blocks[0], FlacMetadataBlock::VorbisComment(_)));
}
#[test]
fn directly_builds_canonical_wav_stream() {
let data = [1i16, -2, 3, -4]
.into_iter()
.flat_map(i16::to_le_bytes)
.collect::<Vec<_>>();
let mut stream = super::WavPcmStream::builder(Cursor::new(data))
.sample_rate(44_100)
.channels(2)
.valid_bits_per_sample(16)
.total_samples(2)
.build()
.unwrap();
let mut samples = Vec::new();
stream.read_chunk(2, &mut samples).unwrap();
assert_eq!(
stream.spec().channel_mask,
ordinary_channel_mask(2).unwrap()
);
assert_eq!(samples, vec![1, -2, 3, -4]);
}
#[test]
fn directly_builds_extensible_wav_stream() {
let envelope = crate::pcm::PcmEnvelope {
channels: 4,
valid_bits_per_sample: 20,
container_bits_per_sample: 24,
channel_mask: 0x0033,
};
let mut data = Vec::new();
for &sample in &[1, -2, 3, -4, 5, -6, 7, -8] {
crate::pcm::append_encoded_sample(&mut data, sample, envelope).unwrap();
}
let mut stream = super::WavPcmStream::builder(Cursor::new(data))
.sample_rate(48_000)
.channels(4)
.valid_bits_per_sample(20)
.container_bits_per_sample(24)
.channel_mask(0x0033)
.total_samples(2)
.build()
.unwrap();
let mut samples = Vec::new();
stream.read_chunk(2, &mut samples).unwrap();
assert_eq!(stream.spec().bits_per_sample, 20);
assert_eq!(stream.spec().bytes_per_sample, 3);
assert_eq!(stream.spec().channel_mask, 0x0033);
assert_eq!(samples, vec![1, -2, 3, -4, 5, -6, 7, -8]);
}
#[test]
fn direct_wav_builder_requires_total_samples() {
let error = super::WavPcmStream::builder(Cursor::new(Vec::new()))
.sample_rate(44_100)
.channels(2)
.valid_bits_per_sample(16)
.build()
.unwrap_err();
assert!(error.to_string().contains("total_samples"));
}
}