#![cfg(test)]
#![allow(
clippy::unwrap_used,
clippy::expect_used,
reason = "test modules may unwrap"
)]
use super::*;
use ebml_webm::TrackInfo as CoreTrackInfo;
fn track(codec_id: &str, is_video: bool, width: u32, height: u32) -> CoreTrackInfo {
CoreTrackInfo {
track_number: 1,
track_type: if is_video { 1 } else { 2 },
codec_id: codec_id.to_string(),
codec_private: None,
width,
height,
sample_rate: 8000.0,
channels: 1,
}
}
#[test]
fn codec_kind_maps_supported_codecs() {
assert_eq!(codec_kind("V_VP9"), Some(CodecKind::Vp9));
assert_eq!(codec_kind("V_AV1"), Some(CodecKind::Av1));
assert_eq!(codec_kind("A_OPUS"), Some(CodecKind::Opus));
assert_eq!(codec_kind("A_VORBIS"), Some(CodecKind::Vorbis));
assert_eq!(codec_kind("A_AAC"), Some(CodecKind::Aac));
assert_eq!(codec_kind("A_AAC/MPEG4/LC"), Some(CodecKind::Aac));
}
#[test]
fn codec_kind_none_for_unmapped_webm_codecs() {
assert_eq!(codec_kind("V_VP8"), None);
}
#[test]
fn track_id_truncates_out_of_range_track_number() {
assert_eq!(track_id(1), 1);
assert_eq!(track_id(u64::from(u32::MAX)), u32::MAX);
assert_eq!(track_id(u64::from(u32::MAX) + 1), u32::MAX);
}
#[test]
fn to_stream_info_none_for_unsupported_codec() {
let t = track("V_VP8", true, 1280, 720);
assert_eq!(to_stream_info(&t, MwRational::new(1, 1000)), None);
}
#[test]
fn to_stream_info_maps_video_geometry() {
let t = track("V_VP9", true, 1280, 720);
let info = to_stream_info(&t, MwRational::new(1, 1000)).expect("supported codec");
assert_eq!(info.id(), 1);
assert_eq!(info.codec(), CodecKind::Vp9);
assert_eq!(
info.geometry(),
Some(VideoGeometry {
width: 1280,
height: 720
})
);
}
#[test]
fn to_stream_info_non_video_has_no_geometry() {
let t = track("A_OPUS", false, 0, 0);
let info = to_stream_info(&t, MwRational::new(1, 1000)).expect("supported codec");
assert_eq!(info.geometry(), None);
}
#[test]
fn to_stream_info_threads_real_sample_rate_and_channels() {
let mut t = track("A_OPUS", false, 0, 0);
t.sample_rate = 48_000.0;
t.channels = 2;
let info = to_stream_info(&t, MwRational::new(1, 1000)).expect("supported codec");
assert_eq!(info.sample_rate(), Some(48_000));
assert_eq!(info.channels(), Some(2));
}
#[test]
fn sample_rate_u32_saturates_out_of_range_and_non_finite() {
assert_eq!(sample_rate_u32(48_000.0), 48_000);
assert_eq!(sample_rate_u32(-1.0), 0);
assert_eq!(sample_rate_u32(f64::NAN), 0);
assert_eq!(sample_rate_u32(f64::INFINITY), 0);
}
#[test]
fn channels_u16_saturates_out_of_range() {
assert_eq!(channels_u16(2), 2);
assert_eq!(channels_u16(0), 0);
assert_eq!(channels_u16(u32::from(u16::MAX) + 1), u16::MAX);
}
#[allow(
clippy::cast_possible_truncation,
reason = "len is bounded to 1..=8 by the loop range, always fits usize"
)]
fn enc_size(value: u64) -> Vec<u8> {
for len in 1u64..=8 {
let data_bits = 7 * len;
let max = (1u64 << data_bits) - 2;
if value <= max {
let full = value | (1u64 << data_bits);
let be = full.to_be_bytes();
return be[8 - len as usize..].to_vec();
}
}
Vec::new()
}
fn elem(id: &[u8], payload: &[u8]) -> Vec<u8> {
let mut out = Vec::new();
out.extend_from_slice(id);
out.extend_from_slice(&enc_size(payload.len() as u64));
out.extend_from_slice(payload);
out
}
fn uint_payload(v: u64, len: usize) -> Vec<u8> {
let be = v.to_be_bytes();
be[8 - len..].to_vec()
}
fn build_webm_with_cluster_child(cluster_child: &[u8]) -> Vec<u8> {
let tracks = {
let num = elem(&[0xD7], &uint_payload(1, 1)); let typ = elem(&[0x83], &uint_payload(1, 1)); let codec = elem(&[0x86], b"V_VP9"); let mut body = Vec::new();
body.extend_from_slice(&num);
body.extend_from_slice(&typ);
body.extend_from_slice(&codec);
let track_entry = elem(&[0xAE], &body); elem(&[0x16, 0x54, 0xAE, 0x6B], &track_entry) };
let info = elem(&[0x2A, 0xD7, 0xB1], &uint_payload(1_000_000, 3)); let cluster = {
let timecode = elem(&[0xE7], &uint_payload(0, 1)); let mut body = Vec::new();
body.extend_from_slice(&timecode);
body.extend_from_slice(cluster_child);
elem(&[0x1F, 0x43, 0xB6, 0x75], &body) };
let mut segment_body = Vec::new();
segment_body.extend_from_slice(&info);
segment_body.extend_from_slice(&tracks);
segment_body.extend_from_slice(&cluster);
let segment = elem(&[0x18, 0x53, 0x80, 0x67], &segment_body); let header = elem(&[0x1A, 0x45, 0xDF, 0xA3], &[]);
let mut out = Vec::new();
out.extend_from_slice(&header);
out.extend_from_slice(&segment);
out
}
#[test]
fn poll_packet_wires_block_group_duration() {
let block = {
let mut body = Vec::new();
body.push(0x81); body.extend_from_slice(&0i16.to_be_bytes());
body.push(0x00); body.extend_from_slice(&[9, 9, 9]);
elem(&[0xA1], &body) };
let duration = elem(&[0x9B], &uint_payload(33, 1)); let mut group_body = Vec::new();
group_body.extend_from_slice(&block);
group_body.extend_from_slice(&duration);
let block_group = elem(&[0xA0], &group_body);
let bytes = build_webm_with_cluster_child(&block_group);
let mut d = Demuxer::new();
d.push_bytes(&bytes);
let packet = d.poll_packet().expect("BlockGroup packet");
assert_eq!(&packet.payload[..], &[9, 9, 9]);
assert_eq!(packet.duration, 33);
}
#[test]
fn cues_and_seek_head_pass_through_from_core() {
let cue_point = {
let time = elem(&[0xB3], &uint_payload(5, 1)); let track_positions = {
let track = elem(&[0xF7], &uint_payload(1, 1)); let pos = elem(&[0xF1], &uint_payload(1234, 2)); let mut body = Vec::new();
body.extend_from_slice(&track);
body.extend_from_slice(&pos);
elem(&[0xB7], &body) };
let mut body = Vec::new();
body.extend_from_slice(&time);
body.extend_from_slice(&track_positions);
elem(&[0xBB], &body) };
let cues = elem(&[0x1C, 0x53, 0xBB, 0x6B], &cue_point);
let seek = {
let seek_id = elem(&[0x53, 0xAB], &[0x16, 0x54, 0xAE, 0x6B]); let seek_position = elem(&[0x53, 0xAC], &uint_payload(42, 1)); let mut body = Vec::new();
body.extend_from_slice(&seek_id);
body.extend_from_slice(&seek_position);
elem(&[0x4D, 0xBB], &body) };
let seek_head = elem(&[0x11, 0x4D, 0x9B, 0x74], &seek);
let mut segment_body = Vec::new();
segment_body.extend_from_slice(&seek_head);
segment_body.extend_from_slice(&cues);
let segment = elem(&[0x18, 0x53, 0x80, 0x67], &segment_body); let header = elem(&[0x1A, 0x45, 0xDF, 0xA3], &[]); let mut bytes = Vec::new();
bytes.extend_from_slice(&header);
bytes.extend_from_slice(&segment);
let mut d = Demuxer::new();
d.push_bytes(&bytes);
assert_eq!(d.cues().len(), 1);
assert_eq!(d.cues()[0].time_ticks, 5);
assert_eq!(d.cues()[0].cluster_position, 1234);
assert_eq!(d.seek_head().len(), 1);
assert_eq!(d.seek_head()[0].id, ebml_webm::ids::TRACKS);
assert_eq!(d.seek_head()[0].position, 42);
}
#[cfg(feature = "mux")]
fn video_stream(id: u32) -> StreamInfo {
StreamInfo::Video {
id,
codec: CodecKind::Vp9,
time_base: MwRational::new(1, 1000),
geometry: VideoGeometry {
width: 640,
height: 480,
},
extra_data: Bytes::new(),
}
}
#[cfg(feature = "mux")]
fn audio_stream(id: u32) -> StreamInfo {
StreamInfo::Audio {
id,
codec: CodecKind::Opus,
time_base: MwRational::new(1, 1000),
extra_data: Bytes::new(),
sample_rate: 48_000,
channels: 2,
}
}
#[cfg(feature = "mux")]
#[test]
fn add_track_rejects_unsupported_codec() {
let mut m = Muxer::<Open>::new();
let unsupported = StreamInfo::Video {
id: 0,
codec: CodecKind::H264,
time_base: MwRational::new(1, 1000),
geometry: VideoGeometry {
width: 640,
height: 480,
},
extra_data: Bytes::new(),
};
assert_eq!(
m.add_track(unsupported),
Err(Error::UnsupportedCodec(CodecKind::H264))
);
}
#[cfg(feature = "mux")]
#[test]
fn add_track_rejects_stream_id_zero() {
let mut m = Muxer::<Open>::new();
assert_eq!(
m.add_track(video_stream(0)),
Err(Error::Mux(ebml_webm::MuxError::InvalidTrackNumber))
);
}
#[cfg(feature = "mux")]
#[test]
fn push_packet_rejects_unregistered_stream() {
let mut m = Muxer::<Open>::new();
m.add_track(video_stream(1)).unwrap();
let mut live = m.begin();
let packet = Packet {
stream_id: 99,
pts: 0,
dts: 0,
duration: 0,
is_keyframe: true,
is_discard: false,
payload: Bytes::from_static(b"x"),
};
assert_eq!(live.push_packet(&packet), Err(Error::UnknownStream(99)));
}
#[cfg(feature = "mux")]
#[test]
fn mux_video_audio_round_trips_through_demuxer() {
let mut m = Muxer::<Open>::new();
let v_id = m.add_track(video_stream(1)).unwrap();
let a_id = m.add_track(audio_stream(2)).unwrap();
let mut live = m.begin();
let mk = |stream_id: u32, pts: i64, keyframe: bool, data: &'static [u8]| Packet {
stream_id,
pts,
dts: pts,
duration: 0,
is_keyframe: keyframe,
is_discard: false,
payload: Bytes::from_static(data),
};
live.push_packet(&mk(v_id, 0, true, b"vframe0")).unwrap();
live.push_packet(&mk(a_id, 0, true, b"aframe0")).unwrap();
live.push_packet(&mk(v_id, 33, false, b"vframe1")).unwrap();
live.flush();
let mut bytes = Vec::new();
live.poll_bytes(&mut bytes);
assert!(!bytes.is_empty());
let mut demux = Demuxer::new();
demux.push_bytes(&bytes);
let streams = demux.streams();
assert_eq!(streams.len(), 2);
assert!(streams.iter().any(|s| s.codec() == CodecKind::Vp9));
assert!(streams.iter().any(|s| s.codec() == CodecKind::Opus));
let mut packets = Vec::new();
while let Some(p) = demux.poll_packet() {
packets.push(p);
}
assert_eq!(packets.len(), 3);
assert!(
packets
.iter()
.any(|p| p.stream_id == v_id && &p.payload[..] == b"vframe0" && p.is_keyframe)
);
assert!(
packets
.iter()
.any(|p| p.stream_id == a_id && &p.payload[..] == b"aframe0")
);
assert!(
packets
.iter()
.any(|p| p.stream_id == v_id && p.pts == 33 && &p.payload[..] == b"vframe1")
);
}
#[test]
fn poll_packet_defaults_duration_for_simple_block() {
let simple_block = {
let mut body = Vec::new();
body.push(0x81); body.extend_from_slice(&0i16.to_be_bytes());
body.push(0x80); body.extend_from_slice(&[1, 2, 3]);
elem(&[0xA3], &body) };
let bytes = build_webm_with_cluster_child(&simple_block);
let mut d = Demuxer::new();
d.push_bytes(&bytes);
let packet = d.poll_packet().expect("SimpleBlock packet");
assert_eq!(packet.duration, 0);
}