mediasoup源码分析-初始化、建立连接及媒体数据的处理流程

mediasoup中的Transport有多种类型,以下只分析WebRtcTransport,且只分析音频、视频的传输。

信令文件下载:链接: https://pan.baidu.com/s/1IhSGl3k82nkJZl_QTbdOJw 提取码: jmkr

线程模型

mediasoup使用的是多进程模型,服务器上有多个少CPU核,就启动多少个Worker进程。每个Worker进程采用单线程,运行一个事件循环,所有数据的处理,都是通过事件触发的。

MediaSoup的应用层和媒体处理层分别运行在不同的进程内,它们之间通过管道进行通信。

注意:应用进程和Worker进程之间可以使用普通的管道,也可以使用socketpair,这个部分的代码应该在JS部分。

mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第1张图片

两个客户端通过mediasoup进行视频通话时,此时的mediasoup内部结构如下图:

mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第2张图片

Worker与libuv

每个Worker进程内,都会运行libuv,Worker进程对所有数据的处理,都是基于libuv的事件循环,当有数据到达时,会触发libuv中的事件,根据事件预先注册的回调函数,对数据进行相应的处理,最后再通过libuv将数据结果发送出去。

mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第3张图片

使用libuv收发网络数据

mediasoup可以使用UDP收发数据,也可以使用TCP。对于音视频数据,更多的采用的是UDP。

下面分析mediasoup是如何对libuv的UDP功能进行封装的。

mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第4张图片

RTC::UdpSocket对象是对libuv的封装,需要通过libuv发送数据时,通过调用其父类::UdpSocket::Send()函数,将要发送的数据交由libuv发送。

WebRtcTransport是RTC::UdpSocket::Listener的订阅者,WebRtcTransport类内覆写了OnUdpSocketPacketReceived()虚函数,当libuv收到数据时,会通过这个虚函数将收到的数据送至WebRtcTransport。

接收数据流程如下:

mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第5张图片
注意:mediasoup中有两个同名的类UdpSocket,但一个位于全局匿名空间::中,另一个位于RTC命名空间中,所以::UdpSocket和RTC::UdpSocket是两个不同的类。

发送数据流程如下:

mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第6张图片

使用libuv收发应用层信令

Worker进程是应用层进程的子进程,它们通过管道进行通信。在传递信令时,使用了json进行序列化和反序列。

mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第7张图片

Channel::UnixStreamSocket类是libuv对管道的封装,可以通过Channel::UnixStreamSocket::Send()函数向管道中写入数据,调用Send()函数时,写入的数据是Json对象。

Worker作为Channel::UnixStreamSocket的订阅者,通过覆写OnChannelRequest()虚函数。libuv从管道收到数据后,会将数据解析生成Request对象,然后通过这个虚函数发送给Worker类。

接收数据流程如下:

mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第8张图片

管道中的数据是流式的,需要处理粘包。处理粘包后的数据是字符串,需要解析为json对象。最后还需要将Channel::UnixStreamSocket和json对象封装为Requst对象,通过Request对象可以方便的发送处理后的结果。

应用层进程发送过来的request,Worker进程处理后,需要对应的回复一个response。这个response可以携带有效的数据,也可以仅仅回复"accepted"表示request处理成功。为了将request和response一一对应,它们内部都会有一个id字段,用于它们之间的一一对应。

发送数据流程如下:

在这里插入图片描述

应用层发送的request最后被封装在Requst对象中,其中包含着"id",因为Request对象中包含着Channel::UnixStreamSocket对象,所以可以直接调用Request::Accept()将处理后的结果告诉应用层进程。

Worker进程也可以主动给应用层进程发送消息,通过Notifier::Emit()即可以给应用进程发送消息,Notifier类中有Channel::UnixStreamSocket,所以直接调用Channel::UnixStreamSocket::Send()就可以发送消息。Notifier类内部的数据成员和函数成员都是静态的,所以在任意位置可以直接通过Channel::Notifier::Emit()函数发送消息。

mediasoup初始化过程

在运行mediasoup demo的时候,我将JS进程与Worker进程之间的信令保存了下来,现在根据JS进程与Worker进程之间的信令,分析一下mediasoup与客户端建立连接的流程。

完整的request和response,在这里下载:

在demo中,音视频是相互的,为了方便分析,现在假设只有一端在发送音视频,另一端只接收音视频。

简化后的结构如下图:

mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第9张图片

创建Router

在以下的分析中,我会给出我运行mediasoup demo时的信令。

注意以下给出的信令,都是有删减的,完整的信令可以在上面给出的链接中下载。

request

method:worker.createRouter ,id:1    #id:信令的编号是1
data : {}

internal : {
    "routerId": "ab8baa18-e002-44d7-a6e2-dc298587dd8b"   #指定Router的id
}

response

{
    "accepted": true,
    "id": 1     #指明是对哪条信令的回复
}

从上面的分析,我们可以知道应用层发送过来的信令,经过libuv的处理,会被送至Worker::OnChannelRequest()函数。其实应用层发送的所有信令,都会被送至这里。如果这个函数无法处理发送过来的信令,则会将信令转发给Router,若Router也无法识别信令,则将信令继续往下转发。

void Worker::OnChannelRequest(Channel::UnixStreamSocket* /*channel*/, Channel::Request* request)
{
	...
     switch (request->methodId){
         ...
         /*创建一个router*/
		case Channel::Request::MethodId::WORKER_CREATE_ROUTER:{
			std::string routerId;
			// This may throw.     获取router的id
			SetNewRouterIdFromRequest(request, routerId);

			/*创建Router*/
			auto* router = new RTC::Router(routerId);

			/*将新创建的router保存下来*/
			this->mapRouters[routerId] = router;

			/*发送消息告诉nodejs,request已经成功处理。*/
			request->Accept();
			break;
		}
         ...
     }
}

method:worker.createRouter会指明信令是做什么的,id是这条指令的编号,在回复这条指令时,也需要指明id,这样应用层才知道这是哪条信令的应答。

这里的Router相当于Room,也就是一个房间。Worker进程首先收到的信令是,创建一个房间,同时指定了Router的id是ab8baa18-e002-44d7-a6e2-dc298587dd8b,用于唯一的标识这个房间。

一个Worker中可以有多个Router,Worker内部使用map保存router id和Router实例对象之间的映射关系。

Router::Router(const std::string& id) 
	: id(id)   /*保存Router的id*/
{
		MS_TRACE();
}

创建Router时,仅仅是保存了自己对应的id。

创建AudioLevelObserver

request

method:router.createAudioLevelObserver ,id:2
data : {
    "interval": 800,  #用于检查音频音量的时间间隔(单位:毫秒)
    "maxEntries": 1,  #音量事件中最大的条目数 
    "threshold": -80  #音量事件中条目的最小平均音量(从-1270,单位:dBvo)
}

internal : {
    "routerId": "ab8baa18-e002-44d7-a6e2-dc298587dd8b",
    "rtpObserverId": "079b1620-3865-4c0b-8c0a-e6acba5af7c2"
}

response

{
    "accepted": true,
    "id": 2
}
void Worker::OnChannelRequest(Channel::UnixStreamSocket* /*channel*/, Channel::Request* request)
{
	...
     switch (request->methodId){
	...
		default:
		{
			//通过请求中的routerId解析出Router id
			RTC::Router* router = GetRouterFromRequest(request);

			/*将消息传递至相应的Router*/
			router->HandleRequest(request);

			break;
		}
     }
    ...
}       

当Worker无法识别给定的指令时,它会解析处指令的router id,并将这条指令送至对应的Router。

void Router::HandleRequest(Channel::Request* request)
{
...
		switch (request->methodId){
          ...      
         case Channel::Request::MethodId::ROUTER_CREATE_AUDIO_LEVEL_OBSERVER:
			{
				std::string rtpObserverId;

				//获取rtpObserver的id
				SetNewRtpObserverIdFromRequest(request, rtpObserverId);

				/*创建AudioLevelObserver对象*/
				auto* audioLevelObserver = new RTC::AudioLevelObserver(rtpObserverId, request->data);

				/*建立AudioLevelObserver id到AudioLevelObserver的映射*/
				this->mapRtpObservers[rtpObserverId] = audioLevelObserver;

                  /*回复处理成功*/
				request->Accept();
				break;
			}

mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第10张图片

在Router中创建AudioLevelObserver,同时保存AudioLevelObserver id到AudioLevelObserver对象的映射。

mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第11张图片

AudioLevelObserver::AudioLevelObserver(const std::string& id, json& data) 
		: RTC::RtpObserver(id)
{
	/* 解析应用层传入的参数
	 * interval   : 定时器的定时周期
	 * maxEntries : 监控的audio producer的最大条目数
	 * threshold  : 静音的阈值,音量小于此值则为静音。
	 */
            
    ...
    /*创建定时器,用于定期向应用层汇报统计的音量信息。*/
	this->periodicTimer = new Timer(this);
	this->periodicTimer->Start(this->interval, this->interval);
}

AudioLevelObserver用于检查 producer 收到音频的音量,并定期向应用层汇报producer的音量。

void AudioLevelObserver::ReceiveRtpPacket(RTC::Producer* producer, RTC::RtpPacket* packet)
{
	...
        
	/*从rtp包中读取volume*/
	if (!packet->ReadSsrcAudioLevel(volume, voice))
		return;

	/*获取该prodcuer的dBovs*/
	auto& dBovs = this->mapProducerDBovs.at(producer);

	/*统计所有的音量,并记录统计的次数。*/
	dBovs.totalSum += volume;
	dBovs.count++;
}

当audio producer收到RTP数据包时,会将数据包通过这个函数送至AudioLevelObserver。它会从RTP包中取出音量,记录音量值并增加计数。

void AudioLevelObserver::Update()
{
	...
	/*计算每个producer在这个时间段的平均音量*/
	for (auto& kv : this->mapProducerDBovs){
		... 
	}
    
	// DBovs全部重置为0,以便下次重新统计。
	ResetMapProducerDBovs();

	/*有高于阈值的producer,说明没有静音。*/
	if (!mapDBovsProducer.empty()){
		...	
		/*通知应用层,对应producer的音量是多少。*/
		Channel::Notifier::Emit(this->id, "volumes", data);
	}
	else if (!this->silence)    
	{
		...
		/*通知应用层,所有的producer都处于静音状态。*/
		Channel::Notifier::Emit(this->id, "silence");
	}
}

定时器会定期的执行这个函数,在这个函数中,会将这段时间内的统计信息,生成统计结果,然后上报应用层。

创建接收端WebRtcTransport

request

method:router.createWebRtcTransport ,id:5
data : {
    "enableTcp": false,       #不使用TCP
    "enableUdp": true,        #使用UDP
    
    #仅当consumer连接的接收端支持REMBTCC时,才使用。
    "initialAvailableOutgoingBitrate": 1000000,   #可用的输出码率
    
    "listenIps": [
        {
            "ip": "192.168.10.12"    #WebRtcTransport监听的ip地址
        }
    ],
    "preferTcp": false,   #穿墙时是否优先使用TCP
    "preferUdp": false    #穿墙时是否优先使用UDP
}

internal : {
    #WebRtcTransport对应router的id
    "routerId": "ab8baa18-e002-44d7-a6e2-dc298587dd8b",    
    #WebRtcTransport的id
    "transportId": "253c0380-359c-4102-9dce-c6db3a625944"
}

response

{
    "accepted": true,
    "data": {
        "consumerIds": [],
        "direct": false,
        "dtlsParameters": {     #dtls相关的信息
            "fingerprints": [   #服务器证书的哈希值
			   ...
                {
                    "algorithm": "sha-256",   #使用的哈希算法
                    "value": "42:F5:D7:5D:F0:96:68:66:B4:F7:B9:0F:13:DA:7F:CE:26:48:94:0E:55:03:C2:B7:FA:1D:0D:ED:EB:10:7D:29"        #证书的哈希值
                },
			   ...
            ],
            "role": "auto"     #DTLS中的角色
        },
        "dtlsState": "new",    #DTLS的状态
        "iceCandidates": [     #WebRtcTransport生成的candidate
            {
                "foundation": "udpcandidate",
                "ip": "192.168.10.12",
                "port": 43439,
                "priority": 1076302079,
                "protocol": "udp",
                "type": "host"
            }
        ],
        "iceParameters": {    #Ice Server的相关参数
            "iceLite": true,  #是否使用lite ice,与之对应的是Full ice。
            #以下两个参数用于ICE阶段,验证身份。
            "password": "apfkmkqlij8brze8zqohrd7e3kl0wwj9",  #ice-pwd
            "usernameFragment": "1ga7pj9kdinmeqxb"           #ice-ufrag
        },
        "iceRole": "controlled",    #ice的角色
        "iceState": "new",          #ice的状态
        "id": "253c0380-359c-4102-9dce-c6db3a625944", #WebRtcTransport的id
        "mapRtxSsrcConsumerId": {},
        "mapSsrcConsumerId": {},
        "producerIds": [],
        "recvRtpHeaderExtensions": {},
        "rtpListener": {
            "midTable": {},
            "ridTable": {},
            "ssrcTable": {}
        },
    },
    "id": 5
}
void Router::HandleRequest(Channel::Request* request)
{
    ...
    case Channel::Request::MethodId::ROUTER_CREATE_WEBRTC_TRANSPORT:
    {
        std::string transportId;

        /*获取transportId*/
        SetNewTransportIdFromRequest(request, transportId);

        /*创建WebRtcTransport*/
        auto* webRtcTransport = new RTC::WebRtcTransport(transportId, this, request->data);

        /*保存id到WebRtcTransport对象的映射*/
        this->mapTransports[transportId] = webRtcTransport;

        /*获取webRtcTransport的状态信息,将其发送给上层的Node.js应用程序。*/
        json data = json::object();
        webRtcTransport->FillJson(data);
        
        /*将生成的WebRtcTransport的相关信息,上报给应用进程。*/ 
        request->Accept(data);

        break;
    }
    ...

应用层发出创建WebRtcTransport的信令,最终这个信令在Router中被处理。Router会创建WebRtcTransport对象,同时保存id到WebRtcTransport对象的映射。生成WebRtcTransport对象后,Router会将其状态信息生成Response信令汇报给应用进程。
mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第12张图片
在这里插入图片描述

创建Transport
Transport::Transport(const std::string& id, Listener* listener, json& data)
{
    ...
    /*Sctp相关的设置*/
    ...
        
	/*创建rtcp计时器*/
	this->rtcpTimer = new Timer(this);
}

Transport是WebRtcTransport的父类,所以在创建WebRtcTransport的时候,需要先创建Transport。

在Transport的构造器中,大部分都是对Sctp的设置。Sctp主要用于传输文本数据,所以这里暂不分析。除了Sctp的设置,最重要的是创建了一定定时器,这个定时器用于定期发送Rtcp报文。

设置Transport定时器,定时发送RTCP报文。
inline void Transport::OnTimer(Timer* timer)
{
	if (timer == this->rtcpTimer){
		auto interval  = static_cast<uint64_t>(RTC::RTCP::MaxVideoIntervalMs);
		uint64_t nowMs = DepLibUV::GetTimeMs();

		/*发送SR和RR报文*/
		SendRtcp(nowMs);

		/*计算下一次的RTCP发送间隔*/
		if (!this->mapConsumers.empty()){
			...
		}

		interval *= static_cast<float>(Utils::Crypto::GetRandomUInt(5, 15)) / 10;

		/*设置下次的定时时间*/
		this->rtcpTimer->Start(interval);
	}
}

这是定时器触发时执行的虚函数,定时器触发后,先是发送RTCP报文,报文发送后,再计算下次的发送间隔时间,重新设置定时器。

void Transport::SendRtcp(uint64_t nowMs)
{
	std::unique_ptr<RTC::RTCP::CompoundPacket> packet{ nullptr };

    /*遍历所有的Consumer,向Consumer发送SR报文。*/
	for (auto& kv : this->mapConsumers){
		auto* consumer = kv.second;
		for (auto* rtpStream : consumer->GetRtpStreams()){
			packet.reset(new RTC::RTCP::CompoundPacket());

			/*生成consumer的SR报文*/
			consumer->GetRtcp(packet.get(), rtpStream, nowMs);
			if (packet->HasSenderReport()){
				packet->Serialize(RTC::RTCP::Buffer);
                 /*发送SR报文*/
				SendRtcpCompoundPacket(packet.get());
			}
		}
	}
	...
	/*遍历所有的Producer,向Producer发送SR报文。*/
	for (auto& kv : this->mapProducers){
		auto* producer = kv.second;
		/*生成Producer的RR报文*/
		producer->GetRtcp(packet.get(), nowMs);

		if (packet->GetSize() + sizeof(RTCP::ReceiverReport::Header) > RTC::MtuSize)
		{
			packet->Serialize(RTC::RTCP::Buffer);
             /*发送RR报文*/
			SendRtcpCompoundPacket(packet.get());
			packet.reset(new RTC::RTCP::CompoundPacket());
		}
	}
    ...
}

Consumer连接的是接收客户端,所以mediasoup需要定期向其发送SR报文。Producer连接的是发送客户端,所以mediasoup需要定期向其发送RR报文。

void WebRtcTransport::SendRtcpCompoundPacket(RTC::RTCP::CompoundPacket* packet)
{
    /*如果处于未连接状态,则直接返回。*/
	if (!IsConnected())
		return;
    
	const uint8_t* data = packet->GetData();
	size_t len = packet->GetSize();

	/*如果SRTCP未开启,则向日志打印警告信息。*/
	if (!this->srtpSendSession){
		...
	}

	/*对RTCP报文进行加密*/
	if (!this->srtpSendSession->EncryptRtcp(&data, &len))
		return;

	/*通过ice server发送数据*/
	this->iceServer->GetSelectedTuple()->Send(data, len);

	/*Transport统计发送数据相关信息*/
	RTC::Transport::DataSent(len);
}

SendRtcpCompoundPacket是虚函数,所以在Transport中调用的实际是WebRtcTransport中覆写的虚函数。

WebRtcTransport会将接收到的RTCP报文,使用libsrtp库进行加密,然后通过IceServer将RTCP报文发送至相应的客户端。

mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第13张图片

WebRtcTransport构造器
WebRtcTransport::WebRtcTransport(const std::string& id, RTC::Transport::Listener* listener, json& data)
{
	...
    /*先解析应用进程传入信令中的参数*/

	/*传输默认使用的是udp*/
	bool enableUdp{ true };
	...

	/*在穿墙的时候,udp的优先级高。*/
	bool preferUdp{ false };
	...

	/*在穿墙的时候,tcp的优先级高。*/
	bool preferTcp{ false };
	...

	/*WebRtcTransport可以使用的监听ip地址*/
	std::vector<ListenIp> listenIps(jsonListenIpsIt->size());
	...

	/*为每个可以监听的ip地址,生成candidate。*/
	for (auto& listenIp : listenIps)
	{
		if (enableUdp){
			/*设置udp的优先级*/
			uint16_t iceLocalPreference = IceCandidateDefaultLocalPriority - iceLocalPreferenceDecrement;

			if (preferUdp)   /*如果优先考虑udp,则udp的candidate优先级高一点。*/
				iceLocalPreference += 1000;

			/*计算candidate的优先级*/
			uint32_t icePriority = generateIceCandidatePriority(iceLocalPreference);

			/*生成UdpSocket对象,建立监听socket。*/
			auto* udpSocket = new RTC::UdpSocket(this, listenIp.ip);

			/*建立UdpSocket到announce ip的映射*/
			this->udpSockets[udpSocket] = listenIp.announcedIp;

			/*生成candidate,并保存。*/
			if (listenIp.announcedIp.empty())
				this->iceCandidates.emplace_back(udpSocket, icePriority);
			else
				this->iceCandidates.emplace_back(udpSocket, icePriority, listenIp.announcedIp);
		}

		/*按照ip给出的顺序,优先级依次递减。*/
		iceLocalPreferenceDecrement += 100;
	}

	/*创建IceServer    通过GetRandomString生成ice-ufrag和ice-pwd*/
	this->iceServer = new RTC::IceServer(this, Utils::Crypto::GetRandomString(16), Utils::Crypto::GetRandomString(32));

	/*创建DtlsTransport*/
	this->dtlsTransport = new RTC::DtlsTransport(this);
}

在WebRtcTransport的构造器中,先解析应用层信令中的参数,根据应用层设置的监听ip地址,创建对应的socket,同时生成对应的candidate,以用于之后的ICE。

最后创建了用于之后ICE的IceServer对象,和DtlsTransport对象。在创建IceServer对象时,使用GetRandomString生成ice-ufrag和ice-pwd。这个两个参数主要用于在ICE时进行身份验证。

此时WebRtcTransport已经创建了对应的socket,并且处于监听状态了(LISTEN状态),ip为应用层指定的ip,而port是指定范围内的随机值端口。这个port的范围可以在应用层配置。

IceServer的创建
IceServer::IceServer(Listener* listener, const std::string& usernameFragment, const std::string& password)
	  : listener(listener), usernameFragment(usernameFragment), password(password)
{
		MS_TRACE();
}

IceServer主要用于ICE,建立与客户端的连接。在一般的WebRTC中,端与端的建立是通过Full ICE建立连接的。而mediasoup使用的是lite ICE,所以在ICE中,mediasoup角色为controlled,而客户端角色为controlling

ICE的模式:

Full ice:双方都要进行连通性检查,完整的走一遍流程。

Lite ice:在FULL ice和lite ICE互通时,只需需要FULL ice一方进行连通性检查,Lite一方只需要回应reponse消息。这种模式对于部署在公网到的设备比较常用。

Lite ice只接收binding request请求并回复,不会主动发送binding reque请求给对方。

Full ice既可以接收bindingrequest,也可以发送binding request。

在创建IceServer时,需要生成ice-ufrag和ice-pwd,这两个参数将被应用层进程通过信令传给客户端,客户端在与mediasoup的ICE过程中,用于客户端的身份是否合法。

DTLSTransport的创建
DtlsTransport::DtlsTransport(Listener* listener)
	: listener(listener)
{
	/*基于ctx产生一个新的SSL*/
	this->ssl = SSL_new(DtlsTransport::sslCtx);

	/*在ssl中设置用户的私有数据,即this指针。*/
	SSL_set_ex_data(this->ssl, 0, static_cast<void*>(this));

	/*给read生成一个BIO mem*/
	this->sslBioFromNetwork = BIO_new(BIO_s_mem());

	/*给write生成一个BIO mem*/
	this->sslBioToNetwork = BIO_new(BIO_s_mem());

	/*将生成的read和write的Bio mem关联到ssl上*/
	SSL_set_bio(this->ssl, this->sslBioFromNetwork, this->sslBioToNetwork);

	/*每次发包大小小于MTU,这样IP就不用分片了。*/
	SSL_set_mtu(this->ssl, DtlsMtu);
	DTLS_set_link_mtu(this->ssl, DtlsMtu);

	/*设置超时处理回调函数*/
	DTLS_set_timer_cb(this->ssl, onSslDtlsTimer);

	/*设置定时器*/
	this->timer = new Timer(this);

	return;
}

在socket层与客户端建立连接后,DTLSTransport可以用于获取libsrtp使用的密钥套件信息。在数据传输过程中,Sctp data的加密和解密都由DTLSTransport负责。

int main(int argc, char* argv[])
{
    ...
    RTC::DtlsTransport::ClassInit(); 
    ...
}

在Woker进程的main函数中,这个函数会调用DtlsTransport::ClassInit()用于初始化dtls相关的信息。

void DtlsTransport::ClassInit()
{
	/*如果没有提供证书和私钥,则自己生成私钥和证书。*/
	if (Settings::configuration.dtlsCertificateFile.empty() || Settings::configuration.dtlsPrivateKeyFile.empty()){
		GenerateCertificateAndPrivateKey();        /*自己生成证书和私钥*/
	}else{
		ReadCertificateAndPrivateKeyFromFiles();   /*根据位置信息,读取证书和私钥*/
	}

	/*创建全局的SSL_CTX*/
	CreateSslCtx();

	/*生成certificate的fingerprint*/
	GenerateFingerprints();
}

如果在mediasoup的配置文件中,没有配置了证书和私钥,则会通过算法给mediasoup生成证书和私钥。

有了证书后,需要给证书使用多种哈希算法生成指纹,生成的这些指纹会通过信令传送给客户端。在客户端和服务端Dtls握手过程中,用于客户端验证服务端的证书是否合法。信令是使用ssl进行加密的,所以传送服务端证书指纹是安全的,客户端有了服务端证书的指纹,就可以验证服务端证书是否有效了。

生成WebRtcTransport后,向应用汇报信息。

WebRtcTransport创建完毕后,需要将相关的信息汇报给应用层。

在Router中,先是通过WebRtcTransport::FillJson()生成相应的信息,之后通过管道发送给应用进程。

void WebRtcTransport::FillJson(json& jsonObject) const
{
	...
	/*设置ice的角色为 controlled*/
	jsonObject["iceRole"] = "controlled";

	/*设置本端的ice-ufrag和ice-pwd*/
	(*jsonIceParametersIt)["usernameFragment"] = this->iceServer->GetUsernameFragment();
	(*jsonIceParametersIt)["password"] = this->iceServer->GetPassword();

	/*ice的模式为lite*/
	(*jsonIceParametersIt)["iceLite"] = true;

	/*填写candidate的信息,若设置了多个监听ip,则有多个candidate。*/
	for (size_t i{ 0 }; i < this->iceCandidates.size(); ++i){
		jsonIceCandidatesIt->emplace_back(json::value_t::object);
		...
		iceCandidate.FillJson(jsonEntry);    /*获取candidate信息*/
	}

	/*获取ice的状态*/
	switch (this->iceServer->GetState()){
	case RTC::IceServer::IceState::NEW:
		jsonObject["iceState"] = "new";
    ...
	}

	/*计算服务端证书的哈希值,因为使用不同的算法,所以fingerprint是一组。*/
	(*jsonDtlsParametersIt)["fingerprints"] = json::array();

	/*获取本地证书的fingerprint*/
	auto& fingerprints = this->dtlsTransport->GetLocalFingerprints();    

	/*将相应的哈希算法和对应的哈希值,填入到json中。*/
	for (size_t i{ 0 }; i < fingerprints.size(); ++i){
		...
		jsonEntry["algorithm"] =
			RTC::DtlsTransport::GetFingerprintAlgorithmString(fingerprint.algorithm);
		jsonEntry["value"] = fingerprint.value;
	}

	/*添加dtls的角色*/
	switch (this->dtlsRole){
	case RTC::DtlsTransport::Role::AUTO:
		(*jsonDtlsParametersIt)["role"] = "auto";
		break;
    ...
	}

	/*添加dtls的状态*/
	switch (this->dtlsTransport->GetState()){
	case RTC::DtlsTransport::DtlsState::NEW:
		jsonObject["dtlsState"] = "new";
		break;
	...
	}
}

以上生成的信息会发送给应用层进程,应用进程将上述信息整理后生成SDP,通过信令将服务端的SDP发送给客户端。

IceState

new:尚未收到 ICE 绑定请求

connected:收到有效的 ICE 绑定请求,当没有 USE-CANDIDATE 属性,允许发出媒体。

completed:收到具有 USE_CANDIDATE 属性的 ICE 绑定请求,现在允许双向收发媒体。

disconnected:ICE 是“connected”或“completed”,但突然失败。

closed:transport 处于关闭状态时的 ICE 状态

DtlsRole

auto:DTLS 角色是根据已解析的 ICE 角色确定的(“controlled”角色充当 DTLS 客户端,“controlling”角色充当 DTLS 服务器),由于 mediasoup 是 ICE lite 的实现,因为它始终表现为 ICE “controlled”。

client:DTLS 客户端

server:DTLS 服务端

DtlsState

new:DTLS 程序尚未启动

connecting:DTLS 正在连接中

connected:DTLS 已成功连接(SRTP 密钥已被提取)

failed:DTLS 连接失败

closed:当 transport 已关闭时,DTLS 的状态。

小结

mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第14张图片

设置发送端的最大接收码率

request

method:transport.setMaxIncomingBitrate ,id:7
data : {
    "bitrate": 1500000
}

internal : {
    "routerId": "ab8baa18-e002-44d7-a6e2-dc298587dd8b",
    "transportId": "253c0380-359c-4102-9dce-c6db3a625944"
}

response

{
    "accepted": true,
    "id": 7
}
void Transport::HandleRequest(Channel::Request* request)
{
    ...
    case Channel::Request::MethodId::TRANSPORT_SET_MAX_INCOMING_BITRATE:
    {
        /*从request中获取最大接收码率*/
        auto jsonBitrateIt = request->data.find("bitrate");
        if (jsonBitrateIt == request->data.end() || !Utils::Json::IsPositiveInteger(*jsonBitrateIt))
        {
            MS_THROW_TYPE_ERROR("missing bitrate");
        }

        /*设置最大接收码率*/
        this->maxIncomingBitrate = jsonBitrateIt->get<uint32_t>();

        MS_DEBUG_TAG(bwe, "maximum incoming bitrate set to %" PRIu32, this->maxIncomingBitrate);

        request->Accept();

        /*如果使用tcc,则给tccServer设置最大接收码率。*/
        if (this->tccServer)
            this->tccServer->SetMaxIncomingBitrate(this->maxIncomingBitrate);

        break;
    }
    ...
}

mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第15张图片

mediasoup从WebRTC中移植了remb和TCC相关的处理代码,所以如果客户端支持TCC则设置最大的接收码率。

与发送端建立连接

request

method:transport.connect ,id:13
data : {
    "dtlsParameters": {
        "fingerprints": [ #客户端证书的指纹  用于服务端验证客户端证书的有效性
            {
                "algorithm": "sha-256",   #使用的哈希算法
                "value": "AE:20:E5:FD:92:15:DD:0A:9B:D0:D4:9F:3B:5A:18:73:C2:B6:C5:2E:AD:77:51:1E:8E:36:A3:8A:87:87:99:DD"     #客户端证书的哈希值
            }
        ],
        "role": "server"  #客户端在DTLS中的角色
    }
}

internal : {
    "routerId": "ab8baa18-e002-44d7-a6e2-dc298587dd8b",
    "transportId": "253c0380-359c-4102-9dce-c6db3a625944"
}

response

{
    "accepted": true,
    "data": {
        "dtlsLocalRole": "client"   #服务端在DTLS中的角色
    },
    "id": 13
}
void WebRtcTransport::HandleRequest(Channel::Request* request)
{
    ...
    case Channel::Request::MethodId::TRANSPORT_CONNECT:
{
	/*确保这个过程进处理一次*/
	if (this->connectCalled)
		MS_THROW_ERROR("connect() already called");

	...

        /*获取客户端证书的fingerprint*/
        auto jsonFingerprintsIt = jsonDtlsParametersIt->find("fingerprints");

        ...

        /* 获取客户端DTLS的角色*/
        auto jsonRoleIt = jsonDtlsParametersIt->find("role");


        /*设置mediasoup服务端DTLS的角色*/
        switch (dtlsRemoteRole)
        {
        /*客户端角色为server,则mediasoup服务端的角色为client。*/
        case RTC::DtlsTransport::Role::SERVER:{
            this->dtlsRole = RTC::DtlsTransport::Role::CLIENT;

            break;
        }
        }

        /*标识已被处理过,防止多次处理。*/
        this->connectCalled = true;

        /*将客户端的fingerprint传递至DTLS*/
        if (this->dtlsTransport->SetRemoteFingerprint(dtlsRemoteFingerprint)){
            /*如果可以,则运行DTLSTransport。*/
            MayRunDtlsTransport();
        }

        /*向nodejs报告DTLS的角色*/
        json data = json::object();
        switch (this->dtlsRole){
        case RTC::DtlsTransport::Role::CLIENT:
            data["dtlsLocalRole"] = "client";
            break;
        ...
        }

        request->Accept(data);

        break;
    }
 ...   
}

mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第16张图片

在发送端创建音频producer

request

method:transport.produce ,id:14
data : {
    "kind": "audio",  #用于接收音频的producer
    "paused": false,  #是否以暂停模式启动
    "rtpMapping": {   #producer中的rtp流到consumer之间的映射信息
        "codecs": [   #payload的映射
            {
                "mappedPayloadType": 100,    #producer rtp流中payload type
                "payloadType": 111           #映射后的payload type
            }
        ],
        "encodings": [ #ssrc的映射
            {
                "mappedSsrc": 323744560,   #映射后的ssrc
                "ssrc": 3536928023         #producer rtp流的ssrc
                #consumer可以根据mappedSsrc识别rtp流
            }
        ]
    },
    "rtpParameters": {
        "codecs": [    #producer接收到rtp的编码信息
            {
                "channels": 2,             #双声道
                "clockRate": 48000,        #采样率为48KHz
                "mimeType": "audio/opus",  #使用opus编码器
                "parameters": {
                    "minptime": 10,
                    "sprop-stereo": 1,
                    "usedtx": 1,
                    "useinbandfec": 1
                },
                "payloadType": 111,  #发送端发送的RTP流中payload type为111
                "rtcpFeedback": [
                    {
                        "parameter": "",
                        "type": "transport-cc" #使用google的TCC拥塞控制算法
                    }
                ]
            }
        ],
        "encodings": [
            {
                "dtx": false,        #是否使用不连续传输
                "ssrc": 3536928023   #发送端发送的RTP流的ssrc
            }
        ],
        "headerExtensions": [
            {
                "encrypt": false,   #是否加密RTP扩展头
                "id": 9,
                "parameters": {},
                "uri": "urn:ietf:params:rtp-hdrext:sdes:mid"#扩展头中的内容
            },
            ...
        ],
        "mid": "0",   #用于标识媒体流id
        "rtcp": {
            "cname": "tAPOZzhquxMA1QjR",   #RTP流的cname
            "reducedSize": true   
        }
    }
}

internal : {
    "producerId": "52fd0ef8-1cf8-4580-aa08-9ae33414a714",   #producer id
    "routerId": "ab8baa18-e002-44d7-a6e2-dc298587dd8b",
    "transportId": "253c0380-359c-4102-9dce-c6db3a625944"
}

response

{
    "accepted": true,
    "data": {
        "type": "simple"   #对应Consumer的类型,音频使用SimpleConsumer。
    },
    "id": 14
}
void Transport::HandleRequest(Channel::Request* request)
{
    ...
 	case Channel::Request::MethodId::TRANSPORT_PRODUCE:
    {
        std::string producerId;

        /*获取producer的id*/
        SetNewProducerIdFromRequest(request, producerId);

        /*创建producer*/
        auto* producer = new RTC::Producer(producerId, this, request->data);

        /*通知rtpListener有新的producer产生,添加ssrc、mid、rid到producer的映射。*/
        this->rtpListener.AddProducer(producer);  

        /*通知router有新的producer产生*/
        this->listener->OnTransportNewProducer(this, producer);

        /*id到producer的映射*/
        this->mapProducers[producerId] = producer;

        /*通知应用层,producer对应的Consumer的类型。*/
        json data = json::object();
        data["type"] = RTC::RtpParameters::GetTypeString(producer->GetType());

        request->Accept(data);
    }
    ...
}

创建Producer对象后,通过OnTransportNewProducer()函数告诉Router有Producer创建。

mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第17张图片


inline void Router::OnTransportNewProducer(RTC::Transport* /*transport*/, RTC::Producer* producer)
{
	/*判断这个producer是否已经存在*/
	if (this->mapProducers.find(producer->id) != this->mapProducers.end()){
		...
	}
	
    /*producer id到producer的映射*/
	this->mapProducers[producer->id] = producer;   
    
    /*producer到consumer的映射,占位。现在还没有映射*/
	this->mapProducerConsumers[producer];
    
    /*producer到rtpObserver的映射,占位。现在还没有映射*/
	this->mapProducerRtpObservers[producer];           
}

当transport创建新的producer时,会通知router。

建立producer到AudioLevelObserver映射

request

method:rtpObserver.addProducer ,id:15
data : {}

internal : {
    "producerId": "52fd0ef8-1cf8-4580-aa08-9ae33414a714",   
    "routerId": "ab8baa18-e002-44d7-a6e2-dc298587dd8b",
    "rtpObserverId": "079b1620-3865-4c0b-8c0a-e6acba5af7c2"
}

response

{
    "accepted": true,
    "id": 15
}
void Router::HandleRequest(Channel::Request* request)
{
    ...
    case Channel::Request::MethodId::RTP_OBSERVER_ADD_PRODUCER:
    {
        /*根据id分别获取rtpObserver和producer*/
        RTC::RtpObserver* rtpObserver = GetRtpObserverFromRequest(request);
        RTC::Producer* producer = GetProducerFromRequest(request);

        /*将producer添加到rtpObserver中*/
        rtpObserver->AddProducer(producer);

        /*建立producer到RtpObserver对象的映射*/
        this->mapProducerRtpObservers[producer].insert(rtpObserver);

        request->Accept();

        break;
    }
    ...
}

当producer收到rtp packet时,将rtp packet发送至rtpObserver。

mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第18张图片

创建发送端的视频producer

request

method:transport.produce ,id:19
data : {
    "kind": "video",    #用于接收视频的producer
    "paused": false,    #是否以暂停模式启动
    "rtpMapping": {     #producer中的rtp流到consumer之间的映射
        "codecs":       #payload type的映射
            {
                "mappedPayloadType": 101,  #映射后的payload type
                "payloadType": 96          #producer rtp流中的payload type
            },
            {
                "mappedPayloadType": 102,
                "payloadType": 97
            }
        ],
        "encodings": [   #ssrc的映射
            {
                "mappedSsrc": 214799301,      #映射后的ssrc
                "scalabilityMode": "S1T3",
                "ssrc": 448203859             #producer rtp流的ssrc
            },
            {
                "mappedSsrc": 214799302,
                "scalabilityMode": "S1T3",
                "ssrc": 448203860
            },
            {
                "mappedSsrc": 214799303,
                "scalabilityMode": "S1T3",
                "ssrc": 448203861
            }
        ]
    },
    "rtpParameters": {
        "codecs": [            #producer接收到rtp的编码信息
            {                  #主rtp流的信息
                "clockRate": 90000,       #视频采样率90000,一般都为这个值。
                "mimeType": "video/VP8",  #使用vp8编码器
                "parameters": {},
                "payloadType": 96,        #rtp流中的payload type为96
                "rtcpFeedback": [
                    {
                        "parameter": "",
                        "type": "goog-remb"  #使用google的remb拥塞控制算法
                    },
				   ...
                ]
            },
            {                  #重传流的信息
                "clockRate": 90000,         #重传流的采样率
                "mimeType": "video/rtx",    #标识这是重传流
                "parameters": {
                    "apt": 96               #表明是上面流的重传流
                },
                "payloadType": 97,          #重传流的payload type
                "rtcpFeedback": []
            }
        ],
        "encodings": [    #每条rtp流的rtp信息    simulcast中有三条rtp流
            {
                "active": true,
                "dtx": false,
                "maxBitrate": 500000,      #最大码率
                "rtx": {
                    "ssrc": 3051205391     #对应重传流的ssrc
                },
                "scalabilityMode": "S1T3", #RTP流中空间和时间层的数量
                "scaleResolutionDownBy": 4,
                "ssrc": 448203859   #rtp流的ssrc   由webrtc客户端决定 
            },
            {
			  ...
            },
            {
                ...
            }
        ],
        "headerExtensions": [    #rtp头部扩展中的信息
            {
                "encrypt": false,
                "id": 9,
                "parameters": {},
                "uri": "urn:ietf:params:rtp-hdrext:sdes:mid"
            },
            ...
        ],
        "mid": "2",
        "rtcp": {
            "cname": "tAPOZzhquxMA1QjR",
            "reducedSize": true
        }
    }
}

internal : {
    "producerId": "fca32301-23e9-4d48-bd4a-01af50695541",
    "routerId": "ab8baa18-e002-44d7-a6e2-dc298587dd8b",
    "transportId": "253c0380-359c-4102-9dce-c6db3a625944"
}

response

{
    "accepted": true,
    "data": {
        "type": "simulcast"   #对应的consumer的类型为SimulcastConsumer。
    },
    "id": 19
}

和上面创建音频producer类似

创建接收端的createWebRtcTransport

request

method:router.createWebRtcTransport ,id:20
data : {
...
}

internal : {
...
}

response

{
    "accepted": true,
    "data": {
        ...
    },
    "id": 20
}

和上面创建发送端WebRtctransport类似。

设置接收端的最大接收码率

request

method:transport.setMaxIncomingBitrate ,id:22
data : {
    "bitrate": 1500000
}

internal : {
    "routerId": "ab8baa18-e002-44d7-a6e2-dc298587dd8b",
    "transportId": "870bb417-2843-483e-9919-6ae249ead2e9"
}

response

{
    "accepted": true,
    "id": 22
}

和上面设置发送端最大接收码率类似

创建接收端的音频consumer

request

method:transport.consume ,id:26
data : {
    "consumableRtpEncodings": [   #consumer可以消费的流信息
        {
            "dtx": false,
            "ssrc": 323744560   #producer中映射后的ssrc,consumer消费这条流。
        }
    ],
    "kind": "audio",     #用于发送音频
    "paused": true,      #是否以暂停模式启动
    "rtpParameters": {   #consumer发送流的信息     需要和producer中信息相匹配
        "codecs": [      #编码信息
            {
                "channels": 2,                #双声道
                "clockRate": 48000,           #采样率为48KHz
                "mimeType": "audio/opus",     #使用opus编码
                "parameters": {
                    "minptime": 10,      
                    "sprop-stereo": 1,
                    "usedtx": 1,
                    "useinbandfec": 1
                },
                "payloadType": 100,           #rtp流的payload type为100
                "rtcpFeedback": []
            }
        ],
        "encodings": [   #rtp流的信息
            {
                "ssrc": 762429432    #consumer发送RTP流的ssrc
            }
        ],
        "headerExtensions": [    #consumer发送rtp头部扩展的信息
            {
                "encrypt": false,  #扩展部分不加密
                "id": 1,
                "parameters": {},
                "uri": "urn:ietf:params:rtp-hdrext:sdes:mid"  #扩展的内容
            },
            ...
        ],
        "mid": "0",
        "rtcp": {
            "cname": "tAPOZzhquxMA1QjR",
            "mux": true,
            "reducedSize": true
        }
    },
    "type": "simple"
}

internal : {
    "consumerId": "545db098-3705-472a-bd21-27d6a5272c7c",
    "producerId": "52fd0ef8-1cf8-4580-aa08-9ae33414a714",
    "routerId": "ab8baa18-e002-44d7-a6e2-dc298587dd8b",
    "transportId": "376998e4-908a-419a-aa58-d420992ce2e9"
}

response

{
    "accepted": true,
    "data": {
        "paused": true,
        "producerPaused": false,
        "score": {
            "producerScore": 10,
            "producerScores": [
                10
            ],
            "score": 10
        }
    },
    "id": 26
}
void Transport::HandleRequest(Channel::Request* request)
{
    ...
    case Channel::Request::MethodId::TRANSPORT_CONSUME:
    {
        /*consumer对应producer的id*/
        auto jsonProducerIdIt = request->internal.find("producerId");
        std::string producerId = jsonProducerIdIt->get<std::string>();

        std::string consumerId;
        /*获取consumer的id*/
        SetNewConsumerIdFromRequest(request, consumerId);

        /*获取consumer的类型*/
        auto jsonTypeIt = request->data.find("type");
        auto type = RTC::RtpParameters::GetType(jsonTypeIt->get<std::string>());

        RTC::Consumer* consumer{ nullptr };

        /*根据consumer类型,创建consumer对象。*/
        switch (type)
        {
        case RTC::RtpParameters::Type::SIMPLE:
        {
            // This may throw.    
            consumer = new RTC::SimpleConsumer(consumerId, this, request->data);
            break;
        }
        }

        /*创建consumer后,通知router。告诉consumer需要关联producer的id*/
        this->listener->OnTransportNewConsumer(this, consumer, producerId);


        /*id到consumer的映射,保存创建的consumer对象。*/
        this->mapConsumers[consumerId] = consumer;

        /*建立ssrc到consumer的映射*/
        for (auto ssrc : consumer->GetMediaSsrcs())
        {
            this->mapSsrcConsumer[ssrc] = consumer;
        }

        /*建立rtx ssrc到consumer的映射*/
        for (auto ssrc : consumer->GetRtxSsrcs())
        {
            this->mapRtxSsrcConsumer[ssrc] = consumer;
        }

        ...
        request->Accept(data);
        ...
    } 
    ...
}

mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第19张图片

inline void Router::OnTransportNewConsumer(RTC::Transport* /*transport*/, RTC::Consumer* consumer, std::string& producerId)
{
	/*根据producer的id查找consumer关联的producer*/
	auto mapProducersIt = this->mapProducers.find(producerId);

	/*获取producerId对应的producer*/
	auto* producer = mapProducersIt->second;

	/*找到producer对应的consumer组*/
	auto mapProducerConsumersIt = this->mapProducerConsumers.find(producer);

	/*根据producer的状态更新consumer的状态*/
	if (producer->IsPaused())
		consumer->ProducerPaused();    /*若producer是暂停,状态,则consumer也设置为暂停状态。*/

	/*获取producer映射的consumers组*/
	auto& consumers = mapProducerConsumersIt->second;

	/*将consumer加入到producer映射的consumer组   producer ----> consumer*/
	consumers.insert(consumer);

	/*保存consumer到producer的映射              consumer ----> producer*/
	this->mapConsumerProducer[consumer] = producer;

	/*producer中的所有rtp流都提供给consumer*/
	for (auto& kv : producer->GetRtpStreams())      
	{
		auto* rtpStream = kv.first;
		uint32_t mappedSsrc = kv.second;

		/*让consumer保存producer中的rtpStream。*/
		consumer->ProducerRtpStream(rtpStream, mappedSsrc);
	}
}

建立各种映射关系

创建接收端的视频consumer

request

method:transport.consume ,id:27
data : {
    "consumableRtpEncodings": [  #consumer可以消费的流信息,也就是producer提供的流信息。
        {
            "active": true,
            "dtx": false,
            "maxBitrate": 500000,
            "scalabilityMode": "S1T3",
            "scaleResolutionDownBy": 4,
            "ssrc": 214799301      #producer映射后的ssrc
        },
        {
            ...
        },
        {
            ...
        }
    ],
    "kind": "video",
    "paused": true,
    "rtpParameters": {   #consumer发送流的信息
        "codecs": [      #编解码信息
            {
                "clockRate": 90000,
                "mimeType": "video/VP8",
                "parameters": {},
                "payloadType": 101,
                "rtcpFeedback": [
                    {
                        "parameter": "",
                        "type": "transport-cc"
                    },
                    ...
                ]
            },
            {
                "clockRate": 90000,
                "mimeType": "video/rtx",
                "parameters": {
                    "apt": 101
                },
                "payloadType": 102,
                "rtcpFeedback": []
            }
        ],
        "encodings": [    #rtp流的信息
            {
                "maxBitrate": 5000000,
                "rtx": {
                    "ssrc": 344149589       #重传流的ssrc
                },
                "scalabilityMode": "S3T3",
                "ssrc": 919050306           #consumer发送rtp流的ssrc
            }
        ],
        "headerExtensions": [       #rtp头部扩展的信息
            {
                "encrypt": false,
                "id": 1,
                "parameters": {},
                "uri": "urn:ietf:params:rtp-hdrext:sdes:mid"
            },
            ...
        ],
        "mid": "1",
        "rtcp": {
            "cname": "tAPOZzhquxMA1QjR",
            "mux": true,
            "reducedSize": true
        }
    },
    "type": "simulcast"
}

internal : {
    "consumerId": "de8613b0-4f4e-441f-9417-fecad78d1a4b",
    "producerId": "fca32301-23e9-4d48-bd4a-01af50695541",
    "routerId": "ab8baa18-e002-44d7-a6e2-dc298587dd8b",
    "transportId": "376998e4-908a-419a-aa58-d420992ce2e9"
}

response

{
    "accepted": true,
    "data": {
        "paused": true,
        "preferredLayers": {
            "spatialLayer": 2,
            "temporalLayer": 2
        },
        "producerPaused": false,
        "score": {
            "producerScore": 0,
            "producerScores": [
                10,
                10,
                10
            ],
            "score": 10
        }
    },
    "id": 27
}

分析过程,同上。

与接收端建立连接

request

method:transport.connect ,id:30
data : {
    "dtlsParameters": {
	...
    }
}

internal : {
	...
}

response

{
    "accepted": true,
    ...
}

分析过程,同上。

启动音频流

request

method:consumer.resume ,id:32
data : {}

internal : {
    "consumerId": "545db098-3705-472a-bd21-27d6a5272c7c",
    "producerId": "52fd0ef8-1cf8-4580-aa08-9ae33414a714",
    "routerId": "ab8baa18-e002-44d7-a6e2-dc298587dd8b",
    "transportId": "376998e4-908a-419a-aa58-d420992ce2e9"
}

response

{
    "accepted": true,
    "id": 32
}
void Consumer::HandleRequest(Channel::Request* request){
	...
	case Channel::Request::MethodId::CONSUMER_RESUME:
    {   
        /*处于非暂停状态,直接返回。*/
        if (!this->paused){ 
            request->Accept();
            return;
        }

        /*标记为暂停状态*/
        this->paused = false;

        if (IsActive())
            UserOnResumed();    /*通知子类,状态变为恢复,并请求关键帧。*/

        request->Accept();

        break;
    }
	...
}

mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第20张图片

void SimpleConsumer::UserOnResumed()
{
	this->syncRequired = true;

	if (IsActive())
		RequestKeyFrame();     /*请求关键帧。*/
}
/*向关联的producer请求关键帧。*/
void SimpleConsumer::RequestKeyFrame()
{
	if (this->kind != RTC::Media::Kind::VIDEO)
		return;

	/*根据配置信息,找到consumer对应producer的映射ssrc。*/
	auto mappedSsrc = this->consumableRtpEncodings[0].ssrc;

	/*通知其订阅者transport,请求发送关键帧。*/
	this->listener->OnConsumerKeyFrameRequested(this, mappedSsrc);
}

启动视频流

request

method:consumer.resume ,id:36
data : {}

internal : {
    "consumerId": "de8613b0-4f4e-441f-9417-fecad78d1a4b",
    "producerId": "fca32301-23e9-4d48-bd4a-01af50695541",
    "routerId": "ab8baa18-e002-44d7-a6e2-dc298587dd8b",
    "transportId": "376998e4-908a-419a-aa58-d420992ce2e9"
}

response

{
    "accepted": true,
    "id": 36
}

同上

媒体数据的处理流程

mediasoup底层基于libuv,数据的收发都是基于事件的。当libuv接收到一个数据包时,根据数据包的类型,会激活一个事件,导致相关一系列函数的执行,直到数据处理完毕。处理完毕后会继续回到libuv的loop,继续监听新的事件。

在创建WebRTCTransport的时候,会创建RTC::UdpSocket对象,这个对象就是对libuv的封装,WebRTCTransport的所有数据的收发都由这个对象负责。

RTP、RTCP、STUN、DTLS的数据,的收发都需要经过WebRTCTransport。WebRTCTransport接收数据后会进行分类处理。

mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第21张图片

WebRtcTransport接收数据过程

inline void WebRtcTransport::OnUdpSocketPacketReceived(RTC::UdpSocket* socket, const uint8_t* data, size_t len, const struct sockaddr* remoteAddr)
{
	/*记录收到数据包的远端地址,并组成TransportTuple。*/
	RTC::TransportTuple tuple(socket, remoteAddr);

	/*继续处理接收到的数据*/
	OnPacketReceived(&tuple, data, len);
}

OnUdpSocketPacketReceived是虚函数,当libuv收到数据时,会调用这个虚函数,将数据送至WebRtcTransport。

inline void WebRtcTransport::OnPacketReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len)
{
	/*送至Transport,用于统计接收数据的信息。*/
	RTC::Transport::DataReceived(len);

	if (RTC::StunPacket::IsStun(data, len)){            /*STUN packet*/
		OnStunDataReceived(tuple, data, len);
	}else if (RTC::RTCP::Packet::IsRtcp(data, len)){    /*RTC packet*/
		OnRtcpDataReceived(tuple, data, len);
	}else if (RTC::RtpPacket::IsRtp(data, len)) {       /*RTP packet*/
		OnRtpDataReceived(tuple, data, len);
	}else if (RTC::DtlsTransport::IsDtls(data, len)){   /*DTLS packet*/
		OnDtlsDataReceived(tuple, data, len);         
	}else{
		MS_WARN_DEV("ignoring received packet of unknown type");
	}
}

针对不同的数据包,送至不同的函数处理。

mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第22张图片

WebRtcTransport发送数据过程

mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第23张图片

如果在创建WebRtcTransport的时候,传入了多个监听ip地址,则服务端就会创建多个UdpSocket对象。

在ICE的过程中,WebRtcTransport中的UdpSocket对象被保存到IceServer中,在和客户端进行通信的过程中,由IceServer选择使用哪个UdpSocket对象。所以WebRtcTransport在发送数据时,需要通过IceServer::GetSelectedTuple()返回正在使用的TransportTuple,其中TransportTuple是UdpSocket对象的进一步封装。

RTP数据的处理

音频传输使用SimpleConsumer,数据的处理流程如下:

mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第24张图片

void WebRtcTransport::OnRtpDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len)
{
	...
	/*解密rtp packet*/
	if (!this->srtpRecvSession->DecryptSrtp(const_cast<uint8_t*>(data), &len)){
		RTC::RtpPacket* packet = RTC::RtpPacket::Parse(data, len);
		...
		return;
	}

	/*将收到的rtp packet解析成RtpPacket对象*/
	RTC::RtpPacket* packet = RTC::RtpPacket::Parse(data, len);
	...
	
	/*把RtpPacket送至其父类Transport*/
	RTC::Transport::ReceiveRtpPacket(packet);
}

将收到的RTP packet,先使用libsrtp解密,然后将数据包解析为RtpPacket对象。

void Transport::ReceiveRtpPacket(RTC::RtpPacket* packet)
{
	...
	/*根据收到的packet,查找关联的producer。*/
	RTC::Producer* producer = this->rtpListener.GetProducer(packet);
	...

	/*将packet传给指定的producer,进行下一步处理。*/
	auto result = producer->ReceiveRtpPacket(packet);

	switch (result)     /*根据packet包类型不同,进行不同通道的码率统计。*/
	{
	case RTC::Producer::ReceiveRtpPacketResult::MEDIA:
		this->recvRtpTransmission.Update(packet);  /*媒体通道的码率统计*/               
		break;
	case RTC::Producer::ReceiveRtpPacketResult::RETRANSMISSION:
		this->recvRtxTransmission.Update(packet);  /*重传通道的码率统计*/               
		break;
	default:;
	}

    /*释放rtp包*/
	delete packet; 
}

Transport接收到RtpPacket后,将其送至对应的Producer内。

/*接收到transport传入的packet,对packet进行指定的处理。*/
Producer::ReceiveRtpPacketResult Producer::ReceiveRtpPacket(RTC::RtpPacket* packet)
{
	/*重置当前正在处理的packet*/
	this->currentRtpPacket = nullptr;

	// Count number of RTP streams.    统计当前接收流的数目
	auto numRtpStreamsBefore = this->mapSsrcRtpStream.size();

	/*通过packet,获取对应的接收流。*/
	auto* rtpStream = GetRtpStream(packet);

	if (rtpStream == nullptr){  /*没有查找到对应的rtp接收流*/
		return ReceiveRtpPacketResult::DISCARDED;    /*将packet丢弃*/
	}

	/*对packet进行预处理:如果是视频,则添加头部扩展id。*/
	PreProcessRtpPacket(packet);

	ReceiveRtpPacketResult result;
	bool isRtx{ false };      /*packet是否是rtx流中的packet*/

	/*是主流中的rtp包*/
	if (packet->GetSsrc() == rtpStream->GetSsrc())
	{
		/*设置返回结果,表示是媒体流,视频流或音频流。*/
		result = ReceiveRtpPacketResult::MEDIA;

		/*rtp接收流处理接收到的packet*/
		if (!rtpStream->ReceivePacket(packet))
		{
			/*如果添加了新的rtp接收流,则通知其订阅者。*/
			if (this->mapSsrcRtpStream.size() > numRtpStreamsBefore)
				NotifyNewRtpStream(rtpStream);  /*最终通知到的是与producer相关的consumer*/

			return result;
		}
	}
	/*重传流中的rtp包*/
	else if (packet->GetSsrc() == rtpStream->GetRtxSsrc())
	{
		...
		/*rtp接收流处理重传流中的packet*/
		if (!rtpStream->ReceiveRtxPacket(packet))
			return result;
	} else{  // Should not happen.
		MS_ABORT("found stream does not match received packet");
	}

	/*判断packet是否是关键帧中的包*/
	if (packet->IsKeyFrame()){
		if (this->keyFrameRequestManager)
			this->keyFrameRequestManager->KeyFrameReceived(packet->GetSsrc()); /*更新关键帧*/
	}

	...

	/*在将packet发布至其订阅者之前,对其进行倾轧。主要进行payload type,ssrc,header extension的处理*/
	if (!MangleRtpPacket(packet, rtpStream))
		return ReceiveRtpPacketResult::DISCARDED;

	/*最后再对packet进行一次处理*/
	PostProcessRtpPacket(packet);

	/*将处理后的packet,发送到其订阅者transport中。*/
	this->listener->OnProducerRtpPacketReceived(this, packet);

	return result;
}

在Producer中会将RtpPacket送至RtpStreamRecv处理,用于接收流相应信息的管理。最后将RtpPacket通过Transport送至相应的Consumer。

inline void Transport::OnProducerRtpPacketReceived(RTC::Producer* producer, RTC::RtpPacket* packet)
{
	/*transport将收到的packet传递给router,由router转发至相应的consumer。*/
	this->listener->OnTransportProducerRtpPacketReceived(this, producer, packet);
}

Transport会将RtpPacket送至Router。

void Router::OnTransportProducerRtpPacketReceived(RTC::Transport*, RTC::Producer* producer, RTC::RtpPacket* packet)
{
	/*通过producer找到其对应的consumer组*/
	auto& consumers = this->mapProducerConsumers.at(producer);

	/*遍历每个consumer,将producer中的packet传递至每个consumer中。*/
	for (auto* consumer : consumers){
		...
		/*将packet发送至consumer*/
		consumer->SendRtpPacket(packet);
	}

	/*通过producer找到对应的rtpObserver,对接收到的packet进行音量的统计。*/
	auto it = this->mapProducerRtpObservers.find(producer);
	if (it != this->mapProducerRtpObservers.end()){
		/*找到producer对应的rtpObserver的集合*/
		auto& rtpObservers = it->second;

		/*更新每个rtpObserver中音量的统计值*/
		for (auto* rtpObserver : rtpObservers){
			rtpObserver->ReceiveRtpPacket(producer, packet);
		}
	}
}

接收端Producer收到的RtpPacket送至Router后,Router会将RtpPacket送至对应的Consumer中,同时也会把RtpPacket送至AudioLevelObserver中,用于统计发送端音频数据的音量,以便定期向应用进程汇报。

void SimpleConsumer::SendRtpPacket(RTC::RtpPacket* packet)
{
	...
	/*如果正在进行同步,并且支持关键帧,则不是关键帧就将其丢弃。*/
	if (this->syncRequired && this->keyFrameSupported && !packet->IsKeyFrame())
		return;   
	...

	/*通过consumer的sequence manager获取rtp packet的seq*/
	this->rtpSeqManager.Input(packet->GetSequenceNumber(), seq);

	auto origSsrc = packet->GetSsrc();
	auto origSeq = packet->GetSequenceNumber();

	/*将ssrc改为consumer rtp流的ssrc*/
	packet->SetSsrc(this->rtpParameters.encodings[0].ssrc);

	/*设置为consumer的序列号*/
	packet->SetSequenceNumber(seq);                           

	/*将rtp packet送至consumer的rtpStream中去处理,将收到的rtp packet缓存下来。*/
	if (this->rtpStream->ReceivePacket(packet)){
		/*将packet发送至transport中,通过transport使用libuv将packet发送至对端。*/
		this->listener->OnConsumerSendRtpPacket(this, packet);
	}

	/* 将packet的ssrc和sequence重置为原来的值,以便送至下一个consumer中。*/
	packet->SetSsrc(origSsrc);
	packet->SetSequenceNumber(origSeq);
}

在Consumer中,重新设置RtpPacket的ssrc和序列号。然后将RtpPacket送至Transport。

inline void Transport::OnConsumerSendRtpPacket(RTC::Consumer*, RTC::RtpPacket* packet)
{
	SendRtpPacket(packet);      /*虚函数,调用子类的具体实现。*/
}

调用子类的具体实现

void WebRtcTransport::SendRtpPacket(RTC::RtpPacket* packet, RTC::Transport::onSendCallback* cb)
{
	...
	const uint8_t* data = packet->GetData();
	size_t len = packet->GetSize();

	/*对数据进行加密*/
	if (!this->srtpSendSession->EncryptRtp(&data, &len)){
		...
		return;
	}

	/*通过IceServer将数据发送出去*/
	this->iceServer->GetSelectedTuple()->Send(data, len, cb);
	...
}

对待发送的RtpPacket,使用libsrtp进行加密,将加密后的数据通过IceServer发送至接收客户端。


视频使用SimulcastConsumer,数据的处理流程如下:

待写…

RTCP数据的处理

RTCP数据的发送
void WebRtcTransport::SendRtcpPacket(RTC::RTCP::Packet* packet)
{
	...
	const uint8_t* data = packet->GetData();
	size_t len = packet->GetSize();
	...
	/*对数据进行加密*/
	if (!this->srtpSendSession->EncryptRtcp(&data, &len))
		return;

	/*通过ice server发送数据*/
	this->iceServer->GetSelectedTuple()->Send(data, len);
	...
}

/*发送rtcp compound packet*/
void WebRtcTransport::SendRtcpCompoundPacket(RTC::RTCP::CompoundPacket* packet)
{
	...
	/*对数据进行加密*/
	if (!this->srtpSendSession->EncryptRtcp(&data, &len))
		return;

	/*通过ice server发送数据*/
	this->iceServer->GetSelectedTuple()->Send(data, len);
	...
}

mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第25张图片

RTCP数据的接收
inline void WebRtcTransport::OnRtcpDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len)
{
	...

	/*解密rtcp packet*/
	if (!this->srtpRecvSession->DecryptSrtcp(const_cast<uint8_t*>(data), &len))
		return;

	RTC::RTCP::Packet* packet = RTC::RTCP::Packet::Parse(data, len);
	...

	/*将rtcp对象发送至其父类Transport*/
	RTC::Transport::ReceiveRtcpPacket(packet);
}
void Transport::ReceiveRtcpPacket(RTC::RTCP::Packet* packet)
{
	/*有可能是一个rtcp复合包,所以需要循环的处理。*/
	while (packet)
	{
		HandleRtcpPacket(packet);       /*处理rtcp packet*/

		auto* previousPacket = packet;

		/*指向下一个rtcp packet*/
		packet = packet->GetNext();

		delete previousPacket;
	}
}
void Transport::HandleRtcpPacket(RTC::RTCP::Packet* packet)
{
	switch (packet->GetType())
	{
	case RTC::RTCP::Type::RR:
		...
	case RTC::RTCP::Type::PSFB:
		...
	case RTC::RTCP::Type::RTPFB:
		...
	case RTC::RTCP::Type::SR:
		...
	case RTC::RTCP::Type::SDES:
		...
	case RTC::RTCP::Type::BYE:
		...
	case RTC::RTCP::Type::XR:
		...
	default:
	{}
	}
}

针对不同的RTCP包,进行不同方式的处理。

由此看出mediasoup支持的RTCP报文有:RRSRSDESBYEXRPSFBRTPFB,其中PSFB包括PLIFIRAFB,其中AFB报文是WebRTC自己扩展的报文,用于remb算法;RTPFB又包括NACKTCC,其中TCC报文是WebRTC自己扩展的报文,用于google TCC算法。

mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第26张图片

RR报文的处理
void Transport::HandleRtcpPacket(RTC::RTCP::Packet* packet)
{
    ...
    case RTC::RTCP::Type::RR:
    {
        /*将RTCP对象转成RR对象*/
        auto* rr = static_cast<RTC::RTCP::ReceiverReportPacket*>(packet);

        for (auto it = rr->Begin(); it != rr->End(); ++it){
            auto& report = *it;
            /*根据report block中的ssrc查找对应的consumer*/
            auto* consumer = GetConsumerByMediaSsrc(report->GetSsrc());

            if (!consumer){   /*没有找到对应的Consumer*/
                if (report->GetSsrc() == RTC::RtpProbationSsrc){
                    continue;
                }

                MS_DEBUG_TAG(rtcp, "no Consumer found for received Receiver Report [ssrc:%" PRIu32 "]", report->GetSsrc());
                continue;
            }

            /*consumer关联的端点发送过来RR报文,consumer需要对其处理。*/
            consumer->ReceiveRtcpReceiverReport(report);
        }

        break;
    }
    ...
}

在mediasoup中,只有Consumer连接的客户端,才会向mediasoup发送RR报文,而Producer连接的客户端是不会发送RR报文,只会发送SR报文。所以收到RR报文后,需要找到对应的Consumer,并将这个RR报文交由其处理。

每个RR报文中,可能存在多个report block,所以要依次遍历。处理report block时,先根据其内部的ssrc找到对应的Consumer,将这个report block送至对应的Consumer进行处理。

void SimpleConsumer::ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report)
{
	MS_TRACE();
	/*将RR报文送至rtp发送流中处理*/
	this->rtpStream->ReceiveRtcpReceiverReport(report);
}
void RtpStreamSend::ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report)
{
	...
	uint64_t nowMs = DepLibUV::GetTimeMs();
	auto ntp = Utils::Time::TimeMs2Ntp(nowMs);    /*获取此刻的NTP时间*/

	/*取NTP时间的中间32位*/     
	uint32_t compactNtp = (ntp.seconds & 0x0000FFFF) << 16;
	compactNtp |= (ntp.fractions & 0xFFFF0000) >> 16;

	uint32_t lastSr = report->GetLastSenderReport();
	uint32_t dlsr = report->GetDelaySinceLastSenderReport();

	uint32_t rtt{ 0 };

	if (!lastSr || !dlsr)
		rtt = 0;
	else if (compactNtp > dlsr + lastSr)
		rtt = compactNtp - dlsr - lastSr;    /*计算RTT*/
	else
		rtt = 0;

	this->rtt = (rtt >> 16) * 1000;   
	this->rtt += (static_cast<float>(rtt & 0x0000FFFF) / 65536) * 1000;  

	if (this->rtt > 0.0f)
		this->hasRtt = true;  /*设置rtt有效*/

	/*RR的累计丢包总数*/
	this->packetsLost = report->GetTotalLost();

	/*丢包率*/
	this->fractionLost = report->GetFractionLost();

	...
}

RtpStreamSend对RR报文的处理

其中的一些信息会用于生成Consumer发送给接收客户端的SR报文

SR报文的处理
void Transport::HandleRtcpPacket(RTC::RTCP::Packet* packet)
{
    ...
    case RTC::RTCP::Type::SR:
    {
        auto* sr = static_cast<RTC::RTCP::SenderReportPacket*>(packet);

        for (auto it = sr->Begin(); it != sr->End(); ++it){
            auto& report = *it;
            /*查找SR中ssrc对应的producer*/
            auto* producer = this->rtpListener.GetProducer(report->GetSsrc());

            if (!producer){
                MS_DEBUG_TAG(rtcp, "no Producer found for received Sender Report [ssrc:%" PRIu32 "]", report->GetSsrc());

                continue;
            }
			
            /*将SR报文送至对应的Producer处理*/
            producer->ReceiveRtcpSenderReport(report);
        }

        break;
    }
    ...
}

mediasoup中只有Producer连接的客户端,才会向mediasoup发送SR报文,所以收到SR报文后,交由对应的Producer进行处理。

void Producer::ReceiveRtcpSenderReport(RTC::RTCP::SenderReport* report)
{
	/*通过packet的ssrc,获取其对应的接收流。*/
	auto it = this->mapSsrcRtpStream.find(report->GetSsrc());
	if (it != this->mapSsrcRtpStream.end()){
		auto* rtpStream = it->second;
		bool first = rtpStream->GetSenderReportNtpMs() == 0;

		/*将SR报文送至接收流中处理*/
		rtpStream->ReceiveRtcpSenderReport(report);

		/*通知其订阅者,这个流收到了SR报文。会送至对应的Consumer。*/
		this->listener->OnProducerRtcpSenderReport(this, rtpStream, first);

		return;
	}

	/*如果在主流中没有找到,则在重传流中找。*/
	auto it2 = this->mapRtxSsrcRtpStream.find(report->GetSsrc());
	if (it2 != this->mapRtxSsrcRtpStream.end()){
		auto* rtpStream = it2->second;

		/*交由对应的rtx流处理SR报文*/
		rtpStream->ReceiveRtxRtcpSenderReport(report);

		return;
	}
}

Producer中有重传RTP包的时候,使用的是单独的RTP流,所以Producer有两种接收流,一种是RTP流,另一种是RTX流,也就是重传流。注意这两个流的SSRC是不同的。

接收到SR报文后,需要判断是否RTP流的SR还是RTX流的SR。接着将SR送至对应的RTC::RtpStreamRecv进行处理。与Consumer中的RTC::RtpStreamSend类似。

在Producer中对SR的处理分成两部分,一方面会将SR报文送至RtpStreamRecv进行处理,另一方面需要将收到的SR报告给Producer对应的Consumer。

void RtpStreamRecv::ReceiveRtcpSenderReport(RTC::RTCP::SenderReport* report)
{
	this->lastSrReceived = DepLibUV::GetTimeMs();
	this->lastSrTimestamp = report->GetNtpSec() << 16;
	this->lastSrTimestamp += report->GetNtpFrac() >> 16;

	Utils::Time::Ntp ntp; 

	ntp.seconds = report->GetNtpSec();
	ntp.fractions = report->GetNtpFrac();

	this->lastSenderReportNtpMs = Utils::Time::Ntp2TimeMs(ntp);
	this->lastSenderReporTs = report->GetRtpTs();
}

将SR送至RtpStreamRecv处理,RtpStreamRecv主要记录发送端的一些信息,这些信息主要用于之后RR报文的生成。生成RR报文将发送给producer连接的发送客户端。

mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第27张图片

PLI报文的处理
void Transport::HandleRtcpPacket(RTC::RTCP::Packet* packet)
{
    ...
    case RTC::RTCP::FeedbackPs::MessageType::PLI:
    {
        /*通过PLI报文携带的ssrc,找到对应的consumer。*/
        auto* consumer = GetConsumerByMediaSsrc(feedback->GetMediaSsrc());

        if (!consumer){
            MS_DEBUG_TAG(rtcp, "no Consumer found for received PLI Feedback packet ""[sender ssrc:%" PRIu32 ", media ssrc:%" PRIu32 "]", feedback->GetMediaSsrc(), feedback->GetMediaSsrc());

            break;
        }

        MS_DEBUG_TAG(rtcp, "PLI received, requesting key frame for Consumer ""[sender ssrc:%" PRIu32 ", media ssrc:%" PRIu32 "]", feedback->GetMediaSsrc(), feedback->GetMediaSsrc());

        /*让consumer重传关键帧*/
        consumer->ReceiveKeyFrameRequest(RTC::RTCP::FeedbackPs::MessageType::PLI, feedback->GetMediaSsrc());

        break;
    }
    ...
}

只有consumer连接的客户端才会发送PLI报文,所以当mediasoup收到PLI报文后,会交由consumer进行处理。

void SimpleConsumer::ReceiveKeyFrameRequest(RTC::RTCP::FeedbackPs::MessageType messageType, uint32_t ssrc)
{
	MS_TRACE();

	switch (messageType){
	case RTC::RTCP::FeedbackPs::MessageType::PLI:{
		EmitTraceEventPliType(ssrc);

		break;
	}

	case RTC::RTCP::FeedbackPs::MessageType::FIR:{
		EmitTraceEventFirType(ssrc);

		break;
	}
	default:;
	}

	/*将关键帧请求报文发送至rtp发送流,仅记录关键帧重传的次数,并不会重传关键帧。*/
	this->rtpStream->ReceiveKeyFrameRequest(messageType);

	if (IsActive())
		RequestKeyFrame(); /*consumer并不记录关键帧,需要向其关联的producer请求关键帧。让其重发关键帧。*/
}

consumer收到PLI报文后,一方面送至RtpStreamSend处理,另一方面consumer需要向其对应的producer请求关键帧。

void RtpStreamSend::ReceiveKeyFrameRequest(RTC::RTCP::FeedbackPs::MessageType messageType)
{
	MS_TRACE();

	/*记录PLI和FIR请求的数量*/
	switch (messageType)
	{
	case RTC::RTCP::FeedbackPs::MessageType::PLI:
		this->pliCount++;
		break;

	case RTC::RTCP::FeedbackPs::MessageType::FIR:
		this->firCount++;
		break;

	default:;
	}
}

consumer将收到的PLI报文送至RtpStreamSend,RtpStreamSend对PLI报文的处理很简单,只是统计收到PLI的次数。这里统计的次数,仅用于向应用(nodejs层)报告当前收到PLI的次数。

void SimpleConsumer::RequestKeyFrame()
{
	if (this->kind != RTC::Media::Kind::VIDEO)
		return;

	/*根据配置信息,找到consumer对应的mappedSsrc。*/
	auto mappedSsrc = this->consumableRtpEncodings[0].ssrc;

	/*通知其订阅者transport,请求发送关键帧。*/
	this->listener->OnConsumerKeyFrameRequested(this, mappedSsrc);
}

Consumer通过transport,通知关联的Producer发送关键帧。

inline void Transport::OnConsumerKeyFrameRequested(RTC::Consumer* consumer, uint32_t mappedSsrc)
{
	if (!IsConnected()){
		MS_WARN_TAG(rtcp, "ignoring key rame request (transport not connected)");
		return;
	}

	/*通知其订阅者router,consumer请求关键帧。*/
	this->listener->OnTransportConsumerKeyFrameRequested(this, consumer, mappedSsrc);
}
/*transport发送过来consumer的关键帧请求*/
inline void Router::OnTransportConsumerKeyFrameRequested(RTC::Transport* /*transport*/, RTC::Consumer* consumer, uint32_t mappedSsrc)
{
	/*找到consumer对应的producer*/
	auto* producer = this->mapConsumerProducer.at(consumer);

	/*让对应的producer发送关键帧请求报文*/
	producer->RequestKeyFrame(mappedSsrc);
}
void Producer::RequestKeyFrame(uint32_t mappedSsrc)
{
	/*通过conusmer的mappedSsrc找到对应的producer的ssrc,即producer的rtp流中comsumer消费的rtp流的ssrc。*/
	auto it = this->mapMappedSsrcSsrc.find(mappedSsrc);

	/*找到producer中rtp流的ssrc*/
	uint32_t ssrc = it->second;

	if (this->currentRtpPacket && this->currentRtpPacket->GetSsrc() == ssrc && this->currentRtpPacket->IsKeyFrame())
	{
		return;  /*如果当前rtp packet的ssrc匹配,且也是一个关键帧,则直接返回。*/
	}

	/*通过keyFrameRequestManager制作关键帧请求报文,向producer连接的远端,请求关键帧。*/
	this->keyFrameRequestManager->KeyFrameNeeded(ssrc);
}
FIR报文的处理

在mediasoup中FIRPLI的处理很相似,所以对FIR的分析可以参考PLI的处理过程。

NACK报文的处理
void Transport::HandleRtcpPacket(RTC::RTCP::Packet* packet)
{
    ...
    case RTC::RTCP::FeedbackRtp::MessageType::NACK:
    {
        auto* nackPacket = static_cast<RTC::RTCP::FeedbackRtpNackPacket*>(packet);

        /*将收到的nack报文送至consumer,进行进一步的处理。*/
        consumer->ReceiveNack(nackPacket);

        break;
    }
    ...
}
void SimpleConsumer::ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket)
{
	...

	/*通知consumer的rtp发送流,处理nack packet。*/
	this->rtpStream->ReceiveNack(nackPacket);
}

Consumer会将收到的NACK报文送至RTC::RtpStreamSend处理

void RtpStreamSend::ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket)
{
	/*统计收到nack包的次数*/
	this->nackCount++;

	for (auto it = nackPacket->Begin(); it != nackPacket->End(); ++it)
	{
		RTC::RTCP::FeedbackRtpNackItem* item = *it;

		/*统计重传包总数  NACK包中的每一项都可以包含多个重传包序号,统计这一项重传包的个数。*/
		this->nackPacketCount += item->CountRequestedPackets();

		/* 将重传的包,放入vector中。*/
		FillRetransmissionContainer(item->GetPacketId(), item->GetLostPacketBitmask());

         /*遍历所有重传的包*/
		for (auto* storageItem : RetransmissionContainer)
		{
			/*到最后了,没有重传包需要处理了。*/
			if (storageItem == nullptr)
				break;

			/*获取重传包的包数据,准备重传。*/
			auto* packet = storageItem->packet;

			/*通知订阅者consumer重传该包*/
			static_cast<RTC::RtpStreamSend::Listener*>(this->listener)->OnRtpStreamRetransmitRtpPacket(this, packet);

			RTC::RtpStream::PacketRetransmitted(packet);

			if (storageItem->sentTimes == 1)  
				RTC::RtpStream::PacketRepaired(packet);
		}
	}
}

先解析出NACK请求中的RTP序号,然后从缓存中找出所有需要重传的RTP包。将找出的所有重传RTP包,通过consumer将这些RTP再次发送给客户端。

inline void SimpleConsumer::OnRtpStreamRetransmitRtpPacket(RTC::RtpStreamSend* /*rtpStream*/, RTC::RtpPacket* packet)
{
	/*将待重传的packet,发送至transport。*/
	this->listener->OnConsumerRetransmitRtpPacket(this, packet);
}
inline void Transport::OnConsumerRetransmitRtpPacket(RTC::Consumer* /*consumer*/, RTC::RtpPacket* packet)
{
	...
	/*调用子类实现的函数,重新发送要求重传的packet。*/
	SendRtpPacket(packet);
	...
}
void WebRtcTransport::SendRtpPacket(RTC::RtpPacket* packet, RTC::Transport::onSendCallback* cb)
{
        ...
}

DTLS数据的处理

接收DTLS数据
inline void WebRtcTransport::OnDtlsDataReceived(const RTC::TransportTuple* tuple, const uint8_t* data, size_t len)
{
	...	

	if (this->dtlsTransport->GetState() == RTC::DtlsTransport::DtlsState::CONNECTING ||
		this->dtlsTransport->GetState() == RTC::DtlsTransport::DtlsState::CONNECTED){
		MS_DEBUG_DEV("DTLS data received, passing it to the DTLS transport");

		/*将数据送至DtlsTransport,进行解密。*/
		this->dtlsTransport->ProcessDtlsData(data, len);
	}
	...
}

将DTLS数据送至DtlsTransport处理

void DtlsTransport::ProcessDtlsData(const uint8_t* data, size_t len)
{
	...
	/*先将收到的数据存储到BIO中*/
	written = BIO_write(this->sslBioFromNetwork, static_cast<const void*>(data), static_cast<int>(len));


	/*然后调用openssl中SSL_read接口,获取解密后的数据。   读取的解密数据放在了sslReadBuffer中。*/
	read = SSL_read(this->ssl, static_cast<void*>(DtlsTransport::sslReadBuffer), SslReadBufferSize);

	/*握手阶段,用于发送握手信息。*/
	SendPendingOutgoingDtlsData();

	/*应用层数据*/
	if (read > 0){
		/*通知WebRtcTransport,数据已解密。*/
		this->listener->OnDtlsTransportApplicationDataReceived(this, (uint8_t*)DtlsTransport::sslReadBuffer, static_cast<size_t>(read));
	}
}

收到的DTLS数据包,可能是握手数据包,也可能是Sctp数据包。如果是后者,将数据写入BIO后,让dtls解密接收到的数据,然后通过SSL_read()获取解密后的数据,最后通过OnDtlsTransportApplicationDataReceived函数将解密后的数据送至WebRtcTransport。

inline void WebRtcTransport::OnDtlsTransportApplicationDataReceived(const RTC::DtlsTransport* /*dtlsTransport*/, const uint8_t* data, size_t len)
{
		RTC::Transport::ReceiveSctpData(data, len);
}

void Transport::ReceiveSctpData(const uint8_t* data, size_t len)
{
	/*交由sctp association对象处理*/
	this->sctpAssociation->ProcessSctpData(data, len);
}

处理Sctp数据

mediasoup中的所有数据全部都需要加密,普通文本信息通过DTLS加密,而音视频数据通过libsrtp加密。
mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第28张图片

发送DTLS数据
void SctpAssociation::OnUsrSctpSendSctpData(void* buffer, size_t len)
{
	const uint8_t* data = static_cast<uint8_t*>(buffer);

	this->listener->OnSctpAssociationSendData(this, data, len);
}

SctpAssociation发送数据

inline void Transport::OnSctpAssociationSendData(RTC::SctpAssociation* /*sctpAssociation*/, const uint8_t* data, size_t len)
{
	...
	if (this->sctpAssociation)
		SendSctpData(data, len);
}
void WebRtcTransport::SendSctpData(const uint8_t* data, size_t len)
{
	...
	/*直接通过dtls发送数据,数据的解密和解密由DTLS做。*/
	this->dtlsTransport->SendApplicationData(data, len);
}

先将数据通过SSL_write加密后写入到BIO中,经过DTLS加密,再把加密后的数据通过libuv发送出去。

void DtlsTransport::SendApplicationData(const uint8_t* data, size_t len)
{
	...
	/*向SSL中写入数据*/
	written = SSL_write(this->ssl, static_cast<const void*>(data), static_cast<int>(len));

	/*加密后的数据被写入了BIO中,现在将BIO中的数据通过libuv发送出去。*/
	SendPendingOutgoingDtlsData();
}
inline void DtlsTransport::SendPendingOutgoingDtlsData()
{
	/*没有数据可发送,则直接返回。*/
	if (BIO_eof(this->sslBioToNetwork))
		return;
	...

	/*从BIO中获取已经加密的数据*/
	read = BIO_get_mem_data(this->sslBioToNetwork, &data); // NOLINT

	/*没有数据可发送,直接返回。*/
	if (read <= 0)
		return;

	/*通知其订阅者,发送加密后的数据,加密数据放在data中,长度为read。*/
	this->listener->OnDtlsTransportSendData(this, reinterpret_cast<uint8_t*>(data), static_cast<size_t>(read));
	...
}

从BIO中获取解密后的数据,并将其发送出去。调用这个函数的时候,可能发送的是Sctp数据,也可能发送的是握手数据。

inline void WebRtcTransport::OnDtlsTransportSendData(const RTC::DtlsTransport* /*dtlsTransport*/, const uint8_t* data, size_t len)
{
	...
	/*使用ice建立的数据通道发送数据*/
	this->iceServer->GetSelectedTuple()->Send(data, len);
	...
}

mediasoup源码分析-初始化、建立连接及媒体数据的处理流程_第29张图片

STUN数据的处理

inline void WebRtcTransport::OnStunDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len)
{
	MS_TRACE();

	/*将收到的STUN packet解析成StunPacket对象*/
	RTC::StunPacket* packet = RTC::StunPacket::Parse(data, len);


	/*将这个StunPacket对象传递至iceServer处理*/
	this->iceServer->ProcessStunPacket(packet, tuple);

	delete packet;
}

待补充…

你可能感兴趣的:(mediasoup源码分析,mediasoup,流媒体服务器,c++)