use std::collections::VecDeque;
use oxideav_core::{
CodecId, CodecParameters, Encoder, Error, Frame, Packet, Result, SampleFormat, TimeBase,
};
use crate::codec_decoder::CODEC_ID_STR;
use crate::encoder::{EncoderConfig, StreamEncoder, FRAME_LEN};
use crate::he_aac_encoder::{HeAacConfig, HeAacEncoder, HE_FRAME_LEN};
pub const DEFAULT_BITRATE_PER_CHANNEL: u32 = 64_000;
pub fn make_encoder(params: &CodecParameters) -> Result<Box<dyn Encoder>> {
let sample_rate = params.sample_rate.unwrap_or(44_100);
let channels = params.channels.unwrap_or(2);
if let Some(fmt) = params.sample_format {
if fmt != SampleFormat::S16 {
return Err(Error::unsupported(
"oxideav-aac encoder accepts interleaved S16 input only",
));
}
}
if !params.extradata.is_empty() {
let asc = crate::asc::AudioSpecificConfig::parse_bits_bounded(
&mut oxideav_core::bits::BitReader::new(¶ms.extradata),
0,
params.extradata.len() as u64 * 8,
)
.map_err(|e| Error::invalid(format!("oxideav-aac encoder extradata ASC: {e}")))?;
let probe_sbr = asc
.trailing_sbr_probe
.as_ref()
.is_some_and(|p| p.sbr_present_flag);
if asc.sbr_present || probe_sbr {
let out_rate = asc
.extension_sample_rate
.or_else(|| {
asc.trailing_sbr_probe
.as_ref()
.and_then(|p| p.extension_sample_rate)
})
.unwrap_or(asc.sample_rate * 2);
if params.sample_rate.is_some_and(|r| r != out_rate) {
return Err(Error::invalid(
"oxideav-aac encoder: extradata ASC extension rate disagrees with params.sample_rate",
));
}
let mut p = params.clone();
p.sample_rate = Some(out_rate);
if p.channels.is_none() {
p.channels = Some(asc.channel_configuration.max(1).into());
}
return make_he_aac_encoder(&p);
}
}
if !(1..=6).contains(&channels) && channels != 8 {
return Err(Error::unsupported(
"oxideav-aac encoder supports the Table 1.19 default channel \
configurations: 1-6 or 8 channels",
));
}
let bitrate = params
.bit_rate
.map(|b| b.min(u64::from(u32::MAX)) as u32)
.unwrap_or(DEFAULT_BITRATE_PER_CHANNEL * u32::from(channels));
let config = EncoderConfig {
sample_rate,
channels: channels as u8,
bitrate,
};
let stream = StreamEncoder::new(config)
.map_err(|e| Error::invalid(format!("oxideav-aac encoder config: {e}")))?;
let mut out_params = CodecParameters::audio(CodecId::new(CODEC_ID_STR));
out_params.sample_rate = Some(sample_rate);
out_params.channels = Some(channels);
out_params.sample_format = Some(SampleFormat::S16);
out_params.bit_rate = Some(u64::from(bitrate));
Ok(Box::new(AacEncoder {
codec_id: CodecId::new(CODEC_ID_STR),
out_params,
stream,
time_base: TimeBase::new(1, i64::from(sample_rate)),
pending_pcm: Vec::new(),
packets: VecDeque::new(),
samples_emitted: 0,
flushed: false,
}))
}
pub fn make_he_aac_encoder(params: &CodecParameters) -> Result<Box<dyn Encoder>> {
let sample_rate = params.sample_rate.unwrap_or(44_100);
let channels = params.channels.unwrap_or(2);
if let Some(fmt) = params.sample_format {
if fmt != SampleFormat::S16 {
return Err(Error::unsupported(
"oxideav-aac encoder accepts interleaved S16 input only",
));
}
}
if !(1..=2).contains(&channels) {
return Err(Error::unsupported(
"oxideav-aac HE-AAC encoder supports 1 or 2 channels",
));
}
let bitrate = params
.bit_rate
.map(|b| b.min(u64::from(u32::MAX)) as u32)
.unwrap_or(DEFAULT_HE_BITRATE_PER_CHANNEL * u32::from(channels));
let stream = HeAacEncoder::new(HeAacConfig::new(sample_rate, channels as u8, bitrate))
.map_err(|e| Error::invalid(format!("oxideav-aac HE-AAC encoder config: {e}")))?;
let mut out_params = CodecParameters::audio(CodecId::new(CODEC_ID_STR));
out_params.sample_rate = Some(sample_rate);
out_params.channels = Some(channels);
out_params.sample_format = Some(SampleFormat::S16);
out_params.bit_rate = Some(u64::from(bitrate));
out_params.extradata = stream.audio_specific_config(false);
Ok(Box::new(HeAacPacketEncoder {
codec_id: CodecId::new(CODEC_ID_STR),
out_params,
stream,
time_base: TimeBase::new(1, i64::from(sample_rate)),
pending_pcm: Vec::new(),
packets: VecDeque::new(),
samples_emitted: 0,
flushed: false,
}))
}
pub const DEFAULT_HE_BITRATE_PER_CHANNEL: u32 = 32_000;
struct HeAacPacketEncoder {
codec_id: CodecId,
out_params: CodecParameters,
stream: HeAacEncoder,
time_base: TimeBase,
pending_pcm: Vec<i16>,
packets: VecDeque<Packet>,
samples_emitted: i64,
flushed: bool,
}
impl HeAacPacketEncoder {
fn drain_hops(&mut self) -> Result<()> {
let ch = usize::from(self.out_params.channels.unwrap_or(1)).max(1);
let hop = HE_FRAME_LEN * ch;
while self.pending_pcm.len() >= hop {
let chunk: Vec<i16> = self.pending_pcm.drain(..hop).collect();
let bytes = self
.stream
.encode_frame(&chunk)
.map_err(|e| Error::invalid(format!("oxideav-aac HE encode: {e}")))?;
self.push_packet(bytes);
}
Ok(())
}
fn push_packet(&mut self, bytes: Vec<u8>) {
let pkt = Packet::new(0, self.time_base, bytes)
.with_pts(self.samples_emitted)
.with_duration(HE_FRAME_LEN as i64)
.with_keyframe(true);
self.samples_emitted += HE_FRAME_LEN as i64;
self.packets.push_back(pkt);
}
}
impl Encoder for HeAacPacketEncoder {
fn codec_id(&self) -> &CodecId {
&self.codec_id
}
fn output_params(&self) -> &CodecParameters {
&self.out_params
}
fn send_frame(&mut self, frame: &Frame) -> Result<()> {
if self.flushed {
return Err(Error::invalid("send_frame after flush"));
}
let audio = match frame {
Frame::Audio(a) => a,
_ => return Err(Error::invalid("oxideav-aac encoder accepts audio frames")),
};
let plane = match audio.data.as_slice() {
[p] => p,
_ => {
return Err(Error::invalid(
"oxideav-aac encoder expects one interleaved S16 plane",
))
}
};
if plane.len() % 2 != 0 {
return Err(Error::invalid("odd byte count in S16 plane"));
}
self.pending_pcm.extend(
plane
.chunks_exact(2)
.map(|b| i16::from_le_bytes([b[0], b[1]])),
);
self.drain_hops()
}
fn receive_packet(&mut self) -> Result<Packet> {
if let Some(pkt) = self.packets.pop_front() {
return Ok(pkt);
}
if self.flushed {
Err(Error::Eof)
} else {
Err(Error::NeedMore)
}
}
fn flush(&mut self) -> Result<()> {
if self.flushed {
return Ok(());
}
if !self.pending_pcm.is_empty() {
let chunk: Vec<i16> = std::mem::take(&mut self.pending_pcm);
let bytes = self
.stream
.encode_frame(&chunk)
.map_err(|e| Error::invalid(format!("oxideav-aac HE encode: {e}")))?;
self.push_packet(bytes);
}
let bytes = self
.stream
.finish()
.map_err(|e| Error::invalid(format!("oxideav-aac HE flush: {e}")))?;
self.push_packet(bytes);
self.flushed = true;
Ok(())
}
}
struct AacEncoder {
codec_id: CodecId,
out_params: CodecParameters,
stream: StreamEncoder,
time_base: TimeBase,
pending_pcm: Vec<i16>,
packets: VecDeque<Packet>,
samples_emitted: i64,
flushed: bool,
}
impl AacEncoder {
fn drain_hops(&mut self) -> Result<()> {
let ch = usize::from(self.out_params.channels.unwrap_or(1)).max(1);
let hop = FRAME_LEN * ch;
while self.pending_pcm.len() >= hop {
let chunk: Vec<i16> = self.pending_pcm.drain(..hop).collect();
let bytes = self
.stream
.encode_frame(&chunk)
.map_err(|e| Error::invalid(format!("oxideav-aac encode: {e}")))?;
self.push_packet(bytes);
}
Ok(())
}
fn push_packet(&mut self, bytes: Vec<u8>) {
let pkt = Packet::new(0, self.time_base, bytes)
.with_pts(self.samples_emitted)
.with_duration(FRAME_LEN as i64)
.with_keyframe(true);
self.samples_emitted += FRAME_LEN as i64;
self.packets.push_back(pkt);
}
}
impl Encoder for AacEncoder {
fn codec_id(&self) -> &CodecId {
&self.codec_id
}
fn output_params(&self) -> &CodecParameters {
&self.out_params
}
fn send_frame(&mut self, frame: &Frame) -> Result<()> {
if self.flushed {
return Err(Error::invalid("send_frame after flush"));
}
let audio = match frame {
Frame::Audio(a) => a,
_ => return Err(Error::invalid("oxideav-aac encoder accepts audio frames")),
};
let plane = match audio.data.as_slice() {
[p] => p,
_ => {
return Err(Error::invalid(
"oxideav-aac encoder expects one interleaved S16 plane",
))
}
};
if plane.len() % 2 != 0 {
return Err(Error::invalid("odd byte count in S16 plane"));
}
self.pending_pcm.extend(
plane
.chunks_exact(2)
.map(|b| i16::from_le_bytes([b[0], b[1]])),
);
self.drain_hops()
}
fn receive_packet(&mut self) -> Result<Packet> {
if let Some(pkt) = self.packets.pop_front() {
return Ok(pkt);
}
if self.flushed {
Err(Error::Eof)
} else {
Err(Error::NeedMore)
}
}
fn flush(&mut self) -> Result<()> {
if self.flushed {
return Ok(());
}
if !self.pending_pcm.is_empty() {
let chunk: Vec<i16> = std::mem::take(&mut self.pending_pcm);
let bytes = self
.stream
.encode_frame(&chunk)
.map_err(|e| Error::invalid(format!("oxideav-aac encode: {e}")))?;
self.push_packet(bytes);
}
let bytes = self
.stream
.finish()
.map_err(|e| Error::invalid(format!("oxideav-aac flush: {e}")))?;
self.push_packet(bytes);
self.flushed = true;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use oxideav_core::AudioFrame;
fn params(rate: u32, channels: u16, bitrate: Option<u64>) -> CodecParameters {
let mut p = CodecParameters::audio(CodecId::new(CODEC_ID_STR));
p.sample_rate = Some(rate);
p.channels = Some(channels);
p.sample_format = Some(SampleFormat::S16);
p.bit_rate = bitrate;
p
}
fn tone_frame(samples: usize, channels: usize) -> Frame {
let mut bytes = Vec::with_capacity(samples * channels * 2);
for i in 0..samples {
let v = (8000.0 * (0.05 * i as f64).sin()) as i16;
for _ in 0..channels {
bytes.extend_from_slice(&v.to_le_bytes());
}
}
Frame::Audio(AudioFrame {
samples: samples as u32,
pts: None,
data: vec![bytes],
})
}
#[test]
fn encoder_builds_and_reports_output_params() {
let enc = make_encoder(¶ms(44_100, 2, Some(128_000))).expect("builds");
assert_eq!(enc.codec_id().as_str(), "aac");
let out = enc.output_params();
assert_eq!(out.sample_rate, Some(44_100));
assert_eq!(out.channels, Some(2));
assert_eq!(out.bit_rate, Some(128_000));
}
#[test]
fn encoder_rejects_unsupported_shapes() {
assert!(make_encoder(¶ms(44_100, 7, None)).is_err());
assert!(make_encoder(¶ms(44_100, 9, None)).is_err());
assert!(make_encoder(¶ms(44_100, 6, None)).is_ok());
assert!(make_encoder(¶ms(44_100, 8, None)).is_ok());
assert!(make_encoder(¶ms(44_055, 1, None)).is_err());
let mut p = params(44_100, 2, None);
p.sample_format = Some(SampleFormat::F32);
assert!(make_encoder(&p).is_err());
}
#[test]
fn frames_in_packets_out_with_flush() {
let mut enc = make_encoder(¶ms(44_100, 1, Some(96_000))).unwrap();
enc.send_frame(&tone_frame(2_560, 1)).unwrap();
let p0 = enc.receive_packet().unwrap();
assert_eq!(p0.pts, Some(0));
assert_eq!(p0.duration, Some(1024));
assert!(p0.flags.keyframe);
assert!(p0.data.starts_with(&[0xFF]));
let p1 = enc.receive_packet().unwrap();
assert_eq!(p1.pts, Some(1024));
assert!(matches!(enc.receive_packet(), Err(Error::NeedMore)));
enc.flush().unwrap();
let p2 = enc.receive_packet().unwrap();
assert_eq!(p2.pts, Some(2048));
let p3 = enc.receive_packet().unwrap();
assert_eq!(p3.pts, Some(3072));
assert!(matches!(enc.receive_packet(), Err(Error::Eof)));
}
#[test]
fn he_profile_via_extradata_and_direct_factory() {
let mut p = params(44_100, 1, Some(40_000));
let mut enc = make_he_aac_encoder(&p).unwrap();
assert_eq!(enc.output_params().sample_rate, Some(44_100));
let extradata = enc.output_params().extradata.clone();
let (asc, _) = crate::asc::AudioSpecificConfig::parse(&extradata).unwrap();
assert!(asc.trailing_sbr_probe.unwrap().sbr_present_flag);
enc.send_frame(&tone_frame(2 * crate::he_aac_encoder::HE_FRAME_LEN, 1))
.unwrap();
enc.flush().unwrap();
let mut stream_bytes = Vec::new();
loop {
match enc.receive_packet() {
Ok(pkt) => {
assert_eq!(pkt.duration, Some(2048));
stream_bytes.extend_from_slice(&pkt.data);
}
Err(Error::Eof) => break,
Err(e) => panic!("unexpected: {e}"),
}
}
let (h, _) = crate::adts::AdtsHeader::parse(&stream_bytes).unwrap();
assert_eq!(h.sample_rate(), 22_050);
let mut dec = crate::decode::StreamDecoder::new();
let frames = dec.decode_all(&stream_bytes).unwrap();
assert!(frames.iter().all(|f| f.sample_rate == 44_100));
p.extradata = extradata;
let enc2 = make_encoder(&p).unwrap();
assert_eq!(enc2.output_params().sample_rate, Some(44_100));
let mut bad = p.clone();
bad.sample_rate = Some(48_000);
assert!(make_encoder(&bad).is_err());
let mut lc = params(44_100, 1, None);
lc.extradata = crate::asc_writer::aac_lc_asc(44_100, 1);
assert!(make_encoder(&lc).is_ok());
assert!(make_he_aac_encoder(¶ms(44_100, 3, None)).is_err());
assert!(make_he_aac_encoder(¶ms(12_000, 1, None)).is_err());
assert!(make_he_aac_encoder(¶ms(16_000, 1, None)).is_ok());
}
#[test]
fn registry_round_trip_decodes_encoder_output() {
let mut enc = make_encoder(¶ms(44_100, 1, Some(128_000))).unwrap();
let n = 4 * FRAME_LEN;
enc.send_frame(&tone_frame(n, 1)).unwrap();
enc.flush().unwrap();
let mut stream_bytes = Vec::new();
loop {
match enc.receive_packet() {
Ok(p) => stream_bytes.extend_from_slice(&p.data),
Err(Error::Eof) => break,
Err(e) => panic!("unexpected: {e}"),
}
}
let mut dec = crate::codec_decoder::make_decoder(¶ms(44_100, 1, None)).unwrap();
let pkt = Packet::new(0, TimeBase::new(1, 44_100), stream_bytes);
dec.send_packet(&pkt).unwrap();
let mut decoded_samples = 0usize;
loop {
match dec.receive_frame() {
Ok(Frame::Audio(a)) => decoded_samples += a.samples as usize,
Ok(_) => panic!("non-audio frame"),
Err(_) => break,
}
}
assert_eq!(decoded_samples, n + FRAME_LEN);
}
}