use crate::errors::CameraError;
use openh264::encoder::{Encoder, FrameType};
use openh264::formats::YUVBuffer;
pub struct H264Encoder {
encoder: Encoder,
width: u32,
height: u32,
frame_count: u64,
last_frame_was_keyframe: bool,
}
impl H264Encoder {
pub fn new(width: u32, height: u32, _fps: f64, _bitrate: u32) -> Result<Self, CameraError> {
let encoder = Encoder::new()
.map_err(|e| CameraError::EncodingError(format!("Failed to create encoder: {}", e)))?;
Ok(Self {
encoder,
width,
height,
frame_count: 0,
last_frame_was_keyframe: false,
})
}
pub fn encode_rgb(&mut self, rgb_data: &[u8]) -> Result<EncodedFrame, CameraError> {
let expected_size = (self.width * self.height * 3) as usize;
if rgb_data.len() != expected_size {
return Err(CameraError::EncodingError(format!(
"Invalid frame size: expected {} bytes, got {}",
expected_size,
rgb_data.len()
)));
}
let yuv = rgb_to_yuv420(rgb_data, self.width, self.height);
self.encode_yuv(&yuv)
}
pub fn encode_yuv(&mut self, yuv_data: &[u8]) -> Result<EncodedFrame, CameraError> {
let yuv_buffer =
YUVBuffer::from_vec(yuv_data.to_vec(), self.width as usize, self.height as usize);
let bitstream = self
.encoder
.encode(&yuv_buffer)
.map_err(|e| CameraError::EncodingError(format!("Encoding failed: {}", e)))?;
self.frame_count += 1;
let is_keyframe = matches!(bitstream.frame_type(), FrameType::IDR | FrameType::I);
self.last_frame_was_keyframe = is_keyframe;
let data = bitstream.to_vec();
Ok(EncodedFrame { data, is_keyframe })
}
pub fn frame_count(&self) -> u64 {
self.frame_count
}
pub fn last_was_keyframe(&self) -> bool {
self.last_frame_was_keyframe
}
pub fn force_keyframe(&mut self) {
self.encoder.force_intra_frame();
}
}
#[derive(Debug, Clone)]
pub struct EncodedFrame {
pub data: Vec<u8>,
pub is_keyframe: bool,
}
fn rgb_to_yuv420(rgb: &[u8], width: u32, height: u32) -> Vec<u8> {
let w = width as usize;
let h = height as usize;
let y_size = w * h;
let uv_size = (w / 2) * (h / 2);
let mut yuv = vec![0u8; y_size + uv_size * 2];
let (y_plane, uv_planes) = yuv.split_at_mut(y_size);
let (u_plane, v_plane) = uv_planes.split_at_mut(uv_size);
for y in 0..h {
for x in 0..w {
let rgb_idx = (y * w + x) * 3;
let r = rgb[rgb_idx] as i32;
let g = rgb[rgb_idx + 1] as i32;
let b = rgb[rgb_idx + 2] as i32;
let y_val = ((66 * r + 129 * g + 25 * b + 128) >> 8) + 16;
y_plane[y * w + x] = y_val.clamp(0, 255) as u8;
if y % 2 == 0 && x % 2 == 0 {
let uv_idx = (y / 2) * (w / 2) + (x / 2);
let u_val = ((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128;
let v_val = ((112 * r - 94 * g - 18 * b + 128) >> 8) + 128;
u_plane[uv_idx] = u_val.clamp(0, 255) as u8;
v_plane[uv_idx] = v_val.clamp(0, 255) as u8;
}
}
}
yuv
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rgb_to_yuv420_size() {
let width = 640u32;
let height = 480u32;
let rgb = vec![128u8; (width * height * 3) as usize];
let yuv = rgb_to_yuv420(&rgb, width, height);
let expected = (width * height * 3 / 2) as usize;
assert_eq!(yuv.len(), expected);
}
#[test]
fn test_encoder_creation() {
let result = H264Encoder::new(640, 480, 30.0, 1_000_000);
assert!(result.is_ok(), "Encoder should be created successfully");
}
#[test]
fn test_encode_frame() {
let mut encoder =
H264Encoder::new(640, 480, 30.0, 1_000_000).expect("Encoder creation failed");
let rgb = vec![128u8; 640 * 480 * 3];
let result = encoder.encode_rgb(&rgb);
assert!(result.is_ok(), "Encoding should succeed");
let encoded = result.unwrap();
assert!(!encoded.data.is_empty(), "Encoded data should not be empty");
assert!(
encoded.data.starts_with(&[0x00, 0x00, 0x00, 0x01])
|| encoded.data.starts_with(&[0x00, 0x00, 0x01]),
"Should start with Annex B start code"
);
assert!(encoded.is_keyframe, "First frame should be a keyframe");
}
}