mod common;
use common::*;
use opus_pure::{
Application, Bandwidth, Error, OpusDecoder, OpusEncoder, OpusMSDecoder, OpusMSEncoder,
RateControl, Signal,
};
fn frame_20ms() -> usize {
960
}
#[test]
fn multistream_surround_round_trips() {
for &(channels, frame) in &[(6usize, 960usize), (8, 960), (6, 2880)] {
let rate = 48_000i32;
let mono = music_like(rate, frame * 10);
let mut src = Vec::with_capacity(frame * 10 * channels);
for &sample in mono.iter().take(frame * 10) {
for c in 0..channels {
src.push(sample * (1.0 - c as f32 * 0.1));
}
}
let mut enc = OpusMSEncoder::new(rate, channels, 1, Application::Audio).unwrap();
let mut dec = OpusMSDecoder::new(rate, channels, 1).unwrap();
let mut decoded = Vec::new();
let mut pkt = vec![0u8; 8000];
for f in 0..10 {
let len = enc
.encode(
&src[f * frame * channels..(f + 1) * frame * channels],
frame,
&mut pkt,
)
.unwrap();
assert!(len > 0);
let mut out = vec![0.0f32; frame * channels];
let n = dec.decode(&pkt[..len], frame, &mut out).unwrap();
assert_eq!(n, frame);
assert!(out.iter().all(|s| s.is_finite()));
decoded.extend_from_slice(&out);
}
let skip = frame * 4 * channels;
for c in 0..channels {
let got = energy(&deinterleave(&decoded[skip..], channels, c));
let want = energy(&deinterleave(&src[skip..], channels, c));
let ratio = got / want.max(1e-12);
assert!(
(0.25..=4.0).contains(&ratio),
"{channels}ch/{frame} samples: channel {c} energy is {ratio:.3}x the input's"
);
}
}
}
#[test]
fn a_surround_decoder_can_apply_the_headers_output_gain() {
let (rate, channels, frame) = (48_000i32, 6usize, 960usize);
let mono = music_like(rate, frame * 4);
let mut src = Vec::with_capacity(frame * 4 * channels);
for &sample in mono.iter().take(frame * 4) {
for c in 0..channels {
src.push(sample * (1.0 - c as f32 * 0.1));
}
}
let mut enc = OpusMSEncoder::new(rate, channels, 1, Application::Audio).unwrap();
let mut pkt = vec![0u8; 8000];
let mut packets = Vec::new();
for f in 0..4 {
let len = enc
.encode(
&src[f * frame * channels..(f + 1) * frame * channels],
frame,
&mut pkt,
)
.unwrap();
packets.push(pkt[..len].to_vec());
}
let decode = |gain_q8: i32| {
let mut dec = OpusMSDecoder::new(rate, channels, 1).unwrap();
assert_eq!(dec.streams().len(), dec.nb_streams());
for d in dec.streams_mut() {
d.gain_q8 = gain_q8;
}
let mut out = Vec::new();
let mut block = vec![0.0f32; frame * channels];
for p in &packets {
let n = dec.decode(p, frame, &mut block).unwrap();
out.extend_from_slice(&block[..n * channels]);
}
out
};
let plain = decode(0);
let quiet = decode(-1536);
assert_eq!(plain.len(), quiet.len());
let want = 10f32.powf(-6.0 / 20.0);
for c in 0..channels {
let (a, b) = (
energy(&deinterleave(&plain, channels, c)),
energy(&deinterleave(&quiet, channels, c)),
);
assert!(a > 1e-9, "channel {c} decoded to silence at unity gain");
let ratio = (b / a).sqrt();
assert!(
(ratio - want).abs() < 0.01,
"channel {c}: gain applied as {ratio:.4}x, expected {want:.4}x"
);
}
}
#[test]
fn signal_type_hint_steers_mode_selection() {
let (rate, frame) = (48_000i32, 960usize);
let src = speech_like(rate, frame * 40);
let count_modes = |hint: Option<Signal>| {
let mut c = Codec::new(rate, 1, Application::Audio)
.frame_samples(frame)
.bitrate(32_000);
c.enc.signal_type = hint;
c.encode_all(&src)
.iter()
.filter(|p| packet_mode(p) != "celt")
.count()
};
let voice = count_modes(Some(Signal::Voice));
let music = count_modes(Some(Signal::Music));
assert!(
voice > music,
"the Voice hint produced {voice} SILK/hybrid frames and Music {music}; \
the hint is not reaching the mode decision"
);
}
#[test]
fn forced_bandwidth_reaches_the_bitstream() {
let (rate, frame) = (48_000i32, 960usize);
let src = music_like(rate, frame * 20);
for &(bw, want_min_cfg, want_max_cfg) in &[
(Bandwidth::Narrowband, 16u8, 19u8),
(Bandwidth::Mediumband, 20, 23), (Bandwidth::Wideband, 20, 23),
(Bandwidth::Superwideband, 24, 27),
(Bandwidth::Fullband, 28, 31),
] {
let packets = Codec::new(rate, 1, Application::Audio)
.frame_samples(frame)
.bitrate(64_000)
.bandwidth(bw)
.encode_all(&src);
for pkt in &packets {
let cfg = pkt[0] >> 3;
assert!(
(want_min_cfg..=want_max_cfg).contains(&cfg),
"{bw:?}: TOC config {cfg} outside {want_min_cfg}..={want_max_cfg}"
);
}
}
}
#[test]
fn cbr_and_vbr_differ_as_advertised() {
for &frame in &[960usize, 2880] {
let rate = 48_000i32;
let mut src = music_like(rate, frame * 30);
for (i, s) in src.iter_mut().enumerate() {
if (i / (frame * 5)) % 2 == 1 {
*s = 0.0;
}
}
let sizes = |cbr: bool| {
let mut c = Codec::new(rate, 1, Application::Audio)
.frame_samples(frame)
.bitrate(64_000);
c.enc.rate_control = if cbr {
RateControl::Cbr
} else {
RateControl::ConstrainedVbr
};
c.encode_all(&src)
.iter()
.map(|p| p.len())
.collect::<Vec<_>>()
};
let cbr = sizes(true);
assert!(
cbr.windows(2).all(|w| w[0] == w[1]),
"{frame}-sample CBR packet sizes varied: {:?}",
&cbr[..5]
);
let want = (64_000i64 * frame as i64 / 48_000 + 4) / 8;
assert_eq!(
cbr[0] as i64, want,
"{frame}-sample CBR packets are {} bytes, not the {want} the bitrate asks for",
cbr[0]
);
let vbr = sizes(false);
assert!(
vbr.windows(2).any(|w| w[0] != w[1]),
"{frame}-sample VBR produced constant-size packets"
);
}
}
#[test]
fn final_range_is_reported_and_changes_per_packet() {
let (rate, frame) = (48_000i32, 960usize);
let src = music_like(rate, frame * 10);
let mut c = Codec::new(rate, 1, Application::Audio).bitrate(64_000);
let mut ranges = Vec::new();
for chunk in src.chunks_exact(frame) {
let mut pkt = vec![0u8; 4000];
c.enc.encode(chunk, frame, &mut pkt).unwrap();
ranges.push(c.enc.final_range());
}
assert!(ranges.iter().any(|&r| r != 0), "final_range is always zero");
assert!(
ranges.windows(2).any(|w| w[0] != w[1]),
"final_range never changes"
);
}
#[test]
fn decode_treats_frame_size_as_buffer_capacity() {
let rate = 48_000i32;
let frame = 960usize; let src = music_like(rate, frame * 20);
let pkt = Codec::new(rate, 1, Application::Audio)
.bitrate(64_000)
.encode_all(&src[..frame])
.remove(0);
let mut exact = vec![0.0f32; frame];
let mut roomy = vec![0.0f32; frame * 6];
let got_exact = OpusDecoder::new(rate, 1)
.unwrap()
.decode(&pkt, frame, &mut exact)
.unwrap();
let got_roomy = OpusDecoder::new(rate, 1)
.unwrap()
.decode(&pkt, frame * 6, &mut roomy)
.unwrap();
assert_eq!(got_exact, frame, "exact buffer: wrong sample count");
assert_eq!(got_roomy, frame, "roomy buffer: should still decode 20 ms");
assert_eq!(
exact[..frame],
roomy[..frame],
"the same packet must decode identically whatever the buffer size"
);
let mut tiny = vec![0.0f32; frame / 2];
assert!(matches!(
OpusDecoder::new(rate, 1)
.unwrap()
.decode(&pkt, frame / 2, &mut tiny),
Err(Error::BufferTooSmall { .. })
));
assert!(matches!(
OpusDecoder::new(rate, 1)
.unwrap()
.decode(&pkt, frame * 6, &mut vec![0.0f32; frame]),
Err(Error::BufferTooSmall { .. })
));
}
#[test]
fn public_types_render_through_debug() {
use opus_pure::{
ChannelLayout, OggOpusReader, OggOpusWriter, OpusEncoder, OpusHead, ParallelConfig,
Repacketizer,
};
let encoder = OpusEncoder::new(48_000, 2, Application::Audio).unwrap();
let rendered = format!("{encoder:?}");
assert!(rendered.starts_with("OpusEncoder {"), "{rendered}");
assert!(rendered.contains("bitrate_bps"), "{rendered}");
assert!(rendered.contains("application: Audio"), "{rendered}");
assert!(rendered.ends_with(".. }"), "{rendered}");
assert!(rendered.len() < 400, "Debug dumped codec state: {rendered}");
let decoder = OpusDecoder::new(48_000, 2).unwrap();
let rendered = format!("{decoder:?}");
assert!(rendered.starts_with("OpusDecoder {"), "{rendered}");
assert!(rendered.contains("final_range"), "{rendered}");
assert!(rendered.len() < 400, "Debug dumped codec state: {rendered}");
let layout = ChannelLayout::surround(6, 1).unwrap();
let rendered = format!("{layout:?}");
assert!(rendered.contains("nb_coupled_streams"), "{rendered}");
let ms_enc = OpusMSEncoder::new(48_000, 6, 1, Application::Audio).unwrap();
assert!(format!("{ms_enc:?}").starts_with("OpusMSEncoder {"));
let ms_dec = OpusMSDecoder::new(48_000, 6, 1).unwrap();
assert!(format!("{ms_dec:?}").starts_with("OpusMSDecoder {"));
let config = ParallelConfig::new(48_000, 2, Application::Audio);
assert!(format!("{config:?}").contains("warmup_ms"));
let rendered = format!("{:?}", Repacketizer::new());
assert!(rendered.contains("nb_frames: 0"), "{rendered}");
let mut writer = OggOpusWriter::new(Vec::new(), OpusHead::new(2, 48_000).unwrap()).unwrap();
let rendered = format!("{writer:?}");
assert!(rendered.starts_with("OggOpusWriter {"), "{rendered}");
assert!(rendered.contains("granule: 0"), "{rendered}");
let mut encoder = OpusEncoder::new(48_000, 2, Application::Audio).unwrap();
let mut packet = vec![0u8; 4000];
let n = encoder
.encode(&vec![0.0f32; 960 * 2], 960, &mut packet)
.unwrap();
writer.write_packet(&packet[..n]).unwrap();
let file = writer.finish().unwrap();
let reader = OggOpusReader::new(std::io::Cursor::new(file)).unwrap();
let rendered = format!("{reader:?}");
assert!(rendered.starts_with("OggOpusReader {"), "{rendered}");
assert!(rendered.contains("channel_count: 2"), "{rendered}");
}
#[test]
fn container_debug_does_not_require_a_debug_source() {
use opus_pure::{OggOpusWriter, OpusHead};
struct NotDebug(Vec<u8>);
impl std::io::Write for NotDebug {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.0.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
let writer =
OggOpusWriter::new(NotDebug(Vec::new()), OpusHead::new(1, 48_000).unwrap()).unwrap();
assert!(format!("{writer:?}").starts_with("OggOpusWriter {"));
}
#[test]
fn multistream_bitrate_reaches_the_streams() {
const CH: usize = 6;
let pcm = interleave(
&(0..CH)
.map(|c| {
music_like(48_000, 960)
.iter()
.map(|s| s * (1.0 - c as f32 * 0.05))
.collect::<Vec<_>>()
})
.collect::<Vec<_>>(),
);
let sizes = |total: i32| -> usize {
let mut enc = OpusMSEncoder::new(48_000, CH, 1, Application::Audio).unwrap();
enc.set_bitrate(total);
let mut pkt = vec![0u8; 16_000];
let _ = enc.encode(&pcm, 960, &mut pkt).unwrap();
enc.encode(&pcm, 960, &mut pkt).unwrap()
};
let low = sizes(96_000);
let high = sizes(384_000);
assert!(
high > low * 2,
"384 kb/s produced {high} bytes and 96 kb/s produced {low}: the total \
bitrate is not reaching the per-stream encoders"
);
let mut enc = OpusMSEncoder::new(48_000, CH, 1, Application::Audio).unwrap();
enc.set_bitrate(96_000);
assert_eq!(
enc.bitrate_bps(),
96_000,
"the getter disagrees with the setter"
);
}
#[test]
fn multistream_exposes_every_stream_setting() {
let mut enc = OpusMSEncoder::new(48_000, 6, 1, Application::Audio).unwrap();
for e in enc.streams_mut() {
e.use_inband_fec = true;
e.packet_loss_perc = 10;
e.complexity = 3;
}
assert!(enc.streams().iter().all(|e| e.use_inband_fec));
assert_eq!(enc.streams().len(), enc.nb_streams());
}
#[test]
fn out_of_range_settings_are_clamped_not_obeyed() {
let pcm = speech_like(48_000, 960);
let mut pkt = vec![0u8; opus_pure::MAX_PACKET_BYTES];
let mut enc = OpusEncoder::new(48_000, 1, Application::Audio).unwrap();
enc.complexity = 100;
enc.packet_loss_perc = 250;
enc.lsb_depth = -1;
enc.bitrate_bps = i32::MAX;
enc.encode(&pcm, 960, &mut pkt).unwrap();
assert_eq!(enc.complexity, 10);
assert_eq!(enc.packet_loss_perc, 100);
assert_eq!(
enc.lsb_depth, 8,
"clamped up to the floor, not down from the top"
);
assert_eq!(
enc.bitrate_bps, 300_000,
"one channel, so 300 kb/s is the ceiling"
);
let mut enc = OpusEncoder::new(48_000, 2, Application::Audio).unwrap();
enc.complexity = -100;
enc.lsb_depth = 99;
enc.bitrate_bps = 1;
enc.encode(&speech_like(48_000, 1920), 960, &mut pkt)
.unwrap();
assert_eq!(enc.complexity, 0);
assert_eq!(enc.lsb_depth, 24);
assert_eq!(enc.bitrate_bps, 500, "the floor libopus uses");
}
#[test]
fn libopus_bitrate_sentinels_are_refused() {
let pcm = speech_like(48_000, 960);
let mut pkt = vec![0u8; 4000];
for sentinel in [-1000, -1, 0] {
let mut enc = OpusEncoder::new(48_000, 1, Application::Audio).unwrap();
enc.bitrate_bps = sentinel;
match enc.encode(&pcm, 960, &mut pkt) {
Err(Error::InvalidArgument(m)) => {
assert!(m.contains("bitrate_bps"), "unhelpful message: {m}")
}
other => panic!("bitrate_bps = {sentinel} gave {other:?}"),
}
}
}
#[test]
fn parallel_encode_reports_errors_rather_than_panicking() {
use opus_pure::{ParallelConfig, encode_parallel};
let cfg = ParallelConfig::new(48_000, 1, Application::Audio);
let pcm = speech_like(48_000, 48_000);
match encode_parallel(&cfg, &pcm, 333) {
Err(Error::InvalidArgument(_)) => {}
other => panic!("expected InvalidArgument, got {:?}", other.map(|v| v.len())),
}
match encode_parallel(&cfg, &speech_like(48_000, 960), 333) {
Err(Error::InvalidArgument(_)) => {}
other => panic!(
"serial path: expected InvalidArgument, got {:?}",
other.map(|v| v.len())
),
}
}
#[test]
fn header_pre_skip_follows_the_encoder_not_a_constant() {
use opus_pure::OpusHead;
for (app, expected) in [
(Application::Audio, 312u16),
(Application::Voip, 312),
(Application::RestrictedLowDelay, 120),
] {
let enc = OpusEncoder::new(48_000, 2, app).unwrap();
let head = OpusHead::for_encoder(&enc, 48_000);
assert_eq!(head.pre_skip, expected, "{app:?}");
assert_eq!(head.pre_skip as usize, enc.lookahead(), "{app:?} at 48 kHz");
}
let enc = OpusEncoder::new(16_000, 1, Application::Audio).unwrap();
assert_eq!(OpusHead::for_encoder(&enc, 16_000).pre_skip, 312);
assert_eq!(enc.lookahead(), 104, "312 at 48 kHz is 104 at 16 kHz");
}
#[test]
fn a_decoder_built_from_the_header_carries_its_gain() {
use opus_pure::OpusHead;
let mut head = OpusHead::new(2, 48_000).unwrap();
head.output_gain_q8 = -1536; let decoder = head.decoder(24_000).unwrap();
assert_eq!(decoder.channels(), 2);
assert_eq!(decoder.sample_rate(), 24_000);
assert_eq!(decoder.gain_q8, -1536);
assert!((head.output_gain_db() - -6.0).abs() < 1e-3);
let surround = OpusHead::for_layout(&opus_pure::ChannelLayout::surround(6, 1).unwrap(), 48_000);
assert!(matches!(
surround.decoder(48_000),
Err(Error::InvalidArgument(_))
));
assert!(matches!(
head.decoder(44_100),
Err(Error::InvalidArgument(_))
));
}
#[test]
fn the_decode_buffer_size_is_a_constant_not_a_magic_number() {
use opus_pure::{MAX_PACKET_SAMPLES, packet};
assert_eq!(MAX_PACKET_SAMPLES, 5760);
let mut enc = OpusEncoder::new(48_000, 2, Application::Audio).unwrap();
let mut pkt = vec![0u8; opus_pure::MAX_PACKET_BYTES];
let n = enc
.encode(&music_like(48_000, 5760 * 2), 5760, &mut pkt)
.unwrap();
assert_eq!(packet::samples(&pkt[..n], 48_000).unwrap(), 5760);
let mut dec = OpusDecoder::new(48_000, 2).unwrap();
let mut out = vec![0.0f32; MAX_PACKET_SAMPLES * 2];
assert_eq!(
dec.decode(&pkt[..n], MAX_PACKET_SAMPLES, &mut out).unwrap(),
5760
);
}
#[test]
fn surround_header_comes_from_the_encoder() {
use opus_pure::{ChannelLayout, OpusHead};
let enc = OpusMSEncoder::new(48_000, 6, 1, Application::Audio).unwrap();
let head = OpusHead::for_ms_encoder(&enc, 48_000);
let layout = ChannelLayout::surround(6, 1).unwrap();
assert_eq!(head.channel_count, 6);
assert_eq!(head.mapping_family, 1);
assert_eq!(head.stream_count as usize, layout.nb_streams);
assert_eq!(head.coupled_count as usize, layout.nb_coupled_streams);
assert_eq!(head.channel_mapping, layout.mapping);
assert!(
OpusHead::for_layout(&ChannelLayout::surround(2, 0).unwrap(), 48_000)
.channel_mapping
.is_empty()
);
}
#[test]
fn rate_control_selects_three_distinct_behaviours() {
let pcm = interleave(&[music_like(48_000, 960 * 8), speech_like(48_000, 960 * 8)]);
let run = |rc: RateControl| -> Vec<usize> {
let mut enc = OpusEncoder::new(48_000, 2, Application::Audio).unwrap();
enc.bitrate_bps = 64_000;
enc.rate_control = rc;
let mut pkt = vec![0u8; opus_pure::MAX_PACKET_BYTES];
pcm.chunks_exact(frame_20ms() * 2)
.map(|c| enc.encode(c, 960, &mut pkt).unwrap())
.collect()
};
let cbr = run(RateControl::Cbr);
let constrained = run(RateControl::ConstrainedVbr);
let free = run(RateControl::Vbr);
assert!(
cbr.windows(2).all(|w| w[0] == w[1]),
"CBR packets vary in size: {cbr:?}"
);
assert!(
constrained.windows(2).any(|w| w[0] != w[1]),
"constrained VBR produced fixed-size packets"
);
assert_ne!(
constrained, free,
"Vbr and ConstrainedVbr produced identical output, so the constraint is \
not reaching the CELT layer"
);
let default = OpusEncoder::new(48_000, 2, Application::Audio).unwrap();
assert_eq!(default.rate_control, RateControl::ConstrainedVbr);
}
#[test]
fn reset_state_returns_a_codec_to_its_starting_point() {
let a = speech_like(48_000, 960 * 4);
let b = music_like(48_000, 960 * 4);
let encode_all = |enc: &mut OpusEncoder, pcm: &[f32]| -> Vec<Vec<u8>> {
let mut pkt = vec![0u8; opus_pure::MAX_PACKET_BYTES];
pcm.chunks_exact(frame_20ms())
.map(|c| {
let n = enc.encode(c, 960, &mut pkt).unwrap();
pkt[..n].to_vec()
})
.collect()
};
let mut fresh = OpusEncoder::new(48_000, 1, Application::Audio).unwrap();
let want = encode_all(&mut fresh, &b);
let mut reused = OpusEncoder::new(48_000, 1, Application::Audio).unwrap();
let _ = encode_all(&mut reused, &a);
reused.reset_state().unwrap();
let got = encode_all(&mut reused, &b);
assert_eq!(got, want, "a reset encoder did not encode like a new one");
let mut enc = OpusEncoder::new(48_000, 1, Application::Voip).unwrap();
enc.bitrate_bps = 24_000;
enc.complexity = 4;
enc.use_inband_fec = true;
enc.reset_state().unwrap();
assert_eq!(enc.bitrate_bps, 24_000);
assert_eq!(enc.complexity, 4);
assert!(enc.use_inband_fec);
assert_eq!(enc.application(), Application::Voip);
let mut dec = OpusDecoder::new(48_000, 2).unwrap();
dec.gain_q8 = 256;
let [celt, ..] = packets_of_every_mode();
let mut pcm = vec![0.0f32; 960 * 2];
dec.decode(&celt[0], 960, &mut pcm).unwrap();
assert_ne!(dec.final_range(), 0);
assert_ne!(dec.last_packet_duration(), 0);
dec.reset_state().unwrap();
assert_eq!(dec.gain_q8, 256, "the gain is a setting, not coding state");
assert_eq!(dec.final_range(), 0);
assert_eq!(dec.last_packet_duration(), 0);
}
fn packets_of_every_mode() -> [Vec<Vec<u8>>; 3] {
let run = |rate: i32, channels: usize, bitrate: i32, app: Application, pcm: Vec<f32>| {
let mut enc = OpusEncoder::new(rate, channels, app).unwrap();
enc.bitrate_bps = bitrate;
let mut pkt = vec![0u8; opus_pure::MAX_PACKET_BYTES];
let frame = rate as usize / 50;
pcm.chunks_exact(frame * channels)
.map(|c| {
let n = enc.encode(c, frame, &mut pkt).unwrap();
pkt[..n].to_vec()
})
.collect::<Vec<_>>()
};
let n = 960 * 12;
[
run(
48_000,
2,
128_000,
Application::Audio,
interleave(&[music_like(48_000, n), speech_like(48_000, n)]),
),
run(
16_000,
1,
12_000,
Application::Voip,
speech_like(16_000, 320 * 12),
),
run(
48_000,
1,
24_000,
Application::Audio,
speech_like(48_000, n),
),
]
}
#[test]
fn a_reset_decoder_decodes_like_a_new_one() {
let modes = packets_of_every_mode();
for (rate, channels) in [
(48_000, 2),
(48_000, 1),
(24_000, 2),
(16_000, 1),
(8_000, 2),
] {
let frame_size = rate as usize * 120 / 1000;
let mut float = vec![0.0f32; frame_size * channels];
let mut s16 = vec![0i16; frame_size * channels];
let play = |dec: &mut OpusDecoder, packets: &[Option<&[u8]>], as_s16: bool| {
let mut float = vec![0.0f32; frame_size * channels];
let mut s16 = vec![0i16; frame_size * channels];
let mut out = Vec::new();
for p in packets {
let p = p.unwrap_or(&[]);
if as_s16 {
let n = dec.decode_s16(p, frame_size, &mut s16).unwrap();
out.extend(s16[..n * channels].iter().map(|&s| s as u32));
} else {
let n = dec.decode(p, frame_size, &mut float).unwrap();
out.extend(float[..n * channels].iter().map(|s| s.to_bits()));
}
}
out
};
for first in 0..modes.len() {
let mut after: Vec<Option<&[u8]>> = Vec::new();
for m in [first, (first + 1) % 3, (first + 2) % 3, first] {
after.extend(modes[m][..3].iter().map(|p| Some(p.as_slice())));
}
for lead_loss in [false, true] {
let after = if lead_loss {
[&[None][..], &after].concat()
} else {
after.clone()
};
for as_s16 in [false, true] {
let want = play(
&mut OpusDecoder::new(rate, channels).unwrap(),
&after,
as_s16,
);
assert!(want.iter().any(|&s| s != 0));
for last in 0..modes.len() {
let mut used = OpusDecoder::new(rate, channels).unwrap();
for m in [(last + 1) % 3, (last + 2) % 3, last] {
for (i, p) in modes[m][3..9].iter().enumerate() {
match i {
1 => used.decode(&[], frame_size, &mut float),
2 => used.decode_fec(p, frame_size, &mut float),
_ => used.decode(p, frame_size, &mut float),
}
.unwrap();
}
}
used.gain_q8 = 256 * 24;
for p in &modes[last][9..] {
used.decode_s16(p, frame_size, &mut s16).unwrap();
}
used.gain_q8 = 0;
used.reset_state().unwrap();
let got = play(&mut used, &after, as_s16);
assert!(
got == want,
"{rate} Hz x{channels}: last decoded mode {last}, then mode {first} \
first{} on the {} path, decoded differently after a reset",
if lead_loss {
" behind a lost packet"
} else {
""
},
if as_s16 { "16-bit" } else { "float" },
);
}
}
}
}
}
}
#[test]
fn decoder_applies_output_gain() {
let pcm = speech_like(48_000, 960 * 3);
let mut enc = OpusEncoder::new(48_000, 1, Application::Audio).unwrap();
let mut pkt = vec![0u8; opus_pure::MAX_PACKET_BYTES];
let packets: Vec<Vec<u8>> = pcm
.chunks_exact(frame_20ms())
.map(|c| {
let n = enc.encode(c, 960, &mut pkt).unwrap();
pkt[..n].to_vec()
})
.collect();
let rms = |gain_q8: i32| -> f64 {
let mut dec = OpusDecoder::new(48_000, 1).unwrap();
dec.gain_q8 = gain_q8;
let mut out = vec![0.0f32; 960];
let mut sum = 0.0f64;
let mut n = 0usize;
for p in &packets {
let got = dec.decode(p, 960, &mut out).unwrap();
sum += out[..got].iter().map(|&s| (s as f64).powi(2)).sum::<f64>();
n += got;
}
(sum / n as f64).sqrt()
};
let unity = rms(0);
let doubled = rms((6.0206 * 256.0) as i32);
assert!(
(doubled / unity - 2.0).abs() < 0.02,
"+6 dB of gain scaled the output by {:.3}, not 2.0",
doubled / unity
);
}
#[test]
fn packet_reports_its_mode_and_bandwidth() {
use opus_pure::{OpusMode, packet};
let mut enc = OpusEncoder::new(48_000, 1, Application::Voip).unwrap();
enc.bitrate_bps = 12_000;
enc.force_bandwidth = Some(Bandwidth::Wideband);
let mut pkt = vec![0u8; 4000];
let n = enc
.encode(&speech_like(48_000, 960), 960, &mut pkt)
.unwrap();
assert_eq!(packet::mode(&pkt[..n]).unwrap(), OpusMode::SilkOnly);
assert_eq!(packet::bandwidth(&pkt[..n]).unwrap(), Bandwidth::Wideband);
let mut enc = OpusEncoder::new(48_000, 2, Application::Audio).unwrap();
enc.bitrate_bps = 256_000;
let n = enc
.encode(
&interleave(&[music_like(48_000, 960), music_like(48_000, 960)]),
960,
&mut pkt,
)
.unwrap();
assert_eq!(packet::mode(&pkt[..n]).unwrap(), OpusMode::CeltOnly);
assert_eq!(packet::bandwidth(&pkt[..n]).unwrap(), Bandwidth::Fullband);
assert!(packet::mode(&[]).is_err());
assert!(packet::bandwidth(&[]).is_err());
}
#[test]
fn tags_append_and_read_back_every_value() {
use opus_pure::OpusTags;
let mut tags = OpusTags::new();
tags.push("ARTIST", "First").unwrap();
tags.push("ARTIST", "Second").unwrap();
tags.push("TITLE", "Only").unwrap();
assert_eq!(tags.get("ARTIST"), Some("First"));
assert_eq!(
tags.get_all("ARTIST").collect::<Vec<_>>(),
vec!["First", "Second"]
);
assert_eq!(
tags.get_all("artist").count(),
2,
"names are case-insensitive"
);
assert_eq!(tags.get_all("MISSING").count(), 0);
assert_eq!(tags.get("TITLE"), Some("Only"));
}
#[test]
fn writer_derives_the_granule_from_the_packet() {
use opus_pure::{OggOpusReader, OggOpusWriter, OpusHead, packet};
for frame in [120usize, 960, 2880] {
let mut enc = OpusEncoder::new(48_000, 1, Application::Audio).unwrap();
let mut pkt = vec![0u8; opus_pure::MAX_PACKET_BYTES];
let head = OpusHead::for_encoder(&enc, 48_000);
let mut w = OggOpusWriter::new(Vec::new(), head).unwrap();
let pcm = speech_like(48_000, frame * 4);
let mut expected = 0u64;
for c in pcm.chunks_exact(frame) {
let n = enc.encode(c, frame, &mut pkt).unwrap();
expected += packet::samples_48k(&pkt[..n]).unwrap() as u64;
w.write_packet(&pkt[..n]).unwrap();
}
let file = w.finish().unwrap();
let mut r = OggOpusReader::new(std::io::Cursor::new(&file)).unwrap();
let last = r.packets().last().unwrap().unwrap();
assert_eq!(
last.page_granule as u64, expected,
"frame {frame}: the granule does not match the audio written"
);
}
}
#[test]
fn reader_iterates_and_stops_on_error() {
use opus_pure::{OggOpusReader, OggOpusWriter, OpusHead};
let mut enc = OpusEncoder::new(48_000, 1, Application::Audio).unwrap();
let mut pkt = vec![0u8; opus_pure::MAX_PACKET_BYTES];
let mut w = OggOpusWriter::new(Vec::new(), OpusHead::for_encoder(&enc, 48_000)).unwrap();
for c in speech_like(48_000, 960 * 6).chunks_exact(frame_20ms()) {
let n = enc.encode(c, 960, &mut pkt).unwrap();
w.write_packet(&pkt[..n]).unwrap();
}
let file = w.finish().unwrap();
let mut r = OggOpusReader::new(std::io::Cursor::new(&file)).unwrap();
let packets = r.packets().collect::<Result<Vec<_>, _>>().unwrap();
assert_eq!(packets.len(), 6);
let truncated = &file[..file.len() * 2 / 3];
let mut r = OggOpusReader::new(std::io::Cursor::new(truncated)).unwrap();
let mut saw_error = false;
let mut count = 0;
for item in r.packets() {
count += 1;
if item.is_err() {
saw_error = true;
}
assert!(count < 1000, "the iterator did not terminate");
}
assert!(saw_error || count > 0);
}
#[test]
fn repacketizer_is_reusable_and_can_append() {
use opus_pure::Repacketizer;
let mut enc = OpusEncoder::new(48_000, 1, Application::Audio).unwrap();
let mut pkt = vec![0u8; opus_pure::MAX_PACKET_BYTES];
let packets: Vec<Vec<u8>> = speech_like(48_000, 960 * 4)
.chunks_exact(frame_20ms())
.map(|c| {
let n = enc.encode(c, 960, &mut pkt).unwrap();
pkt[..n].to_vec()
})
.collect();
let mut rp = Repacketizer::new();
let mut owned = Vec::new();
for p in &packets[..3] {
rp.cat(p).unwrap();
}
let merged = rp.out().unwrap();
rp.out_into(&mut owned).unwrap();
assert_eq!(merged, owned, "out_into disagrees with out");
rp.clear();
assert_eq!(rp.nb_frames(), 0);
for p in &packets[..2] {
rp.cat(p).unwrap();
}
let after_clear = rp.out().unwrap();
let mut fresh = Repacketizer::new();
for p in &packets[..2] {
fresh.cat(p).unwrap();
}
assert_eq!(after_clear, fresh.out().unwrap());
let mut two = Vec::new();
rp.out_into(&mut two).unwrap();
let one_len = two.len();
rp.out_into(&mut two).unwrap();
assert_eq!(
two.len(),
one_len * 2,
"out_into overwrote instead of appending"
);
}
#[test]
fn codecs_report_how_they_were_built() {
let enc = OpusEncoder::new(24_000, 2, Application::Voip).unwrap();
assert_eq!(enc.sample_rate(), 24_000);
assert_eq!(enc.channels(), 2);
assert_eq!(enc.application(), Application::Voip);
let dec = OpusDecoder::new(16_000, 1).unwrap();
assert_eq!(dec.sample_rate(), 16_000);
assert_eq!(dec.channels(), 1);
assert_eq!(dec.last_packet_duration(), 0);
let ms = OpusMSEncoder::new(48_000, 6, 1, Application::Audio).unwrap();
assert_eq!(ms.sample_rate(), 48_000);
assert_eq!(ms.channels(), 6);
assert_eq!(ms.layout().nb_channels, 6);
let msd = OpusMSDecoder::new(48_000, 6, 1).unwrap();
assert_eq!(msd.sample_rate(), 48_000);
assert_eq!(msd.channels(), 6);
assert_eq!(msd.nb_streams(), 4);
}
#[test]
fn decoder_reports_the_last_packet_duration() {
let mut enc = OpusEncoder::new(48_000, 1, Application::Audio).unwrap();
let mut dec = OpusDecoder::new(48_000, 1).unwrap();
let mut pkt = vec![0u8; opus_pure::MAX_PACKET_BYTES];
let mut out = vec![0.0f32; 5760];
for frame in [480usize, 960, 1920] {
let n = enc
.encode(&speech_like(48_000, frame), frame, &mut pkt)
.unwrap();
dec.decode(&pkt[..n], 5760, &mut out).unwrap();
assert_eq!(dec.last_packet_duration(), frame);
}
}