#![cfg(feature = "recording")]
use std::time::{Duration, Instant};
use tempfile::tempdir;
use crabcamera::recording::{H264Encoder, Recorder, RecordingConfig};
use crabcamera::types::CameraFrame;
#[cfg(feature = "audio")]
use crabcamera::audio::{AudioFrame, EncodedAudio, OpusEncoder};
#[test]
fn test_h264_encoder_creation_comprehensive() {
let valid_configs = vec![
(320, 240, 15.0, 500_000), (640, 480, 30.0, 1_000_000), (1280, 720, 30.0, 2_000_000), (1920, 1080, 30.0, 5_000_000), (3840, 2160, 30.0, 10_000_000), ];
for (width, height, fps, bitrate) in valid_configs {
println!(
"Testing encoder config: {}x{} @ {}fps, {}bps",
width, height, fps, bitrate
);
match H264Encoder::new(width, height, fps, bitrate) {
Ok(encoder) => {
println!("✓ Successfully created {}x{} encoder", width, height);
assert_eq!(
encoder.frame_count(),
0,
"New encoder should have zero frame count"
);
assert!(
!encoder.last_was_keyframe(),
"New encoder should not have last keyframe flag"
);
}
Err(e) => {
println!("Failed to create encoder for {}x{}: {}", width, height, e);
if width <= 1920 && height <= 1080 {
panic!(
"Standard resolution should be supported: {}x{}",
width, height
);
}
}
}
}
}
#[test]
fn test_h264_frame_encoding_comprehensive() {
let mut encoder =
H264Encoder::new(640, 480, 30.0, 1_000_000).expect("Encoder creation should succeed");
let width = 640u32;
let height = 480u32;
let frame_size = (width * height * 3) as usize;
let test_frames = vec![
("solid_black", vec![0u8; frame_size]),
("solid_white", vec![255u8; frame_size]),
("solid_gray", vec![128u8; frame_size]),
("gradient", create_gradient_frame(width, height)),
("checkerboard", create_checkerboard_frame(width, height)),
];
for (frame_type, rgb_data) in test_frames {
println!("Testing {} frame encoding", frame_type);
let result = encoder.encode_rgb(&rgb_data);
match result {
Ok(encoded) => {
println!(
"✓ {} frame: {} bytes, keyframe: {}",
frame_type,
encoded.data.len(),
encoded.is_keyframe
);
assert!(!encoded.data.is_empty(), "Encoded data should not be empty");
assert!(
encoded.data.starts_with(&[0, 0, 0, 1]) || encoded.data.starts_with(&[0, 0, 1]),
"Encoded data should start with Annex B prefix"
);
if encoder.frame_count() == 1 {
assert!(encoded.is_keyframe, "First frame should be keyframe");
}
let expected_count = encoder.frame_count();
assert!(expected_count > 0, "Frame count should increment");
validate_h264_nal_units(&encoded.data, encoded.is_keyframe);
}
Err(e) => {
panic!("Encoding {} frame failed: {}", frame_type, e);
}
}
}
println!("Total frames encoded: {}", encoder.frame_count());
assert_eq!(encoder.frame_count(), 5, "Should have encoded 5 frames");
}
#[test]
fn test_h264_keyframe_control() {
let mut encoder =
H264Encoder::new(320, 240, 30.0, 500_000).expect("Encoder creation should succeed");
let rgb_frame = vec![128u8; 320 * 240 * 3];
let mut keyframe_positions = Vec::new();
for i in 0..30 {
if i > 0 && i % 10 == 0 {
encoder.force_keyframe();
println!("Forced keyframe at frame {}", i);
}
let encoded = encoder
.encode_rgb(&rgb_frame)
.expect("Encoding should succeed");
if encoded.is_keyframe {
keyframe_positions.push(i);
println!("Keyframe detected at position {}", i);
}
}
println!("Keyframes at positions: {:?}", keyframe_positions);
assert!(
!keyframe_positions.is_empty(),
"Should have at least one keyframe"
);
assert_eq!(keyframe_positions[0], 0, "First frame should be keyframe");
assert!(
keyframe_positions.len() >= 3,
"Should have forced keyframes: {:?}",
keyframe_positions
);
for &pos in &keyframe_positions[1..] {
assert!(
pos % 10 == 0 || pos == 0,
"Forced keyframe should be at 10-frame intervals: {}",
pos
);
}
}
#[test]
fn test_h264_encoder_error_handling() {
let mut encoder =
H264Encoder::new(640, 480, 30.0, 1_000_000).expect("Encoder creation should succeed");
let invalid_frames = vec![
(vec![0u8; 100], "too small"),
(vec![0u8; 640 * 480 * 3 + 1], "too large"),
(vec![0u8; 640 * 480 * 2], "wrong format (2 bytes per pixel)"),
(vec![0u8; 320 * 240 * 3], "wrong dimensions"),
];
for (invalid_frame, description) in invalid_frames {
println!("Testing error handling for {}", description);
let result = encoder.encode_rgb(&invalid_frame);
match result {
Ok(_) => {
panic!("Expected error for {}, but encoding succeeded", description);
}
Err(e) => {
println!("✓ Expected error for {}: {}", description, e);
let error_str = e.to_string();
assert!(!error_str.is_empty(), "Error message should not be empty");
assert!(error_str.len() > 10, "Error message should be descriptive");
}
}
}
let valid_frame = vec![128u8; 640 * 480 * 3];
let result = encoder.encode_rgb(&valid_frame);
assert!(
result.is_ok(),
"Encoder should still work after error conditions"
);
}
#[test]
fn test_h264_encoding_performance() {
let mut encoder =
H264Encoder::new(1280, 720, 30.0, 2_000_000).expect("HD encoder creation should succeed");
let frame_size = 1280 * 720 * 3;
let test_frame = create_noise_frame(1280, 720, 54321);
println!(
"Testing H.264 encoding performance with {}x720 frames",
1280
);
let start_time = Instant::now();
let mut total_encoded_bytes = 0;
let frame_count = 100;
for i in 0..frame_count {
let mut frame = test_frame.clone();
for pixel in frame.iter_mut().step_by(100) {
*pixel = ((i * 17) % 256) as u8;
}
let encoded = encoder.encode_rgb(&frame).expect("Encoding should succeed");
total_encoded_bytes += encoded.data.len();
if i % 20 == 0 {
println!(
" Frame {}: {} bytes, keyframe: {}",
i,
encoded.data.len(),
encoded.is_keyframe
);
}
}
let encoding_time = start_time.elapsed();
let fps = frame_count as f64 / encoding_time.as_secs_f64();
let megabytes_per_sec =
(frame_count * frame_size) as f64 / encoding_time.as_secs_f64() / 1_000_000.0;
let compression_ratio = (frame_count * frame_size) as f64 / total_encoded_bytes as f64;
println!("Performance results:");
println!(
" Encoded {} frames in {:.2}s",
frame_count,
encoding_time.as_secs_f64()
);
println!(" Encoding FPS: {:.1}", fps);
println!(" Input data rate: {:.1} MB/s", megabytes_per_sec);
println!(" Total encoded bytes: {}", total_encoded_bytes);
println!(" Compression ratio: {:.1}x", compression_ratio);
assert!(fps > 8.0, "Should encode at least 8 FPS, got {:.1}", fps);
assert!(
compression_ratio > 5.0,
"Should achieve reasonable compression: {:.1}x",
compression_ratio
);
assert!(total_encoded_bytes > 0, "Should produce encoded output");
}
#[cfg(feature = "audio")]
mod opus_tests {
use super::*;
#[test]
fn test_opus_encoder_creation_comprehensive() {
let valid_configs = vec![
(48000, 1, 64_000), (48000, 1, 128_000), (48000, 2, 128_000), (48000, 2, 256_000), ];
for (sample_rate, channels, bitrate) in valid_configs {
println!(
"Testing Opus config: {}Hz, {}ch, {}bps",
sample_rate, channels, bitrate
);
let result = OpusEncoder::new(sample_rate, channels, bitrate);
match result {
Ok(encoder) => {
println!("✓ Successfully created Opus encoder");
assert_eq!(encoder.sample_rate(), sample_rate);
assert_eq!(encoder.channels(), channels);
}
Err(e) => {
panic!("Failed to create Opus encoder: {}", e);
}
}
}
let invalid_configs = vec![
(44100, 2, 128_000), (48000, 3, 128_000), (48000, 0, 128_000), ];
for (sample_rate, channels, bitrate) in invalid_configs {
println!(
"Testing invalid Opus config: {}Hz, {}ch, {}bps",
sample_rate, channels, bitrate
);
let result = OpusEncoder::new(sample_rate, channels, bitrate);
match result {
Ok(_) => {
panic!(
"Expected error for invalid config: {}Hz, {}ch",
sample_rate, channels
);
}
Err(e) => {
println!("✓ Expected error: {}", e);
}
}
}
}
#[test]
fn test_opus_encoding_comprehensive() {
let mut encoder =
OpusEncoder::new(48000, 2, 128_000).expect("Opus encoder creation should succeed");
let frame_size = 960 * 2; let test_frames = vec![
("silence", vec![0.0f32; frame_size]),
("sine_wave", create_sine_wave_frame(frame_size, 440.0)),
("white_noise", create_white_noise_frame(frame_size, 111)),
("stereo_test", create_stereo_test_frame(frame_size)),
];
for (frame_type, samples) in test_frames {
println!("Testing {} encoding", frame_type);
let audio_frame = AudioFrame {
samples,
sample_rate: 48000,
channels: 2,
timestamp: 0.0,
};
let result = encoder.encode(&audio_frame);
match result {
Ok(packets) => {
println!("✓ {} encoded: {} packets", frame_type, packets.len());
if !packets.is_empty() {
for (i, packet) in packets.iter().enumerate() {
println!(
" Packet {}: {} bytes, {:.3}s timestamp, {:.3}s duration",
i,
packet.data.len(),
packet.timestamp,
packet.duration
);
validate_opus_packet(packet);
}
}
}
Err(e) => {
panic!("Encoding {} failed: {}", frame_type, e);
}
}
}
}
#[test]
fn test_opus_buffering_behavior() {
let mut encoder =
OpusEncoder::new(48000, 2, 128_000).expect("Opus encoder creation should succeed");
let partial_sizes = vec![100, 200, 500, 659]; let mut total_samples_sent = 0;
for (i, size) in partial_sizes.iter().enumerate() {
println!("Sending partial frame {}: {} samples", i, size);
let audio_frame = AudioFrame {
samples: vec![0.1f32; size * 2], sample_rate: 48000,
channels: 2,
timestamp: total_samples_sent as f64 / 48000.0,
};
total_samples_sent += size;
let packets = encoder
.encode(&audio_frame)
.expect("Encoding should succeed");
if packets.is_empty() {
println!(" No output (buffering)");
} else {
println!(" Produced {} packets", packets.len());
for packet in &packets {
validate_opus_packet(packet);
}
}
}
let flush_packets = encoder.flush().expect("Flush should succeed");
println!("Flush produced {} packets", flush_packets.len());
for packet in &flush_packets {
validate_opus_packet(packet);
}
}
#[test]
fn test_opus_encoding_performance() {
let mut encoder =
OpusEncoder::new(48000, 2, 128_000).expect("Opus encoder creation should succeed");
let frame_size = 960 * 2; let frame_count = 250; let test_samples = create_sine_wave_frame(frame_size, 1000.0);
println!(
"Testing Opus encoding performance with {} frames",
frame_count
);
let start_time = Instant::now();
let mut total_packets = 0;
let mut total_bytes = 0;
for i in 0..frame_count {
let audio_frame = AudioFrame {
samples: test_samples.clone(),
sample_rate: 48000,
channels: 2,
timestamp: i as f64 * 0.020, };
let packets = encoder
.encode(&audio_frame)
.expect("Encoding should succeed");
total_packets += packets.len();
for packet in packets {
total_bytes += packet.data.len();
if i % 50 == 0 && !packet.data.is_empty() {
validate_opus_packet(&packet);
}
}
}
let encoding_time = start_time.elapsed();
let real_time_ratio = 5.0 / encoding_time.as_secs_f64(); let bitrate_actual = (total_bytes * 8) as f64 / 5.0;
println!("Opus performance results:");
println!(
" Encoded {} frames in {:.3}s",
frame_count,
encoding_time.as_secs_f64()
);
println!(" Real-time ratio: {:.1}x", real_time_ratio);
println!(" Total packets: {}", total_packets);
println!(" Total bytes: {}", total_bytes);
println!(
" Actual bitrate: {:.0} bps (target: 128000)",
bitrate_actual
);
assert!(
real_time_ratio > 10.0,
"Should encode much faster than real-time: {:.1}x",
real_time_ratio
);
assert!(
total_packets > 200,
"Should produce reasonable number of packets: {}",
total_packets
);
assert!(
(bitrate_actual - 128_000.0).abs() < 50_000.0,
"Bitrate should be close to target: {:.0} vs 128000",
bitrate_actual
);
}
fn validate_opus_packet(packet: &EncodedAudio) {
assert!(!packet.data.is_empty(), "Packet data should not be empty");
assert!(packet.timestamp >= 0.0, "Timestamp should be non-negative");
assert!(packet.duration > 0.0, "Duration should be positive");
assert!(
packet.duration <= 0.120,
"Duration should be reasonable (≤120ms)"
);
let toc = packet.data[0];
let config = (toc >> 3) & 0x1F;
assert!(config < 32, "Opus config should be valid: {}", config);
let c = toc & 0x03;
assert!(c <= 3, "Opus frame count should be valid: {}", c);
}
fn create_sine_wave_frame(samples: usize, frequency: f64) -> Vec<f32> {
let mut frame = Vec::with_capacity(samples);
for i in 0..samples {
let t = i as f64 / 48000.0;
let sample = (2.0 * std::f64::consts::PI * frequency * t).sin() as f32 * 0.5;
frame.push(sample);
}
frame
}
fn create_white_noise_frame(samples: usize, seed: u64) -> Vec<f32> {
let mut rng = seed;
let mut frame = Vec::with_capacity(samples);
for _ in 0..samples {
rng = rng.wrapping_mul(1103515245).wrapping_add(12345);
let sample = ((rng >> 16) as f32 / 32768.0 - 1.0) * 0.3;
frame.push(sample);
}
frame
}
fn create_stereo_test_frame(samples: usize) -> Vec<f32> {
let mut frame = Vec::with_capacity(samples);
for i in 0..(samples / 2) {
let t = i as f64 / 48000.0;
let left = (2.0 * std::f64::consts::PI * 440.0 * t).sin() as f32 * 0.3;
let right = (2.0 * std::f64::consts::PI * 880.0 * t).sin() as f32 * 0.3;
frame.push(left);
frame.push(right);
}
frame
}
}
#[test]
#[cfg(feature = "audio")]
fn test_integrated_av_encoding_pipeline() {
use crabcamera::recording::AudioConfig;
let dir = tempdir().expect("Create temp dir");
let output = dir.path().join("integrated_test.mp4");
let config = RecordingConfig::new(640, 480, 30.0).with_audio(AudioConfig {
device_id: None,
sample_rate: 48000,
channels: 2,
bitrate: 128_000,
});
let mut recorder = Recorder::new(&output, config).expect("Recorder creation should succeed");
println!("Testing integrated A/V encoding pipeline");
let frame_count = 90; let mut video_frames_written = 0;
for i in 0..frame_count {
let frame = create_test_camera_frame(640, 480, i);
match recorder.write_frame(&frame) {
Ok(_) => {
video_frames_written += 1;
if i % 30 == 0 {
println!(" Written video frame {}", i);
}
}
Err(e) => {
println!("Video frame {} write error: {}", i, e);
}
}
std::thread::sleep(Duration::from_millis(33));
}
let stats = recorder.finish().expect("Recording finish should succeed");
println!("Integrated encoding results:");
println!(" Video frames attempted: {}", frame_count);
println!(" Video frames written: {}", video_frames_written);
println!(" Final video frames: {}", stats.video_frames);
println!(" Audio frames: {}", stats.audio_frames);
println!(" Duration: {:.2}s", stats.duration_secs);
println!(" File size: {} bytes", stats.bytes_written);
assert!(stats.video_frames > 0, "Should have video frames");
assert!(stats.bytes_written > 0, "Should have written data");
assert!(stats.duration_secs > 0.0, "Should have duration");
let file_metadata = std::fs::metadata(&output).expect("Output file should exist");
assert!(file_metadata.len() > 0, "Output file should have content");
assert!(
file_metadata.len() as u64 <= stats.bytes_written + 1000,
"File size should match stats"
);
println!("✓ Integrated A/V encoding pipeline test passed");
}
#[test]
fn test_encoder_stress_conditions() {
let mut encoder =
H264Encoder::new(320, 240, 30.0, 500_000).expect("Encoder creation should succeed");
println!("Testing encoder under stress conditions");
let start_time = Instant::now();
let mut successful_encodes = 0;
let mut total_bytes = 0;
for i in 0..1000 {
let frame = create_rapidly_changing_frame(320, 240, i);
match encoder.encode_rgb(&frame) {
Ok(encoded) => {
successful_encodes += 1;
total_bytes += encoded.data.len();
if i % 100 == 0 {
println!(
" Frame {}: {} bytes, keyframe: {}",
i,
encoded.data.len(),
encoded.is_keyframe
);
}
}
Err(e) => {
println!("Encoding error at frame {}: {}", i, e);
}
}
}
let encoding_duration = start_time.elapsed();
let fps = successful_encodes as f64 / encoding_duration.as_secs_f64();
println!("Stress test results:");
println!(" Successful encodes: {}/1000", successful_encodes);
println!(" Encoding FPS: {:.1}", fps);
println!(" Total encoded bytes: {}", total_bytes);
println!(" Time taken: {:.2}s", encoding_duration.as_secs_f64());
assert!(
successful_encodes >= 950,
"Should handle most frames under stress: {}",
successful_encodes
);
assert!(
fps > 50.0,
"Should maintain reasonable FPS under stress: {:.1}",
fps
);
}
#[test]
fn test_encoder_memory_patterns() {
println!("Testing encoder memory usage patterns");
let configs = vec![(320, 240), (640, 480), (1280, 720)];
let mut encoders = Vec::new();
for (width, height) in configs {
match H264Encoder::new(width, height, 30.0, 1_000_000) {
Ok(encoder) => {
println!("Created {}x{} encoder", width, height);
encoders.push((encoder, width, height));
}
Err(e) => {
println!("Failed to create {}x{} encoder: {}", width, height, e);
}
}
}
for (encoder, width, height) in &mut encoders {
let frame = vec![128u8; (*width * *height * 3) as usize];
for i in 0..10 {
match encoder.encode_rgb(&frame) {
Ok(encoded) => {
assert!(!encoded.data.is_empty(), "Frame should produce output");
if i == 0 {
println!(
" {}x{} first frame: {} bytes",
width,
height,
encoded.data.len()
);
}
}
Err(e) => {
panic!("Encoding failed for {}x{}: {}", width, height, e);
}
}
}
}
println!(
"✓ Memory patterns test completed with {} encoders",
encoders.len()
);
}
fn create_gradient_frame(width: u32, height: u32) -> Vec<u8> {
let mut frame = Vec::with_capacity((width * height * 3) as usize);
for y in 0..height {
for x in 0..width {
let r = ((x as f32 / width as f32) * 255.0) as u8;
let g = ((y as f32 / height as f32) * 255.0) as u8;
let b = 128u8;
frame.push(r);
frame.push(g);
frame.push(b);
}
}
frame
}
fn create_checkerboard_frame(width: u32, height: u32) -> Vec<u8> {
let mut frame = Vec::with_capacity((width * height * 3) as usize);
let square_size = 32;
for y in 0..height {
for x in 0..width {
let checker_x = (x / square_size) % 2;
let checker_y = (y / square_size) % 2;
let is_white = (checker_x + checker_y) % 2 == 0;
let color = if is_white { 255 } else { 0 };
frame.push(color);
frame.push(color);
frame.push(color);
}
}
frame
}
fn create_noise_frame(width: u32, height: u32, seed: u64) -> Vec<u8> {
let mut frame = Vec::with_capacity((width * height * 3) as usize);
let mut rng = seed;
for _ in 0..(width * height) {
rng = rng.wrapping_mul(1103515245).wrapping_add(12345);
let r = ((rng >> 16) & 0xFF) as u8;
rng = rng.wrapping_mul(1103515245).wrapping_add(12345);
let g = ((rng >> 16) & 0xFF) as u8;
rng = rng.wrapping_mul(1103515245).wrapping_add(12345);
let b = ((rng >> 16) & 0xFF) as u8;
frame.push(r);
frame.push(g);
frame.push(b);
}
frame
}
fn create_rapidly_changing_frame(width: u32, height: u32, frame_index: usize) -> Vec<u8> {
let mut frame = Vec::with_capacity((width * height * 3) as usize);
let pattern = frame_index % 16;
for _y in 0..height {
for _x in 0..width {
let base_color = match pattern {
0..=3 => (255, 0, 0), 4..=7 => (0, 255, 0), 8..=11 => (0, 0, 255), _ => (255, 255, 255), };
let intensity = ((frame_index * 17) % 256) as u8;
let r = ((base_color.0 as u16 * intensity as u16) / 255) as u8;
let g = ((base_color.1 as u16 * intensity as u16) / 255) as u8;
let b = ((base_color.2 as u16 * intensity as u16) / 255) as u8;
frame.push(r);
frame.push(g);
frame.push(b);
}
}
frame
}
fn create_test_camera_frame(width: u32, height: u32, frame_index: usize) -> CameraFrame {
let gray = ((frame_index * 7) % 256) as u8;
let data = vec![gray; (width * height * 3) as usize];
CameraFrame::new(data, width, height, "test_camera".to_string())
}
fn validate_h264_nal_units(data: &[u8], is_keyframe: bool) {
assert!(!data.is_empty(), "NAL data should not be empty");
assert!(
data.starts_with(&[0, 0, 0, 1]) || data.starts_with(&[0, 0, 1]),
"Should start with Annex B start code"
);
let mut nal_units = Vec::new();
let mut i = 0;
while i < data.len() {
if i + 3 < data.len() {
if data[i..i + 4] == [0, 0, 0, 1] {
nal_units.push(i + 4);
i += 4;
} else if data[i..i + 3] == [0, 0, 1] {
nal_units.push(i + 3);
i += 3;
} else {
i += 1;
}
} else {
break;
}
}
assert!(!nal_units.is_empty(), "Should find at least one NAL unit");
if !nal_units.is_empty() && nal_units[0] < data.len() {
let nal_type = data[nal_units[0]] & 0x1F;
if is_keyframe {
assert!(
nal_type == 5 || nal_type == 7 || nal_type == 8, "Keyframe should contain IDR/SPS/PPS NAL unit, got type {}",
nal_type
);
}
println!(" Found NAL unit type: {}", nal_type);
}
}