实际的媒体数据(视频/音频)的传输是通过rtp进行传输的。
rtp可以基于udp进行发送,也可以基于tcp进行发送。 (这个有点疑问,看很多都说rtp是基于udp传输)
==》那么乱序,丢包,以及一个图片资源过大,如何拆包相关逻辑呢
rtp传输h264 图像资源,需要了解h264格式数据相关知识,以及如何进行封包发送以及接收后解包处理
rtp传输AAC 音频文件,需要了解aac相关格式(aac有两种格式),同样思考如何封包以及解包。
在进行rtsp测试的时候,发现音频如果按定时器发送帧,会有声音卡顿的现象,这里如何做一些处理呢?
==》rtp包中只是部分的数据,而音视频的播放还需要知道一些其他信息(sdp),如当前流的类型,播放音频时的采样率等一些必要信息
==》1:使用rtp进行推流时,获取一个sdp文件,使用该文件进行拉流播放。
==》2:使用rtp接收到数据后,进行相关的解析后,存储到本地后,进行播放。
==》obs是一个强大的视频直播录制软件,可以支持推流功能。
==》obs可以采集摄像头,音频,桌面,窗口等功能,这里只关注测试推流
==》推流时,需要设置,在 文件–>设置–>推流中进行设置,填写我们的服务器相关,然后选择一些采集方式点击开始推流(测试成功):
rtp实时传输协议,是传输层协议(通常基于udp(实时传输))
===》实际传输:最大传输单元MTU需要考虑
rtp实际内部传输的是实际流数据(可以是一帧完整的(如音频帧),可能不足一帧(如图像资源))
rtp内部实际数据流可以是各种格式的数据,如h264,aac,以及其他协议。
rtp可以支持传输多种流,如一个rtp链接可以同时传输h264和aac的流。
===》如果rtp支持传输多种格式,支持传输多种流等,需要在实际传输前做一定的信息协商(sdp)
rtp协议通常和rtcp协议一起使用。
rtp over rtsp(udp)和rtp over rtsp(tcp)之间的理解?
===》rtp是传输层协议,但本质上说其实还是应用层协议,只是相对应用层协议更底层
===》rtp和rtcp即可以用udp进行传输,也可以用tcp进行传输。
阅读RFC3550中文文档时,rtp的使用场景可以有:多播音频会议,音频和视频会议,混频器,转换器,分层编码,监视器等
rtcp 是rtp控制协议,如会议中人数的增加与离开,混频是相关格式与rtp进行适配,计算当前带宽,控制rtp的发送频率等
===》rtcp本身也占带宽,其发送的频率也有一定的规范
===》rtcp有不同的包类型:SR(发送者报告),RR(接收者报告),SDES(源描述项),BYE(会话结束),APP(应用描述功能)
前 12 个字节出现在每个 RTP 包中,仅仅在被混合器插入时,才出现 CSRC 识别符列表。
版本(V):RTP协议的版本号,占2位,当前协议版本号为2。
填充 ( P):填充标志 占1位,如果P=1,则在该报⽂的尾部填充⼀个或多个额外的⼋位组,它们不是有效载荷 的⼀部分。(可能用于某些 具有固定长度的加密算法,或者用于在底层数据单元中传输多个 RTP 包。)
==》如设置填充位,在包尾将包含附加填充字,它不属于有效载荷。填充字节长度位最后一个字节的值。某些加密算法需要固定大小的填充字,或为在底层协议数据单元中携带几个RTP包。
扩展(X):扩展比特,占1位,如果X=1,则固定头(仅)后面跟随一个头扩展。(扩展头有固定的头格式0XBEDE标志开始,并且有32位对齐)
CSRC 计数(CC):CSRC 计数器,占4位,包含了跟在固定头后面 CSRC 识别符的数目。
标志(M):标记,占1位,不同的有效载荷有不同的含义,
==》对于视频,标记⼀帧的结束;对于⾳频,标记帧的开 始。
负载类型(PT):有效载荷类型,占7位,⽤于说明RTP报⽂中有效载荷的类型,如GSM⾳频、JPEM图像等。
序列号(sequence number):占16位,⽤于标识发送者所发送的RTP报⽂的序列号,每发送⼀个报⽂,序列号增1。
==》接收者 通过序列号来检测报⽂丢失情况,重新排序报⽂,恢复数据。(初始值随机)
时间戳(timestamp):占32位,时间戳反映了该RTP报⽂的第⼀个⼋位组(第一个字节)的采样时刻。
==》接收者使⽤时间戳来 计算延迟和延迟抖动,并进⾏同步控制。
==》时钟频率依赖于负载数据格式,并在描述文件(profile)中进行描述
==》如果 RTP 包是周期性产生的,那么将使用由采样时钟决定的名义上的采样时刻,而不是读取系统时间(对一个固定速率的音频,采样时钟将在每个周期内增加 1。如果一个音频从 输入设备中读取含有 160 个采样周期的块,那么对每个块,时间戳的值增加 160)
==》时间戳的初始值应当是随机的,就像序号一样。几个连续的 RTP 包如果是同时产生的,将有相同的序列号。
==》如果传输的数据是存贮好的,而不是实时采样等到的,那么会使用从参考时钟得到的虚的 表示时间线。
同步信源(SSRC):占32位,⽤于标识同步信源。
==》同步源,所有相同标识的源,一起进行处理。
==》一个同步源的所有包构成了相同计时和序列号 空间的一部分,这样接收方就可以把一个同步源的包放在一起,来进行重放。
==》该标识符是随机选择的,参加同⼀视频会议的 两个同步信源不能有相同的SSRC(要解决冲突)。
==》如麦克风、摄影机、RTP 混频器(见下文)就是同步源
==》一个同步源可能随着时间变化而改变其数据格式,如音频编码。
特约信源(CSRC):每个CSRC标识符占32位,可以有0~15个。(是一个表)
==》作用源,组成混合器中所有起作用的源。
==》个数由CSRC 计数(CC)决定
==》CSRC表:标识了包含在该RTP 报⽂有效载荷中的所有特约信源。
==》由混合器插入,列出所有的混合器中的作用信源。
==》例如音频会议中,哪些人说话被组合在包中,可以让接听者知道谁在说话。
typedef struct _rtp_header_t
{
uint32_t v:2; /* 版本 占2位 2*/
uint32_t p:1; /* 填充标志 占1位 加密或者多个rtp包时用???*/
uint32_t x:1; /* 扩展标志 占1位 增加头扩展,有固定的格式,32位对齐 */
uint32_t cc:4; /* CSRC计数器 占4位 作用源的个数*/
uint32_t m:1; /* 标志 占1位 视频标志结束,音频标志开始*/
uint32_t pt:7; /* 有效载荷,类型 占7位 如GSM⾳频、JPEM图像等*/
uint32_t seq:16; /*序列号 占16位 丢包重排恢复数据用*/
uint32_t timestamp; /*时间戳 占16位 进行延迟控制 */
uint32_t ssrc; /*同步源 占32位 同一标识多个同步源一起处理 */
/*作用源 占32位 混合器情况下才有,这里没加。*/
} rtp_header_t;
作为一个协议,从以下几点理解:
1:根据协议定义结构体
2:构造协议报文,序列化
3:解析协议报文,反序列化
这里简单根据测试源码,对这几个细节做梳理:
//头结构
typedef struct _rtp_header_t
{
uint32_t v:2; /* protocol version */
uint32_t p:1; /* padding flag */
uint32_t x:1; /* header extension flag */
uint32_t cc:4; /* CSRC count */
uint32_t m:1; /* marker bit */
uint32_t pt:7; /* payload type */
uint32_t seq:16; /* sequence number */
uint32_t timestamp; /* timestamp */
uint32_t ssrc; /* synchronization source */
} rtp_header_t;
struct rtp_packet_t // 封装这个RTP 包括 header + [csrc/extension] + payload
{
rtp_header_t rtp;
uint32_t csrc[16]; // 最多16个csrc
const void* extension; // extension(valid only if rtp.x = 1)
uint16_t extlen; // extension length in bytes
uint16_t reserved; // extension reserved
const void* payload; // rtp payload
int payloadlen; // payload length in bytes
};
这里的函数实际上是已经有的rtp包,仅仅是做处理进行发送,其他逻辑后续整理。
//根据rtpt头部数据 rtp_header_t 结构,写入ptr中
static inline void nbo_write_rtp_header(uint8_t *ptr, const rtp_header_t *header)
{
ptr[0] = (uint8_t)((header->v << 6) | (header->p << 5) | (header->x << 4) | header->cc);
ptr[1] = (uint8_t)((header->m << 7) | header->pt);
ptr[2] = (uint8_t)(header->seq >> 8);
ptr[3] = (uint8_t)(header->seq & 0xFF);
nbo_w32(ptr+4, header->timestamp);
nbo_w32(ptr+8, header->ssrc);
}
// 把可读RTP packet封装成要发送出去的数据 序列化
int rtp_packet_serialize_header(const struct rtp_packet_t *pkt, void* data, int bytes)
{
int hdrlen;
uint32_t i;
uint8_t* ptr;
if (RTP_VERSION != pkt->rtp.v || 0 != (pkt->extlen % 4))
{
assert(0); // RTP version field must equal 2 (p66)
return -1;
}
// RFC3550 5.1 RTP Fixed Header Fields(p12)
hdrlen = RTP_FIXED_HEADER + pkt->rtp.cc * 4 + (pkt->rtp.x ? 4 : 0);
if (bytes < hdrlen + pkt->extlen)
return -1;
ptr = (uint8_t *)data;
//写入rtp_header_t 相关数据 包括时间戳和ssrc
nbo_write_rtp_header(ptr, &pkt->rtp);
ptr += RTP_FIXED_HEADER;
// pkt contributing source
//写入csrc
for (i = 0; i < pkt->rtp.cc; i++, ptr += 4)
{
nbo_w32(ptr, pkt->csrc[i]); // csrc列表封装到头部
}
// pkt header extension
//如果有扩展标志,写入再rtp头后面
if (1 == pkt->rtp.x)
{
// 5.3.1 RTP Header Extension
assert(0 == (pkt->extlen % 4));
nbo_w16(ptr, pkt->reserved);
nbo_w16(ptr + 2, pkt->extlen / 4);
memcpy(ptr + 4, pkt->extension, pkt->extlen); // extension封装到头部
ptr += pkt->extlen + 4;
}
return hdrlen + pkt->extlen;
}
//data位最终要发送的数据
int rtp_packet_serialize(const struct rtp_packet_t *pkt, void* data, int bytes)
{
int hdrlen;
//把rtp包头数据写入data
hdrlen = rtp_packet_serialize_header(pkt, data, bytes);
if (hdrlen < RTP_FIXED_HEADER || hdrlen + pkt->payloadlen > bytes)
return -1;
//把实际的payload写入data
memcpy(((uint8_t*)data) + hdrlen, pkt->payload, pkt->payloadlen);
//返回整个data的实际大小
return hdrlen + pkt->payloadlen;
}
//获取到的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 |
| .... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
// 通过收到的数据,解析出来可读的RTP packet 反序列化 bytes是收到的字节序
int rtp_packet_deserialize(struct rtp_packet_t *pkt, const void* data, int bytes)
{
uint32_t i, v;
int hdrlen;
const uint8_t *ptr;
if (bytes < RTP_FIXED_HEADER) // RFC3550 5.1 RTP Fixed Header Fields(p12)
return -1;
ptr = (const unsigned char *)data;
memset(pkt, 0, sizeof(struct rtp_packet_t));
// pkt header 网络字节序的处理
v = nbo_r32(ptr); //uint32 处理前4个字节
pkt->rtp.v = RTP_V(v);
pkt->rtp.p = RTP_P(v);
pkt->rtp.x = RTP_X(v);
pkt->rtp.cc = RTP_CC(v);
pkt->rtp.m = RTP_M(v);
pkt->rtp.pt = RTP_PT(v);
pkt->rtp.seq = RTP_SEQ(v);
pkt->rtp.timestamp = nbo_r32(ptr + 4); //处理接下来的4个字节 即 timestamp
pkt->rtp.ssrc = nbo_r32(ptr + 8); //SSRC
assert(RTP_VERSION == pkt->rtp.v); // 调试的时候用
hdrlen = RTP_FIXED_HEADER + pkt->rtp.cc * 4; // 解析带csrc时的总长度
//根据rtcp头数据进行校验 版本 头长度以及扩展标志和填充标志
if (RTP_VERSION != pkt->rtp.v || bytes < hdrlen + (pkt->rtp.x ? 4 : 0) + (pkt->rtp.p ? 1 : 0))
return -1; // 报错
// pkt contributing source
//如果有作用源相关信息 获取 CSRC 表
for (i = 0; i < pkt->rtp.cc; i++)
{
pkt->csrc[i] = nbo_r32(ptr + 12 + i * 4);
}
assert(bytes >= hdrlen);
pkt->payload = (uint8_t*)ptr + hdrlen; // 跳过头部 拿到payload
pkt->payloadlen = bytes - hdrlen; // payload长度
// pkt header extension
//如果有扩展标志
if (1 == pkt->rtp.x)
{
const uint8_t *rtpext = ptr + hdrlen;
assert(pkt->payloadlen >= 4);
//rtp扩展头也是特定的格式
pkt->extension = rtpext + 4; //这里应该是扩展头特定标识4个字节
pkt->reserved = nbo_r16(rtpext); //扩展头相关
pkt->extlen = nbo_r16(rtpext + 2) * 4;
if (pkt->extlen + 4 > pkt->payloadlen)
{
assert(0);
return -1;
}
else
{
pkt->payload = rtpext + pkt->extlen + 4;
pkt->payloadlen -= pkt->extlen + 4;
}
}
// padding 如果有填充位,则最后一个字节是填充的长度
if (1 == pkt->rtp.p)
{
uint8_t padding = ptr[bytes - 1];
if (pkt->payloadlen < padding)
{
assert(0);
return -1;
}
else
{
pkt->payloadlen -= padding;
}
}
return 0;
}
看到相关的文档,rtp属于传输层协议,都是基于udp传输的,但是又理解到有时候rtp可以通过tcp的方式进行传输,这是遗留的一点疑问。
下一步:
rtp如何与相对应的h264,aac等文件格式交互的? 梳理一个读取h264的文件并进行推流的流程。
rtcp报文涉及SR,RR,SDES,BYE,APP不通类型的报文,以及rtcp在整个业务流程中的控制作用及细节梳理。
分析rtp的测试源码,使用rtp进行传输h264和aac进行梳理
相关知识和资料来源:推荐免费订阅