流媒体弱网优化之路(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 初识