这篇文章是对mediasoup代码分析的一个总结,总结的相对简单,仅是对学习过程的一个记录,更详细的介绍请参考文末参考文章的介绍
总体框架
mediasoup总体的架构分为两个层次,nodejs层和C++层。
nodejs负责信令处理和房间管理,信令部分主要是入会、离会、SDP协商、连接通道建立等,房间管理nodejs是用的protoo,不熟悉的朋友墙裂建议阅读下protoo,protoo已经为我们实现了room、peer的建立、管理等操作,还可以在peer上注册自定义事件,mediasoup很多信令(join、createWebrtcTransport、produce)都是建立在peer上注册事件,理解protoo框架是理解信令流程的基础。
nodejs和worker(C++)之间是主进程和子进程的关系,两者直接通过pipe的方式进行通信,通信协议为json,所有的通信指令定义均在ChannelRequest.cpp中定义
整体的交互流程是,client与server先通过createWebrtcTransport进行sdp协商,协商好音视频传输通道,后续的音视频传输均是在这个通道上进行,真正的音视频rtp均是发送给worker,和nodejs层没有关系
信令流程
connectionrequest
getOrCreateRoom
创建protoo.room
mediasoupWorker.createRouter-->触发c++层面创建router,可以将router理解为一个room
mediasoupRouter.createAudioLevelObserver--->触发c++层面创建AudioLevelObserver
Router::HandleRequest
在构造函数中会创建定时器timer,AudioLevelObserver会定期从producer中的rtp包中获取音量信息,
获取完音量信息后,会定期向应用层上报音量信息
关键函数有:AudioLevelObserver::ReceiveRtpPacket、AudioLevelObserver::Update()
room.handleProtooConnection
创建protoo peer
注册peer request和close事件,后续所有事件都会触发peer request相对应的回调函数进行处理
在request中,有重要的getRouterRtpCapabilities、createWebRtcTransport、connectWebRtcTransport、join、produce等事件
router.createDirectTransport--和bot相关
transport.produceData--和bot相关
peer.getRouterRtpCapabilities
peer.createWebRtcTransport(收)
router.createWebRtcTransport--生成server的sdp信息返回给client
创建子类webrtctransport对象,同时创建基类构造函数,设定定时器,此定时器用于周期发送SR和RR
Transport::OnTimer、Transport::OnTimer
WebRtcTransport::SendRtcpCompoundPacket
在WebRtcTransport构造函数中,创建对应的监听socket,监听ip是nodejs传递过来的,port随机选择,创建对应的candidate,用于后续ice协商
创建ice server,传递ice-ufrag和ice-pwd用于后续的身份校验
创建dtls transport对象
ice和dtle会整理为服务的sdp,返回给client
transport.enableTraceEvent
c++层面Transport::EmitTraceEventBweType,收集带宽估计相关数据,上报到nodejs,
然后nodejs再通过peer.notify的downlinkBwe事件,将信息发送给client
transport.setMaxIncomingBitrate
为带宽估计设置接收的最大码率
peer.createWebRtcTransport(发)
router.createWebRtcTransport
transport.enableTraceEvent
transport.setMaxIncomingBitrate
peer.join
在nodejs层面做的事情
1.为之前已经入会peer的produce创建当前即将入会peer的对应的consumer,注意此处是为每个product都会创建对应的consumer,包括不同的音频和视频produce
2.每创建一个consume,都需要向客户端发送newConsumer消息
3.同时向其它之前已经入会的peer发送newPeer消息
nodejs--->c++
1.transport.consume,nodejs通知c++创建consumer,注意每个consumer都会通知创建
根据consumer的类型(SIMPLE、SIMULCAST、SVC、PIPE)创建对应的consume对象,
创建rtpstream,用于发送rtp包
通知router,建立consumer和producer映射关系
创建tccClient,用于带宽估计
2.consumer.resume,nodejs通知c++启动媒体流的传输
SimpleConsumer::RequestKeyFrame()--->Transport::OnConsumerKeyFrameRequested-->Router::OnTransportConsumerKeyFrameRequested
---->RtpStreamRecv::RequestKeyFrame()-->OnRtpStreamSendRtcpPacket,通过fir或pli索要关键帧
peer.connectWebRtcTransport--大致流程应该是搞ssl握手协商的
transport.connect
get dtlsRemoteFingerprint
get dtlsRemoteRole
WebRtcTransport::MayRunDtlsTransport
DtlsTransport::Run
CLIENT
SSL_do_handshake
SendPendingOutgoingDtlsData
SERVER
SSL_set_accept_state
SSL_do_handshake
peer.connectWebRtcTransport
transport.connect
peer.produce(音频)
transport.produce--c++层面创建produce
发送端创建音频producer,在request上会带上客户端的rtpMapping、rtpParameters
rtpObserver.addProducer建立producer到AudioLevelObserver映射
创建Producer对象后,通过OnTransportNewProducer()函数告诉Router有Producer创建。
创建tccServer,用于带宽估计
为其它已经入会peer创建对应当前producer的consumer,这个流程和上面join时创建consumer是一样的
peer.produce(视频)
同音频
peer.produceData
transport.produceData
peer.produceData
transport.produceData
transport.consumeData
<注>下面这些consumer的事件都是nodejs和c++之间的内部事件,不是client主动发起的,都是由join或produce事件驱动而来
transport.consume
创建接收端的音频consumer
transport.consume
创建接收端的视频consumer
Transport::HandleRequest--创建consumer对象
Router::OnTransportNewConsumer--通知router,建立produce和consumer的映射关系
consumer.resume
启动音频流的传输
consumer.resume
启动视频流的传输
<注>,这里面transport是2个,一个用于发送,一个用于接收,但是produce或consume是多个,取决于发送几个音视频流,接收几个音视频流
<注>, mediasoup中的所有数据全部都需要加密,普通文本信息通过DTLS加密,而音视频数据通过libsrtp加密。
收媒体包代码流程
UdpSocket::UserOnUdpDatagramReceived
WebRtcTransport::OnUdpSocketPacketReceived
WebRtcTransport::OnPacketReceived,根据数据包类型,分别进行不同的处理
OnStunDataReceived
IceServer::ProcessStunPacket
OnRtcpDataReceived
Transport::ReceiveRtcpPacket
Transport::HandleRtcpPacket
--由此看出mediasoup支持的RTCP报文有:RR、SR、SDES、BYE、XR、PSFB、RTPFB,
--其中PSFB包括PLI、FIR、AFB,
--其中AFB报文是WebRTC自己扩展的报文,用于remb算法;
--RTPFB又包括NACK和TCC,其中TCC报文是WebRTC自己扩展的报文,用于google TCC算法
rr--送到对应的consumer处理
sr--送到对应的producer处理
pli--
SimpleConsumer::ReceiveKeyFrameRequest
RtpStreamSend::ReceiveKeyFrameRequest,统计计数+1
SimpleConsumer::RequestKeyFrame()--通过transport--router--找到对应的producer,向producer发送请求关键帧请求
Transport::OnConsumerKeyFrameRequested
Router::OnTransportConsumerKeyFrameRequested
Producer::RequestKeyFrame
nack--
SimpleConsumer::ReceiveNack
RtpStreamSend::ReceiveNack
SimpleConsumer::OnRtpStreamRetransmitRtpPacket --先解析出NACK请求中的RTP序号,
--然后从缓存中找出所有需要重传的RTP包。
--将找出的所有重传RTP包,通过consumer将这些RTP再次发送给客户端。
Transport::OnConsumerRetransmitRtpPacket
WebRtcTransport::SendRtpPacket
OnRtpDataReceived--对srtp进行解密,将二进制字节流data解析成标准rtp packet,将rtp packet传递给基类transport
Transport::ReceiveRtpPacket
Producer::ReceiveRtpPacket
Transport::OnProducerRtpPacketReceived
Router::OnTransportProducerRtpPacketReceived
--rtp传递给router,由router进行路由,传递给对应的consumer
--调用对应的音量观察者,对音量进行统计并上报应用层
SimpleConsumer::SendRtpPacket
Transport::OnConsumerSendRtpPacket
WebRtcTransport::SendRtpPacket--对数据进行加密,加密完通过ice server将包发送出去
OnDtlsDataReceived
DtlsTransport::ProcessDtlsData
--收到的DTLS数据包,可能是握手数据包,也可能是Sctp数据包。
--如果是后者,将数据写入BIO后,让dtls解密接收到的数据,
--然后通过SSL_read()获取解密后的数据,最后通过OnDtlsTransportApplicationDataReceived函数将解密后的数据送至WebRtcTransport。
WebRtcTransport::OnDtlsTransportApplicationDataReceived
Transport::ReceiveSctpData
SctpAssociation::ProcessSctpData
发媒体包代码流程
WebRtcTransport::SendRtpPacket
this->iceServer->GetSelectedTuple()->Send(data, len, cb);------>TransportTuple::Send
信令收包代码流程:
Worker::OnChannelRequest
信令发包代码:
ChannelRequest::Accept
参考文章:
mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_大话音视频的博客-CSDN博客_mediasoup 源码分析mediasoup流程图_wzw88486969的博客-CSDN博客