remote/vendor/nvenc/src/safe/input_buffer.rs
郑博文 e814cb64e7 fix(agent): vendor nvenc 并补 FORCEIDR 标志——修复运行期关键帧请求被静默忽略
根因(实测确认,非推测)
上游 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 并解码成功
2026-10-28 16:05:41 +08:00

61 lines
1.3 KiB
Rust

use std::{ffi::c_void, sync::Arc};
use crate::{encoder::EncoderInternal, sys::result::NVencError};
pub struct InputBuffer {
pub(crate) encoder: Arc<EncoderInternal>,
pub(crate) buffer: *mut c_void,
pub(crate) width: u32,
pub(crate) height: u32,
}
impl InputBuffer {
pub fn lock(&self) -> Result<InputBufferLock<'_>, NVencError> {
let (lock_ptr, pitch) = self.encoder.lock_input_buffer(self.buffer)?;
Ok(InputBufferLock {
input_buffer: self,
lock_ptr,
pitch,
})
}
}
impl Drop for InputBuffer {
fn drop(&mut self) {
self.encoder.destroy_input_buffer(self.buffer).unwrap()
}
}
pub struct InputBufferLock<'a> {
input_buffer: &'a InputBuffer,
lock_ptr: *mut c_void,
pitch: u32,
}
impl InputBufferLock<'_> {
pub unsafe fn data_ptr(&self) -> *mut u8 {
self.lock_ptr as _
}
pub fn width(&self) -> u32 {
self.input_buffer.width
}
pub fn height(&self) -> u32 {
self.input_buffer.height
}
pub fn pitch(&self) -> u32 {
self.pitch
}
}
impl Drop for InputBufferLock<'_> {
fn drop(&mut self) {
self.input_buffer
.encoder
.unlock_input_buffer(self.lock_ptr)
.unwrap();
}
}