#![deny(unsafe_code)]
#![allow(clippy::needless_range_loop)]
pub mod alaw;
pub mod mulaw;
pub mod tables;
use oxideav_core::{CodecCapabilities, CodecId, CodecTag};
use oxideav_core::{CodecInfo, CodecRegistry};
pub const CODEC_ID_MULAW: &str = "pcm_mulaw";
pub const CODEC_ID_ALAW: &str = "pcm_alaw";
pub const MULAW_ALIASES: &[&str] = &["pcm_mulaw", "ulaw", "g711u"];
pub const ALAW_ALIASES: &[&str] = &["pcm_alaw", "alaw", "g711a"];
pub fn register(reg: &mut CodecRegistry) {
for (idx, alias) in MULAW_ALIASES.iter().enumerate() {
let caps = CodecCapabilities::audio("g711_mulaw_sw")
.with_lossy(true)
.with_intra_only(true);
let mut info = CodecInfo::new(CodecId::new(*alias))
.capabilities(caps)
.decoder(mulaw::make_decoder)
.encoder(mulaw::make_encoder);
if idx == 0 {
info = info.tag(CodecTag::wave_format(0x0007));
}
reg.register(info);
}
for (idx, alias) in ALAW_ALIASES.iter().enumerate() {
let caps = CodecCapabilities::audio("g711_alaw_sw")
.with_lossy(true)
.with_intra_only(true);
let mut info = CodecInfo::new(CodecId::new(*alias))
.capabilities(caps)
.decoder(alaw::make_decoder)
.encoder(alaw::make_encoder);
if idx == 0 {
info = info.tag(CodecTag::wave_format(0x0006));
}
reg.register(info);
}
}
#[cfg(test)]
mod tests {
use super::*;
use oxideav_core::{CodecParameters, Frame, Packet, SampleFormat, TimeBase};
fn params(id: &str) -> CodecParameters {
let mut p = CodecParameters::audio(CodecId::new(id));
p.sample_rate = Some(8_000);
p.channels = Some(1);
p.sample_format = Some(SampleFormat::S16);
p
}
#[test]
fn register_all_aliases() {
let mut reg = CodecRegistry::new();
register(&mut reg);
for alias in MULAW_ALIASES.iter().chain(ALAW_ALIASES.iter()) {
let id = CodecId::new(*alias);
assert!(reg.has_decoder(&id), "no decoder for alias {alias}");
assert!(reg.has_encoder(&id), "no encoder for alias {alias}");
}
}
#[test]
fn mulaw_aliases_resolve_to_same_impl() {
let mut reg = CodecRegistry::new();
register(&mut reg);
let input = vec![0x55u8, 0xAA, 0x80, 0x00];
let mut results = Vec::new();
for alias in MULAW_ALIASES {
let p = params(alias);
let mut dec = reg.make_decoder(&p).expect("make_decoder");
let pkt = Packet::new(0, TimeBase::new(1, 8_000), input.clone());
dec.send_packet(&pkt).unwrap();
let Frame::Audio(af) = dec.receive_frame().unwrap() else {
panic!("expected audio frame");
};
results.push(af.data[0].clone());
}
for r in &results[1..] {
assert_eq!(r, &results[0]);
}
}
#[test]
fn mulaw_roundtrip_samples() {
let mut reg = CodecRegistry::new();
register(&mut reg);
let p = params(CODEC_ID_MULAW);
let mut enc = reg.make_encoder(&p).expect("make_encoder");
let mut dec = reg.make_decoder(&p).expect("make_decoder");
let samples: Vec<i16> = vec![0, 1, -1, 100, -100, 10000, -10000, 32000, -32000];
let mut pcm_bytes = Vec::with_capacity(samples.len() * 2);
for &s in &samples {
pcm_bytes.extend_from_slice(&s.to_le_bytes());
}
let input = Frame::Audio(oxideav_core::AudioFrame {
format: SampleFormat::S16,
channels: 1,
sample_rate: 8_000,
samples: samples.len() as u32,
pts: Some(0),
time_base: TimeBase::new(1, 8_000),
data: vec![pcm_bytes],
});
enc.send_frame(&input).unwrap();
let pkt = enc.receive_packet().unwrap();
assert_eq!(pkt.data.len(), samples.len());
dec.send_packet(&pkt).unwrap();
let Frame::Audio(af) = dec.receive_frame().unwrap() else {
panic!("expected audio frame");
};
assert_eq!(af.samples as usize, samples.len());
}
}