#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::print_stderr,
reason = "test modules may unwrap / print"
)]
#![allow(
clippy::similar_names,
reason = "first_packet_has_vps/sps/sequence_header read clearly side by side in these tests"
)]
use super::*;
fn h264_cfg(width: u32, height: u32) -> VideoEncoderConfig {
VideoEncoderConfig {
codec: CodecKind::H264,
width,
height,
time_base: Rational::new(1, 30),
bitrate_bps: 2_000_000,
pixel_format: PixelFormat::Nv12,
input: VideoInputPreference::CpuUploadOk,
gpu_device: None,
}
}
fn hevc_cfg(width: u32, height: u32) -> VideoEncoderConfig {
VideoEncoderConfig {
codec: CodecKind::Hevc,
..h264_cfg(width, height)
}
}
fn av1_cfg(width: u32, height: u32) -> VideoEncoderConfig {
VideoEncoderConfig {
codec: CodecKind::Av1,
..h264_cfg(width, height)
}
}
fn synthetic_nv12_frames(width: u32, height: u32) -> Vec<VideoFrame> {
let nv12_len = (width * height) as usize + (width * height) as usize / 2;
(0..5u8)
.map(|i| {
let mut data = vec![64u8 + i * 20; nv12_len];
for b in &mut data[(width * height) as usize..] {
*b = 128;
}
VideoFrame {
pts: i64::from(i),
duration: 1,
width,
height,
format: PixelFormat::Nv12,
storage: VideoFrameStorage::Cpu {
data: Bytes::from(data),
},
}
})
.collect()
}
#[test]
fn validate_accepts_even_h264_cpu_upload_config() {
assert!(validate(&h264_cfg(640, 480)).is_ok());
}
#[test]
fn validate_accepts_hevc_cpu_upload_config() {
let mut cfg = h264_cfg(640, 480);
cfg.codec = CodecKind::Hevc;
assert!(validate(&cfg).is_ok());
}
#[test]
fn validate_accepts_av1_cpu_upload_config() {
let mut cfg = h264_cfg(640, 480);
cfg.codec = CodecKind::Av1;
assert!(validate(&cfg).is_ok());
}
#[test]
fn validate_rejects_vp9_codec() {
let mut cfg = h264_cfg(640, 480);
cfg.codec = CodecKind::Vp9;
assert_eq!(validate(&cfg), Err(EncodeError::Unsupported));
}
#[test]
fn validate_rejects_zero_copy_gpu_input() {
let mut cfg = h264_cfg(640, 480);
cfg.input = VideoInputPreference::ZeroCopyGpu;
assert_eq!(validate(&cfg), Err(EncodeError::Unsupported));
}
#[test]
fn validate_rejects_zero_dimensions() {
let cfg = h264_cfg(0, 480);
assert_eq!(validate(&cfg), Err(EncodeError::InvalidInput));
}
#[test]
fn validate_rejects_odd_dimensions() {
let cfg = h264_cfg(641, 480);
assert_eq!(validate(&cfg), Err(EncodeError::InvalidInput));
}
#[test]
fn validate_rejects_non_nv12_pixel_format() {
let mut cfg = h264_cfg(640, 480);
cfg.pixel_format = PixelFormat::Bgra8;
assert_eq!(validate(&cfg), Err(EncodeError::Unsupported));
}
#[test]
fn validate_rejects_zero_timebase_denominator() {
let mut cfg = h264_cfg(640, 480);
cfg.time_base = Rational::new(1, 0);
assert_eq!(validate(&cfg), Err(EncodeError::InvalidInput));
}
#[test]
fn frame_rate_divides_den_by_num() {
assert_eq!(frame_rate(Rational::new(1, 30)), [30, 1]);
assert_eq!(frame_rate(Rational::new(1001, 30_000)), [30_000, 1001]);
}
#[test]
fn frame_rate_clamps_zero_numerator_to_one() {
assert_eq!(frame_rate(Rational::new(0, 60)), [60, 1]);
}
#[test]
fn contains_h264_idr_nal_finds_type_5_after_start_code() {
let data = [0x00, 0x00, 0x00, 0x01, 0x65, 0xAA, 0xBB];
assert!(contains_h264_idr_nal(&data));
}
#[test]
fn contains_h264_idr_nal_false_for_non_idr_slice() {
let data = [0x00, 0x00, 0x00, 0x01, 0x41, 0xAA, 0xBB];
assert!(!contains_h264_idr_nal(&data));
}
#[test]
fn contains_hevc_idr_nal_finds_idr_w_radl_after_start_code() {
let data = [0x00, 0x00, 0x00, 0x01, 0x26, 0x01, 0xAA, 0xBB];
assert!(contains_hevc_idr_nal(&data));
}
#[test]
fn contains_hevc_idr_nal_finds_idr_n_lp_after_start_code() {
let data = [0x00, 0x00, 0x00, 0x01, 0x28, 0x01, 0xAA, 0xBB];
assert!(contains_hevc_idr_nal(&data));
}
#[test]
fn contains_hevc_idr_nal_false_for_trail_r_slice() {
let data = [0x00, 0x00, 0x00, 0x01, 0x02, 0x01, 0xAA, 0xBB];
assert!(!contains_hevc_idr_nal(&data));
}
#[test]
fn read_leb128_decodes_single_byte_value() {
assert_eq!(read_leb128(&[0x05, 0xFF]), Some((5, 1)));
}
#[test]
fn read_leb128_decodes_multi_byte_value() {
assert_eq!(read_leb128(&[0xE5, 0x8E, 0x26]), Some((624_485, 3)));
}
#[test]
fn read_leb128_none_when_truncated() {
assert_eq!(read_leb128(&[0x80, 0x80, 0x80]), None);
}
#[test]
fn contains_av1_sequence_header_obu_finds_type_1() {
let data = [0x0A, 0x02, 0xAA, 0xBB];
assert!(contains_av1_sequence_header_obu(&data));
}
#[test]
fn contains_av1_sequence_header_obu_skips_temporal_delimiter_to_find_sequence_header() {
let data = [0x12, 0x00, 0x0A, 0x02, 0xAA, 0xBB];
assert!(contains_av1_sequence_header_obu(&data));
}
#[test]
fn contains_av1_sequence_header_obu_false_without_sequence_header() {
let data = [0x12, 0x00, 0x32, 0x02, 0xAA, 0xBB];
assert!(!contains_av1_sequence_header_obu(&data));
}
#[test]
fn stream_info_from_config_carries_geometry_and_timebase() {
let cfg = h264_cfg(640, 480);
let info = stream_info_from(&cfg);
assert!(matches!(
info,
StreamInfo::Video {
codec: CodecKind::H264,
..
}
));
if let StreamInfo::Video {
time_base,
geometry,
..
} = info
{
assert_eq!(time_base, cfg.time_base);
assert_eq!(geometry.width, 640);
assert_eq!(geometry.height, 480);
}
}
#[test]
fn nvenc_open_and_encode_or_skip_without_hw() {
const WIDTH: u32 = 640;
const HEIGHT: u32 = 480;
let cfg = h264_cfg(WIDTH, HEIGHT);
let mut enc = match NvencSession::open(&cfg) {
Ok(e) => e,
Err(e) => {
eprintln!("skip: NvencSession::open failed ({e:?}) — no NVENC-capable GPU/driver?");
return;
}
};
let nv12_len = (WIDTH * HEIGHT) as usize + (WIDTH * HEIGHT) as usize / 2;
let mut packets_emitted = 0usize;
let mut first_packet_keyframe = false;
let mut first_packet_has_sps = false;
for i in 0..5u8 {
let mut data = vec![64u8 + i * 20; nv12_len];
for b in &mut data[(WIDTH * HEIGHT) as usize..] {
*b = 128;
}
let frame = VideoFrame {
pts: i64::from(i),
duration: 1,
width: WIDTH,
height: HEIGHT,
format: PixelFormat::Nv12,
storage: VideoFrameStorage::Cpu {
data: Bytes::from(data),
},
};
if let Err(e) = enc.push_frame(&frame) {
eprintln!("skip: push_frame failed ({e:?}) — no usable NVENC session?");
return;
}
while let Ok(Some(p)) = enc.poll_packet() {
assert!(!p.payload.is_empty(), "packet {packets_emitted} is empty");
let has_start_code = p.payload.windows(4).any(|w| w == [0, 0, 0, 1]);
assert!(
has_start_code,
"packet {packets_emitted} has no Annex-B start code"
);
if packets_emitted == 0 {
first_packet_keyframe = p.is_keyframe;
first_packet_has_sps = p
.payload
.windows(5)
.any(|w| w[..4] == [0, 0, 0, 1] && (w[4] & 0x1F) == 7);
}
packets_emitted += 1;
}
}
let _ = enc.flush();
while let Ok(Some(p)) = enc.poll_packet() {
assert!(!p.payload.is_empty());
packets_emitted += 1;
}
assert!(packets_emitted >= 1, "expected at least one encoded packet");
assert!(
first_packet_keyframe,
"first packet should be an IDR keyframe"
);
assert!(
first_packet_has_sps,
"first packet should carry an inline SPS NAL"
);
eprintln!("nvenc h264 cpu-upload packets={packets_emitted}");
}
#[test]
fn nvenc_open_and_encode_hevc_or_skip_without_hw() {
const WIDTH: u32 = 640;
const HEIGHT: u32 = 480;
let cfg = hevc_cfg(WIDTH, HEIGHT);
let mut enc = match NvencSession::open(&cfg) {
Ok(e) => e,
Err(e) => {
eprintln!(
"skip: NvencSession::open (HEVC) failed ({e:?}) — no HEVC-capable NVENC GPU/driver?"
);
return;
}
};
let mut packets_emitted = 0usize;
let mut first_packet_keyframe = false;
let mut first_packet_has_vps = false;
let mut first_packet_has_sps = false;
let mut first_packet_dump = String::new();
for frame in synthetic_nv12_frames(WIDTH, HEIGHT) {
if let Err(e) = enc.push_frame(&frame) {
eprintln!("skip: push_frame (HEVC) failed ({e:?}) — no usable NVENC HEVC session?");
return;
}
while let Ok(Some(p)) = enc.poll_packet() {
assert!(!p.payload.is_empty(), "packet {packets_emitted} is empty");
let has_start_code = p.payload.windows(4).any(|w| w == [0, 0, 0, 1]);
assert!(
has_start_code,
"packet {packets_emitted} has no Annex-B start code"
);
if packets_emitted == 0 {
first_packet_keyframe = p.is_keyframe;
first_packet_has_vps = p
.payload
.windows(5)
.any(|w| w[..4] == [0, 0, 0, 1] && (w[4] >> 1) & 0x3F == 32);
first_packet_has_sps = p
.payload
.windows(5)
.any(|w| w[..4] == [0, 0, 0, 1] && (w[4] >> 1) & 0x3F == 33);
first_packet_dump = hex_dump(&p.payload[..p.payload.len().min(32)]);
}
packets_emitted += 1;
}
}
let _ = enc.flush();
while let Ok(Some(p)) = enc.poll_packet() {
assert!(!p.payload.is_empty());
packets_emitted += 1;
}
assert!(packets_emitted >= 1, "expected at least one encoded packet");
assert!(
first_packet_keyframe,
"first packet should be an IDR keyframe"
);
assert!(
first_packet_has_vps,
"first packet should carry an inline VPS NAL (type 32)"
);
assert!(
first_packet_has_sps,
"first packet should carry an inline SPS NAL (type 33)"
);
eprintln!(
"nvenc hevc cpu-upload packets={packets_emitted} first_packet_prefix={first_packet_dump}"
);
}
fn hex_dump(bytes: &[u8]) -> String {
bytes
.iter()
.map(|b| format!("{b:02x}"))
.collect::<Vec<_>>()
.join(" ")
}
#[test]
fn nvenc_open_and_encode_av1_or_skip_without_hw() {
const WIDTH: u32 = 640;
const HEIGHT: u32 = 480;
let cfg = av1_cfg(WIDTH, HEIGHT);
let mut enc = match NvencSession::open(&cfg) {
Ok(e) => e,
Err(e) => {
eprintln!(
"skip: NvencSession::open (AV1) failed ({e:?}) — no AV1-capable NVENC GPU/driver, \
or the `nvenc` crate's generic session/encoder path does not actually drive AV1 \
end to end on this hardware/driver"
);
return;
}
};
let mut packets_emitted = 0usize;
let mut first_packet_keyframe = false;
let mut first_packet_has_sequence_header = false;
let mut first_packet_dump = String::new();
for frame in synthetic_nv12_frames(WIDTH, HEIGHT) {
if let Err(e) = enc.push_frame(&frame) {
eprintln!(
"skip: push_frame (AV1) failed ({e:?}) — session opened but AV1 encode_picture did not"
);
return;
}
while let Ok(Some(p)) = enc.poll_packet() {
assert!(!p.payload.is_empty(), "packet {packets_emitted} is empty");
if packets_emitted == 0 {
first_packet_keyframe = p.is_keyframe;
first_packet_has_sequence_header = contains_av1_sequence_header_obu(&p.payload);
first_packet_dump = hex_dump(&p.payload[..p.payload.len().min(32)]);
}
packets_emitted += 1;
}
}
let _ = enc.flush();
while let Ok(Some(p)) = enc.poll_packet() {
assert!(!p.payload.is_empty());
packets_emitted += 1;
}
assert!(packets_emitted >= 1, "expected at least one encoded packet");
eprintln!(
"nvenc av1 cpu-upload packets={packets_emitted} first_packet_keyframe={first_packet_keyframe} \
first_packet_has_sequence_header_obu={first_packet_has_sequence_header} \
first_packet_prefix={first_packet_dump}"
);
assert!(
first_packet_has_sequence_header,
"first packet should carry an OBU_SEQUENCE_HEADER (AV1's keyframe signal)"
);
}