Files
craft-libs/crates/audio/tests/codecs/track_decoder.rs
T
alehelandClaude Opus 5.5 bdf8cb2a19 craft-audio: Opus encoder
EffectCraft's `effectcraft-opusenc` as `opus::encoder` (and
`opus::Encoder`) behind `opus-encode`: SILK, hybrid and CELT, 48 kHz,
20 ms frames, mono or stereo. The constructors and `encode_float`
return `Result` instead of panicking. `Bandwidth` is one type for the
decoder and the encoder, and the two `Mode` enums convert into each
other. `encode` now turns on `opus-encode`.

The encoder tests (modes, ffmpeg's two Opus decoders as oracles) and
`TrackDecoder` on Opus without an `OpusHead` come with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 21:09:46 -05:00

53 lines
2.4 KiB
Rust

//! `TrackDecoder` on streams an app's own demuxer hands over without codec configuration:
//! ADTS-framed AAC (MPEG-TS) and Opus without an `OpusHead` (RTP).
use craft_audio::aac::{Encoder as AacEncoder, EncoderConfig};
use craft_audio::opus::Encoder as OpusEncoder;
use craft_audio::{CodecId, CodecParams, TrackDecoder};
fn tone(n: usize) -> Vec<f32> {
(0..n).map(|i| (i as f32 * 0.03).sin() * 0.4).collect()
}
#[test]
fn adts_aac_without_extradata() {
let left = tone(48_000);
let right = tone(48_000);
let mut enc = AacEncoder::new(EncoderConfig::cbr(48_000, 2, 128_000).with_adts(true)).unwrap();
let mut units = enc.encode(&[left.as_slice(), right.as_slice()]);
units.extend(enc.flush());
// No AudioSpecificConfig: the first ADTS header gives it, and every header is stripped.
let lookahead: Vec<&[u8]> = units.iter().take(craft_audio::LOOKAHEAD_PACKETS).map(Vec::as_slice).collect();
let mut dec = TrackDecoder::new(&CodecParams::new(CodecId::Aac), &lookahead).unwrap();
assert_eq!((dec.codec(), dec.sample_rate(), dec.channels()), (CodecId::Aac, 48_000, 2));
let frames: usize = units.iter().map(|u| dec.decode(u).unwrap().frames()).sum();
assert_eq!(frames, units.len() * 1024);
// Two ADTS frames in one packet, as a PES payload often holds, decode as both.
let mut dec = TrackDecoder::new(&CodecParams::new(CodecId::Aac), &lookahead).unwrap();
let pair = [units[0].as_slice(), units[1].as_slice()].concat();
assert_eq!(dec.decode(&pair).unwrap().frames(), 2048);
// Raw access units still need their configuration.
let raw = &units[0][7..];
assert!(TrackDecoder::new(&CodecParams::new(CodecId::Aac), &[raw]).is_err());
}
#[test]
fn opus_without_opus_head() {
let mut enc = OpusEncoder::new(1, 64_000).unwrap();
let pcm = tone(OpusEncoder::FRAME_SIZE * 10);
let packets: Vec<Vec<u8>> = pcm.chunks(OpusEncoder::FRAME_SIZE).map(|f| enc.encode_float(f).unwrap()).collect();
let params = CodecParams::new(CodecId::Opus).with_channels(1).with_sample_rate(48_000);
let mut dec = TrackDecoder::new(&params, &[]).unwrap();
assert_eq!((dec.sample_rate(), dec.channels()), (48_000, 1));
for p in &packets {
assert_eq!(dec.decode(p).unwrap().frames(), OpusEncoder::FRAME_SIZE);
}
// More than two channels need the mapping an OpusHead carries.
assert!(TrackDecoder::new(&CodecParams::new(CodecId::Opus).with_channels(6), &[]).is_err());
}