WebRTC之RTP封装与解封装

news2024/9/27 9:26:13

1 前言


rtp_rtcp模块作为Webrtc组件中非常重要的组成部分,首先是对应rtp和rtcp的封装与解封装,第二部分是对QOS各种策略的支持都离不开rtcp的控制协议。这里首先进行协议封装的探讨。

2 RTP协议解析


各个音视频的大佬对下面这张RTP协议图应该并不陌生,这就是RTP头部协议,解析rtp主要就是为了解析头部信息,并且获取到准确的音视频数据部分。整个rtp头部至少包含12个字节,此时CSRC只有一个的情况,同时不包含扩展头部。

rtp解析核心函数是RtpPacket::ParseBuffer,这里完成的rtp头部各个字段的解析。rtp头部解析的方式在各种开源项目不尽相同,有采用数据结构强转的方式,也有webrtc采用字节解析的方式,但是要注意的是转换过程中的网络字节序与主机字节序的一个转换关系。

bool RtpPacket::ParseBuffer(const uint8_t* buffer, size_t size) {

//rtp头大小检查,最小12个字节

if (size < kFixedHeaderSize) {

return false;

}

//版本检查,v = 2

const uint8_t version = buffer[0] >> 6;

if (version != kRtpVersion) {

return false;

}

//填充字段

const bool has_padding = (buffer[0] & 0x20) != 0;

//扩展字段

const bool has_extension = (buffer[0] & 0x10) != 0;

//csrc数量获取

const uint8_t number_of_crcs = buffer[0] & 0x0f;

//标志位,对于视频一般是标识视频最后一个包

marker_ = (buffer[1] & 0x80) != 0;

//荷载类型0-95固定,96-125动态协商

payload_type_ = buffer[1] & 0x7f;

//序列号

sequence_number_ = ByteReader<uint16_t>::ReadBigEndian(&buffer[2]);

//时间戳

timestamp_ = ByteReader<uint32_t>::ReadBigEndian(&buffer[4]);

//源

ssrc_ = ByteReader<uint32_t>::ReadBigEndian(&buffer[8]);

if (size < kFixedHeaderSize + number_of_crcs * 4) {

return false;

}

payload_offset_ = kFixedHeaderSize + number_of_crcs * 4;

extensions_size_ = 0;

extension_entries_.clear();

//扩展解析

if (has_extension) {

/* RTP header extension, RFC 3550.

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

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| defined by profile | length |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| header extension |

| .... |

*/

//此处省略...

}

//填充解析,填充大小放在最后一个字节

if (has_padding && payload_offset_ < size) {

padding_size_ = buffer[size - 1];

if (padding_size_ == 0) {

RTC_LOG(LS_WARNING) << "Padding was set, but padding size is zero";

return false;

}

} else {

padding_size_ = 0;

}

if (payload_offset_ + padding_size_ > size) {

return false;

}

//最终得到荷载大小

payload_size_ = size - payload_offset_ - padding_size_;

return true;

}

3 RTP协议封装


RTP设置比较简单都是获取data数据后与上对于位置进行设置。

4 RTCP协议解析


webrtc中RTCP解析统一入口是在RtcpPacketParser::Parse接口内完成,根据不同的RTCP类型分发到对应的类型类中进行解析。这里需要注意的点一个是for循环主要是针对一个udp数据包可能携带多个RTCP包,主要根据公共头部进行分割。第二是数据分发,目前已经支持的类型包括app,bye,xr,fir,pli,rr,remb,nack,tmmbr等类型.

bool RtcpPacketParser::Parse(const void* data, size_t length) {

++processed_rtcp_packets_;

const uint8_t* const buffer = static_cast<const uint8_t*>(data);

const uint8_t* const buffer_end = buffer + length;

rtcp::CommonHeader header;

for (const uint8_t* next_packet = buffer; next_packet != buffer_end;

next_packet = header.NextPacket()) {

RTC_DCHECK_GT(buffer_end - next_packet, 0);

if (!header.Parse(next_packet, buffer_end - next_packet)) {

RTC_LOG(LS_WARNING)

<< "Invalid rtcp header or unaligned rtcp packet at position "

<< (next_packet - buffer);

return false;

}

switch (header.type()) {

case rtcp::App::kPacketType:

app_.Parse(header);

break;

case rtcp::Bye::kPacketType:

bye_.Parse(header, &sender_ssrc_);

break;

case rtcp::ExtendedReports::kPacketType:

xr_.Parse(header, &sender_ssrc_);

break;

case rtcp::ExtendedJitterReport::kPacketType:

ij_.Parse(header);

break;

case rtcp::Psfb::kPacketType:

switch (header.fmt()) {

case rtcp::Fir::kFeedbackMessageType:

fir_.Parse(header, &sender_ssrc_);

break;

case rtcp::Pli::kFeedbackMessageType:

pli_.Parse(header, &sender_ssrc_);

break;

case rtcp::Psfb::kAfbMessageType:

if (!loss_notification_.Parse(header, &sender_ssrc_) &&

!remb_.Parse(header, &sender_ssrc_)) {

RTC_LOG(LS_WARNING) << "Unknown application layer FB message.";

}

break;

default:

RTC_LOG(LS_WARNING)

<< "Unknown rtcp payload specific feedback type "

<< header.fmt();

break;

}

break;

case rtcp::ReceiverReport::kPacketType:

receiver_report_.Parse(header, &sender_ssrc_);

break;

case rtcp::Rtpfb::kPacketType:

switch (header.fmt()) {

case rtcp::Nack::kFeedbackMessageType:

nack_.Parse(header, &sender_ssrc_);

break;

case rtcp::RapidResyncRequest::kFeedbackMessageType:

rrr_.Parse(header, &sender_ssrc_);

break;

case rtcp::Tmmbn::kFeedbackMessageType:

tmmbn_.Parse(header, &sender_ssrc_);

break;

case rtcp::Tmmbr::kFeedbackMessageType:

tmmbr_.Parse(header, &sender_ssrc_);

break;

case rtcp::TransportFeedback::kFeedbackMessageType:

transport_feedback_.Parse(header, &sender_ssrc_);

break;

default:

RTC_LOG(LS_WARNING)

<< "Unknown rtcp transport feedback type " << header.fmt();

break;

}

break;

case rtcp::Sdes::kPacketType:

sdes_.Parse(header);

break;

case rtcp::SenderReport::kPacketType:

sender_report_.Parse(header, &sender_ssrc_);

break;

default:

RTC_LOG(LS_WARNING) << "Unknown rtcp packet type " << header.type();

break;

}

}

return true;

}

4.1 接收者报告

这里重点介绍接收者报文和发送者报文,其他rtcp将根据实际用途在相关策略篇进行讲解。

首先是RR报文的解析在ReceiverReport类中进行解析解析函数是ReceiverReport::Parse,主要进行RR报文中头部解析和接收报告块解析。接收报告块会进一步进入ReportBlock类解析,

接收报告块协议部分:丢包率,期望序列号,抖动,上次发送sr时间等等这部分数据主要是用于报告当前的丢包情况和网络的往返时间计算等等。

报告块解析采用的是位操作逐个解析的方式,整体比较简单。

4.2 发送者报告

SR报告解析在SenderReport类中进行,核心就是解析相关的字段,主要包括NTP时间用于计算当前的发送时间,当前发送的RTP的时间戳,后续的音视频时间同步需要用到相关字段,发送包的数量和发送的字节数,这些信息主要是统计当前的发送者的流量统计。

解析部分也是采用根据位运算进行逐个字段解析的过程。需要注意的是发送报告一般也会携带接收这报告块。

4.3 Sdes报文解析

sdes就是指源的描述名称,为了解决当ssrc发生变化后通知其他会话成员更新源操作。基本每次会话开始都会先发送sdes报文,来通知对方CNAME相关信息的数据。主要包括源id和描述,描述中存放用户会话名称。

chunks解析名称,主要是获取chunks数量,然后根据CNAME类型为1的进行cname名称解析。

4.4 Bye报文

Bye报文一般会在会话结束时发送,用于终止会话。主要携带两个参数一个是长度,一个是退出的原因。

由于对方已经下线因此需要移除一个csrc源,同时获取退出的原因。

4.5 app报文

这个是自定义报文也是RFC3550定义,用于用户自定义协议使用。在App类中实现。

解析由三部分组成,子协议类型,名称,和数据体。

5 RTCP协议封装


RTCP封装分为两部分一部分是公共头部封装实现在RtcpPacket类中,其实就是设置RTCP类型。weirtcp采用的是对每个数据位进行操作。

void RtcpPacket::CreateHeader(

size_t count_or_format, // Depends on packet type.

uint8_t packet_type,

size_t length,

bool padding,

uint8_t* buffer,

size_t* pos) {

RTC_DCHECK_LE(length, 0xffffU);

RTC_DCHECK_LE(count_or_format, 0x1f);

constexpr uint8_t kVersionBits = 2 << 6;

uint8_t padding_bit = padding ? 1 << 5 : 0;

buffer[*pos + 0] =

kVersionBits | padding_bit | static_cast<uint8_t>(count_or_format);

buffer[*pos + 1] = packet_type;

buffer[*pos + 2] = (length >> 8) & 0xff;

buffer[*pos + 3] = length & 0xff;

*pos += kHeaderLength;

}

各个协议的封装过程其实是解析的逆过程,也是分布在各个具体的协议类中实现。

6 RTP/RTCP协议总结


RTP和RTCP协议解析和封装其实是对于RFC3350的协议实现,旨在对于利用RTP报文完成对于媒体数据的一个封装,同时利用RTCP对媒体数据流的控制信息,来保证数据的传输的可靠性和实时性。两者之间需要平衡从而衍生出来了各种QOS策略来保证媒体数据流的实时性,同时又有较高的数据质量。

原文链接:WebRTC之RTP封装与解封装 - 资料 - 我爱音视频网 - 构建全国最权威的音视频技术交流分享论坛

★文末名片可以免费领取音视频开发学习资料,内容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。

见下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/382105.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ChatGPT到底是个啥?简析ChatGPT!

目录 ​编辑 1. ChatGPT到底是个啥&#xff1f; 1.1. 简介 1.2 玩法 1.2.1.生成公司理念、生成广告标语 1.2.2.写小说写故事写情书 1.2.3.生成自媒体文案 1.2.4.写代码 2.简析ChatGPT 2.1.ChatGPT核心能力 2.2.ChatGPT进化史 2.2.1.历史沿革 2.2.2.算法 2.3.ChatGPT特…

k8s学习之路 | Pod 基础

文章目录Pod 基础认知什么是 PodPod 的示例 yamlPod 的形式Pod 的多容器协同Pod 的生命周期容器的类型应用容器初始化容器临时容器静态 Pod什么是静态 Pod静态 Pod 位置Pod 探针机制探针类型Probe 配置项探针案例Pod 基础认知 什么是 Pod https://kubernetes.io/zh-cn/docs/c…

Word处理控件Aspose.Words功能演示:使用 Java 合并 MS Word 文档

Aspose.Words 是一种高级Word文档处理API&#xff0c;用于执行各种文档管理和操作任务。API支持生成&#xff0c;修改&#xff0c;转换&#xff0c;呈现和打印文档&#xff0c;而无需在跨平台应用程序中直接使用Microsoft Word。此外&#xff0c; Aspose API支持流行文件格式处…

Overlay网络技术

大家好&#xff0c;我是技福的小咖老师。 Overlay网络是通过网络虚拟化技术&#xff0c;在同一张Underlay网络上构建出的一张或者多张虚拟的逻辑网络。不同的Overlay网络虽然共享Underlay网络中的设备和线路&#xff0c;但是Overlay网络中的业务与Underlay网络中的物理组网和互…

aardio - 【库】简单信息框

昨晚得知aardio作者一鹤的妻子病情严重&#xff0c;深感悲痛。今日给一鹤捐赠少许&#xff0c;望其妻能挺过难关&#xff0c;早日康复。 aardio是一个很好的编程工具&#xff0c;我非常喜欢&#xff0c;这两年也一直在用。虽然未曾用其获利&#xff0c;但其灵活的语法&#xff…

操作系统真相还原——第7章 中断

中断&#xff1a;CPU 暂停正在执行的程序&#xff0c;转而去执行处理该事件的程序&#xff0c;当这段程序执行完毕后&#xff0c; CPU 继续执行刚才的程序。 通常&#xff0c;中断牺牲的是个体的时间&#xff0c;但可以实现多设备的并发&#xff0c;从而提高系统效率 操作系统…

评估Jupyter环境的安全性

评估Jupyter环境的安全性 如何判断您的 Jupyter 实例是否安全&#xff1f; NVIDIA AI 红队开发了一个 JupyterLab 扩展来自动评估 Jupyter 环境的安全性。 jupysec 是一种根据近 100 条规则评估用户环境的工具&#xff0c;这些规则检测配置和工件&#xff0c;这些配置和工件已被…

暴力递归到动态规划

暴力递归到动态规划 假设有排成一行的n个位置&#xff0c; 记为1~n&#xff0c;n-定大于或等于2。开始时机器人在其中的m位置上(m 一定是1~n中的一个)。如果机器人来到1位置&#xff0c;那么下一步只能往右来到2位置&#xff1b;如果机器人来到n位置&#xff0c; 那么下一步只能…

js中splice方法和slice方法

splice方法用来操作数组splice(startIndex,deleteNum,item1,....,)此操作会改变原数组。删除数组中元素参数解释&#xff1a;startIndex为起始index索引。deleteNum为从startIndex索引位置开始需要删除的个数。分三种情况&#xff1a;没有传第三个参数的情况下&#xff0c;dele…

pytest两种生成测试报告的方法——html

pytest有两种生成测试报告的方法&#xff08;html和allure&#xff09;&#xff0c;今天就给大家一介绍下html 一.pytest-html基本语法 1.安装&#xff1a;pip install pytest-html 2.查看版本&#xff1a;pip show pytest-html 3.生成测试报告基本语法&#xff1a; 语法一…

STM32物联网项目之程序框架

前言&#xff1a; 这个系列&#xff0c;我主要写我用32f103实现的各种功能模块&#xff0c;已经程序编写过程中&#xff0c;硬件调试中出现的问题&#xff0c;一边记录&#xff0c;一边分享&#xff0c;一边复盘。 使用的是STM32cubemax&#xff0c;自动生成代码&#xff0c;…

每日学术速递3.2

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.Interactive Segmentation as Gaussian Process Classification(CVPR 2023) 标题&#xff1a;作为高斯过程分类的交互式分割 作者&#xff1a;Minghao Zhou, Hong Wang, Qian Zha…

tensorflow1.14.0安装教程--保姆级

//方法不止一种&#xff0c;下面仅展示一种。 注&#xff1a;本人电脑为win11&#xff0c;anaconda的python版本为3.9&#xff0c;但tensorflow需要python版本为3.7&#xff0c;所以下面主要阐述将python版本改为3.7后的安装过程以及常遇到的问题。 1.首先电脑安装好anaconda…

java进阶—多线程

学习线程&#xff0c;我们先来了解了解什么是进程&#xff1f;什么是线程 进程&#xff1a;就是在操作系统中运行的程序 线程&#xff1a;就是进程的一个执行单元&#xff0c;或者一条执行路劲 比如&#xff1a;我们打开应用商店&#xff0c;这个应用商店就是一个进程&#…

字节实习二面

网络体系结构分层&#xff08;7、5、4&#xff09; 答&#xff1a; OSI七层网络体系结构&#xff1a;物理层、数据链路层、网络层、传输层、会话层、表示层、应用层 TCP/IP四层网络体系结构&#xff1a;物理层、网际层、传输层、应用层 TCP/IP五层网络体系结构&#xff1a;物…

ShopWind 多商户商城更新,Vue 3 前后端分离,页面自定义装修

本次为 V4 版本更新&#xff0c;新系统架构(技术栈)vue3 vite (打包编译工具) Composition API(组合式 API setup) Element Plus vueRouter (路由) 第三方组件&#xff1a;axios (数据请求) wangeditor(编辑器)&#xff0c;都是通过接口访问数据&#xff0c;页面效果更佳了…

【强烈建议收藏:MySQL面试必问系列之并发事务锁专题】

一.知识回顾 上节课我们一起学习了MySQL面试必问系列之事务&#xff0c;没有学习的同学可以看一下上一篇文章&#xff0c;肯定对你会有帮助&#xff0c;学习过的同学肯定知道&#xff0c;上节课我们留了一个小尾巴&#xff0c;这个小尾巴是什么呢&#xff1f;就是没有详细展开…

MPI ubuntu安装,mpicc,mpicxx,mpif90的区别

介绍 MPI是并行计算的一个支持库&#xff0c;支持对C、C、fortran语言进行并行计算。 安装基础环境 ubuntu进行gcc/g/gfortran的安装&#xff1a; gcc&#xff1a; ubuntu下自带gcc编译器。可以通过gcc -v命令来查看是否安装。 g&#xff1a; sudo apt-get install buil…

【Python学习笔记】第二十四节 Python 正则表达式

一、正则表达式简介正则表达式&#xff08;regular expression&#xff09;是一个特殊的字符序列&#xff0c;它能帮助你方便的检查一个字符串是否与某种模式匹配。正则表达式是对字符串&#xff08;包括普通字符&#xff08;例如&#xff0c;a 到 z 之间的字母&#xff09;和特…

Day07-flex布局

文章目录弹性布局一 简介二 弹性容器案例-让多个div排成一行三 容器项目的对齐方式案例1-justify-content(主轴对齐)案例2-flex-wrap(换行)案例3-align-items(侧轴对齐)案例4-align-self(项目垂直对齐)案例5-flex-direction(改变轴向)案例6-弹性布局应用四 弹性项目-flex属性案…