音视频学习之rtsp学习rtp协议的理解(rtp)

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 音视频学习之rtsp学习rtp协议的理解(rtp)

1:理论理解相关细节

实际的媒体数据(视频/音频)的传输是通过rtp进行传输的。

rtp可以基于udp进行发送,也可以基于tcp进行发送。 (这个有点疑问,看很多都说rtp是基于udp传输)

==》那么乱序,丢包,以及一个图片资源过大,如何拆包相关逻辑呢

rtp传输h264 图像资源,需要了解h264格式数据相关知识,以及如何进行封包发送以及接收后解包处理

rtp传输AAC 音频文件,需要了解aac相关格式(aac有两种格式),同样思考如何封包以及解包。

在进行rtsp测试的时候,发现音频如果按定时器发送帧,会有声音卡顿的现象,这里如何做一些处理呢?

上一文中有个疑问,使用rtp进行推流,如何播放没有成功,依然有疑问,但至少:

==》rtp包中只是部分的数据,而音视频的播放还需要知道一些其他信息(sdp),如当前流的类型,播放音频时的采样率等一些必要信息

==》1:使用rtp进行推流时,获取一个sdp文件,使用该文件进行拉流播放。

==》2:使用rtp接收到数据后,进行相关的解析后,存储到本地后,进行播放。

上一文中有一个疑问,不知道怎么用obs进行推流:

==》obs是一个强大的视频直播录制软件,可以支持推流功能。

==》obs可以采集摄像头,音频,桌面,窗口等功能,这里只关注测试推流

==》推流时,需要设置,在 文件–>设置–>推流中进行设置,填写我们的服务器相关,然后选择一些采集方式点击开始推流(测试成功):

2:了解rtp相关协议(根据课程已有代码)

2.1:概念了解一下:

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进行传输。

===》rtsp涉及多组传输通道,要定义rtp传输的端口。

2.2:协议理解一下

阅读RFC3550中文文档时,rtp的使用场景可以有:多播音频会议,音频和视频会议,混频器,转换器,分层编码,监视器等

rtcp 是rtp控制协议,如会议中人数的增加与离开,混频是相关格式与rtp进行适配,计算当前带宽,控制rtp的发送频率等

===》rtcp本身也占带宽,其发送的频率也有一定的规范

===》rtcp有不同的包类型:SR(发送者报告),RR(接收者报告),SDES(源描述项),BYE(会话结束),APP(应用描述功能)

2.2.1:rtp报文格式

2.2.2:rtp报文格式简单描述

前 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 报⽂有效载荷中的所有特约信源。

==》由混合器插入,列出所有的混合器中的作用信源。

==》例如音频会议中,哪些人说话被组合在包中,可以让接听者知道谁在说话。

2.2.2: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;

2.3:对应代码理解一下

作为一个协议,从以下几点理解:

1:根据协议定义结构体

2:构造协议报文,序列化

3:解析协议报文,反序列化

这里简单根据测试源码,对这几个细节做梳理:

2.3.1:头结构

//头结构
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
};

2.3.2:构造要发送的rtp包

这里的函数实际上是已经有的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;
}

2.3.3:解析收到rtp包

//获取到的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;
}

3:总结及下一步

看到相关的文档,rtp属于传输层协议,都是基于udp传输的,但是又理解到有时候rtp可以通过tcp的方式进行传输,这是遗留的一点疑问。

下一步:

rtp如何与相对应的h264,aac等文件格式交互的? 梳理一个读取h264的文件并进行推流的流程。

rtcp报文涉及SR,RR,SDES,BYE,APP不通类型的报文,以及rtcp在整个业务流程中的控制作用及细节梳理。

分析rtp的测试源码,使用rtp进行传输h264和aac进行梳理

相关知识和资料来源:推荐免费订阅

目录
相关文章
|
2月前
|
网络协议 开发工具 C#
RTSP协议探究和RTSP播放器技术实现
RTSP播放器可广泛应用于对延迟要求比较高的场景下,比如协同操控相关的智能机器人或无人机、实时视频监控、远程视频会议、网络电视等。通过控制信令实现对流媒体数据的远程控制和传输管理。
|
5月前
|
监控 网络协议 网络安全
【专栏】RTMP和RTSP是流媒体传输常用的协议:秒懂
【4月更文挑战第28天】RTMP和RTSP是流媒体传输常用的协议。RTMP由Adobe开发,适合低延迟的实时通信,常用于网络直播和在线游戏;而RTSP是IETF定义的协议,侧重于流媒体播放控制,适用于视频监控和VoD服务。RTMP在业界普及度高,RTSP则在专业领域更常见。选择时需考虑延迟、应用场景和安全性等因素。
749 1
|
5月前
|
编解码 网络协议 网络性能优化
RTP/RTCP 协议讲解
RTP/RTCP 协议讲解
232 0
|
5月前
|
编解码 网络协议 程序员
【RTP 传输协议】实时视频传输的艺术:深入探索 RTP 协议及其在 C++ 中的实现
【RTP 传输协议】实时视频传输的艺术:深入探索 RTP 协议及其在 C++ 中的实现
1180 0
|
编解码 应用服务中间件 nginx
RTSP协议转换RTMP直播协议
RTSP协议转换RTMP直播协议
533 1
|
编解码 网络协议 计算机视觉
ffmpeg推流rtmp指定udp传输
ffmpeg推流rtmp指定udp传输
545 0
|
安全 网络协议 算法
RTP、RTCP、RTSP 概念
<p style="line-height: 28px; margin-top: 0px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; color: rgb(51, 51, 51); font-family: 'Hiragino Sans GB W3', 'Hiragino Sans GB', Arial, Helve
7010 0
|
编解码 小程序 算法
自己动手写RTP服务器——关于RTP协议
本文会带领着你一步步动手实现一个简单的RTP传输服务器,旨在了解RTP流媒体传输协议以及一些关于多媒体编解码的知识。   关于RTP协议的必备知识 要动手实现一个协议,当然首先需要阅读该协议的文档。
1865 0
|
存储 编解码 监控
流媒体传输协议之 RTP
本系列文章将整理各路流媒体传输协议,包括RTP/RTCP,RTMP,希望通过深入理解各个流媒体传输协议的设计细节,对今后流媒体部分的开发工作有一定的启发。
|
编解码
RTP 协议
概述: 实时传送协议(Real-time Transport Protocol或简写RTP,也可以写成RTTP)是一个网络传输协议,它是由IETF的多媒体传输工作小组1996年在RFC 1889中公布的。
1163 0