本文探讨rtp协议相关知识。
rtp协议规范
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 |P|X| CC |M| PT | SN |
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
| TS |
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
| SSRC |
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
| CSRC(0~15byte) |
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
| Extened Identifier | Extened Length |
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
| Extened Date(...) |
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
| Payload(...) |
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
| Padding Date(...) |PadLen|
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
v:版本(固定为2)
P:1表示有填充,填充是为数据对齐,Padding Date为填充数据,PadLen(包尾) = Padding Date + 1
X:1表示有扩展,扩展是由于12字节头信息无法全部表示控制信息:帧类型(I/P/B),音量,声道,时间戳偏移,绝对时间,丢包去重信息,帧编号,加密信息
CC:CSRC标识符个数即多路数据源个数(多个音频混合数据)
M:1表示帧结束即帧最后一包数据,最后一包数据中包含pyload数据
PT:表示数据编码类型:95(h264),97(h265),8(pcma),9(g722)用于payload的数据解析
SN:rtp包序列号,连续递增
TS:采样时间戳,8000(音频),9000(视频)
SSRC:同步源标识符,随机生成,全局唯一,一个SSRC标识一路音频或视频流
CSRC:每个CSRC占用4字节,CSRC = CC * 4,不能拆封出payload中的数据,用于标识多路数据源的信息(有几人在说,分别是谁,音量多大)
Payload:载荷数据
项目背景
RTP(实时传输协议)是音视频实时通信领域的核心协议,广泛应用于视频会议、直播、语音通话等场景。为满足实时数据传输的需求,独立设计并实现了一套轻量级、高性能的 RTP 协议收发组件,支持 RTP 数据包的封装、解析、发送和接收,适配嵌入式与通用服务器环境。
核心功能
协议层设计与封装: 严格遵循 RTP 协议 RFC3550 标准,定义RTP 固定头、扩展头数据结构,精准映射协议字段(版本号、填充位、扩展位、CSRC 计数、标记位、负载类型、序列号、时间戳、同步源标识等)。封装Rtp类,抽象初始化、发送、接收核心接口,私有化拷贝 / 赋值构造函数避免浅拷贝问题,保证类的封装性与安全性。
网络层实现: 基于UDP协议实现无连接的实时数据传输,完成RtpInit初始化接口:创建收发套接字、绑定端口/IP、填充发送端地址结构,支持本地回环与跨网络通信。
**数据包编解码核心逻辑:**发送端完成 RTP 包组装:依次填充固定头、CSRC 列表、扩展数据、负载数据、填充位,动态校验包长度(适配 MTU=1500 字节限制),避免数据包超限。支持可选扩展头、填充位配置,灵活适配不同音视频编码(如 H.264/OPUS)的传输需求。接收端逐字段解析 RTP 头、CSRC 列表、扩展数据、填充位、负载数据,层层校验数据长度与缓冲区大小,避免内存越界。处理异常场景:CSRC 计数超限、扩展数据长度异常、负载缓冲区不足、填充位长度非法等,返回明确的错误码,保证组件鲁棒性。
关键技术
高性能: 基于UDP无连接特性,减少 TCP 握手/重传开销,适配实时音视频低延迟需求;内存操作采用栈缓冲区(MTU=1500),避免堆内存频繁分配释放。
高可靠性: 全流程参数校验(CSRC 数量≤15、包长度≤MTU、缓冲区大小、填充位合法性),异常场景返回明确错误码,降低线上崩溃风险。
可扩展性: 支持扩展头自定义、负载类型灵活配置,可适配不同音视频编码格式的传输需求。
**跨平台兼容:**基于标准 Linux 网络 API 开发,可移植到嵌入式 Linux(如 ARM)、x86 服务器等环境。
项目成果:
测试用例验证通过,实现本地 RTP 数据包的稳定收发,负载数据解析准确率 100%。
组件轻量化、低耦合,可直接集成到音视频 SDK 中,为上层业务提供统一的 RTP 传输能力。
项目目录结构

文件简介
rtp.h:rtp协议相关定义,包含错误号、rtp协议头、扩展数据头、rtp协议类(rtp初始化、rtp发包、rtp收包)
rtp.cpp:rtp协相关议实现rtp初始化实现,rtp发包实现、rtp收包实现
test.cpp:rtp收发包的测试
run.sh:编译运行脚本
rtp:可执行二进制程序
rtp.log:程序运行日志
rtp.h
/*
* 变更历史:
* 2026-04-11 cxb 创建该文件.
*/
/*
* @file rtp.h
* @brief
* 功能:rtp协议收发数据.
*/
#ifndef __RTP__H__
#define __RTP__H__
#include <iostream>
#include <cstring>
#include <arpa/inet.h>
#include <unistd.h>
/* 网络数据包最大长度 */
#define MTU 1500
/* 错误号 */
typedef enum
{
OK = 0,
ERROR
} Status;
/* rtp协议头 */
struct RtpHeader
{
unsigned char v:2; /* rtp协议版本,默认为2 */
unsigned char p:1; /* 填充对齐标志 */
unsigned char x:1; /* 扩展数据标志 */
unsigned char cc:4; /* CSRC标识符个数 */
unsigned char m:1; /* 帧结束标志 */
unsigned char pt:7; /* 数据编码类型 */
unsigned short sn; /* rtp包序列号 */
unsigned int ts; /* 采样频率 */
unsigned int ssrc; /* 同步源标识符 */
};
/* rtp扩展头数据 */
struct RtpExtensionHeader
{
unsigned short profile; /* 扩展标识*/
unsigned short len; /* 扩展数据长度 */
};
class Rtp
{
public:
/* rtp初始化 */
Status RtpInit(const char *ip, const int port);
/* 析构清空发送和接收包,数据包长度置0,发送和接收句柄置0 */
Rtp()
{
memset(SendPackBuf, 0, MTU);
memset(RecvPackBuf, 0, MTU);
PackLen = 0;
RtpSendFd = 0;
RtpRecvFd = 0;
}
/* 析构清空发送和接收包,数据包长度置0,关闭发送和接收句柄 */
~Rtp()
{
memset(SendPackBuf, 0, MTU);
memset(RecvPackBuf, 0, MTU);
PackLen = 0;
close(RtpSendFd);
close(RtpRecvFd);
}
/* 接收rtp包 */
Status RtpRecv(struct RtpHeader *hdr,
unsigned int *csrc,
unsigned int MaxCsrc,
unsigned short *ExtenedIdentify,
unsigned char *ExtenedData,
unsigned int MaxExtenedLen,
unsigned char *payload,
unsigned int MaxPayload);
/* 发送rtp包 */
Status RtpSend(const unsigned char cc,
const unsigned char m,
const unsigned char pt,
const unsigned short sn,
const unsigned int ts,
const unsigned int ssrc,
const unsigned int *csrc,
const unsigned char *payload,
const unsigned int PayloadLen,
const unsigned short profile,
const unsigned char *ExtenedData,
const unsigned int ExtenedLen,
const unsigned int PadLen);
private:
unsigned char SendPackBuf[MTU]; /* 发送包 */
unsigned char RecvPackBuf[MTU]; /* 接收包 */
unsigned int PackLen; /* 实际包长 */
int RtpRecvFd; /* rtp接收句柄 */
int RtpSendFd; /* rtp发送句柄 */
struct sockaddr_in SendAddr; /* rtp发送协议结构 */
struct sockaddr_in RecvAddr; /* rtp接收协议结构 */
/* 禁止拷贝和赋值构造 */
Rtp(const class Rtp &rtp);
class Rtp operator=(const class Rtp &rtp);
};
#endif
rtp.cpp
/*
* 变更历史:
* 2026-04-11 cxb 创建该文件.
*/
/*
* @file rtp.cpp
* @brief
* 功能:rtp协议的实现.
*/
#include "rtp.h"
#include <sys/socket.h>
#include <netinet/in.h>
/* rtp初始化 */
Status Rtp::RtpInit(const char *ip, const int port)
{
Status RetCode = OK;
/* 创建发送句柄*/
RtpSendFd = socket(AF_INET, SOCK_DGRAM, 0);
if(RtpSendFd == -1)
{
RetCode = ERROR;
return RetCode;
}
/* 填充协议ip和端口 */
SendAddr.sin_family = AF_INET;
SendAddr.sin_port = htons(port);
inet_aton(ip, &SendAddr.sin_addr);
/* 创建接收句柄 */
RtpRecvFd = socket(AF_INET, SOCK_DGRAM, 0);
if(RtpRecvFd == -1)
{
RetCode = ERROR;
return RetCode;
}
/* 绑定端口和ip*/
RecvAddr.sin_family = AF_INET;
RecvAddr.sin_port = htons(port);
RecvAddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(RtpRecvFd, (struct sockaddr *)&RecvAddr, sizeof(RecvAddr));
return RetCode;
}
/* 接收rtp数据包 */
Status Rtp::RtpRecv(struct RtpHeader *hdr,
unsigned int *csrc,
unsigned int MaxCsrc,
unsigned short *ExtenedIdentify,
unsigned char *ExtenedData,
unsigned int MaxExtenedLen,
unsigned char *payload,
unsigned int MaxPayload)
{
Status RetCode = OK;
/* 接收数据包 */
struct sockaddr_in src;
socklen_t len = sizeof(src);
int RecvSize = recvfrom(RtpRecvFd, RecvPackBuf, sizeof(RecvPackBuf), 0, (struct sockaddr *)&src, &len);
if(RecvSize == -1)
{
RetCode = ERROR;
return RetCode;
}
/* 分解rtp协议头 */
struct RtpHeader *buf = (struct RtpHeader *)RecvPackBuf;
hdr->v = buf->v;
hdr->p = buf->p;
hdr->x = buf->x;
hdr->cc = buf->cc;
hdr->m = buf->m;
hdr->pt = buf->pt;
hdr->sn = ntohs(buf->sn);
hdr->ts = ntohl(buf->ts);
hdr->ssrc = ntohl(buf->ssrc);
/* csrc长度校验 */
if(hdr->cc > 15 || hdr->cc > MaxCsrc)
{
RetCode = ERROR;
return RetCode;
}
/* 分解csrc */
unsigned char *ptr = RecvPackBuf + sizeof(struct RtpHeader);
for(int Crsccount = 0; Crsccount < hdr->cc; Crsccount++)
{
csrc[Crsccount] = ntohl(*((unsigned int*)ptr));
ptr += 4;
}
/* 分解扩展数据 */
if(hdr->x)
{
/* 校验包长度异常 */
if((ptr + 4) > (RecvPackBuf + RecvSize))
{
RetCode = ERROR;
return RetCode;
}
/* 分解扩展头 */
struct RtpExtensionHeader *reh = (struct RtpExtensionHeader *)ptr;
*ExtenedIdentify = ntohs(reh->profile);
int ExtenedLen = ntohs(reh->len);
ptr += 4;
/* 校验包长度异常 */
if((ptr + ExtenedLen * 4) > (RecvPackBuf + RecvSize))
{
RetCode = ERROR;
return RetCode;
}
/* 校验提取扩展数据空间是否足够 */
if(ExtenedLen > MaxExtenedLen / 4)
{
RetCode = ERROR;
return RetCode;
}
/* 提取扩展数据 */
memcpy(ExtenedData, ptr, ExtenedLen * 4);
ptr += (ExtenedLen * 4);
}
else
{
/* 无扩展数据 */
*ExtenedIdentify = 0;
memset(ExtenedData, 0, MaxExtenedLen * 4);
}
/* 分解填充数据 */
int PadLen = 0;
if(hdr->p)
{
/* 校验包长度异常 */
if(RecvSize < 1)
{
RetCode = ERROR;
return RetCode;
}
/* 获取填充长度 */
PadLen = RecvPackBuf[RecvSize - 1];
if(PadLen < 0 || PadLen >= RecvSize)
{
RetCode = ERROR;
return RetCode;
}
}
/* 分解pyload */
int PayloadStart = ptr - RecvPackBuf;
int PayloadEnd = RecvSize - PadLen;
int PayloadLen = PayloadEnd - PayloadStart;
if((PayloadLen < 0) || (PayloadLen > MaxPayload))
{
RetCode = ERROR;
return RetCode;
}
/* 提取payload */
memcpy(payload, RecvPackBuf + PayloadStart, PayloadLen);
return RetCode;
}
/* 发送rtp数据包 */
Status Rtp::RtpSend(const unsigned char cc,
const unsigned char m,
const unsigned char pt,
const unsigned short sn,
const unsigned int ts,
const unsigned int ssrc,
const unsigned int *csrc,
const unsigned char *payload,
const unsigned int PayloadLen,
const unsigned short profile,
const unsigned char *ExtenedData,
const unsigned int ExtenedLen,
const unsigned int PadLen)
{
Status RetCode = OK;
/* 置0实际包长,清空发送包 */
PackLen = 0;
memset(SendPackBuf, 0, sizeof(SendPackBuf));
/* 校验cc */
if(cc > 15)
{
RetCode = ERROR;
return RetCode;
}
/* 填充rtp头 */
struct RtpHeader *hdr = (struct RtpHeader *)SendPackBuf;
hdr->v = 2;
hdr->p = (PadLen > 0) ? 1 : 0;
hdr->x = (ExtenedLen > 0 && ExtenedData != nullptr) ? 1 : 0;
hdr->cc = cc;
hdr->m = m;
hdr->pt = pt;
hdr->sn = htons(sn);
hdr->ts = htonl(ts);
hdr->ssrc = htonl(ssrc);
/* 校验包长 */
unsigned int PackSize = sizeof(struct RtpHeader) + cc * 4 + PayloadLen;
if(PackSize > MTU)
{
RetCode = ERROR;
return RetCode;
}
/* 填充csrc */
unsigned char *ptr = SendPackBuf + sizeof(struct RtpHeader);
PackLen += sizeof(struct RtpHeader);
for(int Crsccount = 0; Crsccount < cc; Crsccount++)
{
*((unsigned int *)ptr) = htonl(csrc[Crsccount]);
ptr += 4;
}
PackLen += (cc * 4);
/* 填充扩展数据 */
if(hdr->x)
{
struct RtpExtensionHeader *reh = (struct RtpExtensionHeader *)ptr;
reh->profile = htons(profile);
int Extened32 = (ExtenedLen + 3) / 4;
reh->len = htons(Extened32);
ptr += 4;
PackLen += 4;
memcpy(ptr, ExtenedData, ExtenedLen);
ptr += Extened32 * 4;
PackLen += (Extened32 * 4);
}
/* 填充payload */
memcpy(ptr, payload, PayloadLen);
ptr += PayloadLen;
PackLen += PayloadLen;
/* 对齐数据 */
if(hdr->p && PadLen >= 1)
{
memset(ptr, 0, PadLen);
ptr += (PadLen - 1);
*ptr = PadLen;
PackLen += PadLen;
}
/* 发送数据包 */
int SendSize = sendto(RtpSendFd, SendPackBuf, PackLen, 0, (struct sockaddr*)&SendAddr, sizeof(SendAddr));
if(SendSize == -1)
{
RetCode = ERROR;
return RetCode;
}
return RetCode;
}
test.cpp
/*
* 变更历史:
* 2026-04-11 cxb 创建该文件.
*/
/*
* @file test.cpp
* @brief
* 功能:rtp协议测试.
*/
#include "rtp.h"
#include <thread>
#include <unistd.h>
Rtp rtp;
/* 发送rtp数据 */
void sendpack()
{
std::cout << "rtp send start..." << std::endl;
unsigned char pay[] = "hello rtp";
unsigned int paylen = sizeof(pay);
unsigned int csrc[] = {0xaa, 0xbb, 0xcc};
unsigned int cc = 3;
unsigned char exdata[] = {0x01, 0x02, 0x03, 0x04, 0x05};
unsigned exlen = sizeof(exdata);
rtp.RtpSend(cc, 1, 96, 1, 9000, 1, csrc, pay, paylen, 1, exdata, exlen, 3);
std::cout << "rtp send end..." << std::endl;
}
/* 接收rtp数据 */
void recvpack()
{
std::cout << "rtp recv start..." << std::endl;
struct RtpHeader hdr;
unsigned int csrc[15];
unsigned short exprofile;
unsigned char exdata[256];
unsigned char pay[1500] = {0};
Status ret = rtp.RtpRecv(&hdr, csrc, 15, &exprofile, exdata, sizeof(exdata), pay, sizeof(pay));
if(ret == OK)
std::cout << "rtp recv success:" << pay << std::endl;
else
std::cout << "rtp recv fail:" << std::endl;
std::cout << "rtp recv end..." << std::endl;
}
int main()
{
std::cout << "rtp init..." << std::endl;
rtp.RtpInit("127.0.0.1", 9000);
std::thread t1(recvpack);
sleep(1);
std::thread t2(sendpack);
t1.join();
t2.join();
return 0;
}
#
# 简介:rtp测试程序编译脚本
# 平台:linux
# 可执行测试程序:trtp
# 测试log:rtp.log
#
#!/bin/bash
g++ test.cpp rtp.cpp -o rtp -lpthread
echo "================$(date)==================" >> rtp.log
./rtp >> rtp.log
rtp.log
================2026年 04月 13日 星期一 20:06:04 CST==================
rtp init...
rtp recv start...
rtp send start...
rtp send end...
rtp recv success:hello rtp
rtp recv end...