流媒体学习之路(mediasoup)——simulcast 与 svc 简介(7)
——
我正在的github给大家开发一个用于做实验的项目 —— github.com/qw225967/Bifrost
目标:可以让大家熟悉各类Qos能力、带宽估计能力,提供每个环节关键参数调节接口并实现一个json全配置,提供全面的可视化算法观察能力。
欢迎大家使用
——
文章目录
前面的内容解析了拥塞控制模块,它的作用只是将网络的状况进行及时地反馈以及预测,但是如何在弱网环境下进行可靠传输并没有详细的介绍。当网络传输出现问题(例如:带宽下降、丢包上涨、延迟增加等),那么如何让用户在弱网条件下拥有更好的体验呢?答案是我们降低发送的码率,牺牲观看的清晰度并维持流畅的播放是可行的。下面我们将介绍mediasoup中两种从客户端入手进行码率调整的方案:simulcast 和 SVC(Scalable Video Coding),相关的文档可以浏览mediasoup的官方介绍文档。
一、Simulcast
1.1 simulcast简介
simulcast直译过来是多播的意思,其实就是一个客户端发送多条不同码率大小的流,为了达到这样的效果,客户端会对同一帧画面进行不同分辨率的编码来达到降低码率的目的。

例如上图,推流客户端同时推两路大小不一的流随后在服务器对下行网络进行估计,网络正常时下发720P分辨率的流,网络发生拥塞时下发360P分辨率的流,就能实现码率下降来抵抗弱网。
1.2 mediasoup中的Simulcast
simulcast模块集成在 SimulcastConsumer 类中——SimulcastConsumer.hpp 和 SimulcastConsumer.cpp。下面给出该类的头文件:
class SimulcastConsumer : public RTC::Consumer, public RTC::RtpStreamSend::Listener
{
public:
SimulcastConsumer(
const std::string& id,
const std::string& producerId,
RTC::Consumer::Listener* listener,
json& data);
~SimulcastConsumer() override;
public:
void FillJson(json& jsonObject) const override;
void FillJsonStats(json& jsonArray) const override;
void FillJsonScore(json& jsonObject) const override;
void HandleRequest(Channel::Request* request) override;
RTC::Consumer::Layers GetPreferredLayers() const override
{
RTC::Consumer::Layers layers;
layers.spatial = this->preferredSpatialLayer;
layers.temporal = this->preferredTemporalLayer;
return layers;
}
bool IsActive() const override
{
// clang-format off
return (
RTC::Consumer::IsActive() &&
std::any_of(
this->producerRtpStreams.begin(),
this->producerRtpStreams.end(),
[](const RTC::RtpStream* rtpStream)
{
return (rtpStream != nullptr && rtpStream->GetScore() > 0u);
}
)
);
// clang-format on
}
void ProducerRtpStream(RTC::RtpStream* rtpStream, uint32_t mappedSsrc) override;
void ProducerNewRtpStream(RTC::RtpStream* rtpStream, uint32_t mappedSsrc) override;
void ProducerRtpStreamScore(RTC::RtpStream* rtpStream, uint8_t score, uint8_t previousScore) override;
void ProducerRtcpSenderReport(RTC::RtpStream* rtpStream, bool first) override;
uint8_t GetBitratePriority() const override;
uint32_t IncreaseLayer(uint32_t bitrate, bool considerLoss) override;
void ApplyLayers() override;
uint32_t GetDesiredBitrate() const override;
void SendRtpPacket(RTC::RtpPacket* packet) override;
void GetRtcp(RTC::RTCP::CompoundPacket* packet, RTC::RtpStreamSend* rtpStream, uint64_t nowMs) override;
std::vector<RTC::RtpStreamSend*> GetRtpStreams() override
{
return this->rtpStreams;
}
void NeedWorstRemoteFractionLost(uint32_t mappedSsrc, uint8_t& worstRemoteFractionLost) override;
void ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket) override;
void ReceiveKeyFrameRequest(RTC::RTCP::FeedbackPs::MessageType messageType, uint32_t ssrc) override;
void ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report) override;
uint32_t GetTransmissionRate(uint64_t nowMs) override;
float GetRtt() const override;
private:
void UserOnTransportConnected() override;
void UserOnTransportDisconnected() override;
void UserOnPaused() override;
void UserOnResumed() override;
void CreateRtpStream();
void RequestKeyFrames();
void RequestKeyFrameForTargetSpatialLayer();
void RequestKeyFrameForCurrentSpatialLayer();
void MayChangeLayers(bool force = false);
bool RecalculateTargetLayers(int16_t& newTargetSpatialLayer, int16_t& newTargetTemporalLayer) const;
void UpdateTargetLayers(int16_t newTargetSpatialLayer, int16_t newTargetTemporalLayer);
bool CanSwitchToSpatialLayer(int16_t spatialLayer) const;
void EmitScore() const;
void EmitLayersChange() const;
RTC::RtpStream* GetProducerCurrentRtpStream() const;
RTC::RtpStream* GetProducerTargetRtpStream() const;
RTC::RtpStream* GetProducerTsReferenceRtpStream() const;
/* Pure virtual methods inherited from RtpStreamSend::Listener. */
public:
void OnRtpStreamScore(RTC::RtpStream* rtpStream, uint8_t score, uint8_t previousScore) override;
void OnRtpStreamRetransmitRtpPacket(RTC::RtpStreamSend* rtpStream, RTC::RtpPacket* packet) override;
private:
// Allocated by this.
RTC::RtpStreamSend* rtpStream{
nullptr };
// Others.
std::unordered_map<uint32_t, int16_t> mapMappedSsrcSpatialLayer;
std::vector<RTC::RtpStreamSend*> rtpStreams;
std::vector<RTC::RtpStream*> producerRtpStreams; // Indexed by spatial layer.
bool syncRequired{
false };
RTC::SeqManager<uint16_t> rtpSeqManager;
int16_t preferredSpatialLayer{
-1 };
int16_t preferredTemporalLayer{
-1 };
int16_t provisionalTargetSpatialLayer{
-1 };
int16_t provisionalTargetTemporalLayer{
-1 };
int16_t targetSpatialLayer{
-1 };
int16_t targetTemporalLayer{
-1 };
int16_t currentSpatialLayer{
-1 };
int16_t tsReferenceSpatialLayer{
-1 }; // Used for RTP TS sync.
std::unique_ptr<RTC::Codecs::EncodingContext> encodingContext;
uint32_t tsOffset{
0u }; // RTP Timestamp offset.
bool keyFrameForTsOffsetRequested{
false };
uint64_t lastBweDowngradeAtMs{
0u }; // Last time we moved to lower spatial layer due to BWE.
};
这里需要提到成员命名中的空间层和时间层的概念,码率其实是一个数据大小与时间的比值,那么是两个维度的衡量值,因此我们控制发送码率可以通过两个维度去调整,一个是空间层级、一个是时间层级。与空间强相关的就是我们的分辨率——也就是每张图片的大小,而与时间强相关的则是帧率——也就是每秒多少张图,这样我们就可以控制流的大小了。
而代码中关键成员为下列几个(我们可以直接理解空间为分辨率标志,时间为帧率标志):
空间层级:currentSpatialLayer(当前大小)、targetSpatialLayer(目标大小)、preferredSpatialLayer(最佳大小)、provisionalTargetSpatialLayer(临时大小)、tsReferenceSpatialLayer(参考空间);
时间层级:preferredTemporalLayer(最佳时间)、provisionalTargetTemporalLayer(参考时间)、targetTemporalLayer(目标时间)。
以上的成员作为 producerRtpStreams 管理的标识来对流进行分辨,当有新的流生成时,会根据流中的参数放到producerRtpStreams中等待切换。具体代码如下:
void SimulcastConsumer::ProducerRtpStream(RTC::RtpStream* rtpStream, uint32_t mappedSsrc)
{
MS_TRACE();

本文介绍了mediasoup中用于适应弱网环境的Simulcast和SVC技术。Simulcast通过发送不同码率的流,根据网络状况动态调整;SVC则利用可伸缩编码,在不牺牲流畅性的情况下降低视频质量。文中详细解析了这两个技术的实现原理,包括流的创建、码率调整和传输过程。同时指出了Simulcast的上行带宽浪费和SVC的编码限制问题。
最低0.47元/天 解锁文章
2016

被折叠的 条评论
为什么被折叠?



