WebRTC(十):RTP和SRTP

发布于:2025-06-30 ⋅ 阅读:(19) ⋅ 点赞:(0)

RTP(Real-time Transport Protocol)

作用

RTP 用于传输实时媒体流(如音频、视频),它不提供可靠传输,而是关注低延迟、高实时性。

报文结构

整体结构

RTP 报文由以下部分组成:

RTP Header (12 字节起始) + 可选扩展头 + Payload(负载部分)+ 可选 Padding

如果启用了扩展或 CSRC,则头部长度会超过 12 字节。

RTP Header

Bit位 名称 说明
0-1 V (Version) RTP 版本号,占2位,当前为2
2 P (Padding) 是否有填充位,最后一个字节表明填充长度
3 X (Extension) 是否有扩展头
4-7 CC (CSRC Count) CSRC 的数量(最多15个),每个4字节
8 M (Marker) 标志位,具体含义由应用定义(如帧结束标志)
9-15 PT (Payload Type) 有效负载类型,7位,如 96 表示动态类型
16-31 Sequence Number 序列号,每发一个 RTP 包加1,接收方可用于重排和丢包检测
32-63 Timestamp 时间戳,标识采样时刻,用于同步和延时计算
64-95 SSRC 同步源标识符,用于区分不同源(一个媒体流)
96-… CSRC List 0~15个,标识贡献源

图示(前12字节):

  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 (可选, 每个4字节)   |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

字段说明:

字段 说明
V(Version) 当前为 2,必须正确解析
P(Padding) 若为 1,表明数据末尾有填充,用于加密对齐
X(Extension) 若为 1,RTP header 后跟扩展头
CC(CSRC Count) 表示后续跟随几个 CSRC,每个 4 字节
M(Marker) 由应用定义,如视频帧结束、音频边界等
PT(Payload Type) 表示负载的类型,动态类型在 96~127
Sequence Number 每发一个 RTP 包 +1,接收方可检测丢包、重排
Timestamp 用于同步,单位依赖编码格式(如 AAC 是 90kHz)
SSRC 同步源的唯一 ID,通常为随机值
CSRC 多个贡献者源(如会议中多个语音参与者)

RTP Payload(负载部分)

  • 这是实际承载音频/视频数据的部分,格式由 Payload Type 指定(如 H.264、AAC 等)。
  • RTP 仅负责传输,不解析负载内容。
  • 不同的编码格式有不同的 RTP 负载封装规则(如 H.264 的 FU-A、STAP-A 等)。
静态 Payload Type(0~95)
编号 编码格式 采样率 备注
0 PCMU (G.711 μ-law) 8000 Hz 音频
8 PCMA (G.711 A-law) 8000 Hz 音频
3 GSM 8000 Hz 音频
10 L16 44100 Hz 立体声音频
26 JPEG - 视频
31 H.261 - 视频
32 MPEG Audio - 音频
33 MPEG Video - 视频
动态 Payload Type(96~127)

动态类型必须在 SDP (Session Description Protocol) 或信令(如 SIP INVITE)中协商说明。常见映射:

PT值 编码格式 描述
96+ H.264 RFC 6184 定义
97 H.265 RFC 7798 定义
98 AAC RFC 3640 定义
99 OPUS 音频,动态
100 VP8 Google 实现
101 VP9 Google 实现

可选扩展头(Extension Header)

如果 X=1,表示存在扩展头。格式如下:

  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
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |       defined by profile       |           length              |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                        extension data...                       |
  • “defined by profile”:扩展头类型(如 WebRTC 扩展)

  • “length”:以 32 位字(4 字节)为单位的扩展长度,不含自身头部。

  • 后续紧跟扩展数据内容

工作流程

发送端

  • 将音视频编码成帧
  • 每帧按照 RTP 规则进行分片(如 H.264 的 FU-A)
  • 打包 RTP Header + Payload,发送到目标地址(UDP)
  • 控制信息通过 RTCP 发送,如丢包率、RTT、抖动等

接收端

  • 接收 RTP 报文并解析头部
  • 按 SSRC 区分媒体流
  • 按序列号排序、重组
  • 利用时间戳做同步(音视频同步)
  • 解码播放

RTP Header解析流程(c++)

解析函数

#include <iostream>
#include <cstdint>
#include <cstring>
#include <arpa/inet.h>  // ntohs, ntohl

#pragma pack(push, 1)
struct RtpHeader {
    uint8_t vpxcc;      // Version(2) | Padding(1) | Extension(1) | CC(4)
    uint8_t mpt;        // Marker(1) | PayloadType(7)
    uint16_t seq;       // Sequence Number
    uint32_t timestamp; // Timestamp
    uint32_t ssrc;      // SSRC
};
#pragma pack(pop)

#pragma pack(push, 1)
struct RtpExtensionHeader {
    uint16_t profile_defined;  // Profile-specific ID
    uint16_t length;           // 以 32bit 为单位的长度
};
#pragma pack(pop)

// 解析 RTP 头部
bool parseRtpHeader(const uint8_t* data, size_t len, RtpHeader& header) {
    if (len < sizeof(RtpHeader)) return false;

    std::memcpy(&header, data, sizeof(RtpHeader));

    // 网络字节序转主机字节序
    header.seq = ntohs(header.seq);
    header.timestamp = ntohl(header.timestamp);
    header.ssrc = ntohl(header.ssrc);

    return true;
}

// 解析扩展头,返回扩展负载指针及长度
bool parseRtpExtension(const uint8_t* data, size_t len,
                       uint8_t cc, bool extension,
                       const uint8_t** ext_payload, size_t* ext_len) {
    if (!extension) return false;

    size_t header_offset = 12 + cc * 4;  // 基础头 + CSRC 区

    if (len < header_offset + sizeof(RtpExtensionHeader)) return false;

    RtpExtensionHeader ext_hdr;
    std::memcpy(&ext_hdr, data + header_offset, sizeof(RtpExtensionHeader));

    ext_hdr.profile_defined = ntohs(ext_hdr.profile_defined);
    ext_hdr.length = ntohs(ext_hdr.length);

    size_t ext_payload_len = ext_hdr.length * 4;

    if (len < header_offset + sizeof(RtpExtensionHeader) + ext_payload_len) return false;

    *ext_payload = data + header_offset + sizeof(RtpExtensionHeader);
    *ext_len = ext_payload_len;

    std::cout << "RTP Extension Header:" << std::endl;
    std::cout << " Profile-defined ID: 0x" << std::hex << ext_hdr.profile_defined << std::dec << std::endl;
    std::cout << " Extension length (32-bit words): " << ext_hdr.length << std::endl;
    std::cout << " Extension payload size: " << ext_payload_len << " bytes" << std::endl;

    return true;
}

// 打印 RTP 头信息
void printRtpHeader(const RtpHeader& h) {
    uint8_t version = (h.vpxcc >> 6) & 0x03;
    uint8_t padding = (h.vpxcc >> 5) & 0x01;
    uint8_t extension = (h.vpxcc >> 4) & 0x01;
    uint8_t csrcCount = h.vpxcc & 0x0F;

    uint8_t marker = (h.mpt >> 7) & 0x01;
    uint8_t payloadType = h.mpt & 0x7F;

    std::cout << "RTP Header:" << std::endl;
    std::cout << " Version: " << (int)version << std::endl;
    std::cout << " Padding: " << (int)padding << std::endl;
    std::cout << " Extension: " << (int)extension << std::endl;
    std::cout << " CSRC Count: " << (int)csrcCount << std::endl;
    std::cout << " Marker: " << (int)marker << std::endl;
    std::cout << " Payload Type: " << (int)payloadType << std::endl;
    std::cout << " Sequence Number: " << h.seq << std::endl;
    std::cout << " Timestamp: " << h.timestamp << std::endl;
    std::cout << " SSRC: " << h.ssrc << std::endl;
}

void handleRtpPacket(const uint8_t* data, size_t len) {
    if (len < 12) {
        std::cerr << "Packet too short for RTP" << std::endl;
        return;
    }

    RtpHeader header;
    if (!parseRtpHeader(data, len, header)) {
        std::cerr << "Failed to parse RTP header" << std::endl;
        return;
    }

    printRtpHeader(header);

    uint8_t cc = header.vpxcc & 0x0F;
    bool extension = (header.vpxcc >> 4) & 0x01;

    const uint8_t* ext_payload = nullptr;
    size_t ext_len = 0;

    if (parseRtpExtension(data, len, cc, extension, &ext_payload, &ext_len)) {
        std::cout << " Extension Payload (hex, first up to 16 bytes): ";
        for (size_t i = 0; i < std::min(ext_len, size_t(16)); ++i) {
            printf("%02X ", ext_payload[i]);
        }
        std::cout << std::endl;
    } else if (extension) {
        std::cerr << "Invalid RTP extension header or length" << std::endl;
        return;
    }

    size_t payload_offset = 12 + cc * 4 + (extension ? 4 + ext_len : 0);
    if (payload_offset > len) {
        std::cerr << "Payload offset exceeds packet length" << std::endl;
        return;
    }

    size_t payload_len = len - payload_offset;
    const uint8_t* payload = data + payload_offset;

    std::cout << "RTP Payload size: " << payload_len << " bytes" << std::endl;
    // 这里你可以继续解析 payload 内容,例如 H264 NALU、音频帧等
}

int main() {
    // 构造一个测试 RTP 包(含扩展头)
    uint8_t rtpPacket[] = {
        0x90, 0x60, 0x12, 0x34,        // V=2,P=0,X=1,CC=0 | M=0,PT=96 | Seq=0x1234
        0x00, 0x00, 0x00, 0x01,        // Timestamp = 1
        0x12, 0x34, 0x56, 0x78,        // SSRC = 0x12345678

        // Extension header: profile=0x1000, length=1 (4 bytes extension)
        0x10, 0x00, 0x00, 0x01,

        // Extension payload (4 bytes)
        0xAA, 0xBB, 0xCC, 0xDD,

        // RTP payload (示例4字节)
        0x01, 0x02, 0x03, 0x04
    };

    size_t packet_len = sizeof(rtpPacket);

    handleRtpPacket(rtpPacket, packet_len);

    return 0;
}

运行结果

RTP Header:
 Version: 2
 Padding: 0
 Extension: 1
 CSRC Count: 0
 Marker: 0
 Payload Type: 96
 Sequence Number: 4660
 Timestamp: 1
 SSRC: 305419896
RTP Extension Header:
 Profile-defined ID: 0x1000
 Extension length (32-bit words): 1
 Extension payload size: 4 bytes
 Extension Payload (hex, first up to 16 bytes): AA BB CC DD 
RTP Payload size: 4 bytes

SRTP(Secure RTP)

作用

项目 说明
全称 Secure Real-time Transport Protocol
定义 为 RTP 提供 加密(confidentiality)完整性(integrity)重放保护(replay protection) 的协议
标准 RFC 3711
传输对象 RTP 和 RTCP 报文
应用场景 WebRTC、SIP 电话、视频会议、安全监控、国标 GB28181(国密版本)等
  • 设计目标

    • 加密 RTP Payload,保护媒体内容

    • 校验报文完整性(Message Authentication)

    • 防止重放攻击(Replay Protection)

    • 不增加过多开销(适配实时场景)

  • 特点

    • 轻量级、低延迟(适用于 VoIP/实时视频)

    • 只加密有效载荷(Payload),头部可解析

    • 不改变 RTP 报文格式(兼容性好)

报文结构

SRTP 报文 = RTP 报文 + 加密/认证后的扩展字段

+-----------------------------+
|     RTP Header (未加密)    |
+-----------------------------+
|     RTP Payload (加密)     |
+-----------------------------+
|  RTP padding (可选, 加密)  |
+-----------------------------+
|  Authentication Tag (可选) |
+-----------------------------+

RTP Header(12 字节及可选扩展)

未加密。用于 HMAC 认证计算。

RTP Payload(加密)

  • SRTP 对 负载部分(即 RTP header 后面的媒体数据)进行 AES 加密,有两种方式:
    • AES Counter Mode(AES-CTR):只加密,不使用 MAC(快速)
    • AES f8 模式(已不常用)
    • AES-CM with HMAC-SHA1(推荐方式):加密 + 完整性保护

RTP Padding(可选)

如果 RTP 报文设置了 Padding 位,那么最后会有若干字节的填充用于对齐。SRTP 也会对填充部分进行加密。

Authentication Tag(鉴权标签)

  • HMAC-SHA1 计算的一个 MAC(Message Authentication Code)
  • 默认长度为 10 字节(可配置为 4~10 字节)
  • 用于确保 RTP 报文未被篡改
  • 计算时覆盖:
    • RTP Header + Payload(加密后) + 累加器(包括 SSRC 和 Sequence Number)

核心算法与参数

类型 说明
加密算法 AES-CTR 或 AES-f8
认证算法 HMAC-SHA1(默认)
MAC 长度 默认 80bit(10字节),也可选 32bit(4字节)
密钥派生 使用 主密钥 + Master Salt 派生出多种密钥(加密、认证、RTCP密钥)

密钥派生机制

使用主密钥(Master Key)和主盐值(Master Salt)派生以下密钥:

  1. RTP 加密密钥
  2. RTP 认证密钥
  3. RTP 会话盐值
  4. RTCP 加密密钥
  5. RTCP 认证密钥
  6. RTCP 会话盐值

每个密钥长度根据加密算法而定,典型 AES-128 对应 128-bit。

Replay Protection(重放保护)

  • 基于 RTP Sequence Number + ROC(rollover counter) 实现
  • 接收端保存窗口(例如 64 个序列号)检测重复报文
  • 每当序列号溢出,ROC +1

RTCP 的安全扩展(SRTCP)

RTCP 也有对应的安全扩展,结构如下:

SRTCP = RTCP 原始报文 + 加密字段 + SRTCP Index + Auth Tag
  • 支持选择性加密
  • 使用 Auth Tag 来保证完整性
  • SRTCP Index 替代 RTP 的 ROC

SRTP 密钥协商方式

SRTP 不定义密钥协商方式,但常见有:

1. SDES(Session Description Protocol Security Descriptions)

  • 将密钥写入 SDP 协议中

  • 示例:

    a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:MTIzNDU2Nzg5MDEyMzQ1Ng==
    
  • 优点:简单,易实现

  • 缺点:明文传输,安全性差,已不推荐使用

2. DTLS-SRTP(Datagram TLS)

  • 使用 DTLS 握手协商密钥(如 WebRTC)
  • 端到端加密认证,符合现代安全要求
  • WebRTC 和新版 SIP(使用 ICE+DTLS)都采用它

3. MIKEY(Multimedia Internet KEYing)

  • 早期设计用于 SRTP,现较少使用

WebRTC中RTP/SRTP工作流程

整体流程

[Media Capture] → [Encoder] → [RTP Packetization] → [SRTP 加密] → [网络传输]
                                                                      ↓
                                        [网络接收] ← [SRTP 解密] ← [RTP 解析] ← [Decoder] ← [Render]

工作流程:

             +-------------+      +-----------+
 Media Input | Encoder     | RTP  | RTP Stack |
 (Camera/Mic)+----+--------+ ---> +-----------+
                  |               | RTP Packetization
                  v               | SRTP 加密(libsrtp)
           +------+--------+      |
           | DTLS Handshake | <---+
           +------+--------+
                  |
                  v
         SRTP Key Derivation

核心模块

模块 作用
RTP/RTCP 传输媒体帧和反馈信息(如丢包、带宽)
SRTP RTP 加密传输(使用 AES/HMAC)
DTLS 用于协商 SRTP 密钥(DTLS-SRTP)
ICE/STUN/TURN 建立点对点连接、穿透 NAT
SCTP/DTLS 传输数据通道(非音视频)

处理流程(发送端)

1. 媒体采集与编码

  • 摄像头/麦克风采集音视频
  • 使用 VP8/VP9/H264 等视频编码器,Opus/G.711 等音频编码器编码为压缩帧

2. RTP 封装

  • 编码帧被分片打包为 RTP 包
  • 每个 RTP 包头包括:
    • SSRC(同步源)
    • Sequence Number(顺序)
    • Timestamp(采样时间)
    • Payload Type(编码类型)

3. SRTP 加密

  • RTP Payload(负载)被加密
  • 附加 HMAC 鉴权标签
  • 使用密钥来自 DTLS 握手派生的 SRTP 密钥

4. 通过 UDP 网络发送(可能走 STUN/TURN 服务器)

SRTP 加密流程(WebRTC 默认)

密钥协商流程(DTLS-SRTP)

  1. 在 SDP 协商阶段使用 a=setup, a=fingerprint 等字段建立 DTLS 连接
  2. 建立 DTLS 通道后双方使用 Exporter 从 DTLS 会话中导出密钥(RFC 5705)
  3. 导出的密钥用于 SRTP 加密解密(分别用于加密/认证)

SRTP 加密细节

项目 内容
加密算法 AES-CTR (默认),也可用 AES-GCM
完整性保护 HMAC-SHA1(默认10字节)
支持重放保护 是(基于序列号窗口)
SRTP 不加密字段 RTP Header(但用于 HMAC)

处理流程(接收端)

  1. 从网络接收 RTP 报文(可能经过 TURN relay)
  2. 使用协商好的 SRTP 密钥进行解密 + 验签
  3. 解析 RTP Header(获取序列号、时间戳等)
  4. RTP 组帧(重排序、丢包重传等)
  5. 解码器处理后播放音视频

RTCP 辅助流程(反馈通道)

WebRTC 使用 RTCP 提供控制反馈:

RTCP 类型 用途
SR (Sender Report) 提供 RTP 发送统计,用于同步
RR (Receiver Report) 丢包率、抖动、延迟等统计
PLI (Picture Loss Indication) 请求关键帧(视频重传)
FIR (Full Intra Request) 强制全帧编码
NACK 指定丢失的 RTP 序号,建议重传

RTCP 包通常也通过 SRTCP 进行加密和认证。

传输通道建立流程

SDP Offer/Answer → ICE 候选收集 → DTLS 握手 → SRTP 密钥协商 → 开始传输 RTP/SRTP

各协议协同如下:

协议 功能
SDP 协商能力、媒体参数、加密 fingerprint
ICE NAT 穿透、连接检测
STUN/TURN 发现公网地址 / 中继
DTLS 端到端加密协商
SRTP 媒体数据加密传输
RTP 媒体传输
RTCP 控制反馈与统计

网站公告

今日签到

点亮在社区的每一天
去签到