一、RTP 头结构总览RFC 3550RTP 固定头为12 字节不含 CSRC 列表和扩展头字段按大端序排列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 -------------------------------- |V2|P|X| CC |M| PT | Sequence Number | -------------------------------- | Timestamp | -------------------------------- | SSRC | -------------------------------- | CSRC (0~15 个每个 4 字节) | -------------------------------- | 扩展头若 X1可选 | -------------------------------- | Payload ... | --------------------------------二、各字段详解VVersion2 bitsRTP 协议版本。固定为 2对应 RFC 3550。发送端必须设为 2接收端可忽略非 2 的包。PPadding1 bit填充标志。若为 1载荷末尾有 1 个或多个填充字节这些字节不属于有效载荷。最后一个填充字节的值表示需要忽略的填充字节总数含自身。应用场景加密算法要求块对齐或底层协议需要固定长度。音视频 RTSP 推流中通常设为 0。XExtension1 bit扩展头标志。若为 1固定头之后紧跟一个扩展头格式见 RFC 3550 §5.3.1。应用场景携带传输层无关的扩展信息如绝对发送时间。常规 H264/AAC 推流中设为 0。CCCSRC Count4 bitsCSRC 计数器。表示固定头之后跟随的 CSRC 标识符数量0~15。应用场景多方混音/混流场景标识参与混流的多个源。单源推流中设为 0。MMarker1 bit标记位。含义由 Payload Type 定义用于标记帧边界等重要事件。场景M 位含义H264 视频一帧的最后一个 RTP 包AAC 音频一帧的最后一个 RTP 包通常一个包一帧视频帧分片第一个分片 M0最后一个分片 M1接收端用法收到 M1 表示一帧完整接收可交付解码器。PTPayload Type7 bits载荷类型标识载荷的编码格式决定接收端如何解释载荷。PT 值编码时钟频率说明96H264动态90000SDP 中artpmap:96 H264/9000097AAC动态采样率SDP 中artpmap:97 mpeg4-generic/44100/20~95静态映射固定RFC 3551 定义如 0PCMU推流端在 SDP 中协商 PT通常视频用 96音频用 97。接收端根据 SDP 的rtpmap行确定 PT 对应的编码。Sequence Number16 bits序列号每发送一个 RTP 包递增 1用于接收端检测丢包和恢复包序。初始值应随机防明文攻击递增到 65535 后回绕到 0。接收端用法期望值 上一个包的序列号 1若收到的序列号 期望值 →丢包若收到的序列号 期望值 →乱序或重复Timestamp32 bits时间戳反映 RTP 数据包中第一个字节的采样时刻用于同步和抖动计算。时钟频率由编码格式决定编码时钟频率时间戳增量H26490000 Hz每帧增量 90000 / fpsAAC 44100Hz44100 Hz每帧增量 1024每 AAC 帧 1024 采样AAC 48000Hz48000 Hz每帧增量 1024初始值应随机但同一帧的所有分片 RTP 包必须使用相同的时间戳。示例H264 30fps第 1 帧timestamp T0第 2 帧timestamp T0 90000/30 T0 3000第 3 帧timestamp T0 6000Frame::dts是 90kHz 时基H264 可直接作为 RTP timestampAAC 需从 90kHz 转换为采样率时基rtpTs (dts90k * sampleRate) / 90000SSRCSynchronization Source32 bits同步源标识符唯一标识 RTP 流的发送者随机生成。同一 SSRC 的包属于同一媒体流传输地址变更时 SSRC 应更新同一会话中不同媒体流视频/音频使用不同 SSRC生成建议QRandomGenerator::global()-generate()CSRCContributing Source0~15 × 4 bytes贡献源标识符列表仅在 CC 0 时存在。每个 CSRC 是 4 字节标识参与混流的源。单源推流中 CC0此字段不存在。接收端解析时不能硬编码偏移 12必须根据 CC 和 X 动态计算int headerLen 12 cc * 4; if (x) { uint16_t extLen load_be16(p headerLen 2); headerLen 4 extLen * 4; }三、H264 RTP 封包详解RFC 6184H264 的 RTP 载荷有三种模式模式 1单 NALU 包1 ≤ NAL type ≤ 23一个 RTP 包承载一个完整的 NALURTP 头之后直接跟 NALU不含起始码。--------------------------------- | RTP Header | NALU (无起始码) | ---------------------------------适用条件NALU 大小 ≤ MTU通常 1400 字节。模式 2STAP-ANAL type 24单时间聚合包把多个小 NALU 聚合成一个 RTP 包发送。-------------------------------------------- | RTP Header |STAP-A|NALU1 |NALU2 | ... |NALU N| | | Hdr |LenData|LenData| | | --------------------------------------------STAP-A 头1 字节nal_type 24。之后每个 NALU 前有2 字节长度大端。适用条件SPS/PPS 一起发送或多个小 slice 聚合。模式 3FU-ANAL type 28分片单元把一个大 NALU 拆成多个 RTP 包。FU-A 结构每个分片包-------------------------------------------------- | RTP Header| FU Indicator| FU Header | FU Payload | | (12 字节) | (1 字节) | (1 字节) | (NALU 分片数据) | --------------------------------------------------FU Indicator1 字节0 1 2 3 4 5 6 7 -------- |F|NRI| Type28 | --------F禁止位与原始 NALU 的 F 位一致NRI与原始 NALU 的 NRI 一致Type固定为 28FU-AFU Header1 字节0 1 2 3 4 5 6 7 -------- |S|E|R| Type | --------SStart1 表示分片起始第一个包EEnd1 表示分片结束最后一个包R保留设为 0Type原始 NALU 的 NAL type如 5 表示 IDR分片规则第一个分片S1E0FU Payload 原始 NALU 去掉第 1 字节中间分片S0E0最后一个分片S0E1M 位设为 1完整示例一个 IDR 帧2048 字节 NALUMTU 1400包SEMFU Payload1100NALU[1..1399]2011NALU[1400..2047]四、AAC RTP 封包详解RFC 3640AAC 使用mpeg4-generic载荷格式RTP 载荷由AU Header Section AU Data Section组成。完整结构------------------------------------------------------------- | AU-headers-length | AU-header(s) | AAC raw data | | (2 字节, 大端) | (每个 2 字节) | (无 ADTS 头) | -------------------------------------------------------------AU-headers-length2 字节表示后面AU-header 部分的总比特数。对于单个 AAC 帧固定为 16即 2 字节。AU-header2 字节0 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 ---------------- | AU-size (13 bits) |AU-Idx (3)| ----------------AU-size13 bitsAAC 帧的原始字节数不含 AU-headerAU-Index3 bits固定为 0非交错模式完整 AAC RTP 包示例假设 raw AAC 帧为 380 字节------------------------------------------------ | RTP Header| 00 10 | AU-size | AAC raw data | | (12 字节) | (16 bits) | (133 bits)| (380 字节) | ------------------------------------------------AU-header 的13 位 AU-size 3800x17C3 位 Index 0第 1 字节0x01 (高 5 位 of 13-bit size) 第 2 字节0x7C (低 8 位 of 13-bit size) 3 位 Index0 填充接收端解析cppuint16_t auHeadersLen load_be16(payload); // 应为 16 uint16_t auHeader load_be16(payload 2); int auSize (auHeader 3) 0x1FFF; // 13 bits const uint8_t* aacData payload 4;五、代码实现5.1 RtpPacketizer 接口// protocol/rtsp/rtp/RtpPacketizer.h class RtpPacketizer { public: using PacketCB std::functionvoid(const QByteArray rtpPacket); void setOnPacket(PacketCB cb) { _onPacket std::move(cb); } void setSsrc(uint32_t ssrc) { _ssrc ssrc; } void setPayloadType(uint8_t pt) { _pt pt; } // H264: 输入 FrameAnnex-B输出 0~N 个 RTP 包 void packetizeH264(const Frame::Ptr frame); // AAC: 输入 Frameraw AAC输出 1 个 RTP 包 void packetizeAAC(const Frame::Ptr frame); private: uint16_t _seq 0; // 初始随机 uint32_t _ssrc 0; uint8_t _pt 96; PacketCB _onPacket; QByteArray buildRtpHeader(bool marker, uint32_t timestamp); };5.2 H264 封包核心实现void RtpPacketizer::packetizeH264(const Frame::Ptr frame) { // 1. 拆分 Annex-B 为 NALU 列表 auto nalus H264Utils::splitNalus(frame-data); // 2. 时间戳Frame::dts 已是 90kHz直接使用 uint32_t ts static_castuint32_t(frame-dts); // 3. 过滤 SPS/PPS单独发送或聚合发送 QListQByteArray mediaNalus; for (auto nalu : nalus) { int type uint8_t(nalu[0]) 0x1F; if (type 7 || type 8) continue; // SPS/PPS 单独处理 mediaNalus.append(nalu); } // 4. 逐 NALU 封包 for (int i 0; i mediaNalus.size(); i) { const auto nalu mediaNalus[i]; bool isLastNalu (i mediaNalus.size() - 1); if (nalu.size() 12 kMtu) { // 单 NALU 包 QByteArray pkt buildRtpHeader(isLastNalu frame-keyFrame, ts); pkt.append(nalu); if (_onPacket) _onPacket(pkt); } else { // FU-A 分片 packetizeFuA(nalu, ts, isLastNalu frame-keyFrame); } } } void RtpPacketizer::packetizeFuA(const QByteArray nalu, uint32_t ts, bool lastNaluMarker) { uint8_t nalHeader uint8_t(nalu[0]); uint8_t nri (nalHeader 0x60) 5; uint8_t nalType nalHeader 0x1F; uint8_t fuIndicator (nri 5) | 28; // Type 28 const int maxPayload kMtu - 14; // 12 RTP 2 FU int offset 1; // 跳过原始 NALU 头 bool first true; while (offset nalu.size()) { int chunkSize qMin(maxPayload, nalu.size() - offset); bool last (offset chunkSize nalu.size()); uint8_t fuHeader nalType; if (first) fuHeader | 0x80; // S1 if (last) fuHeader | 0x40; // E1 bool marker (last lastNaluMarker); QByteArray pkt buildRtpHeader(marker, ts); pkt.append(char(fuIndicator)); pkt.append(char(fuHeader)); pkt.append(nalu.constData() offset, chunkSize); if (_onPacket) _onPacket(pkt); offset chunkSize; first false; } }5.3 AAC 封包核心实现void RtpPacketizer::packetizeAAC(const Frame::Ptr frame) { // frame-data 是 raw AAC无 ADTS 头 // 需要知道采样率从 Track 获取 int sampleRate _audioTrack-sampleRate; // 时间戳转换dts 是 90kHz → 采样率时基 uint32_t ts static_castuint32_t(frame-dts * sampleRate / 90000); // 构建 AU Header Section QByteArray auHeader; uint16_t auHeadersLen 16; // 单个 AU-header固定 16 bits auHeader.append(char(auHeadersLen 8)); auHeader.append(char(auHeadersLen 0xFF)); // AU-header: 13 bits size 3 bits index(0) uint16_t sizeAndIndex (frame-data.size() 3) 0xFFF8; auHeader.append(char(sizeAndIndex 8)); auHeader.append(char(sizeAndIndex 0xFF)); // 构建完整 RTP 包 bool marker true; // AAC 一帧一包M1 QByteArray pkt buildRtpHeader(marker, ts); pkt.append(auHeader); pkt.append(frame-data); // raw AAC if (_onPacket) _onPacket(pkt); }5.4 RTP 头构建QByteArray RtpPacketizer::buildRtpHeader(bool marker, uint32_t timestamp) { QByteArray hdr(12, \0); uint8_t* p reinterpret_castuint8_t*(hdr.data()); // 第 1 字节: V2, P0, X0, CC0 p[0] 0x80; // 10 000000 // 第 2 字节: Mmarker, PTpayload type p[1] (marker ? 0x80 : 0x00) | (_pt 0x7F); // 序列号大端 p[2] uint8_t(_seq 8); p[3] uint8_t(_seq 0xFF); _seq; // 时间戳大端 p[4] uint8_t(timestamp 24); p[5] uint8_t(timestamp 16); p[6] uint8_t(timestamp 8); p[7] uint8_t(timestamp 0xFF); // SSRC大端 p[8] uint8_t(_ssrc 24); p[9] uint8_t(_ssrc 16); p[10] uint8_t(_ssrc 8); p[11] uint8_t(_ssrc 0xFF); return hdr; }六、与 RTSP 会话的集成推流端 SETUP 时SDP 中声明 PT 和编码mvideo 0 RTP/AVP 96 artpmap:96 H264/90000 afmtp:96 packetization-mode1;profile-level-id42a01e; sprop-parameter-setsZ0KAHpY0Cg,aM48gA maudio 0 RTP/AVP 97 artpmap:97 mpeg4-generic/44100/2 afmtp:97 profile-level-id15;modeAAC-hbr;SizeLength13; IndexLength3;IndexDeltaLength3;config1210推流端从Track::extradata提取 SPS/PPS 填充sprop-parameter-sets音频从AudioSpecificConfig转 Base64 填充config。接收端解析 SDP 后用 PT 和编码类型初始化RtpDepacketizerFU-A 重组、AU-header 解析。