From 4952cbaf19db323560f22c2fa50de5070804f4c4 Mon Sep 17 00:00:00 2001 From: mofeng-git Date: Sat, 11 Apr 2026 21:55:34 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20RTSP=20=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=AF=B9=20VLC=20=E8=A7=86=E9=A2=91=E6=92=AD=E6=94=BE?= =?UTF-8?q?=E5=99=A8=E7=9A=84=E5=85=BC=E5=AE=B9=E6=80=A7=20#237?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/rtsp/service.rs | 54 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/src/rtsp/service.rs b/src/rtsp/service.rs index ae17ab6d..8f3afb08 100644 --- a/src/rtsp/service.rs +++ b/src/rtsp/service.rs @@ -498,6 +498,9 @@ async fn stream_video_interleaved( let mut h265_payloader = H265Payloader::new(); let mut ctrl_read_buf = [0u8; RTSP_BUF_SIZE]; let mut ctrl_buffer = Vec::with_capacity(RTSP_BUF_SIZE); + // RTP timestamps must increase; pts_ms is often 0 for many frames (capture→encode jitter), + // which yields a flat RTP timestamp and breaks VLC/ffplay. + let mut last_rtp_timestamp: u32 = 0; loop { tokio::select! { @@ -529,7 +532,11 @@ async fn stream_video_interleaved( update_parameter_sets(&mut params, &frame); } - let rtp_timestamp = pts_to_rtp_timestamp(frame.pts_ms); + let rtp_timestamp = monotonic_rtp_timestamp( + frame.pts_ms, + &mut last_rtp_timestamp, + frame.duration, + ); let payloads: Vec = match rtsp_codec { RtspCodec::H264 => h264_payloader @@ -1128,6 +1135,29 @@ fn pts_to_rtp_timestamp(pts_ms: i64) -> u32 { ((pts_ms as u64 * RTP_CLOCK_RATE as u64) / 1000) as u32 } +/// 90 kHz ticks per frame from nominal duration (at least 1). +fn rtp_timestamp_increment(frame_duration: Duration) -> u32 { + let inc = (frame_duration.as_secs_f64() * f64::from(RTP_CLOCK_RATE)).round() as u32; + inc.max(1) +} + +/// Prefer PTS-based RTP time when it advances; otherwise step by `frame_duration` in 90 kHz units. +fn monotonic_rtp_timestamp( + pts_ms: i64, + last: &mut u32, + frame_duration: Duration, +) -> u32 { + let from_pts = pts_to_rtp_timestamp(pts_ms); + let inc = rtp_timestamp_increment(frame_duration); + let ts = if from_pts > *last { + from_pts + } else { + last.wrapping_add(inc) + }; + *last = ts; + ts +} + fn generate_session_id() -> String { let mut rng = rand::rng(); let value: u64 = rng.random(); @@ -1199,6 +1229,28 @@ mod tests { assert_eq!(response.status(), rtsp::StatusCode::MethodNotAllowed); } + #[test] + fn monotonic_rtp_timestamp_steps_when_pts_stays_zero() { + let d = Duration::from_millis(33); + let mut last = 0u32; + let a = monotonic_rtp_timestamp(0, &mut last, d); + let b = monotonic_rtp_timestamp(0, &mut last, d); + let c = monotonic_rtp_timestamp(0, &mut last, d); + assert!(a > 0); + assert!(b > a); + assert!(c > b); + } + + #[test] + fn monotonic_rtp_timestamp_uses_pts_when_it_advances() { + let d = Duration::from_millis(33); + let mut last = 0u32; + let a = monotonic_rtp_timestamp(1000, &mut last, d); + assert_eq!(a, 90_000); + let b = monotonic_rtp_timestamp(2000, &mut last, d); + assert_eq!(b, 180_000); + } + #[test] fn build_sdp_h264_is_parseable_with_expected_video_attributes() { let config = RtspConfig::default();