use super::constants::{BITRATES, PADDING_SIZES, SAMPLES, SAMPLE_RATES, SIDE_INFORMATION_SIZES};
use crate::error::Result;
use crate::macros::decode_err;
use std::io::{Read, Seek, SeekFrom};
use byteorder::{BigEndian, ReadBytesExt};
pub(crate) fn verify_frame_sync(frame_sync: [u8; 2]) -> bool {
frame_sync[0] == 0xFF && frame_sync[1] >> 5 == 0b111
}
pub(crate) fn search_for_frame_sync<R>(input: &mut R) -> std::io::Result<Option<u64>>
where
R: Read,
{
let mut iterator = input.bytes();
let mut buffer = [0u8; 2];
if let Some(byte) = iterator.next() {
buffer[0] = byte?;
}
for (index, byte) in iterator.enumerate() {
buffer[1] = byte?;
if verify_frame_sync(buffer) {
return Ok(Some(index as u64));
}
buffer[0] = buffer[1];
}
Ok(None)
}
const REV_FRAME_SEARCH_BOUNDS: u64 = 1024;
pub(super) fn rev_search_for_frame_sync<R>(
input: &mut R,
pos: &mut u64,
) -> std::io::Result<Option<u64>>
where
R: Read + Seek,
{
let search_bounds = std::cmp::min(*pos, REV_FRAME_SEARCH_BOUNDS);
*pos -= search_bounds;
input.seek(SeekFrom::Start(*pos))?;
let ret = search_for_frame_sync(&mut input.take(search_bounds));
if let Ok(Some(_)) = ret {
input.seek(SeekFrom::Current(-2))?;
}
ret
}
pub(crate) enum HeaderCmpResult {
Equal,
Undetermined,
NotEqual,
}
pub(super) const HEADER_MASK: u32 = 0xFFFE_0C00;
pub(crate) fn cmp_header<R>(
reader: &mut R,
header_size: u32,
first_header_len: u32,
first_header_bytes: u32,
header_mask: u32,
) -> HeaderCmpResult
where
R: Read + Seek,
{
let res = reader.seek(SeekFrom::Current(i64::from(
first_header_len.saturating_sub(header_size),
)));
if res.is_err() {
return HeaderCmpResult::Undetermined;
}
let second_header_data = reader.read_u32::<BigEndian>();
if second_header_data.is_err() {
return HeaderCmpResult::Undetermined;
}
if reader.seek(SeekFrom::Current(-4)).is_err() {
return HeaderCmpResult::Undetermined;
}
match second_header_data {
Ok(second_header_data)
if first_header_bytes & header_mask == second_header_data & header_mask =>
{
HeaderCmpResult::Equal
},
_ => HeaderCmpResult::NotEqual,
}
}
#[derive(Default, PartialEq, Eq, Copy, Clone, Debug)]
#[allow(missing_docs)]
pub enum MpegVersion {
#[default]
V1,
V2,
V2_5,
V4,
}
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum Layer {
Layer1 = 1,
Layer2 = 2,
#[default]
Layer3 = 3,
}
#[derive(Default, Copy, Clone, PartialEq, Eq, Debug)]
#[allow(missing_docs)]
pub enum ChannelMode {
#[default]
Stereo = 0,
JointStereo = 1,
DualChannel = 2,
SingleChannel = 3,
}
#[derive(Default, Copy, Clone, PartialEq, Eq, Debug)]
#[allow(missing_docs, non_camel_case_types)]
pub enum Emphasis {
#[default]
None,
MS5015,
Reserved,
CCIT_J17,
}
#[derive(Copy, Clone)]
pub(crate) struct Header {
pub(crate) sample_rate: u32,
pub(crate) len: u32,
pub(crate) data_start: u32,
pub(crate) samples: u16,
pub(crate) bitrate: u32,
pub(crate) version: MpegVersion,
pub(crate) layer: Layer,
pub(crate) channel_mode: ChannelMode,
pub(crate) mode_extension: Option<u8>,
pub(crate) copyright: bool,
pub(crate) original: bool,
pub(crate) emphasis: Emphasis,
}
impl Header {
pub(super) fn read(data: u32) -> Option<Self> {
let version = match (data >> 19) & 0b11 {
0 => MpegVersion::V2_5,
2 => MpegVersion::V2,
3 => MpegVersion::V1,
_ => return None,
};
let version_index = if version == MpegVersion::V1 { 0 } else { 1 };
let layer = match (data >> 17) & 0b11 {
1 => Layer::Layer3,
2 => Layer::Layer2,
3 => Layer::Layer1,
_ => {
log::debug!("MPEG: Frame header uses a reserved layer");
return None;
},
};
let mut header = Header {
sample_rate: 0,
len: 0,
data_start: 0,
samples: 0,
bitrate: 0,
version,
layer,
channel_mode: ChannelMode::default(),
mode_extension: None,
copyright: false,
original: false,
emphasis: Emphasis::default(),
};
let layer_index = (header.layer as usize).saturating_sub(1);
let bitrate_index = (data >> 12) & 0xF;
header.bitrate = BITRATES[version_index][layer_index][bitrate_index as usize];
if header.bitrate == 0 {
return None;
}
let sample_rate_index = (data >> 10) & 0b11;
header.sample_rate = match sample_rate_index {
3 => return None,
_ => SAMPLE_RATES[header.version as usize][sample_rate_index as usize],
};
let has_padding = ((data >> 9) & 1) == 1;
let mut padding = 0;
if has_padding {
padding = u32::from(PADDING_SIZES[layer_index]);
}
header.channel_mode = match (data >> 6) & 3 {
0 => ChannelMode::Stereo,
1 => ChannelMode::JointStereo,
2 => ChannelMode::DualChannel,
3 => ChannelMode::SingleChannel,
_ => unreachable!(),
};
if let ChannelMode::JointStereo = header.channel_mode {
header.mode_extension = Some(((data >> 4) & 3) as u8);
} else {
header.mode_extension = None;
}
header.copyright = ((data >> 3) & 1) == 1;
header.original = ((data >> 2) & 1) == 1;
header.emphasis = match data & 3 {
0 => Emphasis::None,
1 => Emphasis::MS5015,
2 => Emphasis::Reserved,
3 => Emphasis::CCIT_J17,
_ => unreachable!(),
};
header.data_start = SIDE_INFORMATION_SIZES[version_index][header.channel_mode as usize] + 4;
header.samples = SAMPLES[layer_index][version_index];
header.len =
(u32::from(header.samples) * header.bitrate * 125 / header.sample_rate) + padding;
Some(header)
}
}
pub(super) struct XingHeader {
pub frames: u32,
pub size: u32,
}
impl XingHeader {
pub(super) fn read(reader: &mut &[u8]) -> Result<Option<Self>> {
let reader_len = reader.len();
let mut header = [0; 4];
reader.read_exact(&mut header)?;
match &header {
b"Xing" | b"Info" => {
if reader_len < 16 {
decode_err!(@BAIL MPEG, "Xing header has an invalid size (< 16)");
}
let mut flags = [0; 4];
reader.read_exact(&mut flags)?;
if flags[3] & 0x03 != 0x03 {
log::debug!(
"MPEG: Xing header doesn't have required flags set (0x0001 and 0x0002)"
);
return Ok(None);
}
let frames = reader.read_u32::<BigEndian>()?;
let size = reader.read_u32::<BigEndian>()?;
Ok(Some(Self { frames, size }))
},
b"VBRI" => {
if reader_len < 32 {
decode_err!(@BAIL MPEG, "VBRI header has an invalid size (< 32)");
}
let _info = reader.read_uint::<BigEndian>(6)?;
let size = reader.read_u32::<BigEndian>()?;
let frames = reader.read_u32::<BigEndian>()?;
Ok(Some(Self { frames, size }))
},
_ => Ok(None),
}
}
pub(super) fn is_valid(&self) -> bool {
self.frames > 0 && self.size > 0
}
}
#[cfg(test)]
mod tests {
use crate::tag::utils::test_utils::read_path;
use std::io::{Cursor, Read, Seek, SeekFrom};
#[test]
fn search_for_frame_sync() {
fn test(data: &[u8], expected_result: Option<u64>) {
use super::search_for_frame_sync;
assert_eq!(search_for_frame_sync(&mut &*data).unwrap(), expected_result);
}
test(&[0xFF, 0xFB, 0x00], Some(0));
test(&[0x00, 0x00, 0x01, 0xFF, 0xFB], Some(3));
test(&[0x01, 0xFF], None);
}
#[test]
fn rev_search_for_frame_sync() {
fn test<R: Read + Seek>(reader: &mut R, expected_result: Option<u64>) {
let mut pos = reader.seek(SeekFrom::End(0)).unwrap();
let ret = super::rev_search_for_frame_sync(reader, &mut pos).unwrap();
assert_eq!(ret, expected_result);
}
test(&mut Cursor::new([0xFF, 0xFB, 0x00]), Some(0));
test(&mut Cursor::new([0x00, 0x00, 0x01, 0xFF, 0xFB]), Some(3));
test(&mut Cursor::new([0x01, 0xFF]), None);
let bytes = read_path("tests/files/assets/rev_frame_sync_search.mp3");
let mut reader = Cursor::new(bytes);
test(&mut reader, Some(283));
}
}