#![allow(clippy::cast_precision_loss)]
#![allow(clippy::cast_possible_truncation)]
use crate::traits::AudioDecoder;
use crate::{AudioError, AudioFrame, AudioResult, ChannelLayout};
use oximedia_core::{CodecId, SampleFormat};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AacObjectType {
AacLc,
HeAacV1,
HeAacV2,
}
impl AacObjectType {
#[must_use]
pub fn object_type_id(self) -> u8 {
match self {
Self::AacLc => 2,
Self::HeAacV1 => 5,
Self::HeAacV2 => 29,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct AdtsHeader {
pub mpeg_version: u8,
pub profile: u8,
pub sampling_freq_index: u8,
pub channel_config: u8,
pub frame_length: u16,
pub buffer_fullness: u16,
pub num_aac_frames: u8,
pub has_crc: bool,
}
pub const AAC_SAMPLE_RATES: [u32; 13] = [
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350,
];
impl AdtsHeader {
pub fn parse(data: &[u8]) -> AudioResult<Self> {
if data.len() < 7 {
return Err(AudioError::InvalidData(
"ADTS header requires at least 7 bytes".into(),
));
}
if data[0] != 0xFF || (data[1] & 0xF0) != 0xF0 {
return Err(AudioError::InvalidData(format!(
"ADTS sync word not found: {:02X} {:02X}",
data[0], data[1]
)));
}
let mpeg_version = (data[1] >> 3) & 0x01;
let has_crc = (data[1] & 0x01) == 0; let profile = ((data[2] >> 6) & 0x03) + 1; let sampling_freq_index = (data[2] >> 2) & 0x0F;
let channel_config = ((data[2] & 0x01) << 2) | (data[3] >> 6);
let frame_length = ((u16::from(data[3] & 0x03) << 11)
| (u16::from(data[4]) << 3)
| (u16::from(data[5]) >> 5)) as u16;
let buffer_fullness = ((u16::from(data[5] & 0x1F) << 6) | u16::from(data[6] >> 2)) as u16;
let num_aac_frames = (data[6] & 0x03) + 1;
if sampling_freq_index >= 13 {
return Err(AudioError::InvalidData(format!(
"Invalid sampling frequency index: {sampling_freq_index}"
)));
}
Ok(Self {
mpeg_version,
profile,
sampling_freq_index,
channel_config,
frame_length,
buffer_fullness,
num_aac_frames,
has_crc,
})
}
#[must_use]
pub fn sample_rate(&self) -> u32 {
AAC_SAMPLE_RATES
.get(self.sampling_freq_index as usize)
.copied()
.unwrap_or(44100)
}
#[must_use]
pub fn channels(&self) -> u8 {
match self.channel_config {
0 => 2, 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, 6 => 6, 7 => 8, _ => 2,
}
}
#[must_use]
pub fn header_size(&self) -> usize {
if self.has_crc {
9
} else {
7
}
}
}
fn not_implemented() -> AudioError {
AudioError::UnsupportedFormat(
"AAC decoding is not implemented in oximedia-audio: no spectral (Huffman) \
decode, no filterbank, no SBR/PS. Real AAC decode is planned via an \
external platform extension (e.g. AudioToolbox on macOS). Use ADTS \
header parsing (`AdtsHeader::parse`) for stream inspection only."
.to_string(),
)
}
pub struct AacDecoder {
sample_rate: Option<u32>,
channels: Option<u8>,
decode_errors: u32,
}
impl AacDecoder {
#[must_use]
pub fn new() -> Self {
Self {
sample_rate: None,
channels: None,
decode_errors: 0,
}
}
#[must_use]
pub fn codec_name() -> &'static str {
CodecId::Aac.name()
}
#[must_use]
pub fn decode_errors(&self) -> u32 {
self.decode_errors
}
pub fn send_packet(&mut self, data: &[u8], _pts: i64) -> AudioResult<()> {
self.scan_adts_metadata(data);
self.decode_errors = self.decode_errors.saturating_add(1);
Err(not_implemented())
}
pub fn receive_frame(&mut self) -> AudioResult<Option<AudioFrame>> {
Err(not_implemented())
}
pub fn flush(&mut self) -> AudioResult<()> {
Ok(())
}
pub fn reset(&mut self) {
self.sample_rate = None;
self.channels = None;
self.decode_errors = 0;
}
#[must_use]
pub fn output_format(&self) -> Option<SampleFormat> {
None
}
#[must_use]
pub fn sample_rate(&self) -> Option<u32> {
self.sample_rate
}
#[must_use]
pub fn channel_layout(&self) -> Option<ChannelLayout> {
self.channels.map(Self::channel_config_to_layout)
}
fn scan_adts_metadata(&mut self, data: &[u8]) {
if data.len() < 7 {
return;
}
for i in 0..=(data.len() - 7) {
if data[i] == 0xFF && (data[i + 1] & 0xF0) == 0xF0 {
if let Ok(header) = AdtsHeader::parse(&data[i..]) {
self.sample_rate = Some(header.sample_rate());
self.channels = Some(header.channels());
return;
}
}
}
}
fn channel_config_to_layout(channels: u8) -> ChannelLayout {
match channels {
1 => ChannelLayout::Mono,
2 => ChannelLayout::Stereo,
6 => ChannelLayout::Surround51,
_ => ChannelLayout::Stereo,
}
}
}
impl Default for AacDecoder {
fn default() -> Self {
Self::new()
}
}
impl AudioDecoder for AacDecoder {
fn codec(&self) -> CodecId {
CodecId::Aac
}
fn send_packet(&mut self, data: &[u8], pts: i64) -> AudioResult<()> {
Self::send_packet(self, data, pts)
}
fn receive_frame(&mut self) -> AudioResult<Option<AudioFrame>> {
Self::receive_frame(self)
}
fn flush(&mut self) -> AudioResult<()> {
Self::flush(self)
}
fn reset(&mut self) {
Self::reset(self);
}
fn output_format(&self) -> Option<SampleFormat> {
Self::output_format(self)
}
fn sample_rate(&self) -> Option<u32> {
Self::sample_rate(self)
}
fn channel_layout(&self) -> Option<ChannelLayout> {
Self::channel_layout(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_adts_header_parse_invalid_sync() {
let data = [0x00u8; 7];
assert!(AdtsHeader::parse(&data).is_err());
}
#[test]
fn test_adts_header_parse_too_short() {
let data = [0xFF, 0xF1, 0x50];
assert!(AdtsHeader::parse(&data).is_err());
}
#[test]
fn test_adts_header_sample_rate_lookup() {
let mut data = [0u8; 7];
data[0] = 0xFF;
data[1] = 0xF1; data[2] = 0x50; data[3] = 0x00;
data[4] = 0x1C; data[5] = 0xE0;
data[6] = 0x00;
if let Ok(h) = AdtsHeader::parse(&data) {
let sr = h.sample_rate();
assert!(AAC_SAMPLE_RATES.contains(&sr) || sr == 44100);
}
}
fn adts_header(frame_length: u16) -> [u8; 7] {
let mut data = [0u8; 7];
data[0] = 0xFF;
data[1] = 0xF1; data[2] = (1 << 6) | (3 << 2);
data[3] = (2 << 6) | ((frame_length >> 11) & 0x03) as u8;
data[4] = ((frame_length >> 3) & 0xFF) as u8;
data[5] = (((frame_length & 0x07) << 5) | 0x1F) as u8;
data[6] = 0xFC;
data
}
#[test]
fn test_aac_decoder_new_reports_aac_codec_id() {
let dec = AacDecoder::new();
assert_eq!(AacDecoder::codec_name(), "aac");
assert_eq!(AudioDecoder::codec(&dec), CodecId::Aac);
assert_ne!(AudioDecoder::codec(&dec), CodecId::Mp3);
assert!(dec.sample_rate().is_none());
assert!(dec.channel_layout().is_none());
assert!(dec.output_format().is_none());
assert_eq!(dec.decode_errors(), 0);
}
#[test]
fn test_aac_decoder_as_trait_object_fails_closed() {
let mut dec: Box<dyn AudioDecoder> = Box::new(AacDecoder::new());
assert_eq!(dec.codec(), CodecId::Aac);
let mut packet = adts_header(64).to_vec();
packet.resize(64, 0);
let err = dec
.send_packet(&packet, 0)
.expect_err("AAC decode must not succeed through the trait either");
assert!(
matches!(err, AudioError::UnsupportedFormat(_)),
"expected UnsupportedFormat, got {err:?}"
);
assert_eq!(dec.sample_rate(), Some(48_000));
assert_eq!(dec.channel_layout(), Some(ChannelLayout::Stereo));
assert!(dec.output_format().is_none());
assert!(
dec.receive_frame().is_err(),
"no fabricated frame may be handed out"
);
dec.flush().expect("flush must succeed");
dec.reset();
assert!(
dec.sample_rate().is_none(),
"reset must clear inspection state"
);
}
#[test]
fn test_aac_decoder_is_send() {
fn assert_send<T: Send>() {}
assert_send::<AacDecoder>();
}
#[test]
fn test_aac_decoder_empty_packet_errors() {
let mut dec = AacDecoder::new();
let err = dec
.send_packet(&[], 0)
.expect_err("AAC decode must not succeed");
assert!(
matches!(err, AudioError::UnsupportedFormat(_)),
"expected UnsupportedFormat, got {err:?}"
);
assert!(
format!("{err}").contains("not implemented"),
"error must say decoding is not implemented: {err}"
);
assert!(
dec.receive_frame().is_err(),
"no frame may ever be produced"
);
}
#[test]
fn test_aac_decoder_garbage_data_errors() {
let mut dec = AacDecoder::new();
let garbage = vec![0x55u8; 100];
assert!(
dec.send_packet(&garbage, 0).is_err(),
"garbage must not decode"
);
assert!(dec.receive_frame().is_err());
assert_eq!(dec.decode_errors(), 1);
}
#[test]
fn test_aac_decoder_valid_adts_errors_but_reports_parameters() {
let mut dec = AacDecoder::new();
let mut packet = adts_header(64).to_vec();
packet.resize(64, 0);
assert!(
dec.send_packet(&packet, 0).is_err(),
"valid ADTS must still fail: decoding is not implemented"
);
assert_eq!(dec.sample_rate(), Some(48_000));
assert_eq!(dec.channel_layout(), Some(ChannelLayout::Stereo));
assert!(
dec.receive_frame().is_err(),
"no fabricated frame may be handed out"
);
}
#[test]
fn test_aac_decoder_reset() {
let mut dec = AacDecoder::new();
let mut packet = adts_header(64).to_vec();
packet.resize(64, 0);
let _ = dec.send_packet(&packet, 0);
assert_eq!(dec.decode_errors(), 1);
dec.reset();
assert_eq!(dec.decode_errors(), 0);
assert!(dec.sample_rate().is_none());
assert!(dec.channel_layout().is_none());
}
#[test]
fn test_aac_decoder_flush_is_ok() {
let mut dec = AacDecoder::new();
dec.flush().expect("flush must succeed");
}
#[test]
fn test_aac_object_type_id() {
assert_eq!(AacObjectType::AacLc.object_type_id(), 2);
assert_eq!(AacObjectType::HeAacV1.object_type_id(), 5);
assert_eq!(AacObjectType::HeAacV2.object_type_id(), 29);
}
#[test]
fn test_adts_channel_config_to_channels() {
let mut data = [0u8; 7];
data[0] = 0xFF;
data[1] = 0xF1;
data[2] = 0x4C; data[3] = 0x80; data[4] = 0x00;
data[5] = 0x1C; data[6] = 0x00;
if let Ok(h) = AdtsHeader::parse(&data) {
assert_eq!(h.channels(), 2);
}
}
}