perf(video): 改善视频卡顿问题并优化编码性能

改善内容:
1. NAL单元duration累积bug修复
   - 修改video_track.rs和unified_video_track.rs
   - 只有帧内最后一个NAL获得frame_duration,其他为ZERO
   - 确保同一帧的所有NAL共享相同的RTP时间戳

2. 修复VP8/VP9固定1秒duration错误
   - 将Duration::from_secs(1)改为正确的frame_duration计算

3. PTS计算优化(shared_video_pipeline.rs)
   - 将pipeline_start_time从Mutex<Option<Instant>>改为AtomicI64
   - 消除每帧一次的async mutex lock
   - 使用compare_exchange实现无锁的首帧时间设置

4. 避免重复读取config
   - 在encode_frame中缓存fps,避免末尾再次获取config锁

5. 编码器零拷贝优化
   - H264/H265/VP8/VP9编码器使用drain()替代clone()
   - 减少内存分配和拷贝开销

6. MJPEG处理器优化
   - 无客户端时跳过JPEG编码(WebRTC-only模式优化)

7. RKMPP YUYV直接输入支持
   - hwcodec C++层添加YUYV422格式支持
   - H264编码器添加Yuyv422输入格式选项
This commit is contained in:
mofeng-git
2026-01-02 11:58:55 +08:00
parent 0fc5be21c6
commit 04e62d1e3f
9 changed files with 168 additions and 40 deletions

View File

@@ -99,6 +99,8 @@ pub enum H264InputFormat {
Yuv420p,
/// NV12 - Y plane + interleaved UV plane (optimal for VAAPI)
Nv12,
/// YUYV422 - packed YUV 4:2:2 format (optimal for RKMPP direct input)
Yuyv422,
}
impl Default for H264InputFormat {
@@ -157,6 +159,17 @@ impl H264Config {
}
}
/// Create config for low latency streaming with YUYV422 input (optimal for RKMPP direct input)
pub fn low_latency_yuyv422(resolution: Resolution, bitrate_kbps: u32) -> Self {
Self {
base: EncoderConfig::h264(resolution, bitrate_kbps),
bitrate_kbps,
gop_size: 30,
fps: 30,
input_format: H264InputFormat::Yuyv422,
}
}
/// Create config for quality streaming
pub fn quality(resolution: Resolution, bitrate_kbps: u32) -> Self {
Self {
@@ -275,6 +288,7 @@ impl H264Encoder {
let pixfmt = match config.input_format {
H264InputFormat::Nv12 => AVPixelFormat::AV_PIX_FMT_NV12,
H264InputFormat::Yuv420p => AVPixelFormat::AV_PIX_FMT_YUV420P,
H264InputFormat::Yuyv422 => AVPixelFormat::AV_PIX_FMT_YUYV422,
};
info!(
@@ -367,11 +381,12 @@ impl H264Encoder {
match self.inner.encode(data, pts_ms) {
Ok(frames) => {
// Copy frame data to owned HwEncodeFrame
// Zero-copy: drain frames from hwcodec buffer instead of cloning
// hwcodec returns &mut Vec, so we can take ownership via drain
let owned_frames: Vec<HwEncodeFrame> = frames
.iter()
.drain(..)
.map(|f| HwEncodeFrame {
data: f.data.clone(),
data: f.data, // Move, not clone
pts: f.pts,
key: f.key,
})
@@ -438,7 +453,7 @@ impl Encoder for H264Encoder {
// Assume input is YUV420P
let pts_ms = (sequence * 1000 / self.config.fps as u64) as i64;
let frames = self.encode_yuv420p(data, pts_ms)?;
let mut frames = self.encode_yuv420p(data, pts_ms)?;
if frames.is_empty() {
// Encoder needs more frames (shouldn't happen with our config)
@@ -446,12 +461,12 @@ impl Encoder for H264Encoder {
return Err(AppError::VideoError("Encoder returned no frames".to_string()));
}
// Take the first frame
let frame = &frames[0];
// Take ownership of the first frame (zero-copy)
let frame = frames.remove(0);
let key_frame = frame.key == 1;
Ok(EncodedFrame::h264(
Bytes::from(frame.data.clone()),
Bytes::from(frame.data), // Move Vec into Bytes (zero-copy)
self.config.base.resolution,
key_frame,
sequence,
@@ -479,6 +494,7 @@ impl Encoder for H264Encoder {
match self.config.input_format {
H264InputFormat::Nv12 => matches!(format, PixelFormat::Nv12),
H264InputFormat::Yuv420p => matches!(format, PixelFormat::Yuv420),
H264InputFormat::Yuyv422 => matches!(format, PixelFormat::Yuyv),
}
}
}