use oximedia_core::{OxiError, OxiResult};
pub const FRAME_SYNC: u16 = 0x3FFE;
#[derive(Clone, Debug)]
pub struct FrameHeader {
pub variable_blocksize: bool,
pub block_size: u32,
pub sample_rate: Option<u32>,
pub channel_assignment: ChannelAssignment,
pub sample_size: Option<u8>,
pub number: u64,
pub crc: u8,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ChannelAssignment {
Independent(u8),
LeftSide,
RightSide,
MidSide,
}
impl FrameHeader {
#[allow(clippy::too_many_lines)]
pub fn parse(data: &[u8]) -> OxiResult<(Self, usize)> {
if data.len() < 4 {
return Err(OxiError::UnexpectedEof);
}
let sync = (u16::from(data[0]) << 6) | (u16::from(data[1]) >> 2);
if sync != FRAME_SYNC {
return Err(OxiError::Parse {
offset: 0,
message: format!(
"Invalid frame sync code: expected 0x{FRAME_SYNC:04X}, got 0x{sync:04X}"
),
});
}
if data[1] & 0x02 != 0 {
return Err(OxiError::Parse {
offset: 1,
message: "Reserved bit is not zero".into(),
});
}
let variable_blocksize = data[1] & 0x01 != 0;
let block_size_code = (data[2] >> 4) & 0x0F;
let sample_rate_code = data[2] & 0x0F;
let channel_code = (data[3] >> 4) & 0x0F;
let sample_size_code = (data[3] >> 1) & 0x07;
if data[3] & 0x01 != 0 {
return Err(OxiError::Parse {
offset: 3,
message: "Reserved bit is not zero".into(),
});
}
let mut offset = 4;
let (number, consumed) = parse_utf8_number(&data[offset..])?;
offset += consumed;
let block_size = match block_size_code {
0 => {
return Err(OxiError::Parse {
offset: 2,
message: "Reserved block size code".into(),
})
}
1 => 192,
2..=5 => 576 << (block_size_code - 2),
6 => {
if offset >= data.len() {
return Err(OxiError::UnexpectedEof);
}
let size = u32::from(data[offset]) + 1;
offset += 1;
size
}
7 => {
if offset + 1 >= data.len() {
return Err(OxiError::UnexpectedEof);
}
let size = u32::from(u16::from_be_bytes([data[offset], data[offset + 1]])) + 1;
offset += 2;
size
}
_ => 256 << (block_size_code - 8),
};
let sample_rate = match sample_rate_code {
0 => None, 1 => Some(88_200),
2 => Some(176_400),
3 => Some(192_000),
4 => Some(8_000),
5 => Some(16_000),
6 => Some(22_050),
7 => Some(24_000),
8 => Some(32_000),
9 => Some(44_100),
10 => Some(48_000),
11 => Some(96_000),
12 => {
if offset >= data.len() {
return Err(OxiError::UnexpectedEof);
}
let rate = u32::from(data[offset]) * 1000;
offset += 1;
Some(rate)
}
13 => {
if offset + 1 >= data.len() {
return Err(OxiError::UnexpectedEof);
}
let rate = u32::from(u16::from_be_bytes([data[offset], data[offset + 1]]));
offset += 2;
Some(rate)
}
14 => {
if offset + 1 >= data.len() {
return Err(OxiError::UnexpectedEof);
}
let rate = u32::from(u16::from_be_bytes([data[offset], data[offset + 1]])) * 10;
offset += 2;
Some(rate)
}
15 => {
return Err(OxiError::Parse {
offset: 2,
message: "Invalid sample rate code 15".into(),
})
}
_ => unreachable!(),
};
let channel_assignment = match channel_code {
0..=7 => ChannelAssignment::Independent(channel_code + 1),
8 => ChannelAssignment::LeftSide,
9 => ChannelAssignment::RightSide,
10 => ChannelAssignment::MidSide,
_ => {
return Err(OxiError::Parse {
offset: 3,
message: format!("Reserved channel assignment code: {channel_code}"),
})
}
};
let sample_size = match sample_size_code {
0 => None, 1 => Some(8),
2 => Some(12),
3 => {
return Err(OxiError::Parse {
offset: 3,
message: "Reserved sample size code 3".into(),
})
}
4 => Some(16),
5 => Some(20),
6 => Some(24),
7 => Some(32),
_ => unreachable!(),
};
if offset >= data.len() {
return Err(OxiError::UnexpectedEof);
}
let crc = data[offset];
offset += 1;
Ok((
Self {
variable_blocksize,
block_size,
sample_rate,
channel_assignment,
sample_size,
number,
crc,
},
offset,
))
}
#[must_use]
pub const fn channels(&self) -> u8 {
match self.channel_assignment {
ChannelAssignment::Independent(n) => n,
ChannelAssignment::LeftSide
| ChannelAssignment::RightSide
| ChannelAssignment::MidSide => 2,
}
}
#[must_use]
pub const fn uses_stereo_decorrelation(&self) -> bool {
matches!(
self.channel_assignment,
ChannelAssignment::LeftSide | ChannelAssignment::RightSide | ChannelAssignment::MidSide
)
}
}
fn parse_utf8_number(data: &[u8]) -> OxiResult<(u64, usize)> {
if data.is_empty() {
return Err(OxiError::UnexpectedEof);
}
let first = data[0];
let (mut value, len) = if first < 0x80 {
(u64::from(first), 1)
} else if first < 0xC0 {
return Err(OxiError::Parse {
offset: 0,
message: "Invalid UTF-8 start byte".into(),
});
} else if first < 0xE0 {
if data.len() < 2 {
return Err(OxiError::UnexpectedEof);
}
let value = (u64::from(first & 0x1F) << 6) | u64::from(data[1] & 0x3F);
(value, 2)
} else if first < 0xF0 {
if data.len() < 3 {
return Err(OxiError::UnexpectedEof);
}
let value = (u64::from(first & 0x0F) << 12)
| (u64::from(data[1] & 0x3F) << 6)
| u64::from(data[2] & 0x3F);
(value, 3)
} else if first < 0xF8 {
if data.len() < 4 {
return Err(OxiError::UnexpectedEof);
}
let value = (u64::from(first & 0x07) << 18)
| (u64::from(data[1] & 0x3F) << 12)
| (u64::from(data[2] & 0x3F) << 6)
| u64::from(data[3] & 0x3F);
(value, 4)
} else if first < 0xFC {
if data.len() < 5 {
return Err(OxiError::UnexpectedEof);
}
let value = (u64::from(first & 0x03) << 24)
| (u64::from(data[1] & 0x3F) << 18)
| (u64::from(data[2] & 0x3F) << 12)
| (u64::from(data[3] & 0x3F) << 6)
| u64::from(data[4] & 0x3F);
(value, 5)
} else if first < 0xFE {
if data.len() < 6 {
return Err(OxiError::UnexpectedEof);
}
let value = (u64::from(first & 0x01) << 30)
| (u64::from(data[1] & 0x3F) << 24)
| (u64::from(data[2] & 0x3F) << 18)
| (u64::from(data[3] & 0x3F) << 12)
| (u64::from(data[4] & 0x3F) << 6)
| u64::from(data[5] & 0x3F);
(value, 6)
} else {
if data.len() < 7 {
return Err(OxiError::UnexpectedEof);
}
let value = (u64::from(data[1] & 0x3F) << 30)
| (u64::from(data[2] & 0x3F) << 24)
| (u64::from(data[3] & 0x3F) << 18)
| (u64::from(data[4] & 0x3F) << 12)
| (u64::from(data[5] & 0x3F) << 6)
| u64::from(data[6] & 0x3F);
(value, 7)
};
for byte in data.iter().take(len).skip(1) {
if byte & 0xC0 != 0x80 {
return Err(OxiError::Parse {
offset: 0,
message: "Invalid UTF-8 continuation byte".into(),
});
}
}
if len > 1 {
value = u64::from(first) & (0x7F >> len);
for byte in data.iter().take(len).skip(1) {
value = (value << 6) | u64::from(byte & 0x3F);
}
}
Ok((value, len))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_frame_sync() {
assert_eq!(FRAME_SYNC, 0x3FFE);
}
fn channel_count(ca: ChannelAssignment) -> u8 {
match ca {
ChannelAssignment::Independent(n) => n,
ChannelAssignment::LeftSide
| ChannelAssignment::RightSide
| ChannelAssignment::MidSide => 2,
}
}
#[test]
fn test_channel_assignment() {
assert_eq!(channel_count(ChannelAssignment::Independent(1)), 1);
assert_eq!(channel_count(ChannelAssignment::Independent(2)), 2);
assert_eq!(channel_count(ChannelAssignment::LeftSide), 2);
assert_eq!(channel_count(ChannelAssignment::RightSide), 2);
assert_eq!(channel_count(ChannelAssignment::MidSide), 2);
}
#[test]
fn test_utf8_number_single_byte() {
assert_eq!(
parse_utf8_number(&[0x00]).expect("operation should succeed"),
(0, 1)
);
assert_eq!(
parse_utf8_number(&[0x7F]).expect("operation should succeed"),
(127, 1)
);
assert_eq!(
parse_utf8_number(&[0x42]).expect("operation should succeed"),
(66, 1)
);
}
#[test]
fn test_utf8_number_two_bytes() {
assert_eq!(
parse_utf8_number(&[0xC2, 0x80]).expect("operation should succeed"),
(128, 2)
);
assert_eq!(
parse_utf8_number(&[0xDF, 0xBF]).expect("operation should succeed"),
(2047, 2)
);
}
#[test]
fn test_utf8_number_three_bytes() {
assert_eq!(
parse_utf8_number(&[0xE0, 0xA0, 0x80]).expect("operation should succeed"),
(2048, 3)
);
}
#[test]
fn test_utf8_number_invalid_start() {
assert!(parse_utf8_number(&[0x80]).is_err());
assert!(parse_utf8_number(&[0xBF]).is_err());
}
#[test]
fn test_utf8_number_too_short() {
assert!(parse_utf8_number(&[0xC2]).is_err());
}
#[test]
fn test_frame_header_parse() {
let mut data = Vec::new();
data.push(0xFF); data.push(0xF8); data.push(0x89); data.push(0x18); data.push(0x00); data.push(0x00);
let result = FrameHeader::parse(&data);
assert!(result.is_ok());
let (header, consumed) = result.expect("operation should succeed");
assert!(!header.variable_blocksize);
assert_eq!(header.block_size, 256);
assert_eq!(header.sample_rate, Some(44_100));
assert_eq!(header.channel_assignment, ChannelAssignment::Independent(2));
assert_eq!(header.sample_size, Some(16));
assert_eq!(header.number, 0);
assert_eq!(consumed, 6);
}
#[test]
fn test_frame_header_variable_blocksize() {
let mut data = Vec::new();
data.push(0xFF);
data.push(0xF9); data.push(0x89);
data.push(0x18);
data.push(0x00);
data.push(0x00);
let (header, _) = FrameHeader::parse(&data).expect("operation should succeed");
assert!(header.variable_blocksize);
}
#[test]
fn test_frame_header_invalid_sync() {
let data = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
assert!(FrameHeader::parse(&data).is_err());
}
#[test]
fn test_frame_header_reserved_bit() {
let data = [0xFF, 0xFA, 0x89, 0x18, 0x00, 0x00];
assert!(FrameHeader::parse(&data).is_err());
}
#[test]
fn test_frame_header_stereo_modes() {
let mut data = vec![0xFF, 0xF8, 0x89, 0x88, 0x00, 0x00]; let (header, _) = FrameHeader::parse(&data).expect("operation should succeed");
assert_eq!(header.channel_assignment, ChannelAssignment::LeftSide);
assert!(header.uses_stereo_decorrelation());
data[3] = 0x98; let (header, _) = FrameHeader::parse(&data).expect("operation should succeed");
assert_eq!(header.channel_assignment, ChannelAssignment::RightSide);
data[3] = 0xA8; let (header, _) = FrameHeader::parse(&data).expect("operation should succeed");
assert_eq!(header.channel_assignment, ChannelAssignment::MidSide);
}
#[test]
fn test_frame_header_channels() {
let mut data = vec![0xFF, 0xF8, 0x89, 0x18, 0x00, 0x00];
data[3] = 0x08;
let (header, _) = FrameHeader::parse(&data).expect("operation should succeed");
assert_eq!(header.channels(), 1);
data[3] = 0x78;
let (header, _) = FrameHeader::parse(&data).expect("operation should succeed");
assert_eq!(header.channels(), 8);
data[3] = 0x88;
let (header, _) = FrameHeader::parse(&data).expect("operation should succeed");
assert_eq!(header.channels(), 2);
}
#[test]
fn test_frame_header_block_sizes() {
let mut data = vec![0xFF, 0xF8, 0x00, 0x18, 0x00, 0x00];
data[2] = 0x19;
let (header, _) = FrameHeader::parse(&data).expect("operation should succeed");
assert_eq!(header.block_size, 192);
data[2] = 0x29;
let (header, _) = FrameHeader::parse(&data).expect("operation should succeed");
assert_eq!(header.block_size, 576);
data[2] = 0x39;
let (header, _) = FrameHeader::parse(&data).expect("operation should succeed");
assert_eq!(header.block_size, 1152);
data[2] = 0x89;
let (header, _) = FrameHeader::parse(&data).expect("operation should succeed");
assert_eq!(header.block_size, 256);
data[2] = 0x99;
let (header, _) = FrameHeader::parse(&data).expect("operation should succeed");
assert_eq!(header.block_size, 512);
}
#[test]
fn test_frame_header_extra_block_size() {
let data = vec![0xFF, 0xF8, 0x69, 0x18, 0x00, 0xFF, 0x00]; let (header, consumed) = FrameHeader::parse(&data).expect("operation should succeed");
assert_eq!(header.block_size, 256);
assert_eq!(consumed, 7);
let data = vec![0xFF, 0xF8, 0x79, 0x18, 0x00, 0x0F, 0xFF, 0x00]; let (header, consumed) = FrameHeader::parse(&data).expect("operation should succeed");
assert_eq!(header.block_size, 4096);
assert_eq!(consumed, 8);
}
#[test]
fn test_frame_header_sample_rates() {
let mut data = vec![0xFF, 0xF8, 0x80, 0x18, 0x00, 0x00];
let (header, _) = FrameHeader::parse(&data).expect("operation should succeed");
assert_eq!(header.sample_rate, None);
data[2] = 0x89;
let (header, _) = FrameHeader::parse(&data).expect("operation should succeed");
assert_eq!(header.sample_rate, Some(44_100));
data[2] = 0x8A;
let (header, _) = FrameHeader::parse(&data).expect("operation should succeed");
assert_eq!(header.sample_rate, Some(48_000));
}
#[test]
fn test_frame_header_sample_sizes() {
let mut data = vec![0xFF, 0xF8, 0x89, 0x00, 0x00, 0x00];
let (header, _) = FrameHeader::parse(&data).expect("operation should succeed");
assert_eq!(header.sample_size, None);
data[3] = 0x02;
let (header, _) = FrameHeader::parse(&data).expect("operation should succeed");
assert_eq!(header.sample_size, Some(8));
data[3] = 0x08;
let (header, _) = FrameHeader::parse(&data).expect("operation should succeed");
assert_eq!(header.sample_size, Some(16));
data[3] = 0x0C;
let (header, _) = FrameHeader::parse(&data).expect("operation should succeed");
assert_eq!(header.sample_size, Some(24));
}
}