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

2023-05-16

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进行梳理

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

音视频学习之rtsp学习rtp协议的理解(rtp) 的相关文章

  • 散度和旋度的物理意义是什么?

    高等数学学的时间有点久远了 xff0c 最近需要推倒一些公式 xff0c 以前高数学的时候这公式那定理的都没说什么物理意义 xff0c 现在接触的问题都是具有一定物理意义的 xff0c 感觉对不上 xff0c 回来找找资料好好理解一下 xf
  • 格林公式、高斯公式及斯托克斯公式的理解及相互关系

    最近要推倒波动方程积分解 xff0c 要对散度 旋度以及他们之间的相互关系有一个理解 看了两天 xff0c 自己认为理解的差不多了 xff0c 现在写在这个地方 xff0c 作为笔记 xff0c 以后忘记了拿过来看一下 xff0c 加深一下
  • Radon变换理论介绍

    本人最近在研究Radon变换 xff0c 在查阅了各种资料之后在此写下个人的理解 xff0c 希望与各位牛牛进行交流共同进步 xff0c 也使得理解更加深刻些 Radon变换的本质是将原来的函数做了一个空间转换 xff0c 即 xff0c
  • test

    lt DOCTYPE html gt lt html lang 61 34 en 34 gt lt head gt lt meta charset 61 34 utf 8 34 gt lt meta http equiv 61 34 X U
  • BIT内存顺序

    机器的最小寻址单位是字节 xff0c bit无法寻址 xff0c 也就没有高低地址和起始地址的概念 xff0c 我们需要定义一下bit的 地址 以一个字节为例 xff0c 我们把从左到右的8个bit的位置 position 命名按顺序命名如
  • 无人驾驶感知篇之融合(五)

    今天早上看到上海新增一万七千左右 xff0c 看的真的很揪心 xff01 希望白衣战士能早点战胜这场疫情 xff0c 期待明天能有好消息 xff01 今天具体讲讲多贝叶斯估计算法的原理 xff0c 多贝叶斯估计法的主要思想是将传感器信息依据
  • MAC地址的介绍(单播、广播、组播、数据收发)

    MAC地址组成 网络设备的MAC地址是全球唯一的 MAC地址长度为48比特 xff0c 通常用十六进制表示 MAC地址包含两部分 xff1a 前24比特是组织唯一标识符 xff08 OUI xff0c OrganizationallyUni
  • stm32通用定时器输出PWM控制舵机

    stm32的通用定时器有TIM2 TIM3 TIM4 TIM5 xff0c 每个定时器都有独立的四个通道可以作为 xff1a 输入捕获 输出比较 PWM输出 单脉冲模式输出等 stm32除了基本定时器 xff0c 其他定时器都能输出PWM
  • Linux内核Socket CAN中文文档

    自己在年假中空闲之余翻译的内核中Socket CAN的文档 xff0c 原文地址在 xff1a http lxr linux no linux 43 v2 6 34 Documentation networking can txt 但是这篇
  • c/c++自定义通讯协议(TCP/UDP)

    前言 xff1a TCP与UDP是大家耳熟能详的两种传输层通信协议 xff0c 本质区别在于传输控制策略不相同 xff1a 使用TCP协议 xff0c 可以保证传输层数据包能够有序地被接受方接收到 xff0c 依赖其内部一系列复杂的机制 x
  • ubuntu 使用虚拟can 与 socketCAN使用

    原文链接 xff1a https blog csdn net xiandang8023 article details 127990159 创建虚拟CAN接口 在Linux上能使用虚拟CAN接口之前 xff0c 需要在终端执行以下三个步骤
  • cmake引入第三方库

    cmake引入第三方库 第三方库包含 lib文件和 h hpp文件动态库还包含 dll文件 小例程 3rdparty bin test dll include test hpp lib Debug test lib Release test
  • AHB-APB总线协议

    AHB APB总线协议 文章目录 AHB APB总线协议一 AHB APB总线介绍二 AHB总线设备1 AHB主设备 xff08 master xff09 2 AHB从设备 xff08 slave xff09 3 AHB仲裁器 xff08
  • Modelsim缺失库快速添加

    Modelsim缺失库快速添加 文章目录 Modelsim缺失库快速添加前言一 ini文件二 器件库配置1 将器件库放在modelsim文件夹下2 ini配置文件修改 前言 在单独使用modelsim时 xff0c 假如要编译复杂的工程文件
  • AHB-APB_Lite总线协议及Verilog实现

    AHB APB Lite总线协议及Verilog实现 文章目录 AHB APB Lite总线协议及Verilog实现一 AHB Lite协议介绍二 系统框架介绍三 代码设计四 仿真测试 一 AHB Lite协议介绍 AHB xff08 Ad
  • 通信协议详解(二):IIC总线协议(传输时序+数据格式+设计实现)

    文章目录 一 IIC xff08 Inter Integrated Circuit xff09 介绍二 传输协议1 时序传输时序写操作时序数据有效性开始 amp 结束信号从机应答信号 2 数据格式 三 设计实现1 时钟2 传输过程3 三态门
  • Qt error ------ 'XXX' has not been declared

    1 头文件没加 2 调用函数者的头文件在XXX头文件的下方 转载于 https www cnblogs com god of death p 8572306 html
  • Command Expert安装

    一 安装准备 需先下载两个安装包 1 Commmand Expert安装包 https www keysight com cn zh lib software detail computer software command expert
  • Vitis开发(一):Vivado启动vitis

    Vitis是Xilinx SDK的继承开发工具 xff0c 从Vivado 2019 2版本开始启用 在Vivado 2019 1及更早版本中 xff0c 导出的硬件描述文件为 hdf文件 xff0c 给xilinx sdk使用 在Viva
  • 数字IC刷题(一)

    一 选择 1 To achieve better leakage cells are placed A HVT B LVT C RVT 解 LVT Low V threshold xff1a 低阈值 这种库的漏电流较大 xff0c 但是延迟

随机推荐