
1、为什么从rtsp拉取的媒体流,每个流的rtp时间戳都不是从0开始的
因为 RTP 时间戳根本不要求从 0 开始,协议里就是“随机初始值 + 每采样 +1”——这样设计省掉了“回绕清零”带来的同步风暴,也让多摄像头/多麦克风同时上线时不会“撞钟”。
RFC 3550 原话
The timestamp is a 32-bit field that reflects the sampling instant of the first octet in the RTP data packet.
It must be chosen randomly for each stream.
实际原因
-
防止 90 kHz 回绕冲突
90 000 Hz × 15 h ≈ 2³²,如果全从 0 开始,全网设备同时上线,15 小时后一起回绕,服务器做 RTCP 统计时很难区分是谁的包。
-
多流同时存在时天然“错峰”
摄像头 A 随机到 0x3c5a1234,摄像头 B 随机到 0x9f00abcd,即使它们同一时刻出帧,RTP 时间戳也不同,接收端靠 RTCP SR 各自映射到 NTP 即可,不会把两路流当成同一条。
-
厂家实现简单
芯片上电后把内部 90 kHz 自由运行计数器当前值直接当“第一帧”戳,写完 SDP 就发流,无需额外清零逻辑,也免了断电重启后“时间戳倒退”的麻烦。
-
RTSP/ONVIF 并不要求“归零”
PLAY 响应里的
RTP-Info: rtptime=xxxx可以填任意值;FFmpeg 收到后会把xxxx当“起点”减掉,再换算成 pts,所以观众端依旧从 0 开始看,对业务毫无影响。
2、多流同时传输时,每个流开始的rtp时间戳都不相同,那怎么做同步?
在多流 RTP 各自独立起播、时间戳不同 的场景里,真正用来做音视频同步的并不是 RTP 时间戳本身,而是会话级的 RTCP SR(Sender Report) 里携带的 RTP↔NTP 映射关系。
只要拿到每个流的 SR,就可以把任意一个 RTP 时间戳换算成绝对时钟(NTP)时间,然后再把不同流的数据对齐到同一个时间轴,后面再按解码/渲染时钟做平滑播放即可。流程如下:
1. 发端:每个流周期发 RTCP SR
-
在 SR 包里填 2 个关键字段
– RTP timestamp(对应本流采样时刻)
– NTP timestamp(64-bit,UTC 时钟,单位 1/2³² s)
-
两字段给出线性映射:
NTP = f(RTP) = k·RTP + b
其中斜率 k = 1/采样时钟,截距 b 由发端首次打包时确定。
- 不同流的“起点”可以不同,但 SR 把各自的 RTP 都锚定到同一个 NTP 时钟。
2. 收端:收到 SR → 建立每流“时间映射表”
- 每收到一次 SR,就更新一条记录
stream_id | last_S RTP | last_S NTP | 采样时钟
- 以后凡是属于该流的 RTP 包,都能插值得出对应的 NTP 时间:
NTP_pkt = NTP_SR + (RTP_pkt – RTP_SR) / 采样时钟
误差 ≤ 一个 RTCP 周期(通常 15 s,可配)。
3. 同步:把所有流都换算到“共同时钟”
- 对任意要同时播放的音/视帧,先算出各自的 NTP_present。
- 取 max(NTP_audio, NTP_video) 作为“同步呈现点”,或者按自定义策略(如音频为主轴)。
- 把该 NTP 再反算回各流自己的 RTP 时间戳/采样序号,通知解码器/渲染器在该时刻出图/出声。
- 如果解码/渲染端用的是一个共用系统时钟(如基于 NTP 或 PTPClock),只需在“到点”时喂数据即可;否则再做一次平滑锁相(PLL)消除网络抖动。
4. 网络抖动/乱序补偿
- SR 映射只解决“零点对齐”,网络抖动靠接收缓冲(jitter buffer)。
- 视频一般用较大缓冲(100300 ms),音频用较小缓冲(2080 ms),再靠时间戳拉伸/重采样做微调。
- 若采用 AVSync 主从模式,通常选音频为主轴,视频帧按需重复/丢帧。
3、抓包显示rtsp拉取媒体流的时候,没有收到rtcp sr包,那么音视频如何同步?ffmpeg是怎么做的?
先给结论:
- 没有 RTCP SR 时,理论上无法把两条流的 RTP 时间戳映射到同一条绝对时间轴,所以“教科书”做法是做不了跨流同步的。
- 但 FFmpeg 的 RTSP 模块 并不是“死等” SR,它有两条退路:
- “RTP-Info 冷启动” —— 利用 RTSP 200 OK 里
RTP-Info: url=…;seq=…;rtptime=…给出的 第一帧 RTP 时间戳,把音视频起点强行对齐到 0(虚拟 NTP)。 - “后期补偿” —— 只要后面 任何一次 收到 SR(哪怕几十秒以后),立刻用真实 NTP 重新校正斜率/偏移,把之前累积的误差慢慢拉回。
- “RTP-Info 冷启动” —— 利用 RTSP 200 OK 里
- 因此你抓包看不到 SR,不代表 FFmpeg 没有同步机制,而是它先“假对齐”再“后修正”,肉眼看到的就是“居然还能对上”。
下面把细节拆开说明。
一、SR 包缺失时到底缺了什么
SR 的核心作用只干一件事:
NTP_real = k × RTP + b
其中 k 是采样频率(90 kHz/48 kHz…),b 是两条流 各自的随机偏移量。
没有 SR 就求不出 b,两条流的 b 可能相差几百毫秒到几秒,所以仅靠 RTP 时间戳无法知道“哪一帧音频对应哪一帧视频”。
二、FFmpeg 的“冷启动” fallback(rtsp.c → rtpdec.c)
- 发送 PLAY 后解析服务器返回的
RTP-Info: url=trackID=1;seq=123;rtptime=900000, url=trackID=2;seq=456;rtptime=810000`
把音频、视频的 第一个 rtptime 记为 rtp_start_[a/v]。
-
在
ff_rtsp_fetch_packet()里第一次收到对应流的 RTP 包时,把pts = rtp_ts - rtp_start_再除以采样频率,得到 “从 0 开始的虚拟壁钟”。
两条流都这样做,相当于强行把 b 设为 0,误差就被压缩到“第一帧到达时间差”以内(通常 0–50 ms)。
-
解码线程拿到
AVPacket后,pts 已经是同一虚拟时间轴,直接往时钟里一扔即可播放;后续就按帧率/采样率累加,保证 流内 不漂移。
三、后期再遇到 SR 时的“在线校正”
哪怕 30 s 后才收到 SR,FFmpeg 会:
- 用 SR 里的
<rtp,ntp>重新估算 真实斜率与偏移。 - 把估算结果写进
RTSPState->rtcp_ts_offset[a/v]。 - 后续再算 pts 时加上这个 offset,慢慢把之前 0 对齐造成的系统误差拉回来(通常每秒修正几毫秒,观众感觉不到)。
4、ffmpeg读取到的不同容器的avpacket中的时间戳是怎么来的?
不管容器多五花八门,FFmpeg 的目标都是“把各容器自己的时钟/计数器翻译成统一的 AVPacket->pts(单位 = AVStream->time_base)”,上层永远只认这一个数字。
下面把主流容器“原生时间戳 → AVPacket->pts” 的映射方式一次列清:
| 容器格式 | 原生时间戳来源 | PTS/DTS计算方式 | 时间基 (time_base) | 备注 |
|---|---|---|---|---|
| MP4/MOV/ISOBMFF | stts + ctts + elst 盒子 | 累加 stts.delta,加 ctts 偏移,再减 elst 起始偏移 | 1/timescale (常见 90000、24000) | 时间基通常为 1/timescale |
| MKV/WebM | Block / BlockGroup 里的 Timestamp 字段(单位 ns) | pts = timestamp / av_q2d(time_base) | 通常由 Stream->time_base 自动选择 (如 1/1,000,000,000) | av_q2d(time_base) 用于将时间基转换为秒/格 |
| TS (MPEG-TS) | 基于 90 kHz PCR 的 PTS/DTS (33 bit) | 直接拷贝时间戳,若发生回绕则需处理 | 1/90000 | 时间基固定为 1/90000 |
| FLV | TagHeader 的 Timestamp / TimestampExtended (单位毫秒) | pts = timestamp_ms × 1000 / time_base.den | 通常为 1/1000 | 计算时需将毫秒转换为时间基单位[ccitation:4] |
| AVI | dwSample 索引 × dwScale/dwRate | pts = sample_index × dwScale / dwRate | { dwScale, dwRate } | 通过样本索引和比例计算显示时间 |
| OGG | granule_position (采样计数) | pts = granule – pre_roll | 1/采样率 | 时间基与音频采样率相关 |
| ASF/WMA/WMV | Object 里的 Presentation Time (100-ns 单位) | pts = pr_time / 10000000 / av_q2d(time_base) | 自动约分 | 原始时间单位是100纳秒 |
| WAV/AIFF | 无显式时间戳,按 sample_index / 采样率 计算 | pts = sample_index | 1/采样率 | 对于无压缩音频,通过样本索引和采样率计算时间 |
| RAW H264/H265 | 无容器时间戳;若为RTP流,则使用RTP时间戳 | 若读取裸流,通常PTS设为0或由用户指定 | 需要外部注入 | 原始码流通常不包含显示时间戳 |
317

被折叠的 条评论
为什么被折叠?



