use std::collections::VecDeque;
use std::ffi::c_void;
use std::ptr;
use crate::{CudaLibrary, Error, ReleaseGuard, sys};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Preset(sys::GUID);
impl Preset {
pub const P1: Self = Self(sys::NV_ENC_PRESET_P1_GUID);
pub const P2: Self = Self(sys::NV_ENC_PRESET_P2_GUID);
pub const P3: Self = Self(sys::NV_ENC_PRESET_P3_GUID);
pub const P4: Self = Self(sys::NV_ENC_PRESET_P4_GUID);
pub const P5: Self = Self(sys::NV_ENC_PRESET_P5_GUID);
pub const P6: Self = Self(sys::NV_ENC_PRESET_P6_GUID);
pub const P7: Self = Self(sys::NV_ENC_PRESET_P7_GUID);
fn to_sys(self) -> sys::GUID {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TuningInfo(sys::NV_ENC_TUNING_INFO);
impl TuningInfo {
pub const HIGH_QUALITY: Self = Self(sys::NV_ENC_TUNING_INFO_NV_ENC_TUNING_INFO_HIGH_QUALITY);
pub const LOW_LATENCY: Self = Self(sys::NV_ENC_TUNING_INFO_NV_ENC_TUNING_INFO_LOW_LATENCY);
pub const ULTRA_LOW_LATENCY: Self =
Self(sys::NV_ENC_TUNING_INFO_NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY);
pub const LOSSLESS: Self = Self(sys::NV_ENC_TUNING_INFO_NV_ENC_TUNING_INFO_LOSSLESS);
fn to_sys(self) -> sys::NV_ENC_TUNING_INFO {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum H264Profile {
AutoSelect,
Baseline,
Main,
High,
High10,
High422,
High444,
Stereo,
ProgressiveHigh,
ConstrainedHigh,
}
impl H264Profile {
fn to_sys(self) -> sys::GUID {
match self {
H264Profile::AutoSelect => sys::NV_ENC_CODEC_PROFILE_AUTOSELECT_GUID,
H264Profile::Baseline => sys::NV_ENC_H264_PROFILE_BASELINE_GUID,
H264Profile::Main => sys::NV_ENC_H264_PROFILE_MAIN_GUID,
H264Profile::High => sys::NV_ENC_H264_PROFILE_HIGH_GUID,
H264Profile::High10 => sys::NV_ENC_H264_PROFILE_HIGH_10_GUID,
H264Profile::High422 => sys::NV_ENC_H264_PROFILE_HIGH_422_GUID,
H264Profile::High444 => sys::NV_ENC_H264_PROFILE_HIGH_444_GUID,
H264Profile::Stereo => sys::NV_ENC_H264_PROFILE_STEREO_GUID,
H264Profile::ProgressiveHigh => sys::NV_ENC_H264_PROFILE_PROGRESSIVE_HIGH_GUID,
H264Profile::ConstrainedHigh => sys::NV_ENC_H264_PROFILE_CONSTRAINED_HIGH_GUID,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HevcProfile {
AutoSelect,
Main,
Main10,
Frext,
}
impl HevcProfile {
fn to_sys(self) -> sys::GUID {
match self {
HevcProfile::AutoSelect => sys::NV_ENC_CODEC_PROFILE_AUTOSELECT_GUID,
HevcProfile::Main => sys::NV_ENC_HEVC_PROFILE_MAIN_GUID,
HevcProfile::Main10 => sys::NV_ENC_HEVC_PROFILE_MAIN10_GUID,
HevcProfile::Frext => sys::NV_ENC_HEVC_PROFILE_FREXT_GUID,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Av1Profile {
AutoSelect,
Main,
}
impl Av1Profile {
fn to_sys(self) -> sys::GUID {
match self {
Av1Profile::AutoSelect => sys::NV_ENC_CODEC_PROFILE_AUTOSELECT_GUID,
Av1Profile::Main => sys::NV_ENC_AV1_PROFILE_MAIN_GUID,
}
}
}
#[derive(Debug, Clone)]
pub struct H264EncoderConfig {
pub profile: Option<H264Profile>,
pub idr_period: Option<u32>,
}
#[derive(Debug, Clone)]
pub struct HevcEncoderConfig {
pub profile: Option<HevcProfile>,
pub idr_period: Option<u32>,
}
#[derive(Debug, Clone)]
pub struct Av1EncoderConfig {
pub profile: Option<Av1Profile>,
pub idr_period: Option<u32>,
}
#[derive(Debug, Clone)]
pub enum CodecConfig {
H264(H264EncoderConfig),
Hevc(HevcEncoderConfig),
Av1(Av1EncoderConfig),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EncoderCodec {
H264,
Hevc,
Av1,
}
#[derive(Debug, Clone)]
pub struct EncoderConfig {
pub codec: CodecConfig,
pub width: u32,
pub height: u32,
pub max_encode_width: Option<u32>,
pub max_encode_height: Option<u32>,
pub framerate_num: u32,
pub framerate_den: u32,
pub average_bitrate: Option<u32>,
pub preset: Preset,
pub tuning_info: TuningInfo,
pub rate_control_mode: RateControlMode,
pub gop_length: Option<u32>,
pub frame_interval_p: u32,
pub buffer_format: BufferFormat,
pub device_id: i32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RateControlMode {
ConstQp,
Vbr,
Cbr,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BufferFormat {
Nv12,
Yv12,
Iyuv,
Yuv444,
Yuv420_10bit,
Yuv444_10bit,
Argb,
Abgr,
Argb10,
Abgr10,
}
impl BufferFormat {
fn to_sys(self) -> sys::NV_ENC_BUFFER_FORMAT {
match self {
BufferFormat::Nv12 => sys::_NV_ENC_BUFFER_FORMAT_NV_ENC_BUFFER_FORMAT_NV12,
BufferFormat::Yv12 => sys::_NV_ENC_BUFFER_FORMAT_NV_ENC_BUFFER_FORMAT_YV12,
BufferFormat::Iyuv => sys::_NV_ENC_BUFFER_FORMAT_NV_ENC_BUFFER_FORMAT_IYUV,
BufferFormat::Yuv444 => sys::_NV_ENC_BUFFER_FORMAT_NV_ENC_BUFFER_FORMAT_YUV444,
BufferFormat::Yuv420_10bit => {
sys::_NV_ENC_BUFFER_FORMAT_NV_ENC_BUFFER_FORMAT_YUV420_10BIT
}
BufferFormat::Yuv444_10bit => {
sys::_NV_ENC_BUFFER_FORMAT_NV_ENC_BUFFER_FORMAT_YUV444_10BIT
}
BufferFormat::Argb => sys::_NV_ENC_BUFFER_FORMAT_NV_ENC_BUFFER_FORMAT_ARGB,
BufferFormat::Abgr => sys::_NV_ENC_BUFFER_FORMAT_NV_ENC_BUFFER_FORMAT_ABGR,
BufferFormat::Argb10 => sys::_NV_ENC_BUFFER_FORMAT_NV_ENC_BUFFER_FORMAT_ARGB10,
BufferFormat::Abgr10 => sys::_NV_ENC_BUFFER_FORMAT_NV_ENC_BUFFER_FORMAT_ABGR10,
}
}
fn bytes_per_row(self, width: u32) -> Result<u32, Error> {
let multiplier = match self {
BufferFormat::Nv12 | BufferFormat::Yv12 | BufferFormat::Iyuv | BufferFormat::Yuv444 => {
1u32
}
BufferFormat::Yuv420_10bit | BufferFormat::Yuv444_10bit => 2,
BufferFormat::Argb
| BufferFormat::Abgr
| BufferFormat::Argb10
| BufferFormat::Abgr10 => 4,
};
width.checked_mul(multiplier).ok_or_else(|| {
Error::new_custom("bytes_per_row", "width overflow in pitch calculation")
})
}
fn frame_size(self, width: u32, height: u32) -> Result<usize, Error> {
let pixels = (width as usize)
.checked_mul(height as usize)
.ok_or_else(|| Error::new_custom("frame_size", "width * height overflow"))?;
let size = match self {
BufferFormat::Nv12 | BufferFormat::Yv12 | BufferFormat::Iyuv => {
pixels.checked_mul(3).map(|v| v / 2)
}
BufferFormat::Yuv444 => pixels.checked_mul(3),
BufferFormat::Yuv420_10bit => pixels.checked_mul(3),
BufferFormat::Yuv444_10bit => pixels.checked_mul(6),
BufferFormat::Argb | BufferFormat::Abgr => pixels.checked_mul(4),
BufferFormat::Argb10 | BufferFormat::Abgr10 => pixels.checked_mul(4),
};
size.ok_or_else(|| Error::new_custom("frame_size", "frame size overflow"))
}
}
impl RateControlMode {
fn to_sys(self) -> sys::NV_ENC_PARAMS_RC_MODE {
match self {
RateControlMode::ConstQp => sys::_NV_ENC_PARAMS_RC_MODE_NV_ENC_PARAMS_RC_CONSTQP,
RateControlMode::Vbr => sys::_NV_ENC_PARAMS_RC_MODE_NV_ENC_PARAMS_RC_VBR,
RateControlMode::Cbr => sys::_NV_ENC_PARAMS_RC_MODE_NV_ENC_PARAMS_RC_CBR,
}
}
}
#[derive(Debug, Clone)]
pub struct EncoderCaps {
pub supported_ratecontrol_modes: i32,
pub support_yuv444_encode: bool,
pub support_yuv422_encode: bool,
pub support_meonly_mode: bool,
pub width_max: i32,
pub height_max: i32,
pub width_min: i32,
pub height_min: i32,
pub num_max_bframes: i32,
pub support_10bit_encode: bool,
pub support_lossless_encode: bool,
pub support_lookahead: bool,
pub support_temporal_aq: bool,
}
#[derive(Debug, Clone, Default)]
pub struct ReconfigureParams {
pub width: Option<u32>,
pub height: Option<u32>,
pub framerate_num: Option<u32>,
pub framerate_den: Option<u32>,
pub average_bitrate: Option<u32>,
pub max_bitrate: Option<u32>,
}
#[derive(Debug, Clone)]
pub struct EncodeOptions {
pub force_intra: bool,
pub force_idr: bool,
pub output_spspps: bool,
}
impl EncodeOptions {
fn to_pic_flags(&self) -> u32 {
let mut flags = 0u32;
if self.force_intra {
flags |= sys::NV_ENC_PIC_FLAG_FORCEINTRA;
}
if self.force_idr {
flags |= sys::NV_ENC_PIC_FLAG_FORCEIDR;
}
if self.output_spspps {
flags |= sys::NV_ENC_PIC_FLAG_OUTPUT_SPSPPS;
}
flags
}
}
pub struct Encoder {
lib: CudaLibrary,
ctx: sys::CUcontext,
encoder: sys::NV_ENCODE_API_FUNCTION_LIST,
h_encoder: *mut c_void,
width: u32,
height: u32,
buffer_format: sys::NV_ENC_BUFFER_FORMAT,
buffer_format_enum: BufferFormat,
expected_frame_size: usize,
encoded_frames: VecDeque<EncodedFrame>,
framerate_den: u64,
frame_count: u64,
init_params: sys::NV_ENC_INITIALIZE_PARAMS,
encode_config: sys::NV_ENC_CONFIG,
}
impl Encoder {
pub fn new(config: EncoderConfig) -> 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 lib_clone = lib.clone();
let ctx_guard = ReleaseGuard::new(move || {
let _ = lib_clone.cu_ctx_destroy(ctx);
});
let (encoder_api, h_encoder) = lib.with_context(ctx, || {
let mut encoder_api: sys::NV_ENCODE_API_FUNCTION_LIST = std::mem::zeroed();
encoder_api.version = sys::NV_ENCODE_API_FUNCTION_LIST_VER;
lib.nvenc_create_api_instance(&mut encoder_api)?;
let mut open_session_params: sys::NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS =
std::mem::zeroed();
open_session_params.version = sys::NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER;
open_session_params.deviceType = sys::_NV_ENC_DEVICE_TYPE_NV_ENC_DEVICE_TYPE_CUDA;
open_session_params.device = ctx.cast();
open_session_params.apiVersion = sys::NVENCAPI_VERSION;
let mut h_encoder = ptr::null_mut();
let status = encoder_api
.nvEncOpenEncodeSessionEx
.map(|f| f(&mut open_session_params, &mut h_encoder))
.unwrap_or(sys::_NVENCSTATUS_NV_ENC_ERR_INVALID_PTR);
Error::check_nvenc(status, "nvEncOpenEncodeSessionEx")?;
Ok((encoder_api, h_encoder))
})?;
ctx_guard.cancel();
let mut encoder = Self {
lib: lib.clone(),
ctx,
encoder: encoder_api,
h_encoder,
width: config.width,
height: config.height,
buffer_format: config.buffer_format.to_sys(),
buffer_format_enum: config.buffer_format,
expected_frame_size: config
.buffer_format
.frame_size(config.width, config.height)?,
encoded_frames: VecDeque::new(),
framerate_den: config.framerate_den as u64,
frame_count: 0,
init_params: std::mem::zeroed(),
encode_config: std::mem::zeroed(),
};
lib.with_context(ctx, || encoder.initialize_encoder(&config))?;
Ok(encoder)
}
}
pub fn query_caps(codec: EncoderCodec, device_id: i32) -> Result<EncoderCaps, Error> {
let codec_guid = match codec {
EncoderCodec::H264 => sys::NV_ENC_CODEC_H264_GUID,
EncoderCodec::Hevc => sys::NV_ENC_CODEC_HEVC_GUID,
EncoderCodec::Av1 => sys::NV_ENC_CODEC_AV1_GUID,
};
Self::query_caps_with_codec(device_id, codec_guid)
}
fn query_caps_with_codec(device_id: i32, codec_guid: sys::GUID) -> Result<EncoderCaps, Error> {
unsafe {
let lib = CudaLibrary::load()?;
let mut ctx = ptr::null_mut();
lib.cu_ctx_create(&mut ctx, 0, device_id)?;
let lib_clone = lib.clone();
let ctx_guard = ReleaseGuard::new(move || {
let _ = lib_clone.cu_ctx_destroy(ctx);
});
let caps = lib.with_context(ctx, || {
let mut encoder_api: sys::NV_ENCODE_API_FUNCTION_LIST = std::mem::zeroed();
encoder_api.version = sys::NV_ENCODE_API_FUNCTION_LIST_VER;
lib.nvenc_create_api_instance(&mut encoder_api)?;
let mut open_session_params: sys::NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS =
std::mem::zeroed();
open_session_params.version = sys::NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER;
open_session_params.deviceType = sys::_NV_ENC_DEVICE_TYPE_NV_ENC_DEVICE_TYPE_CUDA;
open_session_params.device = ctx.cast();
open_session_params.apiVersion = sys::NVENCAPI_VERSION;
let mut h_encoder = ptr::null_mut();
let status = encoder_api
.nvEncOpenEncodeSessionEx
.map(|f| f(&mut open_session_params, &mut h_encoder))
.unwrap_or(sys::_NVENCSTATUS_NV_ENC_ERR_INVALID_PTR);
Error::check_nvenc(status, "nvEncOpenEncodeSessionEx")?;
let destroy_fn = encoder_api.nvEncDestroyEncoder;
let session_guard = ReleaseGuard::new(move || {
if let Some(f) = destroy_fn {
f(h_encoder);
}
});
let query_cap = |caps_type: u32| -> Result<i32, Error> {
let mut caps_param: sys::NV_ENC_CAPS_PARAM = std::mem::zeroed();
caps_param.version = sys::NV_ENC_CAPS_PARAM_VER;
caps_param.capsToQuery = caps_type;
let mut caps_val: i32 = 0;
let status = encoder_api
.nvEncGetEncodeCaps
.map(|f| f(h_encoder, codec_guid, &mut caps_param, &mut caps_val))
.unwrap_or(sys::_NVENCSTATUS_NV_ENC_ERR_INVALID_PTR);
Error::check_nvenc(status, "nvEncGetEncodeCaps")?;
Ok(caps_val)
};
let caps = EncoderCaps {
supported_ratecontrol_modes: query_cap(
sys::_NV_ENC_CAPS_NV_ENC_CAPS_SUPPORTED_RATECONTROL_MODES,
)?,
support_yuv444_encode: query_cap(
sys::_NV_ENC_CAPS_NV_ENC_CAPS_SUPPORT_YUV444_ENCODE,
)? != 0,
support_yuv422_encode: query_cap(
sys::_NV_ENC_CAPS_NV_ENC_CAPS_SUPPORT_YUV422_ENCODE,
)? != 0,
support_meonly_mode: query_cap(
sys::_NV_ENC_CAPS_NV_ENC_CAPS_SUPPORT_MEONLY_MODE,
)? != 0,
width_max: query_cap(sys::_NV_ENC_CAPS_NV_ENC_CAPS_WIDTH_MAX)?,
height_max: query_cap(sys::_NV_ENC_CAPS_NV_ENC_CAPS_HEIGHT_MAX)?,
width_min: query_cap(sys::_NV_ENC_CAPS_NV_ENC_CAPS_WIDTH_MIN)?,
height_min: query_cap(sys::_NV_ENC_CAPS_NV_ENC_CAPS_HEIGHT_MIN)?,
num_max_bframes: query_cap(sys::_NV_ENC_CAPS_NV_ENC_CAPS_NUM_MAX_BFRAMES)?,
support_10bit_encode: query_cap(
sys::_NV_ENC_CAPS_NV_ENC_CAPS_SUPPORT_10BIT_ENCODE,
)? != 0,
support_lossless_encode: query_cap(
sys::_NV_ENC_CAPS_NV_ENC_CAPS_SUPPORT_LOSSLESS_ENCODE,
)? != 0,
support_lookahead: query_cap(sys::_NV_ENC_CAPS_NV_ENC_CAPS_SUPPORT_LOOKAHEAD)?
!= 0,
support_temporal_aq: query_cap(
sys::_NV_ENC_CAPS_NV_ENC_CAPS_SUPPORT_TEMPORAL_AQ,
)? != 0,
};
drop(session_guard);
Ok(caps)
})?;
ctx_guard.cancel();
lib.cu_ctx_destroy(ctx)?;
Ok(caps)
}
}
pub fn reconfigure(&mut self, params: ReconfigureParams) -> Result<(), Error> {
self.lib
.clone()
.with_context(self.ctx, || self.reconfigure_inner(params))
}
fn reconfigure_inner(&mut self, params: ReconfigureParams) -> Result<(), Error> {
unsafe {
if let Some(width) = params.width
&& width > self.init_params.maxEncodeWidth
{
return Err(Error::new_custom(
"reconfigure",
"width exceeds maxEncodeWidth",
));
}
if let Some(height) = params.height
&& height > self.init_params.maxEncodeHeight
{
return Err(Error::new_custom(
"reconfigure",
"height exceeds maxEncodeHeight",
));
}
let mut reconfig_params: sys::NV_ENC_RECONFIGURE_PARAMS = std::mem::zeroed();
reconfig_params.version = sys::NV_ENC_RECONFIGURE_PARAMS_VER;
reconfig_params.reInitEncodeParams = self.init_params;
let mut new_config = self.encode_config;
reconfig_params.reInitEncodeParams.encodeConfig = &mut new_config;
if let Some(width) = params.width {
reconfig_params.reInitEncodeParams.encodeWidth = width;
reconfig_params.reInitEncodeParams.darWidth = width;
}
if let Some(height) = params.height {
reconfig_params.reInitEncodeParams.encodeHeight = height;
reconfig_params.reInitEncodeParams.darHeight = height;
}
if let Some(fps_num) = params.framerate_num {
reconfig_params.reInitEncodeParams.frameRateNum = fps_num;
}
if let Some(fps_den) = params.framerate_den {
reconfig_params.reInitEncodeParams.frameRateDen = fps_den;
}
if let Some(bitrate) = params.average_bitrate {
new_config.rcParams.averageBitRate = bitrate;
}
if let Some(max_br) = params.max_bitrate {
new_config.rcParams.maxBitRate = max_br;
}
let status = self
.encoder
.nvEncReconfigureEncoder
.map(|f| f(self.h_encoder, &mut reconfig_params))
.unwrap_or(sys::_NVENCSTATUS_NV_ENC_ERR_INVALID_PTR);
Error::check_nvenc(status, "nvEncReconfigureEncoder")?;
self.encode_config = new_config;
self.init_params = reconfig_params.reInitEncodeParams;
self.init_params.encodeConfig = &mut self.encode_config;
if let Some(width) = params.width {
self.width = width;
}
if let Some(height) = params.height {
self.height = height;
}
if params.width.is_some() || params.height.is_some() {
self.expected_frame_size = self
.buffer_format_enum
.frame_size(self.width, self.height)?;
}
if let Some(fps_den) = params.framerate_den {
self.framerate_den = fps_den as u64;
}
Ok(())
}
}
fn initialize_encoder(&mut self, config: &EncoderConfig) -> Result<(), Error> {
unsafe {
let (codec_guid, profile_guid, idr_period) = match &config.codec {
CodecConfig::H264(h264) => {
let profile = h264.profile.unwrap_or(H264Profile::Main).to_sys();
let idr = h264.idr_period.unwrap_or_else(|| {
config.gop_length.unwrap_or(sys::NVENC_INFINITE_GOPLENGTH)
});
(sys::NV_ENC_CODEC_H264_GUID, profile, idr)
}
CodecConfig::Hevc(hevc) => {
let profile = hevc.profile.unwrap_or(HevcProfile::Main).to_sys();
let idr = hevc.idr_period.unwrap_or_else(|| {
config.gop_length.unwrap_or(sys::NVENC_INFINITE_GOPLENGTH)
});
(sys::NV_ENC_CODEC_HEVC_GUID, profile, idr)
}
CodecConfig::Av1(av1) => {
let profile = av1.profile.unwrap_or(Av1Profile::Main).to_sys();
let idr = av1.idr_period.unwrap_or_else(|| {
config.gop_length.unwrap_or(sys::NVENC_INFINITE_GOPLENGTH)
});
(sys::NV_ENC_CODEC_AV1_GUID, profile, idr)
}
};
let mut preset_config: sys::NV_ENC_PRESET_CONFIG = std::mem::zeroed();
preset_config.version = sys::NV_ENC_PRESET_CONFIG_VER;
preset_config.presetCfg.version = sys::NV_ENC_CONFIG_VER;
let status = self
.encoder
.nvEncGetEncodePresetConfigEx
.map(|f| {
f(
self.h_encoder,
codec_guid,
config.preset.to_sys(),
config.tuning_info.to_sys(),
&mut preset_config,
)
})
.unwrap_or(sys::_NVENCSTATUS_NV_ENC_ERR_INVALID_PTR);
Error::check_nvenc(status, "nvEncGetEncodePresetConfigEx")?;
let mut init_params: sys::NV_ENC_INITIALIZE_PARAMS = std::mem::zeroed();
let mut encode_config: sys::NV_ENC_CONFIG = preset_config.presetCfg;
init_params.version = sys::NV_ENC_INITIALIZE_PARAMS_VER;
init_params.encodeGUID = codec_guid;
init_params.presetGUID = config.preset.to_sys();
init_params.encodeWidth = config.width;
init_params.encodeHeight = config.height;
init_params.darWidth = config.width;
init_params.darHeight = config.height;
init_params.frameRateNum = config.framerate_num;
init_params.frameRateDen = config.framerate_den;
init_params.enablePTD = 1;
init_params.encodeConfig = &mut encode_config;
init_params.maxEncodeWidth = config.max_encode_width.unwrap_or(config.width);
init_params.maxEncodeHeight = config.max_encode_height.unwrap_or(config.height);
init_params.tuningInfo = config.tuning_info.to_sys();
{
encode_config.version = sys::NV_ENC_CONFIG_VER;
encode_config.profileGUID = profile_guid;
encode_config.gopLength =
config.gop_length.unwrap_or(sys::NVENC_INFINITE_GOPLENGTH);
encode_config.frameIntervalP = config.frame_interval_p as i32;
encode_config.rcParams.rateControlMode = config.rate_control_mode.to_sys();
if config.rate_control_mode != RateControlMode::ConstQp {
let bitrate = config.average_bitrate.ok_or_else(|| {
Error::new_custom(
"initialize_encoder",
"average_bitrate must be specified when not using ConstQp mode",
)
})?;
encode_config.rcParams.averageBitRate = bitrate;
encode_config.rcParams.maxBitRate = bitrate;
}
match &config.codec {
CodecConfig::H264(_) => {
encode_config.encodeCodecConfig.h264Config.idrPeriod = idr_period;
}
CodecConfig::Hevc(_) => {
encode_config.encodeCodecConfig.hevcConfig.idrPeriod = idr_period;
}
CodecConfig::Av1(_) => {
encode_config.encodeCodecConfig.av1Config.idrPeriod = idr_period;
}
}
}
let status = self
.encoder
.nvEncInitializeEncoder
.map(|f| f(self.h_encoder, &mut init_params))
.unwrap_or(sys::_NVENCSTATUS_NV_ENC_ERR_INVALID_PTR);
Error::check_nvenc(status, "nvEncInitializeEncoder")?;
self.encode_config = encode_config;
self.init_params = init_params;
self.init_params.encodeConfig = &mut self.encode_config;
Ok(())
}
}
pub fn get_sequence_params(&mut self) -> Result<Vec<u8>, Error> {
self.lib
.with_context(self.ctx, || self.get_sequence_params_inner())
}
fn get_sequence_params_inner(&self) -> Result<Vec<u8>, Error> {
unsafe {
let mut payload_buffer = vec![0u8; sys::NV_MAX_SEQ_HDR_LEN as usize];
let mut out_size: u32 = 0;
let mut seq_params: sys::NV_ENC_SEQUENCE_PARAM_PAYLOAD = std::mem::zeroed();
seq_params.version = sys::NV_ENC_SEQUENCE_PARAM_PAYLOAD_VER;
seq_params.spsppsBuffer = payload_buffer.as_mut_ptr() as *mut std::ffi::c_void;
seq_params.inBufferSize = sys::NV_MAX_SEQ_HDR_LEN;
seq_params.outSPSPPSPayloadSize = &mut out_size;
let status = self
.encoder
.nvEncGetSequenceParams
.map(|f| f(self.h_encoder, &mut seq_params))
.unwrap_or(sys::_NVENCSTATUS_NV_ENC_ERR_INVALID_PTR);
Error::check_nvenc(status, "nvEncGetSequenceParams")?;
payload_buffer.truncate(out_size as usize);
Ok(payload_buffer)
}
}
pub fn encode(&mut self, frame_data: &[u8], options: &EncodeOptions) -> Result<(), Error> {
let expected_size = self.expected_frame_size;
if frame_data.len() != expected_size {
return Err(Error::new_custom("encode", "invalid frame data size"));
}
self.lib
.clone()
.with_context(self.ctx, || self.encode_inner(frame_data, options))
}
fn encode_inner(&mut self, frame_data: &[u8], options: &EncodeOptions) -> Result<(), Error> {
let (device_input, _device_guard) = self.copy_input_data_to_device(frame_data)?;
let (registered_resource, _registered_guard) =
self.register_input_resource(device_input)?;
let (mapped_resource, _mapped_guard) = self.map_input_resource(registered_resource)?;
let (output_buffer, _bitstream_guard) = self.create_output_bitstream_buffer()?;
self.encode_picture(mapped_resource, output_buffer, options)?;
let encoded_frame = self.lock_and_copy_bitstream(output_buffer)?;
self.encoded_frames.push_back(encoded_frame);
Ok(())
}
fn copy_input_data_to_device(
&mut self,
frame_data: &[u8],
) -> Result<(sys::CUdeviceptr, ReleaseGuard<impl FnOnce() + use<>>), Error> {
let mut device_input: sys::CUdeviceptr = 0;
self.lib.cu_mem_alloc(&mut device_input, frame_data.len())?;
let lib = self.lib.clone();
let device_guard = ReleaseGuard::new(move || {
let _ = lib.cu_mem_free(device_input);
});
self.lib
.cu_memcpy_h_to_d(device_input, frame_data.as_ptr().cast(), frame_data.len())?;
Ok((device_input, device_guard))
}
fn register_input_resource(
&mut self,
device_input: sys::CUdeviceptr,
) -> Result<
(
sys::NV_ENC_REGISTERED_PTR,
ReleaseGuard<impl FnOnce() + use<>>,
),
Error,
> {
unsafe {
let mut register_resource: sys::NV_ENC_REGISTER_RESOURCE = std::mem::zeroed();
register_resource.version = sys::NV_ENC_REGISTER_RESOURCE_VER;
register_resource.resourceType =
sys::_NV_ENC_INPUT_RESOURCE_TYPE_NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR;
register_resource.resourceToRegister = device_input as *mut c_void;
register_resource.width = self.width;
register_resource.height = self.height;
register_resource.pitch = self.buffer_format_enum.bytes_per_row(self.width)?;
register_resource.bufferFormat = self.buffer_format;
register_resource.bufferUsage = sys::_NV_ENC_BUFFER_USAGE_NV_ENC_INPUT_IMAGE;
let status = self
.encoder
.nvEncRegisterResource
.map(|f| f(self.h_encoder, &mut register_resource))
.unwrap_or(sys::_NVENCSTATUS_NV_ENC_ERR_INVALID_PTR);
Error::check_nvenc(status, "nvEncRegisterResource")?;
let registered_resource = register_resource.registeredResource;
let unregister = self.encoder.nvEncUnregisterResource;
let h_encoder = self.h_encoder;
let registered_guard = ReleaseGuard::new(move || {
unregister.map(|f| f(h_encoder, registered_resource));
});
Ok((registered_resource, registered_guard))
}
}
fn map_input_resource(
&mut self,
registered_resource: sys::NV_ENC_REGISTERED_PTR,
) -> Result<(sys::NV_ENC_INPUT_PTR, ReleaseGuard<impl FnOnce() + use<>>), Error> {
unsafe {
let mut map_input_resource: sys::NV_ENC_MAP_INPUT_RESOURCE = std::mem::zeroed();
map_input_resource.version = sys::NV_ENC_MAP_INPUT_RESOURCE_VER;
map_input_resource.registeredResource = registered_resource;
let status = self
.encoder
.nvEncMapInputResource
.map(|f| f(self.h_encoder, &mut map_input_resource))
.unwrap_or(sys::_NVENCSTATUS_NV_ENC_ERR_INVALID_PTR);
Error::check_nvenc(status, "nvEncMapInputResource")?;
let mapped_resource = map_input_resource.mappedResource;
let unmap = self.encoder.nvEncUnmapInputResource;
let h_encoder = self.h_encoder;
let mapped_guard = ReleaseGuard::new(move || {
unmap.map(|f| f(h_encoder, mapped_resource));
});
Ok((mapped_resource, mapped_guard))
}
}
fn create_output_bitstream_buffer(
&mut self,
) -> Result<(sys::NV_ENC_OUTPUT_PTR, ReleaseGuard<impl FnOnce() + use<>>), Error> {
unsafe {
let mut create_bitstream: sys::NV_ENC_CREATE_BITSTREAM_BUFFER = std::mem::zeroed();
create_bitstream.version = sys::NV_ENC_CREATE_BITSTREAM_BUFFER_VER;
let status = self
.encoder
.nvEncCreateBitstreamBuffer
.map(|f| f(self.h_encoder, &mut create_bitstream))
.unwrap_or(sys::_NVENCSTATUS_NV_ENC_ERR_INVALID_PTR);
Error::check_nvenc(status, "nvEncCreateBitstreamBuffer")?;
let output_buffer = create_bitstream.bitstreamBuffer;
let destroy = self.encoder.nvEncDestroyBitstreamBuffer;
let h_encoder = self.h_encoder;
let bitstream_guard = ReleaseGuard::new(move || {
destroy.map(|f| f(h_encoder, output_buffer));
});
Ok((output_buffer, bitstream_guard))
}
}
fn encode_picture(
&mut self,
mapped_resource: sys::NV_ENC_INPUT_PTR,
output_buffer: sys::NV_ENC_OUTPUT_PTR,
options: &EncodeOptions,
) -> Result<(), Error> {
unsafe {
let mut pic_params: sys::NV_ENC_PIC_PARAMS = std::mem::zeroed();
pic_params.version = sys::NV_ENC_PIC_PARAMS_VER;
pic_params.inputWidth = self.width;
pic_params.inputHeight = self.height;
pic_params.inputPitch = self.buffer_format_enum.bytes_per_row(self.width)?;
pic_params.inputBuffer = mapped_resource;
pic_params.outputBitstream = output_buffer;
pic_params.bufferFmt = self.buffer_format;
pic_params.pictureStruct = sys::_NV_ENC_PIC_STRUCT_NV_ENC_PIC_STRUCT_FRAME;
pic_params.inputTimeStamp = self.frame_count * self.framerate_den;
pic_params.encodePicFlags = options.to_pic_flags();
self.frame_count += 1;
let status = self
.encoder
.nvEncEncodePicture
.map(|f| f(self.h_encoder, &mut pic_params))
.unwrap_or(sys::_NVENCSTATUS_NV_ENC_ERR_INVALID_PTR);
Error::check_nvenc(status, "nvEncEncodePicture")?;
Ok(())
}
}
fn lock_and_copy_bitstream(
&mut self,
output_buffer: sys::NV_ENC_OUTPUT_PTR,
) -> Result<EncodedFrame, Error> {
unsafe {
let mut lock_bitstream: sys::NV_ENC_LOCK_BITSTREAM = std::mem::zeroed();
lock_bitstream.version = sys::NV_ENC_LOCK_BITSTREAM_VER;
lock_bitstream.outputBitstream = output_buffer;
let status = self
.encoder
.nvEncLockBitstream
.map(|f| f(self.h_encoder, &mut lock_bitstream))
.unwrap_or(sys::_NVENCSTATUS_NV_ENC_ERR_INVALID_PTR);
Error::check_nvenc(status, "nvEncLockBitstream")?;
let unlock_fn = self.encoder.nvEncUnlockBitstream;
let h_encoder = self.h_encoder;
let output_bitstream = lock_bitstream.outputBitstream;
let _unlock_guard = crate::ReleaseGuard::new(move || {
if let Some(f) = unlock_fn {
let _ = f(h_encoder, output_bitstream);
}
});
let ptr = lock_bitstream.bitstreamBufferPtr as *const u8;
let size = lock_bitstream.bitstreamSizeInBytes as usize;
let encoded_data = if ptr.is_null() {
return Err(Error::new_custom(
"nvEncLockBitstream",
"bitstreamBufferPtr is null",
));
} else if size == 0 {
Vec::new()
} else {
std::slice::from_raw_parts(ptr, size).to_vec()
};
let timestamp = lock_bitstream.outputTimeStamp;
let picture_type = PictureType::new(lock_bitstream.pictureType);
Ok(EncodedFrame {
data: encoded_data,
timestamp,
picture_type,
})
}
}
pub fn finish(&mut self) -> Result<(), Error> {
unsafe {
let mut pic_params: sys::NV_ENC_PIC_PARAMS = std::mem::zeroed();
pic_params.version = sys::NV_ENC_PIC_PARAMS_VER;
pic_params.encodePicFlags = sys::NV_ENC_PIC_FLAG_EOS;
pic_params.inputTimeStamp = self.frame_count;
let status = self
.encoder
.nvEncEncodePicture
.map(|f| f(self.h_encoder, &mut pic_params))
.unwrap_or(sys::_NVENCSTATUS_NV_ENC_ERR_INVALID_PTR);
Error::check_nvenc(status, "nvEncEncodePicture")?;
Ok(())
}
}
pub fn next_frame(&mut self) -> Option<EncodedFrame> {
self.encoded_frames.pop_front()
}
}
impl Drop for Encoder {
fn drop(&mut self) {
unsafe {
let _ = self.lib.with_context(self.ctx, || {
if let Some(destroy_fn) = self.encoder.nvEncDestroyEncoder {
destroy_fn(self.h_encoder);
}
Ok(())
});
let _ = self.lib.cu_ctx_destroy(self.ctx);
}
}
}
impl std::fmt::Debug for Encoder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Encoder")
.field("ctx", &format_args!("{:p}", self.ctx))
.field("h_encoder", &format_args!("{:p}", self.h_encoder))
.field("width", &self.width)
.field("height", &self.height)
.field("buffer_format", &self.buffer_format)
.field("frame_count", &self.frame_count)
.finish()
}
}
unsafe impl Send for Encoder {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PictureType {
P,
B,
I,
Idr,
Bi,
Skipped,
IntraRefresh,
NonRefP,
Switch,
Unknown,
}
impl PictureType {
fn new(pic_type: sys::NV_ENC_PIC_TYPE) -> Self {
match pic_type {
sys::_NV_ENC_PIC_TYPE_NV_ENC_PIC_TYPE_P => PictureType::P,
sys::_NV_ENC_PIC_TYPE_NV_ENC_PIC_TYPE_B => PictureType::B,
sys::_NV_ENC_PIC_TYPE_NV_ENC_PIC_TYPE_I => PictureType::I,
sys::_NV_ENC_PIC_TYPE_NV_ENC_PIC_TYPE_IDR => PictureType::Idr,
sys::_NV_ENC_PIC_TYPE_NV_ENC_PIC_TYPE_BI => PictureType::Bi,
sys::_NV_ENC_PIC_TYPE_NV_ENC_PIC_TYPE_SKIPPED => PictureType::Skipped,
sys::_NV_ENC_PIC_TYPE_NV_ENC_PIC_TYPE_INTRA_REFRESH => PictureType::IntraRefresh,
sys::_NV_ENC_PIC_TYPE_NV_ENC_PIC_TYPE_NONREF_P => PictureType::NonRefP,
sys::_NV_ENC_PIC_TYPE_NV_ENC_PIC_TYPE_SWITCH => PictureType::Switch,
_ => PictureType::Unknown,
}
}
}
#[derive(Debug, Clone)]
pub struct EncodedFrame {
data: Vec<u8>,
timestamp: u64,
picture_type: PictureType,
}
impl EncodedFrame {
pub fn data(&self) -> &[u8] {
&self.data
}
pub fn into_data(self) -> Vec<u8> {
self.data
}
pub fn timestamp(&self) -> u64 {
self.timestamp
}
pub fn picture_type(&self) -> PictureType {
self.picture_type
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_encoder_config(codec: CodecConfig) -> EncoderConfig {
EncoderConfig {
codec,
width: 640,
height: 480,
max_encode_width: None,
max_encode_height: None,
framerate_num: 30,
framerate_den: 1,
average_bitrate: Some(5_000_000),
preset: Preset::P4,
tuning_info: TuningInfo::LOW_LATENCY,
rate_control_mode: RateControlMode::Vbr,
gop_length: None,
frame_interval_p: 1,
buffer_format: BufferFormat::Nv12,
device_id: 0,
}
}
#[test]
fn init_h264_encoder() {
let config = test_encoder_config(CodecConfig::H264(H264EncoderConfig {
profile: None,
idr_period: None,
}));
let _encoder = Encoder::new(config).expect("failed to initialize h264 encoder");
println!("h264 encoder initialized successfully");
}
#[test]
fn init_h265_encoder() {
let config = test_encoder_config(CodecConfig::Hevc(HevcEncoderConfig {
profile: None,
idr_period: None,
}));
let _encoder = Encoder::new(config).expect("failed to initialize h265 encoder");
println!("h265 encoder initialized successfully");
}
#[test]
fn init_av1_encoder() {
let config = test_encoder_config(CodecConfig::Av1(Av1EncoderConfig {
profile: None,
idr_period: None,
}));
let _encoder = Encoder::new(config).expect("failed to initialize av1 encoder");
println!("av1 encoder initialized successfully");
}
#[test]
fn test_get_sequence_params_h264() {
let config = test_encoder_config(CodecConfig::H264(H264EncoderConfig {
profile: None,
idr_period: None,
}));
let mut encoder = Encoder::new(config).expect("failed to create h264 encoder");
let seq_params = encoder
.get_sequence_params()
.expect("failed to get sequence parameters");
assert!(
!seq_params.is_empty(),
"Sequence parameters should not be empty"
);
println!("H.264 sequence parameters size: {} bytes", seq_params.len());
}
#[test]
fn test_get_sequence_params_h265() {
let config = test_encoder_config(CodecConfig::Hevc(HevcEncoderConfig {
profile: None,
idr_period: None,
}));
let mut encoder = Encoder::new(config).expect("failed to create h265 encoder");
let seq_params = encoder
.get_sequence_params()
.expect("failed to get sequence parameters");
assert!(
!seq_params.is_empty(),
"Sequence parameters should not be empty"
);
println!("H.265 sequence parameters size: {} bytes", seq_params.len());
}
#[test]
fn test_get_sequence_params_av1() {
let config = test_encoder_config(CodecConfig::Av1(Av1EncoderConfig {
profile: None,
idr_period: None,
}));
let mut encoder = Encoder::new(config).expect("failed to create av1 encoder");
let seq_params = encoder
.get_sequence_params()
.expect("failed to get sequence parameters");
assert!(
!seq_params.is_empty(),
"Sequence parameters should not be empty"
);
println!("AV1 sequence header size: {} bytes", seq_params.len());
}
#[test]
fn test_encode_h264_black_frame() {
let config = test_encoder_config(CodecConfig::H264(H264EncoderConfig {
profile: None,
idr_period: None,
}));
let width = config.width;
let height = config.height;
let mut encoder = Encoder::new(config).expect("failed to create h264 encoder");
let y_size = (width * height) as usize;
let uv_size = (width * height / 2) as usize;
let mut frame_data = vec![16u8; y_size + uv_size];
frame_data[y_size..].fill(128);
encoder
.encode(
&frame_data,
&EncodeOptions {
force_intra: false,
force_idr: false,
output_spspps: false,
},
)
.expect("failed to encode black frame");
encoder.finish().expect("failed to finish encoder");
let mut frames = Vec::new();
while let Some(frame) = encoder.next_frame() {
frames.push(frame);
}
assert!(!frames.is_empty(), "No encoded frames received");
let first_frame = &frames[0];
assert!(
matches!(first_frame.picture_type, PictureType::I | PictureType::Idr),
"First frame should be a keyframe"
);
assert!(
!first_frame.data.is_empty(),
"Encoded frame should have data"
);
println!(
"Successfully encoded black frame: {} frames, first frame size: {} bytes",
frames.len(),
first_frame.data.len()
);
}
#[test]
fn test_encode_h265_black_frame() {
let config = test_encoder_config(CodecConfig::Hevc(HevcEncoderConfig {
profile: None,
idr_period: None,
}));
let width = config.width;
let height = config.height;
let mut encoder = Encoder::new(config).expect("failed to create h265 encoder");
let y_size = (width * height) as usize;
let uv_size = (width * height / 2) as usize;
let mut frame_data = vec![16u8; y_size + uv_size];
frame_data[y_size..].fill(128);
encoder
.encode(
&frame_data,
&EncodeOptions {
force_intra: false,
force_idr: false,
output_spspps: false,
},
)
.expect("failed to encode black frame");
encoder.finish().expect("failed to finish encoder");
let mut frames = Vec::new();
while let Some(frame) = encoder.next_frame() {
frames.push(frame);
}
assert!(!frames.is_empty(), "No encoded frames received");
let first_frame = &frames[0];
assert!(
matches!(first_frame.picture_type, PictureType::I | PictureType::Idr),
"First frame should be a keyframe"
);
assert!(
!first_frame.data.is_empty(),
"Encoded frame should have data"
);
println!(
"Successfully encoded black frame: {} frames, first frame size: {} bytes",
frames.len(),
first_frame.data.len()
);
}
#[test]
fn test_encode_av1_black_frame() {
let config = test_encoder_config(CodecConfig::Av1(Av1EncoderConfig {
profile: None,
idr_period: None,
}));
let width = config.width;
let height = config.height;
let mut encoder = Encoder::new(config).expect("failed to create av1 encoder");
let y_size = (width * height) as usize;
let uv_size = (width * height / 2) as usize;
let mut frame_data = vec![16u8; y_size + uv_size];
frame_data[y_size..].fill(128);
encoder
.encode(
&frame_data,
&EncodeOptions {
force_intra: false,
force_idr: false,
output_spspps: false,
},
)
.expect("failed to encode black frame");
encoder.finish().expect("failed to finish encoder");
let mut frames = Vec::new();
while let Some(frame) = encoder.next_frame() {
frames.push(frame);
}
assert!(!frames.is_empty(), "No encoded frames received");
let first_frame = &frames[0];
assert!(
matches!(first_frame.picture_type, PictureType::I | PictureType::Idr),
"First frame should be a keyframe"
);
assert!(
!first_frame.data.is_empty(),
"Encoded frame should have data"
);
println!(
"Successfully encoded black frame: {} frames, first frame size: {} bytes",
frames.len(),
first_frame.data.len()
);
}
}