use cpal::{Error, ErrorKind, SampleFormat, I24, U24};
use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom};
use std::path::Path;
use crate::output::SampleType;
pub mod audio;
const HEADER_LEN: usize = 32;
const MPEG_SYNC_SEARCH: usize = 8 * 1024;
const ASF_HEADER_GUID: [u8; 16] = [
0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C,
];
const ASF_STREAM_PROPERTIES_GUID: [u8; 16] = [
0x91, 0x07, 0xDC, 0xB7, 0xB7, 0xA9, 0xCF, 0x11, 0x8E, 0xE6, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65,
];
const ASF_AUDIO_MEDIA_GUID: [u8; 16] = [
0x40, 0x9E, 0x69, 0xF8, 0x4D, 0x5B, 0xCF, 0x11, 0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B,
];
const ASF_HEADER_LEN: usize = 30;
const ASF_OBJECT_HEADER_LEN: usize = 24;
const ASF_FORMAT_TAG_OFFSET: usize = 78;
const WAVE_FORMAT_EXTENSIBLE: u16 = 0xFFFE;
const OGG_PAGE_HEADER_LEN: usize = 27;
const OGG_PACKET_PEEK: usize = 8;
const OGG_PAGE_SCAN: usize = 16;
const MATROSKA_WINDOW: usize = 256 * 1024;
const EBML_SEGMENT: u32 = 0x1853_8067;
const EBML_TRACKS: u32 = 0x1654_AE6B;
const EBML_TRACK_ENTRY: u32 = 0xAE;
const EBML_TRACK_TYPE: u32 = 0x83;
const EBML_CODEC_ID: u32 = 0x86;
const MATROSKA_TRACK_AUDIO: u8 = 2;
const MP4_CONTAINER_BOXES: [&[u8; 4]; 5] = [b"moov", b"trak", b"mdia", b"minf", b"stbl"];
const MP4_MAX_DEPTH: u32 = 8;
const MAX_CHUNKS: usize = 4096;
const MAX_CHUNK_READ: usize = 4 * 1024;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Container {
Wav,
Aiff,
Caf,
Ogg,
Flac,
Mp4,
Matroska,
Asf,
Adts,
Mpeg,
Ac3,
Dts,
Amr,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Encoding {
Pcm,
Mp3,
AacLc,
AacHe,
AacHeV2,
Opus,
Vorbis,
Flac,
Alac,
Ac3,
EAc3,
Dts,
DtsHdMa,
TrueHd,
Wma,
AmrNb,
AmrWb,
}
#[derive(Clone, Debug, PartialEq)]
pub enum Samples {
U8(Vec<u8>),
I8(Vec<i8>),
U16(Vec<u16>),
I16(Vec<i16>),
U24(Vec<U24>),
I24(Vec<I24>),
U32(Vec<u32>),
I32(Vec<i32>),
F32(Vec<f32>),
F64(Vec<f64>),
}
macro_rules! with_samples {
($samples:expr, |$vec:ident| $body:expr) => {
match $samples {
Samples::U8($vec) => $body,
Samples::I8($vec) => $body,
Samples::U16($vec) => $body,
Samples::I16($vec) => $body,
Samples::U24($vec) => $body,
Samples::I24($vec) => $body,
Samples::U32($vec) => $body,
Samples::I32($vec) => $body,
Samples::F32($vec) => $body,
Samples::F64($vec) => $body,
}
};
}
impl Samples {
pub fn format(&self) -> SampleFormat {
match self {
Samples::U8(_) => SampleFormat::U8,
Samples::I8(_) => SampleFormat::I8,
Samples::U16(_) => SampleFormat::U16,
Samples::I16(_) => SampleFormat::I16,
Samples::U24(_) => SampleFormat::U24,
Samples::I24(_) => SampleFormat::I24,
Samples::U32(_) => SampleFormat::U32,
Samples::I32(_) => SampleFormat::I32,
Samples::F32(_) => SampleFormat::F32,
Samples::F64(_) => SampleFormat::F64,
}
}
pub fn len(&self) -> usize {
with_samples!(self, |vec| vec.len())
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn to_vec<S: SampleType>(&self) -> Vec<S> {
with_samples!(self, |vec| vec
.iter()
.map(|sample| S::from_f32(SampleType::to_f32(*sample)))
.collect())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Decoded {
pub samples: Samples,
pub sample_rate: u32,
pub channels: u16,
}
impl Decoded {
pub fn sample_format(&self) -> SampleFormat {
self.samples.format()
}
pub fn frames(&self) -> usize {
self.samples.len() / self.channels.max(1) as usize
}
pub fn duration(&self) -> f64 {
self.frames() as f64 / self.sample_rate.max(1) as f64
}
}
pub trait AudioStream<S: SampleType>: Send {
fn sample_rate(&self) -> u32;
fn channels(&self) -> u16;
fn sample_format(&self) -> SampleFormat {
S::format()
}
fn source_format(&self) -> SampleFormat;
fn total_frames(&self) -> Option<u64> {
None
}
fn read(&mut self, out: &mut [S]) -> Result<usize, Error>;
}
const DRAIN_BLOCK: usize = 8192;
const MAX_DRAIN_RESERVE: usize = 1 << 24;
pub fn drain<S: SampleType>(stream: &mut dyn AudioStream<S>) -> Result<Vec<S>, Error> {
let channels = stream.channels();
let mut samples = Vec::new();
if let Some(frames) = stream.total_frames() {
let estimate = (frames as usize).saturating_mul(channels.max(1) as usize);
samples.reserve(estimate.min(MAX_DRAIN_RESERVE));
}
let mut block = vec![S::SILENCE; DRAIN_BLOCK];
loop {
let read = stream.read(&mut block)?;
if read == 0 {
break;
}
samples.extend_from_slice(&block[..read]);
}
Ok(samples)
}
pub fn find_type(path: &Path) -> Result<Encoding, Error> {
let mut file = File::open(path).map_err(|error| io_error(path, error))?;
let container = read_container(&mut file, path)?;
encoding_in(container, &mut file, path)
}
pub fn find_container(path: &Path) -> Result<Container, Error> {
let mut file = File::open(path).map_err(|error| io_error(path, error))?;
read_container(&mut file, path)
}
pub fn decode(path: &Path) -> Result<Decoded, Error> {
let encoding = find_type(path)?;
decode_as(path, encoding)
}
pub fn stream<S: SampleType>(path: &Path) -> Result<Box<dyn AudioStream<S>>, Error> {
let encoding = find_type(path)?;
stream_as(path, encoding)
}
pub fn stream_as<S: SampleType>(
path: &Path,
encoding: Encoding,
) -> Result<Box<dyn AudioStream<S>>, Error> {
match encoding {
Encoding::Pcm => audio::read_pcm(path),
Encoding::Mp3 => audio::read_mp3(path),
Encoding::AacLc | Encoding::AacHe | Encoding::AacHeV2 => audio::read_aac(path, encoding),
Encoding::Opus => audio::read_opus(path),
Encoding::Vorbis => audio::read_vorbis(path),
Encoding::Flac => audio::read_flac(path),
Encoding::Alac => audio::read_alac(path),
Encoding::Ac3 => audio::read_ac3(path),
Encoding::EAc3 => audio::read_eac3(path),
Encoding::Dts => audio::read_dts(path),
Encoding::DtsHdMa => audio::read_dts_hd_ma(path),
Encoding::TrueHd => audio::read_truehd(path),
Encoding::Wma => audio::read_wma(path),
Encoding::AmrNb => audio::read_amr_nb(path),
Encoding::AmrWb => audio::read_amr_wb(path),
}
}
pub fn decode_as(path: &Path, encoding: Encoding) -> Result<Decoded, Error> {
match encoding {
Encoding::Pcm => audio::decode_pcm(path),
Encoding::Mp3 => audio::decode_mp3(path),
Encoding::AacLc | Encoding::AacHe | Encoding::AacHeV2 => audio::decode_aac(path, encoding),
Encoding::Opus => audio::decode_opus(path),
Encoding::Vorbis => audio::decode_vorbis(path),
Encoding::Flac => audio::decode_flac(path),
Encoding::Alac => audio::decode_alac(path),
Encoding::Ac3 => audio::decode_ac3(path),
Encoding::EAc3 => audio::decode_eac3(path),
Encoding::Dts => audio::decode_dts(path),
Encoding::DtsHdMa => audio::decode_dts_hd_ma(path),
Encoding::TrueHd => audio::decode_truehd(path),
Encoding::Wma => audio::decode_wma(path),
Encoding::AmrNb => audio::decode_amr_nb(path),
Encoding::AmrWb => audio::decode_amr_wb(path),
}
}
fn read_container(file: &mut File, path: &Path) -> Result<Container, Error> {
let mut buffer = [0u8; HEADER_LEN];
let read = read_up_to(file, &mut buffer).map_err(|error| io_error(path, error))?;
let header = &buffer[..read];
if let Some(container) = sniff_container(header) {
return Ok(container);
}
if is_mpeg_audio(file, header).map_err(|error| io_error(path, error))? {
return Ok(Container::Mpeg);
}
Err(Error::with_message(
ErrorKind::InvalidInput,
format!("unrecognised audio format: {}", path.display()),
))
}
fn sniff_container(header: &[u8]) -> Option<Container> {
if tag_at(header, 0, b"RIFF") && tag_at(header, 8, b"WAVE") {
return Some(Container::Wav);
}
if tag_at(header, 0, b"FORM") && (tag_at(header, 8, b"AIFF") || tag_at(header, 8, b"AIFC")) {
return Some(Container::Aiff);
}
if tag_at(header, 0, b"caff") {
return Some(Container::Caf);
}
if tag_at(header, 0, b"fLaC") {
return Some(Container::Flac);
}
if tag_at(header, 0, b"OggS") {
return Some(Container::Ogg);
}
if tag_at(header, 4, b"ftyp") {
return Some(Container::Mp4);
}
if tag_at(header, 0, &[0x1A, 0x45, 0xDF, 0xA3]) {
return Some(Container::Matroska);
}
if tag_at(header, 0, &ASF_HEADER_GUID) {
return Some(Container::Asf);
}
if tag_at(header, 0, b"#!AMR") {
return Some(Container::Amr);
}
if tag_at(header, 0, &[0x0B, 0x77]) {
return Some(Container::Ac3);
}
if is_dts_sync(header) {
return Some(Container::Dts);
}
if is_adts_header(header) {
return Some(Container::Adts);
}
None
}
fn is_mpeg_audio(file: &mut File, header: &[u8]) -> io::Result<bool> {
if tag_at(header, 0, b"ID3") && header.len() >= 10 {
file.seek(SeekFrom::Start(id3_len(header) as u64))?;
let mut window = vec![0u8; MPEG_SYNC_SEARCH];
let read = read_up_to(file, &mut window)?;
return Ok(window[..read].windows(4).any(is_frame_header));
}
Ok(is_frame_header(header))
}
fn encoding_in(container: Container, file: &mut File, path: &Path) -> Result<Encoding, Error> {
match container {
Container::Flac => Ok(Encoding::Flac),
Container::Mpeg => Ok(Encoding::Mp3),
Container::Amr => encoding_in_amr(file, path),
Container::Ac3 => encoding_in_ac3(file, path),
Container::Dts => encoding_in_dts(file, path),
Container::Adts => encoding_in_adts(file, path),
Container::Wav => encoding_in_wav(file, path),
Container::Aiff => encoding_in_aiff(file, path),
Container::Caf => encoding_in_caf(file, path),
Container::Ogg => encoding_in_ogg(file, path),
Container::Mp4 => encoding_in_mp4(file, path),
Container::Matroska => encoding_in_matroska(file, path),
Container::Asf => encoding_in_asf(file, path),
}
}
fn encoding_in_amr(file: &mut File, path: &Path) -> Result<Encoding, Error> {
let header = header_from_start(file, path, 9)?;
if tag_at(&header, 0, b"#!AMR-WB\n") {
Ok(Encoding::AmrWb)
} else if tag_at(&header, 0, b"#!AMR\n") {
Ok(Encoding::AmrNb)
} else {
Err(Error::with_message(
ErrorKind::InvalidInput,
format!("unrecognised AMR variant: {}", path.display()),
))
}
}
fn encoding_in_ac3(file: &mut File, path: &Path) -> Result<Encoding, Error> {
let header = header_from_start(file, path, 6)?;
if header.len() < 6 {
return Err(Error::with_message(
ErrorKind::InvalidInput,
format!("truncated AC-3 sync frame: {}", path.display()),
));
}
match header[5] >> 3 {
16 => Ok(Encoding::EAc3),
_ => Ok(Encoding::Ac3),
}
}
fn encoding_in_dts(file: &mut File, path: &Path) -> Result<Encoding, Error> {
let _ = (file, path);
Ok(Encoding::Dts)
}
fn encoding_in_adts(file: &mut File, path: &Path) -> Result<Encoding, Error> {
let header = header_from_start(file, path, 3)?;
if header.len() < 3 {
return Err(Error::with_message(
ErrorKind::InvalidInput,
format!("truncated ADTS frame: {}", path.display()),
));
}
match header[2] >> 6 {
0b01 => Ok(Encoding::AacLc),
profile => Err(Error::with_message(
ErrorKind::UnsupportedOperation,
format!("unsupported AAC profile {profile} in {}", path.display()),
)),
}
}
fn encoding_in_wav(file: &mut File, path: &Path) -> Result<Encoding, Error> {
let fmt = riff_chunk(file, path, b"fmt ", u32_le_at)?
.ok_or_else(|| malformed(path, "WAV with no `fmt ` chunk"))?;
let mut tag = u16_le_at(&fmt, 0).ok_or_else(|| malformed(path, "WAV `fmt ` chunk"))?;
if tag == WAVE_FORMAT_EXTENSIBLE {
tag = u16_le_at(&fmt, 24)
.ok_or_else(|| malformed(path, "WAV extensible `fmt ` chunk"))?;
}
wave_format_tag(tag).ok_or_else(|| unsupported(path, format!("WAVE format tag {tag:#06x}")))
}
fn encoding_in_aiff(file: &mut File, path: &Path) -> Result<Encoding, Error> {
let header = header_from_start(file, path, 12)?;
let compressed = tag_at(&header, 8, b"AIFC");
if !compressed {
return Ok(Encoding::Pcm);
}
let comm = riff_chunk(file, path, b"COMM", u32_be_at)?
.ok_or_else(|| malformed(path, "AIFF with no `COMM` chunk"))?;
let compression = fourcc_at(&comm, 18).ok_or_else(|| malformed(path, "AIFF-C `COMM` chunk"))?;
aiff_compression(&compression)
.ok_or_else(|| unsupported(path, format!("AIFF-C compression {}", fourcc_name(&compression))))
}
fn encoding_in_caf(file: &mut File, path: &Path) -> Result<Encoding, Error> {
let desc = caf_chunk(file, path, b"desc")?
.ok_or_else(|| malformed(path, "CAF with no `desc` chunk"))?;
let format = fourcc_at(&desc, 8).ok_or_else(|| malformed(path, "CAF `desc` chunk"))?;
caf_format(&format)
.ok_or_else(|| unsupported(path, format!("CAF format {}", fourcc_name(&format))))
}
fn encoding_in_ogg(file: &mut File, path: &Path) -> Result<Encoding, Error> {
let mut offset = 0u64;
for _ in 0..OGG_PAGE_SCAN {
let header = read_at(file, path, offset, OGG_PAGE_HEADER_LEN)?;
if header.len() < OGG_PAGE_HEADER_LEN || !tag_at(&header, 0, b"OggS") {
break;
}
let segments = header[OGG_PAGE_HEADER_LEN - 1] as usize;
let table = read_at(file, path, offset + OGG_PAGE_HEADER_LEN as u64, segments)?;
if table.len() < segments {
break;
}
let packet_at = offset + OGG_PAGE_HEADER_LEN as u64 + segments as u64;
let packet = read_at(file, path, packet_at, OGG_PACKET_PEEK)?;
if let Some(encoding) = ogg_codec(&packet) {
return Ok(encoding);
}
let body: usize = table.iter().map(|&length| length as usize).sum();
offset = packet_at + body as u64;
}
Err(unsupported(path, "Ogg codec".to_string()))
}
fn encoding_in_mp4(file: &mut File, path: &Path) -> Result<Encoding, Error> {
let end = file
.metadata()
.map_err(|error| io_error(path, error))?
.len();
mp4_encoding(file, path, 0, end, 0)?
.ok_or_else(|| unsupported(path, "MP4 audio track".to_string()))
}
fn encoding_in_matroska(file: &mut File, path: &Path) -> Result<Encoding, Error> {
let data = read_at(file, path, 0, MATROSKA_WINDOW)?;
matroska_encoding(&data).ok_or_else(|| unsupported(path, "Matroska audio track".to_string()))
}
fn encoding_in_asf(file: &mut File, path: &Path) -> Result<Encoding, Error> {
let mut offset = ASF_HEADER_LEN as u64;
for _ in 0..MAX_CHUNKS {
let header = read_at(file, path, offset, ASF_OBJECT_HEADER_LEN)?;
if header.len() < ASF_OBJECT_HEADER_LEN {
break;
}
let size = u64_le_at(&header, 16).ok_or_else(|| malformed(path, "ASF object header"))?;
if size < ASF_OBJECT_HEADER_LEN as u64 {
break;
}
if header[..16] == ASF_STREAM_PROPERTIES_GUID {
let object = read_at(file, path, offset, size.min(MAX_CHUNK_READ as u64) as usize)?;
if object.len() >= ASF_FORMAT_TAG_OFFSET + 2
&& object[24..40] == ASF_AUDIO_MEDIA_GUID
{
let tag = u16_le_at(&object, ASF_FORMAT_TAG_OFFSET)
.ok_or_else(|| malformed(path, "ASF stream properties"))?;
return wave_format_tag(tag)
.ok_or_else(|| unsupported(path, format!("WAVE format tag {tag:#06x}")));
}
}
offset += size;
}
Err(unsupported(path, "ASF audio stream".to_string()))
}
fn riff_chunk(
file: &mut File,
path: &Path,
id: &[u8; 4],
size_at: fn(&[u8], usize) -> Option<u32>,
) -> Result<Option<Vec<u8>>, Error> {
let mut offset = 12u64;
for _ in 0..MAX_CHUNKS {
let header = read_at(file, path, offset, 8)?;
if header.len() < 8 {
return Ok(None);
}
let size = size_at(&header, 4).ok_or_else(|| malformed(path, "RIFF chunk header"))? as u64;
if &header[..4] == id {
let want = size.min(MAX_CHUNK_READ as u64) as usize;
return Ok(Some(read_at(file, path, offset + 8, want)?));
}
offset += 8 + size + (size & 1);
}
Ok(None)
}
fn caf_chunk(file: &mut File, path: &Path, id: &[u8; 4]) -> Result<Option<Vec<u8>>, Error> {
let mut offset = 8u64;
for _ in 0..MAX_CHUNKS {
let header = read_at(file, path, offset, 12)?;
if header.len() < 12 {
return Ok(None);
}
let size = i64_be_at(&header, 4).ok_or_else(|| malformed(path, "CAF chunk header"))?;
if &header[..4] == id {
let want = if size < 0 {
MAX_CHUNK_READ
} else {
(size as u64).min(MAX_CHUNK_READ as u64) as usize
};
return Ok(Some(read_at(file, path, offset + 12, want)?));
}
if size < 0 {
return Ok(None);
}
offset += 12 + size as u64;
}
Ok(None)
}
fn mp4_encoding(
file: &mut File,
path: &Path,
start: u64,
end: u64,
depth: u32,
) -> Result<Option<Encoding>, Error> {
if depth > MP4_MAX_DEPTH {
return Ok(None);
}
let mut offset = start;
for _ in 0..MAX_CHUNKS {
if offset + 8 > end {
break;
}
let header = read_at(file, path, offset, 16)?;
if header.len() < 8 {
break;
}
let declared =
u32_be_at(&header, 0).ok_or_else(|| malformed(path, "MP4 box header"))? as u64;
let kind = fourcc_at(&header, 4).ok_or_else(|| malformed(path, "MP4 box header"))?;
let (size, body) = match declared {
1 => (
u64_be_at(&header, 8).ok_or_else(|| malformed(path, "MP4 large box header"))?,
offset + 16,
),
0 => (end - offset, offset + 8),
_ => (declared, offset + 8),
};
if size < body - offset {
break;
}
let body_end = (offset + size).min(end);
if &kind == b"stsd" {
if let Some(encoding) = stsd_encoding(file, path, body, body_end)? {
return Ok(Some(encoding));
}
} else if MP4_CONTAINER_BOXES.iter().any(|container| *container == &kind) {
if let Some(encoding) = mp4_encoding(file, path, body, body_end, depth + 1)? {
return Ok(Some(encoding));
}
}
offset += size;
}
Ok(None)
}
fn stsd_encoding(
file: &mut File,
path: &Path,
start: u64,
end: u64,
) -> Result<Option<Encoding>, Error> {
let mut offset = start + 8;
for _ in 0..MAX_CHUNKS {
if offset + 8 > end {
break;
}
let header = read_at(file, path, offset, 8)?;
if header.len() < 8 {
break;
}
let size = u32_be_at(&header, 0).ok_or_else(|| malformed(path, "MP4 sample entry"))? as u64;
let format = fourcc_at(&header, 4).ok_or_else(|| malformed(path, "MP4 sample entry"))?;
if size < 8 {
break;
}
if &format == b"mp4a" {
let length = size.min(MAX_CHUNK_READ as u64) as usize;
let entry = read_at(file, path, offset, length)?;
return Ok(Some(esds_encoding(&entry).unwrap_or(Encoding::AacLc)));
}
if let Some(encoding) = mp4_sample_format(&format) {
return Ok(Some(encoding));
}
offset += size;
}
Ok(None)
}
fn esds_encoding(entry: &[u8]) -> Option<Encoding> {
let start = find_bytes(entry, b"esds")? + 8;
let descriptors = entry.get(start..)?;
let stream = descriptor(descriptors, 0x03)?;
let flags = *stream.get(2)?;
let mut offset = 3;
if flags & 0x80 != 0 {
offset += 2; }
if flags & 0x40 != 0 {
offset += 1 + *stream.get(offset)? as usize; }
if flags & 0x20 != 0 {
offset += 2; }
let config = descriptor(stream.get(offset..)?, 0x04)?;
match *config.first()? {
0x40 => {
let specific = descriptor(config.get(13..)?, 0x05)?;
audio_object_type(specific)
}
0x66 | 0x67 | 0x68 => Some(Encoding::AacLc), 0x69 | 0x6B => Some(Encoding::Mp3), 0xA5 => Some(Encoding::Ac3),
0xA6 => Some(Encoding::EAc3),
0xA9 => Some(Encoding::Dts),
0xDD => Some(Encoding::Vorbis),
_ => None,
}
}
fn descriptor(data: &[u8], tag: u8) -> Option<&[u8]> {
if *data.first()? != tag {
return None;
}
let mut length = 0usize;
let mut offset = 1;
for _ in 0..4 {
let byte = *data.get(offset)?;
offset += 1;
length = (length << 7) | (byte & 0x7F) as usize;
if byte & 0x80 == 0 {
break;
}
}
data.get(offset..(offset + length).min(data.len()))
}
fn audio_object_type(config: &[u8]) -> Option<Encoding> {
let first = *config.first()?;
let mut object_type = (first >> 3) as u16;
if object_type == 31 {
let second = *config.get(1)?;
object_type = 32 + (((first & 0x07) as u16) << 3 | (second >> 5) as u16);
}
match object_type {
2 => Some(Encoding::AacLc),
5 => Some(Encoding::AacHe),
29 => Some(Encoding::AacHeV2),
_ => None,
}
}
fn matroska_encoding(data: &[u8]) -> Option<Encoding> {
let segment = ebml_child(data, EBML_SEGMENT)?;
let tracks = ebml_child(segment, EBML_TRACKS)?;
let mut offset = 0;
while offset < tracks.len() {
let (id, payload, next) = ebml_element(tracks, offset)?;
if id == EBML_TRACK_ENTRY {
if let Some(encoding) = track_entry_encoding(payload) {
return Some(encoding);
}
}
if next <= offset {
break;
}
offset = next;
}
None
}
fn track_entry_encoding(entry: &[u8]) -> Option<Encoding> {
if *ebml_child(entry, EBML_TRACK_TYPE)?.first()? != MATROSKA_TRACK_AUDIO {
return None;
}
matroska_codec(ebml_child(entry, EBML_CODEC_ID)?)
}
fn ebml_child(data: &[u8], id: u32) -> Option<&[u8]> {
let mut offset = 0;
while offset < data.len() {
let (found, payload, next) = ebml_element(data, offset)?;
if found == id {
return Some(payload);
}
if next <= offset {
return None;
}
offset = next;
}
None
}
fn ebml_element(data: &[u8], offset: usize) -> Option<(u32, &[u8], usize)> {
let (id, after_id) = ebml_id(data, offset)?;
let (size, after_size) = ebml_size(data, after_id)?;
let end = match size {
Some(size) => after_size.checked_add(size as usize)?.min(data.len()),
None => data.len(),
};
Some((id, data.get(after_size..end)?, end))
}
fn ebml_id(data: &[u8], offset: usize) -> Option<(u32, usize)> {
let length = ebml_marker_len(*data.get(offset)?)?;
if length > 4 {
return None;
}
let mut id = 0u32;
for index in 0..length {
id = (id << 8) | *data.get(offset + index)? as u32;
}
Some((id, offset + length))
}
fn ebml_size(data: &[u8], offset: usize) -> Option<(Option<u64>, usize)> {
let first = *data.get(offset)?;
let length = ebml_marker_len(first)?;
if length > 8 {
return None;
}
let mut value = first as u64 & (0xFF >> length);
let mut unknown = value == (0xFFu64 >> length);
for index in 1..length {
let byte = *data.get(offset + index)?;
value = (value << 8) | byte as u64;
unknown &= byte == 0xFF;
}
Some((if unknown { None } else { Some(value) }, offset + length))
}
fn ebml_marker_len(byte: u8) -> Option<usize> {
(byte != 0).then(|| byte.leading_zeros() as usize + 1)
}
fn wave_format_tag(tag: u16) -> Option<Encoding> {
Some(match tag {
0x0001 | 0x0003 | 0x0006 | 0x0007 => Encoding::Pcm,
0x0055 => Encoding::Mp3,
0x00FF | 0x1600 | 0x1601 => Encoding::AacLc,
0x000A | 0x0160 | 0x0161 | 0x0162 | 0x0163 => Encoding::Wma,
0x0092 | 0x2000 => Encoding::Ac3,
0x2001 => Encoding::Dts,
0xF1AC => Encoding::Flac,
_ => return None,
})
}
fn aiff_compression(compression: &[u8; 4]) -> Option<Encoding> {
Some(match compression {
b"NONE" | b"sowt" | b"twos" | b"raw " | b"in24" | b"in32" | b"fl32" | b"FL32"
| b"fl64" | b"FL64" => Encoding::Pcm,
b"ulaw" | b"ULAW" | b"alaw" | b"ALAW" => Encoding::Pcm,
b"alac" => Encoding::Alac,
b".mp3" => Encoding::Mp3,
b"aac " => Encoding::AacLc,
_ => return None,
})
}
fn caf_format(format: &[u8; 4]) -> Option<Encoding> {
Some(match format {
b"lpcm" => Encoding::Pcm,
b"ulaw" | b"alaw" => Encoding::Pcm,
b"alac" => Encoding::Alac,
b"aac " => Encoding::AacLc,
b".mp3" => Encoding::Mp3,
b"flac" => Encoding::Flac,
b"opus" => Encoding::Opus,
_ => return None,
})
}
fn mp4_sample_format(format: &[u8; 4]) -> Option<Encoding> {
Some(match format {
b"alac" => Encoding::Alac,
b"ac-3" => Encoding::Ac3,
b"ec-3" => Encoding::EAc3,
b"dtsc" | b"dtse" | b"dtsh" => Encoding::Dts,
b"dtsl" => Encoding::DtsHdMa,
b"Opus" => Encoding::Opus,
b"fLaC" => Encoding::Flac,
b"mlpa" => Encoding::TrueHd,
b"samr" => Encoding::AmrNb,
b"sawb" => Encoding::AmrWb,
b".mp3" | b"mp3 " => Encoding::Mp3,
b"sowt" | b"twos" | b"lpcm" | b"raw " | b"in24" | b"in32" | b"fl32" | b"fl64"
| b"NONE" => Encoding::Pcm,
b"ulaw" | b"alaw" => Encoding::Pcm,
_ => return None,
})
}
fn matroska_codec(codec: &[u8]) -> Option<Encoding> {
let codec = std::str::from_utf8(codec).ok()?.trim_end_matches('\0');
Some(match codec {
"A_OPUS" => Encoding::Opus,
"A_VORBIS" => Encoding::Vorbis,
"A_FLAC" => Encoding::Flac,
"A_ALAC" => Encoding::Alac,
"A_TRUEHD" => Encoding::TrueHd,
"A_MPEG/L3" => Encoding::Mp3,
"A_EAC3" => Encoding::EAc3,
"A_DTS/LOSSLESS" => Encoding::DtsHdMa,
_ if codec.starts_with("A_AAC") => {
if codec.ends_with("/SBR") {
Encoding::AacHe
} else {
Encoding::AacLc
}
}
_ if codec.starts_with("A_PCM") => Encoding::Pcm,
_ if codec.starts_with("A_AC3") => Encoding::Ac3,
_ if codec.starts_with("A_DTS") => Encoding::Dts,
_ => return None,
})
}
fn ogg_codec(packet: &[u8]) -> Option<Encoding> {
if tag_at(packet, 0, b"OpusHead") {
return Some(Encoding::Opus);
}
if tag_at(packet, 0, b"\x01vorbis") {
return Some(Encoding::Vorbis);
}
if tag_at(packet, 0, b"\x7FFLAC") {
return Some(Encoding::Flac);
}
None
}
fn is_frame_header(bytes: &[u8]) -> bool {
if bytes.len() < 4 {
return false;
}
if bytes[0] != 0xFF || bytes[1] & 0xE0 != 0xE0 {
return false;
}
let version = (bytes[1] >> 3) & 0b11;
let layer = (bytes[1] >> 1) & 0b11;
let bitrate = bytes[2] >> 4;
let sample_rate = (bytes[2] >> 2) & 0b11;
version != 0b01 && layer != 0b00 && bitrate != 0b1111 && sample_rate != 0b11
}
fn is_adts_header(bytes: &[u8]) -> bool {
bytes.len() >= 4 && bytes[0] == 0xFF && bytes[1] & 0xF6 == 0xF0
}
fn is_dts_sync(bytes: &[u8]) -> bool {
const SYNC_WORDS: [[u8; 4]; 5] = [
[0x7F, 0xFE, 0x80, 0x01], [0xFE, 0x7F, 0x01, 0x80], [0x1F, 0xFF, 0xE8, 0x00], [0xFF, 0x1F, 0x00, 0xE8], [0x64, 0x58, 0x20, 0x25], ];
SYNC_WORDS.iter().any(|sync| tag_at(bytes, 0, sync))
}
fn id3_len(header: &[u8]) -> usize {
let size = header[6..10]
.iter()
.fold(0usize, |total, byte| (total << 7) | (byte & 0x7F) as usize);
let footer = if header[5] & 0x10 != 0 { 10 } else { 0 };
10 + size + footer
}
fn tag_at(header: &[u8], offset: usize, tag: &[u8]) -> bool {
header.len() >= offset + tag.len() && &header[offset..offset + tag.len()] == tag
}
fn header_from_start(file: &mut File, path: &Path, len: usize) -> Result<Vec<u8>, Error> {
read_at(file, path, 0, len)
}
fn read_at(file: &mut File, path: &Path, offset: u64, len: usize) -> Result<Vec<u8>, Error> {
file.seek(SeekFrom::Start(offset))
.map_err(|error| io_error(path, error))?;
let mut buffer = vec![0u8; len];
let read = read_up_to(file, &mut buffer).map_err(|error| io_error(path, error))?;
buffer.truncate(read);
Ok(buffer)
}
fn find_bytes(haystack: &[u8], needle: &[u8]) -> Option<usize> {
haystack
.windows(needle.len())
.position(|window| window == needle)
}
fn fourcc_at(data: &[u8], offset: usize) -> Option<[u8; 4]> {
data.get(offset..offset + 4)?.try_into().ok()
}
fn fourcc_name(code: &[u8; 4]) -> String {
String::from_utf8_lossy(code).into_owned()
}
fn u16_le_at(data: &[u8], offset: usize) -> Option<u16> {
Some(u16::from_le_bytes(
data.get(offset..offset + 2)?.try_into().ok()?,
))
}
fn u32_le_at(data: &[u8], offset: usize) -> Option<u32> {
Some(u32::from_le_bytes(
data.get(offset..offset + 4)?.try_into().ok()?,
))
}
fn u32_be_at(data: &[u8], offset: usize) -> Option<u32> {
Some(u32::from_be_bytes(
data.get(offset..offset + 4)?.try_into().ok()?,
))
}
fn u64_le_at(data: &[u8], offset: usize) -> Option<u64> {
Some(u64::from_le_bytes(
data.get(offset..offset + 8)?.try_into().ok()?,
))
}
fn u64_be_at(data: &[u8], offset: usize) -> Option<u64> {
Some(u64::from_be_bytes(
data.get(offset..offset + 8)?.try_into().ok()?,
))
}
fn i64_be_at(data: &[u8], offset: usize) -> Option<i64> {
Some(i64::from_be_bytes(
data.get(offset..offset + 8)?.try_into().ok()?,
))
}
fn read_up_to(file: &mut File, buffer: &mut [u8]) -> io::Result<usize> {
let mut filled = 0;
while filled < buffer.len() {
match file.read(&mut buffer[filled..]) {
Ok(0) => break,
Ok(read) => filled += read,
Err(error) if error.kind() == io::ErrorKind::Interrupted => continue,
Err(error) => return Err(error),
}
}
Ok(filled)
}
fn feature_required(what: &str, feature: &str) -> Error {
Error::with_message(
ErrorKind::UnsupportedOperation,
format!("{what} requires the `{feature}` feature"),
)
}
fn no_decoder(what: &str) -> Error {
Error::with_message(
ErrorKind::UnsupportedOperation,
format!("{what} has no Rust decoder available; the format is identified but cannot be decoded"),
)
}
fn malformed(path: &Path, what: &str) -> Error {
Error::with_message(
ErrorKind::InvalidInput,
format!("malformed {what}: {}", path.display()),
)
}
fn unsupported(path: &Path, what: String) -> Error {
Error::with_message(
ErrorKind::UnsupportedOperation,
format!("unsupported {what}: {}", path.display()),
)
}
fn io_error(path: &Path, error: io::Error) -> Error {
let kind = match error.kind() {
io::ErrorKind::PermissionDenied => ErrorKind::PermissionDenied,
_ => ErrorKind::Other,
};
Error::with_message(kind, format!("could not read {}: {error}", path.display()))
}