use crate::id3::ID3Tags;
use crate::util::BitReader;
use crate::{AudexError, FileType, Result, StreamInfo};
use std::io::{Read, Seek, SeekFrom};
use std::path::Path;
use std::time::Duration;
#[cfg(feature = "async")]
use crate::util::loadfile_read_async;
#[cfg(feature = "async")]
use tokio::fs::File as TokioFile;
#[cfg(feature = "async")]
use tokio::io::{AsyncReadExt, AsyncSeekExt};
const AC3_HEADER_SIZE: u16 = 7;
const AC3_SYNC_WORD: [u8; 2] = [0x0B, 0x77];
const AC3_SAMPLE_RATES: [u32; 3] = [48000, 44100, 32000];
const AC3_BITRATES: [u16; 19] = [
32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 576, 640,
];
const EAC3_BLOCKS: [u16; 4] = [1, 2, 3, 6];
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
enum ChannelMode {
DualMono = 0,
Mono = 1,
Stereo = 2,
C3F = 3,
C2F1R = 4,
C3F1R = 5,
C2F2R = 6,
C3F2R = 7,
}
impl ChannelMode {
fn from_bits(value: u64) -> Result<Self> {
match value {
0 => Ok(ChannelMode::DualMono),
1 => Ok(ChannelMode::Mono),
2 => Ok(ChannelMode::Stereo),
3 => Ok(ChannelMode::C3F),
4 => Ok(ChannelMode::C2F1R),
5 => Ok(ChannelMode::C3F1R),
6 => Ok(ChannelMode::C2F2R),
7 => Ok(ChannelMode::C3F2R),
_ => Err(AudexError::AC3Error(format!(
"invalid channel mode: {}",
value
))),
}
}
fn base_channels(&self) -> u16 {
match self {
ChannelMode::DualMono => 2,
ChannelMode::Mono => 1,
ChannelMode::Stereo => 2,
ChannelMode::C3F => 3,
ChannelMode::C2F1R => 3,
ChannelMode::C3F1R => 4,
ChannelMode::C2F2R => 4,
ChannelMode::C3F2R => 5,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
enum EAC3FrameType {
Independent = 0,
Dependent = 1,
AC3Convert = 2,
}
impl EAC3FrameType {
fn from_bits(value: u64) -> Result<Self> {
match value {
0 => Ok(EAC3FrameType::Independent),
1 => Ok(EAC3FrameType::Dependent),
2 => Ok(EAC3FrameType::AC3Convert),
3 => Err(AudexError::AC3Error(format!(
"invalid frame type: {}",
value
))),
_ => Err(AudexError::AC3Error(format!(
"invalid frame type: {}",
value
))),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct AC3Info {
channels: u16,
length: Option<Duration>,
sample_rate: u32,
bitrate: u32,
codec: String,
}
impl AC3Info {
pub fn sample_rate(&self) -> u32 {
self.sample_rate
}
pub fn channels(&self) -> u16 {
self.channels
}
pub fn bitrate(&self) -> u32 {
self.bitrate
}
pub fn is_eac3(&self) -> bool {
self.codec == "ec-3"
}
pub fn codec(&self) -> &str {
&self.codec
}
pub fn length(&self) -> Option<Duration> {
self.length
}
pub fn from_reader<R: Read + Seek>(reader: &mut R) -> Result<Self> {
let mut header = [0u8; 6];
reader.read_exact(&mut header)?;
if header[0..2] != AC3_SYNC_WORD {
return Err(AudexError::AC3Error("not an AC3 file".to_string()));
}
let bitstream_id = header[5] >> 3;
if bitstream_id > 16 {
return Err(AudexError::AC3Error(format!(
"invalid bitstream_id {}",
bitstream_id
)));
}
reader.seek(SeekFrom::Start(2))?;
let mut info = Self::read_header(reader, bitstream_id)?;
info.length = Some(info.guess_length(reader)?);
Ok(info)
}
pub(crate) fn read_header<R: Read + Seek>(reader: &mut R, bitstream_id: u8) -> Result<Self> {
let mut bit_reader =
BitReader::new(reader).map_err(|e| AudexError::ParseError(e.to_string()))?;
if bitstream_id <= 10 {
Self::read_header_normal(&mut bit_reader, bitstream_id)
} else {
Self::read_header_enhanced(&mut bit_reader)
}
}
fn read_header_normal<R: Read + Seek>(
bit_reader: &mut BitReader<R>,
bitstream_id: u8,
) -> Result<Self> {
bit_reader
.skip(16)
.map_err(|e| AudexError::ParseError(e.to_string()))?;
let sr_code = bit_reader
.read_bits(2)
.map_err(|e| AudexError::ParseError(e.to_string()))? as u8;
if sr_code == 3 {
return Err(AudexError::AC3Error(format!(
"invalid sample rate code {}",
sr_code
)));
}
let frame_size_code = bit_reader
.read_bits(6)
.map_err(|e| AudexError::ParseError(e.to_string()))?
as u8;
if frame_size_code > 37 {
return Err(AudexError::AC3Error(format!(
"invalid frame size code {}",
frame_size_code
)));
}
bit_reader
.skip(5)
.map_err(|e| AudexError::ParseError(e.to_string()))?;
bit_reader
.skip(3)
.map_err(|e| AudexError::ParseError(e.to_string()))?;
let channel_mode_bits = bit_reader
.read_bits(3)
.map_err(|e| AudexError::ParseError(e.to_string()))?;
let channel_mode = ChannelMode::from_bits(channel_mode_bits)?;
bit_reader
.skip(2)
.map_err(|e| AudexError::ParseError(e.to_string()))?;
let lfe_on = bit_reader
.read_bits(1)
.map_err(|e| AudexError::ParseError(e.to_string()))? as u16;
let sr_shift = bitstream_id.max(8) - 8;
let sample_rate = *AC3_SAMPLE_RATES.get(sr_code as usize).ok_or_else(|| {
AudexError::AC3Error(format!("sample rate code {} out of range", sr_code))
})? >> sr_shift;
let bitrate = ((*AC3_BITRATES
.get((frame_size_code >> 1) as usize)
.ok_or_else(|| {
AudexError::AC3Error(format!("frame size code {} out of range", frame_size_code))
})? as u32)
* 1000)
>> sr_shift;
let channels = channel_mode.base_channels() + lfe_on;
Self::skip_unused_header_bits_normal(bit_reader, channel_mode)
.map_err(|e| AudexError::ParseError(e.to_string()))?;
Ok(AC3Info {
channels,
length: None,
sample_rate,
bitrate,
codec: "ac-3".to_string(),
})
}
fn read_header_enhanced<R: Read + Seek>(bit_reader: &mut BitReader<R>) -> Result<Self> {
let frame_type_bits = bit_reader
.read_bits(2)
.map_err(|e| AudexError::ParseError(e.to_string()))?;
let frame_type = EAC3FrameType::from_bits(frame_type_bits)?;
bit_reader
.skip(3)
.map_err(|e| AudexError::ParseError(e.to_string()))?;
let frame_size_raw = (bit_reader
.read_bits(11)
.map_err(|e| AudexError::ParseError(e.to_string()))?
+ 1)
<< 1;
let frame_size = u16::try_from(frame_size_raw).map_err(|_| {
AudexError::AC3Error(format!(
"E-AC-3 frame size {} exceeds u16 range",
frame_size_raw
))
})?;
if frame_size < AC3_HEADER_SIZE {
return Err(AudexError::AC3Error(format!(
"invalid frame size {}",
frame_size
)));
}
let sr_code = bit_reader
.read_bits(2)
.map_err(|e| AudexError::ParseError(e.to_string()))? as u8;
let (numblocks_code, sample_rate) = if sr_code == 3 {
let sr_code2 = bit_reader
.read_bits(2)
.map_err(|e| AudexError::ParseError(e.to_string()))?
as u8;
if sr_code2 == 3 {
return Err(AudexError::AC3Error(format!(
"invalid sample rate code {}",
sr_code2
)));
}
(
3u8,
*AC3_SAMPLE_RATES.get(sr_code2 as usize).ok_or_else(|| {
AudexError::AC3Error(format!("sample rate code {} out of range", sr_code2))
})? / 2,
)
} else {
let numblocks_code = bit_reader
.read_bits(2)
.map_err(|e| AudexError::ParseError(e.to_string()))?
as u8;
(
numblocks_code,
*AC3_SAMPLE_RATES.get(sr_code as usize).ok_or_else(|| {
AudexError::AC3Error(format!("sample rate code {} out of range", sr_code))
})?,
)
};
let channel_mode_bits = bit_reader
.read_bits(3)
.map_err(|e| AudexError::ParseError(e.to_string()))?;
let channel_mode = ChannelMode::from_bits(channel_mode_bits)?;
let lfe_on = bit_reader
.read_bits(1)
.map_err(|e| AudexError::ParseError(e.to_string()))? as u16;
let num_blocks = *EAC3_BLOCKS.get(numblocks_code as usize).ok_or_else(|| {
AudexError::InvalidData(format!("E-AC-3 numblkscod {} out of range", numblocks_code))
})? as u32;
let bitrate = (8 * frame_size as u32 * sample_rate) / (num_blocks * 256);
bit_reader
.skip(5)
.map_err(|e| AudexError::ParseError(e.to_string()))?;
let channels = channel_mode.base_channels() + lfe_on;
Self::skip_unused_header_bits_enhanced(
bit_reader,
frame_type,
channel_mode,
sr_code,
numblocks_code,
)
.map_err(|e| AudexError::ParseError(e.to_string()))?;
Ok(AC3Info {
channels,
length: None,
sample_rate,
bitrate,
codec: "ec-3".to_string(),
})
}
fn skip_unused_header_bits_normal<R: Read + Seek>(
bit_reader: &mut BitReader<R>,
channel_mode: ChannelMode,
) -> std::result::Result<(), crate::util::BitReaderError> {
bit_reader.skip(5)?;
if bit_reader.read_bits(1)? == 1 {
bit_reader.skip(8)?;
}
if bit_reader.read_bits(1)? == 1 {
bit_reader.skip(8)?;
}
if bit_reader.read_bits(1)? == 1 {
bit_reader.skip(7)?; }
if channel_mode == ChannelMode::DualMono {
bit_reader.skip(5)?;
if bit_reader.read_bits(1)? == 1 {
bit_reader.skip(8)?; }
if bit_reader.read_bits(1)? == 1 {
bit_reader.skip(8)?; }
if bit_reader.read_bits(1)? == 1 {
bit_reader.skip(7)?; }
}
bit_reader.skip(2)?;
let timecod1e = bit_reader.read_bits(1)?;
let timecod2e = bit_reader.read_bits(1)?;
if timecod1e == 1 {
bit_reader.skip(14)?;
}
if timecod2e == 1 {
bit_reader.skip(14)?;
}
if bit_reader.read_bits(1)? == 1 {
let addbsil = bit_reader.read_bits(6)?;
bit_reader.skip(((addbsil + 1) * 8) as i32)?;
}
Ok(())
}
fn skip_unused_header_bits_enhanced<R: Read + Seek>(
bit_reader: &mut BitReader<R>,
frame_type: EAC3FrameType,
channel_mode: ChannelMode,
sr_code: u8,
numblocks_code: u8,
) -> std::result::Result<(), crate::util::BitReaderError> {
bit_reader.skip(5)?;
if bit_reader.read_bits(1)? == 1 {
bit_reader.skip(8)?;
}
if channel_mode == ChannelMode::DualMono {
bit_reader.skip(5)?; if bit_reader.read_bits(1)? == 1 {
bit_reader.skip(8)?; }
}
if frame_type == EAC3FrameType::Dependent && bit_reader.read_bits(1)? == 1 {
bit_reader.skip(16)?; }
if bit_reader.read_bits(1)? == 1 {
return Ok(());
}
if bit_reader.read_bits(1)? == 1 {
bit_reader.skip(5)?;
if channel_mode == ChannelMode::Stereo {
bit_reader.skip(4)?;
} else if channel_mode >= ChannelMode::C2F2R {
bit_reader.skip(2)?; }
if bit_reader.read_bits(1)? == 1 {
bit_reader.skip(8)?;
}
if channel_mode == ChannelMode::DualMono && bit_reader.read_bits(1)? == 1 {
bit_reader.skip(8)?; }
if sr_code < 3 {
bit_reader.skip(1)?; }
}
if frame_type == EAC3FrameType::Independent && numblocks_code == 3 {
bit_reader.skip(1)?; }
if frame_type == EAC3FrameType::AC3Convert
&& numblocks_code != 3
&& bit_reader.read_bits(1)? == 1
{
bit_reader.skip(6)?; }
if bit_reader.read_bits(1)? == 1 {
let addbsil = bit_reader.read_bits(6)?;
bit_reader.skip(((addbsil + 1) * 8) as i32)?;
}
Ok(())
}
pub(crate) fn guess_length<R: Read + Seek>(&self, reader: &mut R) -> Result<Duration> {
if self.bitrate == 0 {
return Ok(Duration::from_secs(0));
}
let start = reader.stream_position()?;
let end = reader.seek(SeekFrom::End(0))?;
let data_length = end.saturating_sub(start);
let seconds = (8.0 * data_length as f64) / self.bitrate as f64;
Ok(Duration::from_secs_f64(seconds))
}
}
impl StreamInfo for AC3Info {
fn length(&self) -> Option<Duration> {
self.length
}
fn bitrate(&self) -> Option<u32> {
Some(self.bitrate)
}
fn sample_rate(&self) -> Option<u32> {
Some(self.sample_rate)
}
fn channels(&self) -> Option<u16> {
Some(self.channels)
}
fn bits_per_sample(&self) -> Option<u16> {
None
}
fn pprint(&self) -> String {
format!(
"{}, {} Hz, {:.2} seconds, {} channel(s), {} bps",
self.codec,
self.sample_rate,
self.length.map(|d| d.as_secs_f64()).unwrap_or(0.0),
self.channels,
self.bitrate
)
}
}
impl std::fmt::Display for AC3Info {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.pprint())
}
}
#[derive(Debug)]
pub struct AC3 {
pub info: AC3Info,
pub tags: Option<ID3Tags>,
pub filename: Option<String>,
}
impl AC3 {
pub fn new() -> Self {
AC3 {
info: AC3Info::default(),
tags: None,
filename: None,
}
}
fn parse_file<R: Read + Seek>(&mut self, reader: &mut R) -> Result<()> {
self.info = AC3Info::from_reader(reader)?;
self.tags = None;
Ok(())
}
pub fn load_from_reader<R: Read + Seek>(reader: &mut R) -> Result<Self> {
let mut ac3 = AC3::new();
ac3.parse_file(reader)?;
Ok(ac3)
}
#[cfg(feature = "async")]
pub async fn load_async<P: AsRef<Path>>(path: P) -> Result<Self> {
let mut file = loadfile_read_async(&path).await?;
let mut ac3 = AC3::new();
ac3.filename = Some(path.as_ref().to_string_lossy().to_string());
ac3.info = Self::parse_info_async(&mut file).await?;
Ok(ac3)
}
#[cfg(feature = "async")]
async fn parse_info_async(file: &mut TokioFile) -> Result<AC3Info> {
file.seek(SeekFrom::Start(0)).await?;
let mut header_buf = [0u8; 256];
let bytes_read = file.read(&mut header_buf).await?;
let file_size = file.seek(SeekFrom::End(0)).await?;
let mut cursor = std::io::Cursor::new(&header_buf[..bytes_read]);
let mut header = [0u8; 6];
std::io::Read::read_exact(&mut cursor, &mut header)
.map_err(|_| AudexError::AC3Error("not enough data".to_string()))?;
if header[0..2] != AC3_SYNC_WORD {
return Err(AudexError::AC3Error("not an AC3 file".to_string()));
}
let bitstream_id = header[5] >> 3;
if bitstream_id > 16 {
return Err(AudexError::AC3Error(format!(
"invalid bitstream_id {}",
bitstream_id
)));
}
cursor.set_position(2);
let mut info = AC3Info::read_header(&mut cursor, bitstream_id)?;
let header_end_pos = cursor.position();
if info.bitrate > 0 {
let data_length = file_size.saturating_sub(header_end_pos);
info.length = Some(Duration::from_secs_f64(
(8.0 * data_length as f64) / info.bitrate as f64,
));
} else {
info.length = Some(Duration::from_secs(0));
}
Ok(info)
}
}
impl Default for AC3 {
fn default() -> Self {
Self::new()
}
}
impl FileType for AC3 {
type Tags = ID3Tags;
type Info = AC3Info;
fn format_id() -> &'static str {
"AC3"
}
fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
debug_event!("parsing AC-3 stream info");
let mut file = std::fs::File::open(&path)?;
let mut ac3 = AC3::new();
ac3.filename = Some(path.as_ref().to_string_lossy().to_string());
ac3.parse_file(&mut file)?;
Ok(ac3)
}
fn load_from_reader(reader: &mut dyn crate::ReadSeek) -> Result<Self> {
debug_event!("parsing AC-3 stream info from reader");
let mut instance = Self::new();
let mut reader = reader;
instance.parse_file(&mut reader)?;
Ok(instance)
}
fn save(&mut self) -> Result<()> {
Err(AudexError::TagOperationUnsupported(
"AC-3 doesn't support embedded tags".to_string(),
))
}
fn clear(&mut self) -> Result<()> {
Err(AudexError::TagOperationUnsupported(
"AC-3 doesn't support embedded tags".to_string(),
))
}
fn add_tags(&mut self) -> Result<()> {
Err(AudexError::TagOperationUnsupported(
"AC-3 doesn't support embedded tags".to_string(),
))
}
fn tags(&self) -> Option<&Self::Tags> {
self.tags.as_ref()
}
fn tags_mut(&mut self) -> Option<&mut Self::Tags> {
self.tags.as_mut()
}
fn info(&self) -> &Self::Info {
&self.info
}
fn score(filename: &str, header: &[u8]) -> i32 {
let mut score = 0i32;
if header.starts_with(&AC3_SYNC_WORD) {
score += 2;
}
let filename_lower = filename.to_lowercase();
if filename_lower.ends_with(".ac3") || filename_lower.ends_with(".eac3") {
score += 1;
}
score
}
fn mime_types() -> &'static [&'static str] {
&[
"audio/ac3",
"audio/x-ac3",
"audio/eac3",
"audio/vnd.dolby.dd-raw",
]
}
}
pub fn clear<P: AsRef<Path>>(_path: P) -> Result<()> {
Err(AudexError::TagOperationUnsupported(
"AC-3 doesn't support embedded tags".to_string(),
))
}