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

@@ -490,10 +490,13 @@ impl UnifiedVideoTrack {
/// Write VP8 frame (raw encoded)
async fn write_vp8_frame(&self, data: &[u8], is_keyframe: bool) -> Result<()> {
// Calculate frame duration based on configured FPS
let frame_duration = Duration::from_micros(1_000_000 / self.config.fps.max(1) as u64);
// VP8 frames are sent directly
let sample = Sample {
data: Bytes::copy_from_slice(data),
duration: Duration::from_secs(1),
duration: frame_duration,
..Default::default()
};
@@ -514,10 +517,13 @@ impl UnifiedVideoTrack {
/// Write VP9 frame (raw encoded)
async fn write_vp9_frame(&self, data: &[u8], is_keyframe: bool) -> Result<()> {
// Calculate frame duration based on configured FPS
let frame_duration = Duration::from_micros(1_000_000 / self.config.fps.max(1) as u64);
// VP9 frames are sent directly
let sample = Sample {
data: Bytes::copy_from_slice(data),
duration: Duration::from_secs(1),
duration: frame_duration,
..Default::default()
};
@@ -537,25 +543,33 @@ impl UnifiedVideoTrack {
}
/// Send NAL units via track (for H264/H265)
///
/// Important: Only the last NAL unit should have the frame duration set.
/// All NAL units in a frame share the same RTP timestamp, so only the last
/// one should increment the timestamp by the frame duration.
async fn send_nal_units(&self, nals: Vec<Bytes>, is_keyframe: bool) -> Result<()> {
let mut total_bytes = 0u64;
let mut nal_count = 0;
let nal_count = nals.len();
// Calculate frame duration based on configured FPS
let frame_duration = Duration::from_micros(1_000_000 / self.config.fps.max(1) as u64);
for nal_data in nals {
for (i, nal_data) in nals.into_iter().enumerate() {
let is_last = i == nal_count - 1;
// Only the last NAL should have duration set
// This ensures all NALs in a frame share the same RTP timestamp
let sample = Sample {
data: nal_data.clone(),
duration: Duration::from_secs(1),
duration: if is_last { frame_duration } else { Duration::ZERO },
..Default::default()
};
if let Err(e) = self.track.write_sample(&sample).await {
if nal_count % 100 == 0 {
if i % 100 == 0 {
debug!("write_sample failed (no peer?): {}", e);
}
}
total_bytes += nal_data.len() as u64;
nal_count += 1;
}
if nal_count > 0 {

View File

@@ -484,17 +484,25 @@ impl UniversalVideoTrack {
}
/// Send NAL units as samples (H264 only)
///
/// Important: Only the last NAL unit should have the frame duration set.
/// All NAL units in a frame share the same RTP timestamp, so only the last
/// one should increment the timestamp by the frame duration.
async fn send_nals(&self, nals: Vec<Bytes>, is_keyframe: bool) -> Result<()> {
let mut total_bytes = 0u64;
// Calculate frame duration based on configured FPS
let frame_duration = Duration::from_micros(1_000_000 / self.config.fps.max(1) as u64);
let nal_count = nals.len();
match &self.track {
TrackType::Sample(track) => {
for nal_data in nals {
for (i, nal_data) in nals.into_iter().enumerate() {
let is_last = i == nal_count - 1;
// Only the last NAL should have duration set
// This ensures all NALs in a frame share the same RTP timestamp
let sample = Sample {
data: nal_data.clone(),
duration: frame_duration,
duration: if is_last { frame_duration } else { Duration::ZERO },
..Default::default()
};