音视频学习(九十一):rtp扩展头

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、视频会议、实时流媒体
  • 主要用于 带宽控制、统计信息、同步控制等功能
相关推荐
留白_4 小时前
MySQL学习(4)——多表操作
学习
ai产品老杨4 小时前
重构视频底座:基于Docker容器化与GB28181/RTSP的异构AI算力调度架构
docker·重构·音视频
视频技术分享4 小时前
音视频SDK开发核心技术深度解析:从编解码到实时通信
音视频
ai产品老杨4 小时前
万物互联的视频网关:基于GB28181/RTSP的多协议融合与边缘推流架构解析
架构·音视频
im_AMBER5 小时前
编辑器项目开发复盘:主题切换
前端·学习·前端框架·编辑器·html5
2401_865721335 小时前
WEEK 3 刷题&学习记录
网络·学习·ctf
WX186163619097 小时前
多音轨录音视频损坏后的视频修复声音恢复案例
数码相机·音视频
YWamy8 小时前
音视频SDK深度解析:概念、演进与行业挑战
音视频
AI2512248 小时前
AI视频生成器技术评测:运动质量与稳定性分析
人工智能·microsoft·音视频
好奇龙猫8 小时前
【日语学习-日语知识点小记-日本語体系構造-JLPT-N2前期阶段-第一阶段(19):単語文法】
学习