SRS4.0源代码分析之WebRTC服务总体介绍
yuyutoo 2024-12-06 20:39 1 浏览 0 评论
1、前言:
WebRTC是一个开放的Web标准,用于支持在浏览器之间的语音、视频和通用数据的双向实时通信。在以Google为首的大厂推动下,WebRTC各项技术逐渐成熟并标准化,成为各种主流浏览器都支持的基于Web的实时音视频通信解决方案。 WebRTC本身是一个应用在客户端的类P2P技术,SRS4.0引入WebRTC处理能力,主要是为了构建服务器的SFU能力(什么是SFU读者可自行搜索)。这里借用一个网图来说明SFU的工作原理:
如上图所示,对于视频会议场景,一般都有多个WebRTC客户端。此时通过部署SFU服务器,每个WebRTC客户端都和SFU服务器之间建立一条针对本地音视频数据的推流连接,同时,WebRTC客户端和SFU服务器之间还可以按需建立多个拉流连接。这样做的好处是即利用了SFU服务器强大的客户端接入能力,又不会在服务端因为音视频混流消耗过多的CPU计算能力。
所以,SRS4.0引入WebRTC能力的主要目的是: 1)支持浏览器无插件的从SRS服务器拉流并直接播放。 2)降低音视频数据的总延时(最低毫秒级延时)。 3)支持双向音视频能力,支持直播连麦场景。
2、目标:
WebRTC包括的知识点非常多,从SDP报文的生成与交换、ICE方式建立连接,DTLS握手/SRTP加解密、RTP/RTCP数据封装与传输,到面对网络抖动、带宽不足时各种提升音视频用户体验的Qos处理,每个知识点涉及的内容都非常多,本章将从WebRTC推拉流连接建立开始,通过分析音视频数据在关键类和关键函数之间的总体流向,先从整体上了解SRS4.0 WebRTC服务器模块的代码逻辑。
3、内容:
3.1 SRS4.0 WebRTC服务启动
SRS WebRTC服务模块的初始化和启动接口在文件srs_app_rtc_server.cpp中,整体处理逻辑包括: 1)生成用于DTLS的自签名证书 2)启动UDP端口(8000)监听,处理STUN/DTLS/RTP报文 3)注册推拉流API接口
srs_error_t RtcServerAdapter::initialize()
{
......
// 此函数内部调用openssl库,生成自签名证书,用于后续的DTLS认证
if ((err = _srs_rtc_dtls_certificate->initialize()) != srs_success) {
return srs_error_wrap(err, "rtc dtls certificate initialize");
}
// 此函数内部订阅5秒定时器的超时消息,通过此消息完成一些周期性工作
if ((err = rtc->initialize()) != srs_success) {
return srs_error_wrap(err, "rtc server initialize");
}
return err;
}
srs_error_t RtcServerAdapter::run()
{
......
// 创建UDP端口监听对象SrsUdpMuxListener,默认监听8000端口
if ((err = rtc->listen_udp()) != srs_success) {
return srs_error_wrap(err, "listen udp");
}
// 向全局SrsHttpServeMux对象注册RTC模块的推拉流API
if ((err = rtc->listen_api()) != srs_success) {
return srs_error_wrap(err, "listen api");
}
// 启动_srs_rtc_manager内部协程,用于清理内部僵尸连接,回收资源
if ((err = _srs_rtc_manager->start()) != srs_success) {
return srs_error_wrap(err, "start manager");
}
return err;
}
前面我们知道,RTMP客户端和服务器之间,总是先建立一条socket连接,客户端通过此连接向服务器发送推拉流请求命令,服务器接收到请求命令后,使用同一个socket连接传输音视频数据流。所以,RTMP协议的推拉流控制命令和音视频数据流,总是由同一个socket连接传输,并通过不同的RTMP报文类型,实现socket复用。
WebRTC协议基于P2P/ICE技术在两个WebRTC终端之间建立UDP数据通道,当终端1向终端2发送建立连接的请求时,一般总是要经过一个单独的信令服务器完成这些控制命令的转发(具体的做法就是两个WebRTC终端分别和信令服务器保持长连接,并用不同的客户端ID标识不同的客户端长连接,当终端1向终端2发送请求命令时,只要在命令报文中带上终端2的客户端ID,并把请求报文发送到信令服务器,信令服务器就能将请求报文通过正确的客户端长连接转发到终端2)。
所以,任何实用的WebRTC系统,一定会有自己的信令服务器和控制命令,而且这部分的实现通常都是私有的,因为WebRTC标准本身就没有定义这些必要的控制命令。
下面这个草案,参考RTMP协议的推拉流URL规范: https://github.com/rtcdn/rtcdn-draft 定义了形如 webrtc://domain/会议ID/推流客户端ID的WebRTC推拉流URL。 以视频会议为例,不同会议之间的会议ID必须不同,同一个会议中不同客户端的推流ID必须不同。
同时,SRS4.0通过HTTP(S)服务对外提供了推流API接口(/rtc/v1/publish/)和拉流API接口(/rtc/v1/play/),下面代码是这两个API接口的注册逻辑。
srs_error_t SrsRtcServer::listen_api()
{
......
// 获取全局API管理对象SrsHttpServeMux
SrsHttpServeMux* http_api_mux = _srs_hybrid->srs()->instance()->api_server();
// 注册WebRTC拉流API对应的处理对象SrsGoApiRtcPlay
if ((err = http_api_mux->handle("/rtc/v1/play/", new SrsGoApiRtcPlay(this))) != srs_success) {
return srs_error_wrap(err, "handle play");
}
// 注册WebRTC推流API对应的处理对象SrsGoApiRtcPublish
if ((err = http_api_mux->handle("/rtc/v1/publish/", new SrsGoApiRtcPublish(this))) != srs_success) {
return srs_error_wrap(err, "handle publish");
}
return err;
}
3.2 处理推流API
WebRTC客户端通过推流API接口(/rtc/v1/publish/)向SRS服务器发送推流请求命令,此时SRS的通过如下处理流程,最终创建一个推流端接收对象SrsRtcPublishStream。
srs_error_t SrsGoApiRtcPublish::serve_http() { // 此函数为推流API的处理入口
do_serve_http(w, r, res); // 处理远端推流请求(包含客户端SDP信息),并构造请求响应
return srs_api_response(w, r, res->dumps()); // 向客户端发送请求响应(包含本端SDP信息)
}
srs_error_t SrsGoApiRtcPublish::do_serve_http() { // 处理远端推流请求,并构造请求响应
......
server_->create_session(&ruc, local_sdp, &session); // 创建会话对象和本端SDP信息
}
srs_error_t SrsRtcServer::create_session() {
......
// 参考WebRTC推拉流URL
// 以"/会议ID/推流客户端ID"字符串为Key,为每个推流端创建一个对应的SrsRtcSource对象
_srs_rtc_sources->fetch_or_create(req, &source);
// 为每个推流端创建SrsRtcConnection类型的session对象
SrsRtcConnection* session = new SrsRtcConnection(this, cid);
do_create_session(ruc, local_sdp, session); //
}
srs_error_t SrsRtcServer::do_create_session(SrsRtcUserConfig* ruc, SrsSdp& local_sdp, SrsRtcConnection* session){
if (ruc->publish_) {
session->add_publisher(ruc, local_sdp); // 为session添加推流端处理对象
}
session->initialize(); //
_srs_rtc_manager->add_with_name(username, session);// 以本地随机字符串ufrag+远端ufrag为Key,保存session对象
}
srs_error_t SrsRtcConnection::add_publisher(SrsRtcUserConfig* ruc, SrsSdp& local_sdp){
create_publisher(req, stream_desc);
}
srs_error_t SrsRtcConnection::create_publisher(SrsRequest* req, SrsRtcSourceDescription* stream_desc)
{
// 创建推流端处理对象SrsRtcPublishStream,并启动内部的SrsRtcPLIWorker协程
SrsRtcPublishStream* publisher = new SrsRtcPublishStream();
publisher->start();
}
SRS接收到用户发送的推流API(/rtc/v1/publish/)后,通过上面的函数调用栈,最终创建了SrsRtcConnection对象、SrsRtcPublishStream对象和SrsRtcSource对象。
C++音视频学习资料免费获取方法:关注音视频开发T哥,点击「链接」即可免费获取2023年最新C++音视频开发进阶独家免费学习大礼包!
3.3 处理拉流API
WebRTC客户端通过拉流API接口(/rtc/v1/play/)向SRS服务器发送拉流请求命令,此时SRS的通过如下处理流程,最终创建一个拉流端发送对象SrsRtcPlayStream。
srs_error_t SrsGoApiRtcPlay::serve_http() { // 此函数为拉流API的处理入口
do_serve_http(w, r, res); // 处理远端拉流请求(包含客户端SDP信息),并构造请求响应
return srs_api_response(w, r, res->dumps()); // 向客户端发送请求响应(包含本端SDP信息)
}
srs_error_t SrsGoApiRtcPlay::do_serve_http() { // 处理远端推流请求,并构造请求响应
server_->create_session(&ruc, local_sdp, &session); // 创建会话和本端SDP
}
srs_error_t SrsRtcServer::create_session() {
......
// 参考WebRTC推拉流URL
// 以"/会议ID/推流客户端ID"字符串为Key,为拉流端找到对应推流端的SrsRtcSource对象
_srs_rtc_sources->fetch_or_create(req, &source);
// 为每个拉流端创建SrsRtcConnection类型的session对象
SrsRtcConnection* session = new SrsRtcConnection(this, cid);
do_create_session(ruc, local_sdp, session); //
}
srs_error_t SrsRtcServer::do_create_session(SrsRtcUserConfig* ruc, SrsSdp& local_sdp, SrsRtcConnection* session)
{
if (ruc->publish_) {
......
} else {
session->add_player(ruc, local_sdp); // 为session添加拉流端处理对象
}
session->initialize(); //
_srs_rtc_manager->add_with_name(username, session);// 以本地随机字符串ufrag+远端ufrag为Key,保存session对象
}
srs_error_t SrsRtcConnection::add_player(SrsRtcUserConfig* ruc, SrsSdp& local_sdp){
create_player(req, play_sub_relations);// 创建拉流端处理对象SrsRtcPlayStream
}
srs_error_t SrsRtcConnection::create_player(SrsRequest* req, std::map<uint32_t, SrsRtcTrackDescription*> sub_relations){
// 创建拉流端处理对象SrsRtcPlayStream,并启动拉流端处理协程
SrsRtcPlayStream* player = new SrsRtcPlayStream();
player->start();
}
srs_error_t SrsRtcPlayStream::cycle(){ // 拉流端处理协程
source->create_consumer(consumer); // 为每个拉流端创建SrsRtcConsumer消费者对象
while (true) {
consumer->dump_packet(&pkt); // 在SrsRtcConsumer消费者队列中等待并获取报文
if (!pkt) {
consumer->wait(mw_msgs);
continue;
}
send_packet(pkt);// 将SrsRtcConsumer消费者队列中的报文发送的拉流客户端
}
}
SRS接收到用户发送的拉流API(/rtc/v1/play/)后,通过上面的函数调用栈,最终创建了SrsRtcConnection对象、SrsRtcPlayStream对象和SrsRtcConsumer对象。
3.4 SDP交换与ICE建立连接
上面的过程只创建了针对WebRTC服务的关键对象,接下来需要分析,推拉流客户端与WebRTC服务的监听端口(8000)之间如何建立连接。WebRTC客户端与服务端之间的连接建立方式采用了类P2P私网穿透的方式。这种方式的一个最大特点就是一个WebRTC客户端向服务端发起连接请求时,事先并不知道服务端的IP地址和端口号,所以WebRTC连接建立一般包括两个阶段:
1)WebRTC客户端与服务端之间以offer和answer的方式交换包含各自IP地址+端口号信息的SDP(Session Description Protocol)报文。
2)WebRTC客户端从服务端SDP报文中获取服务端的IP地址和端口号,并以ICE(Interactive Connectivity Establishment)方式,在客户端和服务端之间建立连接,用于后续音视频数据的传输。 网上关于SDP和ICE的资料比较多,可根据需要学习、参考
浜戣绠? - WebRTC SDP 璇﹁В鍜屽墫鏋? - 闃块噷浜戣棰戜簯 - SegmentFault 鎬濆惁 WebRTC SDP 详解和剖析
html5 - WebRTC浼氳瘽鎻忚堪鍗忚锛圫DP锛夎瑙? - 涓汉鏂囩珷 - SegmentFault 鎬濆惁 WebRTC会话描述协议(SDP)详解
WebRTC 之ICE浅谈 | 内有干货免费下载 - 知乎 WebRTC 之ICE浅谈
下面是浏览器发送给SRS服务器的offer SDP,因为是trickle模式,所以SDP中没有包含客户端的IP地址,当然这并不影响最终的连接建立。
v=0
o=- 6308787264381624235 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0 1
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126
a=ice-options:trickle
a=sendonly
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 121 127 120 125 107 108 109 35 36 124 119 123
a=ice-options:trickle
a=sendonly
SRS服务端响应的answer SDP,其中candidate属性包含了SRS服务器的IP地址和端口描述信息(192.168.9.102 8000),并且服务端采用ice-lite模式简化了ICE协商过程。
v=0
o=SRS/4.0.140(Leo) 32138128 2 IN IP4 0.0.0.0
s=SRSPublishSession
t=0 0
a=ice-lite
a=group:BUNDLE 0 1
m=audio 9 UDP/TLS/RTP/SAVPF 111
a=recvonly
a=candidate:0 1 udp 2130706431 192.168.9.102 8000 typ host generation 0
m=video 9 UDP/TLS/RTP/SAVPF 125 124
a=recvonly
a=candidate:0 1 udp 2130706431 192.168.9.102 8000 typ host generation 0
接下来,浏览器向SRS服务器的8000端口发送一个Binding Request报文,服务器给浏览器回一个Binding Success Response响应。最终,推拉流客户端与SRS服务器(8000端口)建立连接。
后续,客户端和服务器之间将在此连接上完成DTLS校验,并进行音视频RTP报文的传输。
4、总结:
SRS4.0 WebRTC模块整体架构和处理流程是:
1)监听UDP端口(默认8000),并注册推流API接口(/rtc/v1/publish/)和拉流API接口(/rtc/v1/play/)。
2)推流端处理逻辑创建SrsRtcConnection对象、SrsRtcPublishStream对象和SrsRtcSource对象; ???拉流端处理逻辑创建SrsRtcConnection对象、SrsRtcPlayStream对象和SrsRtcConsumer对象。
3)推拉流客户端与SRS服务器之间通过SDP交换,采用ICE方式建立UDP连接,完成DTLS安全协商。
4)最终,音视频数据从推流客户端到拉流客户端的数据流向如下图所示: ??a、推流客户端–>服务器8000端口–>SrsRtcConnection–>SrsRtcPublishStream–>SrsRtcSource–>SrsRtcConsumer数据队列 ??b、SrsRtcConsumer数据队列—>SrsRtcPlayStream::cycle()协程获取数据—>拉流客户端
原文链接:9、SRS4.0源代码分析之WebRTC服务总体介绍_srs webrtc_黑板报的博客-CSDN博客
- 上一篇:常见API接口渗透测试流程
- 下一篇:仓颉语言一行代码开发一个web服务
相关推荐
- 如何在HTML中使用JavaScript:从基础到高级的全面指南!
-
“这里是云端源想IT,帮你...
- 推荐9个Github上热门的CSS开源框架
-
大家好,我是Echa。...
- 硬核!知网首篇被引过万的论文讲了啥?作者什么来头?
-
整理|袁小华近日,知网首篇被引量破万的中文论文及其作者备受关注。知网中心网站数据显示,截至2021年7月23日,由华南师范大学教授温忠麟等人发表在《心理学报》2004年05期上的学术论文“中介效应检验...
- 为什么我推荐使用JSX开发Vue3_为什么用vue不用jquery
-
在很长的一段时间中,Vue官方都以简单上手作为其推广的重点。这确实给Vue带来了非常大的用户量,尤其是最追求需求开发效率,往往不那么在意工程代码质量的国内中小企业中,Vue占据的份额极速增长...
-
- 【干货】一文详解html和css,前端开发需要哪些技术?
-
网站开发简介...
-
2025-02-20 18:34 yuyutoo
- 分享几个css实用技巧_cssli
-
本篇将介绍几个css小技巧,目录如下:自定义引用标签的符号重置所有标签样式...
- 如何在浏览器中运行 .NET_怎么用浏览器运行代码
-
概述:...
- 前端-干货分享:更牛逼的CSS管理方法-层(CSS Layers)
-
使用CSS最困难的部分之一是处理CSS的权重值,它可以决定到底哪条规则会最终被应用,尤其是如果你想在Bootstrap这样的框架中覆盖其已有样式,更加显得麻烦。不过随着CSS层的引入,这一...
-
- HTML 基础标签库_html标签基本结构
-
HTML标题HTML标题(Heading)是通过-...
-
2025-02-20 18:34 yuyutoo
- 前端css面试20道常见考题_高级前端css面试题
-
1.请解释一下CSS3的flexbox(弹性盒布局模型),以及适用场景?display:flex;在父元素设置,子元素受弹性盒影响,默认排成一行,如果超出一行,按比例压缩flex:1;子元素设置...
- vue引入外部js文件并使用_vue3 引入外部js
-
要在Vue中引入外部的JavaScript文件,可以使用以下几种方法:1.使用``标签引入外部的JavaScript文件。在Vue的HTML模板中,可以直接使用``标签来引入外部的JavaScrip...
- 网页设计得懂css的规范_html+css网页设计
-
在初级的前端工作人员,刚入职的时候,可能在学习前端技术,写代码不是否那么的规范,而在工作中,命名的规范的尤为重要,它直接与你的代码质量挂钩。网上也受很多,但比较杂乱,在加上每年的命名都会发生一变化。...
- Google在Chrome中引入HTML 5.1标记
-
虽然负责制定Web标准的WorldWideWebConsortium(W3C)尚未宣布HTML5正式推荐规格,而Google已经迁移到了HTML5.1。即将发布的Chrome38将引入H...
- HTML DOM 引用( ) 对象_html中如何引用js
-
引用对象引用对象定义了一个同内联元素的HTML引用。标签定义短的引用。元素经常在引用的内容周围添加引号。HTML文档中的每一个标签,都会创建一个引用对象。...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)