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             |
 |                             ....                              |

字段说明:

字段含义
VRTP版本号
P填充位
X扩展头标志位
CCCSRC数量
MMarker位
PTPayload 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 定义
  • 用于标识扩展类型
  • 常见值:
值说明
0xBEDERFC5285 One-Byte Header Extension
0x1000RFC5285 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 ...                                     |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

字段说明:

字段长度说明
ID4bit扩展ID
LEN4bit数据长度
dataN 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 ...      |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

字段:

字段长度
ID8bit
length8bit
dataN 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含义用途
1urn:ietf:params:rtp-hdrext:ssrc-audio-level音频电平语音活动检测
2urn:ietf:params:rtp-hdrext:csrc-audio-levelCSRC音量混音音量
3http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time绝对发送时间带宽估计
4urn:3gpp:video-orientation视频旋转手机视频方向
5http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01Transport CC拥塞控制
6http://www.webrtc.org/experiments/rtp-hdrext/playout-delay播放延迟延迟控制
7http://www.webrtc.org/experiments/rtp-hdrext/video-content-type视频内容类型屏幕共享识别
8http://www.webrtc.org/experiments/rtp-hdrext/video-timing视频编码时间调试统计
9http://www.webrtc.org/experiments/rtp-hdrext/color-space颜色空间HDR/色域
10urn:ietf:params:rtp-hdrext:sdes:mid媒体IDBUNDLE
11urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id流IDSimulcast
12urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id重传流IDRTX

RTP扩展头解析流程(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、视频会议、实时流媒体
  • 主要用于 带宽控制、统计信息、同步控制等功能
Logo

火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。

更多推荐