流媒体弱网优化之路(mediasoup)——H264-SVC介绍和使用

Posted dog head

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了流媒体弱网优化之路(mediasoup)——H264-SVC介绍和使用相关的知识,希望对你有一定的参考价值。

流媒体弱网优化之路(mediasoup)——H264-SVC介绍和使用

文章目录


一、背景

  2022年5月25日,mediasoup提交了基于H264-SVC的修改,开始正式支持H264-SVC。在这之前,mediasoup只支持VP9的SVC能力,而国内很多厂商使用的都是H264编码,包括我目前也是,但幸运的是我正要做H264-SVC就立刻等到了mediasoup的更新,通过这篇文章咱聊聊。

// RTC/Codecs/Tools.hpp

...

	case RTC::RtpParameters::Type::SVC:
	
		switch (mimeType.type)
		
			case RTC::RtpCodecMimeType::Type::VIDEO:
			
				switch (mimeType.subtype)
				
					case RTC::RtpCodecMimeType::Subtype::VP9:
					case RTC::RtpCodecMimeType::Subtype::H264_SVC:
						return true;
					default:
						return false;
				
			
	
			default:
			
				return false;
			
		
	

...

  前面有一篇介绍mediasoup-SVC的文章——写的比较简单——但是基本SVC的一些概念以及mediasoup中的逻辑已经介绍了,大家可以回顾一下。下面我会对mediasoup的一些拓展的细节进行描述。

mediasoup相关细节

细节1:数据包的类型怎么区分?

  WebRTC的Rtp数据除了根据PT进行区分,还会使用拓展字段去做一些额外的信息携带。在mediasoup中,Rtp数据来到Producer后会立刻进入前处理函数——PreProcessRtpPacket——一旦是Video数据就会打上 frameMarking07、frameMarking 这两个拓展字段。

// src/RTC/Producer.cpp

...

	inline void Producer::PreProcessRtpPacket(RTC::RtpPacket* packet)
	
		MS_TRACE();

		if (this->kind == RTC::Media::Kind::VIDEO)
		
			// NOTE: Remove this once framemarking draft becomes RFC.
			packet->SetFrameMarking07ExtensionId(this->rtpHeaderExtensionIds.frameMarking07);
			packet->SetFrameMarkingExtensionId(this->rtpHeaderExtensionIds.frameMarking);
		
	
	
...

  打完拓展字段的标识我们就可以在RtpStreamRecv这个位置给每个包创建payloadDescriptorHandler(载荷描述处理能力)。

// src/RTC/RtpStreamRecv.cpp

...

	bool RtpStreamRecv::ReceivePacket(RTC::RtpPacket* packet)
	
		...
		
		// Process the packet at codec level.
		if (packet->GetPayloadType() == GetPayloadType())
			RTC::Codecs::Tools::ProcessRtpPacket(packet, GetMimeType());
	
		...
	
	
...

// include/RTC/Codecs/Tools.hpp

	static void ProcessRtpPacket(RTC::RtpPacket* packet, const RTC::RtpCodecMimeType& mimeType)
	
		switch (mimeType.type)
		
			case RTC::RtpCodecMimeType::Type::VIDEO:
			
				switch (mimeType.subtype)
				
					case RTC::RtpCodecMimeType::Subtype::VP8:
					
						RTC::Codecs::VP8::ProcessRtpPacket(packet);
	
						break;
					
	
					case RTC::RtpCodecMimeType::Subtype::VP9:
					
						RTC::Codecs::VP9::ProcessRtpPacket(packet);
	
						break;
					
	
					case RTC::RtpCodecMimeType::Subtype::H264:
					
						RTC::Codecs::H264::ProcessRtpPacket(packet);
	
						break;
					
					case RTC::RtpCodecMimeType::Subtype::H264_SVC:
					
						RTC::Codecs::H264_SVC::ProcessRtpPacket(packet);
	
						break;
					
	
					default:;
				
			
	
			default:;
		
	
	
...

  然后会在——MangleRtpPacket——中打上所有需要用的拓展字段。(这里我觉得mediasoup这样的实现不好,因为Consumer和Producer完全独立分开的,原意是在入口处把支持的所有拓展头都先预留出位置,在发送时再判断是否有打上具体的内容,如果没有就不会被序列化。但是这样的实现导致Consumer和Producer耦合了,我们在后续独立开发Consumer的功能时不得不考虑连Producer一起修改)。

// src/RTC/Producer.cpp
	inline bool Producer::MangleRtpPacket(RTC::RtpPacket* packet, RTC::RtpStreamRecv* rtpStream) const
	
		MS_TRACE();

		// Mangle the payload type.
		
			uint8_t payloadType = packet->GetPayloadType();
			auto it             = this->rtpMapping.codecs.find(payloadType);

			if (it == this->rtpMapping.codecs.end())
			
				MS_WARN_TAG(rtp, "unknown payload type [payloadType:%" PRIu8 "]", payloadType);

				return false;
			

			uint8_t mappedPayloadType = it->second;

			packet->SetPayloadType(mappedPayloadType);
		

		// Mangle the SSRC.
		
			uint32_t mappedSsrc = this->mapRtpStreamMappedSsrc.at(rtpStream);

			packet->SetSsrc(mappedSsrc);
		

		// Mangle RTP header extensions.
		
			thread_local static uint8_t buffer[4096];
			thread_local static std::vector<RTC::RtpPacket::GenericExtension> extensions;

			// This happens just once.
			if (extensions.capacity() != 24)
				extensions.reserve(24);

			extensions.clear();

			uint8_t* extenValue;
			uint8_t extenLen;
			uint8_t* bufferPtr buffer ;

			// Add urn:ietf:params:rtp-hdrext:sdes:mid.
			
				extenLen = RTC::MidMaxLength;

				extensions.emplace_back(
				  static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::MID), extenLen, bufferPtr);

				bufferPtr += extenLen;
			

			if (this->kind == RTC::Media::Kind::AUDIO)
			
				// Proxy urn:ietf:params:rtp-hdrext:ssrc-audio-level.
				extenValue = packet->GetExtension(this->rtpHeaderExtensionIds.ssrcAudioLevel, extenLen);

				if (extenValue)
				
					std::memcpy(bufferPtr, extenValue, extenLen);

					extensions.emplace_back(
					  static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::SSRC_AUDIO_LEVEL),
					  extenLen,
					  bufferPtr);

					// Not needed since this is the latest added extension.
					// bufferPtr += extenLen;
				
			
			else if (this->kind == RTC::Media::Kind::VIDEO)
			
				// Add http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time.
				
					extenLen = 3u;

					// NOTE: Add value 0. The sending Transport will update it.
					uint32_t absSendTime 0u ;

					Utils::Byte::Set3Bytes(bufferPtr, 0, absSendTime);

					extensions.emplace_back(
					  static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::ABS_SEND_TIME), extenLen, bufferPtr);

					bufferPtr += extenLen;
				

				// Add http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01.
				
					extenLen = 2u;

					// NOTE: Add value 0. The sending Transport will update it.
					uint16_t wideSeqNumber 0u ;

					Utils::Byte::Set2Bytes(bufferPtr, 0, wideSeqNumber);

					extensions.emplace_back(
					  static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::TRANSPORT_WIDE_CC_01),
					  extenLen,
					  bufferPtr);

					bufferPtr += extenLen;
				

				// NOTE: Remove this once framemarking draft becomes RFC.
				// Proxy http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07.
				extenValue = packet->GetExtension(this->rtpHeaderExtensionIds.frameMarking07, extenLen);

				if (extenValue)
				
					std::memcpy(bufferPtr, extenValue, extenLen);

					extensions.emplace_back(
					  static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::FRAME_MARKING_07),
					  extenLen,
					  bufferPtr);

					bufferPtr += extenLen;
				

				// Proxy urn:ietf:params:rtp-hdrext:framemarking.
				extenValue = packet->GetExtension(this->rtpHeaderExtensionIds.frameMarking, extenLen);

				if (extenValue)
				
					std::memcpy(bufferPtr, extenValue, extenLen);

					extensions.emplace_back(
					  static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::FRAME_MARKING), extenLen, bufferPtr);

					bufferPtr += extenLen;
				

				// Proxy urn:3gpp:video-orientation.
				extenValue = packet->GetExtension(this->rtpHeaderExtensionIds.videoOrientation, extenLen);

				if (extenValue)
				
					std::memcpy(bufferPtr, extenValue, extenLen);

					extensions.emplace_back(
					  static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::VIDEO_ORIENTATION),
					  extenLen,
					  bufferPtr);

					bufferPtr += extenLen;
				

				// Proxy urn:ietf:params:rtp-hdrext:toffset.
				extenValue = packet->GetExtension(this->rtpHeaderExtensionIds.toffset, extenLen);

				if (extenValue)
				
					std::memcpy(bufferPtr, extenValue, extenLen);

					extensions.emplace_back(
					  static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::TOFFSET), extenLen, bufferPtr);

					bufferPtr += extenLen;
				

				// Proxy http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time.
				extenValue = packet->GetExtension(this->rtpHeaderExtensionIds.absCaptureTime, extenLen);

				if (extenValue)
				
					std::memcpy(bufferPtr, extenValue, extenLen);

					extensions.emplace_back(
					  static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::ABS_CAPTURE_TIME),
					  extenLen,
					  bufferPtr);

					// Not needed since this is the latest added extension.
					// bufferPtr += extenLen;
				
			

			// Set the new extensions into the packet using One-Byte format.
			packet->SetExtensions(1, extensions);

			// Assign mediasoup RTP header extension ids (just those that mediasoup may
			// be interested in after passing it to the Router).
			packet->SetMidExtensionId(static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::MID));
			packet->SetAbsSendTimeExtensionId(
			  static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::ABS_SEND_TIME));
			packet->SetTransportWideCc01ExtensionId(
			  static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::TRANSPORT_WIDE_CC_01));
			// NOTE: Remove this once framemarking draft becomes RFC.
			packet->SetFrameMarking07ExtensionId(
			  static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::FRAME_MARKING_07));
			packet->SetFrameMarkingExtensionId(
			  static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::FRAME_MARKING));
			packet->SetSsrcAudioLevelExtensionId(
			  static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::SSRC_AUDIO_LEVEL));
			packet->SetVideoOrientationExtensionId(
			  static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::VIDEO_ORIENTATION));
		

		return true;
	

...

  Producer处理完后调用自己的观察者上抛回Transport,而Transport管理了多个Consumer。在创建Consumer的时候已经根据拉流的信令来确定了SvcConsumer,于是Consumer中的Codecs就挂载上了H264-SVC的Codecs。Transport依次遍历所有的Consumer将数据下发到Consumer的SendRtpPacket函数,在这个函数中就会根据Codecs去识别payload的类型了。

// src/RTC/SvcConsumer.cpp	

	void SvcConsumer::SendRtpPacket(RTC::RtpPacket* packet)
	
	
	...
	
		bool marker false ;
		bool origMarker = packet->HasMarker();

		if (!packet->ProcessPayload(this->encodingContext.get(), marker))
		
			this->rtpSeqManager.Drop(packet->GetSequenceNumber());

			return;
		
		
	...
	
	

细节2:GCC怎么与Consumer关联?

  GCC在mediasoup中是由TransportCongestionControlClient、TransportCongestionControlSever两个类来进行集成的,大家可以参考一下我这篇文章。这两个类都是在Transport这一层,大家可以发现Transport的概念更多的倾向于一个终端对一个Transport。怎么理解呢?我们一个Transport中会存在多个Consumer,而每个Consumer都可以理解为一条对应的流。
  方便理解,咱们以腾讯会议为例。一个主持人可能需要使用屏幕分享、视频交互、语音交互三条数据流,那么很合理的是一个用户如果需要同时看这三条流就需要拉三路。也就是说,一个用户(终端)需要同时拉三条流或者更多(多人会议)。但是对于服务端来说与该用户传输数据的网络是同一个,那么顺理成章的带宽估计就必须对那三条数据同时进行估计。


  所以mediasoup在Transport级别创建了TransportCongestionControlClient类。但是这个带宽估计是需要结合每个数据包的TCC拓展字段来统计Feedback的,那么每个数据包就都需要在发送给下行之前打上TCC的拓展头下行才能根据拓展头字段计算delta来进行到达记录。上文介绍到在Producer中已经为所有支持的拓展头都预留了位置,具体写入内容的地方在Transport发送的地方(经过Consumer上抛回来)。

// src/RTC/Transport.cpp

...

// 里边这个宏 ENABLE_RTC_SENDER_BANDWIDTH_ESTIMATOR 是mediasoup自己实现的一个简单的带宽估计,一般是关闭的
// tccClient是使用webrtc内部的带宽估计集成的

	inline void Transport::OnConsumerSendRtpPacket(RTC::Consumer* consumer, RTC::RtpPacket* packet)
	
		MS_TRACE();

		// Update abs-send-time if present.
		packet->UpdateAbsSendTime(DepLibUV::GetTimeMs());

		// Update transport wide sequence number if present.
		// clang-format off
		if (
			this->tccClient &&
			this->tccClient->GetBweType() == RTC::BweType::TRANSPORT_CC &&
			packet->UpdateTransportWideCc01(this->transportWideCcSeq + 1)
		)
		// clang-format on
		
			this->transportWideCcSeq++;

			auto* tccClient = this->tccClient;
			webrtc::RtpPacketSendInfo packetInfo;

			packetInfo.ssrc                      = packet->GetSsrc();
			packetInfo.transport_sequence_number = this->transportWideCcSeq;
			packetInfo.has_rtp_sequence_number   = true;
			packetInfo.rtp_sequence_number       = packet->GetSequenceNumber();
			packetInfo.length                    = packet->GetSize();
			packetInfo.pacing_info               = this->tccClient->GetPacingInfo();

			// Indicate the pacer (and prober) that a packet is to be sent.
			this->tccClient->InsertPacket(packetInfo);

#ifdef ENABLE_RTC_SENDER_BANDWIDTH_ESTIMATOR
			auto* senderBwe = this->senderBwe;
			RTC::SenderBandwidthEstimator::SentInfo sentInfo;

			sentInfo.wideSeq     = this->transportWideCcSeq;
			sentInfo.size        = packet->GetSize();
			sentInfo.sendingAtMs = DepLibUV::GetTimeMs();

			auto* cb = new onSendCallback(
			  [tccClient, &packetInfo, senderBwe, &sentInfo](bool sent)
			  
				  if (sent)
				  
					  tccClient->PacketSent(packetInfo, DepLibUV::GetTimeMsInt64());

					  sentInfo.sentAtMs = DepLibUV::GetTimeMs();

					  senderBwe->RtpPacketSent(sentInfo);
				  
			  );

			SendRtpPacket(consumer, packet, cb);
#else
			const auto* cb = new onSendCallback(
			  [tccClient, &packetInfo](bool sent)
			  
				  if (sent)
					  tccClient->PacketSent(packetInfo, DepLibUV::GetTimeMsInt64());
			  );

			SendRtpPacket(consumer, packet, cb)以上是关于流媒体弱网优化之路(mediasoup)——H264-SVC介绍和使用的主要内容,如果未能解决你的问题,请参考以下文章

流媒体服务器(17)—— 流媒体开源服务 MediaSoup 初识

WebRTC进阶流媒体服务器开发Mediasoup源码分析之Mediasoup主业务流程

流媒体开源服务 MediaSoup 初识

用SRS搭建WebRTC流媒体服务器实战

物联网架构成长之路(42)-直播流媒体入门(RTMP篇)

物联网架构成长之路(41)-直播流媒体入门(RTSP篇)