RTP 基本头部结构
RTP 报文由 固定头部(12字节)+ 可选扩展头 + 负载数据 组成。标准 RTP 头结构如下:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X| CC |M| PT | sequence number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| synchronization source (SSRC) identifier |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| contributing source (CSRC) identifiers |
| .... |
字段说明:
| 字段 | 含义 |
|---|---|
| V | RTP版本号 |
| P | 填充位 |
| X | 扩展头标志位 |
| CC | CSRC数量 |
| M | Marker位 |
| PT | Payload Type |
| Sequence Number | 序列号 |
| Timestamp | 时间戳 |
| SSRC | 同步源标识 |
其中 X 位(Extension bit) 决定 RTP 是否存在扩展头。
X = 0→ 没有扩展头X = 1→ RTP 头后面存在 Extension Header
RTP 扩展头结构
当 X=1 时,在 RTP 固定头后会增加 扩展头结构:
0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| defined by profile | length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| extension data |
| .... |
字段含义:
1. profile-defined(16bit)
- 由 RTP profile 定义
- 用于标识扩展类型
- 常见值:
| 值 | 说明 |
|---|---|
| 0xBEDE | RFC5285 One-Byte Header Extension |
| 0x1000 | RFC5285 Two-Byte Header Extension |
在 WebRTC 和现代 RTP 系统中最常见的是 0xBEDE。
2. length(16bit)
表示扩展数据长度:
单位 = 32bit word
例如:
length = 2
表示:
2 * 4 = 8 bytes 扩展数据
3. Extension Data
扩展字段真正的数据部分,由不同应用定义,例如:
- WebRTC
- SRTP
- 视频同步
- 网络统计
- 绝对时间
RFC5285 扩展机制
传统 RTP 扩展只能有一个扩展块,不方便扩展,因此 RFC5285 提供了一种更加灵活的扩展机制。
主要有两种格式:
- One-Byte Header Extension
- Two-Byte Header Extension
One-Byte Header Extension
最常见于 WebRTC、Chrome、libwebrtc。
标识
profile = 0xBEDE
结构
0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| ID | LEN | data ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
字段说明:
| 字段 | 长度 | 说明 |
|---|---|---|
| ID | 4bit | 扩展ID |
| LEN | 4bit | 数据长度 |
| data | N bytes | 扩展数据 |
注意:
LEN = 实际长度 - 1
例如:
LEN = 3
表示:
4 bytes data
示例
假设 RTP 扩展:
ID = 3
LEN = 1
DATA = 0x12 0x34
编码:
0011 0001 12 34
解析:
ID = 3
len = 1+1 = 2 bytes
Two-Byte Header Extension
用于更复杂扩展。
标识
profile = 0x1000
结构:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| ID | length | data ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
字段:
| 字段 | 长度 |
|---|---|
| ID | 8bit |
| length | 8bit |
| data | N bytes |
优点:
- ID范围更大
- 数据长度可达255字节
常见 RTP 扩展类型
在 WebRTC、GB28181、SRT、视频会议 中经常使用以下扩展。
1. Absolute Send Time
用于 带宽估计(BWE)
URI:
http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
长度:
3 bytes
用途:
- 发送端时间
- 网络延迟计算
- 拥塞控制
2. Transport Wide CC
用于 拥塞控制
URI:
http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions
数据:
transport sequence number
用于:
- WebRTC congestion control
- RTCP feedback
3. Audio Level
用于 语音检测
URI:
urn:ietf:params:rtp-hdrext:ssrc-audio-level
数据结构:
V | level
| 字段 | 说明 |
|---|---|
| V | 语音活动 |
| level | 音量 |
4. Video Orientation
用于 手机视频旋转
URI:
urn:3gpp:video-orientation
内容:
旋转角度
5. WebRTC常见扩展ID汇总
| ID(示例) | URI | 含义 | 用途 |
|---|---|---|---|
| 1 | urn:ietf:params:rtp-hdrext:ssrc-audio-level | 音频电平 | 语音活动检测 |
| 2 | urn:ietf:params:rtp-hdrext:csrc-audio-level | CSRC音量 | 混音音量 |
| 3 | http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time | 绝对发送时间 | 带宽估计 |
| 4 | urn:3gpp:video-orientation | 视频旋转 | 手机视频方向 |
| 5 | http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01 | Transport CC | 拥塞控制 |
| 6 | http://www.webrtc.org/experiments/rtp-hdrext/playout-delay | 播放延迟 | 延迟控制 |
| 7 | http://www.webrtc.org/experiments/rtp-hdrext/video-content-type | 视频内容类型 | 屏幕共享识别 |
| 8 | http://www.webrtc.org/experiments/rtp-hdrext/video-timing | 视频编码时间 | 调试统计 |
| 9 | http://www.webrtc.org/experiments/rtp-hdrext/color-space | 颜色空间 | HDR/色域 |
| 10 | urn:ietf:params:rtp-hdrext:sdes:mid | 媒体ID | BUNDLE |
| 11 | urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id | 流ID | Simulcast |
| 12 | urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id | 重传流ID | RTX |
RTP扩展头解析流程(c++)
c++
#include <iostream>
#include <stdint.h>
#include <arpa/inet.h>
#include <string.h>
using namespace std;
#pragma pack(push,1)
struct RtpHeader
{
#if __BYTE_ORDER == __BIG_ENDIAN
uint8_t version:2;
uint8_t padding:1;
uint8_t extension:1;
uint8_t csrcCount:4;
uint8_t marker:1;
uint8_t payloadType:7;
#else
uint8_t csrcCount:4;
uint8_t extension:1;
uint8_t padding:1;
uint8_t version:2;
uint8_t payloadType:7;
uint8_t marker:1;
#endif
uint16_t sequenceNumber;
uint32_t timestamp;
uint32_t ssrc;
};
struct RtpExtensionHeader
{
uint16_t profile;
uint16_t length;
};
#pragma pack(pop)
////////////////////////////////////////////////////////
// 打印HEX
////////////////////////////////////////////////////////
void PrintHex(uint8_t* data, int len)
{
for(int i=0;i<len;i++)
{
printf("%02X ", data[i]);
}
printf("\n");
}
////////////////////////////////////////////////////////
// One Byte Extension
////////////////////////////////////////////////////////
void ParseOneByteExtension(uint8_t* data, int size)
{
int pos = 0;
cout << "---- One Byte Extensions ----" << endl;
while(pos < size)
{
uint8_t id = data[pos] >> 4;
uint8_t len = (data[pos] & 0x0F) + 1;
pos++;
if(id == 0)
{
continue;
}
if(pos + len > size)
break;
cout << "Extension ID: " << (int)id << endl;
cout << "Length: " << (int)len << endl;
cout << "Data: ";
PrintHex(data + pos, len);
pos += len;
}
}
////////////////////////////////////////////////////////
// Two Byte Extension
////////////////////////////////////////////////////////
void ParseTwoByteExtension(uint8_t* data, int size)
{
int pos = 0;
cout << "---- Two Byte Extensions ----" << endl;
while(pos + 2 <= size)
{
uint8_t id = data[pos];
uint8_t len = data[pos+1];
pos += 2;
if(id == 0)
{
pos += len;
continue;
}
if(pos + len > size)
break;
cout << "Extension ID: " << (int)id << endl;
cout << "Length: " << (int)len << endl;
cout << "Data: ";
PrintHex(data + pos, len);
pos += len;
}
}
////////////////////////////////////////////////////////
// RTP解析
////////////////////////////////////////////////////////
bool ParseRtpPacket(uint8_t* packet, int size)
{
if(size < 12)
{
cout << "Invalid RTP packet" << endl;
return false;
}
RtpHeader* rtp = (RtpHeader*)packet;
cout << "========== RTP Header ==========" << endl;
cout << "Version: " << (int)rtp->version << endl;
cout << "Padding: " << (int)rtp->padding << endl;
cout << "Extension: " << (int)rtp->extension << endl;
cout << "CSRC Count: " << (int)rtp->csrcCount << endl;
cout << "Marker: " << (int)rtp->marker << endl;
cout << "Payload Type: " << (int)rtp->payloadType << endl;
cout << "Sequence: " << ntohs(rtp->sequenceNumber) << endl;
cout << "Timestamp: " << ntohl(rtp->timestamp) << endl;
cout << "SSRC: " << ntohl(rtp->ssrc) << endl;
int offset = 12;
////////////////////////////////////////////////////
// CSRC
////////////////////////////////////////////////////
if(rtp->csrcCount > 0)
{
cout << "========== CSRC ==========" << endl;
for(int i=0;i<rtp->csrcCount;i++)
{
uint32_t csrc = ntohl(*(uint32_t*)(packet+offset));
cout << "CSRC[" << i << "] = " << csrc << endl;
offset += 4;
}
}
////////////////////////////////////////////////////
// Extension
////////////////////////////////////////////////////
if(rtp->extension)
{
if(size < offset + 4)
{
cout << "Invalid extension header" << endl;
return false;
}
RtpExtensionHeader* ext =
(RtpExtensionHeader*)(packet + offset);
uint16_t profile = ntohs(ext->profile);
uint16_t length = ntohs(ext->length);
int ext_bytes = length * 4;
cout << "========== RTP Extension ==========" << endl;
cout << "Profile: 0x" << hex << profile << dec << endl;
cout << "Length words: " << length << endl;
cout << "Length bytes: " << ext_bytes << endl;
offset += 4;
if(size < offset + ext_bytes)
{
cout << "Invalid extension length" << endl;
return false;
}
uint8_t* ext_data = packet + offset;
if(profile == 0xBEDE)
{
ParseOneByteExtension(ext_data, ext_bytes);
}
else if(profile == 0x1000)
{
ParseTwoByteExtension(ext_data, ext_bytes);
}
else
{
cout << "Unknown extension profile" << endl;
}
offset += ext_bytes;
}
////////////////////////////////////////////////////
// Payload
////////////////////////////////////////////////////
cout << "========== Payload ==========" << endl;
int payload_size = size - offset;
cout << "Payload Offset: " << offset << endl;
cout << "Payload Size: " << payload_size << endl;
if(payload_size > 0)
{
cout << "Payload (first 32 bytes): ";
int dump = payload_size > 32 ? 32 : payload_size;
PrintHex(packet + offset, dump);
}
cout << endl;
return true;
}
////////////////////////////////////////////////////////
// 测试RTP包
////////////////////////////////////////////////////////
int main()
{
uint8_t rtp_packet[] =
{
// RTP Header
0x90,0x60,
0x00,0x01,
0x00,0x00,0x00,0x01,
0x12,0x34,0x56,0x78,
// Extension header
0xBE,0xDE,
0x00,0x02,
// Extension data (8 bytes)
0x31,0x12,0x34,
0x52,0xAA,0xBB,0xCC,
0x00
};
int size = sizeof(rtp_packet);
cout << "RTP Packet Size: " << size << endl << endl;
ParseRtpPacket(rtp_packet, size);
return 0;
}
总结
RTP 扩展头提供了一种 灵活扩展 RTP 功能的机制,其核心特点包括:
- 通过 RTP 头的 X 位 标识扩展存在
- 扩展头包含 profile + length + data
- RFC5285 定义 One-byte / Two-byte 扩展格式
- 广泛应用于 WebRTC、视频会议、实时流媒体
- 主要用于 带宽控制、统计信息、同步控制等功能