RTP(Real-time Transport Protocol,实时传输协议)数据解析的官方标准完全基于IETF(互联网工程任务组)发布的 RFC 文档
官网:
- RFC Editor 官网(最权威的 RFC 文档发布平台):www.rfc-editor.org/
- IETF Datatracker(IETF 官方文档追踪平台):datatracker.ietf.org/ 文档:
- www.rfc-editor.org/rfc/rfc3550…
- datatracker.ietf.org/doc/html/rf…
- www.rfc-editor.org/rfc/rfc3551…
- datatracker.ietf.org/doc/html/rf…
流程:
接收RTP包
↓
解析RTP头部(验证版本、长度等)
↓
根据Payload Type分发处理:
├── 96: H.264视频 → FU-A/STAP-A重组 → 完整NAL单元
├── 26: JPEG视频 → 重建JPEG文件格式 → 完整JPEG帧
├── 0/8: G.711音频 → 直接提取音频数据
└── 其他: 不支持的格式 → 返回错误
↓
时间戳处理(音视频同步)
↓
帧重组(RTP Marker标志)
↓
传递给上层解码/显示
RTSP over TCP 数据是交错发送的,所以需要从TCP流中解析数据流,区分RTSP命令和RTP数据包。
TCP大概会接收到如下交织的数据流:
RTSP/1.0 200 OK\r\nCSeq: 1\r\n...\r\n\r\n // RTSP命令: 普通文本,以RTSP开头
$<通道><长度><RTP数据> // 每个RTP包以'$'开头
$<通道><长度><RTP数据> // RTP包
DESCRIBE rtsp://...\r\nCSeq: 2\r\n\r\n // RTSP命令: 普通文本
RTP数据
RTP数据 INTERLEAVED_INFO + RTP_HEADER + RTP_HEADER.csrccount * 4 + (RTP_HEADER.extension=1时4+拓展长度) + H264数据/H265数据/音频 H264数据:(NALU_HEADER + FU_HEADER + RTSP_FRAME)
typedef struct {
tuint8 magic; // '$' (0x24) - 交织模式标识, RTP数据以'$' (0x24)开头
tuint8 channel; // 通道号 (0=视频RTP, 1=视频RTCP, 2=音频RTP...)
tuint16 datalength; // RTP数据长度(网络字节序)
} INTERLEAVED_INFO; // 总共4字节
// h264 define
typedef struct _rtp_header_
{
tuint8 csrccount:4; // CSRC计数(0-15)
tuint8 extension:1; // 扩展标志 `0`=无扩展,`1`=有扩展头,最后一字节表示填充长度
tuint8 padding:1; // 填充标志 `0`=无填充,`1`=有填充
tuint8 version:2; // 版本(总是2)
tuint8 payloadtype:7; // 负载类型(Payload Type),标识编码格式,96=H.264,97=AAC
tuint8 marker:1; // 标记位(Mark bit),视频:帧结束标志;音频:静音开始
tuint16 sequenceNumber; // 序列号(16位,网络字节序)从随机值开始,递增,每包+1,用于检测丢包
tuint32 timeStamp; // 时间戳(32位,网络字节序) 视频:90kHz时钟;音频:采样率时钟
tuint32 ssrc; // 同步源标识(32位,网络字节序)流的唯一ID,随机生成,区分不同流
// uint32_t csrc[cc]; // 贡献源列表,仅当 CC>0 时存在
// 仅当 X=1 时存在,第一字节是拓展数据长度+扩展数据
}RTP_HEADER;
// h264结构 ——begin
typedef struct _nalu_header_
{
tuint8 type:5; //对应NAL_UNIT_TYPE
tuint8 nri:2;
tuint8 forbidden_zero_bit:1;
}NALU_HEADER;
typedef struct _fragmentation_unit_header_
{
tuint8 type:5; //和原始数据中nalu中的type一样
tuint8 reserve_bit:1; //保留位,必须是0;
tuint8 end_bit:1; //指示是不是这一帧的最后一个分片
tuint8 start_bit:1; //指示是不是这一帧的第一个分片
}FU_HEADER;
typedef struct {
unsigned long long ullFrametime; // 帧时间
bool bKeyFrame; // 是否为关键帧
int frameType; // 帧类型(视频/音频)
union {
int videoEnc; // 视频编码类型
int audioEnc; // 音频编码类型
} encodeType;
unsigned int dwWidth; // 视频宽度
unsigned int dwHeight; // 视频高度
unsigned int dwFrameLen; // 帧长度
} RTSP_FRAME;
// h264结构 ——end
负载类型
负载类型可以同RTSP DESCRIBE命令解析SDP协议读出来:
| PT值 | 编码格式 | 时钟频率 | 说明 |
|---|---|---|---|
| 0 | PCMU (G.711 μ-law) | 8000 | 音频电话质量 |
| 8 | PCMA (G.711 A-law) | 8000 | 音频电话质量 |
| 9 | G.722 | 8000 | 音频7kHz带宽 |
| 18 | G.729 | 8000 | 音频低比特率 |
| 96 | H.264/AVC | 90000 | 视频,最常见 |
| 97 | MPEG4-GENERIC (AAC) | 44100/48000 | 音频 |
| 98 | H.263 | 90000 | 视频 |
| 99 | MPEG4-ES | 90000 | 视频 |
| 102 | H.261 | 90000 | 视频 |
| 103 | H.263+ | 90000 | 视频 |
nal类型
typedef enum _nal_unit_type_
{
NAL_UNIT_TYPE_ZERO = 0, //未规定
SLICE_LAYER_WITHOUT_PARTITIONING_RBSP = 1,//非IDR图像中不采用数据划分的片段
SLICE_DATA_PARTION_A_LAYER_RBSP = 2,//非IDR图像中A类数据划分片段
SLICE_DATA_PARTION_B_LAYER_RBSP = 3,//非IDR图像中B类数据划分片段
SLICE_DATA_PARTION_C_LAYER_RBSP = 4,//非IDR图像中C类数据划分片段
SLICE_LAYER_WITH_PARTITIONING_RBSP = 5,//IDR图像的片段
SEI_RBSP = 6,//补充增强信息 (SEI)
SEQ_PARAMETER_SET_RBSP = 7,//序列参数集
PIC_PARAMETER_SET_RBSP = 8,//图像参数集
ACCESS_UNIT_DELIMITER_RBSP = 9,//分割符
END_OF_SEQ_RBSP = 10,//序列结束符
END_OF_STREAM_RBSP = 11,//流结束符
FILLER_DATA_RBSP = 12,//填充数据
//13-23保留 24-31未定义
SINGLE_TIME_AGGREGATION_PACKET_A = 24, //单时间聚合包
SINGLE_TIME_AGGREGATION_PACKET_B = 25, //单时间聚合包
MULTI_TIME_AGGREGATION_PACKET_A = 26, //多时间聚合包
MULTI_TIME_AGGREGATION_PACKET_B = 27, //多时间聚合包
FRAGMENTATION_UNIT_A = 28, //分片的包
FRAGMENTATION_UNIT_B = 29, //分片的包
}NAL_UNIT_TYPE;