use std::ffi::c_void;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::ptr;
use std::sync::Mutex;
use std::sync::mpsc::{self, Receiver, Sender};
use crate::{CudaLibrary, Error, sys};
#[derive(Debug, Clone)]
pub struct DecoderCaps {
pub is_supported: bool,
pub max_width: u32,
pub max_height: u32,
pub max_mb_count: u32,
pub min_width: u32,
pub min_height: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DecoderCodec {
H264,
Hevc,
Av1,
Vp8,
Vp9,
Jpeg,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SurfaceFormat {
Nv12,
}
impl SurfaceFormat {
fn to_sys(self) -> u32 {
match self {
SurfaceFormat::Nv12 => sys::cudaVideoSurfaceFormat_enum_cudaVideoSurfaceFormat_NV12,
}
}
}
#[derive(Debug, Clone)]
pub struct DecoderConfig {
pub codec: DecoderCodec,
pub device_id: i32,
pub max_num_decode_surfaces: u32,
pub max_display_delay: u32,
pub surface_format: SurfaceFormat,
}
pub struct Decoder {
lib: CudaLibrary,
ctx: sys::CUcontext,
ctx_lock: sys::CUvideoctxlock,
parser: sys::CUvideoparser,
state: Box<Mutex<DecoderState>>,
frame_rx: Receiver<Result<DecodedFrame, Error>>,
}
impl Decoder {
pub fn new(config: DecoderConfig) -> Result<Self, Error> {
let codec_type = match config.codec {
DecoderCodec::H264 => sys::cudaVideoCodec_enum_cudaVideoCodec_H264,
DecoderCodec::Hevc => sys::cudaVideoCodec_enum_cudaVideoCodec_HEVC,
DecoderCodec::Av1 => sys::cudaVideoCodec_enum_cudaVideoCodec_AV1,
DecoderCodec::Vp8 => sys::cudaVideoCodec_enum_cudaVideoCodec_VP8,
DecoderCodec::Vp9 => sys::cudaVideoCodec_enum_cudaVideoCodec_VP9,
DecoderCodec::Jpeg => sys::cudaVideoCodec_enum_cudaVideoCodec_JPEG,
};
Self::new_with_codec(codec_type, config)
}
pub fn query_caps(codec: DecoderCodec, device_id: i32) -> Result<DecoderCaps, Error> {
let codec_type = match codec {
DecoderCodec::H264 => sys::cudaVideoCodec_enum_cudaVideoCodec_H264,
DecoderCodec::Hevc => sys::cudaVideoCodec_enum_cudaVideoCodec_HEVC,
DecoderCodec::Av1 => sys::cudaVideoCodec_enum_cudaVideoCodec_AV1,
DecoderCodec::Vp8 => sys::cudaVideoCodec_enum_cudaVideoCodec_VP8,
DecoderCodec::Vp9 => sys::cudaVideoCodec_enum_cudaVideoCodec_VP9,
DecoderCodec::Jpeg => sys::cudaVideoCodec_enum_cudaVideoCodec_JPEG,
};
Self::query_caps_with_codec(device_id, codec_type)
}
fn query_caps_with_codec(
device_id: i32,
codec_type: sys::cudaVideoCodec,
) -> Result<DecoderCaps, Error> {
unsafe {
let lib = CudaLibrary::load()?;
let mut ctx = std::ptr::null_mut();
lib.cu_ctx_create(&mut ctx, 0, device_id)?;
let lib_clone = lib.clone();
let ctx_guard = crate::ReleaseGuard::new(move || {
let _ = lib_clone.cu_ctx_destroy(ctx);
});
let caps = lib.with_context(ctx, || {
let mut decode_caps: sys::CUVIDDECODECAPS = std::mem::zeroed();
decode_caps.eCodecType = codec_type;
decode_caps.eChromaFormat =
sys::cudaVideoChromaFormat_enum_cudaVideoChromaFormat_420;
decode_caps.nBitDepthMinus8 = 0;
lib.cuvid_get_decoder_caps(&mut decode_caps)?;
Ok(DecoderCaps {
is_supported: decode_caps.bIsSupported != 0,
max_width: decode_caps.nMaxWidth,
max_height: decode_caps.nMaxHeight,
max_mb_count: decode_caps.nMaxMBCount,
min_width: decode_caps.nMinWidth as u32,
min_height: decode_caps.nMinHeight as u32,
})
})?;
ctx_guard.cancel();
lib.cu_ctx_destroy(ctx)?;
Ok(caps)
}
}
fn new_with_codec(
codec_type: sys::cudaVideoCodec,
config: DecoderConfig,
) -> Result<Self, Error> {
unsafe {
let lib = CudaLibrary::load()?;
let mut ctx = ptr::null_mut();
let ctx_flags = 0; lib.cu_ctx_create(&mut ctx, ctx_flags, config.device_id)?;
let ctx_guard = crate::ReleaseGuard::new(|| {
let _ = lib.cu_ctx_destroy(ctx);
});
let mut ctx_lock = ptr::null_mut();
lib.cuvid_ctx_lock_create(&mut ctx_lock, ctx)?;
let ctx_lock_guard = crate::ReleaseGuard::new(|| {
let _ = lib.cuvid_ctx_lock_destroy(ctx_lock);
});
let (frame_tx, frame_rx) = mpsc::channel();
let state = Box::new(Mutex::new(DecoderState {
lib: lib.clone(),
decoder: ptr::null_mut(),
width: 0,
height: 0,
surface_width: 0,
surface_height: 0,
surface_format: config.surface_format.to_sys(),
frame_tx,
ctx,
ctx_lock,
}));
let mut parser_params: sys::CUVIDPARSERPARAMS = std::mem::zeroed();
parser_params.CodecType = codec_type;
parser_params.ulMaxNumDecodeSurfaces = config.max_num_decode_surfaces;
parser_params.ulMaxDisplayDelay = config.max_display_delay;
parser_params.pUserData = (&*state) as *const _ as *mut c_void;
parser_params.pfnSequenceCallback = Some(handle_video_sequence);
parser_params.pfnDecodePicture = Some(handle_picture_decode);
parser_params.pfnDisplayPicture = Some(handle_picture_display);
let mut parser = ptr::null_mut();
lib.cuvid_create_video_parser(&mut parser, &mut parser_params)?;
ctx_guard.cancel();
ctx_lock_guard.cancel();
Ok(Self {
lib,
ctx,
ctx_lock,
parser,
state,
frame_rx,
})
}
}
pub fn decode(&mut self, data: &[u8]) -> Result<(), Error> {
unsafe {
let mut packet: sys::CUVIDSOURCEDATAPACKET = std::mem::zeroed();
packet.payload = data.as_ptr();
packet.payload_size = data.len() as u64;
packet.flags = 0;
packet.timestamp = 0;
self.lib.cuvid_parse_video_data(self.parser, &mut packet)?;
}
Ok(())
}
pub fn finish(&mut self) -> Result<(), Error> {
unsafe {
let mut packet: sys::CUVIDSOURCEDATAPACKET = std::mem::zeroed();
packet.payload = ptr::null();
packet.payload_size = 0;
packet.flags = sys::CUvideopacketflags_CUVID_PKT_ENDOFSTREAM as u64;
packet.timestamp = 0;
self.lib.cuvid_parse_video_data(self.parser, &mut packet)?;
self.lib
.with_context(self.ctx, || self.lib.cu_ctx_synchronize())?;
}
Ok(())
}
pub fn next_frame(&mut self) -> Result<Option<DecodedFrame>, Error> {
if self.state.is_poisoned() {
return Err(Error::new_custom(
"next_frame",
"decoder state is poisoned (a thread panicked while holding the lock)",
));
}
self.frame_rx.try_recv().ok().transpose()
}
}
impl Drop for Decoder {
fn drop(&mut self) {
if !self.ctx.is_null() {
let _ = self
.lib
.with_context(self.ctx, || self.lib.cu_ctx_synchronize());
}
if !self.parser.is_null() {
let _ = self.lib.cuvid_destroy_video_parser(self.parser);
}
if let Ok(state) = self.state.lock()
&& !state.decoder.is_null()
{
let _ = self
.lib
.with_context(self.ctx, || self.lib.cuvid_destroy_decoder(state.decoder));
}
if !self.ctx_lock.is_null() {
let _ = self.lib.cuvid_ctx_lock_destroy(self.ctx_lock);
}
if !self.ctx.is_null() {
let _ = self.lib.cu_ctx_destroy(self.ctx);
}
}
}
impl std::fmt::Debug for Decoder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let state = self.state.lock().ok();
f.debug_struct("Decoder")
.field("ctx", &format_args!("{:p}", self.ctx))
.field("ctx_lock", &format_args!("{:p}", self.ctx_lock))
.field("parser", &format_args!("{:p}", self.parser))
.field(
"decoder",
&state.as_ref().map(|s| format!("{:p}", s.decoder)),
)
.field("width", &state.as_ref().map(|s| s.width))
.field("height", &state.as_ref().map(|s| s.height))
.field("surface_width", &state.as_ref().map(|s| s.surface_width))
.field("surface_height", &state.as_ref().map(|s| s.surface_height))
.finish()
}
}
unsafe impl Send for Decoder {}
struct DecoderState {
lib: CudaLibrary,
decoder: sys::CUvideodecoder,
width: u32,
height: u32,
surface_width: u32,
surface_height: u32,
surface_format: u32,
frame_tx: Sender<Result<DecodedFrame, Error>>,
ctx: sys::CUcontext,
ctx_lock: sys::CUvideoctxlock,
}
unsafe extern "C" fn handle_video_sequence(
user_data: *mut c_void,
format: *mut sys::CUVIDEOFORMAT,
) -> i32 {
if user_data.is_null() || format.is_null() {
return 0;
}
let state_mutex = unsafe { &*(user_data as *const Mutex<DecoderState>) };
let result = catch_unwind(AssertUnwindSafe(|| {
let format = unsafe { &*format };
let Ok(mut state) = state_mutex.lock() else {
return 0;
};
let result = handle_video_sequence_inner(&mut state, format);
match result {
Ok(num_surfaces) => num_surfaces,
Err(e) => {
let _ = state.frame_tx.send(Err(e));
0
}
}
}));
match result {
Ok(v) => v,
Err(_) => {
if let Ok(state) = state_mutex.lock() {
let _ = state.frame_tx.send(Err(Error::new_custom(
"handle_video_sequence",
"panic occurred in FFI callback",
)));
}
0
}
}
}
fn handle_video_sequence_inner(
state: &mut DecoderState,
format: &sys::CUVIDEOFORMAT,
) -> Result<i32, Error> {
if !state.decoder.is_null() {
state
.lib
.with_context(state.ctx, || state.lib.cuvid_destroy_decoder(state.decoder))?;
state.decoder = ptr::null_mut();
}
let mut create_info: sys::CUVIDDECODECREATEINFO = unsafe { std::mem::zeroed() };
create_info.CodecType = format.codec;
create_info.ChromaFormat = format.chroma_format;
create_info.OutputFormat = state.surface_format;
create_info.bitDepthMinus8 = format.bit_depth_luma_minus8 as u64;
create_info.DeinterlaceMode = if format.progressive_sequence != 0 {
sys::cudaVideoDeinterlaceMode_enum_cudaVideoDeinterlaceMode_Weave
} else {
sys::cudaVideoDeinterlaceMode_enum_cudaVideoDeinterlaceMode_Adaptive
};
create_info.ulNumOutputSurfaces = 2; create_info.ulCreationFlags = sys::cudaVideoCreateFlags_enum_cudaVideoCreate_PreferCUVID as u64; create_info.ulNumDecodeSurfaces = format.min_num_decode_surfaces as u64;
create_info.ulWidth = format.coded_width as u64;
create_info.ulHeight = format.coded_height as u64;
create_info.ulMaxWidth = format.coded_width as u64;
create_info.ulMaxHeight = format.coded_height as u64;
create_info.ulTargetWidth = format.coded_width as u64;
create_info.ulTargetHeight = format.coded_height as u64;
create_info.vidLock = state.ctx_lock;
state.lib.with_context(state.ctx, || {
state
.lib
.cuvid_create_decoder(&mut state.decoder, &mut create_info)
})?;
let left = format.display_area.left;
let right = format.display_area.right;
let top = format.display_area.top;
let bottom = format.display_area.bottom;
if left < 0
|| top < 0
|| right <= left
|| bottom <= top
|| right as u32 > format.coded_width
|| bottom as u32 > format.coded_height
{
return Err(Error::new_custom(
"handle_video_sequence",
"invalid display_area in video format",
));
}
state.width = (right - left) as u32;
state.height = (bottom - top) as u32;
state.surface_width = format.coded_width;
state.surface_height = format.coded_height;
Ok(format.min_num_decode_surfaces as i32)
}
unsafe extern "C" fn handle_picture_decode(
user_data: *mut c_void,
pic_params: *mut sys::CUVIDPICPARAMS,
) -> i32 {
if user_data.is_null() || pic_params.is_null() {
return 0;
}
let state_mutex = unsafe { &*(user_data as *const Mutex<DecoderState>) };
let result = catch_unwind(AssertUnwindSafe(|| {
let Ok(mut state) = state_mutex.lock() else {
return 0;
};
let result = handle_picture_decode_inner(&mut state, unsafe { &*pic_params });
match result {
Ok(_) => 1,
Err(e) => {
let _ = state.frame_tx.send(Err(e));
0
}
}
}));
match result {
Ok(v) => v,
Err(_) => {
if let Ok(state) = state_mutex.lock() {
let _ = state.frame_tx.send(Err(Error::new_custom(
"handle_picture_decode",
"panic occurred in FFI callback",
)));
}
0
}
}
}
fn handle_picture_decode_inner(
state: &mut DecoderState,
pic_params: &sys::CUVIDPICPARAMS,
) -> Result<(), Error> {
if state.decoder.is_null() {
return Err(Error::new_custom(
"handle_picture_decode",
"decoder not initialized",
));
}
state.lib.with_context(state.ctx, || {
state
.lib
.cuvid_decode_picture(state.decoder, pic_params as *const _ as *mut _)
})?;
Ok(())
}
unsafe extern "C" fn handle_picture_display(
user_data: *mut c_void,
disp_info: *mut sys::CUVIDPARSERDISPINFO,
) -> i32 {
if user_data.is_null() || disp_info.is_null() {
return 0;
}
let state_mutex = unsafe { &*(user_data as *const Mutex<DecoderState>) };
let result = catch_unwind(AssertUnwindSafe(|| {
let Ok(state) = state_mutex.lock() else {
return 0;
};
let result = handle_picture_display_inner(&state, unsafe { &*disp_info });
match result {
Ok(_) => 1,
Err(e) => {
let _ = state.frame_tx.send(Err(e));
0
}
}
}));
match result {
Ok(v) => v,
Err(_) => {
if let Ok(state) = state_mutex.lock() {
let _ = state.frame_tx.send(Err(Error::new_custom(
"handle_picture_display",
"panic occurred in FFI callback",
)));
}
0
}
}
}
fn handle_picture_display_inner(
state: &DecoderState,
disp_info: &sys::CUVIDPARSERDISPINFO,
) -> Result<(), Error> {
if state.decoder.is_null() {
return Err(Error::new_custom(
"handle_picture_display",
"decoder not initialized",
));
}
let decoded_frame = state.lib.with_context(state.ctx, || unsafe {
let mut proc_params: sys::CUVIDPROCPARAMS = std::mem::zeroed();
proc_params.progressive_frame = disp_info.progressive_frame;
proc_params.top_field_first = disp_info.top_field_first;
proc_params.second_field = disp_info.repeat_first_field + 1;
proc_params.output_stream = ptr::null_mut();
let mut device_ptr = 0u64;
let mut pitch = 0u32;
state.lib.cuvid_map_video_frame(
state.decoder,
disp_info.picture_index,
&mut device_ptr,
&mut pitch,
&mut proc_params,
)?;
let _unmap_guard = crate::ReleaseGuard::new(|| {
let _ = state.lib.cuvid_unmap_video_frame(state.decoder, device_ptr);
});
let aligned_height = (state.surface_height + 1) & !1;
let y_size = pitch as usize * state.height as usize;
let uv_size = pitch as usize * (state.height as usize).div_ceil(2);
let frame_size = y_size + uv_size;
let mut host_data = vec![0u8; frame_size];
state
.lib
.cu_memcpy_d_to_h(host_data.as_mut_ptr() as *mut c_void, device_ptr, y_size)?;
let uv_offset = pitch as u64 * aligned_height as u64;
state.lib.cu_memcpy_d_to_h(
host_data[y_size..].as_mut_ptr() as *mut c_void,
device_ptr + uv_offset,
uv_size,
)?;
Ok(DecodedFrame {
width: state.width,
height: state.height,
pitch: pitch as usize,
data: host_data,
})
})?;
let _ = state.frame_tx.send(Ok(decoded_frame));
Ok(())
}
#[derive(Debug, Clone)]
pub struct DecodedFrame {
width: u32,
height: u32,
pitch: usize,
data: Vec<u8>,
}
impl DecodedFrame {
pub fn y_plane(&self) -> &[u8] {
let y_size = self.pitch * self.height as usize;
&self.data[..y_size]
}
pub fn uv_plane(&self) -> &[u8] {
let y_size = self.pitch * self.height as usize;
let uv_size = self.pitch * (self.height as usize).div_ceil(2);
&self.data[y_size..y_size + uv_size]
}
pub fn y_stride(&self) -> usize {
self.pitch
}
pub fn uv_stride(&self) -> usize {
self.pitch
}
pub fn width(&self) -> usize {
self.width as usize
}
pub fn height(&self) -> usize {
self.height as usize
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_decoder_config(codec: DecoderCodec) -> DecoderConfig {
DecoderConfig {
codec,
device_id: 0,
max_num_decode_surfaces: 20,
max_display_delay: 0,
surface_format: SurfaceFormat::Nv12,
}
}
#[test]
fn init_h264_decoder() {
let config = test_decoder_config(DecoderCodec::H264);
let _decoder = Decoder::new(config).expect("Failed to initialize h264 decoder");
println!("h264 decoder initialized successfully");
}
#[test]
fn init_h265_decoder() {
let config = test_decoder_config(DecoderCodec::Hevc);
let _decoder = Decoder::new(config).expect("Failed to initialize h265 decoder");
println!("h265 decoder initialized successfully");
}
#[test]
fn init_av1_decoder() {
let config = test_decoder_config(DecoderCodec::Av1);
let _decoder = Decoder::new(config).expect("Failed to initialize av1 decoder");
println!("av1 decoder initialized successfully");
}
#[test]
fn init_vp8_decoder() {
let config = test_decoder_config(DecoderCodec::Vp8);
let _decoder = Decoder::new(config).expect("Failed to initialize vp8 decoder");
println!("vp8 decoder initialized successfully");
}
#[test]
fn init_vp9_decoder() {
let config = test_decoder_config(DecoderCodec::Vp9);
let _decoder = Decoder::new(config).expect("Failed to initialize vp9 decoder");
println!("vp9 decoder initialized successfully");
}
#[test]
fn test_multiple_decoders() {
let config = test_decoder_config(DecoderCodec::Hevc);
let _decoder1 =
Decoder::new(config.clone()).expect("Failed to initialize first h265 decoder");
let _decoder2 = Decoder::new(config).expect("Failed to initialize second h265 decoder");
println!("Multiple h265 decoders initialized successfully");
}
#[test]
fn test_decode_h265_black_frame() {
let vps = vec![
64, 1, 12, 1, 255, 255, 1, 96, 0, 0, 3, 0, 144, 0, 0, 3, 0, 0, 3, 0, 90, 149, 152, 9,
];
let sps = vec![
66, 1, 1, 1, 96, 0, 0, 3, 0, 144, 0, 0, 3, 0, 0, 3, 0, 90, 160, 5, 2, 1, 225, 101, 149,
154, 73, 50, 188, 5, 160, 32, 0, 0, 3, 0, 32, 0, 0, 3, 3, 33,
];
let pps = vec![68, 1, 193, 114, 180, 98, 64];
let frame_data = vec![
40, 1, 175, 29, 16, 90, 181, 140, 90, 213, 247, 1, 91, 255, 242, 78, 254, 199, 0, 31,
209, 50, 148, 21, 162, 38, 146, 0, 0, 3, 1, 203, 169, 113, 202, 5, 24, 129, 39, 128, 0,
0, 3, 0, 7, 204, 147, 13, 148, 32, 0, 0, 3, 0, 0, 3, 0, 12, 24, 135, 0, 0, 3, 0, 0, 3,
0, 0, 3, 0, 28, 240, 0, 0, 3, 0, 0, 3, 0, 0, 3, 0, 8, 104, 0, 0, 3, 0, 0, 3, 0, 0, 3,
0, 104, 192, 0, 0, 3, 0, 0, 3, 0, 0, 3, 1, 223, 0, 0, 3, 0, 9, 248,
];
let mut h265_data = Vec::new();
let start_code = [0u8, 0, 0, 1];
h265_data.extend_from_slice(&start_code);
h265_data.extend_from_slice(&vps);
h265_data.extend_from_slice(&start_code);
h265_data.extend_from_slice(&sps);
h265_data.extend_from_slice(&start_code);
h265_data.extend_from_slice(&pps);
h265_data.extend_from_slice(&start_code);
h265_data.extend_from_slice(&frame_data);
let config = test_decoder_config(DecoderCodec::Hevc);
let mut decoder = Decoder::new(config).expect("Failed to create h265 decoder");
decoder
.decode(&h265_data)
.expect("Failed to decode H.265 data");
decoder.finish().expect("Failed to finish decoding");
let frame = decoder
.next_frame()
.expect("Decoding error occurred")
.expect("No decoded frame available");
assert_eq!(frame.width(), 640);
assert_eq!(frame.height(), 480);
assert_eq!(frame.y_plane().len(), frame.y_stride() * frame.height());
assert_eq!(
frame.uv_plane().len(),
frame.uv_stride() * frame.height().div_ceil(2)
);
assert!(frame.y_stride() >= frame.width());
assert!(frame.uv_stride() >= frame.width());
let y_data = frame.y_plane();
let uv_data = frame.uv_plane();
let y_avg = y_data.iter().map(|&x| x as u32).sum::<u32>() / y_data.len() as u32;
assert!(
(10..=30).contains(&y_avg),
"Y average should be around 16 for black, got {}",
y_avg
);
let uv_avg = uv_data.iter().map(|&x| x as u32).sum::<u32>() / uv_data.len() as u32;
assert!(
(70..=140).contains(&uv_avg),
"UV average should be in reasonable range for the encoded frame, got {}",
uv_avg
);
println!(
"Successfully decoded H.265 black frame: {}x{} (stride: {})",
frame.width(),
frame.height(),
frame.y_stride()
);
println!("Y average: {}, UV average: {}", y_avg, uv_avg);
}
#[test]
fn test_decode_h264_black_frame() {
let sps = vec![
103, 100, 0, 30, 172, 217, 64, 160, 61, 176, 17, 0, 0, 3, 0, 1, 0, 0, 3, 0, 50, 15, 22,
45, 150,
];
let pps = vec![104, 235, 227, 203, 34, 192];
let frame_data = vec![
101, 136, 132, 0, 43, 255, 254, 246, 115, 124, 10, 107, 109, 176, 149, 46, 5, 118, 247,
102, 163, 229, 208, 146, 229, 251, 16, 96, 250, 208, 0, 0, 3, 0, 0, 3, 0, 0, 16, 15,
210, 222, 245, 204, 98, 91, 229, 32, 0, 0, 9, 216, 2, 56, 13, 16, 118, 133, 116, 69,
196, 32, 71, 6, 120, 150, 16, 161, 210, 50, 128, 0, 0, 3, 0, 0, 3, 0, 0, 3, 0, 0, 3, 0,
0, 3, 0, 0, 3, 0, 0, 3, 0, 0, 3, 0, 0, 3, 0, 37, 225,
];
let mut h264_data = Vec::new();
let start_code = [0u8, 0, 0, 1];
h264_data.extend_from_slice(&start_code);
h264_data.extend_from_slice(&sps);
h264_data.extend_from_slice(&start_code);
h264_data.extend_from_slice(&pps);
h264_data.extend_from_slice(&start_code);
h264_data.extend_from_slice(&frame_data);
let config = test_decoder_config(DecoderCodec::H264);
let mut decoder = Decoder::new(config).expect("Failed to create h264 decoder");
decoder
.decode(&h264_data)
.expect("Failed to decode H.264 data");
decoder.finish().expect("Failed to finish decoding");
let frame = decoder
.next_frame()
.expect("Decoding error occurred")
.expect("No decoded frame available");
assert_eq!(frame.width(), 640);
assert_eq!(frame.height(), 480);
assert_eq!(frame.y_plane().len(), frame.y_stride() * frame.height());
assert_eq!(
frame.uv_plane().len(),
frame.uv_stride() * frame.height().div_ceil(2)
);
assert!(frame.y_stride() >= frame.width());
assert!(frame.uv_stride() >= frame.width());
let y_data = frame.y_plane();
let uv_data = frame.uv_plane();
let y_avg = y_data.iter().map(|&x| x as u32).sum::<u32>() / y_data.len() as u32;
assert!(
(10..=30).contains(&y_avg),
"Y average should be around 16 for black, got {}",
y_avg
);
let uv_avg = uv_data.iter().map(|&x| x as u32).sum::<u32>() / uv_data.len() as u32;
assert!(
(70..=140).contains(&uv_avg),
"UV average should be in reasonable range for the encoded frame, got {}",
uv_avg
);
println!(
"Successfully decoded H.264 black frame: {}x{} (stride: {})",
frame.width(),
frame.height(),
frame.y_stride()
);
println!("Y average: {}, UV average: {}", y_avg, uv_avg);
}
#[test]
fn test_decode_av1_black_frame() {
let av1_data = vec![
10, 11, 0, 0, 0, 36, 196, 255, 223, 63, 254, 96, 16, 50, 35, 16, 0, 144, 0, 0, 0, 160, 0, 0, 128, 1, 197, 120, 80, 103, 179, 239, 241, 100,
76, 173, 116, 93, 183, 31, 101, 221, 87, 90, 233, 219, 28, 199, 243, 128,
];
let config = test_decoder_config(DecoderCodec::Av1);
let mut decoder = Decoder::new(config).expect("Failed to create av1 decoder");
decoder
.decode(&av1_data)
.expect("Failed to decode AV1 data");
decoder.finish().expect("Failed to finish decoding");
let frame = decoder
.next_frame()
.expect("Decoding error occurred")
.expect("No decoded frame available");
assert_eq!(frame.width(), 640);
assert_eq!(frame.height(), 480);
assert_eq!(frame.y_plane().len(), frame.y_stride() * frame.height());
assert_eq!(
frame.uv_plane().len(),
frame.uv_stride() * frame.height().div_ceil(2)
);
assert!(frame.y_stride() >= frame.width());
assert!(frame.uv_stride() >= frame.width());
let y_data = frame.y_plane();
let uv_data = frame.uv_plane();
let y_avg = y_data.iter().map(|&x| x as u32).sum::<u32>() / y_data.len() as u32;
assert!(
(10..=30).contains(&y_avg),
"Y average should be around 16 for black, got {}",
y_avg
);
let uv_avg = uv_data.iter().map(|&x| x as u32).sum::<u32>() / uv_data.len() as u32;
assert!(
(70..=140).contains(&uv_avg),
"UV average should be in reasonable range for the encoded frame, got {}",
uv_avg
);
println!(
"Successfully decoded AV1 black frame: {}x{} (stride: {})",
frame.width(),
frame.height(),
frame.y_stride()
);
println!("Y average: {}, UV average: {}", y_avg, uv_avg);
}
#[test]
fn test_decode_vp8_black_frame() {
let vp8_data = vec![
80, 66, 0, 157, 1, 42, 128, 2, 224, 1, 2, 199, 8, 133, 133, 136, 153, 132, 136, 15, 2,
0, 6, 22, 4, 247, 6, 129, 100, 159, 107, 219, 155, 39, 56, 123, 39, 56, 123, 39, 56,
123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39,
56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123,
39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56,
123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39,
56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123,
39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56,
123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39,
56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123,
39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56,
123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39,
56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123,
39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56,
123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39,
56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123,
39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56,
123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39,
56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123,
39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56,
123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39,
56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123,
39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56,
123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39,
56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123,
39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56,
123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 56, 123, 39, 55, 128, 254,
250, 215, 128,
];
let config = test_decoder_config(DecoderCodec::Vp8);
let mut decoder = Decoder::new(config).expect("Failed to create vp8 decoder");
decoder
.decode(&vp8_data)
.expect("Failed to decode VP8 data");
decoder.finish().expect("Failed to finish decoding");
let frame = decoder
.next_frame()
.expect("Decoding error occurred")
.expect("No decoded frame available");
assert_eq!(frame.width(), 640);
assert_eq!(frame.height(), 480);
assert_eq!(frame.y_plane().len(), frame.y_stride() * frame.height());
assert_eq!(
frame.uv_plane().len(),
frame.uv_stride() * frame.height().div_ceil(2)
);
assert!(frame.y_stride() >= frame.width());
assert!(frame.uv_stride() >= frame.width());
let y_data = frame.y_plane();
let uv_data = frame.uv_plane();
let y_avg = y_data.iter().map(|&x| x as u32).sum::<u32>() / y_data.len() as u32;
assert!(
(10..=30).contains(&y_avg),
"Y average should be around 16 for black, got {}",
y_avg
);
let uv_avg = uv_data.iter().map(|&x| x as u32).sum::<u32>() / uv_data.len() as u32;
assert!(
(70..=140).contains(&uv_avg),
"UV average should be in reasonable range for the encoded frame, got {}",
uv_avg
);
println!(
"Successfully decoded VP8 black frame: {}x{} (stride: {})",
frame.width(),
frame.height(),
frame.y_stride()
);
println!("Y average: {}, UV average: {}", y_avg, uv_avg);
}
#[test]
fn test_decode_vp9_black_frame() {
let vp9_data = vec![
130, 73, 131, 66, 0, 39, 240, 29, 246, 0, 56, 36, 28, 24, 74, 16, 0, 80, 97, 246, 58,
246, 128, 92, 209, 238, 0, 0, 0, 0, 0, 20, 103, 26, 154, 224, 98, 35, 126, 68, 120,
240, 227, 199, 143, 30, 28, 238, 113, 218, 24, 0, 103, 26, 154, 224, 98, 35, 126, 68,
120, 240, 227, 199, 143, 30, 28, 238, 113, 218, 24, 0,
];
let config = test_decoder_config(DecoderCodec::Vp9);
let mut decoder = Decoder::new(config).expect("Failed to create vp9 decoder");
decoder
.decode(&vp9_data)
.expect("Failed to decode VP9 data");
decoder.finish().expect("Failed to finish decoding");
let frame = decoder
.next_frame()
.expect("Decoding error occurred")
.expect("No decoded frame available");
assert_eq!(frame.width(), 640);
assert_eq!(frame.height(), 480);
assert_eq!(frame.y_plane().len(), frame.y_stride() * frame.height());
assert_eq!(
frame.uv_plane().len(),
frame.uv_stride() * frame.height().div_ceil(2)
);
assert!(frame.y_stride() >= frame.width());
assert!(frame.uv_stride() >= frame.width());
let y_data = frame.y_plane();
let uv_data = frame.uv_plane();
let y_avg = y_data.iter().map(|&x| x as u32).sum::<u32>() / y_data.len() as u32;
assert!(
(10..=30).contains(&y_avg),
"Y average should be around 16 for black, got {}",
y_avg
);
let uv_avg = uv_data.iter().map(|&x| x as u32).sum::<u32>() / uv_data.len() as u32;
assert!(
(70..=140).contains(&uv_avg),
"UV average should be in reasonable range for the encoded frame, got {}",
uv_avg
);
println!(
"Successfully decoded VP9 black frame: {}x{} (stride: {})",
frame.width(),
frame.height(),
frame.y_stride()
);
println!("Y average: {}, UV average: {}", y_avg, uv_avg);
}
}