#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
use crate::gpu_libs;
use std::ffi::c_void;
use std::ptr;
const NV_ENC_SUCCESS: u32 = 0;
const NV_ENC_ERR_NEED_MORE_INPUT: u32 = 10;
const NVENCAPI_MAJOR_VERSION: u32 = 12;
const NVENCAPI_MINOR_VERSION: u32 = 1;
const NVENCAPI_VERSION: u32 = NVENCAPI_MAJOR_VERSION | (NVENCAPI_MINOR_VERSION << 24);
const fn nvencapi_struct_version(typ_ver: u32) -> u32 {
NVENCAPI_VERSION | (typ_ver << 16) | (0x7 << 28)
}
const NV_ENC_OPEN_ENCODE_SESSION_EX_VER: u32 = nvencapi_struct_version(1);
const NV_ENC_INITIALIZE_PARAMS_VER: u32 = nvencapi_struct_version(6) | (1 << 31);
const NV_ENC_PRESET_CONFIG_VER: u32 = nvencapi_struct_version(4) | (1 << 31);
const NV_ENC_CONFIG_VER: u32 = nvencapi_struct_version(8) | (1 << 31);
const NV_ENC_CREATE_INPUT_BUFFER_VER: u32 = nvencapi_struct_version(1);
const NV_ENC_CREATE_BITSTREAM_BUFFER_VER: u32 = nvencapi_struct_version(1);
const NV_ENC_PIC_PARAMS_VER: u32 = nvencapi_struct_version(6) | (1 << 31);
const NV_ENC_LOCK_BITSTREAM_VER: u32 = nvencapi_struct_version(1) | (1 << 31);
const NV_ENC_BUFFER_FORMAT_NV12: u32 = 0x00000001;
const NV_ENC_BUFFER_FORMAT_ARGB: u32 = 0x01000000; const NV_ENC_BUFFER_FORMAT_ABGR: u32 = 0x10000000;
const NV_ENC_CAPS_PARAM_VER: u32 = nvencapi_struct_version(1);
const NV_ENC_CAPS_PARAM_SIZE: usize = 256;
const NV_ENC_CAPS_SUPPORT_YUV444_ENCODE: u32 = 15;
const NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR: u32 = 0x01;
const NV_ENC_REGISTER_RESOURCE_VER: u32 = nvencapi_struct_version(4);
const NV_ENC_MAP_INPUT_RESOURCE_VER: u32 = nvencapi_struct_version(4);
const NVENC_REGISTER_RESOURCE_SIZE: usize = 2048;
const NVENC_MAP_INPUT_RESOURCE_SIZE: usize = 2048;
const NV_ENC_CODEC_H264_GUID: NvGuid = NvGuid(
0x6BC82762,
0x4E63,
0x4CA4,
[0xAA, 0x85, 0x1E, 0x50, 0xF3, 0x21, 0xF6, 0xBF],
);
const NV_ENC_CODEC_AV1_GUID: NvGuid = NvGuid(
0x0A352289,
0x0AA7,
0x4759,
[0x86, 0x2D, 0x5D, 0x15, 0xCD, 0x16, 0xD2, 0x54],
);
const NV_ENC_PRESET_P1_GUID: NvGuid = NvGuid(
0xFC0A8D3E,
0x45F8,
0x4CF8,
[0x80, 0xC7, 0x29, 0x88, 0x71, 0x59, 0x0E, 0xBF],
);
const NV_ENC_H264_PROFILE_HIGH_444_GUID: NvGuid = NvGuid(
0x7AC663CB,
0xA598,
0x49D8,
[0xB1, 0x0E, 0x10, 0x38, 0x6E, 0x79, 0xCB, 0x1B],
);
const NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY: u32 = 3;
const NV_ENC_PIC_TYPE_I: u32 = 2;
const NV_ENC_PIC_TYPE_IDR: u32 = 3;
const NV_ENC_PIC_FLAGS_FORCEIDR: u32 = 2;
const NV_ENC_PARAMS_RC_CONSTQP: u32 = 0;
#[repr(C)]
#[derive(Clone, Copy)]
struct NvGuid(u32, u16, u16, [u8; 8]);
#[repr(C)]
struct NvEncFunctionList {
version: u32,
_reserved: u32,
nvEncOpenEncodeSession: *const c_void,
nvEncGetEncodeGUIDCount: *const c_void,
nvEncGetEncodeGUIDs: *const c_void,
nvEncGetEncodeProfileGUIDCount: *const c_void,
nvEncGetEncodeProfileGUIDs: *const c_void,
nvEncGetInputFormatCount: *const c_void,
nvEncGetInputFormats: *const c_void,
nvEncGetEncodeCaps: unsafe extern "C" fn(
encoder: *mut c_void,
encode_guid: NvGuid,
caps_param: *mut c_void,
caps_val: *mut i32,
) -> u32,
nvEncGetEncodePresetCount: *const c_void,
nvEncGetEncodePresetGUIDs: *const c_void,
nvEncGetEncodePresetConfig: *const c_void,
nvEncInitializeEncoder: unsafe extern "C" fn(encoder: *mut c_void, params: *mut c_void) -> u32,
nvEncCreateInputBuffer: unsafe extern "C" fn(encoder: *mut c_void, params: *mut c_void) -> u32,
nvEncDestroyInputBuffer: unsafe extern "C" fn(encoder: *mut c_void, buffer: *mut c_void) -> u32,
nvEncCreateBitstreamBuffer:
unsafe extern "C" fn(encoder: *mut c_void, params: *mut c_void) -> u32,
nvEncDestroyBitstreamBuffer:
unsafe extern "C" fn(encoder: *mut c_void, buffer: *mut c_void) -> u32,
nvEncEncodePicture: unsafe extern "C" fn(encoder: *mut c_void, params: *mut c_void) -> u32,
nvEncLockBitstream: unsafe extern "C" fn(encoder: *mut c_void, params: *mut c_void) -> u32,
nvEncUnlockBitstream: unsafe extern "C" fn(encoder: *mut c_void, buffer: *mut c_void) -> u32,
nvEncLockInputBuffer: unsafe extern "C" fn(encoder: *mut c_void, params: *mut c_void) -> u32,
nvEncUnlockInputBuffer: unsafe extern "C" fn(encoder: *mut c_void, buffer: *mut c_void) -> u32,
nvEncGetEncodeStats: *const c_void,
nvEncGetSequenceParams: *const c_void,
nvEncRegisterAsyncEvent: *const c_void,
nvEncUnregisterAsyncEvent: *const c_void,
nvEncMapInputResource: unsafe extern "C" fn(encoder: *mut c_void, params: *mut c_void) -> u32,
nvEncUnmapInputResource:
unsafe extern "C" fn(encoder: *mut c_void, resource: *mut c_void) -> u32,
nvEncDestroyEncoder: unsafe extern "C" fn(encoder: *mut c_void) -> u32,
nvEncInvalidateRefFrames: *const c_void,
nvEncOpenEncodeSessionEx:
unsafe extern "C" fn(params: *mut c_void, encoder: *mut *mut c_void) -> u32,
nvEncRegisterResource: unsafe extern "C" fn(encoder: *mut c_void, params: *mut c_void) -> u32,
nvEncUnregisterResource:
unsafe extern "C" fn(encoder: *mut c_void, resource: *mut c_void) -> u32,
nvEncReconfigureEncoder: *const c_void,
_reserved1: *const c_void,
nvEncCreateMVBuffer: *const c_void,
nvEncDestroyMVBuffer: *const c_void,
nvEncRunMotionEstimationOnly: *const c_void,
nvEncGetLastErrorString: *const c_void,
nvEncSetIOCudaStreams: *const c_void,
nvEncGetEncodePresetConfigEx: unsafe extern "C" fn(
encoder: *mut c_void,
encode_guid: NvGuid,
preset_guid: NvGuid,
tuning_info: u32,
preset_config: *mut c_void,
) -> u32,
nvEncGetSequenceParamEx: *const c_void,
nvEncLookaheadPicture: *const c_void,
_future: [*const c_void; 64],
}
unsafe impl Send for NvEncFunctionList {}
unsafe impl Sync for NvEncFunctionList {}
const NVENC_OPEN_ENCODE_SESSION_EX_SIZE: usize = 1552;
const NVENC_CONFIG_SIZE: usize = 3584;
const NVENC_PRESET_CONFIG_SIZE: usize = 5128;
const NVENC_INITIALIZE_PARAMS_SIZE: usize = 1808;
const NVENC_CREATE_INPUT_BUFFER_SIZE: usize = 776;
const NVENC_CREATE_BITSTREAM_BUFFER_SIZE: usize = 776;
const NVENC_PIC_PARAMS_SIZE: usize = 3360;
const NVENC_LOCK_BITSTREAM_SIZE: usize = 1552;
fn w32(buf: &mut [u8], off: usize, val: u32) {
buf[off..off + 4].copy_from_slice(&val.to_ne_bytes());
}
fn w64(buf: &mut [u8], off: usize, val: u64) {
buf[off..off + 8].copy_from_slice(&val.to_ne_bytes());
}
fn wptr(buf: &mut [u8], off: usize, val: *mut c_void) {
buf[off..off + 8].copy_from_slice(&(val as u64).to_ne_bytes());
}
fn wguid(buf: &mut [u8], off: usize, g: NvGuid) {
w32(buf, off, g.0);
buf[off + 4..off + 6].copy_from_slice(&g.1.to_ne_bytes());
buf[off + 6..off + 8].copy_from_slice(&g.2.to_ne_bytes());
buf[off + 8..off + 16].copy_from_slice(&g.3);
}
fn r32(buf: &[u8], off: usize) -> u32 {
u32::from_ne_bytes(buf[off..off + 4].try_into().unwrap())
}
fn rptr(buf: &[u8], off: usize) -> *mut c_void {
u64::from_ne_bytes(buf[off..off + 8].try_into().unwrap()) as *mut c_void
}
pub struct NvencDirectEncoder {
encoder: *mut c_void,
input_buffer: *mut c_void, output_buffer: *mut c_void,
width: u32,
height: u32,
frame_idx: u32,
force_idr: bool,
codec_flag: u8, fns: &'static NvEncFunctionList,
cuda_ctx: gpu_libs::CUcontext,
cuda_devptr: gpu_libs::CUdeviceptr,
cuda_registered: *mut c_void, cuda_pitch: u32, pinned_host: *mut u8, pinned_size: usize,
cuda_devptr_abgr: gpu_libs::CUdeviceptr,
cuda_registered_abgr: *mut c_void,
cuda_devptr_nv12: gpu_libs::CUdeviceptr,
cuda_registered_nv12: *mut c_void,
nv12_pitch: u32,
verbose: bool,
h264_sps_pps: Vec<u8>,
}
unsafe impl Send for NvencDirectEncoder {}
impl NvencDirectEncoder {
pub fn try_new(
codec: &str,
width: u32,
height: u32,
qp: u32,
verbose: bool,
chroma: crate::surface_encoder::ChromaSubsampling,
) -> Result<Self, String> {
let cuda = gpu_libs::cuda().map_err(|e| format!("CUDA: {e}"))?;
let nvenc_fns = gpu_libs::nvenc().map_err(|e| format!("NVENC: {e}"))?;
let mut status = unsafe { (cuda.cuInit)(0) };
if status != 0 {
return Err(format!("cuInit failed: {status}"));
}
let cuda_device_idx: i32 = std::env::var("BLIT_CUDA_DEVICE")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(0);
let mut device: gpu_libs::CUdevice = 0;
status = unsafe { (cuda.cuDeviceGet)(&mut device, cuda_device_idx) };
if status != 0 {
return Err(format!("cuDeviceGet({cuda_device_idx}) failed: {status}"));
}
let mut ctx: gpu_libs::CUcontext = ptr::null_mut();
status = unsafe { (cuda.cuCtxCreate_v2)(&mut ctx, 0, device) };
if status != 0 {
return Err(format!("cuCtxCreate failed: {status}"));
}
static NVENC_FN_LIST: std::sync::OnceLock<Result<NvEncFunctionList, String>> =
std::sync::OnceLock::new();
let result = NVENC_FN_LIST.get_or_init(|| {
let fn_list_ver = nvencapi_struct_version(2);
let mut fl = std::mem::MaybeUninit::<NvEncFunctionList>::zeroed();
unsafe { (*fl.as_mut_ptr()).version = fn_list_ver };
let nv_status =
unsafe { (nvenc_fns.NvEncodeAPICreateInstance)(fl.as_mut_ptr().cast()) };
let fl = unsafe { fl.assume_init() };
if nv_status != NV_ENC_SUCCESS {
return Err(format!("NvEncodeAPICreateInstance failed: {nv_status}"));
}
Ok(fl)
});
let fns = match result {
Ok(fl) => fl,
Err(e) => return Err(e.clone()),
};
let fns: &'static NvEncFunctionList =
unsafe { &*(fns as *const NvEncFunctionList) };
let mut open_buf = vec![0u8; NVENC_OPEN_ENCODE_SESSION_EX_SIZE];
w32(&mut open_buf, 0, NV_ENC_OPEN_ENCODE_SESSION_EX_VER); w32(&mut open_buf, 4, 1); wptr(&mut open_buf, 8, ctx); w32(&mut open_buf, 24, NVENCAPI_VERSION);
let mut encoder: *mut c_void = ptr::null_mut();
let nv_status = unsafe {
(fns.nvEncOpenEncodeSessionEx)(open_buf.as_mut_ptr() as *mut c_void, &mut encoder)
};
if nv_status != NV_ENC_SUCCESS {
return Err(format!("nvEncOpenEncodeSessionEx failed: {nv_status}"));
}
let (codec_guid, codec_flag) = match codec {
"h264" => (
NV_ENC_CODEC_H264_GUID,
blit_remote::SURFACE_FRAME_CODEC_H264,
),
"av1" => (NV_ENC_CODEC_AV1_GUID, blit_remote::SURFACE_FRAME_CODEC_AV1),
_ => return Err(format!("unsupported NVENC codec: {codec}")),
};
if chroma.is_444() {
let mut caps_param = vec![0u8; NV_ENC_CAPS_PARAM_SIZE];
w32(&mut caps_param, 0, NV_ENC_CAPS_PARAM_VER);
w32(&mut caps_param, 4, NV_ENC_CAPS_SUPPORT_YUV444_ENCODE);
let mut caps_val: i32 = 0;
let nv_status = unsafe {
(fns.nvEncGetEncodeCaps)(
encoder,
codec_guid,
caps_param.as_mut_ptr() as *mut c_void,
&mut caps_val,
)
};
if nv_status != NV_ENC_SUCCESS || caps_val == 0 {
unsafe { (fns.nvEncDestroyEncoder)(encoder) };
return Err(format!(
"NVENC {codec} does not support 4:4:4 encoding on this GPU"
));
}
}
let mut preset_buf = vec![0u8; NVENC_PRESET_CONFIG_SIZE];
w32(&mut preset_buf, 0, NV_ENC_PRESET_CONFIG_VER); w32(&mut preset_buf, 8, NV_ENC_CONFIG_VER);
let nv_status = unsafe {
(fns.nvEncGetEncodePresetConfigEx)(
encoder,
codec_guid,
NV_ENC_PRESET_P1_GUID,
NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY,
preset_buf.as_mut_ptr() as *mut c_void,
)
};
if nv_status != NV_ENC_SUCCESS {
return Err(format!("nvEncGetEncodePresetConfigEx failed: {nv_status}"));
}
let mut config_buf = vec![0u8; NVENC_CONFIG_SIZE];
config_buf.copy_from_slice(&preset_buf[8..8 + NVENC_CONFIG_SIZE]);
w32(&mut config_buf, 20, 120); w32(&mut config_buf, 24, 1); w32(&mut config_buf, 40, NV_ENC_PARAMS_RC_CONSTQP);
w32(&mut config_buf, 44, qp); w32(&mut config_buf, 48, qp); w32(&mut config_buf, 52, qp);
if chroma.is_444() && codec == "h264" {
wguid(&mut config_buf, 4, NV_ENC_H264_PROFILE_HIGH_444_GUID);
}
let mut init_buf = vec![0u8; NVENC_INITIALIZE_PARAMS_SIZE];
w32(&mut init_buf, 0, NV_ENC_INITIALIZE_PARAMS_VER);
wguid(&mut init_buf, 4, codec_guid); wguid(&mut init_buf, 20, NV_ENC_PRESET_P1_GUID); w32(&mut init_buf, 36, width); w32(&mut init_buf, 40, height); w32(&mut init_buf, 44, width); w32(&mut init_buf, 48, height); w32(&mut init_buf, 52, 60); w32(&mut init_buf, 56, 1); w32(&mut init_buf, 64, 1); wptr(&mut init_buf, 88, config_buf.as_mut_ptr() as *mut c_void); w32(&mut init_buf, 96, width); w32(&mut init_buf, 100, height); w32(&mut init_buf, 136, NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY);
let nv_status =
unsafe { (fns.nvEncInitializeEncoder)(encoder, init_buf.as_mut_ptr() as *mut c_void) };
if nv_status != NV_ENC_SUCCESS {
return Err(format!("nvEncInitializeEncoder failed: {nv_status}"));
}
let mut input_buf = vec![0u8; NVENC_CREATE_INPUT_BUFFER_SIZE];
w32(&mut input_buf, 0, NV_ENC_CREATE_INPUT_BUFFER_VER);
w32(&mut input_buf, 4, width); w32(&mut input_buf, 8, height); w32(&mut input_buf, 16, NV_ENC_BUFFER_FORMAT_ARGB);
let nv_status =
unsafe { (fns.nvEncCreateInputBuffer)(encoder, input_buf.as_mut_ptr() as *mut c_void) };
if nv_status != NV_ENC_SUCCESS {
return Err(format!("nvEncCreateInputBuffer failed: {nv_status}"));
}
let input_buffer_ptr = rptr(&input_buf, 24);
let mut output_buf = vec![0u8; NVENC_CREATE_BITSTREAM_BUFFER_SIZE];
w32(&mut output_buf, 0, NV_ENC_CREATE_BITSTREAM_BUFFER_VER);
let nv_status = unsafe {
(fns.nvEncCreateBitstreamBuffer)(encoder, output_buf.as_mut_ptr() as *mut c_void)
};
if nv_status != NV_ENC_SUCCESS {
return Err(format!("nvEncCreateBitstreamBuffer failed: {nv_status}"));
}
let output_buffer_ptr = rptr(&output_buf, 16);
let mut cuda_devptr: gpu_libs::CUdeviceptr = 0;
let mut cuda_pitch_bytes: usize = 0;
status = unsafe {
(cuda.cuMemAllocPitch_v2)(
&mut cuda_devptr,
&mut cuda_pitch_bytes,
(width * 4) as usize, height as usize,
16, )
};
if status != 0 {
return Err(format!("cuMemAllocPitch failed: {status}"));
}
let cuda_pitch = cuda_pitch_bytes as u32;
let frame_size = cuda_pitch_bytes * height as usize;
let mut pinned_host: *mut c_void = ptr::null_mut();
status = unsafe { (cuda.cuMemAllocHost_v2)(&mut pinned_host, frame_size) };
if status != 0 {
unsafe { (cuda.cuMemFree_v2)(cuda_devptr) };
return Err(format!("cuMemAllocHost failed: {status}"));
}
let mut reg_buf = vec![0u8; NVENC_REGISTER_RESOURCE_SIZE];
w32(&mut reg_buf, 0, NV_ENC_REGISTER_RESOURCE_VER);
w32(&mut reg_buf, 4, NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR);
w32(&mut reg_buf, 8, width);
w32(&mut reg_buf, 12, height);
w32(&mut reg_buf, 16, cuda_pitch);
wptr(&mut reg_buf, 24, cuda_devptr as *mut c_void);
w32(&mut reg_buf, 40, NV_ENC_BUFFER_FORMAT_ARGB);
let nv_status =
unsafe { (fns.nvEncRegisterResource)(encoder, reg_buf.as_mut_ptr() as *mut c_void) };
if nv_status != NV_ENC_SUCCESS {
unsafe { (cuda.cuMemFree_v2)(cuda_devptr) };
return Err(format!("nvEncRegisterResource failed: {nv_status}"));
}
let cuda_registered = rptr(®_buf, 32);
let mut cuda_devptr_abgr: gpu_libs::CUdeviceptr = 0;
let mut abgr_pitch_bytes: usize = 0;
status = unsafe {
(cuda.cuMemAllocPitch_v2)(
&mut cuda_devptr_abgr,
&mut abgr_pitch_bytes,
(width * 4) as usize,
height as usize,
16,
)
};
if status != 0 {
return Err(format!("cuMemAllocPitch (ABGR) failed: {status}"));
}
debug_assert_eq!(abgr_pitch_bytes, cuda_pitch_bytes);
let mut reg_abgr = vec![0u8; NVENC_REGISTER_RESOURCE_SIZE];
w32(&mut reg_abgr, 0, NV_ENC_REGISTER_RESOURCE_VER);
w32(&mut reg_abgr, 4, NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR);
w32(&mut reg_abgr, 8, width);
w32(&mut reg_abgr, 12, height);
w32(&mut reg_abgr, 16, cuda_pitch);
wptr(&mut reg_abgr, 24, cuda_devptr_abgr as *mut c_void);
w32(&mut reg_abgr, 40, NV_ENC_BUFFER_FORMAT_ABGR);
let nv_status =
unsafe { (fns.nvEncRegisterResource)(encoder, reg_abgr.as_mut_ptr() as *mut c_void) };
if nv_status != NV_ENC_SUCCESS {
unsafe { (cuda.cuMemFree_v2)(cuda_devptr_abgr) };
return Err(format!("nvEncRegisterResource (ABGR) failed: {nv_status}"));
}
let cuda_registered_abgr = rptr(®_abgr, 32);
let mut cuda_devptr_nv12: gpu_libs::CUdeviceptr = 0;
let mut nv12_pitch_bytes: usize = 0;
let nv12_alloc_h = height + height / 2;
status = unsafe {
(cuda.cuMemAllocPitch_v2)(
&mut cuda_devptr_nv12,
&mut nv12_pitch_bytes,
width as usize, nv12_alloc_h as usize,
16,
)
};
if status != 0 {
return Err(format!("cuMemAllocPitch (NV12) failed: {status}"));
}
let nv12_pitch = nv12_pitch_bytes as u32;
let mut reg_nv12 = vec![0u8; NVENC_REGISTER_RESOURCE_SIZE];
w32(&mut reg_nv12, 0, NV_ENC_REGISTER_RESOURCE_VER);
w32(&mut reg_nv12, 4, NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR);
w32(&mut reg_nv12, 8, width);
w32(&mut reg_nv12, 12, height);
w32(&mut reg_nv12, 16, nv12_pitch);
wptr(&mut reg_nv12, 24, cuda_devptr_nv12 as *mut c_void);
w32(&mut reg_nv12, 40, NV_ENC_BUFFER_FORMAT_NV12);
let nv_status =
unsafe { (fns.nvEncRegisterResource)(encoder, reg_nv12.as_mut_ptr() as *mut c_void) };
if nv_status != NV_ENC_SUCCESS {
unsafe { (cuda.cuMemFree_v2)(cuda_devptr_nv12) };
return Err(format!("nvEncRegisterResource (NV12) failed: {nv_status}"));
}
let cuda_registered_nv12 = rptr(®_nv12, 32);
if verbose {
eprintln!(
"[nvenc-direct] initialized {codec} encoder for {width}x{height} pitch={cuda_pitch} nv12_pitch={nv12_pitch} (CUDA upload)"
);
}
Ok(Self {
encoder,
input_buffer: input_buffer_ptr,
output_buffer: output_buffer_ptr,
width,
height,
frame_idx: 0,
force_idr: false,
codec_flag,
fns,
cuda_ctx: ctx,
cuda_devptr,
cuda_registered,
cuda_pitch,
pinned_host: pinned_host as *mut u8,
pinned_size: frame_size,
cuda_devptr_abgr,
cuda_registered_abgr,
cuda_devptr_nv12,
cuda_registered_nv12,
nv12_pitch,
verbose,
h264_sps_pps: Vec::new(),
})
}
pub fn request_keyframe(&mut self) {
self.force_idr = true;
}
fn is_keyframe_pic_type(&self, pic_type: u32) -> bool {
if pic_type == NV_ENC_PIC_TYPE_IDR {
return true;
}
if self.codec_flag == blit_remote::SURFACE_FRAME_CODEC_AV1 && pic_type == NV_ENC_PIC_TYPE_I
{
return true;
}
false
}
fn ensure_h264_sps_pps(&mut self, data: &mut Vec<u8>, is_idr: bool) {
if self.codec_flag != blit_remote::SURFACE_FRAME_CODEC_H264 || !is_idr {
return;
}
let has_sps_pps = h264_has_sps_pps(data);
if has_sps_pps {
if self.h264_sps_pps.is_empty()
&& let Some(prefix) = h264_extract_sps_pps_prefix(data)
{
self.h264_sps_pps = prefix;
}
} else if !self.h264_sps_pps.is_empty() {
let mut full = self.h264_sps_pps.clone();
full.append(data);
*data = full;
}
}
#[cfg(target_os = "linux")]
#[allow(clippy::too_many_arguments)]
pub fn encode_dmabuf_fd(
&mut self,
fd: std::os::fd::RawFd,
fourcc: u32,
_modifier: u64,
stride: u32,
_offset: u32,
src_width: u32,
src_height: u32,
) -> Option<(Vec<u8>, bool)> {
let cuda = gpu_libs::cuda().ok()?;
let cu_import = cuda.cuImportExternalMemory?;
let cu_get_buf = cuda.cuExternalMemoryGetMappedBuffer?;
let cu_destroy = cuda.cuDestroyExternalMemory?;
let nvenc_fmt = match fourcc {
f if f == blit_compositor::drm_fourcc::ARGB8888
|| f == blit_compositor::drm_fourcc::XRGB8888 =>
{
NV_ENC_BUFFER_FORMAT_ARGB
}
f if f == blit_compositor::drm_fourcc::ABGR8888
|| f == blit_compositor::drm_fourcc::XBGR8888 =>
{
NV_ENC_BUFFER_FORMAT_ABGR
}
_ => return None, };
let buf_size = unsafe { libc::lseek(fd, 0, libc::SEEK_END) };
if buf_size <= 0 {
return None;
}
let buf_size = buf_size as u64;
let dup_fd = unsafe { libc::dup(fd) };
if dup_fd < 0 {
return None;
}
unsafe { (cuda.cuCtxPushCurrent_v2)(self.cuda_ctx) };
const CU_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD: u32 = 1;
let mut handle_desc = [0u8; 128];
handle_desc[0..4].copy_from_slice(&CU_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD.to_ne_bytes());
handle_desc[8..12].copy_from_slice(&dup_fd.to_ne_bytes());
handle_desc[16..24].copy_from_slice(&buf_size.to_ne_bytes());
let mut ext_mem: gpu_libs::CUexternalMemory = ptr::null_mut();
let status = unsafe { cu_import(&mut ext_mem, handle_desc.as_ptr() as *const _) };
if status != 0 {
unsafe { libc::close(dup_fd) };
let mut dummy: gpu_libs::CUcontext = ptr::null_mut();
unsafe { (cuda.cuCtxPopCurrent_v2)(&mut dummy) };
static LOGGED: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
if !LOGGED.swap(true, std::sync::atomic::Ordering::Relaxed) {
eprintln!("[nvenc-dmabuf] cuImportExternalMemory failed: {status}");
}
return None;
}
let mut buf_desc = [0u8; 128];
buf_desc[8..16].copy_from_slice(&buf_size.to_ne_bytes());
let mut devptr: gpu_libs::CUdeviceptr = 0;
let status = unsafe { cu_get_buf(&mut devptr, ext_mem, buf_desc.as_ptr() as *const _) };
if status != 0 {
unsafe { cu_destroy(ext_mem) };
let mut dummy: gpu_libs::CUcontext = ptr::null_mut();
unsafe { (cuda.cuCtxPopCurrent_v2)(&mut dummy) };
eprintln!("[nvenc-dmabuf] cuExternalMemoryGetMappedBuffer failed: {status}");
return None;
}
let enc_w = src_width;
let enc_h = src_height;
let pitch = stride;
let mut reg_buf = vec![0u8; NVENC_REGISTER_RESOURCE_SIZE];
w32(&mut reg_buf, 0, NV_ENC_REGISTER_RESOURCE_VER);
w32(&mut reg_buf, 4, NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR);
w32(&mut reg_buf, 8, enc_w);
w32(&mut reg_buf, 12, enc_h);
w32(&mut reg_buf, 16, pitch);
wptr(&mut reg_buf, 24, devptr as *mut c_void);
w32(&mut reg_buf, 40, nvenc_fmt);
let nv_status = unsafe {
(self.fns.nvEncRegisterResource)(self.encoder, reg_buf.as_mut_ptr() as *mut c_void)
};
if nv_status != NV_ENC_SUCCESS {
unsafe { cu_destroy(ext_mem) };
let mut dummy: gpu_libs::CUcontext = ptr::null_mut();
unsafe { (cuda.cuCtxPopCurrent_v2)(&mut dummy) };
eprintln!("[nvenc-dmabuf] nvEncRegisterResource failed: {nv_status}");
return None;
}
let registered = rptr(®_buf, 32);
let mut map_buf = vec![0u8; NVENC_MAP_INPUT_RESOURCE_SIZE];
w32(&mut map_buf, 0, NV_ENC_MAP_INPUT_RESOURCE_VER);
wptr(&mut map_buf, 16, registered);
let nv_status = unsafe {
(self.fns.nvEncMapInputResource)(self.encoder, map_buf.as_mut_ptr() as *mut c_void)
};
if nv_status != NV_ENC_SUCCESS {
unsafe {
(self.fns.nvEncUnregisterResource)(self.encoder, registered);
cu_destroy(ext_mem);
}
let mut dummy: gpu_libs::CUcontext = ptr::null_mut();
unsafe { (cuda.cuCtxPopCurrent_v2)(&mut dummy) };
eprintln!("[nvenc-dmabuf] nvEncMapInputResource failed: {nv_status}");
return None;
}
let mapped_resource = rptr(&map_buf, 24);
let mut pic_buf = vec![0u8; NVENC_PIC_PARAMS_SIZE];
w32(&mut pic_buf, 0, NV_ENC_PIC_PARAMS_VER);
w32(&mut pic_buf, 4, enc_w);
w32(&mut pic_buf, 8, enc_h);
w32(&mut pic_buf, 12, pitch);
w32(&mut pic_buf, 20, self.frame_idx);
w64(&mut pic_buf, 24, self.frame_idx as u64);
wptr(&mut pic_buf, 40, mapped_resource);
wptr(&mut pic_buf, 48, self.output_buffer);
w32(&mut pic_buf, 64, nvenc_fmt);
w32(&mut pic_buf, 68, 1);
if self.force_idr {
w32(&mut pic_buf, 16, NV_ENC_PIC_FLAGS_FORCEIDR | 0x4);
w32(&mut pic_buf, 72, NV_ENC_PIC_TYPE_IDR);
}
self.frame_idx += 1;
let nv_status = unsafe {
(self.fns.nvEncEncodePicture)(self.encoder, pic_buf.as_mut_ptr() as *mut c_void)
};
let result = if nv_status == NV_ENC_SUCCESS {
self.force_idr = false;
let mut lock_buf = vec![0u8; NVENC_LOCK_BITSTREAM_SIZE];
w32(&mut lock_buf, 0, NV_ENC_LOCK_BITSTREAM_VER);
wptr(&mut lock_buf, 8, self.output_buffer);
let lock_status = unsafe {
(self.fns.nvEncLockBitstream)(self.encoder, lock_buf.as_mut_ptr() as *mut c_void)
};
if lock_status == NV_ENC_SUCCESS {
let size = r32(&lock_buf, 36) as usize;
let buf_ptr = rptr(&lock_buf, 56) as *const u8;
let nal_data = if !buf_ptr.is_null() && size > 0 {
unsafe { std::slice::from_raw_parts(buf_ptr, size) }.to_vec()
} else {
Vec::new()
};
let is_idr = self.is_keyframe_pic_type(r32(&lock_buf, 64));
unsafe { (self.fns.nvEncUnlockBitstream)(self.encoder, self.output_buffer) };
if nal_data.is_empty() {
None
} else {
let mut nal_data = nal_data;
self.ensure_h264_sps_pps(&mut nal_data, is_idr);
Some((nal_data, is_idr))
}
} else {
None
}
} else {
if nv_status != NV_ENC_ERR_NEED_MORE_INPUT {
eprintln!("[nvenc-dmabuf] nvEncEncodePicture failed: {nv_status}");
}
None
};
unsafe {
(self.fns.nvEncUnmapInputResource)(self.encoder, mapped_resource);
(self.fns.nvEncUnregisterResource)(self.encoder, registered);
cu_destroy(ext_mem);
}
let mut dummy: gpu_libs::CUcontext = ptr::null_mut();
unsafe { (cuda.cuCtxPopCurrent_v2)(&mut dummy) };
if result.is_some() {
static LOGGED_OK: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
if !LOGGED_OK.swap(true, std::sync::atomic::Ordering::Relaxed) && self.verbose {
eprintln!(
"[nvenc-dmabuf] zero-copy encode ok {src_width}x{src_height} stride={stride}"
);
}
}
result
}
pub fn codec_flag(&self) -> u8 {
self.codec_flag
}
pub fn encode_bgra_padded(
&mut self,
bgra: &[u8],
src_w: usize,
src_h: usize,
) -> Option<(Vec<u8>, bool)> {
let t0 = std::time::Instant::now();
let enc_w = self.width as usize;
let enc_h = self.height as usize;
let pitch = self.cuda_pitch as usize; let frame_bytes = pitch * enc_h;
assert!(frame_bytes <= self.pinned_size);
let dst = self.pinned_host;
let src_row_bytes = src_w * 4;
let copy_bytes = (enc_w.min(src_w)) * 4;
for row in 0..enc_h {
let sr = row.min(src_h.saturating_sub(1));
let src_start = sr * src_row_bytes;
let dst_off = row * pitch;
unsafe {
ptr::copy_nonoverlapping(
bgra.as_ptr().add(src_start),
dst.add(dst_off),
copy_bytes,
);
}
if enc_w > src_w {
let last = unsafe {
std::slice::from_raw_parts(bgra.as_ptr().add(src_start + (src_w - 1) * 4), 4)
};
for col in src_w..enc_w {
let off = dst_off + col * 4;
unsafe { ptr::copy_nonoverlapping(last.as_ptr(), dst.add(off), 4) };
}
}
let used = enc_w * 4;
if used < pitch {
unsafe { ptr::write_bytes(dst.add(dst_off + used), 0, pitch - used) };
}
}
let t_write = t0.elapsed();
let result = self.upload_and_encode(
self.cuda_devptr,
self.cuda_registered,
NV_ENC_BUFFER_FORMAT_ARGB,
frame_bytes,
);
let t_total = t0.elapsed();
if t_total.as_millis() > 50 && self.verbose {
eprintln!(
"[nvenc-timing] {}x{} (src {}x{}) write={:.1}ms encode={:.1}ms total={:.1}ms",
self.width,
self.height,
src_w,
src_h,
t_write.as_secs_f64() * 1000.0,
(t_total - t_write).as_secs_f64() * 1000.0,
t_total.as_secs_f64() * 1000.0,
);
}
result
}
fn upload_and_encode(
&mut self,
devptr: gpu_libs::CUdeviceptr,
registered: *mut c_void,
buf_fmt: u32,
upload_bytes: usize,
) -> Option<(Vec<u8>, bool)> {
let pitch = self.cuda_pitch;
let cuda = crate::gpu_libs::cuda().expect("CUDA loaded during init");
unsafe { (cuda.cuCtxPushCurrent_v2)(self.cuda_ctx) };
let status = unsafe {
(cuda.cuMemcpyHtoD_v2)(devptr, self.pinned_host as *const c_void, upload_bytes)
};
if status != 0 {
eprintln!("[nvenc-direct] cuMemcpyHtoD failed: {status}");
let mut dummy: gpu_libs::CUcontext = ptr::null_mut();
unsafe { (cuda.cuCtxPopCurrent_v2)(&mut dummy) };
return None;
}
unsafe { (cuda.cuStreamSynchronize)(ptr::null_mut()) };
let mut map_buf = vec![0u8; NVENC_MAP_INPUT_RESOURCE_SIZE];
w32(&mut map_buf, 0, NV_ENC_MAP_INPUT_RESOURCE_VER);
wptr(&mut map_buf, 16, registered);
let status = unsafe {
(self.fns.nvEncMapInputResource)(self.encoder, map_buf.as_mut_ptr() as *mut c_void)
};
if status != NV_ENC_SUCCESS {
eprintln!("[nvenc-direct] nvEncMapInputResource failed: {status}");
let mut dummy: gpu_libs::CUcontext = ptr::null_mut();
unsafe { (cuda.cuCtxPopCurrent_v2)(&mut dummy) };
return None;
}
let mapped_resource = rptr(&map_buf, 24);
let mut pic_buf = vec![0u8; NVENC_PIC_PARAMS_SIZE];
w32(&mut pic_buf, 0, NV_ENC_PIC_PARAMS_VER);
w32(&mut pic_buf, 4, self.width);
w32(&mut pic_buf, 8, self.height);
w32(&mut pic_buf, 12, pitch);
w32(&mut pic_buf, 20, self.frame_idx);
w64(&mut pic_buf, 24, self.frame_idx as u64);
wptr(&mut pic_buf, 40, mapped_resource);
wptr(&mut pic_buf, 48, self.output_buffer);
w32(&mut pic_buf, 64, buf_fmt);
w32(&mut pic_buf, 68, 1);
if self.force_idr {
w32(&mut pic_buf, 16, NV_ENC_PIC_FLAGS_FORCEIDR | 0x4);
w32(&mut pic_buf, 72, NV_ENC_PIC_TYPE_IDR);
}
self.frame_idx += 1;
let status = unsafe {
(self.fns.nvEncEncodePicture)(self.encoder, pic_buf.as_mut_ptr() as *mut c_void)
};
if status != NV_ENC_SUCCESS {
unsafe { (self.fns.nvEncUnmapInputResource)(self.encoder, mapped_resource) };
let mut dummy: gpu_libs::CUcontext = ptr::null_mut();
unsafe { (cuda.cuCtxPopCurrent_v2)(&mut dummy) };
if status != NV_ENC_ERR_NEED_MORE_INPUT {
eprintln!("[nvenc-direct] nvEncEncodePicture failed: {status}");
}
return None;
}
self.force_idr = false;
let mut lock_buf = vec![0u8; NVENC_LOCK_BITSTREAM_SIZE];
w32(&mut lock_buf, 0, NV_ENC_LOCK_BITSTREAM_VER);
wptr(&mut lock_buf, 8, self.output_buffer);
let status = unsafe {
(self.fns.nvEncLockBitstream)(self.encoder, lock_buf.as_mut_ptr() as *mut c_void)
};
if status != NV_ENC_SUCCESS {
eprintln!("[nvenc-direct] nvEncLockBitstream failed: {status}");
unsafe { (self.fns.nvEncUnmapInputResource)(self.encoder, mapped_resource) };
let mut dummy: gpu_libs::CUcontext = ptr::null_mut();
unsafe { (cuda.cuCtxPopCurrent_v2)(&mut dummy) };
return None;
}
let size = r32(&lock_buf, 36) as usize;
let buf_ptr = rptr(&lock_buf, 56) as *const u8;
let nal_data = if !buf_ptr.is_null() && size > 0 {
unsafe { std::slice::from_raw_parts(buf_ptr, size) }.to_vec()
} else {
Vec::new()
};
let is_idr = self.is_keyframe_pic_type(r32(&lock_buf, 64));
unsafe { (self.fns.nvEncUnlockBitstream)(self.encoder, self.output_buffer) };
unsafe { (self.fns.nvEncUnmapInputResource)(self.encoder, mapped_resource) };
let mut dummy_ctx: gpu_libs::CUcontext = ptr::null_mut();
unsafe { (cuda.cuCtxPopCurrent_v2)(&mut dummy_ctx) };
if nal_data.is_empty() {
None
} else {
let mut nal_data = nal_data;
self.ensure_h264_sps_pps(&mut nal_data, is_idr);
Some((nal_data, is_idr))
}
}
pub fn encode_rgba_padded(
&mut self,
rgba: &[u8],
src_w: usize,
src_h: usize,
) -> Option<(Vec<u8>, bool)> {
let enc_w = self.width as usize;
let enc_h = self.height as usize;
let pitch = self.cuda_pitch as usize;
let frame_bytes = pitch * enc_h;
assert!(frame_bytes <= self.pinned_size);
let dst = self.pinned_host;
let src_row_bytes = src_w * 4;
let copy_bytes = (enc_w.min(src_w)) * 4;
for row in 0..enc_h {
let sr = row.min(src_h.saturating_sub(1));
let src_start = sr * src_row_bytes;
let dst_off = row * pitch;
unsafe {
ptr::copy_nonoverlapping(
rgba.as_ptr().add(src_start),
dst.add(dst_off),
copy_bytes,
);
}
if enc_w > src_w {
let last = unsafe {
std::slice::from_raw_parts(rgba.as_ptr().add(src_start + (src_w - 1) * 4), 4)
};
for col in src_w..enc_w {
let off = dst_off + col * 4;
unsafe { ptr::copy_nonoverlapping(last.as_ptr(), dst.add(off), 4) };
}
}
let used = enc_w * 4;
if used < pitch {
unsafe { ptr::write_bytes(dst.add(dst_off + used), 0, pitch - used) };
}
}
self.upload_and_encode(
self.cuda_devptr_abgr,
self.cuda_registered_abgr,
NV_ENC_BUFFER_FORMAT_ABGR,
frame_bytes,
)
}
pub fn encode_nv12(
&mut self,
data: &[u8],
y_stride: usize,
uv_stride: usize,
src_h: usize,
) -> Option<(Vec<u8>, bool)> {
let enc_w = self.width as usize;
let enc_h = self.height as usize;
let nv12_pitch = self.nv12_pitch as usize;
let y_plane_size = nv12_pitch * enc_h;
let uv_h = enc_h / 2;
let nv12_total = y_plane_size + nv12_pitch * uv_h;
assert!(nv12_total <= self.pinned_size);
let dst = self.pinned_host;
for row in 0..enc_h {
let sr = row.min(src_h.saturating_sub(1));
let src_off = sr * y_stride;
let dst_off = row * nv12_pitch;
let copy_len = enc_w.min(y_stride);
if src_off + copy_len <= data.len() {
unsafe {
ptr::copy_nonoverlapping(
data.as_ptr().add(src_off),
dst.add(dst_off),
copy_len,
);
}
}
if enc_w < nv12_pitch {
unsafe { ptr::write_bytes(dst.add(dst_off + enc_w), 0, nv12_pitch - enc_w) };
}
}
let src_uv_h = src_h / 2;
let uv_src_base = y_stride * src_h;
for row in 0..uv_h {
let sr = row.min(src_uv_h.saturating_sub(1));
let src_off = uv_src_base + sr * uv_stride;
let dst_off = y_plane_size + row * nv12_pitch;
let copy_len = enc_w.min(uv_stride);
if src_off + copy_len <= data.len() {
unsafe {
ptr::copy_nonoverlapping(
data.as_ptr().add(src_off),
dst.add(dst_off),
copy_len,
);
}
}
if enc_w < nv12_pitch {
unsafe { ptr::write_bytes(dst.add(dst_off + enc_w), 0, nv12_pitch - enc_w) };
}
}
self.upload_and_encode_nv12(nv12_total)
}
fn upload_and_encode_nv12(&mut self, upload_bytes: usize) -> Option<(Vec<u8>, bool)> {
let pitch = self.nv12_pitch;
let cuda = crate::gpu_libs::cuda().expect("CUDA loaded during init");
unsafe { (cuda.cuCtxPushCurrent_v2)(self.cuda_ctx) };
let status = unsafe {
(cuda.cuMemcpyHtoD_v2)(
self.cuda_devptr_nv12,
self.pinned_host as *const c_void,
upload_bytes,
)
};
if status != 0 {
eprintln!("[nvenc-direct] cuMemcpyHtoD (NV12) failed: {status}");
let mut dummy: gpu_libs::CUcontext = ptr::null_mut();
unsafe { (cuda.cuCtxPopCurrent_v2)(&mut dummy) };
return None;
}
unsafe { (cuda.cuStreamSynchronize)(ptr::null_mut()) };
let mut map_buf = vec![0u8; NVENC_MAP_INPUT_RESOURCE_SIZE];
w32(&mut map_buf, 0, NV_ENC_MAP_INPUT_RESOURCE_VER);
wptr(&mut map_buf, 16, self.cuda_registered_nv12);
let status = unsafe {
(self.fns.nvEncMapInputResource)(self.encoder, map_buf.as_mut_ptr() as *mut c_void)
};
if status != NV_ENC_SUCCESS {
eprintln!("[nvenc-direct] nvEncMapInputResource (NV12) failed: {status}");
let mut dummy: gpu_libs::CUcontext = ptr::null_mut();
unsafe { (cuda.cuCtxPopCurrent_v2)(&mut dummy) };
return None;
}
let mapped_resource = rptr(&map_buf, 24);
let mut pic_buf = vec![0u8; NVENC_PIC_PARAMS_SIZE];
w32(&mut pic_buf, 0, NV_ENC_PIC_PARAMS_VER);
w32(&mut pic_buf, 4, self.width);
w32(&mut pic_buf, 8, self.height);
w32(&mut pic_buf, 12, pitch);
w32(&mut pic_buf, 20, self.frame_idx);
w64(&mut pic_buf, 24, self.frame_idx as u64);
wptr(&mut pic_buf, 40, mapped_resource);
wptr(&mut pic_buf, 48, self.output_buffer);
w32(&mut pic_buf, 64, NV_ENC_BUFFER_FORMAT_NV12);
w32(&mut pic_buf, 68, 1);
if self.force_idr {
w32(&mut pic_buf, 16, NV_ENC_PIC_FLAGS_FORCEIDR | 0x4);
w32(&mut pic_buf, 72, NV_ENC_PIC_TYPE_IDR);
}
self.frame_idx += 1;
let status = unsafe {
(self.fns.nvEncEncodePicture)(self.encoder, pic_buf.as_mut_ptr() as *mut c_void)
};
if status != NV_ENC_SUCCESS {
unsafe { (self.fns.nvEncUnmapInputResource)(self.encoder, mapped_resource) };
let mut dummy: gpu_libs::CUcontext = ptr::null_mut();
unsafe { (cuda.cuCtxPopCurrent_v2)(&mut dummy) };
if status != NV_ENC_ERR_NEED_MORE_INPUT {
eprintln!("[nvenc-direct] nvEncEncodePicture (NV12) failed: {status}");
}
return None;
}
self.force_idr = false;
let mut lock_buf = vec![0u8; NVENC_LOCK_BITSTREAM_SIZE];
w32(&mut lock_buf, 0, NV_ENC_LOCK_BITSTREAM_VER);
wptr(&mut lock_buf, 8, self.output_buffer);
let status = unsafe {
(self.fns.nvEncLockBitstream)(self.encoder, lock_buf.as_mut_ptr() as *mut c_void)
};
if status != NV_ENC_SUCCESS {
eprintln!("[nvenc-direct] nvEncLockBitstream (NV12) failed: {status}");
unsafe { (self.fns.nvEncUnmapInputResource)(self.encoder, mapped_resource) };
let mut dummy: gpu_libs::CUcontext = ptr::null_mut();
unsafe { (cuda.cuCtxPopCurrent_v2)(&mut dummy) };
return None;
}
let size = r32(&lock_buf, 36) as usize;
let buf_ptr = rptr(&lock_buf, 56) as *const u8;
let nal_data = if !buf_ptr.is_null() && size > 0 {
unsafe { std::slice::from_raw_parts(buf_ptr, size) }.to_vec()
} else {
Vec::new()
};
let is_idr = self.is_keyframe_pic_type(r32(&lock_buf, 64));
unsafe { (self.fns.nvEncUnlockBitstream)(self.encoder, self.output_buffer) };
unsafe { (self.fns.nvEncUnmapInputResource)(self.encoder, mapped_resource) };
let mut dummy_ctx: gpu_libs::CUcontext = ptr::null_mut();
unsafe { (cuda.cuCtxPopCurrent_v2)(&mut dummy_ctx) };
if nal_data.is_empty() {
None
} else {
let mut nal_data = nal_data;
self.ensure_h264_sps_pps(&mut nal_data, is_idr);
Some((nal_data, is_idr))
}
}
}
fn h264_has_sps_pps(data: &[u8]) -> bool {
let mut has_sps = false;
let mut has_pps = false;
for_each_annex_b_nal(data, |nal_type, _offset| {
if nal_type == 7 {
has_sps = true;
}
if nal_type == 8 {
has_pps = true;
}
});
has_sps && has_pps
}
fn h264_extract_sps_pps_prefix(data: &[u8]) -> Option<Vec<u8>> {
let mut first_vcl_offset = None;
for_each_annex_b_nal(data, |nal_type, offset| {
if first_vcl_offset.is_none() && (nal_type == 5 || nal_type == 1) {
first_vcl_offset = Some(offset);
}
});
first_vcl_offset
.filter(|&off| off > 0)
.map(|off| data[..off].to_vec())
}
fn for_each_annex_b_nal(data: &[u8], mut f: impl FnMut(u8, usize)) {
let len = data.len();
let mut i = 0;
while i < len.saturating_sub(3) {
if data[i] == 0 && data[i + 1] == 0 {
let (sc_len, nal_start) = if data[i + 2] == 1 {
(3, i + 3)
} else if data[i + 2] == 0 && i + 3 < len && data[i + 3] == 1 {
(4, i + 4)
} else {
i += 1;
continue;
};
let _ = sc_len;
if nal_start < len {
let nal_type = data[nal_start] & 0x1f;
f(nal_type, i);
}
i = nal_start + 1;
} else {
i += 1;
}
}
}
impl Drop for NvencDirectEncoder {
fn drop(&mut self) {
unsafe {
if let Ok(cuda) = gpu_libs::cuda() {
(cuda.cuCtxPushCurrent_v2)(self.cuda_ctx);
}
if !self.cuda_registered.is_null() {
(self.fns.nvEncUnregisterResource)(self.encoder, self.cuda_registered);
}
if !self.cuda_registered_abgr.is_null() {
(self.fns.nvEncUnregisterResource)(self.encoder, self.cuda_registered_abgr);
}
if !self.cuda_registered_nv12.is_null() {
(self.fns.nvEncUnregisterResource)(self.encoder, self.cuda_registered_nv12);
}
(self.fns.nvEncDestroyInputBuffer)(self.encoder, self.input_buffer);
(self.fns.nvEncDestroyBitstreamBuffer)(self.encoder, self.output_buffer);
(self.fns.nvEncDestroyEncoder)(self.encoder);
if let Ok(cuda) = gpu_libs::cuda() {
if !self.pinned_host.is_null() {
(cuda.cuMemFreeHost)(self.pinned_host as *mut c_void);
}
if self.cuda_devptr != 0 {
(cuda.cuMemFree_v2)(self.cuda_devptr);
}
if self.cuda_devptr_abgr != 0 {
(cuda.cuMemFree_v2)(self.cuda_devptr_abgr);
}
if self.cuda_devptr_nv12 != 0 {
(cuda.cuMemFree_v2)(self.cuda_devptr_nv12);
}
(cuda.cuCtxDestroy_v2)(self.cuda_ctx);
}
}
}
}