根因(实测确认,非推测) 上游 nvenc 0.1.0 的 encode_picture 只设置 picture_type=NV_ENC_PIC_TYPE_IDR, 却从未设置 encode_pic_flags=NV_ENC_PIC_FLAG_FORCEIDR。NVENC 会据此忽略请求、 继续输出 P 帧。首帧之所以「看起来正常」,是因为 frame_idx==0 本来就强制 IDR, 与该字段无关——这也是该缺陷长期未被发现的原因。 证据(同一 frame_count=4,修复前后) - 修复前:输出 NAL 首字节 0x61 → 0x61 & 0x1F = 1 = 普通 P 片 - 修复后:输出 NAL 首字节 0x65 → 0x65 & 0x1F = 5 = IDR 片 影响 这解释了构建运行手册 §6.1 记录的「静止桌面黑屏」:NVENC 路径的重编 IDR 从未 真正生效,KeyFrameReq 拿不到 IDR 就无法从解码错误中恢复。 改动 - 新增 vendor/nvenc(上游为 MIT 许可,可自由修改分发,符合商用要求) - vendor/nvenc/src/safe/encoder.rs:picture_type=IDR 时置 ForceIDR; IntraRefresh 时置 ForceIntra。改动处有「本项目补丁」注释标记 - agent/Cargo.toml:nvenc 改为 path 依赖指向 vendor 版本 建议后续向上游提 PR(github.com/AlsoSylv/nvenc),以降低长期维护成本。 验证:跳帧后发 KeyFrameReq,第 3 帧即收到 IDR 并解码成功
199 lines
6.8 KiB
Rust
199 lines
6.8 KiB
Rust
use std::{marker::PhantomData, sync::Arc};
|
|
|
|
use crate::{
|
|
encoder::{Encoder, EncoderInternal},
|
|
sys::{
|
|
enums::{NVencBufferFormat, NVencDeviceType, NVencTuningInfo},
|
|
result::NVencError,
|
|
structs::{
|
|
Guid, NV_ENC_INITIALIZE_PARAMS_VER, NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER,
|
|
NVencConfig, NVencInitializeParams, NVencOpenEncodeSessionExParams, NVencPresetConfig,
|
|
},
|
|
version::NVENC_API_VERSION,
|
|
},
|
|
};
|
|
|
|
pub struct Session<T> {
|
|
encoder: Arc<EncoderInternal>,
|
|
p: PhantomData<T>,
|
|
}
|
|
|
|
pub struct NeedsConfig;
|
|
|
|
pub struct NeedsInit;
|
|
|
|
#[cfg(windows)]
|
|
use windows::core::Interface;
|
|
|
|
impl Session<NeedsConfig> {
|
|
#[cfg(windows)]
|
|
pub fn open_dx(device: &impl Interface) -> Result<Self, NVencError> {
|
|
let lib = crate::nvenc_init().unwrap();
|
|
let function_list = lib.create_instance()?;
|
|
let mut session_params: NVencOpenEncodeSessionExParams = unsafe { std::mem::zeroed() };
|
|
session_params.version = NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER;
|
|
session_params.device_type = NVencDeviceType::DirectX;
|
|
session_params.api_version = NVENC_API_VERSION;
|
|
session_params.device = device.as_raw();
|
|
let mut encoder = std::ptr::null_mut();
|
|
unsafe {
|
|
(function_list.nvenc_open_encode_session_ex)(&raw mut session_params, &raw mut encoder)
|
|
.into_error()
|
|
}?;
|
|
assert!(!encoder.is_null());
|
|
let encoder = EncoderInternal {
|
|
encoder: unsafe { std::ptr::NonNull::new_unchecked(encoder) },
|
|
function_list,
|
|
};
|
|
Ok(Self {
|
|
encoder: Arc::new(encoder),
|
|
p: PhantomData,
|
|
})
|
|
}
|
|
|
|
pub fn open_gl() -> Result<Self, NVencError> {
|
|
let lib = crate::nvenc_init().unwrap();
|
|
let function_list = lib.create_instance()?;
|
|
let mut session_params: NVencOpenEncodeSessionExParams = unsafe { std::mem::zeroed() };
|
|
session_params.version = NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER;
|
|
session_params.device_type = NVencDeviceType::OpenGL;
|
|
session_params.api_version = NVENC_API_VERSION;
|
|
session_params.device = std::ptr::null_mut();
|
|
|
|
let mut encoder = std::ptr::null_mut();
|
|
unsafe {
|
|
(function_list.nvenc_open_encode_session_ex)(&raw mut session_params, &raw mut encoder)
|
|
.into_error()
|
|
}?;
|
|
assert!(!encoder.is_null());
|
|
let encoder = EncoderInternal {
|
|
encoder: unsafe { std::ptr::NonNull::new_unchecked(encoder) },
|
|
function_list,
|
|
};
|
|
Ok(Self {
|
|
encoder: Arc::new(encoder),
|
|
p: PhantomData,
|
|
})
|
|
}
|
|
|
|
pub fn get_encode_codecs(&self) -> Result<Box<[Guid]>, NVencError> {
|
|
let mut count = 0;
|
|
unsafe {
|
|
(self.encoder.function_list.nvenc_get_encoder_guid_count)(
|
|
self.encoder.encoder.as_ptr(),
|
|
&raw mut count,
|
|
)
|
|
.into_error()
|
|
}?;
|
|
let mut array = vec![Guid::default(); count as usize];
|
|
unsafe {
|
|
(self.encoder.function_list.nvenc_get_encoder_guids)(
|
|
self.encoder.encoder.as_ptr(),
|
|
array.as_mut_ptr(),
|
|
count,
|
|
&raw mut count,
|
|
)
|
|
}
|
|
.into_error()?;
|
|
array.truncate(count as usize);
|
|
Ok(array.into())
|
|
}
|
|
|
|
pub fn get_encode_presets(&self, codec: Guid) -> Result<Box<[Guid]>, NVencError> {
|
|
let mut count = 0;
|
|
unsafe {
|
|
(self.encoder.function_list.nvenc_get_encode_preset_count)(
|
|
self.encoder.encoder.as_ptr(),
|
|
codec.clone(),
|
|
&raw mut count,
|
|
)
|
|
.into_error()
|
|
}?;
|
|
let mut array = vec![Guid::default(); count as usize];
|
|
unsafe {
|
|
(self.encoder.function_list.nvenc_get_encode_preset_guids)(
|
|
self.encoder.encoder.as_ptr(),
|
|
codec,
|
|
array.as_mut_ptr(),
|
|
count,
|
|
&raw mut count,
|
|
)
|
|
}
|
|
.into_error()?;
|
|
array.truncate(count as usize);
|
|
Ok(array.into())
|
|
}
|
|
|
|
pub fn get_encode_preset_config_ex(
|
|
self,
|
|
codec: Guid,
|
|
preset: Guid,
|
|
tuning_info: NVencTuningInfo,
|
|
) -> Result<(Session<NeedsInit>, NVencPresetConfig), NVencError> {
|
|
self.encoder
|
|
.get_encode_preset_config_ex(codec, preset, tuning_info)
|
|
.map(|config| {
|
|
(
|
|
Session {
|
|
encoder: self.encoder,
|
|
p: PhantomData,
|
|
},
|
|
config,
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
pub struct InitParams<'a> {
|
|
pub encode_guid: Guid,
|
|
pub preset_guid: Guid,
|
|
pub resolution: [u32; 2],
|
|
pub aspect_ratio: [u32; 2],
|
|
pub frame_rate: [u32; 2],
|
|
pub tuning_info: NVencTuningInfo,
|
|
pub buffer_format: NVencBufferFormat,
|
|
pub encode_config: &'a mut NVencConfig,
|
|
pub enable_ptd: bool,
|
|
pub max_encoder_resolution: [u32; 2],
|
|
// TODO: Support for async encoding and bit fields
|
|
}
|
|
|
|
impl Session<NeedsInit> {
|
|
pub fn init_encoder(self, init_params: InitParams) -> Result<Encoder, NVencError> {
|
|
// This sucks, lol
|
|
let raw_init_params = NVencInitializeParams {
|
|
encode_guid: init_params.encode_guid,
|
|
preset_guid: init_params.preset_guid,
|
|
encode_width: init_params.resolution[0],
|
|
encode_height: init_params.resolution[1],
|
|
dar_width: init_params.aspect_ratio[0],
|
|
dar_height: init_params.aspect_ratio[1],
|
|
frame_rate_num: init_params.frame_rate[0],
|
|
frame_rate_den: init_params.frame_rate[1],
|
|
tuning_info: init_params.tuning_info,
|
|
buffer_format: init_params.buffer_format,
|
|
encode_config: init_params.encode_config as _,
|
|
enable_ptd: init_params.enable_ptd as u32,
|
|
version: NV_ENC_INITIALIZE_PARAMS_VER,
|
|
enable_encode_async: 0,
|
|
bit_flags: crate::sys::structs::NVencInitializeParamsBitfields::new(),
|
|
priv_data_size: 0,
|
|
rsvd: 0,
|
|
priv_data: std::ptr::null_mut(),
|
|
max_encode_width: init_params.max_encoder_resolution[0],
|
|
max_encode_height: init_params.max_encoder_resolution[1],
|
|
max_me_hint_counts_per_block: [Default::default(), Default::default()],
|
|
num_state_buffers: 0,
|
|
output_stats_level: crate::sys::enums::NVencOutputStatsLevel::None,
|
|
rsvd1: unsafe { std::mem::zeroed() },
|
|
rsvd2: unsafe { std::mem::zeroed() },
|
|
};
|
|
|
|
self.encoder.init_encoder(raw_init_params)?;
|
|
Ok(Encoder {
|
|
encoder: self.encoder,
|
|
marker: PhantomData,
|
|
})
|
|
}
|
|
}
|