百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

SRS4.0源代码分析之WebRTC服务总体介绍

yuyutoo 2024-12-06 20:39 3 浏览 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博客



相关推荐

墨尔本一华裔男子与亚裔男子分别失踪数日 警方寻人

中新网5月15日电据澳洲新快网报道,据澳大利亚维州警察局网站消息,22岁的华裔男子邓跃(Yue‘Peter’Deng,音译)失踪已6天,维州警方于当地时间13日发布寻人通告,寻求公众协助寻找邓跃。华...

网络交友须谨慎!美国犹他州一男子因涉嫌杀害女网友被捕

伊森·洪克斯克(图源网络,侵删)据美国广播公司(ABC)25日报道,美国犹他州一名男子于24日因涉嫌谋杀被捕。警方表示,这名男子主动告知警局,称其杀害了一名在网络交友软件上认识的25岁女子。雷顿警...

一课译词:来龙去脉(来龙去脉 的意思解释)

Mountainranges[Photo/SIPA]“来龙去脉”,汉语成语,本指山脉的走势和去向,现比喻一件事的前因后果(causeandeffectofanevent),可以翻译为“i...

高考重要考点:range(range高考用法)

range可以用作动词,也可以用作名词,含义特别多,在阅读理解中出现的频率很高,还经常作为完形填空的选项,而且在作文中使用是非常好的高级词汇。...

C++20 Ranges:现代范围操作(现代c++白皮书)

1.引言:C++20Ranges库简介C++20引入的Ranges库是C++标准库的重要更新,旨在提供更现代化、表达力更强的方式来处理数据序列(范围,range)。Ranges库基于...

学习VBA,报表做到飞 第二章 数组 2.4 Filter函数

第二章数组2.4Filter函数Filter函数功能与autofilter函数类似,它对一个一维数组进行筛选,返回一个从0开始的数组。...

VBA学习笔记:数组:数组相关函数—Split,Join

Split拆分字符串函数,语法Split(expression,字符,Limit,compare),第1参数为必写,后面3个参数都是可选项。Expression为需要拆分的数据,“字符”就是以哪个字...

VBA如何自定义序列,学会这些方法,让你工作更轻松

No.1在Excel中,自定义序列是一种快速填表机制,如何有效地利用这个方法,可以大大增加工作效率。通常在操作工作表的时候,可能会输入一些很有序的序列,如果一一录入就显得十分笨拙。Excel给出了一种...

Excel VBA入门教程1.3 数组基础(vba数组详解)

1.3数组使用数组和对象时,也要声明,这里说下数组的声明:'确定范围的数组,可以存储b-a+1个数,a、b为整数Dim数组名称(aTob)As数据类型Dimarr...

远程网络调试工具百宝箱-MobaXterm

MobaXterm是一个功能强大的远程网络工具百宝箱,它将所有重要的远程网络工具(SSH、Telnet、X11、RDP、VNC、FTP、MOSH、Serial等)和Unix命令(bash、ls、cat...

AREX:携程新一代自动化回归测试工具的设计与实现

一、背景随着携程机票BU业务规模的不断提高,业务系统日趋复杂,各种问题和挑战也随之而来。对于研发测试团队,面临着各种效能困境,包括业务复杂度高、数据构造工作量大、回归测试全量回归、沟通成本高、测试用例...

Windows、Android、IOS、Web自动化工具选择策略

Windows平台中应用UI自动化测试解决方案AutoIT是开源工具,该工具识别windows的标准控件效果不错,但是当它遇到应用中非标准控件定义的UI元素时往往就无能为力了,这个时候选择silkte...

python自动化工具:pywinauto(python快速上手 自动化)

简介Pywinauto是完全由Python构建的一个模块,可以用于自动化Windows上的GUI应用程序。同时,它支持鼠标、键盘操作,在元素控件树较复杂的界面,可以辅助我们完成自动化操作。我在...

时下最火的 Airtest 如何测试手机 APP?

引言Airtest是网易出品的一款基于图像识别的自动化测试工具,主要应用在手机APP和游戏的测试。一旦使用了这个工具进行APP的自动化,你就会发现自动化测试原来是如此简单!!连接手机要进行...

【推荐】7个最强Appium替代工具,移动App自动化测试必备!

在移动应用开发日益火爆的今天,自动化测试成为了确保应用质量和用户体验的关键环节。Appium作为一款广泛应用的移动应用自动化测试工具,为测试人员所熟知。然而,在不同的测试场景和需求下,还有许多其他优...

取消回复欢迎 发表评论: