From be961ea692db053ab7588868fbcd3cb775b688b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Wed, 11 Feb 2026 12:37:37 +0100 Subject: [PATCH 01/37] Worker: New Consumer class handling all possible producer stream modes. New ProducerStreamManager interface with the following implementations: - SimpleProducerStreamManager: handles a single stream with no spatial or temporal layers. - SimulcastProducerStreamManager: handles N streams with 1 spatial and N temporal layers. - SvcProducerStreamManager: handles 1 stream with N spatial and M temporal layers. Consumer + SimpleProducerStreamManager replaces SimpleConsumer. Consumer + SimulcastProducerStreamManager replaces SimulcastConsumer. Consumer + SvcProducerStreamManager replaces SvcConsumer. --- worker/include/RTC/Consumer.hpp | 125 +- worker/include/RTC/OldConsumer.hpp | 211 +++ worker/include/RTC/PipeConsumer.hpp | 6 +- worker/include/RTC/ProducerStreamManager.hpp | 183 +++ worker/include/RTC/SimpleConsumer.hpp | 8 +- .../RTC/SimpleProducerStreamManager.hpp | 67 + worker/include/RTC/SimulcastConsumer.hpp | 8 +- .../RTC/SimulcastProducerStreamManager.hpp | 83 ++ worker/include/RTC/SvcConsumer.hpp | 8 +- .../include/RTC/SvcProducerStreamManager.hpp | 67 + worker/meson.build | 6 +- worker/src/RTC/Consumer.cpp | 1268 ++++++++++++++++- worker/src/RTC/OldConsumer.cpp | 603 ++++++++ worker/src/RTC/PipeConsumer.cpp | 9 +- worker/src/RTC/SimpleConsumer.cpp | 9 +- .../src/RTC/SimpleProducerStreamManager.cpp | 323 +++++ worker/src/RTC/SimulcastConsumer.cpp | 10 +- .../RTC/SimulcastProducerStreamManager.cpp | 1004 +++++++++++++ worker/src/RTC/SvcConsumer.cpp | 11 +- worker/src/RTC/SvcProducerStreamManager.cpp | 635 +++++++++ worker/src/RTC/Transport.cpp | 46 +- ...estSimpleConsumer.cpp => TestConsumer.cpp} | 11 +- 22 files changed, 4524 insertions(+), 177 deletions(-) create mode 100644 worker/include/RTC/OldConsumer.hpp create mode 100644 worker/include/RTC/ProducerStreamManager.hpp create mode 100644 worker/include/RTC/SimpleProducerStreamManager.hpp create mode 100644 worker/include/RTC/SimulcastProducerStreamManager.hpp create mode 100644 worker/include/RTC/SvcProducerStreamManager.hpp create mode 100644 worker/src/RTC/OldConsumer.cpp create mode 100644 worker/src/RTC/SimpleProducerStreamManager.cpp create mode 100644 worker/src/RTC/SimulcastProducerStreamManager.cpp create mode 100644 worker/src/RTC/SvcProducerStreamManager.cpp rename worker/test/src/RTC/{TestSimpleConsumer.cpp => TestConsumer.cpp} (97%) diff --git a/worker/include/RTC/Consumer.hpp b/worker/include/RTC/Consumer.hpp index 86f6333342..de764bd9d8 100644 --- a/worker/include/RTC/Consumer.hpp +++ b/worker/include/RTC/Consumer.hpp @@ -7,6 +7,7 @@ #include "FBS/consumer.h" #include "FBS/transport.h" #include "RTC/ConsumerTypes.hpp" +#include "RTC/ProducerStreamManager.hpp" #include "RTC/RTCP/CompoundPacket.hpp" #include "RTC/RTCP/FeedbackRtpNack.hpp" #include "RTC/RTCP/ReceiverReport.hpp" @@ -16,14 +17,21 @@ #include "RTC/RTP/RtpStreamSend.hpp" #include "RTC/RTP/SharedPacket.hpp" #include "RTC/RtpDictionaries.hpp" +#include "RTC/SeqManager.hpp" #include "RTC/Shared.hpp" #include +#include +#include #include #include namespace RTC { - class Consumer : public Channel::ChannelSocket::RequestHandler + using namespace ConsumerTypes; + + class Consumer : public Channel::ChannelSocket::RequestHandler, + public RTC::RTP::RtpStreamSend::Listener, + public RTC::ProducerStreamManager::Listener { public: class Listener @@ -56,20 +64,18 @@ namespace RTC const std::string& id, const std::string& producerId, RTC::Consumer::Listener* listener, - const FBS::Transport::ConsumeRequest* data, - RTC::RtpParameters::Type type); + const FBS::Transport::ConsumeRequest* data); ~Consumer() override; public: - flatbuffers::Offset FillBuffer( + flatbuffers::Offset FillBuffer( + flatbuffers::FlatBufferBuilder& builder) const; + flatbuffers::Offset FillBufferBase( + flatbuffers::FlatBufferBuilder& builder) const; + flatbuffers::Offset FillBufferStats( + flatbuffers::FlatBufferBuilder& builder); + flatbuffers::Offset FillBufferScore( flatbuffers::FlatBufferBuilder& builder) const; - virtual flatbuffers::Offset FillBufferStats( - flatbuffers::FlatBufferBuilder& builder) = 0; - virtual flatbuffers::Offset FillBufferScore( - flatbuffers::FlatBufferBuilder& /*builder*/) const - { - return 0; - }; RTC::Media::Kind GetKind() const { return this->kind; @@ -86,12 +92,9 @@ namespace RTC { return this->type; } - virtual RTC::ConsumerTypes::VideoLayers GetPreferredLayers() const + VideoLayers GetPreferredLayers() const { - // By default return 1:1. - RTC::ConsumerTypes::VideoLayers layers; - - return layers; + return this->producerStreamManager->GetPreferredLayers(); } const std::vector& GetMediaSsrcs() const { @@ -101,16 +104,15 @@ namespace RTC { return this->rtxSsrcs; } - virtual bool IsActive() const + bool IsActive() const override { - // The parent Consumer just checks whether Consumer and Producer are - // not paused and the transport connected. // clang-format off return ( this->transportConnected && !this->paused && !this->producerPaused && - !this->producerClosed + !this->producerClosed && + this->producerStreamManager->IsProducerStreamActive() ); // clang-format on } @@ -126,32 +128,34 @@ namespace RTC } void ProducerPaused(); void ProducerResumed(); - virtual void ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) = 0; - virtual void ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) = 0; + void ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc); + void ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc); void ProducerRtpStreamScores(const std::vector* scores); - virtual void ProducerRtpStreamScore( - RTC::RTP::RtpStreamRecv* rtpStream, uint8_t score, uint8_t previousScore) = 0; - virtual void ProducerRtcpSenderReport(RTC::RTP::RtpStreamRecv* rtpStream, bool first) = 0; + void ProducerRtpStreamScore(RTC::RTP::RtpStreamRecv* rtpStream, uint8_t score, uint8_t previousScore); + void ProducerRtcpSenderReport(RTC::RTP::RtpStreamRecv* rtpStream, bool first); void ProducerClosed(); void SetExternallyManagedBitrate() { this->externallyManagedBitrate = true; + this->producerStreamManager->SetExternallyManagedBitrate(); + } + uint8_t GetBitratePriority() const; + uint32_t IncreaseLayer(uint32_t bitrate, bool considerLoss); + void ApplyLayers(); + uint32_t GetDesiredBitrate() const; + void SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket); + bool GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs); + const std::vector& GetRtpStreams() const + { + return this->rtpStreams; } - virtual uint8_t GetBitratePriority() const = 0; - virtual uint32_t IncreaseLayer(uint32_t bitrate, bool considerLoss) = 0; - virtual void ApplyLayers() = 0; - virtual uint32_t GetDesiredBitrate() const = 0; - virtual void SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket) = 0; - virtual bool GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs) = 0; - virtual const std::vector& GetRtpStreams() const = 0; - virtual void NeedWorstRemoteFractionLost(uint32_t mappedSsrc, uint8_t& worstRemoteFractionLost) = 0; - virtual void ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket) = 0; - virtual void ReceiveKeyFrameRequest( - RTC::RTCP::FeedbackPs::MessageType messageType, uint32_t ssrc) = 0; - virtual void ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report) = 0; - virtual void ReceiveRtcpXrReceiverReferenceTime(RTC::RTCP::ReceiverReferenceTime* report) = 0; - virtual uint32_t GetTransmissionRate(uint64_t nowMs) = 0; - virtual float GetRtt() const = 0; + void NeedWorstRemoteFractionLost(uint32_t mappedSsrc, uint8_t& worstRemoteFractionLost); + void ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket); + void ReceiveKeyFrameRequest(RTC::RTCP::FeedbackPs::MessageType messageType, uint32_t ssrc); + void ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report); + void ReceiveRtcpXrReceiverReferenceTime(RTC::RTCP::ReceiverReferenceTime* report); + uint32_t GetTransmissionRate(uint64_t nowMs); + float GetRtt() const; /* Methods inherited from Channel::ChannelSocket::RequestHandler. */ public: @@ -165,10 +169,33 @@ namespace RTC void EmitTraceEvent(flatbuffers::Offset& notification) const; private: - virtual void UserOnTransportConnected() = 0; - virtual void UserOnTransportDisconnected() = 0; - virtual void UserOnPaused() = 0; - virtual void UserOnResumed() = 0; + void EmitScore() const; + void EmitLayersChange() const; + + private: + void UserOnTransportConnected(); + void UserOnTransportDisconnected(); + void UserOnPaused(); + void UserOnResumed(); + + private: + void CreateRtpStream(); + void StorePacketInTargetLayerRetransmissionBuffer( + RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket); + + /* Pure virtual methods inherited from RtpStreamSend::Listener. */ + public: + void OnRtpStreamScore(RTC::RTP::RtpStream* rtpStream, uint8_t score, uint8_t previousScore) override; + void OnRtpStreamRetransmitRtpPacket( + RTC::RTP::RtpStreamSend* rtpStream, RTC::RTP::Packet* packet) override; + + /* Pure virtual methods inherited from ProducerStreamManager::Listener. */ + public: + void OnProducerStreamManagerKeyFrameRequested(uint32_t mappedSsrc) override; + void OnProducerStreamManagerNeedBitrateChange() override; + void OnProducerStreamManagerLayersChanged() override; + void OnProducerStreamManagerClearRetransmissionBuffer() override; + void OnProducerStreamManagerScore() override; public: // Passed by argument. @@ -186,8 +213,6 @@ namespace RTC struct RTC::RTP::HeaderExtensionIds rtpHeaderExtensionIds; const std::vector* producerRtpStreamScores{ nullptr }; // Others. - // Whether a payload type is supported or not is represented in the - // corresponding position of the bitset. std::bitset<128u> supportedCodecPayloadTypes; uint64_t lastRtcpSentTime{ 0u }; uint16_t maxRtcpInterval{ 0u }; @@ -196,13 +221,21 @@ namespace RTC struct TraceEventTypes traceEventTypes; private: + // Allocated by this. + RTC::RTP::RtpStreamSend* rtpStream{ nullptr }; // Others. + std::vector rtpStreams; std::vector mediaSsrcs; std::vector rtxSsrcs; bool transportConnected{ false }; bool paused{ false }; bool producerPaused{ false }; bool producerClosed{ false }; + bool lastSentPacketHasMarker{ false }; + RTC::SeqManager rtpSeqManager; + std::unique_ptr producerStreamManager; + std::map::SeqLowerThan> + targetLayerRetransmissionBuffer; }; } // namespace RTC diff --git a/worker/include/RTC/OldConsumer.hpp b/worker/include/RTC/OldConsumer.hpp new file mode 100644 index 0000000000..d3ad7c42dc --- /dev/null +++ b/worker/include/RTC/OldConsumer.hpp @@ -0,0 +1,211 @@ +#ifndef MS_RTC_OLD_CONSUMER_HPP +#define MS_RTC_OLD_CONSUMER_HPP + +#include "common.hpp" +#include "Channel/ChannelRequest.hpp" +#include "Channel/ChannelSocket.hpp" +#include "FBS/consumer.h" +#include "RTC/ConsumerTypes.hpp" +#include "RTC/RTCP/CompoundPacket.hpp" +#include "RTC/RTCP/FeedbackRtpNack.hpp" +#include "RTC/RTCP/ReceiverReport.hpp" +#include "RTC/RTP/HeaderExtensionIds.hpp" +#include "RTC/RTP/Packet.hpp" +#include "RTC/RTP/RtpStreamRecv.hpp" +#include "RTC/RTP/RtpStreamSend.hpp" +#include "RTC/RTP/SharedPacket.hpp" +#include "RTC/RtpDictionaries.hpp" +#include "RTC/Shared.hpp" +#include +#include +#include + +namespace RTC +{ + using namespace ConsumerTypes; + + class OldConsumer : public Channel::ChannelSocket::RequestHandler + { + public: + class Listener + { + public: + virtual ~Listener() = default; + + public: + virtual void OnConsumerSendRtpPacket(RTC::OldConsumer* consumer, RTC::RTP::Packet* packet) = 0; + virtual void OnConsumerRetransmitRtpPacket( + RTC::OldConsumer* consumer, RTC::RTP::Packet* packet) = 0; + virtual void OnConsumerKeyFrameRequested(RTC::OldConsumer* consumer, uint32_t mappedSsrc) = 0; + virtual void OnConsumerNeedBitrateChange(RTC::OldConsumer* consumer) = 0; + virtual void OnConsumerNeedZeroBitrate(RTC::OldConsumer* consumer) = 0; + virtual void OnConsumerProducerClosed(RTC::OldConsumer* consumer) = 0; + }; + + private: + struct TraceEventTypes + { + bool rtp{ false }; + bool keyframe{ false }; + bool nack{ false }; + bool pli{ false }; + bool fir{ false }; + }; + + public: + OldConsumer( + RTC::Shared* shared, + const std::string& id, + const std::string& producerId, + RTC::OldConsumer::Listener* listener, + const FBS::Transport::ConsumeRequest* data, + RTC::RtpParameters::Type type); + ~OldConsumer() override; + + public: + flatbuffers::Offset FillBuffer( + flatbuffers::FlatBufferBuilder& builder) const; + virtual flatbuffers::Offset FillBufferStats( + flatbuffers::FlatBufferBuilder& builder) = 0; + virtual flatbuffers::Offset FillBufferScore( + flatbuffers::FlatBufferBuilder& /*builder*/) const + { + return 0; + }; + RTC::Media::Kind GetKind() const + { + return this->kind; + } + const RTC::RtpParameters& GetRtpParameters() const + { + return this->rtpParameters; + } + const struct RTC::RTP::HeaderExtensionIds& GetRtpHeaderExtensionIds() const + { + return this->rtpHeaderExtensionIds; + } + RTC::RtpParameters::Type GetType() const + { + return this->type; + } + virtual VideoLayers GetPreferredLayers() const + { + // By default return 1:1. + VideoLayers layers; + + return layers; + } + const std::vector& GetMediaSsrcs() const + { + return this->mediaSsrcs; + } + const std::vector& GetRtxSsrcs() const + { + return this->rtxSsrcs; + } + virtual bool IsActive() const + { + // The parent Consumer just checks whether Consumer and Producer are + // not paused and the transport connected. + // clang-format off + return ( + this->transportConnected && + !this->paused && + !this->producerPaused && + !this->producerClosed + ); + // clang-format on + } + void TransportConnected(); + void TransportDisconnected(); + bool IsPaused() const + { + return this->paused; + } + bool IsProducerPaused() const + { + return this->producerPaused; + } + void ProducerPaused(); + void ProducerResumed(); + virtual void ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) = 0; + virtual void ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) = 0; + void ProducerRtpStreamScores(const std::vector* scores); + virtual void ProducerRtpStreamScore( + RTC::RTP::RtpStreamRecv* rtpStream, uint8_t score, uint8_t previousScore) = 0; + virtual void ProducerRtcpSenderReport(RTC::RTP::RtpStreamRecv* rtpStream, bool first) = 0; + void ProducerClosed(); + void SetExternallyManagedBitrate() + { + this->externallyManagedBitrate = true; + } + virtual uint8_t GetBitratePriority() const = 0; + virtual uint32_t IncreaseLayer(uint32_t bitrate, bool considerLoss) = 0; + virtual void ApplyLayers() = 0; + virtual uint32_t GetDesiredBitrate() const = 0; + virtual void SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket) = 0; + virtual bool GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs) = 0; + virtual const std::vector& GetRtpStreams() const = 0; + virtual void NeedWorstRemoteFractionLost(uint32_t mappedSsrc, uint8_t& worstRemoteFractionLost) = 0; + virtual void ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket) = 0; + virtual void ReceiveKeyFrameRequest( + RTC::RTCP::FeedbackPs::MessageType messageType, uint32_t ssrc) = 0; + virtual void ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report) = 0; + virtual void ReceiveRtcpXrReceiverReferenceTime(RTC::RTCP::ReceiverReferenceTime* report) = 0; + virtual uint32_t GetTransmissionRate(uint64_t nowMs) = 0; + virtual float GetRtt() const = 0; + + /* Methods inherited from Channel::ChannelSocket::RequestHandler. */ + public: + void HandleRequest(Channel::ChannelRequest* request) override; + + protected: + void EmitTraceEventRtpAndKeyFrameTypes(const RTC::RTP::Packet* packet, bool isRtx = false) const; + void EmitTraceEventPliType(uint32_t ssrc) const; + void EmitTraceEventFirType(uint32_t ssrc) const; + void EmitTraceEventNackType() const; + void EmitTraceEvent(flatbuffers::Offset& notification) const; + + private: + virtual void UserOnTransportConnected() = 0; + virtual void UserOnTransportDisconnected() = 0; + virtual void UserOnPaused() = 0; + virtual void UserOnResumed() = 0; + + public: + // Passed by argument. + std::string id; + std::string producerId; + + protected: + // Passed by argument. + RTC::Shared* shared{ nullptr }; + RTC::OldConsumer::Listener* listener{ nullptr }; + RTC::Media::Kind kind; + RTC::RtpParameters rtpParameters; + RTC::RtpParameters::Type type; + std::vector consumableRtpEncodings; + struct RTC::RTP::HeaderExtensionIds rtpHeaderExtensionIds; + const std::vector* producerRtpStreamScores{ nullptr }; + // Others. + // Whether a payload type is supported or not is represented in the + // corresponding position of the bitset. + std::bitset<128u> supportedCodecPayloadTypes; + uint64_t lastRtcpSentTime{ 0u }; + uint16_t maxRtcpInterval{ 0u }; + bool externallyManagedBitrate{ false }; + uint8_t priority{ 1u }; + struct TraceEventTypes traceEventTypes; + + private: + // Others. + std::vector mediaSsrcs; + std::vector rtxSsrcs; + bool transportConnected{ false }; + bool paused{ false }; + bool producerPaused{ false }; + bool producerClosed{ false }; + }; +} // namespace RTC + +#endif diff --git a/worker/include/RTC/PipeConsumer.hpp b/worker/include/RTC/PipeConsumer.hpp index 84ca8c0e87..5c8b880a00 100644 --- a/worker/include/RTC/PipeConsumer.hpp +++ b/worker/include/RTC/PipeConsumer.hpp @@ -1,14 +1,14 @@ #ifndef MS_RTC_PIPECONSUMER_HPP #define MS_RTC_PIPECONSUMER_HPP -#include "RTC/Consumer.hpp" +#include "RTC/OldConsumer.hpp" #include "RTC/SeqManager.hpp" #include "RTC/Shared.hpp" #include namespace RTC { - class PipeConsumer : public RTC::Consumer, public RTC::RTP::RtpStreamSend::Listener + class PipeConsumer : public RTC::OldConsumer, public RTC::RTP::RtpStreamSend::Listener { private: static void StorePacketInTargetLayerRetransmissionBuffer( @@ -22,7 +22,7 @@ namespace RTC RTC::Shared* shared, const std::string& id, const std::string& producerId, - RTC::Consumer::Listener* listener, + RTC::OldConsumer::Listener* listener, const FBS::Transport::ConsumeRequest* data); ~PipeConsumer() override; diff --git a/worker/include/RTC/ProducerStreamManager.hpp b/worker/include/RTC/ProducerStreamManager.hpp new file mode 100644 index 0000000000..93de5f27a7 --- /dev/null +++ b/worker/include/RTC/ProducerStreamManager.hpp @@ -0,0 +1,183 @@ +#ifndef MS_RTC_PRODUCER_STREAM_MANAGER_HPP +#define MS_RTC_PRODUCER_STREAM_MANAGER_HPP + +#include "common.hpp" +#include "Logger.hpp" +#include "RTC/ConsumerTypes.hpp" +#include "RTC/RTP/Codecs/PayloadDescriptorHandler.hpp" +#include "RTC/RTP/Packet.hpp" +#include "RTC/RTP/RtpStreamRecv.hpp" +#include "RTC/RtpDictionaries.hpp" +#include +#include + +namespace RTC +{ + using namespace ConsumerTypes; + + class ProducerStreamManager + { + public: + class Listener + { + public: + virtual ~Listener() = default; + + public: + virtual bool IsActive() const = 0; + virtual void OnProducerStreamManagerKeyFrameRequested(uint32_t mappedSsrc) = 0; + virtual void OnProducerStreamManagerNeedBitrateChange() = 0; + virtual void OnProducerStreamManagerLayersChanged() = 0; + virtual void OnProducerStreamManagerClearRetransmissionBuffer() = 0; + virtual void OnProducerStreamManagerScore() = 0; + }; + + struct RtpPacketProcessResult + { + enum class Type : uint8_t + { + FORWARD, + DROP, + SILENT_DROP, + BUFFER + }; + + Type type{ Type::SILENT_DROP }; + + // Valid when type == FORWARD: + uint32_t tsOffset{ 0u }; + bool isSyncPacket{ false }; + uint16_t syncSeqValue{ 0u }; + bool shouldSyncEncodingContext{ false }; + bool spatialLayerSwitched{ false }; + bool temporalLayerChanged{ false }; + bool marker{ false }; + bool sendBufferedPackets{ false }; + }; + + public: + ProducerStreamManager( + const std::vector& consumableRtpEncodings, + const VideoLayers& preferredLayers, + std::unique_ptr encodingContext, + RTC::Media::Kind kind, + bool keyFrameSupported, + Listener* listener) + : listener(listener), keyFrameSupported(keyFrameSupported), kind(kind), + consumableRtpEncodings(consumableRtpEncodings), encodingContext(std::move(encodingContext)), + preferredLayers(preferredLayers) + { + MS_TRACE(); + } + virtual ~ProducerStreamManager() = default; + + public: + virtual VideoLayers GetTargetLayers() const = 0; + virtual int16_t GetCurrentSpatialLayer() const = 0; + virtual int16_t GetCurrentTemporalLayer() const = 0; + virtual RTC::RTP::RtpStreamRecv* GetProducerCurrentRtpStream() const = 0; + virtual RTC::RTP::RtpStreamRecv* GetProducerTargetRtpStream() const = 0; + RTC::RTP::Codecs::EncodingContext* GetEncodingContext() const + { + return this->encodingContext.get(); + } + const VideoLayers& GetPreferredLayers() const + { + return this->preferredLayers; + } + void SetPreferredLayers(const VideoLayers& layers) + { + this->preferredLayers = layers; + } + + virtual bool IsProducerStreamActive() const = 0; + + virtual void ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) = 0; + virtual void ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) = 0; + virtual void ProducerRtpStreamScore( + RTC::RTP::RtpStreamRecv* rtpStream, uint8_t score, uint8_t previousScore) = 0; + virtual void ProducerRtcpSenderReport(RTC::RTP::RtpStreamRecv* rtpStream, bool first) = 0; + + void SetExternallyManagedBitrate() + { + this->externallyManagedBitrate = true; + } + + virtual uint32_t IncreaseLayer( + uint32_t bitrate, bool considerLoss, float lossPercentage, uint64_t nowMs) = 0; + virtual void ApplyLayers(uint64_t rtpStreamActiveMs) = 0; + virtual uint32_t GetDesiredBitrate(uint64_t nowMs) const = 0; + + virtual RtpPacketProcessResult ProcessRtpPacket( + RTC::RTP::Packet* packet, + bool lastSentPacketHasMarker, + uint32_t clockRate, + uint32_t maxPacketTs) = 0; + + virtual void RequestKeyFrame() = 0; + virtual void RequestKeyFrameForTargetSpatialLayer() = 0; + virtual void RequestKeyFrameForCurrentSpatialLayer() = 0; + + virtual void UpdateTargetLayers(int16_t newTargetSpatialLayer, int16_t newTargetTemporalLayer) = 0; + virtual bool RecalculateTargetLayers(VideoLayers& newTargetLayers) const = 0; + + void MayChangeLayers(bool force) + { + MS_TRACE(); + + VideoLayers newTargetLayers; + + if (RecalculateTargetLayers(newTargetLayers)) + { + // If bitrate externally managed, don't bother the transport unless + // the newTargetSpatialLayer has changed (or force is true). + // This is because, if bitrate is externally managed, the target temporal + // layer is managed by the available given bitrate so the transport + // will let us change it when it considers. + if (this->externallyManagedBitrate) + { + if (newTargetLayers.spatial != GetTargetLayers().spatial || force) + { + this->listener->OnProducerStreamManagerNeedBitrateChange(); + } + } + else + { + UpdateTargetLayers(newTargetLayers.spatial, newTargetLayers.temporal); + } + } + } + virtual void OnTransportConnected() = 0; + virtual void OnTransportDisconnected() = 0; + virtual void OnPaused() = 0; + virtual void OnResumed() = 0; + + protected: + bool IsActive() const + { + return this->listener->IsActive(); + } + + protected: + // Passed by argument. + Listener* listener{ nullptr }; + bool keyFrameSupported{ false }; + RTC::Media::Kind kind{}; + std::vector consumableRtpEncodings; + + // Encoding context. + std::unique_ptr encodingContext; + + // Externally managed bitrate. + bool externallyManagedBitrate{ false }; + + // Layer preferences. + VideoLayers preferredLayers; + VideoLayers provisionalTargetLayers; + + // Sync state. + bool syncRequired{ false }; + }; +} // namespace RTC + +#endif diff --git a/worker/include/RTC/SimpleConsumer.hpp b/worker/include/RTC/SimpleConsumer.hpp index 0e69241573..62a43891b1 100644 --- a/worker/include/RTC/SimpleConsumer.hpp +++ b/worker/include/RTC/SimpleConsumer.hpp @@ -1,21 +1,21 @@ #ifndef MS_RTC_SIMPLE_CONSUMER_HPP #define MS_RTC_SIMPLE_CONSUMER_HPP -#include "RTC/Consumer.hpp" +#include "RTC/OldConsumer.hpp" #include "RTC/SeqManager.hpp" #include "RTC/Shared.hpp" #include namespace RTC { - class SimpleConsumer : public RTC::Consumer, public RTC::RTP::RtpStreamSend::Listener + class SimpleConsumer : public RTC::OldConsumer, public RTC::RTP::RtpStreamSend::Listener { public: SimpleConsumer( RTC::Shared* shared, const std::string& id, const std::string& producerId, - RTC::Consumer::Listener* listener, + RTC::OldConsumer::Listener* listener, const FBS::Transport::ConsumeRequest* data); ~SimpleConsumer() override; @@ -30,7 +30,7 @@ namespace RTC { // clang-format off return ( - RTC::Consumer::IsActive() && + RTC::OldConsumer::IsActive() && this->producerRtpStream && // If there is no RTP inactivity check do not consider the stream // inactive despite it has score 0. diff --git a/worker/include/RTC/SimpleProducerStreamManager.hpp b/worker/include/RTC/SimpleProducerStreamManager.hpp new file mode 100644 index 0000000000..d0410b24c4 --- /dev/null +++ b/worker/include/RTC/SimpleProducerStreamManager.hpp @@ -0,0 +1,67 @@ +#ifndef MS_RTC_SIMPLE_CONSUMER_STREAM_HPP +#define MS_RTC_SIMPLE_CONSUMER_STREAM_HPP + +#include "RTC/ProducerStreamManager.hpp" + +namespace RTC +{ + class SimpleProducerStreamManager : public ProducerStreamManager + { + public: + SimpleProducerStreamManager( + const std::vector& consumableRtpEncodings, + const VideoLayers& preferredLayers, + std::unique_ptr encodingContext, + RTC::Media::Kind kind, + bool keyFrameSupported, + Listener* listener); + + public: + VideoLayers GetTargetLayers() const override + { + return {}; + } + int16_t GetCurrentSpatialLayer() const override + { + return 0; + } + int16_t GetCurrentTemporalLayer() const override + { + return 0; + } + RTC::RTP::RtpStreamRecv* GetProducerCurrentRtpStream() const override; + RTC::RTP::RtpStreamRecv* GetProducerTargetRtpStream() const override; + bool IsProducerStreamActive() const override; + void ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; + void ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; + void ProducerRtpStreamScore( + RTC::RTP::RtpStreamRecv* rtpStream, uint8_t score, uint8_t previousScore) override; + void ProducerRtcpSenderReport(RTC::RTP::RtpStreamRecv* rtpStream, bool first) override; + uint32_t IncreaseLayer( + uint32_t bitrate, bool considerLoss, float lossPercentage, uint64_t nowMs) override; + void ApplyLayers(uint64_t rtpStreamActiveMs) override; + uint32_t GetDesiredBitrate(uint64_t nowMs) const override; + RtpPacketProcessResult ProcessRtpPacket( + RTC::RTP::Packet* packet, + bool lastSentPacketHasMarker, + uint32_t clockRate, + uint32_t maxPacketTs) override; + void RequestKeyFrame() override; + void RequestKeyFrameForTargetSpatialLayer() override; + void RequestKeyFrameForCurrentSpatialLayer() override; + void UpdateTargetLayers(int16_t spatial, int16_t temporal) override; + bool RecalculateTargetLayers(VideoLayers& newTargetLayers) const override; + void OnTransportConnected() override; + void OnTransportDisconnected() override; + void OnPaused() override; + void OnResumed() override; + + private: + // Producer RTP stream (single stream for Simple). + RTC::RTP::RtpStreamRecv* producerRtpStream{ nullptr }; + // Prevents double IncreaseLayer in one BWE round. + bool managingBitrate{ false }; + }; +} // namespace RTC + +#endif diff --git a/worker/include/RTC/SimulcastConsumer.hpp b/worker/include/RTC/SimulcastConsumer.hpp index 6654be5a49..dd9c5f03fd 100644 --- a/worker/include/RTC/SimulcastConsumer.hpp +++ b/worker/include/RTC/SimulcastConsumer.hpp @@ -1,7 +1,7 @@ #ifndef MS_RTC_SIMULCAST_CONSUMER_HPP #define MS_RTC_SIMULCAST_CONSUMER_HPP -#include "RTC/Consumer.hpp" +#include "RTC/OldConsumer.hpp" #include "RTC/RTP/Codecs/PayloadDescriptorHandler.hpp" #include "RTC/SeqManager.hpp" #include "RTC/Shared.hpp" @@ -9,14 +9,14 @@ namespace RTC { - class SimulcastConsumer : public RTC::Consumer, public RTC::RTP::RtpStreamSend::Listener + class SimulcastConsumer : public RTC::OldConsumer, public RTC::RTP::RtpStreamSend::Listener { public: SimulcastConsumer( RTC::Shared* shared, const std::string& id, const std::string& producerId, - RTC::Consumer::Listener* listener, + RTC::OldConsumer::Listener* listener, const FBS::Transport::ConsumeRequest* data); ~SimulcastConsumer() override; @@ -40,7 +40,7 @@ namespace RTC { // clang-format off return ( - RTC::Consumer::IsActive() && + RTC::OldConsumer::IsActive() && std::any_of( this->producerRtpStreams.begin(), this->producerRtpStreams.end(), diff --git a/worker/include/RTC/SimulcastProducerStreamManager.hpp b/worker/include/RTC/SimulcastProducerStreamManager.hpp new file mode 100644 index 0000000000..efb2011f4b --- /dev/null +++ b/worker/include/RTC/SimulcastProducerStreamManager.hpp @@ -0,0 +1,83 @@ +#ifndef MS_RTC_SIMULCAST_CONSUMER_STREAM_HPP +#define MS_RTC_SIMULCAST_CONSUMER_STREAM_HPP + +#include "RTC/ProducerStreamManager.hpp" +#include + +namespace RTC +{ + class SimulcastProducerStreamManager : public ProducerStreamManager + { + public: + SimulcastProducerStreamManager( + const std::vector& consumableRtpEncodings, + const VideoLayers& preferredLayers, + std::unique_ptr encodingContext, + RTC::Media::Kind kind, + bool keyFrameSupported, + Listener* listener); + + public: + VideoLayers GetTargetLayers() const override + { + return this->targetLayers; + } + int16_t GetCurrentSpatialLayer() const override + { + return this->currentSpatialLayer; + } + int16_t GetCurrentTemporalLayer() const override + { + return this->encodingContext->GetCurrentTemporalLayer(); + } + RTC::RTP::RtpStreamRecv* GetProducerCurrentRtpStream() const override; + RTC::RTP::RtpStreamRecv* GetProducerTargetRtpStream() const override; + bool IsProducerStreamActive() const override; + void ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; + void ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; + void ProducerRtpStreamScore( + RTC::RTP::RtpStreamRecv* rtpStream, uint8_t score, uint8_t previousScore) override; + void ProducerRtcpSenderReport(RTC::RTP::RtpStreamRecv* rtpStream, bool first) override; + uint32_t IncreaseLayer( + uint32_t bitrate, bool considerLoss, float lossPercentage, uint64_t nowMs) override; + void ApplyLayers(uint64_t rtpStreamActiveMs) override; + uint32_t GetDesiredBitrate(uint64_t nowMs) const override; + RtpPacketProcessResult ProcessRtpPacket( + RTC::RTP::Packet* packet, + bool lastSentPacketHasMarker, + uint32_t clockRate, + uint32_t maxPacketTs) override; + void RequestKeyFrame() override; + void RequestKeyFrameForTargetSpatialLayer() override; + void RequestKeyFrameForCurrentSpatialLayer() override; + void UpdateTargetLayers(int16_t newTargetSpatialLayer, int16_t newTargetTemporalLayer) override; + bool RecalculateTargetLayers(VideoLayers& newTargetLayers) const override; + void OnTransportConnected() override; + void OnTransportDisconnected() override; + void OnPaused() override; + void OnResumed() override; + + private: + bool CanSwitchToSpatialLayer(int16_t spatialLayer) const; + RTC::RTP::RtpStreamRecv* GetProducerTsReferenceRtpStream() const; + + private: + // Producer RTP streams (multiple for Simulcast). + std::vector producerRtpStreams; + absl::flat_hash_map mapMappedSsrcSpatialLayer; + VideoLayers targetLayers; + int16_t currentSpatialLayer{ -1 }; + int16_t spatialLayerToSync{ -1 }; + // Timestamp synchronization. + int16_t tsReferenceSpatialLayer{ -1 }; + uint32_t tsOffset{ 0u }; + bool keyFrameForTsOffsetRequested{ false }; + // Old-packet filtering after spatial switch. + uint16_t snReferenceSpatialLayer{ 0u }; + bool checkingForOldPacketsInSpatialLayer{ false }; + // BWE downgrade tracking. + uint64_t lastBweDowngradeAtMs{ 0u }; + }; +} // namespace RTC + +#endif diff --git a/worker/include/RTC/SvcConsumer.hpp b/worker/include/RTC/SvcConsumer.hpp index 35686d6024..c3a3cd4edf 100644 --- a/worker/include/RTC/SvcConsumer.hpp +++ b/worker/include/RTC/SvcConsumer.hpp @@ -1,7 +1,7 @@ #ifndef MS_RTC_SVC_CONSUMER_HPP #define MS_RTC_SVC_CONSUMER_HPP -#include "RTC/Consumer.hpp" +#include "RTC/OldConsumer.hpp" #include "RTC/RTP/Codecs/PayloadDescriptorHandler.hpp" #include "RTC/SeqManager.hpp" #include "RTC/Shared.hpp" @@ -9,14 +9,14 @@ namespace RTC { - class SvcConsumer : public RTC::Consumer, public RTC::RTP::RtpStreamSend::Listener + class SvcConsumer : public RTC::OldConsumer, public RTC::RTP::RtpStreamSend::Listener { public: SvcConsumer( RTC::Shared* shared, const std::string& id, const std::string& producerId, - RTC::Consumer::Listener* listener, + RTC::OldConsumer::Listener* listener, const FBS::Transport::ConsumeRequest* data); ~SvcConsumer() override; @@ -40,7 +40,7 @@ namespace RTC { // clang-format off return ( - RTC::Consumer::IsActive() && + RTC::OldConsumer::IsActive() && this->producerRtpStream && // If there is no RTP inactivity check do not consider the stream // inactive despite it has score 0. diff --git a/worker/include/RTC/SvcProducerStreamManager.hpp b/worker/include/RTC/SvcProducerStreamManager.hpp new file mode 100644 index 0000000000..a822245a5d --- /dev/null +++ b/worker/include/RTC/SvcProducerStreamManager.hpp @@ -0,0 +1,67 @@ +#ifndef MS_RTC_SVC_CONSUMER_STREAM_HPP +#define MS_RTC_SVC_CONSUMER_STREAM_HPP + +#include "RTC/ProducerStreamManager.hpp" + +namespace RTC +{ + class SvcProducerStreamManager : public ProducerStreamManager + { + public: + SvcProducerStreamManager( + const std::vector& consumableRtpEncodings, + const VideoLayers& preferredLayers, + std::unique_ptr encodingContext, + RTC::Media::Kind kind, + bool keyFrameSupported, + Listener* listener); + + public: + VideoLayers GetTargetLayers() const override + { + return this->encodingContext->GetTargetLayers(); + } + int16_t GetCurrentSpatialLayer() const override + { + return this->encodingContext->GetCurrentSpatialLayer(); + } + int16_t GetCurrentTemporalLayer() const override + { + return this->encodingContext->GetCurrentTemporalLayer(); + } + RTC::RTP::RtpStreamRecv* GetProducerCurrentRtpStream() const override; + RTC::RTP::RtpStreamRecv* GetProducerTargetRtpStream() const override; + bool IsProducerStreamActive() const override; + void ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; + void ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; + void ProducerRtpStreamScore( + RTC::RTP::RtpStreamRecv* rtpStream, uint8_t score, uint8_t previousScore) override; + void ProducerRtcpSenderReport(RTC::RTP::RtpStreamRecv* rtpStream, bool first) override; + uint32_t IncreaseLayer( + uint32_t bitrate, bool considerLoss, float lossPercentage, uint64_t nowMs) override; + void ApplyLayers(uint64_t rtpStreamActiveMs) override; + uint32_t GetDesiredBitrate(uint64_t nowMs) const override; + RtpPacketProcessResult ProcessRtpPacket( + RTC::RTP::Packet* packet, + bool lastSentPacketHasMarker, + uint32_t clockRate, + uint32_t maxPacketTs) override; + void RequestKeyFrame() override; + void RequestKeyFrameForTargetSpatialLayer() override; + void RequestKeyFrameForCurrentSpatialLayer() override; + void UpdateTargetLayers(int16_t newTargetSpatialLayer, int16_t newTargetTemporalLayer) override; + bool RecalculateTargetLayers(VideoLayers& newTargetLayers) const override; + void OnTransportConnected() override; + void OnTransportDisconnected() override; + void OnPaused() override; + void OnResumed() override; + + private: + // Producer RTP stream (single stream for SVC). + RTC::RTP::RtpStreamRecv* producerRtpStream{ nullptr }; + // BWE downgrade tracking. + uint64_t lastBweDowngradeAtMs{ 0u }; + }; +} // namespace RTC + +#endif diff --git a/worker/meson.build b/worker/meson.build index cb48c93bf3..44984706c6 100644 --- a/worker/meson.build +++ b/worker/meson.build @@ -110,6 +110,7 @@ common_sources = [ 'src/RTC/DtlsTransport.cpp', 'src/RTC/KeyFrameRequestManager.cpp', 'src/RTC/NackGenerator.cpp', + 'src/RTC/OldConsumer.cpp', 'src/RTC/PipeConsumer.cpp', 'src/RTC/PipeTransport.cpp', 'src/RTC/PlainTransport.cpp', @@ -127,9 +128,12 @@ common_sources = [ 'src/RTC/Serializable.cpp', 'src/RTC/Shared.cpp', 'src/RTC/SimpleConsumer.cpp', + 'src/RTC/SimpleProducerStreamManager.cpp', 'src/RTC/SimulcastConsumer.cpp', + 'src/RTC/SimulcastProducerStreamManager.cpp', 'src/RTC/SrtpSession.cpp', 'src/RTC/SvcConsumer.cpp', + 'src/RTC/SvcProducerStreamManager.cpp', 'src/RTC/TcpConnection.cpp', 'src/RTC/TcpServer.cpp', 'src/RTC/Transport.cpp', @@ -405,12 +409,12 @@ executable( test_sources = [ 'test/src/tests.cpp', 'test/src/testHelpers.cpp', + 'test/src/RTC/TestConsumer.cpp', 'test/src/RTC/TestKeyFrameRequestManager.cpp', 'test/src/RTC/TestNackGenerator.cpp', 'test/src/RTC/TestRateCalculator.cpp', 'test/src/RTC/TestRtpEncodingParameters.cpp', 'test/src/RTC/TestSeqManager.cpp', - 'test/src/RTC/TestSimpleConsumer.cpp', 'test/src/RTC/TestTransportCongestionControlServer.cpp', 'test/src/RTC/TestTransportTuple.cpp', 'test/src/RTC/TestTrendCalculator.cpp', diff --git a/worker/src/RTC/Consumer.cpp b/worker/src/RTC/Consumer.cpp index 7606ce767f..91aa88da2d 100644 --- a/worker/src/RTC/Consumer.cpp +++ b/worker/src/RTC/Consumer.cpp @@ -5,20 +5,32 @@ #include "DepLibUV.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" +#include "Utils.hpp" +#include "RTC/RTP/Codecs/Tools.hpp" +#include "RTC/SimpleProducerStreamManager.hpp" +#include "RTC/SimulcastProducerStreamManager.hpp" +#include "RTC/SvcProducerStreamManager.hpp" +#ifdef MS_RTC_LOGGER_RTP +#include "RTC/RtcLogger.hpp" +#endif +#include // std::numeric_limits namespace RTC { + /* Static. */ + + static constexpr size_t TargetLayerRetransmissionBufferSize{ 30u }; + /* Instance methods. */ Consumer::Consumer( RTC::Shared* shared, const std::string& id, const std::string& producerId, - Listener* listener, - const FBS::Transport::ConsumeRequest* data, - RTC::RtpParameters::Type type) + RTC::Consumer::Listener* listener, + const FBS::Transport::ConsumeRequest* data) : id(id), producerId(producerId), shared(shared), listener(listener), - kind(RTC::Media::Kind(data->kind())), type(type) + kind(RTC::Media::Kind(data->kind())), type(RTC::RtpParameters::Type(data->type())) { MS_TRACE(); @@ -117,11 +129,6 @@ namespace RTC this->rtpHeaderExtensionIds.videoOrientation = exten.id; } - if (this->rtpHeaderExtensionIds.videoOrientation == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::VIDEO_ORIENTATION) - { - this->rtpHeaderExtensionIds.videoOrientation = exten.id; - } - if (this->rtpHeaderExtensionIds.absCaptureTime == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::ABS_CAPTURE_TIME) { this->rtpHeaderExtensionIds.absCaptureTime = exten.id; @@ -174,14 +181,268 @@ namespace RTC { this->maxRtcpInterval = RTC::RTCP::MaxVideoIntervalMs; } + + auto& encoding = this->rtpParameters.encodings[0]; + + // Determine keyFrameSupported. + const auto* mediaCodec = this->rtpParameters.GetCodecForEncoding(encoding); + bool keyFrameSupported = RTC::RTP::Codecs::Tools::CanBeKeyFrame(mediaCodec->mimeType); + + // Build preferred layers from FBS data. + VideoLayers preferredLayers; + + // Let's choose an initial output seq number between 1000 and 32768 to avoid + // libsrtp bug: + // https://github.com/versatica/mediasoup/issues/1437 + const auto initialOutputSeq = + Utils::Crypto::GetRandomUInt(1000u, std::numeric_limits::max() / 2); + + this->rtpSeqManager = RTC::SeqManager(initialOutputSeq); + + // Create the appropriate ProducerStreamManager subclass based on type. + switch (this->type) + { + case RTC::RtpParameters::Type::SIMPLE: + { + // Ensure there is a single encoding. + if (this->consumableRtpEncodings.size() != 1u) + { + MS_THROW_TYPE_ERROR("invalid consumableRtpEncodings with size != 1"); + } + + // Create the encoding context for Opus (DTX filtering). + std::unique_ptr encodingContext; + + if ( + mediaCodec->mimeType.type == RTC::RtpCodecMimeType::Type::AUDIO && + (mediaCodec->mimeType.subtype == RTC::RtpCodecMimeType::Subtype::OPUS || + mediaCodec->mimeType.subtype == RTC::RtpCodecMimeType::Subtype::MULTIOPUS)) + { + RTC::RTP::Codecs::EncodingContext::Params params; + + encodingContext.reset( + RTC::RTP::Codecs::Tools::GetEncodingContext(mediaCodec->mimeType, params)); + + // ignoreDtx is set to false by default. + encodingContext->SetIgnoreDtx(data->ignoreDtx()); + } + + this->producerStreamManager = std::make_unique( + this->consumableRtpEncodings, + preferredLayers, + std::move(encodingContext), + this->kind, + keyFrameSupported, + this); + + break; + } + + case RTC::RtpParameters::Type::SIMULCAST: + { + // We allow a single encoding in simulcast (so we can enable temporal + // layers with a single simulcast stream). + + // Ensure there are as many spatial layers as encodings. + if (encoding.spatialLayers != this->consumableRtpEncodings.size()) + { + MS_THROW_TYPE_ERROR( + "encoding.spatialLayers does not match number of consumableRtpEncodings"); + } + + // Set preferredLayers. + if (flatbuffers::IsFieldPresent(data, FBS::Transport::ConsumeRequest::VT_PREFERREDLAYERS)) + { + const auto* fbsPreferredLayers = data->preferredLayers(); + + preferredLayers.spatial = fbsPreferredLayers->spatialLayer(); + + if (preferredLayers.spatial > encoding.spatialLayers - 1) + { + preferredLayers.spatial = static_cast(encoding.spatialLayers - 1); + } + + if (auto preferredTemporalLayer = fbsPreferredLayers->temporalLayer(); + preferredTemporalLayer.has_value()) + { + preferredLayers.temporal = preferredTemporalLayer.value(); + + if (preferredLayers.temporal > encoding.temporalLayers - 1) + { + preferredLayers.temporal = static_cast(encoding.temporalLayers - 1); + } + } + else + { + preferredLayers.temporal = static_cast(encoding.temporalLayers - 1); + } + } + else + { + // Initially set preferredSpatialLayer and preferredTemporalLayer + // to the maximum value. + preferredLayers.spatial = static_cast(encoding.spatialLayers - 1); + preferredLayers.temporal = static_cast(encoding.temporalLayers - 1); + } + + // Create the encoding context. + if (!RTC::RTP::Codecs::Tools::IsValidTypeForCodec(this->type, mediaCodec->mimeType)) + { + MS_THROW_TYPE_ERROR( + "%s codec not supported for simulcast", mediaCodec->mimeType.ToString().c_str()); + } + + RTC::RTP::Codecs::EncodingContext::Params params; + + params.spatialLayers = encoding.spatialLayers; + params.temporalLayers = encoding.temporalLayers; + + std::unique_ptr encodingContext( + RTC::RTP::Codecs::Tools::GetEncodingContext(mediaCodec->mimeType, params)); + + MS_ASSERT(encodingContext, "no encoding context for this codec"); + + this->producerStreamManager = std::make_unique( + this->consumableRtpEncodings, + preferredLayers, + std::move(encodingContext), + this->kind, + keyFrameSupported, + this); + + break; + } + + case RTC::RtpParameters::Type::SVC: + { + // Ensure there is a single encoding. + if (this->consumableRtpEncodings.size() != 1u) + { + MS_THROW_TYPE_ERROR("invalid consumableRtpEncodings with size != 1"); + } + + // Ensure there are multiple spatial or temporal layers. + if (encoding.spatialLayers < 2u && encoding.temporalLayers < 2u) + { + MS_THROW_TYPE_ERROR("invalid number of layers"); + } + + // Set preferredLayers. + if (flatbuffers::IsFieldPresent(data, FBS::Transport::ConsumeRequest::VT_PREFERREDLAYERS)) + { + preferredLayers.spatial = data->preferredLayers()->spatialLayer(); + + if (preferredLayers.spatial > encoding.spatialLayers - 1) + { + preferredLayers.spatial = static_cast(encoding.spatialLayers - 1); + } + + if (flatbuffers::IsFieldPresent( + data->preferredLayers(), FBS::Consumer::ConsumerLayers::VT_TEMPORALLAYER)) + { + preferredLayers.temporal = data->preferredLayers()->temporalLayer().value(); + + if (preferredLayers.temporal > encoding.temporalLayers - 1) + { + preferredLayers.temporal = static_cast(encoding.temporalLayers - 1); + } + } + else + { + preferredLayers.temporal = static_cast(encoding.temporalLayers - 1); + } + } + else + { + // Initially set preferredSpatialLayer and preferredTemporalLayer + // to the maximum value. + preferredLayers.spatial = static_cast(encoding.spatialLayers - 1); + preferredLayers.temporal = static_cast(encoding.temporalLayers - 1); + } + + // Create the encoding context. + if (!RTC::RTP::Codecs::Tools::IsValidTypeForCodec(this->type, mediaCodec->mimeType)) + { + MS_THROW_TYPE_ERROR( + "%s codec not supported for svc", mediaCodec->mimeType.ToString().c_str()); + } + + RTC::RTP::Codecs::EncodingContext::Params params; + + params.spatialLayers = encoding.spatialLayers; + params.temporalLayers = encoding.temporalLayers; + params.ksvc = encoding.ksvc; + + std::unique_ptr encodingContext( + RTC::RTP::Codecs::Tools::GetEncodingContext(mediaCodec->mimeType, params)); + + MS_ASSERT(encodingContext, "no encoding context for this codec"); + + this->producerStreamManager = std::make_unique( + this->consumableRtpEncodings, + preferredLayers, + std::move(encodingContext), + this->kind, + keyFrameSupported, + this); + + break; + } + + default: + { + MS_THROW_TYPE_ERROR("invalid consumer type"); + } + } + + // Create RtpStreamSend instance for sending a single stream to the remote. + CreateRtpStream(); + + // NOTE: This may throw. + this->shared->channelMessageRegistrator->RegisterHandler( + this->id, + /*channelRequestHandler*/ this, + /*channelNotificationHandler*/ nullptr); } Consumer::~Consumer() { MS_TRACE(); + + this->shared->channelMessageRegistrator->UnregisterHandler(this->id); + + delete this->rtpStream; + this->targetLayerRetransmissionBuffer.clear(); + } + + flatbuffers::Offset Consumer::FillBuffer( + flatbuffers::FlatBufferBuilder& builder) const + { + MS_TRACE(); + + // Call the base method. + auto base = FillBufferBase(builder); + // Add rtpStream. + std::vector> rtpStreams; + rtpStreams.emplace_back(this->rtpStream->FillBuffer(builder)); + + auto targetLayers = this->producerStreamManager->GetTargetLayers(); + + auto dump = FBS::Consumer::CreateConsumerDumpDirect( + builder, + base, + &rtpStreams, + this->producerStreamManager->GetPreferredLayers().spatial, + targetLayers.spatial, + this->producerStreamManager->GetCurrentSpatialLayer(), + this->producerStreamManager->GetPreferredLayers().temporal, + targetLayers.temporal, + this->producerStreamManager->GetCurrentTemporalLayer()); + + return FBS::Consumer::CreateDumpResponse(builder, dump); } - flatbuffers::Offset Consumer::FillBuffer( + flatbuffers::Offset Consumer::FillBufferBase( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); @@ -249,12 +510,146 @@ namespace RTC this->priority); } + flatbuffers::Offset Consumer::FillBufferStats( + flatbuffers::FlatBufferBuilder& builder) + { + MS_TRACE(); + + std::vector> rtpStreams; + + // Add stats of our send stream. + rtpStreams.emplace_back(this->rtpStream->FillBufferStats(builder)); + + // Add stats of the current recv stream. + auto* producerCurrentRtpStream = this->producerStreamManager->GetProducerCurrentRtpStream(); + + if (producerCurrentRtpStream) + { + rtpStreams.emplace_back(producerCurrentRtpStream->FillBufferStats(builder)); + } + + return FBS::Consumer::CreateGetStatsResponseDirect(builder, &rtpStreams); + } + + flatbuffers::Offset Consumer::FillBufferScore( + flatbuffers::FlatBufferBuilder& builder) const + { + MS_TRACE(); + + MS_ASSERT(this->producerRtpStreamScores, "producerRtpStreamScores not set"); + + uint8_t producerScore{ 0 }; + + auto* producerCurrentRtpStream = this->producerStreamManager->GetProducerCurrentRtpStream(); + + if (producerCurrentRtpStream) + { + producerScore = producerCurrentRtpStream->GetScore(); + } + + return FBS::Consumer::CreateConsumerScoreDirect( + builder, this->rtpStream->GetScore(), producerScore, this->producerRtpStreamScores); + } + void Consumer::HandleRequest(Channel::ChannelRequest* request) { MS_TRACE(); switch (request->method) { + case Channel::ChannelRequest::Method::CONSUMER_DUMP: + { + auto dumpOffset = FillBuffer(request->GetBufferBuilder()); + + request->Accept(FBS::Response::Body::Consumer_DumpResponse, dumpOffset); + + break; + } + + case Channel::ChannelRequest::Method::CONSUMER_REQUEST_KEY_FRAME: + { + if (IsActive()) + { + this->producerStreamManager->RequestKeyFrame(); + } + + request->Accept(); + + break; + } + + case Channel::ChannelRequest::Method::CONSUMER_SET_PREFERRED_LAYERS: + { + // Simple consumers have no layers concept. + if (this->type == RTC::RtpParameters::Type::SIMPLE) + { + // Accept with empty preferred layers object. + auto responseOffset = + FBS::Consumer::CreateSetPreferredLayersResponse(request->GetBufferBuilder()); + + request->Accept(FBS::Response::Body::Consumer_SetPreferredLayersResponse, responseOffset); + + break; + } + + auto previousPreferredLayers = this->producerStreamManager->GetPreferredLayers(); + + const auto* body = request->data->body_as(); + const auto* preferredLayers = body->preferredLayers(); + + VideoLayers newPreferredLayers; + + // Spatial layer. + newPreferredLayers.spatial = preferredLayers->spatialLayer(); + + if (newPreferredLayers.spatial > this->rtpStream->GetSpatialLayers() - 1) + { + newPreferredLayers.spatial = static_cast(this->rtpStream->GetSpatialLayers() - 1); + } + + // preferredTemporalLayer is optional. + auto preferredTemporalLayer = preferredLayers->temporalLayer(); + + if (preferredTemporalLayer.has_value()) + { + newPreferredLayers.temporal = preferredTemporalLayer.value(); + + if (newPreferredLayers.temporal > this->rtpStream->GetTemporalLayers() - 1) + { + newPreferredLayers.temporal = + static_cast(this->rtpStream->GetTemporalLayers() - 1); + } + } + else + { + newPreferredLayers.temporal = + static_cast(this->rtpStream->GetTemporalLayers() - 1); + } + + this->producerStreamManager->SetPreferredLayers(newPreferredLayers); + + MS_DEBUG_DEV( + "preferred layers changed [spatial:%" PRIi16 ", temporal:%" PRIi16 ", consumerId:%s]", + newPreferredLayers.spatial, + newPreferredLayers.temporal, + this->id.c_str()); + + preferredTemporalLayer = newPreferredLayers.temporal; + auto preferredLayersOffset = FBS::Consumer::CreateConsumerLayers( + request->GetBufferBuilder(), newPreferredLayers.spatial, preferredTemporalLayer); + auto responseOffset = FBS::Consumer::CreateSetPreferredLayersResponse( + request->GetBufferBuilder(), preferredLayersOffset); + + request->Accept(FBS::Response::Body::Consumer_SetPreferredLayersResponse, responseOffset); + + if (IsActive() && newPreferredLayers != previousPreferredLayers) + { + this->producerStreamManager->MayChangeLayers(/*force*/ true); + } + + break; + } + case Channel::ChannelRequest::Method::CONSUMER_GET_STATS: { auto responseOffset = FillBufferStats(request->GetBufferBuilder()); @@ -465,6 +860,20 @@ namespace RTC this->shared->channelNotifier->Emit(this->id, FBS::Notification::Event::CONSUMER_PRODUCER_RESUME); } + void Consumer::ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) + { + MS_TRACE(); + + this->producerStreamManager->ProducerRtpStream(rtpStream, mappedSsrc); + } + + void Consumer::ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) + { + MS_TRACE(); + + this->producerStreamManager->ProducerNewRtpStream(rtpStream, mappedSsrc); + } + void Consumer::ProducerRtpStreamScores(const std::vector* scores) { MS_TRACE(); @@ -473,6 +882,23 @@ namespace RTC this->producerRtpStreamScores = scores; } + void Consumer::ProducerRtpStreamScore( + RTC::RTP::RtpStreamRecv* rtpStream, uint8_t score, uint8_t previousScore) + { + MS_TRACE(); + + EmitScore(); + + this->producerStreamManager->ProducerRtpStreamScore(rtpStream, score, previousScore); + } + + void Consumer::ProducerRtcpSenderReport(RTC::RTP::RtpStreamRecv* rtpStream, bool first) + { + MS_TRACE(); + + this->producerStreamManager->ProducerRtcpSenderReport(rtpStream, first); + } + // The caller (Router) is supposed to proceed with the deletion of this Consumer // right after calling this method. Otherwise ugly things may happen. void Consumer::ProducerClosed() @@ -488,72 +914,736 @@ namespace RTC this->listener->OnConsumerProducerClosed(this); } - void Consumer::EmitTraceEventRtpAndKeyFrameTypes(const RTC::RTP::Packet* packet, bool isRtx) const + uint8_t Consumer::GetBitratePriority() const { MS_TRACE(); - if (this->traceEventTypes.keyframe && packet->IsKeyFrame()) - { - auto rtpPacketDump = packet->FillBuffer(this->shared->channelNotifier->GetBufferBuilder()); - auto traceInfo = FBS::Consumer::CreateKeyFrameTraceInfo( - this->shared->channelNotifier->GetBufferBuilder(), rtpPacketDump, isRtx); - - auto notification = FBS::Consumer::CreateTraceNotification( - this->shared->channelNotifier->GetBufferBuilder(), - FBS::Consumer::TraceEventType::KEYFRAME, - DepLibUV::GetTimeMs(), - FBS::Common::TraceDirection::DIRECTION_OUT, - FBS::Consumer::TraceInfo::KeyFrameTraceInfo, - traceInfo.Union()); + MS_ASSERT(this->externallyManagedBitrate, "bitrate is not externally managed"); - EmitTraceEvent(notification); - } - else if (this->traceEventTypes.rtp) + // Audio does not play the BWE game. + if (this->kind != RTC::Media::Kind::VIDEO) { - auto rtpPacketDump = packet->FillBuffer(this->shared->channelNotifier->GetBufferBuilder()); - auto traceInfo = FBS::Consumer::CreateRtpTraceInfo( - this->shared->channelNotifier->GetBufferBuilder(), rtpPacketDump, isRtx); - - auto notification = FBS::Consumer::CreateTraceNotification( - this->shared->channelNotifier->GetBufferBuilder(), - FBS::Consumer::TraceEventType::RTP, - DepLibUV::GetTimeMs(), - FBS::Common::TraceDirection::DIRECTION_OUT, - FBS::Consumer::TraceInfo::RtpTraceInfo, - traceInfo.Union()); + return 0u; + } - EmitTraceEvent(notification); + if (!IsActive()) + { + return 0u; } + + return this->priority; } - void Consumer::EmitTraceEventPliType(uint32_t ssrc) const + uint32_t Consumer::IncreaseLayer(uint32_t bitrate, bool considerLoss) { MS_TRACE(); - if (!this->traceEventTypes.pli) + MS_ASSERT(this->externallyManagedBitrate, "bitrate is not externally managed"); + MS_ASSERT(IsActive(), "should be active"); + + float lossPercentage{ 0.0f }; + + if (considerLoss) { - return; + lossPercentage = this->rtpStream->GetLossPercentage(); } - auto traceInfo = - FBS::Consumer::CreatePliTraceInfo(this->shared->channelNotifier->GetBufferBuilder(), ssrc); - - auto notification = FBS::Consumer::CreateTraceNotification( - this->shared->channelNotifier->GetBufferBuilder(), - FBS::Consumer::TraceEventType::PLI, - DepLibUV::GetTimeMs(), - FBS::Common::TraceDirection::DIRECTION_IN, - FBS::Consumer::TraceInfo::PliTraceInfo, - traceInfo.Union()); + auto nowMs = DepLibUV::GetTimeMs(); - EmitTraceEvent(notification); + return this->producerStreamManager->IncreaseLayer(bitrate, considerLoss, lossPercentage, nowMs); } - void Consumer::EmitTraceEventFirType(uint32_t ssrc) const + void Consumer::ApplyLayers() { MS_TRACE(); - if (!this->traceEventTypes.fir) + MS_ASSERT(this->externallyManagedBitrate, "bitrate is not externally managed"); + MS_ASSERT(IsActive(), "should be active"); + + this->producerStreamManager->ApplyLayers(this->rtpStream->GetActiveMs()); + } + + uint32_t Consumer::GetDesiredBitrate() const + { + MS_TRACE(); + + MS_ASSERT(this->externallyManagedBitrate, "bitrate is not externally managed"); + + // Audio does not play the BWE game. + if (this->kind != RTC::Media::Kind::VIDEO) + { + return 0u; + } + + if (!IsActive()) + { + return 0u; + } + + auto nowMs = DepLibUV::GetTimeMs(); + auto desiredBitrate = this->producerStreamManager->GetDesiredBitrate(nowMs); + + // If consumer.rtpParameters.encodings[0].maxBitrate was given and it's + // greater than computed one, then use it. + auto maxBitrate = this->rtpParameters.encodings[0].maxBitrate; + + desiredBitrate = std::max(maxBitrate, desiredBitrate); + + return desiredBitrate; + } + + // NOLINTNEXTLINE(misc-no-recursion) + void Consumer::SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket) + { + MS_TRACE(); + +#ifdef MS_RTC_LOGGER_RTP + packet->logger.consumerId = this->id; +#endif + + if (!IsActive()) + { +#ifdef MS_RTC_LOGGER_RTP + packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::CONSUMER_INACTIVE); +#endif + + this->rtpSeqManager.Drop(packet->GetSequenceNumber()); + + return; + } + + auto payloadType = packet->GetPayloadType(); + + // NOTE: This may happen if this Consumer supports just some codecs of those + // in the corresponding Producer. + if (!this->supportedCodecPayloadTypes[payloadType]) + { + MS_WARN_DEV("payload type not supported [payloadType:%" PRIu8 "]", payloadType); + +#ifdef MS_RTC_LOGGER_RTP + packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::UNSUPPORTED_PAYLOAD_TYPE); +#endif + + this->rtpSeqManager.Drop(packet->GetSequenceNumber()); + + return; + } + + // Ask the ProducerStreamManager to process the packet. + auto action = this->producerStreamManager->ProcessRtpPacket( + packet, + this->lastSentPacketHasMarker, + this->rtpStream->GetClockRate(), + this->rtpStream->GetMaxPacketTs()); + + switch (action.type) + { + case ProducerStreamManager::RtpPacketProcessResult::Type::DROP: + { +#ifdef MS_RTC_LOGGER_RTP + packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::DROPPED_BY_CODEC); +#endif + + this->rtpSeqManager.Drop(packet->GetSequenceNumber()); + + return; + } + + case ProducerStreamManager::RtpPacketProcessResult::Type::SILENT_DROP: + { + // Don't account in seq manager. + return; + } + + case ProducerStreamManager::RtpPacketProcessResult::Type::BUFFER: + { +#ifdef MS_RTC_LOGGER_RTP + packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::NOT_A_KEYFRAME); +#endif + + StorePacketInTargetLayerRetransmissionBuffer(packet, sharedPacket); + + return; + } + + case ProducerStreamManager::RtpPacketProcessResult::Type::FORWARD: + { + // Continue below. + break; + } + } + + // Handle sync. + if (action.isSyncPacket) + { + this->rtpSeqManager.Sync(action.syncSeqValue); + } + + if (action.shouldSyncEncodingContext) + { + auto* encodingContext = this->producerStreamManager->GetEncodingContext(); + + if (encodingContext) + { + encodingContext->SyncRequired(); + } + } + + // Handle spatial layer switch. + if (action.spatialLayerSwitched) + { + // Reset the score of our RtpStream to 10. + this->rtpStream->ResetScore(10u, /*notify*/ false); + + // Emit the layersChange event. + EmitLayersChange(); + + // Emit the score event. + EmitScore(); + } + + // Handle temporal layer change. + if (action.temporalLayerChanged) + { + EmitLayersChange(); + } + + // Update RTP seq number and timestamp based on offset. + uint16_t seq; + const uint32_t timestamp = packet->GetTimestamp() - action.tsOffset; + + this->rtpSeqManager.Input(packet->GetSequenceNumber(), seq); + + // Save original packet fields. + auto origSsrc = packet->GetSsrc(); + auto origSeq = packet->GetSequenceNumber(); + auto origTimestamp = packet->GetTimestamp(); + const bool origMarker = packet->HasMarker(); + + // Rewrite packet. + packet->SetSsrc(this->rtpParameters.encodings[0].ssrc); + packet->SetSequenceNumber(seq); + packet->SetTimestamp(timestamp); + packet->SetMarker(action.marker); + +#ifdef MS_RTC_LOGGER_RTP + packet->logger.sendRtpTimestamp = timestamp; + packet->logger.sendSeqNumber = seq; +#endif + + if (action.isSyncPacket) + { + MS_DEBUG_TAG( + rtp, + "sending sync packet [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 + "] from original [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "]", + packet->GetSsrc(), + packet->GetSequenceNumber(), + packet->GetTimestamp(), + origSsrc, + origSeq, + origTimestamp); + } + + const RTC::RTP::RtpStreamSend::ReceivePacketResult result = + this->rtpStream->ReceivePacket(packet, sharedPacket); + + if (result != RTC::RTP::RtpStreamSend::ReceivePacketResult::DISCARDED) + { + if (this->rtpSeqManager.GetMaxOutput() == packet->GetSequenceNumber()) + { + this->lastSentPacketHasMarker = packet->HasMarker(); + } + + // Send the packet. + this->listener->OnConsumerSendRtpPacket(this, packet); + + // May emit 'trace' event. + EmitTraceEventRtpAndKeyFrameTypes(packet); + } + else + { + MS_WARN_TAG( + rtp, + "failed to send packet [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 + "] from original [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "]", + packet->GetSsrc(), + packet->GetSequenceNumber(), + packet->GetTimestamp(), + origSsrc, + origSeq, + origTimestamp); + +#ifdef MS_RTC_LOGGER_RTP + packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::SEND_RTP_STREAM_DISCARDED); +#endif + } + + // Restore packet fields. + packet->SetSsrc(origSsrc); + packet->SetSequenceNumber(origSeq); + packet->SetTimestamp(origTimestamp); + packet->SetMarker(origMarker); + + // Restore the original payload if needed. + packet->RestorePayload(); + + // If sharedPacket doesn't have a packet inside and it has been stored we + // need to clone the packet into it. + if (!sharedPacket.HasPacket() && result == RTC::RTP::RtpStreamSend::ReceivePacketResult::ACCEPTED_AND_STORED) + { + sharedPacket.Assign(packet); + } + + // If sent packet was the first packet of a key frame, let's send buffered + // packets belonging to the same key frame that arrived earlier due to + // packet misorder. + if (action.sendBufferedPackets) + { + // NOTE: Only send buffered packets if the first packet containing the + // key frame was sent. + if (result != RTC::RTP::RtpStreamSend::ReceivePacketResult::DISCARDED) + { + for (auto& kv : this->targetLayerRetransmissionBuffer) + { + auto& bufferedSharedPacket = kv.second; + auto* bufferedPacket = bufferedSharedPacket.GetPacket(); + + if (bufferedPacket->GetSequenceNumber() > origSeq) + { + MS_DEBUG_DEV( + "sending packet buffered in the target layer retransmission buffer " + "[ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 + "] after sending first packet of the key frame [ssrc:%" PRIu32 ", seq:%" PRIu16 + ", ts:%" PRIu32 "]", + bufferedPacket->GetSsrc(), + bufferedPacket->GetSequenceNumber(), + bufferedPacket->GetTimestamp(), + packet->GetSsrc(), + packet->GetSequenceNumber(), + packet->GetTimestamp()); + + SendRtpPacket(bufferedPacket, bufferedSharedPacket); + + // Be sure that the target layer retransmission buffer has not + // been emptied as a result of sending this packet. If so, exit + // the loop. + if (this->targetLayerRetransmissionBuffer.empty()) + { + MS_DEBUG_DEV( + "target layer retransmission buffer emptied while iterating " + "it, exiting the loop"); + + break; + } + } + } + } + + this->targetLayerRetransmissionBuffer.clear(); + } + } + + bool Consumer::GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs) + { + MS_TRACE(); + + if (static_cast((nowMs - this->lastRtcpSentTime) * 1.15) < this->maxRtcpInterval) + { + return true; + } + + auto* senderReport = this->rtpStream->GetRtcpSenderReport(nowMs); + + if (!senderReport) + { + return true; + } + + // Build SDES chunk for this sender. + auto* sdesChunk = this->rtpStream->GetRtcpSdesChunk(); + + auto* delaySinceLastRrSsrcInfo = this->rtpStream->GetRtcpXrDelaySinceLastRrSsrcInfo(nowMs); + + // RTCP Compound packet buffer cannot hold the data. + if (!packet->Add(senderReport, sdesChunk, delaySinceLastRrSsrcInfo)) + { + return false; + } + + this->lastRtcpSentTime = nowMs; + + return true; + } + + void Consumer::NeedWorstRemoteFractionLost(uint32_t /*mappedSsrc*/, uint8_t& worstRemoteFractionLost) + { + MS_TRACE(); + + if (!IsActive()) + { + return; + } + + auto fractionLost = this->rtpStream->GetFractionLost(); + + // If our fraction lost is worse than the given one, update it. + worstRemoteFractionLost = std::max(fractionLost, worstRemoteFractionLost); + } + + void Consumer::ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket) + { + MS_TRACE(); + + if (!IsActive()) + { + return; + } + + // May emit 'trace' event. + EmitTraceEventNackType(); + + this->rtpStream->ReceiveNack(nackPacket); + } + + void Consumer::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:; + } + + this->rtpStream->ReceiveKeyFrameRequest(messageType); + + if (IsActive()) + { + this->producerStreamManager->RequestKeyFrameForCurrentSpatialLayer(); + } + } + + void Consumer::ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report) + { + MS_TRACE(); + + this->rtpStream->ReceiveRtcpReceiverReport(report); + } + + void Consumer::ReceiveRtcpXrReceiverReferenceTime(RTC::RTCP::ReceiverReferenceTime* report) + { + MS_TRACE(); + + this->rtpStream->ReceiveRtcpXrReceiverReferenceTime(report); + } + + uint32_t Consumer::GetTransmissionRate(uint64_t nowMs) + { + MS_TRACE(); + + if (!IsActive()) + { + return 0u; + } + + return this->rtpStream->GetBitrate(nowMs); + } + + float Consumer::GetRtt() const + { + MS_TRACE(); + + return this->rtpStream->GetRtt(); + } + + void Consumer::UserOnTransportConnected() + { + MS_TRACE(); + + this->producerStreamManager->OnTransportConnected(); + } + + void Consumer::UserOnTransportDisconnected() + { + MS_TRACE(); + + this->rtpStream->Pause(); + this->targetLayerRetransmissionBuffer.clear(); + + this->producerStreamManager->OnTransportDisconnected(); + } + + void Consumer::UserOnPaused() + { + MS_TRACE(); + + this->rtpStream->Pause(); + this->targetLayerRetransmissionBuffer.clear(); + + this->producerStreamManager->OnPaused(); + + if (this->externallyManagedBitrate) + { + // Audio does not play the BWE game. + if (this->kind != RTC::Media::Kind::VIDEO) + { + return; + } + + this->listener->OnConsumerNeedZeroBitrate(this); + } + } + + void Consumer::UserOnResumed() + { + MS_TRACE(); + + this->producerStreamManager->OnResumed(); + } + + void Consumer::CreateRtpStream() + { + MS_TRACE(); + + auto& encoding = this->rtpParameters.encodings[0]; + const auto* mediaCodec = this->rtpParameters.GetCodecForEncoding(encoding); + + MS_DEBUG_TAG( + rtp, "[ssrc:%" PRIu32 ", payloadType:%" PRIu8 "]", encoding.ssrc, mediaCodec->payloadType); + + // Set stream params. + RTC::RTP::RtpStream::Params params; + + params.ssrc = encoding.ssrc; + params.payloadType = mediaCodec->payloadType; + params.mimeType = mediaCodec->mimeType; + params.clockRate = mediaCodec->clockRate; + params.cname = this->rtpParameters.rtcp.cname; + params.spatialLayers = encoding.spatialLayers; + params.temporalLayers = encoding.temporalLayers; + + // Check in band FEC in codec parameters. + if (mediaCodec->parameters.HasInteger("useinbandfec") && mediaCodec->parameters.GetInteger("useinbandfec") == 1) + { + MS_DEBUG_TAG(rtp, "in band FEC enabled"); + + params.useInBandFec = true; + } + + // Check DTX in codec parameters. + if (mediaCodec->parameters.HasInteger("usedtx") && mediaCodec->parameters.GetInteger("usedtx") == 1) + { + MS_DEBUG_TAG(rtp, "DTX enabled"); + + params.useDtx = true; + } + + // Check DTX in the encoding. + if (encoding.dtx) + { + MS_DEBUG_TAG(rtp, "DTX enabled"); + + params.useDtx = true; + } + + for (const auto& fb : mediaCodec->rtcpFeedback) + { + if (!params.useNack && fb.type == "nack" && fb.parameter.empty()) + { + MS_DEBUG_2TAGS(rtp, rtcp, "NACK supported"); + + params.useNack = true; + } + else if (!params.usePli && fb.type == "nack" && fb.parameter == "pli") + { + MS_DEBUG_2TAGS(rtp, rtcp, "PLI supported"); + + params.usePli = true; + } + else if (!params.useFir && fb.type == "ccm" && fb.parameter == "fir") + { + MS_DEBUG_2TAGS(rtp, rtcp, "FIR supported"); + + params.useFir = true; + } + } + + this->rtpStream = new RTC::RTP::RtpStreamSend(this, params, this->rtpParameters.mid); + this->rtpStreams.push_back(this->rtpStream); + + // If the Consumer is paused, tell the RtpStreamSend. + if (IsPaused() || IsProducerPaused()) + { + this->rtpStream->Pause(); + } + + const auto* rtxCodec = this->rtpParameters.GetRtxCodecForEncoding(encoding); + + if (rtxCodec && encoding.hasRtx) + { + this->rtpStream->SetRtx(rtxCodec->payloadType, encoding.rtx.ssrc); + } + } + + void Consumer::StorePacketInTargetLayerRetransmissionBuffer( + RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket) + { + MS_TRACE(); + + MS_DEBUG_DEV( + "storing packet in target layer retransmission buffer [ssrc:%" PRIu32 ", seq:%" PRIu16 + ", ts:%" PRIu32 "]", + packet->GetSsrc(), + packet->GetSequenceNumber(), + packet->GetTimestamp()); + + // Store original packet into the buffer. Only clone once and only if + // necessary. + if (!sharedPacket.HasPacket()) + { + sharedPacket.Assign(packet); + } + // Assert that, if sharedPacket was already filled, both packet and + // sharedPacket are the very same RTP packet. + else + { + sharedPacket.AssertSamePacket(packet); + } + + this->targetLayerRetransmissionBuffer[packet->GetSequenceNumber()] = sharedPacket; + + if (this->targetLayerRetransmissionBuffer.size() > TargetLayerRetransmissionBufferSize) + { + this->targetLayerRetransmissionBuffer.erase(this->targetLayerRetransmissionBuffer.begin()); + } + } + + void Consumer::EmitScore() const + { + MS_TRACE(); + + auto scoreOffset = FillBufferScore(this->shared->channelNotifier->GetBufferBuilder()); + + auto notificationOffset = FBS::Consumer::CreateScoreNotification( + this->shared->channelNotifier->GetBufferBuilder(), scoreOffset); + + this->shared->channelNotifier->Emit( + this->id, + FBS::Notification::Event::CONSUMER_SCORE, + FBS::Notification::Body::Consumer_ScoreNotification, + notificationOffset); + } + + void Consumer::EmitLayersChange() const + { + MS_TRACE(); + + MS_DEBUG_DEV( + "current layers changed to [spatial:%" PRIi16 ", temporal:%" PRIi16 ", consumerId:%s]", + this->producerStreamManager->GetCurrentSpatialLayer(), + this->producerStreamManager->GetCurrentTemporalLayer(), + this->id.c_str()); + + flatbuffers::Offset layersOffset; + + if (this->producerStreamManager->GetCurrentSpatialLayer() >= 0) + { + layersOffset = FBS::Consumer::CreateConsumerLayers( + this->shared->channelNotifier->GetBufferBuilder(), + this->producerStreamManager->GetCurrentSpatialLayer(), + this->producerStreamManager->GetCurrentTemporalLayer()); + } + + auto notificationOffset = FBS::Consumer::CreateLayersChangeNotification( + this->shared->channelNotifier->GetBufferBuilder(), layersOffset); + + this->shared->channelNotifier->Emit( + this->id, + FBS::Notification::Event::CONSUMER_LAYERS_CHANGE, + FBS::Notification::Body::Consumer_LayersChangeNotification, + notificationOffset); + } + + void Consumer::EmitTraceEventRtpAndKeyFrameTypes(const RTC::RTP::Packet* packet, bool isRtx) const + { + MS_TRACE(); + + if (this->traceEventTypes.keyframe && packet->IsKeyFrame()) + { + auto rtpPacketDump = packet->FillBuffer(this->shared->channelNotifier->GetBufferBuilder()); + auto traceInfo = FBS::Consumer::CreateKeyFrameTraceInfo( + this->shared->channelNotifier->GetBufferBuilder(), rtpPacketDump, isRtx); + + auto notification = FBS::Consumer::CreateTraceNotification( + this->shared->channelNotifier->GetBufferBuilder(), + FBS::Consumer::TraceEventType::KEYFRAME, + DepLibUV::GetTimeMs(), + FBS::Common::TraceDirection::DIRECTION_OUT, + FBS::Consumer::TraceInfo::KeyFrameTraceInfo, + traceInfo.Union()); + + EmitTraceEvent(notification); + } + else if (this->traceEventTypes.rtp) + { + auto rtpPacketDump = packet->FillBuffer(this->shared->channelNotifier->GetBufferBuilder()); + auto traceInfo = FBS::Consumer::CreateRtpTraceInfo( + this->shared->channelNotifier->GetBufferBuilder(), rtpPacketDump, isRtx); + + auto notification = FBS::Consumer::CreateTraceNotification( + this->shared->channelNotifier->GetBufferBuilder(), + FBS::Consumer::TraceEventType::RTP, + DepLibUV::GetTimeMs(), + FBS::Common::TraceDirection::DIRECTION_OUT, + FBS::Consumer::TraceInfo::RtpTraceInfo, + traceInfo.Union()); + + EmitTraceEvent(notification); + } + } + + void Consumer::EmitTraceEventPliType(uint32_t ssrc) const + { + MS_TRACE(); + + if (!this->traceEventTypes.pli) + { + return; + } + + auto traceInfo = + FBS::Consumer::CreatePliTraceInfo(this->shared->channelNotifier->GetBufferBuilder(), ssrc); + + auto notification = FBS::Consumer::CreateTraceNotification( + this->shared->channelNotifier->GetBufferBuilder(), + FBS::Consumer::TraceEventType::PLI, + DepLibUV::GetTimeMs(), + FBS::Common::TraceDirection::DIRECTION_IN, + FBS::Consumer::TraceInfo::PliTraceInfo, + traceInfo.Union()); + + EmitTraceEvent(notification); + } + + void Consumer::EmitTraceEventFirType(uint32_t ssrc) const + { + MS_TRACE(); + + if (!this->traceEventTypes.fir) { return; } @@ -600,4 +1690,72 @@ namespace RTC FBS::Notification::Body::Consumer_TraceNotification, notification); } + + void Consumer::OnRtpStreamScore( + RTC::RTP::RtpStream* /*rtpStream*/, uint8_t /*score*/, uint8_t /*previousScore*/) + { + MS_TRACE(); + + // Emit the score event. + EmitScore(); + + // NOTE @jmillan: Previously present in Simulcast and SVC. Does it make sense to move it here? + // Previously present in Simulcast and SVC. Does it make sense to move it here? + if (IsActive()) + { + // Just check target layers if our bitrate is not externally managed. + if (!this->externallyManagedBitrate) + { + this->producerStreamManager->MayChangeLayers(/*force*/ false); + } + } + } + + void Consumer::OnRtpStreamRetransmitRtpPacket( + RTC::RTP::RtpStreamSend* /*rtpStream*/, RTC::RTP::Packet* packet) + { + MS_TRACE(); + + this->listener->OnConsumerRetransmitRtpPacket(this, packet); + + // May emit 'trace' event. + EmitTraceEventRtpAndKeyFrameTypes(packet, this->rtpStream->HasRtx()); + } + + /* ProducerStreamManager::Listener methods. */ + + void Consumer::OnProducerStreamManagerKeyFrameRequested(uint32_t mappedSsrc) + { + MS_TRACE(); + + this->listener->OnConsumerKeyFrameRequested(this, mappedSsrc); + } + + void Consumer::OnProducerStreamManagerNeedBitrateChange() + { + MS_TRACE(); + + this->listener->OnConsumerNeedBitrateChange(this); + } + + void Consumer::OnProducerStreamManagerLayersChanged() + { + MS_TRACE(); + + EmitLayersChange(); + } + + void Consumer::OnProducerStreamManagerClearRetransmissionBuffer() + { + MS_TRACE(); + + this->targetLayerRetransmissionBuffer.clear(); + } + + void Consumer::OnProducerStreamManagerScore() + { + MS_TRACE(); + + EmitScore(); + } } // namespace RTC diff --git a/worker/src/RTC/OldConsumer.cpp b/worker/src/RTC/OldConsumer.cpp new file mode 100644 index 0000000000..12258fe0e0 --- /dev/null +++ b/worker/src/RTC/OldConsumer.cpp @@ -0,0 +1,603 @@ +#define MS_CLASS "RTC::OldConsumer" +// #define MS_LOG_DEV_LEVEL 3 + +#include "RTC/OldConsumer.hpp" +#include "DepLibUV.hpp" +#include "Logger.hpp" +#include "MediaSoupErrors.hpp" + +namespace RTC +{ + /* Instance methods. */ + + OldConsumer::OldConsumer( + RTC::Shared* shared, + const std::string& id, + const std::string& producerId, + Listener* listener, + const FBS::Transport::ConsumeRequest* data, + RTC::RtpParameters::Type type) + : id(id), producerId(producerId), shared(shared), listener(listener), + kind(RTC::Media::Kind(data->kind())), type(type) + { + MS_TRACE(); + + // This may throw. + this->rtpParameters = RTC::RtpParameters(data->rtpParameters()); + + if (this->rtpParameters.encodings.empty()) + { + MS_THROW_TYPE_ERROR("empty rtpParameters.encodings"); + } + + // All encodings must have SSRCs. + for (auto& encoding : this->rtpParameters.encodings) + { + if (encoding.ssrc == 0) + { + MS_THROW_TYPE_ERROR("invalid encoding in rtpParameters (missing ssrc)"); + } + else if (encoding.hasRtx && encoding.rtx.ssrc == 0) + { + MS_THROW_TYPE_ERROR("invalid encoding in rtpParameters (missing rtx.ssrc)"); + } + } + + if (data->consumableRtpEncodings()->size() == 0) + { + MS_THROW_TYPE_ERROR("empty consumableRtpEncodings"); + } + + this->consumableRtpEncodings.reserve(data->consumableRtpEncodings()->size()); + + for (size_t i{ 0 }; i < data->consumableRtpEncodings()->size(); ++i) + { + const auto* entry = data->consumableRtpEncodings()->Get(i); + + // This may throw due the constructor of RTC::RtpEncodingParameters. + this->consumableRtpEncodings.emplace_back(entry); + + // Verify that it has ssrc field. + auto& encoding = this->consumableRtpEncodings[i]; + + if (encoding.ssrc == 0u) + { + MS_THROW_TYPE_ERROR("wrong encoding in consumableRtpEncodings (missing ssrc)"); + } + } + + // Fill RTP header extension ids and their mapped values. + // This may throw. + for (auto& exten : this->rtpParameters.headerExtensions) + { + if (exten.id == 0u) + { + MS_THROW_TYPE_ERROR("RTP extension id cannot be 0"); + } + + if (this->rtpHeaderExtensionIds.mid == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::MID) + { + this->rtpHeaderExtensionIds.mid = exten.id; + } + + if (this->rtpHeaderExtensionIds.rid == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::RTP_STREAM_ID) + { + this->rtpHeaderExtensionIds.rid = exten.id; + } + + if (this->rtpHeaderExtensionIds.rrid == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::REPAIRED_RTP_STREAM_ID) + { + this->rtpHeaderExtensionIds.rrid = exten.id; + } + + if (this->rtpHeaderExtensionIds.absSendTime == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::ABS_SEND_TIME) + { + this->rtpHeaderExtensionIds.absSendTime = exten.id; + } + + if (this->rtpHeaderExtensionIds.transportWideCc01 == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::TRANSPORT_WIDE_CC_01) + { + this->rtpHeaderExtensionIds.transportWideCc01 = exten.id; + } + + if (this->rtpHeaderExtensionIds.ssrcAudioLevel == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::SSRC_AUDIO_LEVEL) + { + this->rtpHeaderExtensionIds.ssrcAudioLevel = exten.id; + } + + if ( + this->rtpHeaderExtensionIds.dependencyDescriptor == 0u && + exten.type == RTC::RtpHeaderExtensionUri::Type::DEPENDENCY_DESCRIPTOR) + { + this->rtpHeaderExtensionIds.dependencyDescriptor = exten.id; + } + + if (this->rtpHeaderExtensionIds.videoOrientation == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::VIDEO_ORIENTATION) + { + this->rtpHeaderExtensionIds.videoOrientation = exten.id; + } + + if (this->rtpHeaderExtensionIds.videoOrientation == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::VIDEO_ORIENTATION) + { + this->rtpHeaderExtensionIds.videoOrientation = exten.id; + } + + if (this->rtpHeaderExtensionIds.absCaptureTime == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::ABS_CAPTURE_TIME) + { + this->rtpHeaderExtensionIds.absCaptureTime = exten.id; + } + + if (this->rtpHeaderExtensionIds.playoutDelay == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::PLAYOUT_DELAY) + { + this->rtpHeaderExtensionIds.playoutDelay = exten.id; + } + + if (this->rtpHeaderExtensionIds.mediasoupPacketId == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::MEDIASOUP_PACKET_ID) + { + this->rtpHeaderExtensionIds.mediasoupPacketId = exten.id; + } + } + + // paused is set to false by default. + this->paused = data->paused(); + + // Fill supported codec payload types. + for (auto& codec : this->rtpParameters.codecs) + { + if (codec.mimeType.IsMediaCodec()) + { + this->supportedCodecPayloadTypes[codec.payloadType] = true; + } + } + + // Fill media SSRCs vector. + for (auto& encoding : this->rtpParameters.encodings) + { + this->mediaSsrcs.push_back(encoding.ssrc); + } + + // Fill RTX SSRCs vector. + for (auto& encoding : this->rtpParameters.encodings) + { + if (encoding.hasRtx) + { + this->rtxSsrcs.push_back(encoding.rtx.ssrc); + } + } + + // Set the RTCP report generation interval. + if (this->kind == RTC::Media::Kind::AUDIO) + { + this->maxRtcpInterval = RTC::RTCP::MaxAudioIntervalMs; + } + else + { + this->maxRtcpInterval = RTC::RTCP::MaxVideoIntervalMs; + } + } + + OldConsumer::~OldConsumer() + { + MS_TRACE(); + } + + flatbuffers::Offset OldConsumer::FillBuffer( + flatbuffers::FlatBufferBuilder& builder) const + { + MS_TRACE(); + + // Add rtpParameters. + auto rtpParameters = this->rtpParameters.FillBuffer(builder); + + // Add consumableRtpEncodings. + std::vector> consumableRtpEncodings; + consumableRtpEncodings.reserve(this->consumableRtpEncodings.size()); + + for (const auto& encoding : this->consumableRtpEncodings) + { + consumableRtpEncodings.emplace_back(encoding.FillBuffer(builder)); + } + + // Add supportedCodecPayloadTypes. + std::vector supportedCodecPayloadTypes; + + for (auto i = 0; i < 128; ++i) + { + if (this->supportedCodecPayloadTypes[i]) + { + supportedCodecPayloadTypes.push_back(i); + } + } + + // Add traceEventTypes. + std::vector traceEventTypes; + + if (this->traceEventTypes.rtp) + { + traceEventTypes.emplace_back(FBS::Consumer::TraceEventType::RTP); + } + if (this->traceEventTypes.keyframe) + { + traceEventTypes.emplace_back(FBS::Consumer::TraceEventType::KEYFRAME); + } + if (this->traceEventTypes.nack) + { + traceEventTypes.emplace_back(FBS::Consumer::TraceEventType::NACK); + } + if (this->traceEventTypes.pli) + { + traceEventTypes.emplace_back(FBS::Consumer::TraceEventType::PLI); + } + if (this->traceEventTypes.fir) + { + traceEventTypes.emplace_back(FBS::Consumer::TraceEventType::FIR); + } + + return FBS::Consumer::CreateBaseConsumerDumpDirect( + builder, + this->id.c_str(), + RTC::RtpParameters::TypeToFbs(this->type), + this->producerId.c_str(), + this->kind == RTC::Media::Kind::AUDIO ? FBS::RtpParameters::MediaKind::AUDIO + : FBS::RtpParameters::MediaKind::VIDEO, + rtpParameters, + &consumableRtpEncodings, + &supportedCodecPayloadTypes, + &traceEventTypes, + this->paused, + this->producerPaused, + this->priority); + } + + void OldConsumer::HandleRequest(Channel::ChannelRequest* request) + { + MS_TRACE(); + + switch (request->method) + { + case Channel::ChannelRequest::Method::CONSUMER_GET_STATS: + { + auto responseOffset = FillBufferStats(request->GetBufferBuilder()); + + request->Accept(FBS::Response::Body::Consumer_GetStatsResponse, responseOffset); + + break; + } + + case Channel::ChannelRequest::Method::CONSUMER_PAUSE: + { + if (this->paused) + { + request->Accept(); + + break; + } + + const bool wasActive = IsActive(); + + this->paused = true; + + MS_DEBUG_DEV("Consumer paused [consumerId:%s]", this->id.c_str()); + + if (wasActive) + { + UserOnPaused(); + } + + request->Accept(); + + break; + } + + case Channel::ChannelRequest::Method::CONSUMER_RESUME: + { + if (!this->paused) + { + request->Accept(); + + break; + } + + this->paused = false; + + MS_DEBUG_DEV("Consumer resumed [consumerId:%s]", this->id.c_str()); + + if (IsActive()) + { + UserOnResumed(); + } + + request->Accept(); + + break; + } + + case Channel::ChannelRequest::Method::CONSUMER_SET_PRIORITY: + { + const auto* body = request->data->body_as(); + + if (body->priority() < 1u) + { + MS_THROW_TYPE_ERROR("wrong priority (must be higher than 0)"); + } + + this->priority = body->priority(); + + auto responseOffset = + FBS::Consumer::CreateSetPriorityResponse(request->GetBufferBuilder(), this->priority); + + request->Accept(FBS::Response::Body::Consumer_SetPriorityResponse, responseOffset); + + break; + } + + case Channel::ChannelRequest::Method::CONSUMER_ENABLE_TRACE_EVENT: + { + const auto* body = request->data->body_as(); + + // Reset traceEventTypes. + struct TraceEventTypes newTraceEventTypes; + + for (const auto& type : *body->events()) + { + switch (type) + { + case FBS::Consumer::TraceEventType::KEYFRAME: + { + newTraceEventTypes.keyframe = true; + + break; + } + case FBS::Consumer::TraceEventType::FIR: + { + newTraceEventTypes.fir = true; + + break; + } + case FBS::Consumer::TraceEventType::NACK: + { + newTraceEventTypes.nack = true; + + break; + } + case FBS::Consumer::TraceEventType::PLI: + { + newTraceEventTypes.pli = true; + + break; + } + case FBS::Consumer::TraceEventType::RTP: + { + newTraceEventTypes.rtp = true; + + break; + } + } + } + + this->traceEventTypes = newTraceEventTypes; + + request->Accept(); + + break; + } + + default: + { + MS_THROW_ERROR("unknown method '%s'", request->methodCStr); + } + } + } + + void OldConsumer::TransportConnected() + { + MS_TRACE(); + + if (this->transportConnected) + { + return; + } + + this->transportConnected = true; + + MS_DEBUG_DEV("Transport connected [consumerId:%s]", this->id.c_str()); + + UserOnTransportConnected(); + } + + void OldConsumer::TransportDisconnected() + { + MS_TRACE(); + + if (!this->transportConnected) + { + return; + } + + this->transportConnected = false; + + MS_DEBUG_DEV("Transport disconnected [consumerId:%s]", this->id.c_str()); + + UserOnTransportDisconnected(); + } + + void OldConsumer::ProducerPaused() + { + MS_TRACE(); + + if (this->producerPaused) + { + return; + } + + const bool wasActive = IsActive(); + + this->producerPaused = true; + + MS_DEBUG_DEV("Producer paused [consumerId:%s]", this->id.c_str()); + + if (wasActive) + { + UserOnPaused(); + } + + this->shared->channelNotifier->Emit(this->id, FBS::Notification::Event::CONSUMER_PRODUCER_PAUSE); + } + + void OldConsumer::ProducerResumed() + { + MS_TRACE(); + + if (!this->producerPaused) + { + return; + } + + this->producerPaused = false; + + MS_DEBUG_DEV("Producer resumed [consumerId:%s]", this->id.c_str()); + + if (IsActive()) + { + UserOnResumed(); + } + + this->shared->channelNotifier->Emit(this->id, FBS::Notification::Event::CONSUMER_PRODUCER_RESUME); + } + + void OldConsumer::ProducerRtpStreamScores(const std::vector* scores) + { + MS_TRACE(); + + // This is gonna be a constant pointer. + this->producerRtpStreamScores = scores; + } + + // The caller (Router) is supposed to proceed with the deletion of this Consumer + // right after calling this method. Otherwise ugly things may happen. + void OldConsumer::ProducerClosed() + { + MS_TRACE(); + + this->producerClosed = true; + + MS_DEBUG_DEV("Producer closed [consumerId:%s]", this->id.c_str()); + + this->shared->channelNotifier->Emit(this->id, FBS::Notification::Event::CONSUMER_PRODUCER_CLOSE); + + this->listener->OnConsumerProducerClosed(this); + } + + void OldConsumer::EmitTraceEventRtpAndKeyFrameTypes(const RTC::RTP::Packet* packet, bool isRtx) const + { + MS_TRACE(); + + if (this->traceEventTypes.keyframe && packet->IsKeyFrame()) + { + auto rtpPacketDump = packet->FillBuffer(this->shared->channelNotifier->GetBufferBuilder()); + auto traceInfo = FBS::Consumer::CreateKeyFrameTraceInfo( + this->shared->channelNotifier->GetBufferBuilder(), rtpPacketDump, isRtx); + + auto notification = FBS::Consumer::CreateTraceNotification( + this->shared->channelNotifier->GetBufferBuilder(), + FBS::Consumer::TraceEventType::KEYFRAME, + DepLibUV::GetTimeMs(), + FBS::Common::TraceDirection::DIRECTION_OUT, + FBS::Consumer::TraceInfo::KeyFrameTraceInfo, + traceInfo.Union()); + + EmitTraceEvent(notification); + } + else if (this->traceEventTypes.rtp) + { + auto rtpPacketDump = packet->FillBuffer(this->shared->channelNotifier->GetBufferBuilder()); + auto traceInfo = FBS::Consumer::CreateRtpTraceInfo( + this->shared->channelNotifier->GetBufferBuilder(), rtpPacketDump, isRtx); + + auto notification = FBS::Consumer::CreateTraceNotification( + this->shared->channelNotifier->GetBufferBuilder(), + FBS::Consumer::TraceEventType::RTP, + DepLibUV::GetTimeMs(), + FBS::Common::TraceDirection::DIRECTION_OUT, + FBS::Consumer::TraceInfo::RtpTraceInfo, + traceInfo.Union()); + + EmitTraceEvent(notification); + } + } + + void OldConsumer::EmitTraceEventPliType(uint32_t ssrc) const + { + MS_TRACE(); + + if (!this->traceEventTypes.pli) + { + return; + } + + auto traceInfo = + FBS::Consumer::CreatePliTraceInfo(this->shared->channelNotifier->GetBufferBuilder(), ssrc); + + auto notification = FBS::Consumer::CreateTraceNotification( + this->shared->channelNotifier->GetBufferBuilder(), + FBS::Consumer::TraceEventType::PLI, + DepLibUV::GetTimeMs(), + FBS::Common::TraceDirection::DIRECTION_IN, + FBS::Consumer::TraceInfo::PliTraceInfo, + traceInfo.Union()); + + EmitTraceEvent(notification); + } + + void OldConsumer::EmitTraceEventFirType(uint32_t ssrc) const + { + MS_TRACE(); + + if (!this->traceEventTypes.fir) + { + return; + } + + auto traceInfo = + FBS::Consumer::CreateFirTraceInfo(this->shared->channelNotifier->GetBufferBuilder(), ssrc); + + auto notification = FBS::Consumer::CreateTraceNotification( + this->shared->channelNotifier->GetBufferBuilder(), + FBS::Consumer::TraceEventType::FIR, + DepLibUV::GetTimeMs(), + FBS::Common::TraceDirection::DIRECTION_IN, + FBS::Consumer::TraceInfo::FirTraceInfo, + traceInfo.Union()); + + EmitTraceEvent(notification); + } + + void OldConsumer::EmitTraceEventNackType() const + { + MS_TRACE(); + + if (!this->traceEventTypes.nack) + { + return; + } + + auto notification = FBS::Consumer::CreateTraceNotification( + this->shared->channelNotifier->GetBufferBuilder(), + FBS::Consumer::TraceEventType::NACK, + DepLibUV::GetTimeMs(), + FBS::Common::TraceDirection::DIRECTION_IN); + + EmitTraceEvent(notification); + } + + void OldConsumer::EmitTraceEvent(flatbuffers::Offset& notification) const + { + MS_TRACE(); + + this->shared->channelNotifier->Emit( + this->id, + FBS::Notification::Event::CONSUMER_TRACE, + FBS::Notification::Body::Consumer_TraceNotification, + notification); + } +} // namespace RTC diff --git a/worker/src/RTC/PipeConsumer.cpp b/worker/src/RTC/PipeConsumer.cpp index 746d5f68f6..83189e38a7 100644 --- a/worker/src/RTC/PipeConsumer.cpp +++ b/worker/src/RTC/PipeConsumer.cpp @@ -61,9 +61,10 @@ namespace RTC RTC::Shared* shared, const std::string& id, const std::string& producerId, - RTC::Consumer::Listener* listener, + RTC::OldConsumer::Listener* listener, const FBS::Transport::ConsumeRequest* data) - : RTC::Consumer::Consumer(shared, id, producerId, listener, data, RTC::RtpParameters::Type::PIPE) + : RTC::OldConsumer::OldConsumer( + shared, id, producerId, listener, data, RTC::RtpParameters::Type::PIPE) { MS_TRACE(); @@ -113,7 +114,7 @@ namespace RTC MS_TRACE(); // Call the parent method. - auto base = RTC::Consumer::FillBuffer(builder); + auto base = RTC::OldConsumer::FillBuffer(builder); // Add rtpStreams. std::vector> rtpStreams; @@ -199,7 +200,7 @@ namespace RTC default: { // Pass it to the parent class. - RTC::Consumer::HandleRequest(request); + RTC::OldConsumer::HandleRequest(request); } } } diff --git a/worker/src/RTC/SimpleConsumer.cpp b/worker/src/RTC/SimpleConsumer.cpp index 3b3962e6ff..7e48377a59 100644 --- a/worker/src/RTC/SimpleConsumer.cpp +++ b/worker/src/RTC/SimpleConsumer.cpp @@ -25,9 +25,10 @@ namespace RTC RTC::Shared* shared, const std::string& id, const std::string& producerId, - RTC::Consumer::Listener* listener, + RTC::OldConsumer::Listener* listener, const FBS::Transport::ConsumeRequest* data) - : RTC::Consumer::Consumer(shared, id, producerId, listener, data, RTC::RtpParameters::Type::SIMPLE) + : RTC::OldConsumer::OldConsumer( + shared, id, producerId, listener, data, RTC::RtpParameters::Type::SIMPLE) { MS_TRACE(); @@ -91,7 +92,7 @@ namespace RTC MS_TRACE(); // Call the parent method. - auto base = RTC::Consumer::FillBuffer(builder); + auto base = RTC::OldConsumer::FillBuffer(builder); // Add rtpStream. std::vector> rtpStreams; rtpStreams.emplace_back(this->rtpStream->FillBuffer(builder)); @@ -180,7 +181,7 @@ namespace RTC default: { // Pass it to the parent class. - RTC::Consumer::HandleRequest(request); + RTC::OldConsumer::HandleRequest(request); } } } diff --git a/worker/src/RTC/SimpleProducerStreamManager.cpp b/worker/src/RTC/SimpleProducerStreamManager.cpp new file mode 100644 index 0000000000..b9dab5d631 --- /dev/null +++ b/worker/src/RTC/SimpleProducerStreamManager.cpp @@ -0,0 +1,323 @@ +#define MS_CLASS "RTC::SimpleProducerStreamManager" +// #define MS_LOG_DEV_LEVEL 3 + +#include "RTC/SimpleProducerStreamManager.hpp" +#include "Logger.hpp" + +namespace RTC +{ + /* Instance methods. */ + + SimpleProducerStreamManager::SimpleProducerStreamManager( + const std::vector& consumableRtpEncodings, + const VideoLayers& preferredLayers, + std::unique_ptr encodingContext, + RTC::Media::Kind kind, + bool keyFrameSupported, + Listener* listener) + : ProducerStreamManager( + consumableRtpEncodings, preferredLayers, std::move(encodingContext), kind, keyFrameSupported, listener) + { + MS_TRACE(); + } + + RTC::RTP::RtpStreamRecv* SimpleProducerStreamManager::GetProducerCurrentRtpStream() const + { + MS_TRACE(); + + return this->producerRtpStream; + } + + RTC::RTP::RtpStreamRecv* SimpleProducerStreamManager::GetProducerTargetRtpStream() const + { + MS_TRACE(); + + return this->producerRtpStream; + } + + bool SimpleProducerStreamManager::IsProducerStreamActive() const + { + MS_TRACE(); + + // clang-format off + return ( + this->producerRtpStream && + // If there is no RTP inactivity check do not consider the stream + // inactive despite it has score 0. + (this->producerRtpStream->GetScore() > 0u || !this->producerRtpStream->HasRtpInactivityCheckEnabled()) + ); + + // clang-format on + } + + void SimpleProducerStreamManager::ProducerRtpStream( + RTC::RTP::RtpStreamRecv* rtpStream, uint32_t /*mappedSsrc*/) + { + MS_TRACE(); + + this->producerRtpStream = rtpStream; + } + + void SimpleProducerStreamManager::ProducerNewRtpStream( + RTC::RTP::RtpStreamRecv* rtpStream, uint32_t /*mappedSsrc*/) + { + MS_TRACE(); + + this->producerRtpStream = rtpStream; + + // Emit the score event. + this->listener->OnProducerStreamManagerScore(); + } + + void SimpleProducerStreamManager::ProducerRtpStreamScore( + RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint8_t /*score*/, uint8_t /*previousScore*/) + { + MS_TRACE(); + } + + void SimpleProducerStreamManager::ProducerRtcpSenderReport( + RTC::RTP::RtpStreamRecv* /*rtpStream*/, bool /*first*/) + { + MS_TRACE(); + + // Do nothing. + } + + uint32_t SimpleProducerStreamManager::IncreaseLayer( + uint32_t bitrate, bool /*considerLoss*/, float /*lossPercentage*/, uint64_t nowMs) + { + MS_TRACE(); + + MS_ASSERT(this->externallyManagedBitrate, "bitrate is not externally managed"); + MS_ASSERT(this->kind == RTC::Media::Kind::VIDEO, "should be video"); + MS_ASSERT(IsActive(), "should be active"); + + // If this is not the first time this method is called within the same + // iteration, return 0 since a video SimpleConsumer does not keep state + // about this. + if (this->managingBitrate) + { + return 0u; + } + + this->managingBitrate = true; + + if (!this->producerRtpStream) + { + return 0u; + } + + // Video SimpleConsumer does not really play the BWE game. However, let's + // be honest and try to be nice. + auto desiredBitrate = this->producerRtpStream->GetBitrate(nowMs); + + if (desiredBitrate < bitrate) + { + return desiredBitrate; + } + else + { + return bitrate; + } + } + + void SimpleProducerStreamManager::ApplyLayers(uint64_t /*rtpStreamActiveMs*/) + { + MS_TRACE(); + + MS_ASSERT(this->externallyManagedBitrate, "bitrate is not externally managed"); + MS_ASSERT(this->kind == RTC::Media::Kind::VIDEO, "should be video"); + MS_ASSERT(IsActive(), "should be active"); + + this->managingBitrate = false; + + // Simple does not play the BWE game (even if video kind). + } + + uint32_t SimpleProducerStreamManager::GetDesiredBitrate(uint64_t nowMs) const + { + MS_TRACE(); + + MS_ASSERT(this->externallyManagedBitrate, "bitrate is not externally managed"); + + // Audio does not play the BWE game. + if (this->kind != RTC::Media::Kind::VIDEO) + { + return 0u; + } + + if (!IsActive()) + { + return 0u; + } + + return this->producerRtpStream->GetBitrate(nowMs); + } + + ProducerStreamManager::RtpPacketProcessResult SimpleProducerStreamManager::ProcessRtpPacket( + RTC::RTP::Packet* packet, + bool /*lastSentPacketHasMarker*/, + uint32_t /*clockRate*/, + uint32_t /*maxPacketTs*/) + { + MS_TRACE(); + + RtpPacketProcessResult result; + + // If we need to sync, support key frames and this is not a key frame, + // ignore the packet. + if (this->syncRequired && this->keyFrameSupported && !packet->IsKeyFrame()) + { + // NOTE: No need to drop the packet in the RTP sequence manager since here + // we are blocking all packets but the key frame that would trigger sync + // below. + + // Store the packet for the scenario in which this packet is part of the + // key frame and it arrived before the first packet of the key frame. + result.type = RtpPacketProcessResult::Type::BUFFER; + + return result; + } + + // Packets with only padding are not forwarded. + if (packet->GetPayloadLength() == 0) + { + result.type = RtpPacketProcessResult::Type::DROP; + + return result; + } + + // Preserve the original marker bit. ProcessPayload may update it. + bool marker{ packet->HasMarker() }; + + // Process the payload if needed. Drop packet if necessary. + if (this->encodingContext && !packet->ProcessPayload(this->encodingContext.get(), marker)) + { + MS_DEBUG_DEV( + "discarding packet [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "]", + packet->GetSsrc(), + packet->GetSequenceNumber(), + packet->GetTimestamp()); + + result.type = RtpPacketProcessResult::Type::DROP; + + return result; + } + + // Whether this is the first packet after re-sync. + const bool isSyncPacket = this->syncRequired; + + // Whether packets stored in the target layer retransmission buffer must be + // sent once this packet is sent. + bool sendBufferedPackets{ false }; + + // Sync sequence number and timestamp if required. + if (isSyncPacket) + { + if (packet->IsKeyFrame()) + { + MS_DEBUG_TAG( + rtp, + "sync key frame received [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "]", + packet->GetSsrc(), + packet->GetSequenceNumber(), + packet->GetTimestamp()); + + sendBufferedPackets = true; + } + + result.isSyncPacket = true; + result.syncSeqValue = packet->GetSequenceNumber() - 1; + + this->syncRequired = false; + } + + // Set forward action. + result.type = RtpPacketProcessResult::Type::FORWARD; + result.tsOffset = 0u; + result.marker = marker; + result.sendBufferedPackets = sendBufferedPackets; + + return result; + } + + void SimpleProducerStreamManager::RequestKeyFrame() + { + MS_TRACE(); + + if (this->kind != RTC::Media::Kind::VIDEO) + { + return; + } + + auto mappedSsrc = this->consumableRtpEncodings[0].ssrc; + + this->listener->OnProducerStreamManagerKeyFrameRequested(mappedSsrc); + } + + void SimpleProducerStreamManager::RequestKeyFrameForTargetSpatialLayer() + { + MS_TRACE(); + + RequestKeyFrame(); + } + + void SimpleProducerStreamManager::RequestKeyFrameForCurrentSpatialLayer() + { + MS_TRACE(); + + RequestKeyFrame(); + } + + void SimpleProducerStreamManager::UpdateTargetLayers(int16_t /*spatial*/, int16_t /*temporal*/) + { + MS_TRACE(); + } + + bool SimpleProducerStreamManager::RecalculateTargetLayers(VideoLayers& /*newTargetLayers*/) const + { + MS_TRACE(); + + // No layer changes possible for Simple. + return false; + } + + void SimpleProducerStreamManager::OnTransportConnected() + { + MS_TRACE(); + + this->syncRequired = true; + + if (this->listener->IsActive()) + { + RequestKeyFrame(); + } + } + + void SimpleProducerStreamManager::OnTransportDisconnected() + { + MS_TRACE(); + + // Nothing specific for Simple. + } + + void SimpleProducerStreamManager::OnPaused() + { + MS_TRACE(); + + // Nothing specific for Simple. + } + + void SimpleProducerStreamManager::OnResumed() + { + MS_TRACE(); + + this->syncRequired = true; + + if (this->listener->IsActive()) + { + RequestKeyFrame(); + } + } + +} // namespace RTC diff --git a/worker/src/RTC/SimulcastConsumer.cpp b/worker/src/RTC/SimulcastConsumer.cpp index ccde25bd08..33d49471c4 100644 --- a/worker/src/RTC/SimulcastConsumer.cpp +++ b/worker/src/RTC/SimulcastConsumer.cpp @@ -28,9 +28,9 @@ namespace RTC RTC::Shared* shared, const std::string& id, const std::string& producerId, - RTC::Consumer::Listener* listener, + RTC::OldConsumer::Listener* listener, const FBS::Transport::ConsumeRequest* data) - : RTC::Consumer::Consumer( + : RTC::OldConsumer::OldConsumer( shared, id, producerId, listener, data, RTC::RtpParameters::Type::SIMULCAST) { MS_TRACE(); @@ -149,7 +149,7 @@ namespace RTC MS_TRACE(); // Call the parent method. - auto base = RTC::Consumer::FillBuffer(builder); + auto base = RTC::OldConsumer::FillBuffer(builder); // Add rtpStream. std::vector> rtpStreams; rtpStreams.emplace_back(this->rtpStream->FillBuffer(builder)); @@ -300,7 +300,7 @@ namespace RTC default: { // Pass it to the parent class. - RTC::Consumer::HandleRequest(request); + RTC::OldConsumer::HandleRequest(request); } } } @@ -347,7 +347,7 @@ namespace RTC // Emit the score event. EmitScore(); - if (RTC::Consumer::IsActive()) + if (RTC::OldConsumer::IsActive()) { // All Producer streams are dead. if (!IsActive()) diff --git a/worker/src/RTC/SimulcastProducerStreamManager.cpp b/worker/src/RTC/SimulcastProducerStreamManager.cpp new file mode 100644 index 0000000000..fe348da3a7 --- /dev/null +++ b/worker/src/RTC/SimulcastProducerStreamManager.cpp @@ -0,0 +1,1004 @@ +#define MS_CLASS "RTC::SimulcastProducerStreamManager" +// #define MS_LOG_DEV_LEVEL 3 + +#include "RTC/SimulcastProducerStreamManager.hpp" +#include "Logger.hpp" + +namespace RTC +{ + /* Static. */ + + static constexpr uint64_t StreamMinActiveMs{ 2000u }; + static constexpr uint64_t BweDowngradeConservativeMs{ 10000u }; + static constexpr uint64_t BweDowngradeMinActiveMs{ 8000u }; + static constexpr uint16_t MaxSequenceNumberGap{ 100u }; + + /* Instance methods. */ + + SimulcastProducerStreamManager::SimulcastProducerStreamManager( + const std::vector& consumableRtpEncodings, + const VideoLayers& preferredLayers, + std::unique_ptr encodingContext, + RTC::Media::Kind kind, + bool keyFrameSupported, + Listener* listener) + : ProducerStreamManager( + consumableRtpEncodings, preferredLayers, std::move(encodingContext), kind, keyFrameSupported, listener) + { + MS_TRACE(); + + // Build mapMappedSsrcSpatialLayer and reserve producerRtpStreams. + for (size_t idx{ 0u }; idx < this->consumableRtpEncodings.size(); ++idx) + { + auto& encoding = this->consumableRtpEncodings[idx]; + + this->mapMappedSsrcSpatialLayer[encoding.ssrc] = static_cast(idx); + } + + this->producerRtpStreams.resize(this->consumableRtpEncodings.size(), nullptr); + } + + RTC::RTP::RtpStreamRecv* SimulcastProducerStreamManager::GetProducerCurrentRtpStream() const + { + MS_TRACE(); + + if (this->currentSpatialLayer == -1) + { + return nullptr; + } + + // This may return nullptr. + return this->producerRtpStreams.at(this->currentSpatialLayer); + } + + RTC::RTP::RtpStreamRecv* SimulcastProducerStreamManager::GetProducerTargetRtpStream() const + { + MS_TRACE(); + + if (this->targetLayers.spatial == -1) + { + return nullptr; + } + + // This may return nullptr. + return this->producerRtpStreams.at(this->targetLayers.spatial); + } + + bool SimulcastProducerStreamManager::IsProducerStreamActive() const + { + MS_TRACE(); + + // clang-format off + return std::any_of( + this->producerRtpStreams.begin(), + this->producerRtpStreams.end(), + [](const RTC::RTP::RtpStreamRecv* rtpStream) + { + return ( + rtpStream != nullptr && + (rtpStream->GetScore() > 0u || !rtpStream->HasRtpInactivityCheckEnabled()) + ); + } + ); + // clang-format on + } + + void SimulcastProducerStreamManager::ProducerRtpStream( + RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) + { + MS_TRACE(); + + auto it = this->mapMappedSsrcSpatialLayer.find(mappedSsrc); + + MS_ASSERT(it != this->mapMappedSsrcSpatialLayer.end(), "unknown mappedSsrc"); + + const int16_t spatialLayer = it->second; + + this->producerRtpStreams[spatialLayer] = rtpStream; + } + + void SimulcastProducerStreamManager::ProducerNewRtpStream( + RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) + { + MS_TRACE(); + + auto it = this->mapMappedSsrcSpatialLayer.find(mappedSsrc); + + MS_ASSERT(it != this->mapMappedSsrcSpatialLayer.end(), "unknown mappedSsrc"); + + const int16_t spatialLayer = it->second; + + this->producerRtpStreams[spatialLayer] = rtpStream; + + // Emit the score event. + this->listener->OnProducerStreamManagerScore(); + + if (IsActive()) + { + MayChangeLayers(/*force*/ false); + } + } + + void SimulcastProducerStreamManager::ProducerRtpStreamScore( + RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint8_t score, uint8_t previousScore) + { + MS_TRACE(); + + if (this->listener->IsActive()) + { + // All Producer streams are dead. + if (!IsProducerStreamActive()) + { + UpdateTargetLayers(-1, -1); + } + // Just check target layers if the stream has died or reborned or if + // bitrate is not externally managed. + else if (!this->externallyManagedBitrate || (score == 0u || previousScore == 0u)) + { + MayChangeLayers(/*force*/ false); + } + } + } + + void SimulcastProducerStreamManager::ProducerRtcpSenderReport( + RTC::RTP::RtpStreamRecv* rtpStream, bool first) + { + MS_TRACE(); + + // Just interested if this is the first Sender Report for a RTP stream. + if (!first) + { + return; + } + + MS_DEBUG_TAG(simulcast, "first SenderReport [ssrc:%" PRIu32 "]", rtpStream->GetSsrc()); + + // If our RTP timestamp reference stream does not yet have SR, do nothing + // since we know we won't be able to switch. + auto* producerTsReferenceRtpStream = GetProducerTsReferenceRtpStream(); + + if (!producerTsReferenceRtpStream || !producerTsReferenceRtpStream->GetSenderReportNtpMs()) + { + return; + } + + if (IsActive()) + { + MayChangeLayers(/*force*/ false); + } + } + + uint32_t SimulcastProducerStreamManager::IncreaseLayer( + uint32_t bitrate, bool considerLoss, float lossPercentage, uint64_t nowMs) + { + MS_TRACE(); + + // If already in the preferred layers, do nothing. + if (this->provisionalTargetLayers == this->preferredLayers) + { + return 0u; + } + + uint32_t virtualBitrate; + + if (considerLoss) + { + // Calculate virtual available bitrate based on given bitrate and our + // packet lost. + if (lossPercentage < 2) + { + virtualBitrate = 1.08 * bitrate; + } + else if (lossPercentage > 10) + { + virtualBitrate = (1 - 0.5 * (lossPercentage / 100)) * bitrate; + } + else + { + virtualBitrate = bitrate; + } + } + else + { + virtualBitrate = bitrate; + } + + uint32_t requiredBitrate{ 0u }; + int16_t spatialLayer{ 0 }; + int16_t temporalLayer{ 0 }; + + for (size_t sIdx{ 0u }; sIdx < this->producerRtpStreams.size(); ++sIdx) + { + spatialLayer = static_cast(sIdx); + + // If this is higher than current spatial layer and we moved to current + // spatial layer due to BWE limitations, check how much it has elapsed + // since then. + if (nowMs - this->lastBweDowngradeAtMs < BweDowngradeConservativeMs) + { + if (this->provisionalTargetLayers.spatial > -1 && spatialLayer > this->currentSpatialLayer) + { + MS_DEBUG_DEV( + "avoid upgrading to spatial layer %" PRIi16 " due to recent BWE downgrade", spatialLayer); + + goto done; + } + } + + // Ignore spatial layers lower than the one we already have. + if (spatialLayer < this->provisionalTargetLayers.spatial) + { + continue; + } + // If this is higher than preferred spatial layer, abort. + else if (spatialLayer > this->preferredLayers.spatial) + { + MS_DEBUG_DEV( + "avoid upgrading to spatial layer %" PRIi16 + " since it's higher than preferred spatial layer %" PRIi16, + spatialLayer, + this->preferredLayers.spatial); + + goto done; + } + + // This can be null. + auto* producerRtpStream = this->producerRtpStreams.at(spatialLayer); + + // Producer stream does not exist. Ignore. + if (!producerRtpStream) + { + continue; + } + + // Ignore spatial layers (streams) with score 0. + if (producerRtpStream->GetScore() == 0) + { + continue; + } + + // If the stream has not been active time enough and we have an active one + // already, move to the next spatial layer. + if ( + spatialLayer != this->provisionalTargetLayers.spatial && + this->provisionalTargetLayers.spatial != -1 && + producerRtpStream->GetActiveMs() < StreamMinActiveMs) + { + const auto* provisionalProducerRtpStream = + this->producerRtpStreams.at(this->provisionalTargetLayers.spatial); + + // The stream for the current provisional spatial layer has been active + // for enough time, move to the next spatial layer. + if (provisionalProducerRtpStream->GetActiveMs() >= StreamMinActiveMs) + { + continue; + } + } + + // We may not yet switch to this spatial layer. + if (!CanSwitchToSpatialLayer(spatialLayer)) + { + continue; + } + + temporalLayer = 0; + + // Check bitrate of every temporal layer. + for (; temporalLayer < producerRtpStream->GetTemporalLayers(); ++temporalLayer) + { + // Ignore temporal layers lower than the one we already have (taking + // into account the spatial layer too). + if ( + spatialLayer == this->provisionalTargetLayers.spatial && + temporalLayer <= this->provisionalTargetLayers.temporal) + { + continue; + } + + requiredBitrate = producerRtpStream->GetLayerBitrate(nowMs, 0, temporalLayer); + + // This is simulcast so we must subtract the bitrate of the current + // temporal spatial layer if this is the temporal layer 0 of a higher + // spatial layer. + if ( + requiredBitrate && temporalLayer == 0 && this->provisionalTargetLayers.spatial > -1 && + spatialLayer > this->provisionalTargetLayers.spatial) + { + auto* provisionalProducerRtpStream = + this->producerRtpStreams.at(this->provisionalTargetLayers.spatial); + auto provisionalRequiredBitrate = provisionalProducerRtpStream->GetBitrate( + nowMs, 0, this->provisionalTargetLayers.temporal); + + if (requiredBitrate > provisionalRequiredBitrate) + { + requiredBitrate -= provisionalRequiredBitrate; + } + else + { + requiredBitrate = 1u; // Don't set 0 since it would be ignored. + } + } + + MS_DEBUG_DEV( + "testing layers %" PRIi16 ":%" PRIi16 " [virtual bitrate:%" PRIu32 + ", required bitrate:%" PRIu32 "]", + spatialLayer, + temporalLayer, + virtualBitrate, + requiredBitrate); + + // If active layer, end iterations here. Otherwise move to next spatial + // layer. + if (requiredBitrate) + { + goto done; + } + else + { + break; + } + } + + // If this is the preferred spatial layer or higher, take it and exit. + if (spatialLayer >= this->preferredLayers.spatial) + { + break; + } + } + + done: + + // No higher active layers found. + if (!requiredBitrate) + { + return 0u; + } + + // No luck. + if (requiredBitrate > virtualBitrate) + { + return 0u; + } + + // Set provisional layers. + this->provisionalTargetLayers.spatial = spatialLayer; + this->provisionalTargetLayers.temporal = temporalLayer; + + MS_DEBUG_DEV( + "setting provisional layers to %" PRIi16 ":%" PRIi16 " [virtual bitrate:%" PRIu32 + ", required bitrate:%" PRIu32 "]", + this->provisionalTargetLayers.spatial, + this->provisionalTargetLayers.temporal, + virtualBitrate, + requiredBitrate); + + if (requiredBitrate <= bitrate) + { + return requiredBitrate; + } + else if (requiredBitrate <= virtualBitrate) + { + return bitrate; + } + else + { + return requiredBitrate; // NOTE: This cannot happen. + } + } + + void SimulcastProducerStreamManager::ApplyLayers(uint64_t rtpStreamActiveMs) + { + MS_TRACE(); + + auto provisionalTargetLayers = this->provisionalTargetLayers; + + // Reset provisional target layers. + this->provisionalTargetLayers.Reset(); + + if (provisionalTargetLayers != this->targetLayers) + { + UpdateTargetLayers(provisionalTargetLayers.spatial, provisionalTargetLayers.temporal); + + // If this looks like a spatial layer downgrade due to BWE limitations, set + // member. + if ( + rtpStreamActiveMs > BweDowngradeMinActiveMs && + this->targetLayers.spatial < this->currentSpatialLayer && + this->currentSpatialLayer <= this->preferredLayers.spatial) + { + MS_DEBUG_DEV( + "possible target spatial layer downgrade (from %" PRIi16 " to %" PRIi16 + ") due to BWE limitation", + this->currentSpatialLayer, + this->targetLayers.spatial); + + this->lastBweDowngradeAtMs = DepLibUV::GetTimeMs(); + } + } + } + + uint32_t SimulcastProducerStreamManager::GetDesiredBitrate(uint64_t nowMs) const + { + MS_TRACE(); + + uint32_t desiredBitrate{ 0u }; + + // Let's iterate all streams of the Producer (from highest to lowest) and + // obtain their bitrate. Choose the highest one. + // NOTE: When the Producer enables a higher stream, initially the bitrate of + // it could be less than the bitrate of a lower stream. That's why we + // iterate all streams here anyway. + for (auto sIdx{ static_cast(this->producerRtpStreams.size() - 1) }; sIdx >= 0; --sIdx) + { + auto* producerRtpStream = this->producerRtpStreams.at(sIdx); + + if (!producerRtpStream) + { + continue; + } + + auto streamBitrate = producerRtpStream->GetBitrate(nowMs); + + desiredBitrate = std::max(streamBitrate, desiredBitrate); + } + + return desiredBitrate; + } + + ProducerStreamManager::RtpPacketProcessResult SimulcastProducerStreamManager::ProcessRtpPacket( + RTC::RTP::Packet* packet, bool lastSentPacketHasMarker, uint32_t clockRate, uint32_t maxPacketTs) + { + MS_TRACE(); + + RtpPacketProcessResult result; + + auto spatialLayer = this->mapMappedSsrcSpatialLayer.at(packet->GetSsrc()); + + // Check target layers validity. + if (this->targetLayers.temporal == -1) + { + if (spatialLayer == this->currentSpatialLayer) + { + result.type = RtpPacketProcessResult::Type::DROP; + } + else + { + result.type = RtpPacketProcessResult::Type::SILENT_DROP; + } + + return result; + } + + bool shouldSwitchCurrentSpatialLayer{ false }; + + // Check whether this is the packet we are waiting for in order to update + // the current spatial layer. + if ( + this->currentSpatialLayer != this->targetLayers.spatial && + spatialLayer == this->targetLayers.spatial) + { + // Ignore if not a key frame. + if (this->keyFrameSupported && !packet->IsKeyFrame()) + { + result.type = RtpPacketProcessResult::Type::BUFFER; + + return result; + } + + shouldSwitchCurrentSpatialLayer = true; + + // Need to resync the stream. + this->syncRequired = true; + this->spatialLayerToSync = spatialLayer; + } + // If the packet belongs to different spatial layer than the one being sent, + // drop it. + else if (spatialLayer != this->currentSpatialLayer) + { + result.type = RtpPacketProcessResult::Type::SILENT_DROP; + + return result; + } + + // If we need to sync and this is not a key frame, ignore the packet. + if (this->syncRequired && this->keyFrameSupported && !packet->IsKeyFrame()) + { + result.type = RtpPacketProcessResult::Type::BUFFER; + + return result; + } + + // Packets with only padding are not forwarded. + if (packet->GetPayloadLength() == 0) + { + if (spatialLayer == this->currentSpatialLayer) + { + result.type = RtpPacketProcessResult::Type::DROP; + } + else + { + result.type = RtpPacketProcessResult::Type::SILENT_DROP; + } + + return result; + } + + // Whether this is the first packet after re-sync. + const bool isSyncPacket = this->syncRequired; + + // Sync sequence number and timestamp if required. + if (isSyncPacket && (this->spatialLayerToSync == -1 || spatialLayer == this->spatialLayerToSync)) + { + if (packet->IsKeyFrame()) + { + MS_DEBUG_TAG( + rtp, + "sync key frame received [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "]", + packet->GetSsrc(), + packet->GetSequenceNumber(), + packet->GetTimestamp()); + + result.sendBufferedPackets = true; + } + + uint32_t tsOffset{ 0u }; + + // Sync our RTP stream's RTP timestamp. + if (spatialLayer == this->tsReferenceSpatialLayer) + { + tsOffset = 0u; + } + // If this is not the RTP stream we use as TS reference, do NTP based RTP + // TS synchronization. + else + { + auto* producerTsReferenceRtpStream = GetProducerTsReferenceRtpStream(); + auto* producerTargetRtpStream = GetProducerTargetRtpStream(); + + // NOTE: If we are here is because we have Sender Reports for both the + // TS reference stream and the target one. + MS_ASSERT( + producerTsReferenceRtpStream->GetSenderReportNtpMs(), + "no Sender Report for TS reference RTP stream"); + MS_ASSERT( + producerTargetRtpStream->GetSenderReportNtpMs(), "no Sender Report for current RTP stream"); + + // Calculate NTP and TS stuff. + auto ntpMs1 = producerTsReferenceRtpStream->GetSenderReportNtpMs(); + auto ts1 = producerTsReferenceRtpStream->GetSenderReportTs(); + auto ntpMs2 = producerTargetRtpStream->GetSenderReportNtpMs(); + auto ts2 = producerTargetRtpStream->GetSenderReportTs(); + int64_t diffMs; + + if (ntpMs2 >= ntpMs1) + { + diffMs = ntpMs2 - ntpMs1; + } + else + { + diffMs = -1 * (ntpMs1 - ntpMs2); + } + + const int64_t diffTs = diffMs * clockRate / 1000; + const uint32_t newTs2 = ts2 - diffTs; + + // Apply offset. This is the difference that later must be removed from + // the sending RTP packet. + tsOffset = newTs2 - ts1; + } + + // When switching to a new stream it may happen that the timestamp of this + // key frame is lower than the highest timestamp sent to the remote endpoint. + // If so, apply an extra offset to "fix" it for the whole life of this + // selected Producer stream. + if (shouldSwitchCurrentSpatialLayer && (packet->GetTimestamp() - tsOffset <= maxPacketTs)) + { + // Max delay in ms we allow for the stream when switching. + // https://en.wikipedia.org/wiki/Audio-to-video_synchronization#Recommendations + static const uint32_t MaxExtraOffsetMs{ 75u }; + + // Outgoing packet matches the highest timestamp seen in the previous + // stream. Apply an expected offset for a new frame in a 30fps stream. + static const uint8_t MsOffset{ 33u }; // (1 / 30 * 1000). + + const int64_t maxTsExtraOffset = MaxExtraOffsetMs * clockRate / 1000; + uint32_t tsExtraOffset = + maxPacketTs - packet->GetTimestamp() + tsOffset + (MsOffset * clockRate / 1000); + + // NOTE: Don't ask for a key frame if already done. + if (this->keyFrameForTsOffsetRequested) + { + // Give up and use the theoretical offset. + if (tsExtraOffset > maxTsExtraOffset) + { + MS_WARN_TAG( + simulcast, + "giving up on proper stream switching after got a requested keyframe for " + "which still too high RTP timestamp extra offset is needed (%" PRIu32 ")", + tsExtraOffset); + + tsExtraOffset = 1u; + } + } + else if (tsExtraOffset > maxTsExtraOffset) + { + MS_WARN_TAG( + simulcast, + "cannot switch stream due to too high RTP timestamp extra offset needed " + "(%" PRIu32 "), requesting keyframe", + tsExtraOffset); + + RequestKeyFrameForTargetSpatialLayer(); + + this->keyFrameForTsOffsetRequested = true; + + // Reset flags since we are discarding this key frame. + this->syncRequired = false; + this->spatialLayerToSync = -1; + + result.type = RtpPacketProcessResult::Type::SILENT_DROP; + + return result; + } + + if (tsExtraOffset > 0u) + { + MS_DEBUG_TAG( + simulcast, + "RTP timestamp extra offset generated for stream switching: %" PRIu32, + tsExtraOffset); + + // Increase the timestamp offset for the whole life of this Producer + // stream (until switched to a different one). + tsOffset -= tsExtraOffset; + } + } + + this->tsOffset = tsOffset; + + // Sync our RTP stream's sequence number. + // If previous frame has not been sent completely when we switch layer, + // we can tell libwebrtc that previous frame is incomplete by skipping + // one RTP sequence number. + // https://github.com/versatica/mediasoup/issues/408 + result.isSyncPacket = true; + result.syncSeqValue = packet->GetSequenceNumber() - (lastSentPacketHasMarker ? 1 : 2); + result.shouldSyncEncodingContext = true; + + this->syncRequired = false; + this->spatialLayerToSync = -1; + this->keyFrameForTsOffsetRequested = false; + } + + // Old-packet filtering after spatial layer switch. + if (!shouldSwitchCurrentSpatialLayer && this->checkingForOldPacketsInSpatialLayer) + { + if (SeqManager::IsSeqLowerThan( + packet->GetSequenceNumber(), this->snReferenceSpatialLayer)) + { + result.type = RtpPacketProcessResult::Type::DROP; + + return result; + } + else if (SeqManager::IsSeqHigherThan( + packet->GetSequenceNumber(), this->snReferenceSpatialLayer + MaxSequenceNumberGap)) + { + this->checkingForOldPacketsInSpatialLayer = false; + } + } + + // Preserve the original marker bit. ProcessPayload may update it. + bool marker{ packet->HasMarker() }; + + if (shouldSwitchCurrentSpatialLayer) + { + // Update current spatial layer. + this->currentSpatialLayer = this->targetLayers.spatial; + + this->snReferenceSpatialLayer = packet->GetSequenceNumber(); + this->checkingForOldPacketsInSpatialLayer = true; + + // Update target and current temporal layer. + this->encodingContext->SetTargetTemporalLayer(this->targetLayers.temporal); + this->encodingContext->SetCurrentTemporalLayer(packet->GetTemporalLayer()); + + // Rewrite payload if needed. + packet->ProcessPayload(this->encodingContext.get(), marker); + + result.spatialLayerSwitched = true; + } + else + { + auto previousTemporalLayer = this->encodingContext->GetCurrentTemporalLayer(); + + // Rewrite payload if needed. Drop packet if necessary. + if (!packet->ProcessPayload(this->encodingContext.get(), marker)) + { + result.type = RtpPacketProcessResult::Type::DROP; + + return result; + } + + if (previousTemporalLayer != this->encodingContext->GetCurrentTemporalLayer()) + { + result.temporalLayerChanged = true; + } + } + + // Set forward action. + result.type = RtpPacketProcessResult::Type::FORWARD; + result.tsOffset = this->tsOffset; + result.marker = marker; + + return result; + } + + void SimulcastProducerStreamManager::RequestKeyFrame() + { + MS_TRACE(); + + auto* producerTargetRtpStream = GetProducerTargetRtpStream(); + auto* producerCurrentRtpStream = GetProducerCurrentRtpStream(); + + if (producerTargetRtpStream) + { + auto mappedSsrc = this->consumableRtpEncodings[this->targetLayers.spatial].ssrc; + + this->listener->OnProducerStreamManagerKeyFrameRequested(mappedSsrc); + } + + if (producerCurrentRtpStream && producerCurrentRtpStream != producerTargetRtpStream) + { + auto mappedSsrc = this->consumableRtpEncodings[this->currentSpatialLayer].ssrc; + + this->listener->OnProducerStreamManagerKeyFrameRequested(mappedSsrc); + } + } + + void SimulcastProducerStreamManager::RequestKeyFrameForTargetSpatialLayer() + { + MS_TRACE(); + + auto* producerTargetRtpStream = GetProducerTargetRtpStream(); + + if (!producerTargetRtpStream) + { + return; + } + + auto mappedSsrc = this->consumableRtpEncodings[this->targetLayers.spatial].ssrc; + + this->listener->OnProducerStreamManagerKeyFrameRequested(mappedSsrc); + } + + void SimulcastProducerStreamManager::RequestKeyFrameForCurrentSpatialLayer() + { + MS_TRACE(); + + auto* producerCurrentRtpStream = GetProducerCurrentRtpStream(); + + if (!producerCurrentRtpStream) + { + return; + } + + auto mappedSsrc = this->consumableRtpEncodings[this->currentSpatialLayer].ssrc; + + this->listener->OnProducerStreamManagerKeyFrameRequested(mappedSsrc); + } + + void SimulcastProducerStreamManager::UpdateTargetLayers( + int16_t newTargetSpatialLayer, int16_t newTargetTemporalLayer) + { + MS_TRACE(); + + // If we don't have yet a RTP timestamp reference, set it now. + if ( + newTargetSpatialLayer != -1 && (this->tsReferenceSpatialLayer == -1 || + !GetProducerTsReferenceRtpStream()->GetSenderReportNtpMs())) + { + MS_DEBUG_TAG( + simulcast, "using spatial layer %" PRIi16 " as RTP timestamp reference", newTargetSpatialLayer); + + this->tsReferenceSpatialLayer = newTargetSpatialLayer; + } + + // If the new target spatial layer doesn't match the current one, clear the + // target layer retransmission buffer. + if (newTargetSpatialLayer != this->targetLayers.spatial) + { + this->listener->OnProducerStreamManagerClearRetransmissionBuffer(); + } + + if (newTargetSpatialLayer == -1) + { + // Unset current and target layers. + this->targetLayers.spatial = -1; + this->targetLayers.temporal = -1; + this->currentSpatialLayer = -1; + + this->encodingContext->SetTargetTemporalLayer(-1); + this->encodingContext->SetCurrentTemporalLayer(-1); + + MS_DEBUG_TAG(simulcast, "target layers changed [spatial:-1, temporal:-1]"); + + this->listener->OnProducerStreamManagerLayersChanged(); + + return; + } + + this->targetLayers.spatial = newTargetSpatialLayer; + this->targetLayers.temporal = newTargetTemporalLayer; + + // If the new target spatial layer matches the current one, apply the new + // target temporal layer now. + if (this->targetLayers.spatial == this->currentSpatialLayer) + { + this->encodingContext->SetTargetTemporalLayer(this->targetLayers.temporal); + } + + MS_DEBUG_TAG( + simulcast, + "target layers changed [spatial:%" PRIi16 ", temporal:%" PRIi16 "]", + this->targetLayers.spatial, + this->targetLayers.temporal); + + // If the target spatial layer is different than the current one, request + // a key frame. + if (this->targetLayers.spatial != this->currentSpatialLayer) + { + RequestKeyFrameForTargetSpatialLayer(); + } + } + + bool SimulcastProducerStreamManager::RecalculateTargetLayers(VideoLayers& newTargetLayers) const + { + MS_TRACE(); + + // Start with no layers. + newTargetLayers.Reset(); + + auto nowMs = DepLibUV::GetTimeMs(); + + for (size_t sIdx{ 0u }; sIdx < this->producerRtpStreams.size(); ++sIdx) + { + auto spatialLayer = static_cast(sIdx); + auto* producerRtpStream = this->producerRtpStreams.at(sIdx); + auto producerScore = producerRtpStream ? producerRtpStream->GetScore() : 0u; + + // If this is higher than current spatial layer and we moved to current + // spatial layer due to BWE limitations, check how much it has elapsed + // since then. + if (nowMs - this->lastBweDowngradeAtMs < BweDowngradeConservativeMs) + { + if (newTargetLayers.spatial > -1 && spatialLayer > this->currentSpatialLayer) + { + continue; + } + } + + // Ignore spatial layers for non existing Producer streams or for those + // with score 0. + if (producerScore == 0u) + { + continue; + } + + // If the stream has not been active time enough and we have an active one + // already, move to the next spatial layer. + // NOTE: Require bitrate externally managed for this. + if (this->externallyManagedBitrate && newTargetLayers.spatial != -1 && producerRtpStream->GetActiveMs() < StreamMinActiveMs) + { + continue; + } + + // We may not yet switch to this spatial layer. + if (!CanSwitchToSpatialLayer(spatialLayer)) + { + continue; + } + + newTargetLayers.spatial = spatialLayer; + + // If this is the preferred or higher spatial layer take it and exit. + if (spatialLayer >= this->preferredLayers.spatial) + { + break; + } + } + + if (newTargetLayers.spatial != -1) + { + if (newTargetLayers.spatial == this->preferredLayers.spatial) + { + newTargetLayers.temporal = this->preferredLayers.temporal; + } + else if (newTargetLayers.spatial < this->preferredLayers.spatial) + { + newTargetLayers.temporal = + static_cast(this->encodingContext->GetTemporalLayers() - 1); + } + else + { + newTargetLayers.temporal = 0; + } + } + + // Return true if any target layer changed. + return (newTargetLayers != this->targetLayers); + } + + void SimulcastProducerStreamManager::OnTransportConnected() + { + MS_TRACE(); + + this->syncRequired = true; + this->spatialLayerToSync = -1; + this->keyFrameForTsOffsetRequested = false; + + if (this->listener->IsActive()) + { + MayChangeLayers(/*force*/ false); + } + } + + void SimulcastProducerStreamManager::OnTransportDisconnected() + { + MS_TRACE(); + + this->lastBweDowngradeAtMs = 0u; + + UpdateTargetLayers(-1, -1); + } + + void SimulcastProducerStreamManager::OnPaused() + { + MS_TRACE(); + + this->lastBweDowngradeAtMs = 0u; + + UpdateTargetLayers(-1, -1); + } + + void SimulcastProducerStreamManager::OnResumed() + { + MS_TRACE(); + + this->syncRequired = true; + this->spatialLayerToSync = -1; + this->keyFrameForTsOffsetRequested = false; + this->checkingForOldPacketsInSpatialLayer = false; + + if (this->listener->IsActive()) + { + MayChangeLayers(/*force*/ false); + } + } + + bool SimulcastProducerStreamManager::CanSwitchToSpatialLayer(int16_t spatialLayer) const + { + MS_TRACE(); + + MS_ASSERT( + this->producerRtpStreams.at(spatialLayer), + "no Producer RtpStream for the given spatialLayer:%" PRIi16, + spatialLayer); + + return ( + this->tsReferenceSpatialLayer == -1 || spatialLayer == this->tsReferenceSpatialLayer || + this->producerRtpStreams.at(spatialLayer)->GetSenderReportNtpMs()); + } + + RTC::RTP::RtpStreamRecv* SimulcastProducerStreamManager::GetProducerTsReferenceRtpStream() const + { + MS_TRACE(); + + if (this->tsReferenceSpatialLayer == -1) + { + return nullptr; + } + + // This may return nullptr. + return this->producerRtpStreams.at(this->tsReferenceSpatialLayer); + } +} // namespace RTC diff --git a/worker/src/RTC/SvcConsumer.cpp b/worker/src/RTC/SvcConsumer.cpp index 8afa631fdf..f9b9646540 100644 --- a/worker/src/RTC/SvcConsumer.cpp +++ b/worker/src/RTC/SvcConsumer.cpp @@ -26,9 +26,10 @@ namespace RTC RTC::Shared* shared, const std::string& id, const std::string& producerId, - RTC::Consumer::Listener* listener, + RTC::OldConsumer::Listener* listener, const FBS::Transport::ConsumeRequest* data) - : RTC::Consumer::Consumer(shared, id, producerId, listener, data, RTC::RtpParameters::Type::SVC) + : RTC::OldConsumer::OldConsumer( + shared, id, producerId, listener, data, RTC::RtpParameters::Type::SVC) { MS_TRACE(); @@ -130,7 +131,7 @@ namespace RTC MS_TRACE(); // Call the parent method. - auto base = RTC::Consumer::FillBuffer(builder); + auto base = RTC::OldConsumer::FillBuffer(builder); // Add rtpStream. std::vector> rtpStreams; rtpStreams.emplace_back(this->rtpStream->FillBuffer(builder)); @@ -276,7 +277,7 @@ namespace RTC default: { // Pass it to the parent class. - RTC::Consumer::HandleRequest(request); + RTC::OldConsumer::HandleRequest(request); } } } @@ -311,7 +312,7 @@ namespace RTC // Emit score event. EmitScore(); - if (RTC::Consumer::IsActive()) + if (RTC::OldConsumer::IsActive()) { // Just check target layers if the stream has died or reborned. if (!this->externallyManagedBitrate || (score == 0u || previousScore == 0u)) diff --git a/worker/src/RTC/SvcProducerStreamManager.cpp b/worker/src/RTC/SvcProducerStreamManager.cpp new file mode 100644 index 0000000000..601f7b0f04 --- /dev/null +++ b/worker/src/RTC/SvcProducerStreamManager.cpp @@ -0,0 +1,635 @@ +#define MS_CLASS "RTC::SvcProducerStreamManager" +// #define MS_LOG_DEV_LEVEL 3 + +#include "RTC/SvcProducerStreamManager.hpp" +#include "Logger.hpp" + +namespace RTC +{ + /* Static. */ + + static constexpr uint64_t BweDowngradeConservativeMs{ 10000u }; + static constexpr uint64_t BweDowngradeMinActiveMs{ 8000u }; + + /* Instance methods. */ + + SvcProducerStreamManager::SvcProducerStreamManager( + const std::vector& consumableRtpEncodings, + const VideoLayers& preferredLayers, + std::unique_ptr encodingContext, + RTC::Media::Kind kind, + bool keyFrameSupported, + Listener* listener) + : ProducerStreamManager( + consumableRtpEncodings, preferredLayers, std::move(encodingContext), kind, keyFrameSupported, listener) + { + MS_TRACE(); + } + + RTC::RTP::RtpStreamRecv* SvcProducerStreamManager::GetProducerCurrentRtpStream() const + { + MS_TRACE(); + + // SVC has a single producer stream. Return it if we have a current layer. + if (this->encodingContext->GetCurrentSpatialLayer() == -1) + { + return nullptr; + } + + return this->producerRtpStream; + } + + RTC::RTP::RtpStreamRecv* SvcProducerStreamManager::GetProducerTargetRtpStream() const + { + MS_TRACE(); + + // SVC has a single producer stream. Return it if we have a target layer. + if (this->encodingContext->GetTargetSpatialLayer() == -1) + { + return nullptr; + } + + return this->producerRtpStream; + } + + bool SvcProducerStreamManager::IsProducerStreamActive() const + { + MS_TRACE(); + + // clang-format off + return ( + this->producerRtpStream && + // If there is no RTP inactivity check do not consider the stream + // inactive despite it has score 0. + (this->producerRtpStream->GetScore() > 0u || !this->producerRtpStream->HasRtpInactivityCheckEnabled()) + ); + // clang-format on + } + + void SvcProducerStreamManager::ProducerRtpStream( + RTC::RTP::RtpStreamRecv* rtpStream, uint32_t /*mappedSsrc*/) + { + MS_TRACE(); + + this->producerRtpStream = rtpStream; + } + + void SvcProducerStreamManager::ProducerNewRtpStream( + RTC::RTP::RtpStreamRecv* rtpStream, uint32_t /*mappedSsrc*/) + { + MS_TRACE(); + + this->producerRtpStream = rtpStream; + + // Emit the score event. + this->listener->OnProducerStreamManagerScore(); + + if (IsActive()) + { + MayChangeLayers(/*force*/ false); + } + } + + void SvcProducerStreamManager::ProducerRtpStreamScore( + RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint8_t score, uint8_t previousScore) + { + MS_TRACE(); + + if (this->listener->IsActive()) + { + // Just check target layers if the stream has died or reborned or if + // bitrate is not externally managed. + if (!this->externallyManagedBitrate || (score == 0u || previousScore == 0u)) + { + MayChangeLayers(/*force*/ false); + } + } + } + + void SvcProducerStreamManager::ProducerRtcpSenderReport( + RTC::RTP::RtpStreamRecv* /*rtpStream*/, bool /*first*/) + { + MS_TRACE(); + + // Do nothing. + } + + uint32_t SvcProducerStreamManager::IncreaseLayer( + uint32_t bitrate, bool considerLoss, float lossPercentage, uint64_t nowMs) + { + MS_TRACE(); + + if (!this->producerRtpStream || this->producerRtpStream->GetScore() == 0u) + { + return 0u; + } + + // If already in the preferred layers, do nothing. + if (this->provisionalTargetLayers == this->preferredLayers) + { + return 0u; + } + + uint32_t virtualBitrate; + + if (considerLoss) + { + // Calculate virtual available bitrate based on given bitrate and our + // packet lost. + if (lossPercentage < 2) + { + virtualBitrate = 1.08 * bitrate; + } + else if (lossPercentage > 10) + { + virtualBitrate = (1 - 0.5 * (lossPercentage / 100)) * bitrate; + } + else + { + virtualBitrate = bitrate; + } + } + else + { + virtualBitrate = bitrate; + } + + uint32_t requiredBitrate{ 0u }; + int16_t spatialLayer{ 0 }; + int16_t temporalLayer{ 0 }; + + for (; spatialLayer < this->producerRtpStream->GetSpatialLayers(); ++spatialLayer) + { + // If this is higher than current spatial layer and we moved to current + // spatial layer due to BWE limitations, check how much it has elapsed + // since then. + if (nowMs - this->lastBweDowngradeAtMs < BweDowngradeConservativeMs) + { + if (this->provisionalTargetLayers.spatial > -1 && spatialLayer > this->encodingContext->GetCurrentSpatialLayer()) + { + MS_DEBUG_DEV( + "avoid upgrading to spatial layer %" PRIi16 " due to recent BWE downgrade", spatialLayer); + + goto done; + } + } + + // Ignore spatial layers lower than the one we already have. + if (spatialLayer < this->provisionalTargetLayers.spatial) + { + continue; + } + + temporalLayer = 0; + + // Check bitrate of every temporal layer. + for (; temporalLayer < this->producerRtpStream->GetTemporalLayers(); ++temporalLayer) + { + // Ignore temporal layers lower than the one we already have (taking + // into account the spatial layer too). + if ( + spatialLayer == this->provisionalTargetLayers.spatial && + temporalLayer <= this->provisionalTargetLayers.temporal) + { + continue; + } + + requiredBitrate = + this->producerRtpStream->GetLayerBitrate(nowMs, spatialLayer, temporalLayer); + + // When using K-SVC we must subtract the bitrate of the current used + // layer if the new layer is the temporal layer 0 of a higher spatial + // layer. + if ( + this->encodingContext->IsKSvc() && requiredBitrate && temporalLayer == 0 && + this->provisionalTargetLayers.spatial > -1 && + spatialLayer > this->provisionalTargetLayers.spatial) + { + auto provisionalRequiredBitrate = this->producerRtpStream->GetSpatialLayerBitrate( + nowMs, this->provisionalTargetLayers.spatial); + + if (requiredBitrate > provisionalRequiredBitrate) + { + requiredBitrate -= provisionalRequiredBitrate; + } + else + { + requiredBitrate = 1u; // Don't set 0 since it would be ignored. + } + } + + MS_DEBUG_DEV( + "testing layers %" PRIi16 ":%" PRIi16 " [virtual bitrate:%" PRIu32 + ", required bitrate:%" PRIu32 "]", + spatialLayer, + temporalLayer, + virtualBitrate, + requiredBitrate); + + // If active layer, end iterations here. Otherwise move to next spatial + // layer. + if (requiredBitrate) + { + goto done; + } + else + { + break; + } + } + + // If this is the preferred or higher spatial layer, take it and exit. + if (spatialLayer >= this->preferredLayers.spatial) + { + break; + } + } + + done: + + // No higher active layers found. + if (!requiredBitrate) + { + return 0u; + } + + // No luck. + if (requiredBitrate > virtualBitrate) + { + return 0u; + } + + // Set provisional layers. + this->provisionalTargetLayers.spatial = spatialLayer; + this->provisionalTargetLayers.temporal = temporalLayer; + + MS_DEBUG_DEV( + "upgrading to layers %" PRIi16 ":%" PRIi16 " [virtual bitrate:%" PRIu32 + ", required bitrate:%" PRIu32 "]", + this->provisionalTargetLayers.spatial, + this->provisionalTargetLayers.temporal, + virtualBitrate, + requiredBitrate); + + if (requiredBitrate <= bitrate) + { + return requiredBitrate; + } + else if (requiredBitrate <= virtualBitrate) + { + return bitrate; + } + else + { + return requiredBitrate; // NOTE: This cannot happen. + } + } + + void SvcProducerStreamManager::ApplyLayers(uint64_t rtpStreamActiveMs) + { + MS_TRACE(); + + auto provisionalTargetLayers = this->provisionalTargetLayers; + + // Reset provisional target layers. + this->provisionalTargetLayers.Reset(); + + if (provisionalTargetLayers != this->encodingContext->GetTargetLayers()) + { + UpdateTargetLayers(provisionalTargetLayers.spatial, provisionalTargetLayers.temporal); + + // If this looks like a spatial layer downgrade due to BWE limitations, + // set member. + if ( + rtpStreamActiveMs > BweDowngradeMinActiveMs && + this->encodingContext->GetTargetSpatialLayer() < + this->encodingContext->GetCurrentSpatialLayer() && + this->encodingContext->GetCurrentSpatialLayer() <= this->preferredLayers.spatial) + { + MS_DEBUG_DEV( + "possible target spatial layer downgrade (from %" PRIi16 " to %" PRIi16 + ") due to BWE limitation", + this->encodingContext->GetCurrentSpatialLayer(), + this->encodingContext->GetTargetSpatialLayer()); + + this->lastBweDowngradeAtMs = DepLibUV::GetTimeMs(); + } + } + } + + uint32_t SvcProducerStreamManager::GetDesiredBitrate(uint64_t nowMs) const + { + MS_TRACE(); + + if (!this->producerRtpStream) + { + return 0u; + } + + uint32_t desiredBitrate{ 0u }; + + // When using K-SVC each spatial layer is independent of the others. + if (this->encodingContext->IsKSvc()) + { + // Let's iterate all spatial layers of the Producer (from highest to lowest) + // and obtain their bitrate. Choose the highest one. + // NOTE: When the Producer enables a higher spatial layer, initially the + // bitrate of it could be less than the bitrate of a lower one. That's why + // we iterate all spatial layers here anyway. + for (auto spatialLayer{ this->producerRtpStream->GetSpatialLayers() - 1 }; spatialLayer >= 0; + --spatialLayer) + { + auto spatialLayerBitrate = + this->producerRtpStream->GetSpatialLayerBitrate(nowMs, spatialLayer); + + desiredBitrate = std::max(spatialLayerBitrate, desiredBitrate); + } + } + else + { + desiredBitrate = this->producerRtpStream->GetBitrate(nowMs); + } + + return desiredBitrate; + } + + ProducerStreamManager::RtpPacketProcessResult SvcProducerStreamManager::ProcessRtpPacket( + RTC::RTP::Packet* packet, + bool /*lastSentPacketHasMarker*/, + uint32_t /*clockRate*/, + uint32_t /*maxPacketTs*/) + { + MS_TRACE(); + + RtpPacketProcessResult result; + + if (this->encodingContext->GetTargetSpatialLayer() == -1 || this->encodingContext->GetTargetTemporalLayer() == -1) + { + result.type = RtpPacketProcessResult::Type::DROP; + + return result; + } + + // If we need to sync and this is not a key frame, ignore the packet. + if (this->syncRequired && !packet->IsKeyFrame()) + { + // NOTE: No need to drop the packet in the RTP sequence manager since here + // we are blocking all packets but the key frame that would trigger sync + // below. + + // Store the packet for the scenario in which this packet is part of the + // key frame and it arrived before the first packet of the key frame. + result.type = RtpPacketProcessResult::Type::BUFFER; + + return result; + } + + // Packets with only padding are not forwarded. + if (packet->GetPayloadLength() == 0) + { + result.type = RtpPacketProcessResult::Type::DROP; + + return result; + } + + // Whether this is the first packet after re-sync. + const bool isSyncPacket = this->syncRequired; + + // Whether packets stored in the target layer retransmission buffer must be + // sent once this packet is sent. + bool sendBufferedPackets{ false }; + + // Sync sequence number and timestamp if required. + if (isSyncPacket) + { + if (packet->IsKeyFrame()) + { + MS_DEBUG_TAG( + rtp, + "sync key frame received [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "]", + packet->GetSsrc(), + packet->GetSequenceNumber(), + packet->GetTimestamp()); + + sendBufferedPackets = true; + } + + result.isSyncPacket = true; + result.syncSeqValue = packet->GetSequenceNumber() - 1; + result.shouldSyncEncodingContext = true; + + this->syncRequired = false; + } + + auto previousLayers = this->encodingContext->GetCurrentLayers(); + + bool marker{ false }; + + if (!packet->ProcessPayload(this->encodingContext.get(), marker)) + { + result.type = RtpPacketProcessResult::Type::DROP; + + return result; + } + + if (previousLayers != this->encodingContext->GetCurrentLayers()) + { + result.temporalLayerChanged = true; + } + + // Set forward action. + result.type = RtpPacketProcessResult::Type::FORWARD; + result.tsOffset = 0u; + result.marker = marker; + result.sendBufferedPackets = sendBufferedPackets; + + return result; + } + + void SvcProducerStreamManager::RequestKeyFrame() + { + MS_TRACE(); + + auto mappedSsrc = this->consumableRtpEncodings[0].ssrc; + + this->listener->OnProducerStreamManagerKeyFrameRequested(mappedSsrc); + } + + void SvcProducerStreamManager::RequestKeyFrameForTargetSpatialLayer() + { + MS_TRACE(); + + RequestKeyFrame(); + } + + void SvcProducerStreamManager::RequestKeyFrameForCurrentSpatialLayer() + { + MS_TRACE(); + + RequestKeyFrame(); + } + + void SvcProducerStreamManager::UpdateTargetLayers( + int16_t newTargetSpatialLayer, int16_t newTargetTemporalLayer) + { + MS_TRACE(); + + if (newTargetSpatialLayer == -1) + { + // Unset current and target layers. + this->encodingContext->SetTargetSpatialLayer(-1); + this->encodingContext->SetCurrentSpatialLayer(-1); + this->encodingContext->SetTargetTemporalLayer(-1); + this->encodingContext->SetCurrentTemporalLayer(-1); + + MS_DEBUG_TAG(svc, "target layers changed [spatial:-1, temporal:-1]"); + + this->listener->OnProducerStreamManagerLayersChanged(); + + return; + } + + this->encodingContext->SetTargetSpatialLayer(newTargetSpatialLayer); + this->encodingContext->SetTargetTemporalLayer(newTargetTemporalLayer); + + MS_DEBUG_TAG( + svc, + "target layers changed [spatial:%" PRIi16 ", temporal:%" PRIi16 "]", + newTargetSpatialLayer, + newTargetTemporalLayer); + + // Target spatial layer has changed. + if (newTargetSpatialLayer != this->encodingContext->GetCurrentSpatialLayer()) + { + // In K-SVC always ask for a keyframe when changing target spatial layer. + if (this->encodingContext->IsKSvc()) + { + MS_DEBUG_DEV("K-SVC: requesting keyframe to target spatial change"); + + RequestKeyFrame(); + } + // In full SVC just ask for a keyframe when upgrading target spatial layer. + // NOTE: This is because nobody implements RTCP LRR yet. + else if (newTargetSpatialLayer > this->encodingContext->GetCurrentSpatialLayer()) + { + MS_DEBUG_DEV("full SVC: requesting keyframe to target spatial upgrade"); + + RequestKeyFrame(); + } + } + } + + bool SvcProducerStreamManager::RecalculateTargetLayers(VideoLayers& newTargetLayers) const + { + MS_TRACE(); + + // Start with no layers. + newTargetLayers.Reset(); + + auto nowMs = DepLibUV::GetTimeMs(); + int16_t spatialLayer{ 0 }; + + if (!this->producerRtpStream) + { + goto done; + } + + if (this->producerRtpStream->GetScore() == 0u) + { + goto done; + } + + for (; spatialLayer < this->producerRtpStream->GetSpatialLayers(); ++spatialLayer) + { + // If this is higher than current spatial layer and we moved to current + // spatial layer due to BWE limitations, check how much it has elapsed + // since then. + if (nowMs - this->lastBweDowngradeAtMs < BweDowngradeConservativeMs) + { + if (newTargetLayers.spatial > -1 && spatialLayer > this->encodingContext->GetCurrentSpatialLayer()) + { + continue; + } + } + + if (!this->producerRtpStream->GetSpatialLayerBitrate(nowMs, spatialLayer)) + { + continue; + } + + newTargetLayers.spatial = spatialLayer; + + // If this is the preferred or higher spatial layer and has bitrate, + // take it and exit. + if (spatialLayer >= this->preferredLayers.spatial) + { + break; + } + } + + if (newTargetLayers.spatial != -1) + { + if (newTargetLayers.spatial == this->preferredLayers.spatial) + { + newTargetLayers.temporal = this->preferredLayers.temporal; + } + else if (newTargetLayers.spatial < this->preferredLayers.spatial) + { + newTargetLayers.temporal = + static_cast(this->encodingContext->GetTemporalLayers() - 1); + } + else + { + newTargetLayers.temporal = 0; + } + } + + done: + + // Return true if any target layer changed. + return newTargetLayers != this->encodingContext->GetTargetLayers(); + } + + void SvcProducerStreamManager::OnTransportConnected() + { + MS_TRACE(); + + this->syncRequired = true; + + if (this->listener->IsActive()) + { + MayChangeLayers(/*force*/ false); + } + } + + void SvcProducerStreamManager::OnTransportDisconnected() + { + MS_TRACE(); + + this->lastBweDowngradeAtMs = 0u; + + UpdateTargetLayers(-1, -1); + } + + void SvcProducerStreamManager::OnPaused() + { + MS_TRACE(); + + this->lastBweDowngradeAtMs = 0u; + + UpdateTargetLayers(-1, -1); + } + + void SvcProducerStreamManager::OnResumed() + { + MS_TRACE(); + + this->syncRequired = true; + + if (this->listener->IsActive()) + { + MayChangeLayers(/*force*/ false); + } + } + +} // namespace RTC diff --git a/worker/src/RTC/Transport.cpp b/worker/src/RTC/Transport.cpp index ec9dac0038..d7961dd1db 100644 --- a/worker/src/RTC/Transport.cpp +++ b/worker/src/RTC/Transport.cpp @@ -12,7 +12,8 @@ #include "FBS/transport.h" #include "RTC/BweType.hpp" #include "RTC/Consts.hpp" -#include "RTC/PipeConsumer.hpp" +#include "RTC/Consumer.hpp" +// #include "RTC/PipeConsumer.hpp" // TODO: PipeConsumer not yet ported to new Consumer. #include "RTC/RTCP/FeedbackPs.hpp" #include "RTC/RTCP/FeedbackPsAfb.hpp" #include "RTC/RTCP/FeedbackPsRemb.hpp" @@ -20,9 +21,6 @@ #include "RTC/RTCP/FeedbackRtpTransport.hpp" #include "RTC/RTCP/XrDelaySinceLastRr.hpp" #include "RTC/RtpDictionaries.hpp" -#include "RTC/SimpleConsumer.hpp" -#include "RTC/SimulcastConsumer.hpp" -#include "RTC/SvcConsumer.hpp" #ifdef MS_RTC_LOGGER_RTP #include "RTC/RtcLogger.hpp" #endif @@ -801,39 +799,15 @@ namespace RTC RTC::Consumer* consumer{ nullptr }; - switch (type) + if (type == RTC::RtpParameters::Type::PIPE) { - case RTC::RtpParameters::Type::SIMPLE: - { - // This may throw. - consumer = new RTC::SimpleConsumer(this->shared, consumerId, producerId, this, body); - - break; - } - - case RTC::RtpParameters::Type::SIMULCAST: - { - // This may throw. - consumer = new RTC::SimulcastConsumer(this->shared, consumerId, producerId, this, body); - - break; - } - - case RTC::RtpParameters::Type::SVC: - { - // This may throw. - consumer = new RTC::SvcConsumer(this->shared, consumerId, producerId, this, body); - - break; - } - - case RTC::RtpParameters::Type::PIPE: - { - // This may throw. - consumer = new RTC::PipeConsumer(this->shared, consumerId, producerId, this, body); - - break; - } + // TODO: PipeConsumer still inherits from OldConsumer. Handle separately. + MS_THROW_TYPE_ERROR("pipe consumer not yet supported in new Consumer"); + } + else + { + // This may throw. + consumer = new RTC::Consumer(this->shared, consumerId, producerId, this, body); } // Notify the listener. diff --git a/worker/test/src/RTC/TestSimpleConsumer.cpp b/worker/test/src/RTC/TestConsumer.cpp similarity index 97% rename from worker/test/src/RTC/TestSimpleConsumer.cpp rename to worker/test/src/RTC/TestConsumer.cpp index ccdebe2cf1..bcb40bf52c 100644 --- a/worker/test/src/RTC/TestSimpleConsumer.cpp +++ b/worker/test/src/RTC/TestConsumer.cpp @@ -3,13 +3,13 @@ #include "Channel/ChannelSocket.hpp" #include "FBS/rtpParameters.h" #include "FBS/transport.h" +#include "RTC/Consumer.hpp" #include "RTC/RTP/Packet.hpp" #include "RTC/RTP/RtpStream.hpp" #include "RTC/RTP/RtpStreamRecv.hpp" #include "RTC/RTP/SharedPacket.hpp" #include "RTC/RtpDictionaries.hpp" #include "RTC/Shared.hpp" -#include "RTC/SimpleConsumer.hpp" #include namespace @@ -113,7 +113,7 @@ namespace return rtpParameters.FillBuffer(builder); }; - std::unique_ptr createConsumer(ConsumerListener* listener) + std::unique_ptr createConsumer(ConsumerListener* listener) { flatbuffers::FlatBufferBuilder bufferBuilder; @@ -141,7 +141,7 @@ namespace const auto* consumeRequest = flatbuffers::GetRoot(buf); - return std::make_unique( + return std::make_unique( &shared, consumeRequest->consumerId()->str(), consumeRequest->producerId()->str(), @@ -180,12 +180,12 @@ namespace } std::unique_ptr listener; - std::unique_ptr consumer; + std::unique_ptr consumer; std::unique_ptr rtpStream; }; } // namespace -SCENARIO("SimpleConsumer", "[rtp][consumer]") +SCENARIO("Consumer with SimpleProducerStreamManager", "[rtp][consumer]") { // TODO: We should NOT parse RTP packets for tests anymore. We should use // RTC::RTP::Packet::Factory() instead. @@ -229,7 +229,6 @@ SCENARIO("SimpleConsumer", "[rtp][consumer]") 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, }; // clang-format on From 0caec34750337c5ac02f860483e1200e2f1d6a5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Mon, 16 Feb 2026 14:47:00 +0100 Subject: [PATCH 02/37] Use qualified names everywhere --- worker/include/RTC/Consumer.hpp | 6 ++---- worker/include/RTC/ProducerStreamManager.hpp | 18 ++++++++---------- .../RTC/SimpleProducerStreamManager.hpp | 6 +++--- .../RTC/SimulcastProducerStreamManager.hpp | 8 ++++---- .../include/RTC/SvcProducerStreamManager.hpp | 6 +++--- worker/src/RTC/Consumer.cpp | 4 ++-- worker/src/RTC/SimpleProducerStreamManager.cpp | 5 +++-- .../src/RTC/SimulcastProducerStreamManager.cpp | 5 +++-- worker/src/RTC/SvcProducerStreamManager.cpp | 5 +++-- 9 files changed, 31 insertions(+), 32 deletions(-) diff --git a/worker/include/RTC/Consumer.hpp b/worker/include/RTC/Consumer.hpp index de764bd9d8..77ae85e77a 100644 --- a/worker/include/RTC/Consumer.hpp +++ b/worker/include/RTC/Consumer.hpp @@ -27,8 +27,6 @@ namespace RTC { - using namespace ConsumerTypes; - class Consumer : public Channel::ChannelSocket::RequestHandler, public RTC::RTP::RtpStreamSend::Listener, public RTC::ProducerStreamManager::Listener @@ -92,7 +90,7 @@ namespace RTC { return this->type; } - VideoLayers GetPreferredLayers() const + RTC::ConsumerTypes::VideoLayers GetPreferredLayers() const { return this->producerStreamManager->GetPreferredLayers(); } @@ -233,7 +231,7 @@ namespace RTC bool producerClosed{ false }; bool lastSentPacketHasMarker{ false }; RTC::SeqManager rtpSeqManager; - std::unique_ptr producerStreamManager; + std::unique_ptr producerStreamManager; std::map::SeqLowerThan> targetLayerRetransmissionBuffer; }; diff --git a/worker/include/RTC/ProducerStreamManager.hpp b/worker/include/RTC/ProducerStreamManager.hpp index 93de5f27a7..ef21da3a90 100644 --- a/worker/include/RTC/ProducerStreamManager.hpp +++ b/worker/include/RTC/ProducerStreamManager.hpp @@ -13,8 +13,6 @@ namespace RTC { - using namespace ConsumerTypes; - class ProducerStreamManager { public: @@ -58,7 +56,7 @@ namespace RTC public: ProducerStreamManager( const std::vector& consumableRtpEncodings, - const VideoLayers& preferredLayers, + const RTC::ConsumerTypes::VideoLayers& preferredLayers, std::unique_ptr encodingContext, RTC::Media::Kind kind, bool keyFrameSupported, @@ -72,7 +70,7 @@ namespace RTC virtual ~ProducerStreamManager() = default; public: - virtual VideoLayers GetTargetLayers() const = 0; + virtual RTC::ConsumerTypes::VideoLayers GetTargetLayers() const = 0; virtual int16_t GetCurrentSpatialLayer() const = 0; virtual int16_t GetCurrentTemporalLayer() const = 0; virtual RTC::RTP::RtpStreamRecv* GetProducerCurrentRtpStream() const = 0; @@ -81,11 +79,11 @@ namespace RTC { return this->encodingContext.get(); } - const VideoLayers& GetPreferredLayers() const + const RTC::ConsumerTypes::VideoLayers& GetPreferredLayers() const { return this->preferredLayers; } - void SetPreferredLayers(const VideoLayers& layers) + void SetPreferredLayers(const RTC::ConsumerTypes::VideoLayers& layers) { this->preferredLayers = layers; } @@ -119,13 +117,13 @@ namespace RTC virtual void RequestKeyFrameForCurrentSpatialLayer() = 0; virtual void UpdateTargetLayers(int16_t newTargetSpatialLayer, int16_t newTargetTemporalLayer) = 0; - virtual bool RecalculateTargetLayers(VideoLayers& newTargetLayers) const = 0; + virtual bool RecalculateTargetLayers(RTC::ConsumerTypes::VideoLayers& newTargetLayers) const = 0; void MayChangeLayers(bool force) { MS_TRACE(); - VideoLayers newTargetLayers; + RTC::ConsumerTypes::VideoLayers newTargetLayers; if (RecalculateTargetLayers(newTargetLayers)) { @@ -172,8 +170,8 @@ namespace RTC bool externallyManagedBitrate{ false }; // Layer preferences. - VideoLayers preferredLayers; - VideoLayers provisionalTargetLayers; + RTC::ConsumerTypes::VideoLayers preferredLayers; + RTC::ConsumerTypes::VideoLayers provisionalTargetLayers; // Sync state. bool syncRequired{ false }; diff --git a/worker/include/RTC/SimpleProducerStreamManager.hpp b/worker/include/RTC/SimpleProducerStreamManager.hpp index d0410b24c4..672b988138 100644 --- a/worker/include/RTC/SimpleProducerStreamManager.hpp +++ b/worker/include/RTC/SimpleProducerStreamManager.hpp @@ -10,14 +10,14 @@ namespace RTC public: SimpleProducerStreamManager( const std::vector& consumableRtpEncodings, - const VideoLayers& preferredLayers, + const RTC::ConsumerTypes::VideoLayers& preferredLayers, std::unique_ptr encodingContext, RTC::Media::Kind kind, bool keyFrameSupported, Listener* listener); public: - VideoLayers GetTargetLayers() const override + RTC::ConsumerTypes::VideoLayers GetTargetLayers() const override { return {}; } @@ -50,7 +50,7 @@ namespace RTC void RequestKeyFrameForTargetSpatialLayer() override; void RequestKeyFrameForCurrentSpatialLayer() override; void UpdateTargetLayers(int16_t spatial, int16_t temporal) override; - bool RecalculateTargetLayers(VideoLayers& newTargetLayers) const override; + bool RecalculateTargetLayers(RTC::ConsumerTypes::VideoLayers& newTargetLayers) const override; void OnTransportConnected() override; void OnTransportDisconnected() override; void OnPaused() override; diff --git a/worker/include/RTC/SimulcastProducerStreamManager.hpp b/worker/include/RTC/SimulcastProducerStreamManager.hpp index efb2011f4b..17b067ac11 100644 --- a/worker/include/RTC/SimulcastProducerStreamManager.hpp +++ b/worker/include/RTC/SimulcastProducerStreamManager.hpp @@ -11,14 +11,14 @@ namespace RTC public: SimulcastProducerStreamManager( const std::vector& consumableRtpEncodings, - const VideoLayers& preferredLayers, + const RTC::ConsumerTypes::VideoLayers& preferredLayers, std::unique_ptr encodingContext, RTC::Media::Kind kind, bool keyFrameSupported, Listener* listener); public: - VideoLayers GetTargetLayers() const override + RTC::ConsumerTypes::VideoLayers GetTargetLayers() const override { return this->targetLayers; } @@ -51,7 +51,7 @@ namespace RTC void RequestKeyFrameForTargetSpatialLayer() override; void RequestKeyFrameForCurrentSpatialLayer() override; void UpdateTargetLayers(int16_t newTargetSpatialLayer, int16_t newTargetTemporalLayer) override; - bool RecalculateTargetLayers(VideoLayers& newTargetLayers) const override; + bool RecalculateTargetLayers(RTC::ConsumerTypes::VideoLayers& newTargetLayers) const override; void OnTransportConnected() override; void OnTransportDisconnected() override; void OnPaused() override; @@ -65,7 +65,7 @@ namespace RTC // Producer RTP streams (multiple for Simulcast). std::vector producerRtpStreams; absl::flat_hash_map mapMappedSsrcSpatialLayer; - VideoLayers targetLayers; + RTC::ConsumerTypes::VideoLayers targetLayers; int16_t currentSpatialLayer{ -1 }; int16_t spatialLayerToSync{ -1 }; // Timestamp synchronization. diff --git a/worker/include/RTC/SvcProducerStreamManager.hpp b/worker/include/RTC/SvcProducerStreamManager.hpp index a822245a5d..93288529b8 100644 --- a/worker/include/RTC/SvcProducerStreamManager.hpp +++ b/worker/include/RTC/SvcProducerStreamManager.hpp @@ -10,14 +10,14 @@ namespace RTC public: SvcProducerStreamManager( const std::vector& consumableRtpEncodings, - const VideoLayers& preferredLayers, + const RTC::ConsumerTypes::VideoLayers& preferredLayers, std::unique_ptr encodingContext, RTC::Media::Kind kind, bool keyFrameSupported, Listener* listener); public: - VideoLayers GetTargetLayers() const override + RTC::ConsumerTypes::VideoLayers GetTargetLayers() const override { return this->encodingContext->GetTargetLayers(); } @@ -50,7 +50,7 @@ namespace RTC void RequestKeyFrameForTargetSpatialLayer() override; void RequestKeyFrameForCurrentSpatialLayer() override; void UpdateTargetLayers(int16_t newTargetSpatialLayer, int16_t newTargetTemporalLayer) override; - bool RecalculateTargetLayers(VideoLayers& newTargetLayers) const override; + bool RecalculateTargetLayers(RTC::ConsumerTypes::VideoLayers& newTargetLayers) const override; void OnTransportConnected() override; void OnTransportDisconnected() override; void OnPaused() override; diff --git a/worker/src/RTC/Consumer.cpp b/worker/src/RTC/Consumer.cpp index 91aa88da2d..beeb28ec7a 100644 --- a/worker/src/RTC/Consumer.cpp +++ b/worker/src/RTC/Consumer.cpp @@ -189,7 +189,7 @@ namespace RTC bool keyFrameSupported = RTC::RTP::Codecs::Tools::CanBeKeyFrame(mediaCodec->mimeType); // Build preferred layers from FBS data. - VideoLayers preferredLayers; + RTC::ConsumerTypes::VideoLayers preferredLayers; // Let's choose an initial output seq number between 1000 and 32768 to avoid // libsrtp bug: @@ -597,7 +597,7 @@ namespace RTC const auto* body = request->data->body_as(); const auto* preferredLayers = body->preferredLayers(); - VideoLayers newPreferredLayers; + RTC::ConsumerTypes::VideoLayers newPreferredLayers; // Spatial layer. newPreferredLayers.spatial = preferredLayers->spatialLayer(); diff --git a/worker/src/RTC/SimpleProducerStreamManager.cpp b/worker/src/RTC/SimpleProducerStreamManager.cpp index b9dab5d631..9dd369af60 100644 --- a/worker/src/RTC/SimpleProducerStreamManager.cpp +++ b/worker/src/RTC/SimpleProducerStreamManager.cpp @@ -10,7 +10,7 @@ namespace RTC SimpleProducerStreamManager::SimpleProducerStreamManager( const std::vector& consumableRtpEncodings, - const VideoLayers& preferredLayers, + const RTC::ConsumerTypes::VideoLayers& preferredLayers, std::unique_ptr encodingContext, RTC::Media::Kind kind, bool keyFrameSupported, @@ -274,7 +274,8 @@ namespace RTC MS_TRACE(); } - bool SimpleProducerStreamManager::RecalculateTargetLayers(VideoLayers& /*newTargetLayers*/) const + bool SimpleProducerStreamManager::RecalculateTargetLayers( + RTC::ConsumerTypes::VideoLayers& /*newTargetLayers*/) const { MS_TRACE(); diff --git a/worker/src/RTC/SimulcastProducerStreamManager.cpp b/worker/src/RTC/SimulcastProducerStreamManager.cpp index fe348da3a7..3266317905 100644 --- a/worker/src/RTC/SimulcastProducerStreamManager.cpp +++ b/worker/src/RTC/SimulcastProducerStreamManager.cpp @@ -17,7 +17,7 @@ namespace RTC SimulcastProducerStreamManager::SimulcastProducerStreamManager( const std::vector& consumableRtpEncodings, - const VideoLayers& preferredLayers, + const RTC::ConsumerTypes::VideoLayers& preferredLayers, std::unique_ptr encodingContext, RTC::Media::Kind kind, bool keyFrameSupported, @@ -851,7 +851,8 @@ namespace RTC } } - bool SimulcastProducerStreamManager::RecalculateTargetLayers(VideoLayers& newTargetLayers) const + bool SimulcastProducerStreamManager::RecalculateTargetLayers( + RTC::ConsumerTypes::VideoLayers& newTargetLayers) const { MS_TRACE(); diff --git a/worker/src/RTC/SvcProducerStreamManager.cpp b/worker/src/RTC/SvcProducerStreamManager.cpp index 601f7b0f04..55e6e6656c 100644 --- a/worker/src/RTC/SvcProducerStreamManager.cpp +++ b/worker/src/RTC/SvcProducerStreamManager.cpp @@ -15,7 +15,7 @@ namespace RTC SvcProducerStreamManager::SvcProducerStreamManager( const std::vector& consumableRtpEncodings, - const VideoLayers& preferredLayers, + const RTC::ConsumerTypes::VideoLayers& preferredLayers, std::unique_ptr encodingContext, RTC::Media::Kind kind, bool keyFrameSupported, @@ -519,7 +519,8 @@ namespace RTC } } - bool SvcProducerStreamManager::RecalculateTargetLayers(VideoLayers& newTargetLayers) const + bool SvcProducerStreamManager::RecalculateTargetLayers( + RTC::ConsumerTypes::VideoLayers& newTargetLayers) const { MS_TRACE(); From f30ee9f774e0632334d7685a638fa05130ce8fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Tue, 17 Feb 2026 14:15:38 +0100 Subject: [PATCH 03/37] Remove unused GetRtpStreams() method --- worker/include/RTC/Consumer.hpp | 4 ---- worker/include/RTC/OldConsumer.hpp | 1 - worker/include/RTC/PipeConsumer.hpp | 4 ---- worker/include/RTC/SimpleConsumer.hpp | 4 ---- worker/include/RTC/SimulcastConsumer.hpp | 4 ---- worker/include/RTC/SvcConsumer.hpp | 4 ---- 6 files changed, 21 deletions(-) diff --git a/worker/include/RTC/Consumer.hpp b/worker/include/RTC/Consumer.hpp index 77ae85e77a..8e5a250527 100644 --- a/worker/include/RTC/Consumer.hpp +++ b/worker/include/RTC/Consumer.hpp @@ -143,10 +143,6 @@ namespace RTC uint32_t GetDesiredBitrate() const; void SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket); bool GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs); - const std::vector& GetRtpStreams() const - { - return this->rtpStreams; - } void NeedWorstRemoteFractionLost(uint32_t mappedSsrc, uint8_t& worstRemoteFractionLost); void ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket); void ReceiveKeyFrameRequest(RTC::RTCP::FeedbackPs::MessageType messageType, uint32_t ssrc); diff --git a/worker/include/RTC/OldConsumer.hpp b/worker/include/RTC/OldConsumer.hpp index d3ad7c42dc..f6c899e75e 100644 --- a/worker/include/RTC/OldConsumer.hpp +++ b/worker/include/RTC/OldConsumer.hpp @@ -145,7 +145,6 @@ namespace RTC virtual uint32_t GetDesiredBitrate() const = 0; virtual void SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket) = 0; virtual bool GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs) = 0; - virtual const std::vector& GetRtpStreams() const = 0; virtual void NeedWorstRemoteFractionLost(uint32_t mappedSsrc, uint8_t& worstRemoteFractionLost) = 0; virtual void ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket) = 0; virtual void ReceiveKeyFrameRequest( diff --git a/worker/include/RTC/PipeConsumer.hpp b/worker/include/RTC/PipeConsumer.hpp index 5c8b880a00..3c205c03b0 100644 --- a/worker/include/RTC/PipeConsumer.hpp +++ b/worker/include/RTC/PipeConsumer.hpp @@ -44,10 +44,6 @@ namespace RTC uint32_t GetDesiredBitrate() const override; void SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket) override; bool GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs) override; - const std::vector& GetRtpStreams() const 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; diff --git a/worker/include/RTC/SimpleConsumer.hpp b/worker/include/RTC/SimpleConsumer.hpp index 62a43891b1..21d3b18eb9 100644 --- a/worker/include/RTC/SimpleConsumer.hpp +++ b/worker/include/RTC/SimpleConsumer.hpp @@ -48,10 +48,6 @@ namespace RTC void ApplyLayers() override; uint32_t GetDesiredBitrate() const override; void SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket) override; - const std::vector& GetRtpStreams() const override - { - return this->rtpStreams; - } bool GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs) override; void NeedWorstRemoteFractionLost(uint32_t mappedSsrc, uint8_t& worstRemoteFractionLost) override; void ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket) override; diff --git a/worker/include/RTC/SimulcastConsumer.hpp b/worker/include/RTC/SimulcastConsumer.hpp index dd9c5f03fd..15dbfdd8ff 100644 --- a/worker/include/RTC/SimulcastConsumer.hpp +++ b/worker/include/RTC/SimulcastConsumer.hpp @@ -65,10 +65,6 @@ namespace RTC uint32_t GetDesiredBitrate() const override; void SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket) override; bool GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs) override; - const std::vector& GetRtpStreams() const 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; diff --git a/worker/include/RTC/SvcConsumer.hpp b/worker/include/RTC/SvcConsumer.hpp index c3a3cd4edf..4fa10bf794 100644 --- a/worker/include/RTC/SvcConsumer.hpp +++ b/worker/include/RTC/SvcConsumer.hpp @@ -59,10 +59,6 @@ namespace RTC uint32_t GetDesiredBitrate() const override; void SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket) override; bool GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs) override; - const std::vector& GetRtpStreams() const 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; From 6ea2714e9f24a9ce631fac1ea94cab56cd7f37eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Tue, 17 Feb 2026 15:34:20 +0100 Subject: [PATCH 04/37] Fix: call packet->logger.Discarded() with the proper reason --- worker/include/RTC/ProducerStreamManager.hpp | 2 +- worker/src/RTC/Consumer.cpp | 8 ----- .../src/RTC/SimpleProducerStreamManager.cpp | 12 ++++++++ .../RTC/SimulcastProducerStreamManager.cpp | 30 +++++++++++++++++++ worker/src/RTC/SvcProducerStreamManager.cpp | 16 ++++++++++ 5 files changed, 59 insertions(+), 9 deletions(-) diff --git a/worker/include/RTC/ProducerStreamManager.hpp b/worker/include/RTC/ProducerStreamManager.hpp index ef21da3a90..e9933cac3a 100644 --- a/worker/include/RTC/ProducerStreamManager.hpp +++ b/worker/include/RTC/ProducerStreamManager.hpp @@ -40,7 +40,7 @@ namespace RTC BUFFER }; - Type type{ Type::SILENT_DROP }; + Type type{ Type::FORWARD }; // Valid when type == FORWARD: uint32_t tsOffset{ 0u }; diff --git a/worker/src/RTC/Consumer.cpp b/worker/src/RTC/Consumer.cpp index beeb28ec7a..2334a570cd 100644 --- a/worker/src/RTC/Consumer.cpp +++ b/worker/src/RTC/Consumer.cpp @@ -1040,10 +1040,6 @@ namespace RTC { case ProducerStreamManager::RtpPacketProcessResult::Type::DROP: { -#ifdef MS_RTC_LOGGER_RTP - packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::DROPPED_BY_CODEC); -#endif - this->rtpSeqManager.Drop(packet->GetSequenceNumber()); return; @@ -1057,10 +1053,6 @@ namespace RTC case ProducerStreamManager::RtpPacketProcessResult::Type::BUFFER: { -#ifdef MS_RTC_LOGGER_RTP - packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::NOT_A_KEYFRAME); -#endif - StorePacketInTargetLayerRetransmissionBuffer(packet, sharedPacket); return; diff --git a/worker/src/RTC/SimpleProducerStreamManager.cpp b/worker/src/RTC/SimpleProducerStreamManager.cpp index 9dd369af60..7adbf8ab05 100644 --- a/worker/src/RTC/SimpleProducerStreamManager.cpp +++ b/worker/src/RTC/SimpleProducerStreamManager.cpp @@ -176,6 +176,10 @@ namespace RTC // key frame and it arrived before the first packet of the key frame. result.type = RtpPacketProcessResult::Type::BUFFER; +#ifdef MS_RTC_LOGGER_RTP + packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::NOT_A_KEYFRAME); +#endif + return result; } @@ -184,6 +188,10 @@ namespace RTC { result.type = RtpPacketProcessResult::Type::DROP; +#ifdef MS_RTC_LOGGER_RTP + packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::EMPTY_PAYLOAD); +#endif + return result; } @@ -201,6 +209,10 @@ namespace RTC result.type = RtpPacketProcessResult::Type::DROP; +#ifdef MS_RTC_LOGGER_RTP + packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::DROPPED_BY_CODEC); +#endif + return result; } diff --git a/worker/src/RTC/SimulcastProducerStreamManager.cpp b/worker/src/RTC/SimulcastProducerStreamManager.cpp index 3266317905..51c00dc145 100644 --- a/worker/src/RTC/SimulcastProducerStreamManager.cpp +++ b/worker/src/RTC/SimulcastProducerStreamManager.cpp @@ -460,6 +460,10 @@ namespace RTC if (spatialLayer == this->currentSpatialLayer) { result.type = RtpPacketProcessResult::Type::DROP; + +#ifdef MS_RTC_LOGGER_RTP + packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::INVALID_TARGET_LAYER); +#endif } else { @@ -482,6 +486,10 @@ namespace RTC { result.type = RtpPacketProcessResult::Type::BUFFER; +#ifdef MS_RTC_LOGGER_RTP + packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::NOT_A_KEYFRAME); +#endif + return result; } @@ -505,6 +513,10 @@ namespace RTC { result.type = RtpPacketProcessResult::Type::BUFFER; +#ifdef MS_RTC_LOGGER_RTP + packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::NOT_A_KEYFRAME); +#endif + return result; } @@ -514,6 +526,10 @@ namespace RTC if (spatialLayer == this->currentSpatialLayer) { result.type = RtpPacketProcessResult::Type::DROP; + +#ifdef MS_RTC_LOGGER_RTP + packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::EMPTY_PAYLOAD); +#endif } else { @@ -638,6 +654,11 @@ namespace RTC result.type = RtpPacketProcessResult::Type::SILENT_DROP; +#ifdef MS_RTC_LOGGER_RTP + packet->logger.Discarded( + RTC::RtcLogger::RtpPacket::DiscardReason::TOO_HIGH_TIMESTAMP_EXTRA_NEEDED); +#endif + return result; } @@ -678,6 +699,11 @@ namespace RTC { result.type = RtpPacketProcessResult::Type::DROP; +#ifdef MS_RTC_LOGGER_RTP + packet->logger.Discarded( + RTC::RtcLogger::RtpPacket::DiscardReason::PACKET_PREVIOUS_TO_SPATIAL_LAYER_SWITCH); +#endif + return result; } else if (SeqManager::IsSeqHigherThan( @@ -716,6 +742,10 @@ namespace RTC { result.type = RtpPacketProcessResult::Type::DROP; +#ifdef MS_RTC_LOGGER_RTP + packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::DROPPED_BY_CODEC); +#endif + return result; } diff --git a/worker/src/RTC/SvcProducerStreamManager.cpp b/worker/src/RTC/SvcProducerStreamManager.cpp index 55e6e6656c..820860164a 100644 --- a/worker/src/RTC/SvcProducerStreamManager.cpp +++ b/worker/src/RTC/SvcProducerStreamManager.cpp @@ -367,6 +367,10 @@ namespace RTC { result.type = RtpPacketProcessResult::Type::DROP; +#ifdef MS_RTC_LOGGER_RTP + packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::INVALID_TARGET_LAYER); +#endif + return result; } @@ -381,6 +385,10 @@ namespace RTC // key frame and it arrived before the first packet of the key frame. result.type = RtpPacketProcessResult::Type::BUFFER; +#ifdef MS_RTC_LOGGER_RTP + packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::NOT_A_KEYFRAME); +#endif + return result; } @@ -389,6 +397,10 @@ namespace RTC { result.type = RtpPacketProcessResult::Type::DROP; +#ifdef MS_RTC_LOGGER_RTP + packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::EMPTY_PAYLOAD); +#endif + return result; } @@ -429,6 +441,10 @@ namespace RTC { result.type = RtpPacketProcessResult::Type::DROP; +#ifdef MS_RTC_LOGGER_RTP + packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::DROPPED_BY_CODEC); +#endif + return result; } From b185abf871011208310ab044c6b79223368bf66b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Tue, 17 Feb 2026 17:11:09 +0100 Subject: [PATCH 05/37] Fix: Restore IsActive() logic Consumer::IsActive() checks: - transportConnected - paused - producerPaused - producerClosed ProducerStreamManager::IsActive() checks: - Consumer::IsActive() - Whether producer streams are active --- worker/include/RTC/Consumer.hpp | 3 +- worker/include/RTC/ProducerStreamManager.hpp | 8 +---- .../RTC/SimpleProducerStreamManager.hpp | 2 +- .../RTC/SimulcastProducerStreamManager.hpp | 2 +- .../include/RTC/SvcProducerStreamManager.hpp | 2 +- .../src/RTC/SimpleProducerStreamManager.cpp | 8 ++--- .../RTC/SimulcastProducerStreamManager.cpp | 31 ++++++++++--------- worker/src/RTC/SvcProducerStreamManager.cpp | 7 +++-- 8 files changed, 30 insertions(+), 33 deletions(-) diff --git a/worker/include/RTC/Consumer.hpp b/worker/include/RTC/Consumer.hpp index 8e5a250527..6e84294729 100644 --- a/worker/include/RTC/Consumer.hpp +++ b/worker/include/RTC/Consumer.hpp @@ -109,8 +109,7 @@ namespace RTC this->transportConnected && !this->paused && !this->producerPaused && - !this->producerClosed && - this->producerStreamManager->IsProducerStreamActive() + !this->producerClosed ); // clang-format on } diff --git a/worker/include/RTC/ProducerStreamManager.hpp b/worker/include/RTC/ProducerStreamManager.hpp index e9933cac3a..76765cc094 100644 --- a/worker/include/RTC/ProducerStreamManager.hpp +++ b/worker/include/RTC/ProducerStreamManager.hpp @@ -88,8 +88,6 @@ namespace RTC this->preferredLayers = layers; } - virtual bool IsProducerStreamActive() const = 0; - virtual void ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) = 0; virtual void ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) = 0; virtual void ProducerRtpStreamScore( @@ -151,12 +149,8 @@ namespace RTC virtual void OnResumed() = 0; protected: - bool IsActive() const - { - return this->listener->IsActive(); - } + virtual bool IsActive() const = 0; - protected: // Passed by argument. Listener* listener{ nullptr }; bool keyFrameSupported{ false }; diff --git a/worker/include/RTC/SimpleProducerStreamManager.hpp b/worker/include/RTC/SimpleProducerStreamManager.hpp index 672b988138..c6bf3e77f4 100644 --- a/worker/include/RTC/SimpleProducerStreamManager.hpp +++ b/worker/include/RTC/SimpleProducerStreamManager.hpp @@ -31,7 +31,7 @@ namespace RTC } RTC::RTP::RtpStreamRecv* GetProducerCurrentRtpStream() const override; RTC::RTP::RtpStreamRecv* GetProducerTargetRtpStream() const override; - bool IsProducerStreamActive() const override; + bool IsActive() const override; void ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; void ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; void ProducerRtpStreamScore( diff --git a/worker/include/RTC/SimulcastProducerStreamManager.hpp b/worker/include/RTC/SimulcastProducerStreamManager.hpp index 17b067ac11..693f9496a0 100644 --- a/worker/include/RTC/SimulcastProducerStreamManager.hpp +++ b/worker/include/RTC/SimulcastProducerStreamManager.hpp @@ -32,7 +32,7 @@ namespace RTC } RTC::RTP::RtpStreamRecv* GetProducerCurrentRtpStream() const override; RTC::RTP::RtpStreamRecv* GetProducerTargetRtpStream() const override; - bool IsProducerStreamActive() const override; + bool IsActive() const override; void ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; void ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; void ProducerRtpStreamScore( diff --git a/worker/include/RTC/SvcProducerStreamManager.hpp b/worker/include/RTC/SvcProducerStreamManager.hpp index 93288529b8..e8fc5ed3d6 100644 --- a/worker/include/RTC/SvcProducerStreamManager.hpp +++ b/worker/include/RTC/SvcProducerStreamManager.hpp @@ -31,7 +31,7 @@ namespace RTC } RTC::RTP::RtpStreamRecv* GetProducerCurrentRtpStream() const override; RTC::RTP::RtpStreamRecv* GetProducerTargetRtpStream() const override; - bool IsProducerStreamActive() const override; + bool IsActive() const override; void ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; void ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; void ProducerRtpStreamScore( diff --git a/worker/src/RTC/SimpleProducerStreamManager.cpp b/worker/src/RTC/SimpleProducerStreamManager.cpp index 7adbf8ab05..dc4b2a8a4a 100644 --- a/worker/src/RTC/SimpleProducerStreamManager.cpp +++ b/worker/src/RTC/SimpleProducerStreamManager.cpp @@ -35,18 +35,18 @@ namespace RTC return this->producerRtpStream; } - bool SimpleProducerStreamManager::IsProducerStreamActive() const + bool SimpleProducerStreamManager::IsActive() const { MS_TRACE(); // clang-format off return ( + this->listener->IsActive() && this->producerRtpStream && // If there is no RTP inactivity check do not consider the stream // inactive despite it has score 0. (this->producerRtpStream->GetScore() > 0u || !this->producerRtpStream->HasRtpInactivityCheckEnabled()) ); - // clang-format on } @@ -301,7 +301,7 @@ namespace RTC this->syncRequired = true; - if (this->listener->IsActive()) + if (IsActive()) { RequestKeyFrame(); } @@ -327,7 +327,7 @@ namespace RTC this->syncRequired = true; - if (this->listener->IsActive()) + if (IsActive()) { RequestKeyFrame(); } diff --git a/worker/src/RTC/SimulcastProducerStreamManager.cpp b/worker/src/RTC/SimulcastProducerStreamManager.cpp index 51c00dc145..ab224e366a 100644 --- a/worker/src/RTC/SimulcastProducerStreamManager.cpp +++ b/worker/src/RTC/SimulcastProducerStreamManager.cpp @@ -64,21 +64,24 @@ namespace RTC return this->producerRtpStreams.at(this->targetLayers.spatial); } - bool SimulcastProducerStreamManager::IsProducerStreamActive() const + bool SimulcastProducerStreamManager::IsActive() const { MS_TRACE(); // clang-format off - return std::any_of( - this->producerRtpStreams.begin(), - this->producerRtpStreams.end(), - [](const RTC::RTP::RtpStreamRecv* rtpStream) - { - return ( - rtpStream != nullptr && - (rtpStream->GetScore() > 0u || !rtpStream->HasRtpInactivityCheckEnabled()) - ); - } + return ( + this->listener->IsActive() && + std::any_of( + this->producerRtpStreams.begin(), + this->producerRtpStreams.end(), + [](const RTC::RTP::RtpStreamRecv* rtpStream) + { + return ( + rtpStream != nullptr && + (rtpStream->GetScore() > 0u || !rtpStream->HasRtpInactivityCheckEnabled()) + ); + } + ) ); // clang-format on } @@ -127,7 +130,7 @@ namespace RTC if (this->listener->IsActive()) { // All Producer streams are dead. - if (!IsProducerStreamActive()) + if (!IsActive()) { UpdateTargetLayers(-1, -1); } @@ -967,7 +970,7 @@ namespace RTC this->spatialLayerToSync = -1; this->keyFrameForTsOffsetRequested = false; - if (this->listener->IsActive()) + if (IsActive()) { MayChangeLayers(/*force*/ false); } @@ -1000,7 +1003,7 @@ namespace RTC this->keyFrameForTsOffsetRequested = false; this->checkingForOldPacketsInSpatialLayer = false; - if (this->listener->IsActive()) + if (IsActive()) { MayChangeLayers(/*force*/ false); } diff --git a/worker/src/RTC/SvcProducerStreamManager.cpp b/worker/src/RTC/SvcProducerStreamManager.cpp index 820860164a..a518fa4cd6 100644 --- a/worker/src/RTC/SvcProducerStreamManager.cpp +++ b/worker/src/RTC/SvcProducerStreamManager.cpp @@ -52,12 +52,13 @@ namespace RTC return this->producerRtpStream; } - bool SvcProducerStreamManager::IsProducerStreamActive() const + bool SvcProducerStreamManager::IsActive() const { MS_TRACE(); // clang-format off return ( + this->listener->IsActive() && this->producerRtpStream && // If there is no RTP inactivity check do not consider the stream // inactive despite it has score 0. @@ -613,7 +614,7 @@ namespace RTC this->syncRequired = true; - if (this->listener->IsActive()) + if (IsActive()) { MayChangeLayers(/*force*/ false); } @@ -643,7 +644,7 @@ namespace RTC this->syncRequired = true; - if (this->listener->IsActive()) + if (IsActive()) { MayChangeLayers(/*force*/ false); } From a6d0a0b52badb5b38f052eb726b4d0537b9948f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Wed, 18 Feb 2026 09:56:36 +0100 Subject: [PATCH 06/37] Tests: TestSimpleProducerStreamManager --- worker/meson.build | 1 + .../RTC/TestSimpleProducerStreamManager.cpp | 540 ++++++++++++++++++ 2 files changed, 541 insertions(+) create mode 100644 worker/test/src/RTC/TestSimpleProducerStreamManager.cpp diff --git a/worker/meson.build b/worker/meson.build index 44984706c6..4112d489a5 100644 --- a/worker/meson.build +++ b/worker/meson.build @@ -410,6 +410,7 @@ test_sources = [ 'test/src/tests.cpp', 'test/src/testHelpers.cpp', 'test/src/RTC/TestConsumer.cpp', + 'test/src/RTC/TestSimpleProducerStreamManager.cpp', 'test/src/RTC/TestKeyFrameRequestManager.cpp', 'test/src/RTC/TestNackGenerator.cpp', 'test/src/RTC/TestRateCalculator.cpp', diff --git a/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp b/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp new file mode 100644 index 0000000000..e0cb034e52 --- /dev/null +++ b/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp @@ -0,0 +1,540 @@ +#include "RTC/RTP/Codecs/PayloadDescriptorHandler.hpp" +#include "RTC/RTP/Packet.hpp" +#include "RTC/RTP/RtpStreamRecv.hpp" +#include "RTC/SimpleProducerStreamManager.hpp" +#include + +namespace +{ + using RtpPacketProcessResult = RTC::ProducerStreamManager::RtpPacketProcessResult; + + // NOLINTBEGIN(readability-identifier-naming) + constexpr uint32_t mappedSsrc = 1234567890; + // NOLINTEND(readability-identifier-naming) + + class MockListener : public RTC::ProducerStreamManager::Listener + { + public: + bool isActive{ true }; + int keyFrameRequestCount{ 0 }; + uint32_t lastKeyFrameRequestedMappedSsrc{ 0 }; + + bool IsActive() const override + { + return this->isActive; + } + + void OnProducerStreamManagerKeyFrameRequested(uint32_t mappedSsrc) override + { + this->keyFrameRequestCount++; + this->lastKeyFrameRequestedMappedSsrc = mappedSsrc; + } + + void OnProducerStreamManagerNeedBitrateChange() override + { + } + + void OnProducerStreamManagerLayersChanged() override + { + } + + void OnProducerStreamManagerClearRetransmissionBuffer() override + { + } + + void OnProducerStreamManagerScore() override + { + } + }; + + class RtpStreamRecvListener : public RTC::RTP::RtpStreamRecv::Listener + { + public: + void OnRtpStreamScore( + RTC::RTP::RtpStream* /*rtpStream*/, uint8_t /*score*/, uint8_t /*previousScore*/) override + { + } + + void OnRtpStreamSendRtcpPacket( + RTC::RTP::RtpStreamRecv* /*rtpStream*/, RTC::RTCP::Packet* /*packet*/) override + { + } + + void OnRtpStreamNeedWorstRemoteFractionLost( + RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint8_t& /*worstRemoteFractionLost*/) override + { + } + }; + + class MockEncodingContext : public RTC::RTP::Codecs::EncodingContext + { + public: + MockEncodingContext() : RTC::RTP::Codecs::EncodingContext(this->params) + { + } + void SyncRequired() override + { + } + + private: + RTC::RTP::Codecs::EncodingContext::Params params; + }; + + class MockPayloadDescriptorHandler : public RTC::RTP::Codecs::PayloadDescriptorHandler + { + public: + explicit MockPayloadDescriptorHandler() = default; + ~MockPayloadDescriptorHandler() override = default; + void Dump(int /*indentation*/) const override + { + } + bool Process( + RTC::RTP::Codecs::EncodingContext* /*context*/, + RTC::RTP::Packet* /*packet*/, + bool& /*marker*/) override + { + return this->processResult; + } + void RtpPacketChanged(RTC::RTP::Packet* /*packet*/) override + { + } + std::unique_ptr GetEncoder() const override + { + return nullptr; + } + void Encode( + RTC::RTP::Packet* /*packet*/, RTC::RTP::Codecs::PayloadDescriptor::Encoder* /*encoder*/) override + { + } + void Restore(RTC::RTP::Packet* /*packet*/) override + { + } + uint8_t GetSpatialLayer() const override + { + return 0; + } + uint8_t GetTemporalLayer() const override + { + return 0; + } + bool IsKeyFrame() const override + { + return this->isKeyFrame; + } + + public: + bool isKeyFrame{ false }; + bool processResult{ true }; + }; + + std::unique_ptr createManager( + MockListener* listener, + bool keyFrameSupported = true, + RTC::Media::Kind kind = RTC::Media::Kind::VIDEO, + std::unique_ptr encodingContext = nullptr) + { + RTC::RtpEncodingParameters encoding; + encoding.ssrc = mappedSsrc; + std::vector consumableRtpEncodings{ encoding }; + + RTC::ConsumerTypes::VideoLayers preferredLayers; + + return std::make_unique( + consumableRtpEncodings, + preferredLayers, + std::move(encodingContext), + kind, + keyFrameSupported, + listener); + } + + // RtpStreamRecvListener must outlive the RtpStreamRecv. + RtpStreamRecvListener streamRecvListener; // NOLINT(readability-identifier-naming) + + std::unique_ptr createRtpStreamRecv() + { + RTC::RTP::RtpStream::Params params; + + params.ssrc = mappedSsrc; + params.clockRate = 90000; + + return std::make_unique(&streamRecvListener, params, 0u, false); + } + + // Feed packets into the RtpStreamRecv so GetBitrate() returns non-zero. + void feedRtpStreamRecv(RTC::RTP::RtpStreamRecv* rtpStream, uint8_t* buffer, size_t len, uint16_t count) + { + std::unique_ptr packet(RTC::RTP::Packet::Parse(buffer, len)); + + for (uint16_t seq = 1; seq <= count; ++seq) + { + packet->SetSequenceNumber(seq); + rtpStream->ReceivePacket(packet.get()); + } + + auto nowMs = DepLibUV::GetTimeMs(); + + // bitrate (bps) = totalBytes * 8000 / windowSizeMs. + // windowSizeMs for RtpStreamRecv is 2500. + auto expectedBitrate = + static_cast(std::trunc((count * packet->GetLength() * 8000.0f / 2500) + 0.5f)); + + REQUIRE(rtpStream->GetBitrate(nowMs) == expectedBitrate); + } +} // namespace + +SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager]") +{ + // clang-format off + uint8_t buffer[] = + { + 0x80, 0x01, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x04, + 0x49, 0x96, 0x02, 0xD2, // SSRC: 1234567890. + // Payload (4 bytes). + 0xFF, 0xFF, 0xFF, 0xFF, + // Extra buffer for cloning. + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + }; + // clang-format on + + const size_t packetLength{ 16 }; + const size_t bufferLength{ packetLength + 12 }; + + SECTION("returns BUFFER when sync required and packet is not a keyframe") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->OnTransportConnected(); + + std::unique_ptr packet(RTC::RTP::Packet::Parse(buffer, bufferLength)); + + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 0, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::BUFFER); + } + + SECTION("returns FORWARD with sendBufferedPackets when syncing with a keyframe") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->OnTransportConnected(); + + std::unique_ptr packet(RTC::RTP::Packet::Parse(buffer, bufferLength)); + const uint16_t seq{ 100 }; + + packet->SetSequenceNumber(seq); + + // packet->SetPayloadDescriptorHandler() will take the ownership. + auto* payloadDescriptorHandler = new MockPayloadDescriptorHandler(); + payloadDescriptorHandler->isKeyFrame = true; + packet->SetPayloadDescriptorHandler(payloadDescriptorHandler); + + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 0, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result.isSyncPacket == true); + REQUIRE(result.syncSeqValue == seq - 1); + REQUIRE(result.sendBufferedPackets == true); + } + + SECTION("returns DROP for empty payload packets") + { + MockListener listener; + auto manager = createManager(&listener, /*keyFrameSupported*/ false); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->OnTransportConnected(); + + std::unique_ptr packet(RTC::RTP::Packet::Parse(buffer, bufferLength)); + + packet->SetSequenceNumber(1); + packet->RemovePayload(); + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 0, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::DROP); + } + + SECTION("returns DROP when ProcessPayload fails") + { + MockListener listener; + auto encodingContext = std::make_unique(); + auto manager = createManager( + &listener, + /*keyFrameSupported*/ false, + RTC::Media::Kind::VIDEO, + std::move(encodingContext)); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->OnTransportConnected(); + + std::unique_ptr packet(RTC::RTP::Packet::Parse(buffer, bufferLength)); + + packet->SetSequenceNumber(1); + + // Set a handler whose Process() returns false. + auto* handler = new MockPayloadDescriptorHandler(); + handler->processResult = false; + packet->SetPayloadDescriptorHandler(handler); + + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 0, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::DROP); + } + + SECTION("returns FORWARD and completes sync when keyFrameSupported is false for the first packet") + { + MockListener listener; + auto manager = createManager(&listener, /*keyFrameSupported*/ false); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->OnTransportConnected(); + + std::unique_ptr packet(RTC::RTP::Packet::Parse(buffer, bufferLength)); + const uint16_t seq{ 100 }; + + packet->SetSequenceNumber(seq); + + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 0, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result.isSyncPacket == true); + REQUIRE(result.syncSeqValue == seq - 1); + REQUIRE(result.sendBufferedPackets == false); + REQUIRE(result.tsOffset == 0u); + } + + SECTION("returns FORWARD for normal packets after sync") + { + MockListener listener; + auto manager = createManager(&listener, /*keyFrameSupported*/ false); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->OnTransportConnected(); + + // Complete sync with first packet. + std::unique_ptr packet(RTC::RTP::Packet::Parse(buffer, bufferLength)); + + packet->SetSequenceNumber(1); + manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 0, /*maxPacketTs*/ 0); + + // Send a second normal packet. + packet->SetSequenceNumber(2); + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 0, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result.isSyncPacket == false); + REQUIRE(result.sendBufferedPackets == false); + REQUIRE(result.tsOffset == 0u); + } + + SECTION("OnTransportConnected requests keyframe when active") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + + // OnTransportConnected should request a keyframe since the manager is active. + manager->OnTransportConnected(); + + REQUIRE(listener.keyFrameRequestCount == 1); + REQUIRE(listener.lastKeyFrameRequestedMappedSsrc == mappedSsrc); + } + + SECTION("OnTransportConnected does not request keyframe when not active") + { + MockListener listener; + listener.isActive = false; + + auto manager = createManager(&listener); + + // Don't wire producerRtpStream — manager is not active. + manager->OnTransportConnected(); + + REQUIRE(listener.keyFrameRequestCount == 0); + } + + SECTION("OnResumed sets syncRequired and requests keyframe") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->OnTransportConnected(); + + int keyFrameCount = listener.keyFrameRequestCount; + + // Call OnResumed — should set syncRequired and request keyframe again. + manager->OnResumed(); + + REQUIRE(listener.keyFrameRequestCount == keyFrameCount + 1); + + // Prove syncRequired was set: sending a non-keyframe returns BUFFER. + std::unique_ptr packet(RTC::RTP::Packet::Parse(buffer, bufferLength)); + + packet->SetSequenceNumber(1); + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 0, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::BUFFER); + } + + SECTION("IncreaseLayer returns producer bitrate when it is less than available bitrate") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->SetExternallyManagedBitrate(); + manager->OnTransportConnected(); + + // Feed packets so the stream has non-zero bitrate. + feedRtpStreamRecv(rtpStream.get(), buffer, bufferLength, 100); + + auto nowMs = DepLibUV::GetTimeMs(); + auto steamBitrate = rtpStream->GetBitrate(nowMs); + auto usedBitrate = manager->IncreaseLayer( + /*bitrate*/ steamBitrate + 1u, /*considerLoss*/ false, /*lossPercentage*/ 0.0f, nowMs); + + REQUIRE(usedBitrate == steamBitrate); + } + + SECTION("IncreaseLayer returns available bitrate when it is less than producer bitrate") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->SetExternallyManagedBitrate(); + manager->OnTransportConnected(); + + // Feed packets so the stream has non-zero bitrate. + feedRtpStreamRecv(rtpStream.get(), buffer, bufferLength, 100); + + auto nowMs = DepLibUV::GetTimeMs(); + auto streamBitrate = rtpStream->GetBitrate(nowMs); + uint32_t availableBitrate = streamBitrate - 1; + auto usedBitrate = manager->IncreaseLayer( + /*bitrate*/ availableBitrate, /*considerLoss*/ false, /*lossPercentage*/ 0.0f, nowMs); + + REQUIRE(usedBitrate == availableBitrate); + } + + SECTION("IncreaseLayer returns 0 on second call in same iteration") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->SetExternallyManagedBitrate(); + manager->OnTransportConnected(); + + // Feed packets so the stream has non-zero bitrate. + feedRtpStreamRecv(rtpStream.get(), buffer, bufferLength, 100); + + auto nowMs = DepLibUV::GetTimeMs(); + + // First call claims bitrate. + auto usedBitrate = manager->IncreaseLayer( + /*bitrate*/ 1000000u, /*considerLoss*/ false, /*lossPercentage*/ 0.0f, nowMs); + + REQUIRE(usedBitrate > 0u); + + // Second call in same iteration should return 0. + auto usedBitrate2 = manager->IncreaseLayer( + /*bitrate*/ 1000000u, /*considerLoss*/ false, /*lossPercentage*/ 0.0f, nowMs); + + REQUIRE(usedBitrate2 == 0u); + } + + SECTION("IncreaseLayer works again after ApplyLayers") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->SetExternallyManagedBitrate(); + manager->OnTransportConnected(); + + // Feed packets so the stream has non-zero bitrate. + feedRtpStreamRecv(rtpStream.get(), buffer, bufferLength, 100); + + auto nowMs = DepLibUV::GetTimeMs(); + + // First iteration: claim bitrate and apply. + manager->IncreaseLayer( + /*bitrate*/ 1000000u, /*considerLoss*/ false, /*lossPercentage*/ 0.0f, nowMs); + manager->ApplyLayers(/*rtpStreamActiveMs*/ 0u); + + // After ApplyLayers, IncreaseLayer should work again. + auto usedBitrate = manager->IncreaseLayer( + /*bitrate*/ 1000000u, /*considerLoss*/ false, /*lossPercentage*/ 0.0f, nowMs); + + REQUIRE(usedBitrate > 0u); + } + + SECTION("GetDesiredBitrate returns producer bitrate for video") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->SetExternallyManagedBitrate(); + manager->OnTransportConnected(); + + // Feed packets so the stream has non-zero bitrate. + feedRtpStreamRecv(rtpStream.get(), buffer, bufferLength, 100); + + auto nowMs = DepLibUV::GetTimeMs(); + auto steamBitrate = rtpStream->GetBitrate(nowMs); + auto desiredBitrate = manager->GetDesiredBitrate(nowMs); + + REQUIRE(desiredBitrate == steamBitrate); + } + + SECTION("GetDesiredBitrate returns 0 for audio kind") + { + MockListener listener; + auto manager = createManager(&listener, /*keyFrameSupported*/ false, RTC::Media::Kind::AUDIO); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->SetExternallyManagedBitrate(); + manager->OnTransportConnected(); + + // Feed packets so the stream has non-zero bitrate. + feedRtpStreamRecv(rtpStream.get(), buffer, bufferLength, 100); + + auto nowMs = DepLibUV::GetTimeMs(); + auto desiredBitrate = manager->GetDesiredBitrate(nowMs); + + REQUIRE(desiredBitrate == 0u); + } +} From 38d121c9f529aa8fcd498aaf75942bbbf86bb8a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Wed, 18 Feb 2026 12:07:36 +0100 Subject: [PATCH 07/37] clang-tidy fixes --- worker/src/RTC/Consumer.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/worker/src/RTC/Consumer.cpp b/worker/src/RTC/Consumer.cpp index 2334a570cd..bf254b2c5a 100644 --- a/worker/src/RTC/Consumer.cpp +++ b/worker/src/RTC/Consumer.cpp @@ -185,8 +185,8 @@ namespace RTC auto& encoding = this->rtpParameters.encodings[0]; // Determine keyFrameSupported. - const auto* mediaCodec = this->rtpParameters.GetCodecForEncoding(encoding); - bool keyFrameSupported = RTC::RTP::Codecs::Tools::CanBeKeyFrame(mediaCodec->mimeType); + const auto* mediaCodec = this->rtpParameters.GetCodecForEncoding(encoding); + const bool keyFrameSupported = RTC::RTP::Codecs::Tools::CanBeKeyFrame(mediaCodec->mimeType); // Build preferred layers from FBS data. RTC::ConsumerTypes::VideoLayers preferredLayers; @@ -337,10 +337,12 @@ namespace RTC preferredLayers.spatial = static_cast(encoding.spatialLayers - 1); } - if (flatbuffers::IsFieldPresent( - data->preferredLayers(), FBS::Consumer::ConsumerLayers::VT_TEMPORALLAYER)) + // preferredTemporalLayer is optional. + auto preferredTemporalLayer = data->preferredLayers()->temporalLayer(); + + if (preferredTemporalLayer) { - preferredLayers.temporal = data->preferredLayers()->temporalLayer().value(); + preferredLayers.temporal = preferredTemporalLayer.value(); if (preferredLayers.temporal > encoding.temporalLayers - 1) { From 7039bcd46a565533d99a77bd4db3773a3a0fff72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Wed, 18 Feb 2026 12:23:27 +0100 Subject: [PATCH 08/37] Enhance tests, use Factory() to create RTP Packet --- .../RTC/TestSimpleProducerStreamManager.cpp | 55 +++++-------------- 1 file changed, 15 insertions(+), 40 deletions(-) diff --git a/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp b/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp index e0cb034e52..ed68f0d02d 100644 --- a/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp +++ b/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp @@ -1,8 +1,7 @@ #include "RTC/RTP/Codecs/PayloadDescriptorHandler.hpp" -#include "RTC/RTP/Packet.hpp" #include "RTC/RTP/RtpStreamRecv.hpp" +#include "RTC/RTP/rtpCommon.hpp" #include "RTC/SimpleProducerStreamManager.hpp" -#include namespace { @@ -162,14 +161,12 @@ namespace } // Feed packets into the RtpStreamRecv so GetBitrate() returns non-zero. - void feedRtpStreamRecv(RTC::RTP::RtpStreamRecv* rtpStream, uint8_t* buffer, size_t len, uint16_t count) + void feedRtpStreamRecv(RTC::RTP::RtpStreamRecv* rtpStream, RTC::RTP::Packet* packet, uint16_t count) { - std::unique_ptr packet(RTC::RTP::Packet::Parse(buffer, len)); - - for (uint16_t seq = 1; seq <= count; ++seq) + for (uint16_t seq = packet->GetSequenceNumber() + 1; seq <= count; ++seq) { packet->SetSequenceNumber(seq); - rtpStream->ReceivePacket(packet.get()); + rtpStream->ReceivePacket(packet); } auto nowMs = DepLibUV::GetTimeMs(); @@ -185,23 +182,12 @@ namespace SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager]") { - // clang-format off - uint8_t buffer[] = - { - 0x80, 0x01, 0x00, 0x08, - 0x00, 0x00, 0x00, 0x04, - 0x49, 0x96, 0x02, 0xD2, // SSRC: 1234567890. - // Payload (4 bytes). - 0xFF, 0xFF, 0xFF, 0xFF, - // Extra buffer for cloning. - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - }; - // clang-format on + std::unique_ptr packet( + RTC::RTP::Packet::Factory(rtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer))); - const size_t packetLength{ 16 }; - const size_t bufferLength{ packetLength + 12 }; + packet->SetPayloadType(1); + packet->SetSsrc(mappedSsrc); + packet->SetPayloadLength(sizeof(rtpCommon::FactoryBuffer) - RTC::RTP::Packet::FixedHeaderMinLength); SECTION("returns BUFFER when sync required and packet is not a keyframe") { @@ -212,8 +198,6 @@ SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager]") manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); manager->OnTransportConnected(); - std::unique_ptr packet(RTC::RTP::Packet::Parse(buffer, bufferLength)); - auto result = manager->ProcessRtpPacket( packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 0, /*maxPacketTs*/ 0); @@ -228,8 +212,6 @@ SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager]") manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); manager->OnTransportConnected(); - - std::unique_ptr packet(RTC::RTP::Packet::Parse(buffer, bufferLength)); const uint16_t seq{ 100 }; packet->SetSequenceNumber(seq); @@ -257,8 +239,6 @@ SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager]") manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); manager->OnTransportConnected(); - std::unique_ptr packet(RTC::RTP::Packet::Parse(buffer, bufferLength)); - packet->SetSequenceNumber(1); packet->RemovePayload(); auto result = manager->ProcessRtpPacket( @@ -281,8 +261,6 @@ SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager]") manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); manager->OnTransportConnected(); - std::unique_ptr packet(RTC::RTP::Packet::Parse(buffer, bufferLength)); - packet->SetSequenceNumber(1); // Set a handler whose Process() returns false. @@ -305,7 +283,6 @@ SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager]") manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); manager->OnTransportConnected(); - std::unique_ptr packet(RTC::RTP::Packet::Parse(buffer, bufferLength)); const uint16_t seq{ 100 }; packet->SetSequenceNumber(seq); @@ -330,7 +307,6 @@ SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager]") manager->OnTransportConnected(); // Complete sync with first packet. - std::unique_ptr packet(RTC::RTP::Packet::Parse(buffer, bufferLength)); packet->SetSequenceNumber(1); manager->ProcessRtpPacket( @@ -392,7 +368,6 @@ SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager]") REQUIRE(listener.keyFrameRequestCount == keyFrameCount + 1); // Prove syncRequired was set: sending a non-keyframe returns BUFFER. - std::unique_ptr packet(RTC::RTP::Packet::Parse(buffer, bufferLength)); packet->SetSequenceNumber(1); auto result = manager->ProcessRtpPacket( @@ -412,7 +387,7 @@ SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager]") manager->OnTransportConnected(); // Feed packets so the stream has non-zero bitrate. - feedRtpStreamRecv(rtpStream.get(), buffer, bufferLength, 100); + feedRtpStreamRecv(rtpStream.get(), packet.get(), 100); auto nowMs = DepLibUV::GetTimeMs(); auto steamBitrate = rtpStream->GetBitrate(nowMs); @@ -433,7 +408,7 @@ SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager]") manager->OnTransportConnected(); // Feed packets so the stream has non-zero bitrate. - feedRtpStreamRecv(rtpStream.get(), buffer, bufferLength, 100); + feedRtpStreamRecv(rtpStream.get(), packet.get(), 100); auto nowMs = DepLibUV::GetTimeMs(); auto streamBitrate = rtpStream->GetBitrate(nowMs); @@ -455,7 +430,7 @@ SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager]") manager->OnTransportConnected(); // Feed packets so the stream has non-zero bitrate. - feedRtpStreamRecv(rtpStream.get(), buffer, bufferLength, 100); + feedRtpStreamRecv(rtpStream.get(), packet.get(), 100); auto nowMs = DepLibUV::GetTimeMs(); @@ -483,7 +458,7 @@ SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager]") manager->OnTransportConnected(); // Feed packets so the stream has non-zero bitrate. - feedRtpStreamRecv(rtpStream.get(), buffer, bufferLength, 100); + feedRtpStreamRecv(rtpStream.get(), packet.get(), 100); auto nowMs = DepLibUV::GetTimeMs(); @@ -510,7 +485,7 @@ SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager]") manager->OnTransportConnected(); // Feed packets so the stream has non-zero bitrate. - feedRtpStreamRecv(rtpStream.get(), buffer, bufferLength, 100); + feedRtpStreamRecv(rtpStream.get(), packet.get(), 100); auto nowMs = DepLibUV::GetTimeMs(); auto steamBitrate = rtpStream->GetBitrate(nowMs); @@ -530,7 +505,7 @@ SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager]") manager->OnTransportConnected(); // Feed packets so the stream has non-zero bitrate. - feedRtpStreamRecv(rtpStream.get(), buffer, bufferLength, 100); + feedRtpStreamRecv(rtpStream.get(), packet.get(), 100); auto nowMs = DepLibUV::GetTimeMs(); auto desiredBitrate = manager->GetDesiredBitrate(nowMs); From 1ba9d01217996249d61e0a235af2948bbfd6efe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Wed, 18 Feb 2026 13:04:19 +0100 Subject: [PATCH 09/37] feedback --- worker/test/src/RTC/TestSimpleProducerStreamManager.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp b/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp index ed68f0d02d..82ac6a03d7 100644 --- a/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp +++ b/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp @@ -2,6 +2,7 @@ #include "RTC/RTP/RtpStreamRecv.hpp" #include "RTC/RTP/rtpCommon.hpp" #include "RTC/SimpleProducerStreamManager.hpp" +#include namespace { @@ -187,7 +188,7 @@ SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager]") packet->SetPayloadType(1); packet->SetSsrc(mappedSsrc); - packet->SetPayloadLength(sizeof(rtpCommon::FactoryBuffer) - RTC::RTP::Packet::FixedHeaderMinLength); + packet->SetPayloadLength(40); SECTION("returns BUFFER when sync required and packet is not a keyframe") { From d4e26bd6dde3de58bc6ce1d8354325fb20c5bf99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Thu, 19 Feb 2026 11:22:56 +0100 Subject: [PATCH 10/37] Tests: TestSimulcastProducerStreamManager --- worker/meson.build | 1 + .../RTC/TestSimpleProducerStreamManager.cpp | 15 +- .../TestSimulcastProducerStreamManager.cpp | 1031 +++++++++++++++++ 3 files changed, 1043 insertions(+), 4 deletions(-) create mode 100644 worker/test/src/RTC/TestSimulcastProducerStreamManager.cpp diff --git a/worker/meson.build b/worker/meson.build index 4112d489a5..dc74ede893 100644 --- a/worker/meson.build +++ b/worker/meson.build @@ -411,6 +411,7 @@ test_sources = [ 'test/src/testHelpers.cpp', 'test/src/RTC/TestConsumer.cpp', 'test/src/RTC/TestSimpleProducerStreamManager.cpp', + 'test/src/RTC/TestSimulcastProducerStreamManager.cpp', 'test/src/RTC/TestKeyFrameRequestManager.cpp', 'test/src/RTC/TestNackGenerator.cpp', 'test/src/RTC/TestRateCalculator.cpp', diff --git a/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp b/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp index 82ac6a03d7..8ac86cf961 100644 --- a/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp +++ b/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp @@ -1,3 +1,4 @@ +#include "Utils.hpp" #include "RTC/RTP/Codecs/PayloadDescriptorHandler.hpp" #include "RTC/RTP/RtpStreamRecv.hpp" #include "RTC/RTP/rtpCommon.hpp" @@ -69,7 +70,7 @@ namespace class MockEncodingContext : public RTC::RTP::Codecs::EncodingContext { public: - MockEncodingContext() : RTC::RTP::Codecs::EncodingContext(this->params) + MockEncodingContext() : RTC::RTP::Codecs::EncodingContext(MockEncodingContext::params) { } void SyncRequired() override @@ -77,9 +78,12 @@ namespace } private: - RTC::RTP::Codecs::EncodingContext::Params params; + static RTC::RTP::Codecs::EncodingContext::Params params; }; + // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) + RTC::RTP::Codecs::EncodingContext::Params MockEncodingContext::params; + class MockPayloadDescriptorHandler : public RTC::RTP::Codecs::PayloadDescriptorHandler { public: @@ -164,7 +168,10 @@ namespace // Feed packets into the RtpStreamRecv so GetBitrate() returns non-zero. void feedRtpStreamRecv(RTC::RTP::RtpStreamRecv* rtpStream, RTC::RTP::Packet* packet, uint16_t count) { - for (uint16_t seq = packet->GetSequenceNumber() + 1; seq <= count; ++seq) + auto firstSeq = static_cast(packet->GetSequenceNumber() + 1); + auto lastSeq = static_cast(firstSeq + count); + + for (uint16_t seq = firstSeq; Utils::Number::IsLowerThan(seq, lastSeq); ++seq) { packet->SetSequenceNumber(seq); rtpStream->ReceivePacket(packet); @@ -181,7 +188,7 @@ namespace } } // namespace -SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager]") +SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager][simple]") { std::unique_ptr packet( RTC::RTP::Packet::Factory(rtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer))); diff --git a/worker/test/src/RTC/TestSimulcastProducerStreamManager.cpp b/worker/test/src/RTC/TestSimulcastProducerStreamManager.cpp new file mode 100644 index 0000000000..e16f434759 --- /dev/null +++ b/worker/test/src/RTC/TestSimulcastProducerStreamManager.cpp @@ -0,0 +1,1031 @@ +#include "Utils.hpp" +#include "RTC/RTCP/SenderReport.hpp" +#include "RTC/RTP/Codecs/PayloadDescriptorHandler.hpp" +#include "RTC/RTP/RtpStreamRecv.hpp" +#include "RTC/RTP/rtpCommon.hpp" +#include "RTC/SimulcastProducerStreamManager.hpp" + +namespace +{ + // NOLINTBEGIN(readability-identifier-naming) + constexpr uint32_t mappedSsrc0 = 1000; + constexpr uint32_t mappedSsrc1 = 2000; + constexpr uint32_t mappedSsrc2 = 3000; + + const std::vector threeSsrcs = { mappedSsrc0, mappedSsrc1, mappedSsrc2 }; + const std::vector twoSsrcs = { mappedSsrc0, mappedSsrc1 }; + const std::vector oneSsrc = { mappedSsrc0 }; + // NOLINTEND(readability-identifier-naming) + + class MockListener : public RTC::ProducerStreamManager::Listener + { + public: + bool IsActive() const override + { + return this->isActive; + } + + void OnProducerStreamManagerKeyFrameRequested(uint32_t mappedSsrc) override + { + this->keyFrameRequestCount++; + this->lastKeyFrameRequestedMappedSsrc = mappedSsrc; + } + + void OnProducerStreamManagerNeedBitrateChange() override + { + this->needBitrateChangeCount++; + } + + void OnProducerStreamManagerLayersChanged() override + { + this->layersChangedCount++; + } + + void OnProducerStreamManagerClearRetransmissionBuffer() override + { + } + + void OnProducerStreamManagerScore() override + { + } + + public: + bool isActive{ true }; + int keyFrameRequestCount{ 0 }; + uint32_t lastKeyFrameRequestedMappedSsrc{ 0 }; + int layersChangedCount{ 0 }; + int needBitrateChangeCount{ 0 }; + }; + + class RtpStreamRecvListener : public RTC::RTP::RtpStreamRecv::Listener + { + public: + void OnRtpStreamScore( + RTC::RTP::RtpStream* /*rtpStream*/, uint8_t /*score*/, uint8_t /*previousScore*/) override + { + } + + void OnRtpStreamSendRtcpPacket( + RTC::RTP::RtpStreamRecv* /*rtpStream*/, RTC::RTCP::Packet* /*packet*/) override + { + } + + void OnRtpStreamNeedWorstRemoteFractionLost( + RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint8_t& /*worstRemoteFractionLost*/) override + { + } + }; + + class MockEncodingContext : public RTC::RTP::Codecs::EncodingContext + { + public: + MockEncodingContext() : RTC::RTP::Codecs::EncodingContext(MockEncodingContext::params) + { + } + void SyncRequired() override + { + } + + private: + static RTC::RTP::Codecs::EncodingContext::Params params; + }; + + // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) + RTC::RTP::Codecs::EncodingContext::Params MockEncodingContext::params; + + class MockPayloadDescriptorHandler : public RTC::RTP::Codecs::PayloadDescriptorHandler + { + public: + explicit MockPayloadDescriptorHandler() = default; + ~MockPayloadDescriptorHandler() override = default; + void Dump(int /*indentation*/) const override + { + } + bool Process( + RTC::RTP::Codecs::EncodingContext* /*context*/, + RTC::RTP::Packet* /*packet*/, + bool& /*marker*/) override + { + return this->processResult; + } + void RtpPacketChanged(RTC::RTP::Packet* /*packet*/) override + { + } + std::unique_ptr GetEncoder() const override + { + return nullptr; + } + void Encode( + RTC::RTP::Packet* /*packet*/, RTC::RTP::Codecs::PayloadDescriptor::Encoder* /*encoder*/) override + { + } + void Restore(RTC::RTP::Packet* /*packet*/) override + { + } + uint8_t GetSpatialLayer() const override + { + return 0; + } + uint8_t GetTemporalLayer() const override + { + return 0; + } + bool IsKeyFrame() const override + { + return this->isKeyFrame; + } + + public: + bool isKeyFrame{ false }; + bool processResult{ true }; + }; + + std::unique_ptr createManager( + MockListener* listener, + const std::vector& ssrcs = threeSsrcs, + RTC::ConsumerTypes::VideoLayers preferredLayers = { 2, 2 }, + bool keyFrameSupported = true, + RTC::Media::Kind kind = RTC::Media::Kind::VIDEO, + std::unique_ptr encodingContext = nullptr) + { + std::vector consumableRtpEncodings; + + for (auto ssrc : ssrcs) + { + RTC::RtpEncodingParameters encoding; + encoding.ssrc = ssrc; + consumableRtpEncodings.push_back(encoding); + } + + if (!encodingContext) + { + encodingContext = std::make_unique(); + } + + return std::make_unique( + consumableRtpEncodings, + preferredLayers, + std::move(encodingContext), + kind, + keyFrameSupported, + listener); + } + + // RtpStreamRecvListener must outlive the RtpStreamRecv. + RtpStreamRecvListener streamRecvListener; // NOLINT(readability-identifier-naming) + + std::unique_ptr createRtpStreamRecv(uint32_t ssrc) + { + RTC::RTP::RtpStream::Params params; + + params.ssrc = ssrc; + params.clockRate = 90000; + + return std::make_unique(&streamRecvListener, params, 0u, false); + } + + // Feed packets into the RtpStreamRecv so GetBitrate() returns non-zero. + void feedRtpStreamRecv(RTC::RTP::RtpStreamRecv* rtpStream, RTC::RTP::Packet* packet, uint16_t count) + { + auto firstSeq = static_cast(packet->GetSequenceNumber() + 1); + auto lastSeq = static_cast(firstSeq + count); + + for (uint16_t seq = firstSeq; Utils::Number::IsLowerThan(seq, lastSeq); ++seq) + { + packet->SetSequenceNumber(seq); + rtpStream->ReceivePacket(packet); + } + + auto nowMs = DepLibUV::GetTimeMs(); + + // bitrate (bps) = totalBytes * 8000 / windowSizeMs. + // windowSizeMs for RtpStreamRecv is 2500. + auto expectedBitrate = + static_cast(std::trunc((count * packet->GetLength() * 8000.0f / 2500) + 0.5f)); + + REQUIRE(rtpStream->GetBitrate(nowMs) == expectedBitrate); + } +} // namespace + +SCENARIO("SimulcastProducerStreamManager", "[rtp][producer-stream-manager][simulcast]") +{ + std::unique_ptr packet( + RTC::RTP::Packet::Factory(rtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer))); + + packet->SetPayloadType(1); + packet->SetSsrc(mappedSsrc0); + packet->SetTimestamp(1000); + packet->SetPayloadLength(40); + + SECTION("ProcessRtpPacket returns DROP when target temporal layer is -1") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream0 = createRtpStreamRecv(mappedSsrc0); + + manager->ProducerRtpStream(rtpStream0.get(), mappedSsrc0); + + // Set target layer to 0, complete sync. + manager->UpdateTargetLayers(0, 0); + + packet->SetSsrc(mappedSsrc0); + packet->SetSequenceNumber(1); + + auto* handler = new MockPayloadDescriptorHandler(); + handler->isKeyFrame = true; + packet->SetPayloadDescriptorHandler(handler); + + manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + // Reset target layers to -1,-1 (simulates pause/disconnect). + manager->UpdateTargetLayers(-1, -1); + + // Packet from previous current layer should be dropped. + packet->SetSequenceNumber(2); + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::SILENT_DROP); + } + + SECTION( + "ProcessRtpPacket returns BUFFER when sync required and packet is from target spatial layer but not keyframe") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream0 = createRtpStreamRecv(mappedSsrc0); + + manager->ProducerRtpStream(rtpStream0.get(), mappedSsrc0); + + // Set target layer to 0. This sets syncRequired since target != current. + manager->UpdateTargetLayers(0, 0); + + packet->SetSsrc(mappedSsrc0); + packet->SetSequenceNumber(1); + + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::BUFFER); + } + + SECTION("ProcessRtpPacket requires keyframe for spatial layer switch") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream0 = createRtpStreamRecv(mappedSsrc0); + auto rtpStream1 = createRtpStreamRecv(mappedSsrc1); + + manager->ProducerRtpStream(rtpStream0.get(), mappedSsrc0); + manager->ProducerRtpStream(rtpStream1.get(), mappedSsrc1); + + // Set target layer to 0 and complete sync. + manager->UpdateTargetLayers(0, 0); + + packet->SetSsrc(mappedSsrc0); + packet->SetSequenceNumber(1); + + auto* handler0 = new MockPayloadDescriptorHandler(); + handler0->isKeyFrame = true; + packet->SetPayloadDescriptorHandler(handler0); + + manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + // Now switch target layer to 1. + manager->UpdateTargetLayers(1, 0); + + // Send non-keyframe from layer 1 — should be buffered. + packet->SetSsrc(mappedSsrc1); + packet->SetSequenceNumber(1); + + // Explicitly set a non-keyframe handler. + auto* handler1 = new MockPayloadDescriptorHandler(); + handler1->isKeyFrame = false; + packet->SetPayloadDescriptorHandler(handler1); + + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::BUFFER); + + // Send keyframe from layer 1 — should switch and forward. + packet->SetSequenceNumber(2); + + auto* handler2 = new MockPayloadDescriptorHandler(); + handler2->isKeyFrame = true; + packet->SetPayloadDescriptorHandler(handler2); + + result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result.isSyncPacket == true); + REQUIRE(result.spatialLayerSwitched == true); + REQUIRE(result.sendBufferedPackets == true); + REQUIRE(manager->GetCurrentSpatialLayer() == 1); + } + + SECTION("ProcessRtpPacket returns SILENT_DROP for non-target and non-current spatial layer packet") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream0 = createRtpStreamRecv(mappedSsrc0); + + manager->ProducerRtpStream(rtpStream0.get(), mappedSsrc0); + + // Set target to spatial layer 0. + manager->UpdateTargetLayers(0, 0); + + // Sync on layer 0 with a keyframe. + packet->SetSsrc(mappedSsrc0); + packet->SetSequenceNumber(1); + + auto* handler0 = new MockPayloadDescriptorHandler(); + handler0->isKeyFrame = true; + packet->SetPayloadDescriptorHandler(handler0); + + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::FORWARD); + + // Now send a packet from spatial layer 1 — should be silently dropped. + packet->SetSsrc(mappedSsrc1); + packet->SetSequenceNumber(1); + + result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::SILENT_DROP); + } + + SECTION( + "ProcessRtpPacket returns SILENT_DROP when sync required and packet is from non-target spatial layer") + { + MockListener listener; + auto manager = createManager(&listener, /*ssrcs*/ twoSsrcs); + auto rtpStream0 = createRtpStreamRecv(mappedSsrc0); + auto rtpStream1 = createRtpStreamRecv(mappedSsrc1); + + manager->ProducerRtpStream(rtpStream0.get(), mappedSsrc0); + manager->ProducerRtpStream(rtpStream1.get(), mappedSsrc1); + + // Set target layer to 0. This sets syncRequired since target != current. + manager->UpdateTargetLayers(0, 0); + + // Send a packet from layer 1 (non-target) while sync is pending. + packet->SetSsrc(mappedSsrc1); + packet->SetSequenceNumber(1); + + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::SILENT_DROP); + } + + SECTION("ProcessRtpPacket returns FORWARD with sendBufferedPackets when syncing with a keyframe") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream0 = createRtpStreamRecv(mappedSsrc0); + + manager->ProducerRtpStream(rtpStream0.get(), mappedSsrc0); + manager->UpdateTargetLayers(0, 0); + + const uint16_t seq{ 100 }; + + packet->SetSsrc(mappedSsrc0); + packet->SetSequenceNumber(seq); + + auto* handler = new MockPayloadDescriptorHandler(); + handler->isKeyFrame = true; + packet->SetPayloadDescriptorHandler(handler); + + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result.isSyncPacket == true); + REQUIRE(result.sendBufferedPackets == true); + } + + SECTION("ProcessRtpPacket returns FORWARD and completes sync when keyFrameSupported is false") + { + MockListener listener; + auto manager = createManager( + &listener, + /*ssrcs*/ threeSsrcs, + /*preferredLayers*/ { 2, 2 }, + /*keyFrameSupported*/ false); + auto rtpStream0 = createRtpStreamRecv(mappedSsrc0); + + manager->ProducerRtpStream(rtpStream0.get(), mappedSsrc0); + manager->UpdateTargetLayers(0, 0); + + const uint16_t seq{ 100 }; + + packet->SetSsrc(mappedSsrc0); + packet->SetSequenceNumber(seq); + + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result.isSyncPacket == true); + REQUIRE(result.tsOffset == 0u); + } + + SECTION("ProcessRtpPacket returns DROP for empty payload packets on current spatial layer") + { + MockListener listener; + auto manager = createManager( + &listener, + /*ssrcs*/ threeSsrcs, + /*preferredLayers*/ { 2, 2 }, + /*keyFrameSupported*/ false); + auto rtpStream0 = createRtpStreamRecv(mappedSsrc0); + + manager->ProducerRtpStream(rtpStream0.get(), mappedSsrc0); + manager->UpdateTargetLayers(0, 0); + + // Complete sync. + packet->SetSsrc(mappedSsrc0); + packet->SetSequenceNumber(1); + manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + // Send empty payload packet on current layer. + packet->SetSequenceNumber(2); + packet->RemovePayload(); + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::DROP); + } + + SECTION("ProcessRtpPacket returns SILENT_DROP for empty payload packets on non-current spatial layer") + { + MockListener listener; + auto manager = createManager( + &listener, + /*ssrcs*/ threeSsrcs, + /*preferredLayers*/ { 2, 2 }, + /*keyFrameSupported*/ false); + auto rtpStream0 = createRtpStreamRecv(mappedSsrc0); + + manager->ProducerRtpStream(rtpStream0.get(), mappedSsrc0); + manager->UpdateTargetLayers(0, 0); + + // Complete sync on layer 0. + packet->SetSsrc(mappedSsrc0); + packet->SetSequenceNumber(1); + manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + // Send empty payload packet on non-current layer. + packet->SetSsrc(mappedSsrc1); + packet->SetSequenceNumber(1); + packet->RemovePayload(); + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::SILENT_DROP); + } + + SECTION("ProcessRtpPacket returns FORWARD for current layer packets after sync") + { + MockListener listener; + auto manager = createManager( + &listener, + /*ssrcs*/ threeSsrcs, + /*preferredLayers*/ { 2, 2 }, + /*keyFrameSupported*/ false); + auto rtpStream0 = createRtpStreamRecv(mappedSsrc0); + + manager->ProducerRtpStream(rtpStream0.get(), mappedSsrc0); + manager->UpdateTargetLayers(0, 0); + + // Complete sync with first packet. + packet->SetSsrc(mappedSsrc0); + packet->SetSequenceNumber(1); + manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + // Send a second normal packet. + packet->SetSequenceNumber(2); + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result.isSyncPacket == false); + REQUIRE(result.sendBufferedPackets == false); + REQUIRE(result.tsOffset == 0u); + } + + SECTION("ProcessRtpPacket forwards packets from current layer after spatial switch") + { + MockListener listener; + auto manager = createManager( + &listener, + /*ssrcs*/ twoSsrcs, + /*preferredLayers*/ { 1, 0 }, + /*keyFrameSupported*/ false); + auto rtpStream0 = createRtpStreamRecv(mappedSsrc0); + auto rtpStream1 = createRtpStreamRecv(mappedSsrc1); + + manager->ProducerRtpStream(rtpStream0.get(), mappedSsrc0); + manager->ProducerRtpStream(rtpStream1.get(), mappedSsrc1); + + // Set target layer to 0 and sync. + manager->UpdateTargetLayers(0, 0); + + packet->SetSsrc(mappedSsrc0); + packet->SetSequenceNumber(1); + manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(manager->GetCurrentSpatialLayer() == 0); + + // Switch to layer 1 — keyFrameSupported=false so first packet syncs. + manager->UpdateTargetLayers(1, 0); + + packet->SetSsrc(mappedSsrc1); + packet->SetSequenceNumber(1); + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result.spatialLayerSwitched == true); + REQUIRE(manager->GetCurrentSpatialLayer() == 1); + + // Subsequent packets on layer 1 should be forwarded normally. + packet->SetSequenceNumber(2); + result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result.spatialLayerSwitched == false); + + // Packets from old layer 0 should be silently dropped. + packet->SetSsrc(mappedSsrc0); + packet->SetSequenceNumber(2); + result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::SILENT_DROP); + } + + SECTION("OnTransportConnected sets syncRequired") + { + MockListener listener; + auto manager = createManager( + &listener, + /*ssrcs*/ oneSsrc, + /*preferredLayers*/ { 0, 0 }, + /*keyFrameSupported*/ false); + auto rtpStream0 = createRtpStreamRecv(mappedSsrc0); + + manager->ProducerRtpStream(rtpStream0.get(), mappedSsrc0); + + // Give score > 0 so RecalculateTargetLayers keeps target at layer 0. + manager->ProducerRtpStreamScore(rtpStream0.get(), /*score*/ 7, /*previousScore*/ 0); + + // Set target and sync on layer 0. + manager->UpdateTargetLayers(0, 0); + + packet->SetSsrc(mappedSsrc0); + packet->SetSequenceNumber(1); + manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + // Verify we are synced: normal packet is forwarded without sync flag. + packet->SetSequenceNumber(2); + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result.isSyncPacket == false); + + // Now OnTransportConnected resets syncRequired. + manager->OnTransportConnected(); + + // Next packet should be a sync packet. + packet->SetSequenceNumber(3); + result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result.isSyncPacket == true); + } + + SECTION("OnTransportConnected requests keyframe when active and target changes") + { + MockListener listener; + auto manager = createManager(&listener, /*ssrcs*/ twoSsrcs, /*preferredLayers*/ { 1, 0 }); + auto rtpStream0 = createRtpStreamRecv(mappedSsrc0); + auto rtpStream1 = createRtpStreamRecv(mappedSsrc1); + + manager->ProducerRtpStream(rtpStream0.get(), mappedSsrc0); + manager->ProducerRtpStream(rtpStream1.get(), mappedSsrc1); + + // Both streams have score > 0 so RecalculateTargetLayers can pick layers. + manager->ProducerRtpStreamScore(rtpStream0.get(), /*score*/ 7, /*previousScore*/ 0); + manager->ProducerRtpStreamScore(rtpStream1.get(), /*score*/ 7, /*previousScore*/ 0); + + // Sync on layer 1 with a keyframe. + manager->UpdateTargetLayers(1, 0); + + packet->SetSsrc(mappedSsrc1); + packet->SetSequenceNumber(1); + + auto* handler = new MockPayloadDescriptorHandler(); + handler->isKeyFrame = true; + packet->SetPayloadDescriptorHandler(handler); + + manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + // current=1, target=1. Disconnect resets everything to -1. + manager->OnTransportDisconnected(); + + auto keyFrameCountBefore = listener.keyFrameRequestCount; + + // Reconnect: RecalculateTargetLayers picks preferred layer 1, + // which differs from current (-1), so a keyframe is requested. + manager->OnTransportConnected(); + + REQUIRE(listener.keyFrameRequestCount > keyFrameCountBefore); + } + + SECTION("OnTransportConnected does not request keyframe when not active") + { + MockListener listener; + listener.isActive = false; + + auto manager = createManager(&listener); + + // Don't wire producerRtpStream — manager is not active. + manager->OnTransportConnected(); + + REQUIRE(listener.keyFrameRequestCount == 0); + } + + SECTION("RecalculateTargetLayers skips spatial layer without Sender Report") + { + MockListener listener; + auto manager = createManager(&listener, /*ssrcs*/ twoSsrcs, /*preferredLayers*/ { 1, 0 }); + auto rtpStream0 = createRtpStreamRecv(mappedSsrc0); + auto rtpStream1 = createRtpStreamRecv(mappedSsrc1); + + manager->ProducerRtpStream(rtpStream0.get(), mappedSsrc0); + manager->ProducerRtpStream(rtpStream1.get(), mappedSsrc1); + + // Set target layer to 0. This sets tsReferenceSpatialLayer = 0. + manager->UpdateTargetLayers(0, 0); + + // Sync on layer 0 with a keyframe so current = 0. + packet->SetSsrc(mappedSsrc0); + packet->SetSequenceNumber(1); + + auto* handler = new MockPayloadDescriptorHandler(); + handler->isKeyFrame = true; + packet->SetPayloadDescriptorHandler(handler); + + manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(manager->GetCurrentSpatialLayer() == 0); + REQUIRE(manager->GetTargetLayers().temporal == 0); + + // Give both streams score > 0. RecalculateTargetLayers will be called + // but CanSwitchToSpatialLayer(1) returns false because layer 1 has no + // Sender Report and tsReferenceSpatialLayer is 0 (not -1). + manager->ProducerRtpStreamScore(rtpStream0.get(), /*score*/ 7, /*previousScore*/ 0); + + REQUIRE(manager->GetTargetLayers().spatial == 0); + REQUIRE(manager->GetTargetLayers().temporal == 0); + + manager->ProducerRtpStreamScore(rtpStream1.get(), /*score*/ 7, /*previousScore*/ 0); + + // Despite preferred being layer 1, target stays at layer 0. + REQUIRE(manager->GetTargetLayers().spatial == 0); + REQUIRE(manager->GetTargetLayers().temporal == 0); + + // Verify packets on layer 0 are still forwarded with tsOffset 0. + packet->SetSsrc(mappedSsrc0); + packet->SetSequenceNumber(2); + + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result.tsOffset == 0u); + } + + SECTION("RecalculateTargetLayers switches to preferred layer after Sender Report") + { + MockListener listener; + auto manager = createManager(&listener, /*ssrcs*/ twoSsrcs, /*preferredLayers*/ { 1, 0 }); + auto rtpStream0 = createRtpStreamRecv(mappedSsrc0); + auto rtpStream1 = createRtpStreamRecv(mappedSsrc1); + + manager->ProducerRtpStream(rtpStream0.get(), mappedSsrc0); + manager->ProducerRtpStream(rtpStream1.get(), mappedSsrc1); + + // Set target layer to 0. This sets tsReferenceSpatialLayer = 0. + manager->UpdateTargetLayers(0, 0); + + // Sync on layer 0 with a keyframe so current = 0. + packet->SetSsrc(mappedSsrc0); + packet->SetSequenceNumber(1); + + auto* handler = new MockPayloadDescriptorHandler(); + handler->isKeyFrame = true; + packet->SetPayloadDescriptorHandler(handler); + + manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + // Target stays at 0 because layer 1 has no Sender Report. + REQUIRE(manager->GetTargetLayers().spatial == 0); + + // Feed packets to both streams so ReceiveRtcpSenderReport's UpdateScore + // doesn't drop the score to 0. + packet->SetSsrc(mappedSsrc0); + feedRtpStreamRecv(rtpStream0.get(), packet.get(), 10); + + packet->SetSsrc(mappedSsrc1); + feedRtpStreamRecv(rtpStream1.get(), packet.get(), 10); + + // Provide Sender Reports for both streams. + RTC::RTCP::SenderReport sr0; + sr0.SetSsrc(mappedSsrc0); + sr0.SetNtpSec(1000); + sr0.SetNtpFrac(0); + sr0.SetRtpTs(90000); + rtpStream0->ReceiveRtcpSenderReport(&sr0); + + RTC::RTCP::SenderReport sr1; + sr1.SetSsrc(mappedSsrc1); + sr1.SetNtpSec(1000); + sr1.SetNtpFrac(0); + sr1.SetRtpTs(90000); + rtpStream1->ReceiveRtcpSenderReport(&sr1); + + // Notify the manager about the first Sender Report for layer 1. + // This triggers MayChangeLayers → RecalculateTargetLayers. + // Now CanSwitchToSpatialLayer(1) returns true, so preferred layer 1 is picked. + manager->ProducerRtcpSenderReport(rtpStream1.get(), /*first*/ true); + + REQUIRE(manager->GetTargetLayers().spatial == 1); + + // Send a keyframe from layer 1 to complete the spatial switch. + packet->SetSsrc(mappedSsrc1); + packet->SetSequenceNumber(100); + + auto* handler1 = new MockPayloadDescriptorHandler(); + handler1->isKeyFrame = true; + packet->SetPayloadDescriptorHandler(handler1); + + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result.isSyncPacket == true); + REQUIRE(result.spatialLayerSwitched == true); + // Both SRs have the same NTP/RTP values, so tsOffset is 0. + REQUIRE(result.tsOffset == 0u); + REQUIRE(manager->GetCurrentSpatialLayer() == 1); + } + + SECTION("OnResumed sets syncRequired") + { + MockListener listener; + auto manager = createManager( + &listener, + /*ssrcs*/ threeSsrcs, + /*preferredLayers*/ { 2, 2 }, + /*keyFrameSupported*/ false); + auto rtpStream0 = createRtpStreamRecv(mappedSsrc0); + + manager->ProducerRtpStream(rtpStream0.get(), mappedSsrc0); + + // Set target and sync on layer 0. + manager->UpdateTargetLayers(0, 0); + + packet->SetSsrc(mappedSsrc0); + packet->SetSequenceNumber(1); + manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + // Verify we are synced: normal packet is forwarded without sync flag. + packet->SetSequenceNumber(2); + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result.isSyncPacket == false); + + // Call OnResumed — should set syncRequired. + manager->OnResumed(); + + // Reset target since OnResumed may have reset it via MayChangeLayers. + manager->UpdateTargetLayers(0, 0); + + // Prove syncRequired was set: next packet is a sync packet. + packet->SetSequenceNumber(3); + result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result.isSyncPacket == true); + } + + SECTION("OnTransportDisconnected resets target layers") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream0 = createRtpStreamRecv(mappedSsrc0); + + manager->ProducerRtpStream(rtpStream0.get(), mappedSsrc0); + manager->UpdateTargetLayers(0, 0); + + manager->OnTransportDisconnected(); + + auto targetLayers = manager->GetTargetLayers(); + + REQUIRE(targetLayers.spatial == -1); + REQUIRE(targetLayers.temporal == -1); + } + + SECTION("OnPaused resets target layers") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream0 = createRtpStreamRecv(mappedSsrc0); + + manager->ProducerRtpStream(rtpStream0.get(), mappedSsrc0); + manager->UpdateTargetLayers(0, 0); + + manager->OnPaused(); + + auto targetLayers = manager->GetTargetLayers(); + + REQUIRE(targetLayers.spatial == -1); + REQUIRE(targetLayers.temporal == -1); + } + + SECTION("IncreaseLayer returns 0 on second call in same iteration") + { + MockListener listener; + auto manager = createManager(&listener, /*ssrcs*/ oneSsrc, /*preferredLayers*/ { 0, 0 }); + auto rtpStream0 = createRtpStreamRecv(mappedSsrc0); + + manager->ProducerRtpStream(rtpStream0.get(), mappedSsrc0); + manager->SetExternallyManagedBitrate(); + + // Feed packets so the stream has non-zero bitrate. + packet->SetSsrc(mappedSsrc0); + feedRtpStreamRecv(rtpStream0.get(), packet.get(), 100); + + auto nowMs = DepLibUV::GetTimeMs(); + + // First call claims bitrate. + auto usedBitrate = manager->IncreaseLayer( + /*bitrate*/ 1000000u, /*considerLoss*/ false, /*lossPercentage*/ 0.0f, nowMs); + + REQUIRE(usedBitrate > 0u); + + // Second call in same iteration should return 0 (already at preferred layers). + auto usedBitrate2 = manager->IncreaseLayer( + /*bitrate*/ 1000000u, /*considerLoss*/ false, /*lossPercentage*/ 0.0f, nowMs); + + REQUIRE(usedBitrate2 == 0u); + } + + SECTION("IncreaseLayer works again after ApplyLayers") + { + MockListener listener; + auto manager = createManager(&listener, /*ssrcs*/ twoSsrcs, /*preferredLayers*/ { 1, 0 }); + auto rtpStream0 = createRtpStreamRecv(mappedSsrc0); + auto rtpStream1 = createRtpStreamRecv(mappedSsrc1); + + manager->ProducerRtpStream(rtpStream0.get(), mappedSsrc0); + manager->ProducerRtpStream(rtpStream1.get(), mappedSsrc1); + manager->SetExternallyManagedBitrate(); + + // Feed packets to layer 0. + packet->SetSsrc(mappedSsrc0); + feedRtpStreamRecv(rtpStream0.get(), packet.get(), 100); + + auto nowMs = DepLibUV::GetTimeMs(); + + // First iteration: claim layer 0. + manager->IncreaseLayer( + /*bitrate*/ 1000000u, /*considerLoss*/ false, /*lossPercentage*/ 0.0f, nowMs); + manager->ApplyLayers(/*rtpStreamActiveMs*/ 0u); + + // Feed packets to layer 1. + packet->SetSsrc(mappedSsrc1); + feedRtpStreamRecv(rtpStream1.get(), packet.get(), 100); + + // After ApplyLayers, IncreaseLayer should work again for layer 1. + auto usedBitrate = manager->IncreaseLayer( + /*bitrate*/ 1000000u, /*considerLoss*/ false, /*lossPercentage*/ 0.0f, nowMs); + + REQUIRE(usedBitrate > 0u); + } + + SECTION("GetDesiredBitrate returns max bitrate across all producer streams") + { + MockListener listener; + auto manager = createManager(&listener, /*ssrcs*/ twoSsrcs, /*preferredLayers*/ { 1, 0 }); + auto rtpStream0 = createRtpStreamRecv(mappedSsrc0); + auto rtpStream1 = createRtpStreamRecv(mappedSsrc1); + + manager->ProducerRtpStream(rtpStream0.get(), mappedSsrc0); + manager->ProducerRtpStream(rtpStream1.get(), mappedSsrc1); + manager->SetExternallyManagedBitrate(); + + // Feed packets to both streams with different counts to get different bitrates. + packet->SetSsrc(mappedSsrc0); + feedRtpStreamRecv(rtpStream0.get(), packet.get(), 50); + + packet->SetSsrc(mappedSsrc1); + feedRtpStreamRecv(rtpStream1.get(), packet.get(), 100); + + auto nowMs = DepLibUV::GetTimeMs(); + auto bitrate0 = rtpStream0->GetBitrate(nowMs); + auto bitrate1 = rtpStream1->GetBitrate(nowMs); + + REQUIRE(bitrate1 > bitrate0); + + auto desiredBitrate = manager->GetDesiredBitrate(nowMs); + + REQUIRE(desiredBitrate == bitrate1); + } + + SECTION("GetProducerCurrentRtpStream returns nullptr before sync") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream0 = createRtpStreamRecv(mappedSsrc0); + + manager->ProducerRtpStream(rtpStream0.get(), mappedSsrc0); + + REQUIRE(manager->GetProducerCurrentRtpStream() == nullptr); + REQUIRE(manager->GetCurrentSpatialLayer() == -1); + } + + SECTION("GetProducerTargetRtpStream returns correct stream after UpdateTargetLayers") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream0 = createRtpStreamRecv(mappedSsrc0); + auto rtpStream1 = createRtpStreamRecv(mappedSsrc1); + + manager->ProducerRtpStream(rtpStream0.get(), mappedSsrc0); + manager->ProducerRtpStream(rtpStream1.get(), mappedSsrc1); + + manager->UpdateTargetLayers(1, 0); + + REQUIRE(manager->GetProducerTargetRtpStream() == rtpStream1.get()); + REQUIRE(manager->GetTargetLayers().spatial == 1); + REQUIRE(manager->GetTargetLayers().temporal == 0); + } + + SECTION("RequestKeyFrame requests for both target and current spatial layers") + { + MockListener listener; + auto manager = createManager( + &listener, + /*ssrcs*/ twoSsrcs, + /*preferredLayers*/ { 1, 0 }, + /*keyFrameSupported*/ false); + auto rtpStream0 = createRtpStreamRecv(mappedSsrc0); + auto rtpStream1 = createRtpStreamRecv(mappedSsrc1); + + manager->ProducerRtpStream(rtpStream0.get(), mappedSsrc0); + manager->ProducerRtpStream(rtpStream1.get(), mappedSsrc1); + + // Set target and sync on layer 0. + manager->UpdateTargetLayers(0, 0); + + packet->SetSsrc(mappedSsrc0); + packet->SetSequenceNumber(1); + manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + // Change target layer to 1 (current stays at 0 until keyframe). + manager->UpdateTargetLayers(1, 0); + + listener.keyFrameRequestCount = 0; + + // RequestKeyFrame should request for both target (1) and current (0). + manager->RequestKeyFrame(); + + REQUIRE(listener.keyFrameRequestCount == 2); + } +} From f4b97b51df5b553c9eefef36d6a655a4af297c96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Fri, 20 Mar 2026 15:50:54 +0100 Subject: [PATCH 11/37] TestSvcProducerStreamManager --- worker/meson.build | 1 + .../RTC/TestSimpleProducerStreamManager.cpp | 30 +- .../TestSimulcastProducerStreamManager.cpp | 51 +- .../src/RTC/TestSvcProducerStreamManager.cpp | 1270 +++++++++++++++++ 4 files changed, 1312 insertions(+), 40 deletions(-) create mode 100644 worker/test/src/RTC/TestSvcProducerStreamManager.cpp diff --git a/worker/meson.build b/worker/meson.build index c52be8482a..458df2f1b9 100644 --- a/worker/meson.build +++ b/worker/meson.build @@ -420,6 +420,7 @@ test_sources = [ 'test/src/RTC/TestConsumer.cpp', 'test/src/RTC/TestSimpleProducerStreamManager.cpp', 'test/src/RTC/TestSimulcastProducerStreamManager.cpp', + 'test/src/RTC/TestSvcProducerStreamManager.cpp', 'test/src/RTC/TestKeyFrameRequestManager.cpp', 'test/src/RTC/TestNackGenerator.cpp', 'test/src/RTC/TestRateCalculator.cpp', diff --git a/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp b/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp index 8ac86cf961..c1c73deecf 100644 --- a/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp +++ b/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp @@ -260,10 +260,10 @@ SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager][simple]" MockListener listener; auto encodingContext = std::make_unique(); auto manager = createManager( - &listener, - /*keyFrameSupported*/ false, - RTC::Media::Kind::VIDEO, - std::move(encodingContext)); + &listener, + /*keyFrameSupported*/ false, + RTC::Media::Kind::VIDEO, + std::move(encodingContext)); auto rtpStream = createRtpStreamRecv(); manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); @@ -331,7 +331,7 @@ SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager][simple]" REQUIRE(result.tsOffset == 0u); } - SECTION("OnTransportConnected requests keyframe when active") + SECTION("OnTransportConnected() requests keyframe when active") { MockListener listener; auto manager = createManager(&listener); @@ -346,7 +346,7 @@ SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager][simple]" REQUIRE(listener.lastKeyFrameRequestedMappedSsrc == mappedSsrc); } - SECTION("OnTransportConnected does not request keyframe when not active") + SECTION("OnTransportConnected() does not request keyframe when not active") { MockListener listener; listener.isActive = false; @@ -359,7 +359,7 @@ SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager][simple]" REQUIRE(listener.keyFrameRequestCount == 0); } - SECTION("OnResumed sets syncRequired and requests keyframe") + SECTION("OnResumed() sets syncRequired and requests keyframe") { MockListener listener; auto manager = createManager(&listener); @@ -384,7 +384,7 @@ SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager][simple]" REQUIRE(result.type == RtpPacketProcessResult::Type::BUFFER); } - SECTION("IncreaseLayer returns producer bitrate when it is less than available bitrate") + SECTION("IncreaseLayer() returns producer bitrate when it is less than available bitrate") { MockListener listener; auto manager = createManager(&listener); @@ -400,12 +400,12 @@ SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager][simple]" auto nowMs = DepLibUV::GetTimeMs(); auto steamBitrate = rtpStream->GetBitrate(nowMs); auto usedBitrate = manager->IncreaseLayer( - /*bitrate*/ steamBitrate + 1u, /*considerLoss*/ false, /*lossPercentage*/ 0.0f, nowMs); + /*bitrate*/ steamBitrate + 1u, /*considerLoss*/ false, /*lossPercentage*/ 0.0f, nowMs); REQUIRE(usedBitrate == steamBitrate); } - SECTION("IncreaseLayer returns available bitrate when it is less than producer bitrate") + SECTION("IncreaseLayer() returns available bitrate when it is less than producer bitrate") { MockListener listener; auto manager = createManager(&listener); @@ -422,12 +422,12 @@ SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager][simple]" auto streamBitrate = rtpStream->GetBitrate(nowMs); uint32_t availableBitrate = streamBitrate - 1; auto usedBitrate = manager->IncreaseLayer( - /*bitrate*/ availableBitrate, /*considerLoss*/ false, /*lossPercentage*/ 0.0f, nowMs); + /*bitrate*/ availableBitrate, /*considerLoss*/ false, /*lossPercentage*/ 0.0f, nowMs); REQUIRE(usedBitrate == availableBitrate); } - SECTION("IncreaseLayer returns 0 on second call in same iteration") + SECTION("IncreaseLayer() returns 0 on second call in same iteration") { MockListener listener; auto manager = createManager(&listener); @@ -455,7 +455,7 @@ SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager][simple]" REQUIRE(usedBitrate2 == 0u); } - SECTION("IncreaseLayer works again after ApplyLayers") + SECTION("IncreaseLayer() works again after ApplyLayers()") { MockListener listener; auto manager = createManager(&listener); @@ -482,7 +482,7 @@ SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager][simple]" REQUIRE(usedBitrate > 0u); } - SECTION("GetDesiredBitrate returns producer bitrate for video") + SECTION("GetDesiredBitrate() returns producer bitrate for video") { MockListener listener; auto manager = createManager(&listener); @@ -502,7 +502,7 @@ SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager][simple]" REQUIRE(desiredBitrate == steamBitrate); } - SECTION("GetDesiredBitrate returns 0 for audio kind") + SECTION("GetDesiredBitrate() returns 0 for audio kind") { MockListener listener; auto manager = createManager(&listener, /*keyFrameSupported*/ false, RTC::Media::Kind::AUDIO); diff --git a/worker/test/src/RTC/TestSimulcastProducerStreamManager.cpp b/worker/test/src/RTC/TestSimulcastProducerStreamManager.cpp index e16f434759..065f720b02 100644 --- a/worker/test/src/RTC/TestSimulcastProducerStreamManager.cpp +++ b/worker/test/src/RTC/TestSimulcastProducerStreamManager.cpp @@ -217,7 +217,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producer-stream-manager][simul packet->SetTimestamp(1000); packet->SetPayloadLength(40); - SECTION("ProcessRtpPacket returns DROP when target temporal layer is -1") + SECTION("ProcessRtpPacket() returns DROP when target temporal layer is -1") { MockListener listener; auto manager = createManager(&listener); @@ -250,7 +250,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producer-stream-manager][simul } SECTION( - "ProcessRtpPacket returns BUFFER when sync required and packet is from target spatial layer but not keyframe") + "ProcessRtpPacket() returns BUFFER when sync required and packet is from target spatial layer but not keyframe") { MockListener listener; auto manager = createManager(&listener); @@ -270,7 +270,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producer-stream-manager][simul REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::BUFFER); } - SECTION("ProcessRtpPacket requires keyframe for spatial layer switch") + SECTION("ProcessRtpPacket() requires keyframe for spatial layer switch") { MockListener listener; auto manager = createManager(&listener); @@ -327,7 +327,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producer-stream-manager][simul REQUIRE(manager->GetCurrentSpatialLayer() == 1); } - SECTION("ProcessRtpPacket returns SILENT_DROP for non-target and non-current spatial layer packet") + SECTION("ProcessRtpPacket() returns SILENT_DROP for non-target and non-current spatial layer packet") { MockListener listener; auto manager = createManager(&listener); @@ -362,7 +362,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producer-stream-manager][simul } SECTION( - "ProcessRtpPacket returns SILENT_DROP when sync required and packet is from non-target spatial layer") + "ProcessRtpPacket() returns SILENT_DROP when sync required and packet is from non-target spatial layer") { MockListener listener; auto manager = createManager(&listener, /*ssrcs*/ twoSsrcs); @@ -385,7 +385,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producer-stream-manager][simul REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::SILENT_DROP); } - SECTION("ProcessRtpPacket returns FORWARD with sendBufferedPackets when syncing with a keyframe") + SECTION("ProcessRtpPacket() returns FORWARD with sendBufferedPackets when syncing with a keyframe") { MockListener listener; auto manager = createManager(&listener); @@ -411,7 +411,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producer-stream-manager][simul REQUIRE(result.sendBufferedPackets == true); } - SECTION("ProcessRtpPacket returns FORWARD and completes sync when keyFrameSupported is false") + SECTION("ProcessRtpPacket() returns FORWARD and completes sync when keyFrameSupported is false") { MockListener listener; auto manager = createManager( @@ -437,7 +437,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producer-stream-manager][simul REQUIRE(result.tsOffset == 0u); } - SECTION("ProcessRtpPacket returns DROP for empty payload packets on current spatial layer") + SECTION("ProcessRtpPacket() returns DROP for empty payload packets on current spatial layer") { MockListener listener; auto manager = createManager( @@ -465,7 +465,8 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producer-stream-manager][simul REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::DROP); } - SECTION("ProcessRtpPacket returns SILENT_DROP for empty payload packets on non-current spatial layer") + SECTION( + "ProcessRtpPacket() returns SILENT_DROP for empty payload packets on non-current spatial layer") { MockListener listener; auto manager = createManager( @@ -494,7 +495,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producer-stream-manager][simul REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::SILENT_DROP); } - SECTION("ProcessRtpPacket returns FORWARD for current layer packets after sync") + SECTION("ProcessRtpPacket() returns FORWARD for current layer packets after sync") { MockListener listener; auto manager = createManager( @@ -524,7 +525,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producer-stream-manager][simul REQUIRE(result.tsOffset == 0u); } - SECTION("ProcessRtpPacket forwards packets from current layer after spatial switch") + SECTION("ProcessRtpPacket() forwards packets from current layer after spatial switch") { MockListener listener; auto manager = createManager( @@ -577,7 +578,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producer-stream-manager][simul REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::SILENT_DROP); } - SECTION("OnTransportConnected sets syncRequired") + SECTION("OnTransportConnected() sets syncRequired") { MockListener listener; auto manager = createManager( @@ -620,7 +621,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producer-stream-manager][simul REQUIRE(result.isSyncPacket == true); } - SECTION("OnTransportConnected requests keyframe when active and target changes") + SECTION("OnTransportConnected() requests keyframe when active and target changes") { MockListener listener; auto manager = createManager(&listener, /*ssrcs*/ twoSsrcs, /*preferredLayers*/ { 1, 0 }); @@ -659,7 +660,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producer-stream-manager][simul REQUIRE(listener.keyFrameRequestCount > keyFrameCountBefore); } - SECTION("OnTransportConnected does not request keyframe when not active") + SECTION("OnTransportConnected() does not request keyframe when not active") { MockListener listener; listener.isActive = false; @@ -672,7 +673,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producer-stream-manager][simul REQUIRE(listener.keyFrameRequestCount == 0); } - SECTION("RecalculateTargetLayers skips spatial layer without Sender Report") + SECTION("RecalculateTargetLayers() skips spatial layer without Sender Report") { MockListener listener; auto manager = createManager(&listener, /*ssrcs*/ twoSsrcs, /*preferredLayers*/ { 1, 0 }); @@ -724,7 +725,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producer-stream-manager][simul REQUIRE(result.tsOffset == 0u); } - SECTION("RecalculateTargetLayers switches to preferred layer after Sender Report") + SECTION("RecalculateTargetLayers() switches to preferred layer after Sender Report") { MockListener listener; auto manager = createManager(&listener, /*ssrcs*/ twoSsrcs, /*preferredLayers*/ { 1, 0 }); @@ -800,7 +801,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producer-stream-manager][simul REQUIRE(manager->GetCurrentSpatialLayer() == 1); } - SECTION("OnResumed sets syncRequired") + SECTION("OnResumed() sets syncRequired") { MockListener listener; auto manager = createManager( @@ -843,7 +844,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producer-stream-manager][simul REQUIRE(result.isSyncPacket == true); } - SECTION("OnTransportDisconnected resets target layers") + SECTION("OnTransportDisconnected() resets target layers") { MockListener listener; auto manager = createManager(&listener); @@ -860,7 +861,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producer-stream-manager][simul REQUIRE(targetLayers.temporal == -1); } - SECTION("OnPaused resets target layers") + SECTION("OnPaused() resets target layers") { MockListener listener; auto manager = createManager(&listener); @@ -877,7 +878,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producer-stream-manager][simul REQUIRE(targetLayers.temporal == -1); } - SECTION("IncreaseLayer returns 0 on second call in same iteration") + SECTION("IncreaseLayer() returns 0 on second call in same iteration") { MockListener listener; auto manager = createManager(&listener, /*ssrcs*/ oneSsrc, /*preferredLayers*/ { 0, 0 }); @@ -905,7 +906,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producer-stream-manager][simul REQUIRE(usedBitrate2 == 0u); } - SECTION("IncreaseLayer works again after ApplyLayers") + SECTION("IncreaseLayer() works again after ApplyLayers()") { MockListener listener; auto manager = createManager(&listener, /*ssrcs*/ twoSsrcs, /*preferredLayers*/ { 1, 0 }); @@ -938,7 +939,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producer-stream-manager][simul REQUIRE(usedBitrate > 0u); } - SECTION("GetDesiredBitrate returns max bitrate across all producer streams") + SECTION("GetDesiredBitrate() returns max bitrate across all producer streams") { MockListener listener; auto manager = createManager(&listener, /*ssrcs*/ twoSsrcs, /*preferredLayers*/ { 1, 0 }); @@ -967,7 +968,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producer-stream-manager][simul REQUIRE(desiredBitrate == bitrate1); } - SECTION("GetProducerCurrentRtpStream returns nullptr before sync") + SECTION("GetProducerCurrentRtpStream() returns nullptr before sync") { MockListener listener; auto manager = createManager(&listener); @@ -979,7 +980,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producer-stream-manager][simul REQUIRE(manager->GetCurrentSpatialLayer() == -1); } - SECTION("GetProducerTargetRtpStream returns correct stream after UpdateTargetLayers") + SECTION("GetProducerTargetRtpStream() returns correct stream after UpdateTargetLayers()") { MockListener listener; auto manager = createManager(&listener); @@ -996,7 +997,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producer-stream-manager][simul REQUIRE(manager->GetTargetLayers().temporal == 0); } - SECTION("RequestKeyFrame requests for both target and current spatial layers") + SECTION("RequestKeyFrame() requests for both target and current spatial layers") { MockListener listener; auto manager = createManager( diff --git a/worker/test/src/RTC/TestSvcProducerStreamManager.cpp b/worker/test/src/RTC/TestSvcProducerStreamManager.cpp new file mode 100644 index 0000000000..fea6d2273f --- /dev/null +++ b/worker/test/src/RTC/TestSvcProducerStreamManager.cpp @@ -0,0 +1,1270 @@ +#include "Utils.hpp" +#include "RTC/RTP/Codecs/PayloadDescriptorHandler.hpp" +#include "RTC/RTP/RtpStreamRecv.hpp" +#include "RTC/RTP/rtpCommon.hpp" +#include "RTC/SvcProducerStreamManager.hpp" +#include + +namespace +{ + using RtpPacketProcessResult = RTC::ProducerStreamManager::RtpPacketProcessResult; + + // NOLINTBEGIN(readability-identifier-naming) + constexpr uint32_t mappedSsrc = 1234567890; + // NOLINTEND(readability-identifier-naming) + + class MockListener : public RTC::ProducerStreamManager::Listener + { + public: + bool IsActive() const override + { + return this->isActive; + } + + void OnProducerStreamManagerKeyFrameRequested(uint32_t mappedSsrc) override + { + this->keyFrameRequestCount++; + this->lastKeyFrameRequestedMappedSsrc = mappedSsrc; + } + + void OnProducerStreamManagerNeedBitrateChange() override + { + this->needBitrateChangeCount++; + } + + void OnProducerStreamManagerLayersChanged() override + { + this->layersChangedCount++; + } + + void OnProducerStreamManagerClearRetransmissionBuffer() override + { + } + + void OnProducerStreamManagerScore() override + { + } + + public: + bool isActive{ true }; + int keyFrameRequestCount{ 0 }; + uint32_t lastKeyFrameRequestedMappedSsrc{ 0 }; + int layersChangedCount{ 0 }; + int needBitrateChangeCount{ 0 }; + }; + + class RtpStreamRecvListener : public RTC::RTP::RtpStreamRecv::Listener + { + public: + void OnRtpStreamScore( + RTC::RTP::RtpStream* /*rtpStream*/, uint8_t /*score*/, uint8_t /*previousScore*/) override + { + } + + void OnRtpStreamSendRtcpPacket( + RTC::RTP::RtpStreamRecv* /*rtpStream*/, RTC::RTCP::Packet* /*packet*/) override + { + } + + void OnRtpStreamNeedWorstRemoteFractionLost( + RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint8_t& /*worstRemoteFractionLost*/) override + { + } + }; + + class MockEncodingContext : public RTC::RTP::Codecs::EncodingContext + { + public: + explicit MockEncodingContext( + uint8_t spatialLayers = 3u, uint8_t temporalLayers = 3u, bool ksvc = false) + : RTC::RTP::Codecs::EncodingContext( + MockEncodingContext::BuildParams(spatialLayers, temporalLayers, ksvc)) + { + } + void SyncRequired() override + { + } + + private: + static RTC::RTP::Codecs::EncodingContext::Params& BuildParams( + uint8_t spatialLayers, uint8_t temporalLayers, bool ksvc) + { + // Use a thread-local so each call gets its own params instance. + // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) + static thread_local RTC::RTP::Codecs::EncodingContext::Params params; + params.spatialLayers = spatialLayers; + params.temporalLayers = temporalLayers; + params.ksvc = ksvc; + return params; + } + }; + + class MockPayloadDescriptorHandler : public RTC::RTP::Codecs::PayloadDescriptorHandler + { + public: + explicit MockPayloadDescriptorHandler() = default; + ~MockPayloadDescriptorHandler() override = default; + void Dump(int /*indentation*/) const override + { + } + bool Process( + RTC::RTP::Codecs::EncodingContext* /*context*/, + RTC::RTP::Packet* /*packet*/, + bool& /*marker*/) override + { + return this->processResult; + } + void RtpPacketChanged(RTC::RTP::Packet* /*packet*/) override + { + } + std::unique_ptr GetEncoder() const override + { + return nullptr; + } + void Encode( + RTC::RTP::Packet* /*packet*/, RTC::RTP::Codecs::PayloadDescriptor::Encoder* /*encoder*/) override + { + } + void Restore(RTC::RTP::Packet* /*packet*/) override + { + } + uint8_t GetSpatialLayer() const override + { + return 0; + } + uint8_t GetTemporalLayer() const override + { + return 0; + } + bool IsKeyFrame() const override + { + return this->isKeyFrame; + } + + public: + bool isKeyFrame{ false }; + bool processResult{ true }; + }; + + std::unique_ptr createManager( + MockListener* listener, + RTC::ConsumerTypes::VideoLayers preferredLayers = { 2, 2 }, + bool keyFrameSupported = true, + RTC::Media::Kind kind = RTC::Media::Kind::VIDEO, + std::unique_ptr encodingContext = nullptr) + { + RTC::RtpEncodingParameters encoding; + encoding.ssrc = mappedSsrc; + std::vector consumableRtpEncodings{ encoding }; + + if (!encodingContext) + { + encodingContext = std::make_unique(); + } + + return std::make_unique( + consumableRtpEncodings, + preferredLayers, + std::move(encodingContext), + kind, + keyFrameSupported, + listener); + } + + // RtpStreamRecvListener must outlive the RtpStreamRecv. + RtpStreamRecvListener streamRecvListener; // NOLINT(readability-identifier-naming) + + std::unique_ptr createRtpStreamRecv( + uint32_t ssrc = mappedSsrc, uint8_t spatialLayers = 3u, uint8_t temporalLayers = 3u) + { + RTC::RTP::RtpStream::Params params; + + params.ssrc = ssrc; + params.clockRate = 90000; + params.spatialLayers = spatialLayers; + params.temporalLayers = temporalLayers; + + return std::make_unique(&streamRecvListener, params, 0u, false); + } + + // Feed packets into the RtpStreamRecv so GetBitrate() returns non-zero. + void feedRtpStreamRecv(RTC::RTP::RtpStreamRecv* rtpStream, RTC::RTP::Packet* packet, uint16_t count) + { + auto firstSeq = static_cast(packet->GetSequenceNumber() + 1); + auto lastSeq = static_cast(firstSeq + count); + + for (uint16_t seq = firstSeq; Utils::Number::IsLowerThan(seq, lastSeq); ++seq) + { + packet->SetSequenceNumber(seq); + rtpStream->ReceivePacket(packet); + } + + auto nowMs = DepLibUV::GetTimeMs(); + + // bitrate (bps) = totalBytes * 8000 / windowSizeMs. + // windowSizeMs for RtpStreamRecv is 2500. + auto expectedBitrate = + static_cast(std::trunc((count * packet->GetLength() * 8000.0f / 2500) + 0.5f)); + + REQUIRE(rtpStream->GetBitrate(nowMs) == expectedBitrate); + } +} // namespace + +SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") +{ + std::unique_ptr packet( + RTC::RTP::Packet::Factory(rtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer))); + + packet->SetPayloadType(1); + packet->SetSsrc(mappedSsrc); + packet->SetTimestamp(1000); + packet->SetPayloadLength(40); + + SECTION("ProcessRtpPacket() returns DROP when target spatial layer is -1") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + + // Default target layers are -1,-1 — packet must be dropped. + packet->SetSequenceNumber(1); + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::DROP); + } + + SECTION("ProcessRtpPacket() returns DROP when target temporal layer is -1") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + + // Set only spatial — temporal stays -1. + manager->UpdateTargetLayers(0, -1); + + packet->SetSequenceNumber(1); + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::DROP); + } + + SECTION("ProcessRtpPacket() returns BUFFER when sync required and packet is not a keyframe") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->UpdateTargetLayers(0, 0); + + // In SVC, syncRequired is set by OnTransportConnected/OnResumed, not by + // UpdateTargetLayers. Trigger it explicitly. + manager->OnTransportConnected(); + + // Re-apply target since MayChangeLayers may have reset it. + manager->UpdateTargetLayers(0, 0); + + packet->SetSequenceNumber(1); + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::BUFFER); + } + + SECTION( + "ProcessRtpPacket() returns FORWARD with isSyncPacket and sendBufferedPackets on keyframe sync") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->UpdateTargetLayers(0, 0); + + // syncRequired is only set by OnTransportConnected/OnResumed in SVC. + manager->OnTransportConnected(); + + // Re-apply target since MayChangeLayers may have reset it. + manager->UpdateTargetLayers(0, 0); + + const uint16_t seq{ 100 }; + + packet->SetSequenceNumber(seq); + + // packet->SetPayloadDescriptorHandler() takes ownership. + auto* handler = new MockPayloadDescriptorHandler(); + handler->isKeyFrame = true; + packet->SetPayloadDescriptorHandler(handler); + + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result.isSyncPacket == true); + REQUIRE(result.syncSeqValue == seq - 1); + REQUIRE(result.shouldSyncEncodingContext == true); + REQUIRE(result.sendBufferedPackets == true); + } + + SECTION("ProcessRtpPacket() always requires a keyframe for sync regardless of keyFrameSupported flag") + { + MockListener listener; + // keyFrameSupported=false has no effect in SVC: sync always needs a keyframe. + auto manager = createManager( + &listener, + /*preferredLayers*/ { 2, 2 }, + /*keyFrameSupported*/ false); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->UpdateTargetLayers(0, 0); + + // syncRequired is only set by OnTransportConnected/OnResumed in SVC. + manager->OnTransportConnected(); + + // Re-apply target since MayChangeLayers may have reset it. + manager->UpdateTargetLayers(0, 0); + + // A non-keyframe must still be buffered even with keyFrameSupported=false. + packet->SetSequenceNumber(1); + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::BUFFER); + + // Now a keyframe completes the sync. + const uint16_t seq{ 100 }; + packet->SetSequenceNumber(seq); + + auto* handler = new MockPayloadDescriptorHandler(); + handler->isKeyFrame = true; + packet->SetPayloadDescriptorHandler(handler); + + result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result.isSyncPacket == true); + REQUIRE(result.syncSeqValue == seq - 1); + REQUIRE(result.tsOffset == 0u); + REQUIRE(result.sendBufferedPackets == true); + } + + SECTION("ProcessRtpPacket() returns DROP for empty payload packets") + { + MockListener listener; + auto manager = createManager( + &listener, + /*preferredLayers*/ { 2, 2 }, + /*keyFrameSupported*/ false); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->UpdateTargetLayers(0, 0); + + // Trigger sync via OnTransportConnected, then re-apply target. + manager->OnTransportConnected(); + manager->UpdateTargetLayers(0, 0); + + // Complete sync with a keyframe (SVC always requires keyframe for sync). + packet->SetSequenceNumber(1); + auto* handler = new MockPayloadDescriptorHandler(); + handler->isKeyFrame = true; + packet->SetPayloadDescriptorHandler(handler); + manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + // Now send a packet with empty payload. + packet->SetSequenceNumber(2); + packet->RemovePayload(); + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::DROP); + } + + SECTION("ProcessRtpPacket() returns DROP when ProcessPayload fails") + { + MockListener listener; + auto encodingContext = std::make_unique(); + auto manager = createManager( + &listener, + /*preferredLayers*/ { 2, 2 }, + /*keyFrameSupported*/ false, + RTC::Media::Kind::VIDEO, + std::move(encodingContext)); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->UpdateTargetLayers(0, 0); + + // Trigger sync via OnTransportConnected, then re-apply target. + manager->OnTransportConnected(); + manager->UpdateTargetLayers(0, 0); + + // Complete sync with a keyframe (SVC always requires keyframe for sync). + packet->SetSequenceNumber(1); + auto* handler = new MockPayloadDescriptorHandler(); + handler->isKeyFrame = true; + packet->SetPayloadDescriptorHandler(handler); + manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + // Set a handler whose Process() returns false. + packet->SetSequenceNumber(2); + handler = new MockPayloadDescriptorHandler(); + handler->processResult = false; + packet->SetPayloadDescriptorHandler(handler); + + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::DROP); + } + + SECTION("ProcessRtpPacket() returns FORWARD for normal packets after sync") + { + MockListener listener; + auto manager = createManager( + &listener, + /*preferredLayers*/ { 2, 2 }, + /*keyFrameSupported*/ false); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->UpdateTargetLayers(0, 0); + + // Trigger sync via OnTransportConnected, then re-apply target. + manager->OnTransportConnected(); + manager->UpdateTargetLayers(0, 0); + + // Complete sync with a keyframe (SVC always requires keyframe for sync). + packet->SetSequenceNumber(1); + auto* handler = new MockPayloadDescriptorHandler(); + handler->isKeyFrame = true; + packet->SetPayloadDescriptorHandler(handler); + manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + // Send a second normal packet — should be a plain FORWARD. + packet->SetSequenceNumber(2); + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result.isSyncPacket == false); + REQUIRE(result.sendBufferedPackets == false); + REQUIRE(result.tsOffset == 0u); + } + + SECTION("ProcessRtpPacket() always sets tsOffset to 0 (SVC uses single stream)") + { + MockListener listener; + auto manager = createManager( + &listener, + /*preferredLayers*/ { 2, 2 }, + /*keyFrameSupported*/ false); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->UpdateTargetLayers(0, 0); + + // Trigger sync, then re-apply target. + manager->OnTransportConnected(); + manager->UpdateTargetLayers(0, 0); + + // Sync with a keyframe — SVC always requires one. + packet->SetSequenceNumber(1); + auto* handler = new MockPayloadDescriptorHandler(); + handler->isKeyFrame = true; + packet->SetPayloadDescriptorHandler(handler); + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result.tsOffset == 0u); + + // Subsequent normal packets also have tsOffset == 0. + packet->SetSequenceNumber(2); + result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result.tsOffset == 0u); + } + + SECTION("UpdateTargetLayers() to -1 resets current and target layers") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->UpdateTargetLayers(1, 1); + + // Reset. + manager->UpdateTargetLayers(-1, -1); + + auto targetLayers = manager->GetTargetLayers(); + + REQUIRE(targetLayers.spatial == -1); + REQUIRE(targetLayers.temporal == -1); + REQUIRE(manager->GetCurrentSpatialLayer() == -1); + } + + SECTION("UpdateTargetLayers() fires OnProducerStreamManagerLayersChanged() when setting -1") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + + int countBefore = listener.layersChangedCount; + + manager->UpdateTargetLayers(-1, -1); + + REQUIRE(listener.layersChangedCount == countBefore + 1); + } + + SECTION("UpdateTargetLayers() requests keyframe for full-SVC spatial upgrade") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + + // Start at layer 0. + manager->UpdateTargetLayers(0, 0); + + int kfCountBefore = listener.keyFrameRequestCount; + + // Upgrade spatial: full-SVC requests a keyframe. + manager->UpdateTargetLayers(1, 0); + + REQUIRE(listener.keyFrameRequestCount > kfCountBefore); + REQUIRE(listener.lastKeyFrameRequestedMappedSsrc == mappedSsrc); + } + + SECTION("GetProducerCurrentRtpStream() returns nullptr when current spatial layer is -1") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + + // No target set yet — current spatial layer is -1. + REQUIRE(manager->GetProducerCurrentRtpStream() == nullptr); + REQUIRE(manager->GetCurrentSpatialLayer() == -1); + } + + SECTION("GetProducerTargetRtpStream() returns nullptr when target spatial layer is -1") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + + REQUIRE(manager->GetProducerTargetRtpStream() == nullptr); + } + + SECTION("GetProducerTargetRtpStream() returns the producer stream after UpdateTargetLayers()") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->UpdateTargetLayers(0, 0); + + REQUIRE(manager->GetProducerTargetRtpStream() == rtpStream.get()); + REQUIRE(manager->GetTargetLayers().spatial == 0); + REQUIRE(manager->GetTargetLayers().temporal == 0); + } + + SECTION("IsActive() returns false when listener is not active") + { + MockListener listener; + listener.isActive = false; + + auto manager = createManager(&listener); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + + REQUIRE(manager->IsActive() == false); + } + + SECTION("IsActive() returns false when no producer stream is set") + { + MockListener listener; + auto manager = createManager(&listener); + + // No ProducerRtpStream call. + REQUIRE(manager->IsActive() == false); + } + + SECTION("RequestKeyFrame() always uses the single SVC mapped SSRC") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->UpdateTargetLayers(0, 0); + + listener.keyFrameRequestCount = 0; + manager->RequestKeyFrame(); + + REQUIRE(listener.keyFrameRequestCount == 1); + REQUIRE(listener.lastKeyFrameRequestedMappedSsrc == mappedSsrc); + } + + SECTION("RequestKeyFrameForTargetSpatialLayer() delegates to single-SSRC RequestKeyFrame()") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->UpdateTargetLayers(1, 0); + + listener.keyFrameRequestCount = 0; + manager->RequestKeyFrameForTargetSpatialLayer(); + + REQUIRE(listener.keyFrameRequestCount == 1); + REQUIRE(listener.lastKeyFrameRequestedMappedSsrc == mappedSsrc); + } + + SECTION("RequestKeyFrameForCurrentSpatialLayer() delegates to single-SSRC RequestKeyFrame()") + { + MockListener listener; + auto manager = createManager( + &listener, + /*preferredLayers*/ { 2, 2 }, + /*keyFrameSupported*/ false); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->UpdateTargetLayers(0, 0); + + // Sync so we have a current layer. + packet->SetSequenceNumber(1); + manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + listener.keyFrameRequestCount = 0; + manager->RequestKeyFrameForCurrentSpatialLayer(); + + REQUIRE(listener.keyFrameRequestCount == 1); + REQUIRE(listener.lastKeyFrameRequestedMappedSsrc == mappedSsrc); + } + + SECTION("OnTransportConnected() sets syncRequired") + { + MockListener listener; + auto manager = createManager( + &listener, + /*preferredLayers*/ { 2, 2 }, + /*keyFrameSupported*/ false); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->UpdateTargetLayers(0, 0); + + // First OnTransportConnected to set syncRequired, then sync with a keyframe. + manager->OnTransportConnected(); + manager->UpdateTargetLayers(0, 0); + + packet->SetSequenceNumber(1); + auto* handler = new MockPayloadDescriptorHandler(); + handler->isKeyFrame = true; + packet->SetPayloadDescriptorHandler(handler); + manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + // Verify synced: next normal packet is not a sync packet. + packet->SetSequenceNumber(2); + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.isSyncPacket == false); + + // Second OnTransportConnected — sets syncRequired again. + manager->OnTransportConnected(); + + // Re-set target because MayChangeLayers may have reset it. + manager->UpdateTargetLayers(0, 0); + + packet->SetSequenceNumber(3); + result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result.isSyncPacket == true); + } + + SECTION("OnTransportConnected() requests keyframe when active and target changes") + { + MockListener listener; + // Use preferred layer 0 so RecalculateTargetLayers can find it with spatial-layer-0 bitrate. + auto manager = createManager(&listener, /*preferredLayers*/ { 0, 0 }); + auto rtpStream = createRtpStreamRecv(mappedSsrc, /*spatialLayers*/ 1u, /*temporalLayers*/ 1u); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + + // Feed packets so GetSpatialLayerBitrate(0) returns non-zero. + // Packets without a PayloadDescriptorHandler default to spatialLayer=0. + feedRtpStreamRecv(rtpStream.get(), packet.get(), 100); + + auto keyFrameCountBefore = listener.keyFrameRequestCount; + + // OnTransportConnected calls MayChangeLayers → RecalculateTargetLayers picks + // layer {0,0} (bitrate > 0), which differs from the initial {-1,-1} → + // UpdateTargetLayers → newTarget(0) > current(-1) → keyframe requested. + manager->OnTransportConnected(); + + REQUIRE(listener.keyFrameRequestCount > keyFrameCountBefore); + REQUIRE(listener.lastKeyFrameRequestedMappedSsrc == mappedSsrc); + } + + SECTION("OnTransportConnected() does not request keyframe when not active") + { + MockListener listener; + listener.isActive = false; + + auto manager = createManager(&listener); + + // Don't wire producerRtpStream — manager is not active. + manager->OnTransportConnected(); + + REQUIRE(listener.keyFrameRequestCount == 0); + } + + SECTION("OnTransportDisconnected() resets target and current layers to -1") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->UpdateTargetLayers(1, 1); + + manager->OnTransportDisconnected(); + + auto targetLayers = manager->GetTargetLayers(); + + REQUIRE(targetLayers.spatial == -1); + REQUIRE(targetLayers.temporal == -1); + REQUIRE(manager->GetCurrentSpatialLayer() == -1); + } + + SECTION("OnPaused() resets target and current layers to -1") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->UpdateTargetLayers(2, 2); + + manager->OnPaused(); + + auto targetLayers = manager->GetTargetLayers(); + + REQUIRE(targetLayers.spatial == -1); + REQUIRE(targetLayers.temporal == -1); + REQUIRE(manager->GetCurrentSpatialLayer() == -1); + } + + SECTION("OnResumed() sets syncRequired and requests keyframe when active") + { + MockListener listener; + // Use preferred layer 0 so RecalculateTargetLayers can find it with spatial-layer-0 bitrate. + auto manager = createManager(&listener, /*preferredLayers*/ { 0, 0 }); + auto rtpStream = createRtpStreamRecv(mappedSsrc, /*spatialLayers*/ 1u, /*temporalLayers*/ 1u); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + + // Feed packets so GetSpatialLayerBitrate(0) returns non-zero. + feedRtpStreamRecv(rtpStream.get(), packet.get(), 100); + + // Connect so RecalculateTargetLayers sets target to {0,0}. + manager->OnTransportConnected(); + + // Disconnect: target and current reset to {-1,-1}. + manager->OnTransportDisconnected(); + + int keyFrameCountBefore = listener.keyFrameRequestCount; + + // OnResumed sets syncRequired and calls MayChangeLayers → RecalculateTargetLayers + // returns {0,0} which differs from {-1,-1} → UpdateTargetLayers(0,0) → + // newTarget(0) > current(-1) → keyframe requested. + manager->OnResumed(); + + REQUIRE(listener.keyFrameRequestCount > keyFrameCountBefore); + REQUIRE(listener.lastKeyFrameRequestedMappedSsrc == mappedSsrc); + } + + SECTION("OnResumed() sets syncRequired: next packet is a sync packet") + { + MockListener listener; + auto manager = createManager( + &listener, + /*preferredLayers*/ { 2, 2 }, + /*keyFrameSupported*/ false); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->UpdateTargetLayers(0, 0); + + // First sync via OnTransportConnected + keyframe. + manager->OnTransportConnected(); + manager->UpdateTargetLayers(0, 0); + + packet->SetSequenceNumber(1); + auto* handler = new MockPayloadDescriptorHandler(); + handler->isKeyFrame = true; + packet->SetPayloadDescriptorHandler(handler); + manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + // Verify synced: second normal packet is not a sync packet. + packet->SetSequenceNumber(2); + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + REQUIRE(result.isSyncPacket == false); + + // Call OnResumed — sets syncRequired again. + manager->OnResumed(); + + // Re-set target since MayChangeLayers may have reset it. + manager->UpdateTargetLayers(0, 0); + + // SVC requires a keyframe to complete sync. + packet->SetSequenceNumber(3); + handler = new MockPayloadDescriptorHandler(); + handler->isKeyFrame = true; + packet->SetPayloadDescriptorHandler(handler); + result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result.isSyncPacket == true); + } + + SECTION("ProducerNewRtpStream() updates the producer stream and fires score event") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream0 = createRtpStreamRecv(); + auto rtpStream1 = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream0.get(), mappedSsrc); + + // Replace with a new stream. + manager->ProducerNewRtpStream(rtpStream1.get(), mappedSsrc); + + // GetProducerTargetRtpStream still returns the new stream when target is set. + manager->UpdateTargetLayers(0, 0); + + REQUIRE(manager->GetProducerTargetRtpStream() == rtpStream1.get()); + } + + SECTION("ProducerRtpStreamScore() triggers MayChangeLayers() when stream dies (score==0)") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->UpdateTargetLayers(1, 1); + + int layersChangedBefore = listener.layersChangedCount; + + // Score drops to 0 — MayChangeLayers is called, RecalculateTargetLayers + // returns -1,-1 (no active stream), UpdateTargetLayers fires layersChanged. + manager->ProducerRtpStreamScore(rtpStream.get(), /*score*/ 0, /*previousScore*/ 7); + + REQUIRE(listener.layersChangedCount > layersChangedBefore); + REQUIRE(manager->GetTargetLayers().spatial == -1); + } + + SECTION("ProducerRtpStreamScore() triggers MayChangeLayers() when stream revives (previousScore==0)") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + + // First bring score to 0 so target becomes -1. + manager->ProducerRtpStreamScore(rtpStream.get(), /*score*/ 0, /*previousScore*/ 7); + REQUIRE(manager->GetTargetLayers().spatial == -1); + + // Revive the stream — MayChangeLayers should fire again. + // Even though RecalculateTargetLayers may still return -1 (no bitrate), + // the important thing is the code path is exercised and no crash occurs. + manager->ProducerRtpStreamScore(rtpStream.get(), /*score*/ 7, /*previousScore*/ 0); + } + + SECTION("IncreaseLayer() returns 0 when no producer stream is set") + { + MockListener listener; + auto manager = createManager(&listener); + + manager->SetExternallyManagedBitrate(); + + auto nowMs = DepLibUV::GetTimeMs(); + auto usedBitrate = manager->IncreaseLayer( + /*bitrate*/ 1000000u, /*considerLoss*/ false, /*lossPercentage*/ 0.0f, nowMs); + + REQUIRE(usedBitrate == 0u); + } + + SECTION("IncreaseLayer() returns 0 when producer stream has score 0") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->SetExternallyManagedBitrate(); + + // Score is 0 by default (no RTP received + inactivity check disabled). + auto nowMs = DepLibUV::GetTimeMs(); + auto usedBitrate = manager->IncreaseLayer( + /*bitrate*/ 1000000u, /*considerLoss*/ false, /*lossPercentage*/ 0.0f, nowMs); + + REQUIRE(usedBitrate == 0u); + } + + SECTION("IncreaseLayer() returns 0 on second call in same iteration (already at preferred layers)") + { + MockListener listener; + // Single spatial/temporal layer so preferred == available immediately. + auto encodingContext = + std::make_unique(/*spatialLayers*/ 1u, /*temporalLayers*/ 1u); + auto manager = createManager( + &listener, + /*preferredLayers*/ { 0, 0 }, + /*keyFrameSupported*/ true, + RTC::Media::Kind::VIDEO, + std::move(encodingContext)); + auto rtpStream = createRtpStreamRecv(mappedSsrc, /*spatialLayers*/ 1u, /*temporalLayers*/ 1u); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->SetExternallyManagedBitrate(); + + // Feed packets so the stream has non-zero bitrate. + feedRtpStreamRecv(rtpStream.get(), packet.get(), 100); + + auto nowMs = DepLibUV::GetTimeMs(); + + // First call claims bitrate. + auto usedBitrate = manager->IncreaseLayer( + /*bitrate*/ 1000000u, /*considerLoss*/ false, /*lossPercentage*/ 0.0f, nowMs); + + REQUIRE(usedBitrate > 0u); + + // Second call in same iteration should return 0 (already at preferred layers). + auto usedBitrate2 = manager->IncreaseLayer( + /*bitrate*/ 1000000u, /*considerLoss*/ false, /*lossPercentage*/ 0.0f, nowMs); + + REQUIRE(usedBitrate2 == 0u); + } + + SECTION("IncreaseLayer() works again after ApplyLayers()") + { + MockListener listener; + auto encodingContext = + std::make_unique(/*spatialLayers*/ 1u, /*temporalLayers*/ 1u); + auto manager = createManager( + &listener, + /*preferredLayers*/ { 0, 0 }, + /*keyFrameSupported*/ true, + RTC::Media::Kind::VIDEO, + std::move(encodingContext)); + auto rtpStream = createRtpStreamRecv(mappedSsrc, /*spatialLayers*/ 1u, /*temporalLayers*/ 1u); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->SetExternallyManagedBitrate(); + + // Feed packets so the stream has non-zero bitrate. + feedRtpStreamRecv(rtpStream.get(), packet.get(), 100); + + auto nowMs = DepLibUV::GetTimeMs(); + + // First iteration. + manager->IncreaseLayer( + /*bitrate*/ 1000000u, /*considerLoss*/ false, /*lossPercentage*/ 0.0f, nowMs); + manager->ApplyLayers(/*rtpStreamActiveMs*/ 0u); + + // After ApplyLayers, IncreaseLayer should work again. + auto usedBitrate = manager->IncreaseLayer( + /*bitrate*/ 1000000u, /*considerLoss*/ false, /*lossPercentage*/ 0.0f, nowMs); + + REQUIRE(usedBitrate > 0u); + } + + SECTION("ApplyLayers() records BWE downgrade when spatial layer drops below current") + { + MockListener listener; + auto encodingContext = + std::make_unique(/*spatialLayers*/ 3u, /*temporalLayers*/ 3u); + auto manager = createManager( + &listener, + /*preferredLayers*/ { 2, 2 }, + /*keyFrameSupported*/ false, + RTC::Media::Kind::VIDEO, + std::move(encodingContext)); + auto rtpStream = createRtpStreamRecv(mappedSsrc, /*spatialLayers*/ 3u, /*temporalLayers*/ 3u); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->SetExternallyManagedBitrate(); + + // Manually set current to layer 2 via ProcessRtpPacket() sync. + manager->UpdateTargetLayers(2, 2); + + packet->SetSequenceNumber(1); + auto* handler = new MockPayloadDescriptorHandler(); + handler->isKeyFrame = true; + packet->SetPayloadDescriptorHandler(handler); + manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0); + + // Simulate a BWE downgrade: IncreaseLayer returns layer 0, then ApplyLayers + // detects current(2) > target(0) and rtpStreamActiveMs > BweDowngradeMinActiveMs. + feedRtpStreamRecv(rtpStream.get(), packet.get(), 100); + + // Force provisional target to spatial 0 by only offering very little bitrate. + // With very low bitrate IncreaseLayer may return 0; skip that and call + // ApplyLayers directly with a high rtpStreamActiveMs. + // The important check is that no crash happens and target can be updated. + manager->ApplyLayers(/*rtpStreamActiveMs*/ 9000u); + + // After ApplyLayers the target may have changed; just assert no crash and + // that layers are either set or -1. + auto targetLayers = manager->GetTargetLayers(); + REQUIRE((targetLayers.spatial >= -1 && targetLayers.spatial <= 2)); + } + + SECTION("GetDesiredBitrate() returns 0 when no producer stream is set") + { + MockListener listener; + auto manager = createManager(&listener); + + auto nowMs = DepLibUV::GetTimeMs(); + auto desiredBitrate = manager->GetDesiredBitrate(nowMs); + + REQUIRE(desiredBitrate == 0u); + } + + SECTION("GetDesiredBitrate() (full SVC) returns total stream bitrate") + { + MockListener listener; + // Full SVC (ksvc=false). + auto encodingContext = std::make_unique(3u, 3u, /*ksvc*/ false); + auto manager = createManager( + &listener, + /*preferredLayers*/ { 2, 2 }, + /*keyFrameSupported*/ true, + RTC::Media::Kind::VIDEO, + std::move(encodingContext)); + auto rtpStream = createRtpStreamRecv(mappedSsrc, 3u, 3u); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->SetExternallyManagedBitrate(); + + feedRtpStreamRecv(rtpStream.get(), packet.get(), 100); + + auto nowMs = DepLibUV::GetTimeMs(); + auto streamBitrate = rtpStream->GetBitrate(nowMs); + auto desiredBitrate = manager->GetDesiredBitrate(nowMs); + + REQUIRE(desiredBitrate == streamBitrate); + } + + SECTION("GetDesiredBitrate() (K-SVC) returns the maximum spatial-layer bitrate") + { + MockListener listener; + // K-SVC (ksvc=true): GetDesiredBitrate iterates per-layer bitrates. + auto encodingContext = std::make_unique(3u, 3u, /*ksvc*/ true); + auto manager = createManager( + &listener, + /*preferredLayers*/ { 2, 2 }, + /*keyFrameSupported*/ true, + RTC::Media::Kind::VIDEO, + std::move(encodingContext)); + auto rtpStream = createRtpStreamRecv(mappedSsrc, 3u, 3u); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->SetExternallyManagedBitrate(); + + // Feed packets so total stream bitrate > 0. + feedRtpStreamRecv(rtpStream.get(), packet.get(), 100); + + auto nowMs = DepLibUV::GetTimeMs(); + auto desiredBitrate = manager->GetDesiredBitrate(nowMs); + + // K-SVC returns the max per-spatial-layer bitrate. With a plain RtpStreamRecv + // and no per-layer packet tagging the returned value may be 0 or > 0. + // The contract is just: no crash and type is uint32_t. + REQUIRE(desiredBitrate >= 0u); + } + + SECTION("RecalculateTargetLayers() returns false when no producer stream is set") + { + MockListener listener; + auto manager = createManager(&listener); + + RTC::ConsumerTypes::VideoLayers newTargetLayers; + bool changed = manager->RecalculateTargetLayers(newTargetLayers); + + // No change from initial -1,-1 target. + REQUIRE(changed == false); + REQUIRE(newTargetLayers.spatial == -1); + } + + SECTION("RecalculateTargetLayers() resets layers when stream score is 0") + { + MockListener listener; + auto manager = createManager(&listener); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + + // Score is 0 by default. + RTC::ConsumerTypes::VideoLayers newTargetLayers; + bool changed = manager->RecalculateTargetLayers(newTargetLayers); + + REQUIRE(newTargetLayers.spatial == -1); + // changed may be false because initial target is also -1. + (void)changed; + } + + SECTION("K-SVC: UpdateTargetLayers() requests keyframe on any spatial layer change") + { + MockListener listener; + auto encodingContext = std::make_unique(3u, 3u, /*ksvc*/ true); + auto manager = createManager( + &listener, + /*preferredLayers*/ { 2, 2 }, + /*keyFrameSupported*/ true, + RTC::Media::Kind::VIDEO, + std::move(encodingContext)); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + + // For K-SVC, even a downgrade should trigger a keyframe request. + manager->UpdateTargetLayers(2, 0); + + int kfCountBefore = listener.keyFrameRequestCount; + + // Downgrade in K-SVC also requests a keyframe. + manager->UpdateTargetLayers(0, 0); + + REQUIRE(listener.keyFrameRequestCount > kfCountBefore); + REQUIRE(listener.lastKeyFrameRequestedMappedSsrc == mappedSsrc); + } + + SECTION("Full SVC: UpdateTargetLayers() does NOT request keyframe on spatial downgrade") + { + MockListener listener; + auto encodingContext = std::make_unique(3u, 3u, /*ksvc*/ false); + auto manager = createManager( + &listener, + /*preferredLayers*/ { 2, 2 }, + /*keyFrameSupported*/ true, + RTC::Media::Kind::VIDEO, + std::move(encodingContext)); + auto rtpStream = createRtpStreamRecv(); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + + // Manually set target AND current spatial layer to 2 so that a downgrade + // to 0 does not qualify as an upgrade (newTarget 0 < current 2). + manager->UpdateTargetLayers(2, 0); + manager->GetEncodingContext()->SetCurrentSpatialLayer(2); + manager->GetEncodingContext()->SetCurrentTemporalLayer(0); + + // Reset counter after the initial upgrade keyframe request. + listener.keyFrameRequestCount = 0; + + // Downgrade in full SVC: current(2) > new target(0) — no keyframe requested. + manager->UpdateTargetLayers(0, 0); + + REQUIRE(listener.keyFrameRequestCount == 0); + } + + SECTION("IncreaseLayer() uses virtual bitrate when considerLoss is true and loss < 2%") + { + MockListener listener; + auto encodingContext = std::make_unique(1u, 1u, /*ksvc*/ false); + auto manager = createManager( + &listener, + /*preferredLayers*/ { 0, 0 }, + /*keyFrameSupported*/ true, + RTC::Media::Kind::VIDEO, + std::move(encodingContext)); + auto rtpStream = createRtpStreamRecv(mappedSsrc, 1u, 1u); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->SetExternallyManagedBitrate(); + + feedRtpStreamRecv(rtpStream.get(), packet.get(), 100); + + auto nowMs = DepLibUV::GetTimeMs(); + auto streamBitrate = rtpStream->GetBitrate(nowMs); + + // With 1% loss virtualBitrate = 1.08 * bitrate, so we can afford the stream + // even with slightly less physical bitrate available. + auto availableBitrate = static_cast(streamBitrate * 0.95f); + + auto usedBitrate = manager->IncreaseLayer( + availableBitrate, /*considerLoss*/ true, /*lossPercentage*/ 1.0f, nowMs); + + // virtualBitrate = 1.08 * availableBitrate >= streamBitrate, so the layer is taken. + REQUIRE(usedBitrate > 0u); + } + + SECTION("IncreaseLayer() uses reduced virtual bitrate when considerLoss is true and loss > 10%") + { + MockListener listener; + auto encodingContext = std::make_unique(1u, 1u, /*ksvc*/ false); + auto manager = createManager( + &listener, + /*preferredLayers*/ { 0, 0 }, + /*keyFrameSupported*/ true, + RTC::Media::Kind::VIDEO, + std::move(encodingContext)); + auto rtpStream = createRtpStreamRecv(mappedSsrc, 1u, 1u); + + manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); + manager->SetExternallyManagedBitrate(); + + feedRtpStreamRecv(rtpStream.get(), packet.get(), 100); + + auto nowMs = DepLibUV::GetTimeMs(); + auto streamBitrate = rtpStream->GetBitrate(nowMs); + + // With 50% loss virtualBitrate = (1 - 0.5*0.5) * bitrate = 0.75 * bitrate. + // Pass available = streamBitrate but virtual will be reduced below required. + auto usedBitrate = + manager->IncreaseLayer(streamBitrate, /*considerLoss*/ true, /*lossPercentage*/ 50.0f, nowMs); + + // virtualBitrate = 0.75 * streamBitrate < streamBitrate (requiredBitrate), + // so the layer cannot be taken → 0. + REQUIRE(usedBitrate == 0u); + } +} From 0b51fdd28d4e7b0138cb078fb269ad5aa7b4fe33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Fri, 20 Mar 2026 16:09:50 +0100 Subject: [PATCH 12/37] clang-tidy --- worker/src/RTC/SimulcastProducerStreamManager.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/worker/src/RTC/SimulcastProducerStreamManager.cpp b/worker/src/RTC/SimulcastProducerStreamManager.cpp index ab224e366a..15413e1747 100644 --- a/worker/src/RTC/SimulcastProducerStreamManager.cpp +++ b/worker/src/RTC/SimulcastProducerStreamManager.cpp @@ -71,9 +71,8 @@ namespace RTC // clang-format off return ( this->listener->IsActive() && - std::any_of( - this->producerRtpStreams.begin(), - this->producerRtpStreams.end(), + std::ranges::any_of( + this->producerRtpStreams, [](const RTC::RTP::RtpStreamRecv* rtpStream) { return ( @@ -697,8 +696,7 @@ namespace RTC // Old-packet filtering after spatial layer switch. if (!shouldSwitchCurrentSpatialLayer && this->checkingForOldPacketsInSpatialLayer) { - if (SeqManager::IsSeqLowerThan( - packet->GetSequenceNumber(), this->snReferenceSpatialLayer)) + if (SeqManager::IsSeqLowerThan(packet->GetSequenceNumber(), this->snReferenceSpatialLayer)) { result.type = RtpPacketProcessResult::Type::DROP; @@ -709,8 +707,9 @@ namespace RTC return result; } - else if (SeqManager::IsSeqHigherThan( - packet->GetSequenceNumber(), this->snReferenceSpatialLayer + MaxSequenceNumberGap)) + else if ( + SeqManager::IsSeqHigherThan( + packet->GetSequenceNumber(), this->snReferenceSpatialLayer + MaxSequenceNumberGap)) { this->checkingForOldPacketsInSpatialLayer = false; } From bf6d2215c138b669e1f270a0993ceb17c9879368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Fri, 20 Mar 2026 16:10:10 +0100 Subject: [PATCH 13/37] make format --- worker/include/RTC/ProducerStreamManager.hpp | 7 +++++-- worker/src/RTC/OldConsumer.cpp | 12 ++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/worker/include/RTC/ProducerStreamManager.hpp b/worker/include/RTC/ProducerStreamManager.hpp index 76765cc094..3f767e94ee 100644 --- a/worker/include/RTC/ProducerStreamManager.hpp +++ b/worker/include/RTC/ProducerStreamManager.hpp @@ -61,8 +61,11 @@ namespace RTC RTC::Media::Kind kind, bool keyFrameSupported, Listener* listener) - : listener(listener), keyFrameSupported(keyFrameSupported), kind(kind), - consumableRtpEncodings(consumableRtpEncodings), encodingContext(std::move(encodingContext)), + : listener(listener), + keyFrameSupported(keyFrameSupported), + kind(kind), + consumableRtpEncodings(consumableRtpEncodings), + encodingContext(std::move(encodingContext)), preferredLayers(preferredLayers) { MS_TRACE(); diff --git a/worker/src/RTC/OldConsumer.cpp b/worker/src/RTC/OldConsumer.cpp index 12258fe0e0..724e33c562 100644 --- a/worker/src/RTC/OldConsumer.cpp +++ b/worker/src/RTC/OldConsumer.cpp @@ -17,8 +17,12 @@ namespace RTC Listener* listener, const FBS::Transport::ConsumeRequest* data, RTC::RtpParameters::Type type) - : id(id), producerId(producerId), shared(shared), listener(listener), - kind(RTC::Media::Kind(data->kind())), type(type) + : id(id), + producerId(producerId), + shared(shared), + listener(listener), + kind(RTC::Media::Kind(data->kind())), + type(type) { MS_TRACE(); @@ -496,7 +500,7 @@ namespace RTC { auto rtpPacketDump = packet->FillBuffer(this->shared->channelNotifier->GetBufferBuilder()); auto traceInfo = FBS::Consumer::CreateKeyFrameTraceInfo( - this->shared->channelNotifier->GetBufferBuilder(), rtpPacketDump, isRtx); + this->shared->channelNotifier->GetBufferBuilder(), rtpPacketDump, isRtx); auto notification = FBS::Consumer::CreateTraceNotification( this->shared->channelNotifier->GetBufferBuilder(), @@ -512,7 +516,7 @@ namespace RTC { auto rtpPacketDump = packet->FillBuffer(this->shared->channelNotifier->GetBufferBuilder()); auto traceInfo = FBS::Consumer::CreateRtpTraceInfo( - this->shared->channelNotifier->GetBufferBuilder(), rtpPacketDump, isRtx); + this->shared->channelNotifier->GetBufferBuilder(), rtpPacketDump, isRtx); auto notification = FBS::Consumer::CreateTraceNotification( this->shared->channelNotifier->GetBufferBuilder(), From 8d61406151983769e2f5e954c36dcce0556e0c64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Fri, 20 Mar 2026 16:13:26 +0100 Subject: [PATCH 14/37] Update worker/src/RTC/SimulcastProducerStreamManager.cpp Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- worker/src/RTC/SimulcastProducerStreamManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worker/src/RTC/SimulcastProducerStreamManager.cpp b/worker/src/RTC/SimulcastProducerStreamManager.cpp index 15413e1747..9f473be2fd 100644 --- a/worker/src/RTC/SimulcastProducerStreamManager.cpp +++ b/worker/src/RTC/SimulcastProducerStreamManager.cpp @@ -627,7 +627,7 @@ namespace RTC if (this->keyFrameForTsOffsetRequested) { // Give up and use the theoretical offset. - if (tsExtraOffset > maxTsExtraOffset) + if (std::cmp_greater(tsExtraOffset , maxTsExtraOffset)) { MS_WARN_TAG( simulcast, From d72b366ce74ba489c2e7fe7d6a35c4bc91756b8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Fri, 20 Mar 2026 16:13:42 +0100 Subject: [PATCH 15/37] Update worker/src/RTC/SimulcastProducerStreamManager.cpp Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- worker/src/RTC/SimulcastProducerStreamManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worker/src/RTC/SimulcastProducerStreamManager.cpp b/worker/src/RTC/SimulcastProducerStreamManager.cpp index 9f473be2fd..cf47d0b66a 100644 --- a/worker/src/RTC/SimulcastProducerStreamManager.cpp +++ b/worker/src/RTC/SimulcastProducerStreamManager.cpp @@ -638,7 +638,7 @@ namespace RTC tsExtraOffset = 1u; } } - else if (tsExtraOffset > maxTsExtraOffset) + else if (std::cmp_greater(tsExtraOffset , maxTsExtraOffset)) { MS_WARN_TAG( simulcast, From 5f765fcbb786dbc9856c18abf38acc727ff0f5f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Fri, 20 Mar 2026 16:14:03 +0100 Subject: [PATCH 16/37] Update worker/src/RTC/SvcProducerStreamManager.cpp Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- worker/src/RTC/SvcProducerStreamManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worker/src/RTC/SvcProducerStreamManager.cpp b/worker/src/RTC/SvcProducerStreamManager.cpp index a518fa4cd6..e4737f0915 100644 --- a/worker/src/RTC/SvcProducerStreamManager.cpp +++ b/worker/src/RTC/SvcProducerStreamManager.cpp @@ -159,7 +159,7 @@ namespace RTC int16_t spatialLayer{ 0 }; int16_t temporalLayer{ 0 }; - for (; spatialLayer < this->producerRtpStream->GetSpatialLayers(); ++spatialLayer) + for (; std::cmp_less(spatialLayer , this->producerRtpStream->GetSpatialLayers()); ++spatialLayer) { // If this is higher than current spatial layer and we moved to current // spatial layer due to BWE limitations, check how much it has elapsed From 3e9ac6699a44c24c66ff7051d6155bf5d728b07f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Fri, 20 Mar 2026 16:14:16 +0100 Subject: [PATCH 17/37] Update worker/src/RTC/SvcProducerStreamManager.cpp Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- worker/src/RTC/SvcProducerStreamManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worker/src/RTC/SvcProducerStreamManager.cpp b/worker/src/RTC/SvcProducerStreamManager.cpp index e4737f0915..c1079ba9f0 100644 --- a/worker/src/RTC/SvcProducerStreamManager.cpp +++ b/worker/src/RTC/SvcProducerStreamManager.cpp @@ -184,7 +184,7 @@ namespace RTC temporalLayer = 0; // Check bitrate of every temporal layer. - for (; temporalLayer < this->producerRtpStream->GetTemporalLayers(); ++temporalLayer) + for (; std::cmp_less(temporalLayer , this->producerRtpStream->GetTemporalLayers()); ++temporalLayer) { // Ignore temporal layers lower than the one we already have (taking // into account the spatial layer too). From 6e60816b01983ddeba0fa2121b71ba637153b444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Fri, 20 Mar 2026 16:14:26 +0100 Subject: [PATCH 18/37] Update worker/src/RTC/SvcProducerStreamManager.cpp Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- worker/src/RTC/SvcProducerStreamManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worker/src/RTC/SvcProducerStreamManager.cpp b/worker/src/RTC/SvcProducerStreamManager.cpp index c1079ba9f0..96a18a4d3b 100644 --- a/worker/src/RTC/SvcProducerStreamManager.cpp +++ b/worker/src/RTC/SvcProducerStreamManager.cpp @@ -557,7 +557,7 @@ namespace RTC goto done; } - for (; spatialLayer < this->producerRtpStream->GetSpatialLayers(); ++spatialLayer) + for (; std::cmp_less(spatialLayer , this->producerRtpStream->GetSpatialLayers()); ++spatialLayer) { // If this is higher than current spatial layer and we moved to current // spatial layer due to BWE limitations, check how much it has elapsed From 971ac412feca26e7e48a8d7fe9285cde73c0d9c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Fri, 20 Mar 2026 16:19:06 +0100 Subject: [PATCH 19/37] format --- worker/src/RTC/SimulcastProducerStreamManager.cpp | 4 ++-- worker/src/RTC/SvcProducerStreamManager.cpp | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/worker/src/RTC/SimulcastProducerStreamManager.cpp b/worker/src/RTC/SimulcastProducerStreamManager.cpp index cf47d0b66a..635d71b878 100644 --- a/worker/src/RTC/SimulcastProducerStreamManager.cpp +++ b/worker/src/RTC/SimulcastProducerStreamManager.cpp @@ -627,7 +627,7 @@ namespace RTC if (this->keyFrameForTsOffsetRequested) { // Give up and use the theoretical offset. - if (std::cmp_greater(tsExtraOffset , maxTsExtraOffset)) + if (std::cmp_greater(tsExtraOffset, maxTsExtraOffset)) { MS_WARN_TAG( simulcast, @@ -638,7 +638,7 @@ namespace RTC tsExtraOffset = 1u; } } - else if (std::cmp_greater(tsExtraOffset , maxTsExtraOffset)) + else if (std::cmp_greater(tsExtraOffset, maxTsExtraOffset)) { MS_WARN_TAG( simulcast, diff --git a/worker/src/RTC/SvcProducerStreamManager.cpp b/worker/src/RTC/SvcProducerStreamManager.cpp index 96a18a4d3b..cc57434052 100644 --- a/worker/src/RTC/SvcProducerStreamManager.cpp +++ b/worker/src/RTC/SvcProducerStreamManager.cpp @@ -159,7 +159,7 @@ namespace RTC int16_t spatialLayer{ 0 }; int16_t temporalLayer{ 0 }; - for (; std::cmp_less(spatialLayer , this->producerRtpStream->GetSpatialLayers()); ++spatialLayer) + for (; std::cmp_less(spatialLayer, this->producerRtpStream->GetSpatialLayers()); ++spatialLayer) { // If this is higher than current spatial layer and we moved to current // spatial layer due to BWE limitations, check how much it has elapsed @@ -184,7 +184,8 @@ namespace RTC temporalLayer = 0; // Check bitrate of every temporal layer. - for (; std::cmp_less(temporalLayer , this->producerRtpStream->GetTemporalLayers()); ++temporalLayer) + for (; std::cmp_less(temporalLayer, this->producerRtpStream->GetTemporalLayers()); + ++temporalLayer) { // Ignore temporal layers lower than the one we already have (taking // into account the spatial layer too). @@ -557,7 +558,7 @@ namespace RTC goto done; } - for (; std::cmp_less(spatialLayer , this->producerRtpStream->GetSpatialLayers()); ++spatialLayer) + for (; std::cmp_less(spatialLayer, this->producerRtpStream->GetSpatialLayers()); ++spatialLayer) { // If this is higher than current spatial layer and we moved to current // spatial layer due to BWE limitations, check how much it has elapsed From 2785458f79a9f4458babff264c065dedb9c87fba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Fri, 20 Mar 2026 16:35:24 +0100 Subject: [PATCH 20/37] tidy --- .../src/RTC/TestSvcProducerStreamManager.cpp | 80 +++++++++---------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/worker/test/src/RTC/TestSvcProducerStreamManager.cpp b/worker/test/src/RTC/TestSvcProducerStreamManager.cpp index fea6d2273f..9de56542db 100644 --- a/worker/test/src/RTC/TestSvcProducerStreamManager.cpp +++ b/worker/test/src/RTC/TestSvcProducerStreamManager.cpp @@ -155,7 +155,7 @@ namespace { RTC::RtpEncodingParameters encoding; encoding.ssrc = mappedSsrc; - std::vector consumableRtpEncodings{ encoding }; + const std::vector consumableRtpEncodings{ encoding }; if (!encodingContext) { @@ -222,7 +222,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("ProcessRtpPacket() returns DROP when target spatial layer is -1") { - MockListener listener; + const MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -238,7 +238,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("ProcessRtpPacket() returns DROP when target temporal layer is -1") { - MockListener listener; + const MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -256,7 +256,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("ProcessRtpPacket() returns BUFFER when sync required and packet is not a keyframe") { - MockListener listener; + const MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -280,7 +280,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION( "ProcessRtpPacket() returns FORWARD with isSyncPacket and sendBufferedPackets on keyframe sync") { - MockListener listener; + const MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -314,7 +314,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("ProcessRtpPacket() always requires a keyframe for sync regardless of keyFrameSupported flag") { - MockListener listener; + const MockListener listener; // keyFrameSupported=false has no effect in SVC: sync always needs a keyframe. auto manager = createManager( &listener, @@ -358,7 +358,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("ProcessRtpPacket() returns DROP for empty payload packets") { - MockListener listener; + const MockListener listener; auto manager = createManager( &listener, /*preferredLayers*/ { 2, 2 }, @@ -391,7 +391,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("ProcessRtpPacket() returns DROP when ProcessPayload fails") { - MockListener listener; + const MockListener listener; auto encodingContext = std::make_unique(); auto manager = createManager( &listener, @@ -430,7 +430,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("ProcessRtpPacket() returns FORWARD for normal packets after sync") { - MockListener listener; + const MockListener listener; auto manager = createManager( &listener, /*preferredLayers*/ { 2, 2 }, @@ -465,7 +465,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("ProcessRtpPacket() always sets tsOffset to 0 (SVC uses single stream)") { - MockListener listener; + const MockListener listener; auto manager = createManager( &listener, /*preferredLayers*/ { 2, 2 }, @@ -501,7 +501,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("UpdateTargetLayers() to -1 resets current and target layers") { - MockListener listener; + const MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -520,13 +520,13 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("UpdateTargetLayers() fires OnProducerStreamManagerLayersChanged() when setting -1") { - MockListener listener; + const MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); - int countBefore = listener.layersChangedCount; + const int countBefore = listener.layersChangedCount; manager->UpdateTargetLayers(-1, -1); @@ -535,7 +535,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("UpdateTargetLayers() requests keyframe for full-SVC spatial upgrade") { - MockListener listener; + const MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -555,7 +555,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("GetProducerCurrentRtpStream() returns nullptr when current spatial layer is -1") { - MockListener listener; + const MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -568,7 +568,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("GetProducerTargetRtpStream() returns nullptr when target spatial layer is -1") { - MockListener listener; + const MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -579,7 +579,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("GetProducerTargetRtpStream() returns the producer stream after UpdateTargetLayers()") { - MockListener listener; + const MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -606,7 +606,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("IsActive() returns false when no producer stream is set") { - MockListener listener; + const MockListener listener; auto manager = createManager(&listener); // No ProducerRtpStream call. @@ -671,7 +671,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("OnTransportConnected() sets syncRequired") { - MockListener listener; + const MockListener listener; auto manager = createManager( &listener, /*preferredLayers*/ { 2, 2 }, @@ -715,7 +715,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("OnTransportConnected() requests keyframe when active and target changes") { - MockListener listener; + const MockListener listener; // Use preferred layer 0 so RecalculateTargetLayers can find it with spatial-layer-0 bitrate. auto manager = createManager(&listener, /*preferredLayers*/ { 0, 0 }); auto rtpStream = createRtpStreamRecv(mappedSsrc, /*spatialLayers*/ 1u, /*temporalLayers*/ 1u); @@ -752,7 +752,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("OnTransportDisconnected() resets target and current layers to -1") { - MockListener listener; + const MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -770,7 +770,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("OnPaused() resets target and current layers to -1") { - MockListener listener; + const MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -788,7 +788,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("OnResumed() sets syncRequired and requests keyframe when active") { - MockListener listener; + const MockListener listener; // Use preferred layer 0 so RecalculateTargetLayers can find it with spatial-layer-0 bitrate. auto manager = createManager(&listener, /*preferredLayers*/ { 0, 0 }); auto rtpStream = createRtpStreamRecv(mappedSsrc, /*spatialLayers*/ 1u, /*temporalLayers*/ 1u); @@ -817,7 +817,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("OnResumed() sets syncRequired: next packet is a sync packet") { - MockListener listener; + const MockListener listener; auto manager = createManager( &listener, /*preferredLayers*/ { 2, 2 }, @@ -864,7 +864,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("ProducerNewRtpStream() updates the producer stream and fires score event") { - MockListener listener; + const MockListener listener; auto manager = createManager(&listener); auto rtpStream0 = createRtpStreamRecv(); auto rtpStream1 = createRtpStreamRecv(); @@ -882,7 +882,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("ProducerRtpStreamScore() triggers MayChangeLayers() when stream dies (score==0)") { - MockListener listener; + const MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -901,7 +901,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("ProducerRtpStreamScore() triggers MayChangeLayers() when stream revives (previousScore==0)") { - MockListener listener; + const MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -919,7 +919,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("IncreaseLayer() returns 0 when no producer stream is set") { - MockListener listener; + const MockListener listener; auto manager = createManager(&listener); manager->SetExternallyManagedBitrate(); @@ -933,7 +933,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("IncreaseLayer() returns 0 when producer stream has score 0") { - MockListener listener; + const MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -950,7 +950,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("IncreaseLayer() returns 0 on second call in same iteration (already at preferred layers)") { - MockListener listener; + const MockListener listener; // Single spatial/temporal layer so preferred == available immediately. auto encodingContext = std::make_unique(/*spatialLayers*/ 1u, /*temporalLayers*/ 1u); @@ -985,7 +985,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("IncreaseLayer() works again after ApplyLayers()") { - MockListener listener; + const MockListener listener; auto encodingContext = std::make_unique(/*spatialLayers*/ 1u, /*temporalLayers*/ 1u); auto manager = createManager( @@ -1018,7 +1018,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("ApplyLayers() records BWE downgrade when spatial layer drops below current") { - MockListener listener; + const MockListener listener; auto encodingContext = std::make_unique(/*spatialLayers*/ 3u, /*temporalLayers*/ 3u); auto manager = createManager( @@ -1060,7 +1060,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("GetDesiredBitrate() returns 0 when no producer stream is set") { - MockListener listener; + const MockListener listener; auto manager = createManager(&listener); auto nowMs = DepLibUV::GetTimeMs(); @@ -1071,7 +1071,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("GetDesiredBitrate() (full SVC) returns total stream bitrate") { - MockListener listener; + const MockListener listener; // Full SVC (ksvc=false). auto encodingContext = std::make_unique(3u, 3u, /*ksvc*/ false); auto manager = createManager( @@ -1096,7 +1096,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("GetDesiredBitrate() (K-SVC) returns the maximum spatial-layer bitrate") { - MockListener listener; + const MockListener listener; // K-SVC (ksvc=true): GetDesiredBitrate iterates per-layer bitrates. auto encodingContext = std::make_unique(3u, 3u, /*ksvc*/ true); auto manager = createManager( @@ -1124,7 +1124,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("RecalculateTargetLayers() returns false when no producer stream is set") { - MockListener listener; + const MockListener listener; auto manager = createManager(&listener); RTC::ConsumerTypes::VideoLayers newTargetLayers; @@ -1137,7 +1137,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("RecalculateTargetLayers() resets layers when stream score is 0") { - MockListener listener; + const MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -1154,7 +1154,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("K-SVC: UpdateTargetLayers() requests keyframe on any spatial layer change") { - MockListener listener; + const MockListener listener; auto encodingContext = std::make_unique(3u, 3u, /*ksvc*/ true); auto manager = createManager( &listener, @@ -1209,7 +1209,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("IncreaseLayer() uses virtual bitrate when considerLoss is true and loss < 2%") { - MockListener listener; + const MockListener listener; auto encodingContext = std::make_unique(1u, 1u, /*ksvc*/ false); auto manager = createManager( &listener, @@ -1240,7 +1240,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("IncreaseLayer() uses reduced virtual bitrate when considerLoss is true and loss > 10%") { - MockListener listener; + const MockListener listener; auto encodingContext = std::make_unique(1u, 1u, /*ksvc*/ false); auto manager = createManager( &listener, From 0086b997cf05530ead9df4e923b426380fb03d61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Fri, 20 Mar 2026 16:42:28 +0100 Subject: [PATCH 21/37] Do not make listener const We should make this decision broadly --- .../src/RTC/TestSvcProducerStreamManager.cpp | 76 +++++++++---------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/worker/test/src/RTC/TestSvcProducerStreamManager.cpp b/worker/test/src/RTC/TestSvcProducerStreamManager.cpp index 9de56542db..f58b6e756c 100644 --- a/worker/test/src/RTC/TestSvcProducerStreamManager.cpp +++ b/worker/test/src/RTC/TestSvcProducerStreamManager.cpp @@ -222,7 +222,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("ProcessRtpPacket() returns DROP when target spatial layer is -1") { - const MockListener listener; + MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -238,7 +238,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("ProcessRtpPacket() returns DROP when target temporal layer is -1") { - const MockListener listener; + MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -256,7 +256,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("ProcessRtpPacket() returns BUFFER when sync required and packet is not a keyframe") { - const MockListener listener; + MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -280,7 +280,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION( "ProcessRtpPacket() returns FORWARD with isSyncPacket and sendBufferedPackets on keyframe sync") { - const MockListener listener; + MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -314,7 +314,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("ProcessRtpPacket() always requires a keyframe for sync regardless of keyFrameSupported flag") { - const MockListener listener; + MockListener listener; // keyFrameSupported=false has no effect in SVC: sync always needs a keyframe. auto manager = createManager( &listener, @@ -358,7 +358,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("ProcessRtpPacket() returns DROP for empty payload packets") { - const MockListener listener; + MockListener listener; auto manager = createManager( &listener, /*preferredLayers*/ { 2, 2 }, @@ -391,7 +391,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("ProcessRtpPacket() returns DROP when ProcessPayload fails") { - const MockListener listener; + MockListener listener; auto encodingContext = std::make_unique(); auto manager = createManager( &listener, @@ -430,7 +430,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("ProcessRtpPacket() returns FORWARD for normal packets after sync") { - const MockListener listener; + MockListener listener; auto manager = createManager( &listener, /*preferredLayers*/ { 2, 2 }, @@ -465,7 +465,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("ProcessRtpPacket() always sets tsOffset to 0 (SVC uses single stream)") { - const MockListener listener; + MockListener listener; auto manager = createManager( &listener, /*preferredLayers*/ { 2, 2 }, @@ -501,7 +501,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("UpdateTargetLayers() to -1 resets current and target layers") { - const MockListener listener; + MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -520,7 +520,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("UpdateTargetLayers() fires OnProducerStreamManagerLayersChanged() when setting -1") { - const MockListener listener; + MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -535,7 +535,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("UpdateTargetLayers() requests keyframe for full-SVC spatial upgrade") { - const MockListener listener; + MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -555,7 +555,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("GetProducerCurrentRtpStream() returns nullptr when current spatial layer is -1") { - const MockListener listener; + MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -568,7 +568,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("GetProducerTargetRtpStream() returns nullptr when target spatial layer is -1") { - const MockListener listener; + MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -579,7 +579,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("GetProducerTargetRtpStream() returns the producer stream after UpdateTargetLayers()") { - const MockListener listener; + MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -606,7 +606,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("IsActive() returns false when no producer stream is set") { - const MockListener listener; + MockListener listener; auto manager = createManager(&listener); // No ProducerRtpStream call. @@ -671,7 +671,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("OnTransportConnected() sets syncRequired") { - const MockListener listener; + MockListener listener; auto manager = createManager( &listener, /*preferredLayers*/ { 2, 2 }, @@ -715,7 +715,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("OnTransportConnected() requests keyframe when active and target changes") { - const MockListener listener; + MockListener listener; // Use preferred layer 0 so RecalculateTargetLayers can find it with spatial-layer-0 bitrate. auto manager = createManager(&listener, /*preferredLayers*/ { 0, 0 }); auto rtpStream = createRtpStreamRecv(mappedSsrc, /*spatialLayers*/ 1u, /*temporalLayers*/ 1u); @@ -752,7 +752,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("OnTransportDisconnected() resets target and current layers to -1") { - const MockListener listener; + MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -770,7 +770,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("OnPaused() resets target and current layers to -1") { - const MockListener listener; + MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -788,7 +788,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("OnResumed() sets syncRequired and requests keyframe when active") { - const MockListener listener; + MockListener listener; // Use preferred layer 0 so RecalculateTargetLayers can find it with spatial-layer-0 bitrate. auto manager = createManager(&listener, /*preferredLayers*/ { 0, 0 }); auto rtpStream = createRtpStreamRecv(mappedSsrc, /*spatialLayers*/ 1u, /*temporalLayers*/ 1u); @@ -817,7 +817,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("OnResumed() sets syncRequired: next packet is a sync packet") { - const MockListener listener; + MockListener listener; auto manager = createManager( &listener, /*preferredLayers*/ { 2, 2 }, @@ -864,7 +864,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("ProducerNewRtpStream() updates the producer stream and fires score event") { - const MockListener listener; + MockListener listener; auto manager = createManager(&listener); auto rtpStream0 = createRtpStreamRecv(); auto rtpStream1 = createRtpStreamRecv(); @@ -882,7 +882,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("ProducerRtpStreamScore() triggers MayChangeLayers() when stream dies (score==0)") { - const MockListener listener; + MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -901,7 +901,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("ProducerRtpStreamScore() triggers MayChangeLayers() when stream revives (previousScore==0)") { - const MockListener listener; + MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -919,7 +919,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("IncreaseLayer() returns 0 when no producer stream is set") { - const MockListener listener; + MockListener listener; auto manager = createManager(&listener); manager->SetExternallyManagedBitrate(); @@ -933,7 +933,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("IncreaseLayer() returns 0 when producer stream has score 0") { - const MockListener listener; + MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -950,7 +950,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("IncreaseLayer() returns 0 on second call in same iteration (already at preferred layers)") { - const MockListener listener; + MockListener listener; // Single spatial/temporal layer so preferred == available immediately. auto encodingContext = std::make_unique(/*spatialLayers*/ 1u, /*temporalLayers*/ 1u); @@ -985,7 +985,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("IncreaseLayer() works again after ApplyLayers()") { - const MockListener listener; + MockListener listener; auto encodingContext = std::make_unique(/*spatialLayers*/ 1u, /*temporalLayers*/ 1u); auto manager = createManager( @@ -1018,7 +1018,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("ApplyLayers() records BWE downgrade when spatial layer drops below current") { - const MockListener listener; + MockListener listener; auto encodingContext = std::make_unique(/*spatialLayers*/ 3u, /*temporalLayers*/ 3u); auto manager = createManager( @@ -1060,7 +1060,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("GetDesiredBitrate() returns 0 when no producer stream is set") { - const MockListener listener; + MockListener listener; auto manager = createManager(&listener); auto nowMs = DepLibUV::GetTimeMs(); @@ -1071,7 +1071,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("GetDesiredBitrate() (full SVC) returns total stream bitrate") { - const MockListener listener; + MockListener listener; // Full SVC (ksvc=false). auto encodingContext = std::make_unique(3u, 3u, /*ksvc*/ false); auto manager = createManager( @@ -1096,7 +1096,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("GetDesiredBitrate() (K-SVC) returns the maximum spatial-layer bitrate") { - const MockListener listener; + MockListener listener; // K-SVC (ksvc=true): GetDesiredBitrate iterates per-layer bitrates. auto encodingContext = std::make_unique(3u, 3u, /*ksvc*/ true); auto manager = createManager( @@ -1124,7 +1124,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("RecalculateTargetLayers() returns false when no producer stream is set") { - const MockListener listener; + MockListener listener; auto manager = createManager(&listener); RTC::ConsumerTypes::VideoLayers newTargetLayers; @@ -1137,7 +1137,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("RecalculateTargetLayers() resets layers when stream score is 0") { - const MockListener listener; + MockListener listener; auto manager = createManager(&listener); auto rtpStream = createRtpStreamRecv(); @@ -1154,7 +1154,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("K-SVC: UpdateTargetLayers() requests keyframe on any spatial layer change") { - const MockListener listener; + MockListener listener; auto encodingContext = std::make_unique(3u, 3u, /*ksvc*/ true); auto manager = createManager( &listener, @@ -1209,7 +1209,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("IncreaseLayer() uses virtual bitrate when considerLoss is true and loss < 2%") { - const MockListener listener; + MockListener listener; auto encodingContext = std::make_unique(1u, 1u, /*ksvc*/ false); auto manager = createManager( &listener, @@ -1240,7 +1240,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") SECTION("IncreaseLayer() uses reduced virtual bitrate when considerLoss is true and loss > 10%") { - const MockListener listener; + MockListener listener; auto encodingContext = std::make_unique(1u, 1u, /*ksvc*/ false); auto manager = createManager( &listener, From 52271fd746bc8081b95e655cf4d5033bcb8217fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Fri, 20 Mar 2026 16:44:55 +0100 Subject: [PATCH 22/37] cosmetic --- worker/test/src/RTC/TestSvcProducerStreamManager.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/worker/test/src/RTC/TestSvcProducerStreamManager.cpp b/worker/test/src/RTC/TestSvcProducerStreamManager.cpp index f58b6e756c..64a481fd6d 100644 --- a/worker/test/src/RTC/TestSvcProducerStreamManager.cpp +++ b/worker/test/src/RTC/TestSvcProducerStreamManager.cpp @@ -47,10 +47,10 @@ namespace public: bool isActive{ true }; - int keyFrameRequestCount{ 0 }; + size_t keyFrameRequestCount{ 0 }; uint32_t lastKeyFrameRequestedMappedSsrc{ 0 }; - int layersChangedCount{ 0 }; - int needBitrateChangeCount{ 0 }; + size_t layersChangedCount{ 0 }; + size_t needBitrateChangeCount{ 0 }; }; class RtpStreamRecvListener : public RTC::RTP::RtpStreamRecv::Listener @@ -544,12 +544,12 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") // Start at layer 0. manager->UpdateTargetLayers(0, 0); - int kfCountBefore = listener.keyFrameRequestCount; + auto keyFrameRequestCountBefore = listener.keyFrameRequestCount; // Upgrade spatial: full-SVC requests a keyframe. manager->UpdateTargetLayers(1, 0); - REQUIRE(listener.keyFrameRequestCount > kfCountBefore); + REQUIRE(listener.keyFrameRequestCount > keyFrameRequestCountBefore); REQUIRE(listener.lastKeyFrameRequestedMappedSsrc == mappedSsrc); } From 6b079f2cb9c2a1fa7e19bae00f0c739262c361f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Fri, 20 Mar 2026 16:49:07 +0100 Subject: [PATCH 23/37] cosmetic --- worker/test/src/RTC/TestSvcProducerStreamManager.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/worker/test/src/RTC/TestSvcProducerStreamManager.cpp b/worker/test/src/RTC/TestSvcProducerStreamManager.cpp index 64a481fd6d..97006af37d 100644 --- a/worker/test/src/RTC/TestSvcProducerStreamManager.cpp +++ b/worker/test/src/RTC/TestSvcProducerStreamManager.cpp @@ -544,7 +544,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") // Start at layer 0. manager->UpdateTargetLayers(0, 0); - auto keyFrameRequestCountBefore = listener.keyFrameRequestCount; + const auto keyFrameRequestCountBefore = listener.keyFrameRequestCount; // Upgrade spatial: full-SVC requests a keyframe. manager->UpdateTargetLayers(1, 0); @@ -889,7 +889,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); manager->UpdateTargetLayers(1, 1); - int layersChangedBefore = listener.layersChangedCount; + const auto layersChangedBefore = listener.layersChangedCount; // Score drops to 0 — MayChangeLayers is called, RecalculateTargetLayers // returns -1,-1 (no active stream), UpdateTargetLayers fires layersChanged. @@ -1145,7 +1145,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") // Score is 0 by default. RTC::ConsumerTypes::VideoLayers newTargetLayers; - bool changed = manager->RecalculateTargetLayers(newTargetLayers); + const auto changed = manager->RecalculateTargetLayers(newTargetLayers); REQUIRE(newTargetLayers.spatial == -1); // changed may be false because initial target is also -1. @@ -1169,12 +1169,12 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") // For K-SVC, even a downgrade should trigger a keyframe request. manager->UpdateTargetLayers(2, 0); - int kfCountBefore = listener.keyFrameRequestCount; + const auto keyFrameRequestCountBefore = listener.keyFrameRequestCount; // Downgrade in K-SVC also requests a keyframe. manager->UpdateTargetLayers(0, 0); - REQUIRE(listener.keyFrameRequestCount > kfCountBefore); + REQUIRE(listener.keyFrameRequestCount > keyFrameRequestCountBefore); REQUIRE(listener.lastKeyFrameRequestedMappedSsrc == mappedSsrc); } From cd9b84c8d1526e4edd0a8e8a226251532ef8981d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Mon, 23 Mar 2026 17:01:42 +0100 Subject: [PATCH 24/37] WIP Consumer pipe-able --- worker/include/RTC/Consumer.hpp | 24 +- worker/src/RTC/Consumer.cpp | 473 +++++++++++++----- worker/src/RTC/PipeConsumer.cpp | 2 +- .../RTC/SimulcastProducerStreamManager.cpp | 14 +- 4 files changed, 365 insertions(+), 148 deletions(-) diff --git a/worker/include/RTC/Consumer.hpp b/worker/include/RTC/Consumer.hpp index 6e84294729..3b83d6ad8b 100644 --- a/worker/include/RTC/Consumer.hpp +++ b/worker/include/RTC/Consumer.hpp @@ -31,6 +31,9 @@ namespace RTC public RTC::RTP::RtpStreamSend::Listener, public RTC::ProducerStreamManager::Listener { + using RetransmissionBuffer = + std::map::SeqLowerThan>; + public: class Listener { @@ -172,9 +175,11 @@ namespace RTC void UserOnResumed(); private: - void CreateRtpStream(); - void StorePacketInTargetLayerRetransmissionBuffer( - RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket); + void CreateRtpStreams(); + static void StorePacketInTargetLayerRetransmissionBuffer( + RetransmissionBuffer& targetLayerRetransmissionBuffer, + RTC::RTP::Packet* packet, + RTC::RTP::SharedPacket& sharedPacket); /* Pure virtual methods inherited from RtpStreamSend::Listener. */ public: @@ -214,10 +219,16 @@ namespace RTC struct TraceEventTypes traceEventTypes; private: - // Allocated by this. - RTC::RTP::RtpStreamSend* rtpStream{ nullptr }; + bool pipe{ false }; // Others. std::vector rtpStreams; + absl::flat_hash_map mapMappedSsrcSsrc; + absl::flat_hash_map mapSsrcRtpStream; + absl::flat_hash_map> mapRtpStreamRtpSeqManager; + // Buffers to store packets that arrive earlier than the first packet of the + // video key frame. + absl::flat_hash_map + mapRtpStreamTargetLayerRetransmissionBuffer; std::vector mediaSsrcs; std::vector rtxSsrcs; bool transportConnected{ false }; @@ -225,10 +236,7 @@ namespace RTC bool producerPaused{ false }; bool producerClosed{ false }; bool lastSentPacketHasMarker{ false }; - RTC::SeqManager rtpSeqManager; std::unique_ptr producerStreamManager; - std::map::SeqLowerThan> - targetLayerRetransmissionBuffer; }; } // namespace RTC diff --git a/worker/src/RTC/Consumer.cpp b/worker/src/RTC/Consumer.cpp index 8488a5ce6f..e10c7ed6c5 100644 --- a/worker/src/RTC/Consumer.cpp +++ b/worker/src/RTC/Consumer.cpp @@ -38,6 +38,9 @@ namespace RTC { MS_TRACE(); + // TMP: Remove, pass it on 'data' arg. + bool pipe = false; + // This may throw. this->rtpParameters = RTC::RtpParameters(data->rtpParameters()); @@ -46,6 +49,12 @@ namespace RTC MS_THROW_TYPE_ERROR("empty rtpParameters.encodings"); } + // Ensure there are as many encodings as consumable encodings for pipe. + if (pipe && this->rtpParameters.encodings.size() != this->consumableRtpEncodings.size()) + { + MS_THROW_TYPE_ERROR("number of rtpParameters.encodings and consumableRtpEncodings do not match"); + } + // All encodings must have SSRCs. for (auto& encoding : this->rtpParameters.encodings) { @@ -195,14 +204,6 @@ namespace RTC // Build preferred layers from FBS data. RTC::ConsumerTypes::VideoLayers preferredLayers; - // Let's choose an initial output seq number between 1000 and 32768 to avoid - // libsrtp bug: - // https://github.com/versatica/mediasoup/issues/1437 - const auto initialOutputSeq = - Utils::Crypto::GetRandomUInt(1000u, std::numeric_limits::max() / 2); - - this->rtpSeqManager = RTC::SeqManager(initialOutputSeq); - // Create the appropriate ProducerStreamManager subclass based on type. switch (this->type) { @@ -402,8 +403,8 @@ namespace RTC } } - // Create RtpStreamSend instance for sending a single stream to the remote. - CreateRtpStream(); + // Create RtpStreamSend instances. + CreateRtpStreams(); // NOTE: This may throw. this->shared->channelMessageRegistrator->RegisterHandler( @@ -418,8 +419,16 @@ namespace RTC this->shared->channelMessageRegistrator->UnregisterHandler(this->id); - delete this->rtpStream; - this->targetLayerRetransmissionBuffer.clear(); + for (auto* rtpStream : this->rtpStreams) + { + delete rtpStream; + } + + this->rtpStreams.clear(); + this->mapMappedSsrcSsrc.clear(); + this->mapSsrcRtpStream.clear(); + this->mapRtpStreamRtpSeqManager.clear(); + this->mapRtpStreamTargetLayerRetransmissionBuffer.clear(); } flatbuffers::Offset Consumer::FillBuffer( @@ -429,9 +438,14 @@ namespace RTC // Call the base method. auto base = FillBufferBase(builder); - // Add rtpStream. + // Add rtpStreams. std::vector> rtpStreams; - rtpStreams.emplace_back(this->rtpStream->FillBuffer(builder)); + rtpStreams.reserve(this->rtpStreams.size()); + + for (const auto* rtpStream : this->rtpStreams) + { + rtpStreams.emplace_back(rtpStream->FillBuffer(builder)); + } auto targetLayers = this->producerStreamManager->GetTargetLayers(); @@ -523,9 +537,13 @@ namespace RTC MS_TRACE(); std::vector> rtpStreams; + rtpStreams.reserve(this->rtpStreams.size()); - // Add stats of our send stream. - rtpStreams.emplace_back(this->rtpStream->FillBufferStats(builder)); + // Add stats of our send streams. + for (auto* rtpStream : this->rtpStreams) + { + rtpStreams.emplace_back(rtpStream->FillBufferStats(builder)); + } // Add stats of the current recv stream. auto* producerCurrentRtpStream = this->producerStreamManager->GetProducerCurrentRtpStream(); @@ -545,6 +563,13 @@ namespace RTC MS_ASSERT(this->producerRtpStreamScores, "producerRtpStreamScores not set"); + // NOTE: Hardcoded values in PipeTransport. + if (this->pipe) + { + return FBS::Consumer::CreateConsumerScoreDirect(builder, 10, 10, this->producerRtpStreamScores); + } + + auto* rtpStream = this->mapSsrcRtpStream.begin()->second; uint8_t producerScore{ 0 }; auto* producerCurrentRtpStream = this->producerStreamManager->GetProducerCurrentRtpStream(); @@ -555,7 +580,7 @@ namespace RTC } return FBS::Consumer::CreateConsumerScoreDirect( - builder, this->rtpStream->GetScore(), producerScore, this->producerRtpStreamScores); + builder, rtpStream->GetScore(), producerScore, this->producerRtpStreamScores); } void Consumer::HandleRequest(Channel::ChannelRequest* request) @@ -577,7 +602,24 @@ namespace RTC { if (IsActive()) { - this->producerStreamManager->RequestKeyFrame(); + if (this->pipe) + { + if (this->kind != RTC::Media::Kind::VIDEO) + { + return; + } + + for (auto& consumableRtpEncoding : this->consumableRtpEncodings) + { + auto mappedSsrc = consumableRtpEncoding.ssrc; + + this->listener->OnConsumerKeyFrameRequested(this, mappedSsrc); + } + } + else + { + this->producerStreamManager->RequestKeyFrame(); + } } request->Accept(); @@ -587,8 +629,8 @@ namespace RTC case Channel::ChannelRequest::Method::CONSUMER_SET_PREFERRED_LAYERS: { - // Simple consumers have no layers concept. - if (this->type == RTC::RtpParameters::Type::SIMPLE) + // Simple consumers and pipes have no layers concept. + if (this->type == RTC::RtpParameters::Type::SIMPLE || this->pipe) { // Accept with empty preferred layers object. auto responseOffset = @@ -599,6 +641,7 @@ namespace RTC break; } + auto* rtpStream = this->mapSsrcRtpStream.begin()->second; auto previousPreferredLayers = this->producerStreamManager->GetPreferredLayers(); const auto* body = request->data->body_as(); @@ -609,9 +652,9 @@ namespace RTC // Spatial layer. newPreferredLayers.spatial = preferredLayers->spatialLayer(); - if (newPreferredLayers.spatial > this->rtpStream->GetSpatialLayers() - 1) + if (newPreferredLayers.spatial > rtpStream->GetSpatialLayers() - 1) { - newPreferredLayers.spatial = static_cast(this->rtpStream->GetSpatialLayers() - 1); + newPreferredLayers.spatial = static_cast(rtpStream->GetSpatialLayers() - 1); } // preferredTemporalLayer is optional. @@ -621,16 +664,14 @@ namespace RTC { newPreferredLayers.temporal = preferredTemporalLayer.value(); - if (newPreferredLayers.temporal > this->rtpStream->GetTemporalLayers() - 1) + if (newPreferredLayers.temporal > rtpStream->GetTemporalLayers() - 1) { - newPreferredLayers.temporal = - static_cast(this->rtpStream->GetTemporalLayers() - 1); + newPreferredLayers.temporal = static_cast(rtpStream->GetTemporalLayers() - 1); } } else { - newPreferredLayers.temporal = - static_cast(this->rtpStream->GetTemporalLayers() - 1); + newPreferredLayers.temporal = static_cast(rtpStream->GetTemporalLayers() - 1); } this->producerStreamManager->SetPreferredLayers(newPreferredLayers); @@ -948,11 +989,19 @@ namespace RTC MS_ASSERT(this->externallyManagedBitrate, "bitrate is not externally managed"); MS_ASSERT(IsActive(), "should be active"); + if (this->pipe) + { + // PipeConsumer does not play the BWE game. + return 0u; + } + float lossPercentage{ 0.0f }; + auto* rtpStream = this->mapSsrcRtpStream.begin()->second; + if (considerLoss) { - lossPercentage = this->rtpStream->GetLossPercentage(); + lossPercentage = rtpStream->GetLossPercentage(); } auto nowMs = DepLibUV::GetTimeMs(); @@ -967,7 +1016,15 @@ namespace RTC MS_ASSERT(this->externallyManagedBitrate, "bitrate is not externally managed"); MS_ASSERT(IsActive(), "should be active"); - this->producerStreamManager->ApplyLayers(this->rtpStream->GetActiveMs()); + if (this->pipe) + { + // PipeConsumer does not play the BWE game. + return; + } + + auto* rtpStream = this->mapSsrcRtpStream.begin()->second; + + this->producerStreamManager->ApplyLayers(rtpStream->GetActiveMs()); } uint32_t Consumer::GetDesiredBitrate() const @@ -976,6 +1033,12 @@ namespace RTC MS_ASSERT(this->externallyManagedBitrate, "bitrate is not externally managed"); + if (this->pipe) + { + // PipeConsumer does not play the BWE game. + return 0u; + } + // Audio does not play the BWE game. if (this->kind != RTC::Media::Kind::VIDEO) { @@ -1008,13 +1071,33 @@ namespace RTC packet->logger.consumerId = this->id; #endif + RTC::RTP::RtpStreamSend* rtpStream; + RTC::SeqManager* rtpSeqManager; + RetransmissionBuffer* targetLayerRetransmissionBuffer; + + if (this->pipe) + { + auto ssrc = this->mapMappedSsrcSsrc.at(packet->GetSsrc()); + rtpStream = this->mapSsrcRtpStream.at(ssrc); + rtpSeqManager = &this->mapRtpStreamRtpSeqManager.at(rtpStream); + targetLayerRetransmissionBuffer = + &this->mapRtpStreamTargetLayerRetransmissionBuffer.at(rtpStream); + } + else + { + rtpStream = this->mapSsrcRtpStream.begin()->second; + rtpSeqManager = &this->mapRtpStreamRtpSeqManager.at(rtpStream); + targetLayerRetransmissionBuffer = + &this->mapRtpStreamTargetLayerRetransmissionBuffer.at(rtpStream); + } + if (!IsActive()) { #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::CONSUMER_INACTIVE); #endif - this->rtpSeqManager.Drop(packet->GetSequenceNumber()); + rtpSeqManager->Drop(packet->GetSequenceNumber()); return; } @@ -1031,24 +1114,20 @@ namespace RTC packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::UNSUPPORTED_PAYLOAD_TYPE); #endif - this->rtpSeqManager.Drop(packet->GetSequenceNumber()); + rtpSeqManager->Drop(packet->GetSequenceNumber()); return; } // Ask the ProducerStreamManager to process the packet. auto action = this->producerStreamManager->ProcessRtpPacket( - packet, - this->lastSentPacketHasMarker, - this->rtpStream->GetClockRate(), - this->rtpStream->GetMaxPacketTs()); + packet, this->lastSentPacketHasMarker, rtpStream->GetClockRate(), rtpStream->GetMaxPacketTs()); switch (action.type) { case ProducerStreamManager::RtpPacketProcessResult::Type::DROP: { - this->rtpSeqManager.Drop(packet->GetSequenceNumber()); - + rtpSeqManager->Drop(packet->GetSequenceNumber()); return; } @@ -1060,7 +1139,8 @@ namespace RTC case ProducerStreamManager::RtpPacketProcessResult::Type::BUFFER: { - StorePacketInTargetLayerRetransmissionBuffer(packet, sharedPacket); + StorePacketInTargetLayerRetransmissionBuffer( + *targetLayerRetransmissionBuffer, packet, sharedPacket); return; } @@ -1075,7 +1155,7 @@ namespace RTC // Handle sync. if (action.isSyncPacket) { - this->rtpSeqManager.Sync(action.syncSeqValue); + rtpSeqManager->Sync(action.syncSeqValue); } if (action.shouldSyncEncodingContext) @@ -1092,7 +1172,7 @@ namespace RTC if (action.spatialLayerSwitched) { // Reset the score of our RtpStream to 10. - this->rtpStream->ResetScore(10u, /*notify*/ false); + rtpStream->ResetScore(10u, /*notify*/ false); // Emit the layersChange event. EmitLayersChange(); @@ -1111,7 +1191,7 @@ namespace RTC uint16_t seq; const uint32_t timestamp = packet->GetTimestamp() - action.tsOffset; - this->rtpSeqManager.Input(packet->GetSequenceNumber(), seq); + rtpSeqManager->Input(packet->GetSequenceNumber(), seq); // Save original packet fields. auto origSsrc = packet->GetSsrc(); @@ -1145,11 +1225,11 @@ namespace RTC } const RTC::RTP::RtpStreamSend::ReceivePacketResult result = - this->rtpStream->ReceivePacket(packet, sharedPacket); + rtpStream->ReceivePacket(packet, sharedPacket); if (result != RTC::RTP::RtpStreamSend::ReceivePacketResult::DISCARDED) { - if (this->rtpSeqManager.GetMaxOutput() == packet->GetSequenceNumber()) + if (rtpSeqManager->GetMaxOutput() == packet->GetSequenceNumber()) { this->lastSentPacketHasMarker = packet->HasMarker(); } @@ -1203,7 +1283,7 @@ namespace RTC // key frame was sent. if (result != RTC::RTP::RtpStreamSend::ReceivePacketResult::DISCARDED) { - for (auto& kv : this->targetLayerRetransmissionBuffer) + for (auto& kv : *targetLayerRetransmissionBuffer) { auto& bufferedSharedPacket = kv.second; auto* bufferedPacket = bufferedSharedPacket.GetPacket(); @@ -1227,7 +1307,7 @@ namespace RTC // Be sure that the target layer retransmission buffer has not // been emptied as a result of sending this packet. If so, exit // the loop. - if (this->targetLayerRetransmissionBuffer.empty()) + if (targetLayerRetransmissionBuffer->empty()) { MS_DEBUG_DEV( "target layer retransmission buffer emptied while iterating " @@ -1239,7 +1319,7 @@ namespace RTC } } - this->targetLayerRetransmissionBuffer.clear(); + targetLayerRetransmissionBuffer->clear(); } } @@ -1247,25 +1327,51 @@ namespace RTC { MS_TRACE(); - if (static_cast((nowMs - this->lastRtcpSentTime) * 1.15) < this->maxRtcpInterval) + // Special condition for PipeConsumer since this method will be called in a + // loop for each stream in this PipeConsumer. + if (this->pipe) + { + if ( + nowMs != this->lastRtcpSentTime && + static_cast((nowMs - this->lastRtcpSentTime) * 1.15) < this->maxRtcpInterval) + { + return true; + } + } + else if (static_cast((nowMs - this->lastRtcpSentTime) * 1.15) < this->maxRtcpInterval) { return true; } - auto* senderReport = this->rtpStream->GetRtcpSenderReport(nowMs); + std::vector senderReports; + std::vector sdesChunks; + std::vector delaySinceLastRrSsrcInfos; - if (!senderReport) + for (auto* rtpStream : this->rtpStreams) { - return true; - } + auto* report = rtpStream->GetRtcpSenderReport(nowMs); + + if (!report) + { + continue; + } - // Build SDES chunk for this sender. - auto* sdesChunk = this->rtpStream->GetRtcpSdesChunk(); + senderReports.push_back(report); - auto* delaySinceLastRrSsrcInfo = this->rtpStream->GetRtcpXrDelaySinceLastRrSsrcInfo(nowMs); + // Build SDES chunk for this sender. + auto* sdesChunk = rtpStream->GetRtcpSdesChunk(); + sdesChunks.push_back(sdesChunk); + + auto* delaySinceLastRrSsrcInfo = rtpStream->GetRtcpXrDelaySinceLastRrSsrcInfo(nowMs); + + if (delaySinceLastRrSsrcInfo) + { + delaySinceLastRrSsrcInfos.push_back(delaySinceLastRrSsrcInfo); + } + } // RTCP Compound packet buffer cannot hold the data. - if (!packet->Add(senderReport, sdesChunk, delaySinceLastRrSsrcInfo)) + if (!packet->Add(senderReports, sdesChunks, delaySinceLastRrSsrcInfos)) { return false; } @@ -1284,10 +1390,13 @@ namespace RTC return; } - auto fractionLost = this->rtpStream->GetFractionLost(); + for (auto* rtpStream : this->rtpStreams) + { + auto fractionLost = rtpStream->GetFractionLost(); - // If our fraction lost is worse than the given one, update it. - worstRemoteFractionLost = std::max(fractionLost, worstRemoteFractionLost); + // If our fraction lost is worse than the given one, update it. + worstRemoteFractionLost = std::max(fractionLost, worstRemoteFractionLost); + } } void Consumer::ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket) @@ -1302,13 +1411,30 @@ namespace RTC // May emit 'trace' event. EmitTraceEventNackType(); - this->rtpStream->ReceiveNack(nackPacket); + RTC::RTP::RtpStreamSend* rtpStream; + + if (this->pipe) + { + auto ssrc = nackPacket->GetMediaSsrc(); + rtpStream = this->mapSsrcRtpStream.at(ssrc); + } + else + { + rtpStream = this->mapSsrcRtpStream.begin()->second; + } + + rtpStream->ReceiveNack(nackPacket); } void Consumer::ReceiveKeyFrameRequest(RTC::RTCP::FeedbackPs::MessageType messageType, uint32_t ssrc) { MS_TRACE(); + if (this->kind != RTC::Media::Kind::VIDEO) + { + return; + } + switch (messageType) { case RTC::RTCP::FeedbackPs::MessageType::PLI: @@ -1328,11 +1454,25 @@ namespace RTC default:; } - this->rtpStream->ReceiveKeyFrameRequest(messageType); + auto* rtpStream = this->mapSsrcRtpStream.at(ssrc); + + rtpStream->ReceiveKeyFrameRequest(messageType); if (IsActive()) { - this->producerStreamManager->RequestKeyFrameForCurrentSpatialLayer(); + if (this->pipe) + { + for (auto& consumableRtpEncoding : this->consumableRtpEncodings) + { + auto mappedSsrc = consumableRtpEncoding.ssrc; + + this->listener->OnConsumerKeyFrameRequested(this, mappedSsrc); + } + } + else + { + this->producerStreamManager->RequestKeyFrameForCurrentSpatialLayer(); + } } } @@ -1340,14 +1480,19 @@ namespace RTC { MS_TRACE(); - this->rtpStream->ReceiveRtcpReceiverReport(report); + auto* rtpStream = this->mapSsrcRtpStream.at(report->GetSsrc()); + + rtpStream->ReceiveRtcpReceiverReport(report); } void Consumer::ReceiveRtcpXrReceiverReferenceTime(RTC::RTCP::ReceiverReferenceTime* report) { MS_TRACE(); - this->rtpStream->ReceiveRtcpXrReceiverReferenceTime(report); + for (auto* rtpStream : this->rtpStreams) + { + rtpStream->ReceiveRtcpXrReceiverReferenceTime(report); + } } uint32_t Consumer::GetTransmissionRate(uint64_t nowMs) @@ -1359,14 +1504,28 @@ namespace RTC return 0u; } - return this->rtpStream->GetBitrate(nowMs); + uint32_t rate{ 0u }; + + for (auto* rtpStream : this->rtpStreams) + { + rate += rtpStream->GetBitrate(nowMs); + } + + return rate; } float Consumer::GetRtt() const { MS_TRACE(); - return this->rtpStream->GetRtt(); + float rtt{ 0 }; + + for (auto* rtpStream : this->rtpStreams) + { + rtt = std::max(rtpStream->GetRtt(), rtt); + } + + return rtt; } void Consumer::UserOnTransportConnected() @@ -1380,8 +1539,17 @@ namespace RTC { MS_TRACE(); - this->rtpStream->Pause(); - this->targetLayerRetransmissionBuffer.clear(); + for (auto* rtpStream : this->rtpStreams) + { + rtpStream->Pause(); + } + + for (auto& kv : this->mapRtpStreamTargetLayerRetransmissionBuffer) + { + auto& targetLayerRetransmissionBuffer = kv.second; + + targetLayerRetransmissionBuffer.clear(); + } this->producerStreamManager->OnTransportDisconnected(); } @@ -1390,8 +1558,17 @@ namespace RTC { MS_TRACE(); - this->rtpStream->Pause(); - this->targetLayerRetransmissionBuffer.clear(); + for (auto* rtpStream : this->rtpStreams) + { + rtpStream->Pause(); + } + + for (auto& kv : this->mapRtpStreamTargetLayerRetransmissionBuffer) + { + auto& targetLayerRetransmissionBuffer = kv.second; + + targetLayerRetransmissionBuffer.clear(); + } this->producerStreamManager->OnPaused(); @@ -1414,92 +1591,115 @@ namespace RTC this->producerStreamManager->OnResumed(); } - void Consumer::CreateRtpStream() + void Consumer::CreateRtpStreams() { MS_TRACE(); - auto& encoding = this->rtpParameters.encodings[0]; - const auto* mediaCodec = this->rtpParameters.GetCodecForEncoding(encoding); - - MS_DEBUG_TAG( - rtp, "[ssrc:%" PRIu32 ", payloadType:%" PRIu8 "]", encoding.ssrc, mediaCodec->payloadType); - - // Set stream params. - RTC::RTP::RtpStream::Params params; - - params.ssrc = encoding.ssrc; - params.payloadType = mediaCodec->payloadType; - params.mimeType = mediaCodec->mimeType; - params.clockRate = mediaCodec->clockRate; - params.cname = this->rtpParameters.rtcp.cname; - params.spatialLayers = encoding.spatialLayers; - params.temporalLayers = encoding.temporalLayers; - - // Check in band FEC in codec parameters. - if (mediaCodec->parameters.HasInteger("useinbandfec") && mediaCodec->parameters.GetInteger("useinbandfec") == 1) + // NOTE: Here we know that SSRCs in Consumer's rtpParameters must be the same + // as in the given consumableRtpEncodings. + for (size_t idx{ 0u }; idx < this->rtpParameters.encodings.size(); ++idx) { - MS_DEBUG_TAG(rtp, "in band FEC enabled"); + auto& encoding = this->rtpParameters.encodings[idx]; + const auto* mediaCodec = this->rtpParameters.GetCodecForEncoding(encoding); + auto& consumableEncoding = this->consumableRtpEncodings[idx]; - params.useInBandFec = true; - } - - // Check DTX in codec parameters. - if (mediaCodec->parameters.HasInteger("usedtx") && mediaCodec->parameters.GetInteger("usedtx") == 1) - { - MS_DEBUG_TAG(rtp, "DTX enabled"); + MS_DEBUG_TAG( + rtp, "[ssrc:%" PRIu32 ", payloadType:%" PRIu8 "]", encoding.ssrc, mediaCodec->payloadType); + + // Set stream params. + RTC::RTP::RtpStream::Params params; + + params.encodingIdx = idx; + params.ssrc = encoding.ssrc; + params.payloadType = mediaCodec->payloadType; + params.mimeType = mediaCodec->mimeType; + params.clockRate = mediaCodec->clockRate; + params.cname = this->rtpParameters.rtcp.cname; + params.spatialLayers = encoding.spatialLayers; + params.temporalLayers = encoding.temporalLayers; + + // Check in band FEC in codec parameters. + if (mediaCodec->parameters.HasInteger("useinbandfec") && mediaCodec->parameters.GetInteger("useinbandfec") == 1) + { + MS_DEBUG_TAG(rtp, "in band FEC enabled"); - params.useDtx = true; - } + params.useInBandFec = true; + } - // Check DTX in the encoding. - if (encoding.dtx) - { - MS_DEBUG_TAG(rtp, "DTX enabled"); + // Check DTX in codec parameters. + if (mediaCodec->parameters.HasInteger("usedtx") && mediaCodec->parameters.GetInteger("usedtx") == 1) + { + MS_DEBUG_TAG(rtp, "DTX enabled"); - params.useDtx = true; - } + params.useDtx = true; + } - for (const auto& fb : mediaCodec->rtcpFeedback) - { - if (!params.useNack && fb.type == "nack" && fb.parameter.empty()) + // Check DTX in the encoding. + if (encoding.dtx) { - MS_DEBUG_2TAGS(rtp, rtcp, "NACK supported"); + MS_DEBUG_TAG(rtp, "DTX enabled"); - params.useNack = true; + params.useDtx = true; } - else if (!params.usePli && fb.type == "nack" && fb.parameter == "pli") + + for (const auto& fb : mediaCodec->rtcpFeedback) { - MS_DEBUG_2TAGS(rtp, rtcp, "PLI supported"); + if (!params.useNack && fb.type == "nack" && fb.parameter.empty()) + { + MS_DEBUG_2TAGS(rtp, rtcp, "NACK supported"); - params.usePli = true; + params.useNack = true; + } + else if (!params.usePli && fb.type == "nack" && fb.parameter == "pli") + { + MS_DEBUG_2TAGS(rtp, rtcp, "PLI supported"); + + params.usePli = true; + } + else if (!params.useFir && fb.type == "ccm" && fb.parameter == "fir") + { + MS_DEBUG_2TAGS(rtp, rtcp, "FIR supported"); + + params.useFir = true; + } } - else if (!params.useFir && fb.type == "ccm" && fb.parameter == "fir") + + auto* rtpStream = new RTC::RTP::RtpStreamSend(this, params, this->rtpParameters.mid); + + // If the Consumer is paused, tell the RtpStreamSend. + if (IsPaused() || IsProducerPaused()) { - MS_DEBUG_2TAGS(rtp, rtcp, "FIR supported"); + rtpStream->Pause(); + } + + const auto* rtxCodec = this->rtpParameters.GetRtxCodecForEncoding(encoding); - params.useFir = true; + if (rtxCodec && encoding.hasRtx) + { + rtpStream->SetRtx(rtxCodec->payloadType, encoding.rtx.ssrc); } - } - this->rtpStream = new RTC::RTP::RtpStreamSend(this, params, this->rtpParameters.mid); - this->rtpStreams.push_back(this->rtpStream); + this->rtpStreams.push_back(rtpStream); + this->mapMappedSsrcSsrc[consumableEncoding.ssrc] = encoding.ssrc; + this->mapSsrcRtpStream[encoding.ssrc] = rtpStream; - // If the Consumer is paused, tell the RtpStreamSend. - if (IsPaused() || IsProducerPaused()) - { - this->rtpStream->Pause(); - } + // Let's choose an initial output seq number between 1000 and 32768 to avoid + // libsrtp bug: + // https://github.com/versatica/mediasoup/issues/1437 + const auto initialOutputSeq = + Utils::Crypto::GetRandomUInt(1000u, std::numeric_limits::max() / 2); - const auto* rtxCodec = this->rtpParameters.GetRtxCodecForEncoding(encoding); + this->mapRtpStreamRtpSeqManager[rtpStream] = RTC::SeqManager(initialOutputSeq); - if (rtxCodec && encoding.hasRtx) - { - this->rtpStream->SetRtx(rtxCodec->payloadType, encoding.rtx.ssrc); + this->mapRtpStreamTargetLayerRetransmissionBuffer[rtpStream]; } } void Consumer::StorePacketInTargetLayerRetransmissionBuffer( - RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket) + std::map::SeqLowerThan>& + targetLayerRetransmissionBuffer, + RTC::RTP::Packet* packet, + RTC::RTP::SharedPacket& sharedPacket) { MS_TRACE(); @@ -1523,11 +1723,11 @@ namespace RTC sharedPacket.AssertSamePacket(packet); } - this->targetLayerRetransmissionBuffer[packet->GetSequenceNumber()] = sharedPacket; + targetLayerRetransmissionBuffer[packet->GetSequenceNumber()] = sharedPacket; - if (this->targetLayerRetransmissionBuffer.size() > TargetLayerRetransmissionBufferSize) + if (targetLayerRetransmissionBuffer.size() > TargetLayerRetransmissionBufferSize) { - this->targetLayerRetransmissionBuffer.erase(this->targetLayerRetransmissionBuffer.begin()); + targetLayerRetransmissionBuffer.erase(targetLayerRetransmissionBuffer.begin()); } } @@ -1711,14 +1911,14 @@ namespace RTC } void Consumer::OnRtpStreamRetransmitRtpPacket( - RTC::RTP::RtpStreamSend* /*rtpStream*/, RTC::RTP::Packet* packet) + RTC::RTP::RtpStreamSend* rtpStream, RTC::RTP::Packet* packet) { MS_TRACE(); this->listener->OnConsumerRetransmitRtpPacket(this, packet); // May emit 'trace' event. - EmitTraceEventRtpAndKeyFrameTypes(packet, this->rtpStream->HasRtx()); + EmitTraceEventRtpAndKeyFrameTypes(packet, rtpStream->HasRtx()); } /* ProducerStreamManager::Listener methods. */ @@ -1748,7 +1948,10 @@ namespace RTC { MS_TRACE(); - this->targetLayerRetransmissionBuffer.clear(); + for (auto& kv : this->mapRtpStreamTargetLayerRetransmissionBuffer) + { + kv.second.clear(); + } } void Consumer::OnProducerStreamManagerScore() diff --git a/worker/src/RTC/PipeConsumer.cpp b/worker/src/RTC/PipeConsumer.cpp index 83189e38a7..6bcec393f3 100644 --- a/worker/src/RTC/PipeConsumer.cpp +++ b/worker/src/RTC/PipeConsumer.cpp @@ -827,7 +827,7 @@ namespace RTC // Let's choose an initial output seq number between 1000 and 32768 to avoid // libsrtp bug: // https://github.com/versatica/mediasoup/issues/1437 - const uint16_t initialOutputSeq = + const auto initialOutputSeq = Utils::Crypto::GetRandomUInt(1000u, std::numeric_limits::max() / 2); this->mapRtpStreamRtpSeqManager[rtpStream] = RTC::SeqManager(initialOutputSeq); diff --git a/worker/src/RTC/SimulcastProducerStreamManager.cpp b/worker/src/RTC/SimulcastProducerStreamManager.cpp index 635d71b878..3992517a7f 100644 --- a/worker/src/RTC/SimulcastProducerStreamManager.cpp +++ b/worker/src/RTC/SimulcastProducerStreamManager.cpp @@ -459,13 +459,13 @@ namespace RTC // Check target layers validity. if (this->targetLayers.temporal == -1) { +#ifdef MS_RTC_LOGGER_RTP + packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::INVALID_TARGET_LAYER); +#endif + if (spatialLayer == this->currentSpatialLayer) { result.type = RtpPacketProcessResult::Type::DROP; - -#ifdef MS_RTC_LOGGER_RTP - packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::INVALID_TARGET_LAYER); -#endif } else { @@ -505,6 +505,9 @@ namespace RTC // drop it. else if (spatialLayer != this->currentSpatialLayer) { +#ifdef MS_RTC_LOGGER_RTP + packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::SPATIAL_LAYER_MISMATCH); +#endif result.type = RtpPacketProcessResult::Type::SILENT_DROP; return result; @@ -525,6 +528,9 @@ namespace RTC // Packets with only padding are not forwarded. if (packet->GetPayloadLength() == 0) { +#ifdef MS_RTC_LOGGER_RTP + packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::EMPTY_PAYLOAD); +#endif if (spatialLayer == this->currentSpatialLayer) { result.type = RtpPacketProcessResult::Type::DROP; From feb84bb335e715c0233d8906ccc12374b0ed27b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Mon, 23 Mar 2026 17:10:51 +0100 Subject: [PATCH 25/37] cosmetic --- worker/src/RTC/Consumer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/worker/src/RTC/Consumer.cpp b/worker/src/RTC/Consumer.cpp index e10c7ed6c5..bc131beb00 100644 --- a/worker/src/RTC/Consumer.cpp +++ b/worker/src/RTC/Consumer.cpp @@ -989,9 +989,9 @@ namespace RTC MS_ASSERT(this->externallyManagedBitrate, "bitrate is not externally managed"); MS_ASSERT(IsActive(), "should be active"); + // Pipe does not play the BWE game. if (this->pipe) { - // PipeConsumer does not play the BWE game. return 0u; } @@ -1016,9 +1016,9 @@ namespace RTC MS_ASSERT(this->externallyManagedBitrate, "bitrate is not externally managed"); MS_ASSERT(IsActive(), "should be active"); + // Pipe does not play the BWE game. if (this->pipe) { - // PipeConsumer does not play the BWE game. return; } @@ -1033,9 +1033,9 @@ namespace RTC MS_ASSERT(this->externallyManagedBitrate, "bitrate is not externally managed"); + // Pipe does not play the BWE game. if (this->pipe) { - // PipeConsumer does not play the BWE game. return 0u; } From f70f31e69f5e7e1b7e04c3a5694a909064d37b23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Fri, 15 May 2026 14:53:20 +0200 Subject: [PATCH 26/37] Adapt to Shared rework --- worker/include/RTC/OldConsumer.hpp | 6 ++-- worker/src/RTC/Consumer.cpp | 19 +++++------ worker/src/RTC/OldConsumer.cpp | 32 +++++++++---------- .../RTC/SimulcastProducerStreamManager.cpp | 1 + worker/src/RTC/SvcProducerStreamManager.cpp | 1 + 5 files changed, 31 insertions(+), 28 deletions(-) diff --git a/worker/include/RTC/OldConsumer.hpp b/worker/include/RTC/OldConsumer.hpp index f6c899e75e..59398d526f 100644 --- a/worker/include/RTC/OldConsumer.hpp +++ b/worker/include/RTC/OldConsumer.hpp @@ -2,6 +2,7 @@ #define MS_RTC_OLD_CONSUMER_HPP #include "common.hpp" +#include "Shared.hpp" #include "Channel/ChannelRequest.hpp" #include "Channel/ChannelSocket.hpp" #include "FBS/consumer.h" @@ -15,7 +16,6 @@ #include "RTC/RTP/RtpStreamSend.hpp" #include "RTC/RTP/SharedPacket.hpp" #include "RTC/RtpDictionaries.hpp" -#include "RTC/Shared.hpp" #include #include #include @@ -54,7 +54,7 @@ namespace RTC public: OldConsumer( - RTC::Shared* shared, + SharedInterface* shared, const std::string& id, const std::string& producerId, RTC::OldConsumer::Listener* listener, @@ -178,7 +178,7 @@ namespace RTC protected: // Passed by argument. - RTC::Shared* shared{ nullptr }; + SharedInterface* shared{ nullptr }; RTC::OldConsumer::Listener* listener{ nullptr }; RTC::Media::Kind kind; RTC::RtpParameters rtpParameters; diff --git a/worker/src/RTC/Consumer.cpp b/worker/src/RTC/Consumer.cpp index 6f73a071f4..99d51fb90e 100644 --- a/worker/src/RTC/Consumer.cpp +++ b/worker/src/RTC/Consumer.cpp @@ -406,7 +406,7 @@ namespace RTC CreateRtpStreams(); // NOTE: This may throw. - this->shared->channelMessageRegistrator->RegisterHandler( + this->shared->GetChannelMessageRegistrator()->RegisterHandler( this->id, /*channelRequestHandler*/ this, /*channelNotificationHandler*/ nullptr); @@ -416,7 +416,7 @@ namespace RTC { MS_TRACE(); - this->shared->channelMessageRegistrator->UnregisterHandler(this->id); + this->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id); for (auto* rtpStream : this->rtpStreams) { @@ -1666,7 +1666,8 @@ namespace RTC } } - auto* rtpStream = new RTC::RTP::RtpStreamSend(this, params, this->rtpParameters.mid); + auto* rtpStream = + new RTC::RTP::RtpStreamSend(this, this->shared, params, this->rtpParameters.mid); // If the Consumer is paused, tell the RtpStreamSend. if (IsPaused() || IsProducerPaused()) @@ -1737,12 +1738,12 @@ namespace RTC { MS_TRACE(); - auto scoreOffset = FillBufferScore(this->shared->channelNotifier->GetBufferBuilder()); + auto scoreOffset = FillBufferScore(this->shared->GetChannelNotifier()->GetBufferBuilder()); auto notificationOffset = FBS::Consumer::CreateScoreNotification( - this->shared->channelNotifier->GetBufferBuilder(), scoreOffset); + this->shared->GetChannelNotifier()->GetBufferBuilder(), scoreOffset); - this->shared->channelNotifier->Emit( + this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::CONSUMER_SCORE, FBS::Notification::Body::Consumer_ScoreNotification, @@ -1764,15 +1765,15 @@ namespace RTC if (this->producerStreamManager->GetCurrentSpatialLayer() >= 0) { layersOffset = FBS::Consumer::CreateConsumerLayers( - this->shared->channelNotifier->GetBufferBuilder(), + this->shared->GetChannelNotifier()->GetBufferBuilder(), this->producerStreamManager->GetCurrentSpatialLayer(), this->producerStreamManager->GetCurrentTemporalLayer()); } auto notificationOffset = FBS::Consumer::CreateLayersChangeNotification( - this->shared->channelNotifier->GetBufferBuilder(), layersOffset); + this->shared->GetChannelNotifier()->GetBufferBuilder(), layersOffset); - this->shared->channelNotifier->Emit( + this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::CONSUMER_LAYERS_CHANGE, FBS::Notification::Body::Consumer_LayersChangeNotification, diff --git a/worker/src/RTC/OldConsumer.cpp b/worker/src/RTC/OldConsumer.cpp index 724e33c562..1042365ff6 100644 --- a/worker/src/RTC/OldConsumer.cpp +++ b/worker/src/RTC/OldConsumer.cpp @@ -11,7 +11,7 @@ namespace RTC /* Instance methods. */ OldConsumer::OldConsumer( - RTC::Shared* shared, + SharedInterface* shared, const std::string& id, const std::string& producerId, Listener* listener, @@ -445,7 +445,7 @@ namespace RTC UserOnPaused(); } - this->shared->channelNotifier->Emit(this->id, FBS::Notification::Event::CONSUMER_PRODUCER_PAUSE); + this->shared->GetChannelNotifier()->Emit(this->id, FBS::Notification::Event::CONSUMER_PRODUCER_PAUSE); } void OldConsumer::ProducerResumed() @@ -466,7 +466,7 @@ namespace RTC UserOnResumed(); } - this->shared->channelNotifier->Emit(this->id, FBS::Notification::Event::CONSUMER_PRODUCER_RESUME); + this->shared->GetChannelNotifier()->Emit(this->id, FBS::Notification::Event::CONSUMER_PRODUCER_RESUME); } void OldConsumer::ProducerRtpStreamScores(const std::vector* scores) @@ -487,7 +487,7 @@ namespace RTC MS_DEBUG_DEV("Producer closed [consumerId:%s]", this->id.c_str()); - this->shared->channelNotifier->Emit(this->id, FBS::Notification::Event::CONSUMER_PRODUCER_CLOSE); + this->shared->GetChannelNotifier()->Emit(this->id, FBS::Notification::Event::CONSUMER_PRODUCER_CLOSE); this->listener->OnConsumerProducerClosed(this); } @@ -498,12 +498,12 @@ namespace RTC if (this->traceEventTypes.keyframe && packet->IsKeyFrame()) { - auto rtpPacketDump = packet->FillBuffer(this->shared->channelNotifier->GetBufferBuilder()); + auto rtpPacketDump = packet->FillBuffer(this->shared->GetChannelNotifier()->GetBufferBuilder()); auto traceInfo = FBS::Consumer::CreateKeyFrameTraceInfo( - this->shared->channelNotifier->GetBufferBuilder(), rtpPacketDump, isRtx); + this->shared->GetChannelNotifier()->GetBufferBuilder(), rtpPacketDump, isRtx); auto notification = FBS::Consumer::CreateTraceNotification( - this->shared->channelNotifier->GetBufferBuilder(), + this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::Consumer::TraceEventType::KEYFRAME, DepLibUV::GetTimeMs(), FBS::Common::TraceDirection::DIRECTION_OUT, @@ -514,12 +514,12 @@ namespace RTC } else if (this->traceEventTypes.rtp) { - auto rtpPacketDump = packet->FillBuffer(this->shared->channelNotifier->GetBufferBuilder()); + auto rtpPacketDump = packet->FillBuffer(this->shared->GetChannelNotifier()->GetBufferBuilder()); auto traceInfo = FBS::Consumer::CreateRtpTraceInfo( - this->shared->channelNotifier->GetBufferBuilder(), rtpPacketDump, isRtx); + this->shared->GetChannelNotifier()->GetBufferBuilder(), rtpPacketDump, isRtx); auto notification = FBS::Consumer::CreateTraceNotification( - this->shared->channelNotifier->GetBufferBuilder(), + this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::Consumer::TraceEventType::RTP, DepLibUV::GetTimeMs(), FBS::Common::TraceDirection::DIRECTION_OUT, @@ -540,10 +540,10 @@ namespace RTC } auto traceInfo = - FBS::Consumer::CreatePliTraceInfo(this->shared->channelNotifier->GetBufferBuilder(), ssrc); + FBS::Consumer::CreatePliTraceInfo(this->shared->GetChannelNotifier()->GetBufferBuilder(), ssrc); auto notification = FBS::Consumer::CreateTraceNotification( - this->shared->channelNotifier->GetBufferBuilder(), + this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::Consumer::TraceEventType::PLI, DepLibUV::GetTimeMs(), FBS::Common::TraceDirection::DIRECTION_IN, @@ -563,10 +563,10 @@ namespace RTC } auto traceInfo = - FBS::Consumer::CreateFirTraceInfo(this->shared->channelNotifier->GetBufferBuilder(), ssrc); + FBS::Consumer::CreateFirTraceInfo(this->shared->GetChannelNotifier()->GetBufferBuilder(), ssrc); auto notification = FBS::Consumer::CreateTraceNotification( - this->shared->channelNotifier->GetBufferBuilder(), + this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::Consumer::TraceEventType::FIR, DepLibUV::GetTimeMs(), FBS::Common::TraceDirection::DIRECTION_IN, @@ -586,7 +586,7 @@ namespace RTC } auto notification = FBS::Consumer::CreateTraceNotification( - this->shared->channelNotifier->GetBufferBuilder(), + this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::Consumer::TraceEventType::NACK, DepLibUV::GetTimeMs(), FBS::Common::TraceDirection::DIRECTION_IN); @@ -598,7 +598,7 @@ namespace RTC { MS_TRACE(); - this->shared->channelNotifier->Emit( + this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::CONSUMER_TRACE, FBS::Notification::Body::Consumer_TraceNotification, diff --git a/worker/src/RTC/SimulcastProducerStreamManager.cpp b/worker/src/RTC/SimulcastProducerStreamManager.cpp index 3992517a7f..c078b976dd 100644 --- a/worker/src/RTC/SimulcastProducerStreamManager.cpp +++ b/worker/src/RTC/SimulcastProducerStreamManager.cpp @@ -2,6 +2,7 @@ // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SimulcastProducerStreamManager.hpp" +#include "DepLibUV.hpp" #include "Logger.hpp" namespace RTC diff --git a/worker/src/RTC/SvcProducerStreamManager.cpp b/worker/src/RTC/SvcProducerStreamManager.cpp index cc57434052..fdc87817d9 100644 --- a/worker/src/RTC/SvcProducerStreamManager.cpp +++ b/worker/src/RTC/SvcProducerStreamManager.cpp @@ -2,6 +2,7 @@ // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SvcProducerStreamManager.hpp" +#include "DepLibUV.hpp" #include "Logger.hpp" namespace RTC From ef73cffa7adc893c521c0ef2ab6ebeaa0ae5137a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Fri, 15 May 2026 15:00:36 +0200 Subject: [PATCH 27/37] fix tests after merge --- .../test/src/RTC/TestSimpleProducerStreamManager.cpp | 12 ++++++++++-- .../src/RTC/TestSimulcastProducerStreamManager.cpp | 12 ++++++++++-- worker/test/src/RTC/TestSvcProducerStreamManager.cpp | 12 ++++++++++-- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp b/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp index c1c73deecf..49362bc9c0 100644 --- a/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp +++ b/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp @@ -1,4 +1,6 @@ +#include "DepLibUV.hpp" #include "Utils.hpp" +#include "mocks/include/MockShared.hpp" #include "RTC/RTP/Codecs/PayloadDescriptorHandler.hpp" #include "RTC/RTP/RtpStreamRecv.hpp" #include "RTC/RTP/rtpCommon.hpp" @@ -154,6 +156,11 @@ namespace // RtpStreamRecvListener must outlive the RtpStreamRecv. RtpStreamRecvListener streamRecvListener; // NOLINT(readability-identifier-naming) + mocks::MockShared mockShared( + []() + { + return DepLibUV::GetTimeMs(); + }); // NOLINT(readability-identifier-naming) std::unique_ptr createRtpStreamRecv() { @@ -162,7 +169,8 @@ namespace params.ssrc = mappedSsrc; params.clockRate = 90000; - return std::make_unique(&streamRecvListener, params, 0u, false); + return std::make_unique( + &streamRecvListener, &mockShared, params, 0u, false); } // Feed packets into the RtpStreamRecv so GetBitrate() returns non-zero. @@ -171,7 +179,7 @@ namespace auto firstSeq = static_cast(packet->GetSequenceNumber() + 1); auto lastSeq = static_cast(firstSeq + count); - for (uint16_t seq = firstSeq; Utils::Number::IsLowerThan(seq, lastSeq); ++seq) + for (uint16_t seq = firstSeq; Utils::Number::IsLowerThan(seq, lastSeq); ++seq) { packet->SetSequenceNumber(seq); rtpStream->ReceivePacket(packet); diff --git a/worker/test/src/RTC/TestSimulcastProducerStreamManager.cpp b/worker/test/src/RTC/TestSimulcastProducerStreamManager.cpp index 065f720b02..7a9697b9b2 100644 --- a/worker/test/src/RTC/TestSimulcastProducerStreamManager.cpp +++ b/worker/test/src/RTC/TestSimulcastProducerStreamManager.cpp @@ -1,4 +1,6 @@ +#include "DepLibUV.hpp" #include "Utils.hpp" +#include "mocks/include/MockShared.hpp" #include "RTC/RTCP/SenderReport.hpp" #include "RTC/RTP/Codecs/PayloadDescriptorHandler.hpp" #include "RTC/RTP/RtpStreamRecv.hpp" @@ -173,6 +175,11 @@ namespace // RtpStreamRecvListener must outlive the RtpStreamRecv. RtpStreamRecvListener streamRecvListener; // NOLINT(readability-identifier-naming) + mocks::MockShared mockShared( + []() + { + return DepLibUV::GetTimeMs(); + }); // NOLINT(readability-identifier-naming) std::unique_ptr createRtpStreamRecv(uint32_t ssrc) { @@ -181,7 +188,8 @@ namespace params.ssrc = ssrc; params.clockRate = 90000; - return std::make_unique(&streamRecvListener, params, 0u, false); + return std::make_unique( + &streamRecvListener, &mockShared, params, 0u, false); } // Feed packets into the RtpStreamRecv so GetBitrate() returns non-zero. @@ -190,7 +198,7 @@ namespace auto firstSeq = static_cast(packet->GetSequenceNumber() + 1); auto lastSeq = static_cast(firstSeq + count); - for (uint16_t seq = firstSeq; Utils::Number::IsLowerThan(seq, lastSeq); ++seq) + for (uint16_t seq = firstSeq; Utils::Number::IsLowerThan(seq, lastSeq); ++seq) { packet->SetSequenceNumber(seq); rtpStream->ReceivePacket(packet); diff --git a/worker/test/src/RTC/TestSvcProducerStreamManager.cpp b/worker/test/src/RTC/TestSvcProducerStreamManager.cpp index 97006af37d..17f8339df7 100644 --- a/worker/test/src/RTC/TestSvcProducerStreamManager.cpp +++ b/worker/test/src/RTC/TestSvcProducerStreamManager.cpp @@ -1,4 +1,6 @@ +#include "DepLibUV.hpp" #include "Utils.hpp" +#include "mocks/include/MockShared.hpp" #include "RTC/RTP/Codecs/PayloadDescriptorHandler.hpp" #include "RTC/RTP/RtpStreamRecv.hpp" #include "RTC/RTP/rtpCommon.hpp" @@ -173,6 +175,11 @@ namespace // RtpStreamRecvListener must outlive the RtpStreamRecv. RtpStreamRecvListener streamRecvListener; // NOLINT(readability-identifier-naming) + mocks::MockShared mockShared( + []() + { + return DepLibUV::GetTimeMs(); + }); // NOLINT(readability-identifier-naming) std::unique_ptr createRtpStreamRecv( uint32_t ssrc = mappedSsrc, uint8_t spatialLayers = 3u, uint8_t temporalLayers = 3u) @@ -184,7 +191,8 @@ namespace params.spatialLayers = spatialLayers; params.temporalLayers = temporalLayers; - return std::make_unique(&streamRecvListener, params, 0u, false); + return std::make_unique( + &streamRecvListener, &mockShared, params, 0u, false); } // Feed packets into the RtpStreamRecv so GetBitrate() returns non-zero. @@ -193,7 +201,7 @@ namespace auto firstSeq = static_cast(packet->GetSequenceNumber() + 1); auto lastSeq = static_cast(firstSeq + count); - for (uint16_t seq = firstSeq; Utils::Number::IsLowerThan(seq, lastSeq); ++seq) + for (uint16_t seq = firstSeq; Utils::Number::IsLowerThan(seq, lastSeq); ++seq) { packet->SetSequenceNumber(seq); rtpStream->ReceivePacket(packet); From bfb56eb8a425bce3701d4e466a1162980285866a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Fri, 15 May 2026 15:07:50 +0200 Subject: [PATCH 28/37] more Shared related changes --- worker/include/RTC/ProducerStreamManager.hpp | 6 +++++- .../RTC/SimpleProducerStreamManager.hpp | 3 ++- .../RTC/SimulcastProducerStreamManager.hpp | 3 ++- .../include/RTC/SvcProducerStreamManager.hpp | 3 ++- worker/src/RTC/Consumer.cpp | 9 ++++++--- .../src/RTC/SimpleProducerStreamManager.cpp | 11 +++++++++-- .../RTC/SimulcastProducerStreamManager.cpp | 16 +++++++++++----- worker/src/RTC/SvcProducerStreamManager.cpp | 16 +++++++++++----- .../RTC/TestSimpleProducerStreamManager.cpp | 19 ++++++++++--------- .../TestSimulcastProducerStreamManager.cpp | 19 ++++++++++--------- .../src/RTC/TestSvcProducerStreamManager.cpp | 19 ++++++++++--------- 11 files changed, 78 insertions(+), 46 deletions(-) diff --git a/worker/include/RTC/ProducerStreamManager.hpp b/worker/include/RTC/ProducerStreamManager.hpp index 3f767e94ee..deb8524f6d 100644 --- a/worker/include/RTC/ProducerStreamManager.hpp +++ b/worker/include/RTC/ProducerStreamManager.hpp @@ -3,6 +3,7 @@ #include "common.hpp" #include "Logger.hpp" +#include "SharedInterface.hpp" #include "RTC/ConsumerTypes.hpp" #include "RTC/RTP/Codecs/PayloadDescriptorHandler.hpp" #include "RTC/RTP/Packet.hpp" @@ -60,8 +61,10 @@ namespace RTC std::unique_ptr encodingContext, RTC::Media::Kind kind, bool keyFrameSupported, - Listener* listener) + Listener* listener, + SharedInterface* shared) : listener(listener), + shared(shared), keyFrameSupported(keyFrameSupported), kind(kind), consumableRtpEncodings(consumableRtpEncodings), @@ -156,6 +159,7 @@ namespace RTC // Passed by argument. Listener* listener{ nullptr }; + SharedInterface* shared{ nullptr }; bool keyFrameSupported{ false }; RTC::Media::Kind kind{}; std::vector consumableRtpEncodings; diff --git a/worker/include/RTC/SimpleProducerStreamManager.hpp b/worker/include/RTC/SimpleProducerStreamManager.hpp index c6bf3e77f4..1f7f3f8c1d 100644 --- a/worker/include/RTC/SimpleProducerStreamManager.hpp +++ b/worker/include/RTC/SimpleProducerStreamManager.hpp @@ -14,7 +14,8 @@ namespace RTC std::unique_ptr encodingContext, RTC::Media::Kind kind, bool keyFrameSupported, - Listener* listener); + Listener* listener, + SharedInterface* shared); public: RTC::ConsumerTypes::VideoLayers GetTargetLayers() const override diff --git a/worker/include/RTC/SimulcastProducerStreamManager.hpp b/worker/include/RTC/SimulcastProducerStreamManager.hpp index 693f9496a0..caf7e0edf2 100644 --- a/worker/include/RTC/SimulcastProducerStreamManager.hpp +++ b/worker/include/RTC/SimulcastProducerStreamManager.hpp @@ -15,7 +15,8 @@ namespace RTC std::unique_ptr encodingContext, RTC::Media::Kind kind, bool keyFrameSupported, - Listener* listener); + Listener* listener, + SharedInterface* shared); public: RTC::ConsumerTypes::VideoLayers GetTargetLayers() const override diff --git a/worker/include/RTC/SvcProducerStreamManager.hpp b/worker/include/RTC/SvcProducerStreamManager.hpp index e8fc5ed3d6..49d3d5a391 100644 --- a/worker/include/RTC/SvcProducerStreamManager.hpp +++ b/worker/include/RTC/SvcProducerStreamManager.hpp @@ -14,7 +14,8 @@ namespace RTC std::unique_ptr encodingContext, RTC::Media::Kind kind, bool keyFrameSupported, - Listener* listener); + Listener* listener, + SharedInterface* shared); public: RTC::ConsumerTypes::VideoLayers GetTargetLayers() const override diff --git a/worker/src/RTC/Consumer.cpp b/worker/src/RTC/Consumer.cpp index 99d51fb90e..2304008cce 100644 --- a/worker/src/RTC/Consumer.cpp +++ b/worker/src/RTC/Consumer.cpp @@ -237,7 +237,8 @@ namespace RTC std::move(encodingContext), this->kind, keyFrameSupported, - this); + this, + this->shared); break; } @@ -313,7 +314,8 @@ namespace RTC std::move(encodingContext), this->kind, keyFrameSupported, - this); + this, + this->shared); break; } @@ -391,7 +393,8 @@ namespace RTC std::move(encodingContext), this->kind, keyFrameSupported, - this); + this, + this->shared); break; } diff --git a/worker/src/RTC/SimpleProducerStreamManager.cpp b/worker/src/RTC/SimpleProducerStreamManager.cpp index dc4b2a8a4a..6d3c471820 100644 --- a/worker/src/RTC/SimpleProducerStreamManager.cpp +++ b/worker/src/RTC/SimpleProducerStreamManager.cpp @@ -14,9 +14,16 @@ namespace RTC std::unique_ptr encodingContext, RTC::Media::Kind kind, bool keyFrameSupported, - Listener* listener) + Listener* listener, + SharedInterface* shared) : ProducerStreamManager( - consumableRtpEncodings, preferredLayers, std::move(encodingContext), kind, keyFrameSupported, listener) + consumableRtpEncodings, + preferredLayers, + std::move(encodingContext), + kind, + keyFrameSupported, + listener, + shared) { MS_TRACE(); } diff --git a/worker/src/RTC/SimulcastProducerStreamManager.cpp b/worker/src/RTC/SimulcastProducerStreamManager.cpp index c078b976dd..ecc8202392 100644 --- a/worker/src/RTC/SimulcastProducerStreamManager.cpp +++ b/worker/src/RTC/SimulcastProducerStreamManager.cpp @@ -2,7 +2,6 @@ // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SimulcastProducerStreamManager.hpp" -#include "DepLibUV.hpp" #include "Logger.hpp" namespace RTC @@ -22,9 +21,16 @@ namespace RTC std::unique_ptr encodingContext, RTC::Media::Kind kind, bool keyFrameSupported, - Listener* listener) + Listener* listener, + SharedInterface* shared) : ProducerStreamManager( - consumableRtpEncodings, preferredLayers, std::move(encodingContext), kind, keyFrameSupported, listener) + consumableRtpEncodings, + preferredLayers, + std::move(encodingContext), + kind, + keyFrameSupported, + listener, + shared) { MS_TRACE(); @@ -415,7 +421,7 @@ namespace RTC this->currentSpatialLayer, this->targetLayers.spatial); - this->lastBweDowngradeAtMs = DepLibUV::GetTimeMs(); + this->lastBweDowngradeAtMs = this->shared->GetTimeMs(); } } } @@ -898,7 +904,7 @@ namespace RTC // Start with no layers. newTargetLayers.Reset(); - auto nowMs = DepLibUV::GetTimeMs(); + auto nowMs = this->shared->GetTimeMs(); for (size_t sIdx{ 0u }; sIdx < this->producerRtpStreams.size(); ++sIdx) { diff --git a/worker/src/RTC/SvcProducerStreamManager.cpp b/worker/src/RTC/SvcProducerStreamManager.cpp index fdc87817d9..3a9c81dea3 100644 --- a/worker/src/RTC/SvcProducerStreamManager.cpp +++ b/worker/src/RTC/SvcProducerStreamManager.cpp @@ -2,7 +2,6 @@ // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SvcProducerStreamManager.hpp" -#include "DepLibUV.hpp" #include "Logger.hpp" namespace RTC @@ -20,9 +19,16 @@ namespace RTC std::unique_ptr encodingContext, RTC::Media::Kind kind, bool keyFrameSupported, - Listener* listener) + Listener* listener, + SharedInterface* shared) : ProducerStreamManager( - consumableRtpEncodings, preferredLayers, std::move(encodingContext), kind, keyFrameSupported, listener) + consumableRtpEncodings, + preferredLayers, + std::move(encodingContext), + kind, + keyFrameSupported, + listener, + shared) { MS_TRACE(); } @@ -315,7 +321,7 @@ namespace RTC this->encodingContext->GetCurrentSpatialLayer(), this->encodingContext->GetTargetSpatialLayer()); - this->lastBweDowngradeAtMs = DepLibUV::GetTimeMs(); + this->lastBweDowngradeAtMs = this->shared->GetTimeMs(); } } } @@ -546,7 +552,7 @@ namespace RTC // Start with no layers. newTargetLayers.Reset(); - auto nowMs = DepLibUV::GetTimeMs(); + auto nowMs = this->shared->GetTimeMs(); int16_t spatialLayer{ 0 }; if (!this->producerRtpStream) diff --git a/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp b/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp index 49362bc9c0..e5956fd4da 100644 --- a/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp +++ b/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp @@ -133,6 +133,14 @@ namespace bool processResult{ true }; }; + // RtpStreamRecvListener must outlive the RtpStreamRecv. + RtpStreamRecvListener streamRecvListener; // NOLINT(readability-identifier-naming) + mocks::MockShared mockShared( + []() + { + return DepLibUV::GetTimeMs(); + }); // NOLINT(readability-identifier-naming) + std::unique_ptr createManager( MockListener* listener, bool keyFrameSupported = true, @@ -151,17 +159,10 @@ namespace std::move(encodingContext), kind, keyFrameSupported, - listener); + listener, + &mockShared); } - // RtpStreamRecvListener must outlive the RtpStreamRecv. - RtpStreamRecvListener streamRecvListener; // NOLINT(readability-identifier-naming) - mocks::MockShared mockShared( - []() - { - return DepLibUV::GetTimeMs(); - }); // NOLINT(readability-identifier-naming) - std::unique_ptr createRtpStreamRecv() { RTC::RTP::RtpStream::Params params; diff --git a/worker/test/src/RTC/TestSimulcastProducerStreamManager.cpp b/worker/test/src/RTC/TestSimulcastProducerStreamManager.cpp index 7a9697b9b2..b5fb04778a 100644 --- a/worker/test/src/RTC/TestSimulcastProducerStreamManager.cpp +++ b/worker/test/src/RTC/TestSimulcastProducerStreamManager.cpp @@ -142,6 +142,14 @@ namespace bool processResult{ true }; }; + // RtpStreamRecvListener must outlive the RtpStreamRecv. + RtpStreamRecvListener streamRecvListener; // NOLINT(readability-identifier-naming) + mocks::MockShared mockShared( + []() + { + return DepLibUV::GetTimeMs(); + }); // NOLINT(readability-identifier-naming) + std::unique_ptr createManager( MockListener* listener, const std::vector& ssrcs = threeSsrcs, @@ -170,17 +178,10 @@ namespace std::move(encodingContext), kind, keyFrameSupported, - listener); + listener, + &mockShared); } - // RtpStreamRecvListener must outlive the RtpStreamRecv. - RtpStreamRecvListener streamRecvListener; // NOLINT(readability-identifier-naming) - mocks::MockShared mockShared( - []() - { - return DepLibUV::GetTimeMs(); - }); // NOLINT(readability-identifier-naming) - std::unique_ptr createRtpStreamRecv(uint32_t ssrc) { RTC::RTP::RtpStream::Params params; diff --git a/worker/test/src/RTC/TestSvcProducerStreamManager.cpp b/worker/test/src/RTC/TestSvcProducerStreamManager.cpp index 17f8339df7..2c2a06890f 100644 --- a/worker/test/src/RTC/TestSvcProducerStreamManager.cpp +++ b/worker/test/src/RTC/TestSvcProducerStreamManager.cpp @@ -148,6 +148,14 @@ namespace bool processResult{ true }; }; + // RtpStreamRecvListener must outlive the RtpStreamRecv. + RtpStreamRecvListener streamRecvListener; // NOLINT(readability-identifier-naming) + mocks::MockShared mockShared( + []() + { + return DepLibUV::GetTimeMs(); + }); // NOLINT(readability-identifier-naming) + std::unique_ptr createManager( MockListener* listener, RTC::ConsumerTypes::VideoLayers preferredLayers = { 2, 2 }, @@ -170,17 +178,10 @@ namespace std::move(encodingContext), kind, keyFrameSupported, - listener); + listener, + &mockShared); } - // RtpStreamRecvListener must outlive the RtpStreamRecv. - RtpStreamRecvListener streamRecvListener; // NOLINT(readability-identifier-naming) - mocks::MockShared mockShared( - []() - { - return DepLibUV::GetTimeMs(); - }); // NOLINT(readability-identifier-naming) - std::unique_ptr createRtpStreamRecv( uint32_t ssrc = mappedSsrc, uint8_t spatialLayers = 3u, uint8_t temporalLayers = 3u) { From 0d8100b44947f8bf9b1acfb458078c43f56099b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Fri, 15 May 2026 16:30:00 +0200 Subject: [PATCH 29/37] Fixes --- worker/include/RTC/ProducerStreamManager.hpp | 4 ++ .../RTC/SimpleProducerStreamManager.hpp | 4 ++ .../RTC/SimulcastProducerStreamManager.hpp | 9 +++++ .../include/RTC/SvcProducerStreamManager.hpp | 4 ++ worker/src/RTC/Consumer.cpp | 38 +++++++++---------- .../RTC/SimulcastProducerStreamManager.cpp | 18 +++++++++ worker/src/RTC/SvcProducerStreamManager.cpp | 3 ++ 7 files changed, 60 insertions(+), 20 deletions(-) diff --git a/worker/include/RTC/ProducerStreamManager.hpp b/worker/include/RTC/ProducerStreamManager.hpp index deb8524f6d..4cc040c73c 100644 --- a/worker/include/RTC/ProducerStreamManager.hpp +++ b/worker/include/RTC/ProducerStreamManager.hpp @@ -81,6 +81,10 @@ namespace RTC virtual int16_t GetCurrentTemporalLayer() const = 0; virtual RTC::RTP::RtpStreamRecv* GetProducerCurrentRtpStream() const = 0; virtual RTC::RTP::RtpStreamRecv* GetProducerTargetRtpStream() const = 0; + // Returns true if the given packet belongs to the stream currently being + // forwarded to the consumer. Used by Consumer to decide whether to account + // a discarded packet in the RTP sequence manager. + virtual bool IsPacketForCurrentStream(const RTC::RTP::Packet* packet) const = 0; RTC::RTP::Codecs::EncodingContext* GetEncodingContext() const { return this->encodingContext.get(); diff --git a/worker/include/RTC/SimpleProducerStreamManager.hpp b/worker/include/RTC/SimpleProducerStreamManager.hpp index 1f7f3f8c1d..570dbc2918 100644 --- a/worker/include/RTC/SimpleProducerStreamManager.hpp +++ b/worker/include/RTC/SimpleProducerStreamManager.hpp @@ -32,6 +32,10 @@ namespace RTC } RTC::RTP::RtpStreamRecv* GetProducerCurrentRtpStream() const override; RTC::RTP::RtpStreamRecv* GetProducerTargetRtpStream() const override; + bool IsPacketForCurrentStream(const RTC::RTP::Packet* /*packet*/) const override + { + return true; + } bool IsActive() const override; void ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; void ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; diff --git a/worker/include/RTC/SimulcastProducerStreamManager.hpp b/worker/include/RTC/SimulcastProducerStreamManager.hpp index caf7e0edf2..72cbf6a053 100644 --- a/worker/include/RTC/SimulcastProducerStreamManager.hpp +++ b/worker/include/RTC/SimulcastProducerStreamManager.hpp @@ -33,6 +33,15 @@ namespace RTC } RTC::RTP::RtpStreamRecv* GetProducerCurrentRtpStream() const override; RTC::RTP::RtpStreamRecv* GetProducerTargetRtpStream() const override; + bool IsPacketForCurrentStream(const RTC::RTP::Packet* packet) const override + { + const auto it = this->mapMappedSsrcSpatialLayer.find(packet->GetSsrc()); + if (it == this->mapMappedSsrcSpatialLayer.end()) + { + return false; + } + return it->second == this->currentSpatialLayer; + } bool IsActive() const override; void ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; void ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; diff --git a/worker/include/RTC/SvcProducerStreamManager.hpp b/worker/include/RTC/SvcProducerStreamManager.hpp index 49d3d5a391..1b72b32f6b 100644 --- a/worker/include/RTC/SvcProducerStreamManager.hpp +++ b/worker/include/RTC/SvcProducerStreamManager.hpp @@ -32,6 +32,10 @@ namespace RTC } RTC::RTP::RtpStreamRecv* GetProducerCurrentRtpStream() const override; RTC::RTP::RtpStreamRecv* GetProducerTargetRtpStream() const override; + bool IsPacketForCurrentStream(const RTC::RTP::Packet* /*packet*/) const override + { + return true; + } bool IsActive() const override; void ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; void ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; diff --git a/worker/src/RTC/Consumer.cpp b/worker/src/RTC/Consumer.cpp index 2304008cce..55d84c3bed 100644 --- a/worker/src/RTC/Consumer.cpp +++ b/worker/src/RTC/Consumer.cpp @@ -1098,11 +1098,13 @@ namespace RTC if (!IsActive()) { + if (this->producerStreamManager->IsPacketForCurrentStream(packet)) + { #ifdef MS_RTC_LOGGER_RTP - packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::CONSUMER_INACTIVE); + packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::CONSUMER_INACTIVE); #endif - - rtpSeqManager->Drop(packet->GetSequenceNumber()); + rtpSeqManager->Drop(packet->GetSequenceNumber()); + } return; } @@ -1113,13 +1115,15 @@ namespace RTC // in the corresponding Producer. if (!this->supportedCodecPayloadTypes[payloadType]) { - MS_WARN_DEV("payload type not supported [payloadType:%" PRIu8 "]", payloadType); + if (this->producerStreamManager->IsPacketForCurrentStream(packet)) + { + MS_WARN_DEV("payload type not supported [payloadType:%" PRIu8 "]", payloadType); #ifdef MS_RTC_LOGGER_RTP - packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::UNSUPPORTED_PAYLOAD_TYPE); + packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::UNSUPPORTED_PAYLOAD_TYPE); #endif - - rtpSeqManager->Drop(packet->GetSequenceNumber()); + rtpSeqManager->Drop(packet->GetSequenceNumber()); + } return; } @@ -1163,16 +1167,6 @@ namespace RTC rtpSeqManager->Sync(action.syncSeqValue); } - if (action.shouldSyncEncodingContext) - { - auto* encodingContext = this->producerStreamManager->GetEncodingContext(); - - if (encodingContext) - { - encodingContext->SyncRequired(); - } - } - // Handle spatial layer switch. if (action.spatialLayerSwitched) { @@ -1600,9 +1594,13 @@ namespace RTC { MS_TRACE(); - // NOTE: Here we know that SSRCs in Consumer's rtpParameters must be the same - // as in the given consumableRtpEncodings. - for (size_t idx{ 0u }; idx < this->rtpParameters.encodings.size(); ++idx) + // NOTE: For non-pipe consumers, all spatial layers are multiplexed through + // a single RtpStreamSend (encodings[0]). Pipe consumers need one stream per + // encoding. Here we know that SSRCs in Consumer's rtpParameters must be the + // same as in the given consumableRtpEncodings. + const size_t numStreams = this->pipe ? this->rtpParameters.encodings.size() : 1u; + + for (size_t idx{ 0u }; idx < numStreams; ++idx) { auto& encoding = this->rtpParameters.encodings[idx]; const auto* mediaCodec = this->rtpParameters.GetCodecForEncoding(encoding); diff --git a/worker/src/RTC/SimulcastProducerStreamManager.cpp b/worker/src/RTC/SimulcastProducerStreamManager.cpp index ecc8202392..cb4851a6c7 100644 --- a/worker/src/RTC/SimulcastProducerStreamManager.cpp +++ b/worker/src/RTC/SimulcastProducerStreamManager.cpp @@ -125,6 +125,16 @@ namespace RTC if (IsActive()) { MayChangeLayers(/*force*/ false); + + // If this stream is the target spatial layer and we are still waiting + // for a key frame (i.e. the previous RequestKeyFrameForTargetSpatialLayer + // call was a no-op because the stream wasn't set yet), request it now. + if ( + spatialLayer == this->targetLayers.spatial && + this->currentSpatialLayer != this->targetLayers.spatial) + { + RequestKeyFrameForTargetSpatialLayer(); + } } } @@ -701,6 +711,14 @@ namespace RTC result.syncSeqValue = packet->GetSequenceNumber() - (lastSentPacketHasMarker ? 1 : 2); result.shouldSyncEncodingContext = true; + // Sync the encoding context now, before ProcessPayload runs below. + // This must happen before ProcessPayload so the codec handler resets + // its state before processing the first (key frame) packet. + if (this->encodingContext) + { + this->encodingContext->SyncRequired(); + } + this->syncRequired = false; this->spatialLayerToSync = -1; this->keyFrameForTsOffsetRequested = false; diff --git a/worker/src/RTC/SvcProducerStreamManager.cpp b/worker/src/RTC/SvcProducerStreamManager.cpp index 3a9c81dea3..074366ba43 100644 --- a/worker/src/RTC/SvcProducerStreamManager.cpp +++ b/worker/src/RTC/SvcProducerStreamManager.cpp @@ -439,6 +439,9 @@ namespace RTC result.syncSeqValue = packet->GetSequenceNumber() - 1; result.shouldSyncEncodingContext = true; + // Sync the encoding context before ProcessPayload runs below. + this->encodingContext->SyncRequired(); + this->syncRequired = false; } From 92fd229a80dcc8392601d29c3b38420c330acd60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Fri, 15 May 2026 16:51:36 +0200 Subject: [PATCH 30/37] PipeProducerStreamManager --- .../include/RTC/PipeProducerStreamManager.hpp | 78 +++ worker/meson.build | 2 + worker/src/RTC/Consumer.cpp | 38 +- worker/src/RTC/PipeProducerStreamManager.cpp | 266 ++++++++++ .../src/RTC/SimpleProducerStreamManager.cpp | 4 +- worker/src/RTC/Transport.cpp | 14 +- .../src/RTC/TestPipeProducerStreamManager.cpp | 472 ++++++++++++++++++ 7 files changed, 849 insertions(+), 25 deletions(-) create mode 100644 worker/include/RTC/PipeProducerStreamManager.hpp create mode 100644 worker/src/RTC/PipeProducerStreamManager.cpp create mode 100644 worker/test/src/RTC/TestPipeProducerStreamManager.cpp diff --git a/worker/include/RTC/PipeProducerStreamManager.hpp b/worker/include/RTC/PipeProducerStreamManager.hpp new file mode 100644 index 0000000000..3cd03367f2 --- /dev/null +++ b/worker/include/RTC/PipeProducerStreamManager.hpp @@ -0,0 +1,78 @@ +#ifndef MS_RTC_PIPE_PRODUCER_STREAM_MANAGER_HPP +#define MS_RTC_PIPE_PRODUCER_STREAM_MANAGER_HPP + +#include "RTC/ProducerStreamManager.hpp" +#include + +namespace RTC +{ + class PipeProducerStreamManager : public ProducerStreamManager + { + public: + PipeProducerStreamManager( + const std::vector& consumableRtpEncodings, + const RTC::ConsumerTypes::VideoLayers& preferredLayers, + std::unique_ptr encodingContext, + RTC::Media::Kind kind, + bool keyFrameSupported, + Listener* listener, + SharedInterface* shared); + + public: + RTC::ConsumerTypes::VideoLayers GetTargetLayers() const override + { + return {}; + } + int16_t GetCurrentSpatialLayer() const override + { + return -1; + } + int16_t GetCurrentTemporalLayer() const override + { + return -1; + } + RTC::RTP::RtpStreamRecv* GetProducerCurrentRtpStream() const override + { + return nullptr; + } + RTC::RTP::RtpStreamRecv* GetProducerTargetRtpStream() const override + { + return nullptr; + } + bool IsPacketForCurrentStream(const RTC::RTP::Packet* /*packet*/) const override + { + // Pipe has no concept of "current" stream — all packets belong. + return true; + } + bool IsActive() const override; + void ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; + void ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; + void ProducerRtpStreamScore( + RTC::RTP::RtpStreamRecv* rtpStream, uint8_t score, uint8_t previousScore) override; + void ProducerRtcpSenderReport(RTC::RTP::RtpStreamRecv* rtpStream, bool first) override; + uint32_t IncreaseLayer( + uint32_t bitrate, bool considerLoss, float lossPercentage, uint64_t nowMs) override; + void ApplyLayers(uint64_t rtpStreamActiveMs) override; + uint32_t GetDesiredBitrate(uint64_t nowMs) const override; + RtpPacketProcessResult ProcessRtpPacket( + RTC::RTP::Packet* packet, + bool lastSentPacketHasMarker, + uint32_t clockRate, + uint32_t maxPacketTs) override; + void RequestKeyFrame() override; + void RequestKeyFrameForTargetSpatialLayer() override; + void RequestKeyFrameForCurrentSpatialLayer() override; + void UpdateTargetLayers(int16_t spatial, int16_t temporal) override; + bool RecalculateTargetLayers(RTC::ConsumerTypes::VideoLayers& newTargetLayers) const override; + void OnTransportConnected() override; + void OnTransportDisconnected() override; + void OnPaused() override; + void OnResumed() override; + + private: + // Per-stream sync state, keyed by mapped SSRC. + absl::flat_hash_map mapMappedSsrcSyncRequired; + }; +} // namespace RTC + +#endif diff --git a/worker/meson.build b/worker/meson.build index da299e719e..11b1361b57 100644 --- a/worker/meson.build +++ b/worker/meson.build @@ -139,6 +139,7 @@ common_sources = [ 'src/RTC/SeqManager.cpp', 'src/RTC/Serializable.cpp', 'src/RTC/SimpleConsumer.cpp', + 'src/RTC/PipeProducerStreamManager.cpp', 'src/RTC/SimpleProducerStreamManager.cpp', 'src/RTC/SimulcastConsumer.cpp', 'src/RTC/SimulcastProducerStreamManager.cpp', @@ -461,6 +462,7 @@ test_sources = [ 'test/src/tests.cpp', 'test/src/testHelpers.cpp', 'test/src/RTC/TestConsumer.cpp', + 'test/src/RTC/TestPipeProducerStreamManager.cpp', 'test/src/RTC/TestSimpleProducerStreamManager.cpp', 'test/src/RTC/TestSimulcastProducerStreamManager.cpp', 'test/src/RTC/TestSvcProducerStreamManager.cpp', diff --git a/worker/src/RTC/Consumer.cpp b/worker/src/RTC/Consumer.cpp index 55d84c3bed..488f88e396 100644 --- a/worker/src/RTC/Consumer.cpp +++ b/worker/src/RTC/Consumer.cpp @@ -5,6 +5,7 @@ #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Utils.hpp" +#include "RTC/PipeProducerStreamManager.hpp" #include "RTC/RTP/Codecs/Tools.hpp" #include "RTC/SimpleProducerStreamManager.hpp" #include "RTC/SimulcastProducerStreamManager.hpp" @@ -37,8 +38,7 @@ namespace RTC { MS_TRACE(); - // TMP: Remove, pass it on 'data' arg. - bool pipe = false; + this->pipe = this->type == RTC::RtpParameters::Type::PIPE; // This may throw. this->rtpParameters = RTC::RtpParameters(data->rtpParameters()); @@ -48,12 +48,6 @@ namespace RTC MS_THROW_TYPE_ERROR("empty rtpParameters.encodings"); } - // Ensure there are as many encodings as consumable encodings for pipe. - if (pipe && this->rtpParameters.encodings.size() != this->consumableRtpEncodings.size()) - { - MS_THROW_TYPE_ERROR("number of rtpParameters.encodings and consumableRtpEncodings do not match"); - } - // All encodings must have SSRCs. for (auto& encoding : this->rtpParameters.encodings) { @@ -90,6 +84,12 @@ namespace RTC } } + // Ensure there are as many encodings as consumable encodings for pipe. + if (pipe && this->rtpParameters.encodings.size() != this->consumableRtpEncodings.size()) + { + MS_THROW_TYPE_ERROR("number of rtpParameters.encodings and consumableRtpEncodings do not match"); + } + // Fill RTP header extension ids and their mapped values. // This may throw. for (auto& exten : this->rtpParameters.headerExtensions) @@ -399,6 +399,21 @@ namespace RTC break; } + case RTC::RtpParameters::Type::PIPE: + { + // Pipe consumer: no layer management, N streams. + this->producerStreamManager = std::make_unique( + this->consumableRtpEncodings, + preferredLayers, + nullptr, + this->kind, + keyFrameSupported, + this, + this->shared); + + break; + } + default: { MS_THROW_TYPE_ERROR("invalid consumer type"); @@ -1199,7 +1214,8 @@ namespace RTC const bool origMarker = packet->HasMarker(); // Rewrite packet. - packet->SetSsrc(this->rtpParameters.encodings[0].ssrc); + // For pipe each stream has its own SSRC; for non-pipe always encodings[0]. + packet->SetSsrc(this->pipe ? rtpStream->GetSsrc() : this->rtpParameters.encodings[0].ssrc); packet->SetSequenceNumber(seq); packet->SetTimestamp(timestamp); packet->SetMarker(action.marker); @@ -1326,8 +1342,8 @@ namespace RTC { MS_TRACE(); - // Special condition for PipeConsumer since this method will be called in a - // loop for each stream in this PipeConsumer. + // Special condition for pipe consumer since this method will be called in a + // loop for each stream. if (this->pipe) { if ( diff --git a/worker/src/RTC/PipeProducerStreamManager.cpp b/worker/src/RTC/PipeProducerStreamManager.cpp new file mode 100644 index 0000000000..0e97b0c137 --- /dev/null +++ b/worker/src/RTC/PipeProducerStreamManager.cpp @@ -0,0 +1,266 @@ +#define MS_CLASS "RTC::PipeProducerStreamManager" +// #define MS_LOG_DEV_LEVEL 3 + +#include "RTC/PipeProducerStreamManager.hpp" +#include "Logger.hpp" + +namespace RTC +{ + /* Instance methods. */ + + PipeProducerStreamManager::PipeProducerStreamManager( + const std::vector& consumableRtpEncodings, + const RTC::ConsumerTypes::VideoLayers& preferredLayers, + std::unique_ptr encodingContext, + RTC::Media::Kind kind, + bool keyFrameSupported, + Listener* listener, + SharedInterface* shared) + : ProducerStreamManager( + consumableRtpEncodings, + preferredLayers, + std::move(encodingContext), + kind, + keyFrameSupported, + listener, + shared) + { + MS_TRACE(); + + // Initialize per-stream sync state for each consumable encoding. + for (const auto& encoding : this->consumableRtpEncodings) + { + this->mapMappedSsrcSyncRequired[encoding.ssrc] = false; + } + } + + bool PipeProducerStreamManager::IsActive() const + { + MS_TRACE(); + + return this->listener->IsActive(); + } + + void PipeProducerStreamManager::ProducerRtpStream( + RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint32_t /*mappedSsrc*/) + { + MS_TRACE(); + + // Do nothing. + } + + void PipeProducerStreamManager::ProducerNewRtpStream( + RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint32_t /*mappedSsrc*/) + { + MS_TRACE(); + + // Do nothing. + } + + void PipeProducerStreamManager::ProducerRtpStreamScore( + RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint8_t /*score*/, uint8_t /*previousScore*/) + { + MS_TRACE(); + + // Do nothing. + } + + void PipeProducerStreamManager::ProducerRtcpSenderReport( + RTC::RTP::RtpStreamRecv* /*rtpStream*/, bool /*first*/) + { + MS_TRACE(); + + // Do nothing. + } + + uint32_t PipeProducerStreamManager::IncreaseLayer( + uint32_t /*bitrate*/, bool /*considerLoss*/, float /*lossPercentage*/, uint64_t /*nowMs*/) + { + MS_TRACE(); + + // Pipe does not play the BWE game. + return 0u; + } + + void PipeProducerStreamManager::ApplyLayers(uint64_t /*rtpStreamActiveMs*/) + { + MS_TRACE(); + + // Pipe does not play the BWE game. + } + + uint32_t PipeProducerStreamManager::GetDesiredBitrate(uint64_t /*nowMs*/) const + { + MS_TRACE(); + + // Pipe does not play the BWE game. + return 0u; + } + + ProducerStreamManager::RtpPacketProcessResult PipeProducerStreamManager::ProcessRtpPacket( + RTC::RTP::Packet* packet, + bool /*lastSentPacketHasMarker*/, + uint32_t /*clockRate*/, + uint32_t /*maxPacketTs*/) + { + MS_TRACE(); + + RtpPacketProcessResult result; + + auto it = this->mapMappedSsrcSyncRequired.find(packet->GetSsrc()); + + MS_ASSERT(it != this->mapMappedSsrcSyncRequired.end(), "unknown mapped SSRC"); + + bool& syncRequired = it->second; + + // If sync required and key frames supported, buffer non-key-frame packets. + if (syncRequired && this->keyFrameSupported && !packet->IsKeyFrame()) + { + result.type = RtpPacketProcessResult::Type::BUFFER; + +#ifdef MS_RTC_LOGGER_RTP + packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::NOT_A_KEYFRAME); +#endif + + return result; + } + + // Packets with only padding are not forwarded. + if (packet->GetPayloadLength() == 0) + { + result.type = RtpPacketProcessResult::Type::DROP; + +#ifdef MS_RTC_LOGGER_RTP + packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::EMPTY_PAYLOAD); +#endif + + return result; + } + + // Whether this is the first packet after re-sync. + const bool isSyncPacket = syncRequired; + + if (isSyncPacket) + { + if (packet->IsKeyFrame()) + { + MS_DEBUG_TAG( + rtp, + "sync key frame received [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "]", + packet->GetSsrc(), + packet->GetSequenceNumber(), + packet->GetTimestamp()); + + result.sendBufferedPackets = true; + } + + result.isSyncPacket = true; + result.syncSeqValue = packet->GetSequenceNumber() - 1; + + syncRequired = false; + } + + // Pipe passes timestamp and marker through unchanged. + result.type = RtpPacketProcessResult::Type::FORWARD; + result.tsOffset = 0u; + result.marker = packet->HasMarker(); + + return result; + } + + void PipeProducerStreamManager::RequestKeyFrame() + { + MS_TRACE(); + + if (this->kind != RTC::Media::Kind::VIDEO) + { + return; + } + + for (const auto& encoding : this->consumableRtpEncodings) + { + this->listener->OnProducerStreamManagerKeyFrameRequested(encoding.ssrc); + } + } + + void PipeProducerStreamManager::RequestKeyFrameForTargetSpatialLayer() + { + MS_TRACE(); + + RequestKeyFrame(); + } + + void PipeProducerStreamManager::RequestKeyFrameForCurrentSpatialLayer() + { + MS_TRACE(); + + RequestKeyFrame(); + } + + void PipeProducerStreamManager::UpdateTargetLayers(int16_t /*spatial*/, int16_t /*temporal*/) + { + MS_TRACE(); + + // No layer management for pipe. + } + + bool PipeProducerStreamManager::RecalculateTargetLayers( + RTC::ConsumerTypes::VideoLayers& /*newTargetLayers*/) const + { + MS_TRACE(); + + // No layer changes for pipe. + return false; + } + + void PipeProducerStreamManager::OnTransportConnected() + { + MS_TRACE(); + + for (auto& kv : this->mapMappedSsrcSyncRequired) + { + kv.second = true; + } + + if (IsActive()) + { + RequestKeyFrame(); + } + } + + void PipeProducerStreamManager::OnTransportDisconnected() + { + MS_TRACE(); + + for (auto& kv : this->mapMappedSsrcSyncRequired) + { + kv.second = false; + } + } + + void PipeProducerStreamManager::OnPaused() + { + MS_TRACE(); + + for (auto& kv : this->mapMappedSsrcSyncRequired) + { + kv.second = false; + } + } + + void PipeProducerStreamManager::OnResumed() + { + MS_TRACE(); + + for (auto& kv : this->mapMappedSsrcSyncRequired) + { + kv.second = true; + } + + if (IsActive()) + { + RequestKeyFrame(); + } + } + +} // namespace RTC diff --git a/worker/src/RTC/SimpleProducerStreamManager.cpp b/worker/src/RTC/SimpleProducerStreamManager.cpp index 6d3c471820..6f02bd97dc 100644 --- a/worker/src/RTC/SimpleProducerStreamManager.cpp +++ b/worker/src/RTC/SimpleProducerStreamManager.cpp @@ -100,7 +100,7 @@ namespace RTC MS_ASSERT(IsActive(), "should be active"); // If this is not the first time this method is called within the same - // iteration, return 0 since a video SimpleConsumer does not keep state + // iteration, return 0 since a video Simple consumer does not keep state // about this. if (this->managingBitrate) { @@ -114,7 +114,7 @@ namespace RTC return 0u; } - // Video SimpleConsumer does not really play the BWE game. However, let's + // Video Simple consumer does not really play the BWE game. However, let's // be honest and try to be nice. auto desiredBitrate = this->producerRtpStream->GetBitrate(nowMs); diff --git a/worker/src/RTC/Transport.cpp b/worker/src/RTC/Transport.cpp index 8c17903bf7..a63da2071f 100644 --- a/worker/src/RTC/Transport.cpp +++ b/worker/src/RTC/Transport.cpp @@ -912,18 +912,8 @@ namespace RTC auto type = RTC::RtpParameters::Type(body->type()); - RTC::Consumer* consumer{ nullptr }; - - if (type == RTC::RtpParameters::Type::PIPE) - { - // TODO: PipeConsumer still inherits from OldConsumer. Handle separately. - MS_THROW_TYPE_ERROR("pipe consumer not yet supported in new Consumer"); - } - else - { - // This may throw. - consumer = new RTC::Consumer(this->shared, consumerId, producerId, this, body); - } + // This may throw. + RTC::Consumer* consumer = new RTC::Consumer(this->shared, consumerId, producerId, this, body); // Notify the listener. // This may throw if no Producer is found. diff --git a/worker/test/src/RTC/TestPipeProducerStreamManager.cpp b/worker/test/src/RTC/TestPipeProducerStreamManager.cpp new file mode 100644 index 0000000000..2f741d6514 --- /dev/null +++ b/worker/test/src/RTC/TestPipeProducerStreamManager.cpp @@ -0,0 +1,472 @@ +#include "DepLibUV.hpp" +#include "mocks/include/MockShared.hpp" +#include "RTC/PipeProducerStreamManager.hpp" +#include "RTC/RTP/Codecs/PayloadDescriptorHandler.hpp" +#include "RTC/RTP/rtpCommon.hpp" +#include + +namespace +{ + using RtpPacketProcessResult = RTC::ProducerStreamManager::RtpPacketProcessResult; + + // NOLINTBEGIN(readability-identifier-naming) + constexpr uint32_t mappedSsrc0 = 1000; + constexpr uint32_t mappedSsrc1 = 2000; + constexpr uint32_t mappedSsrc2 = 3000; + // NOLINTEND(readability-identifier-naming) + + class MockListener : public RTC::ProducerStreamManager::Listener + { + public: + bool IsActive() const override + { + return this->isActive; + } + + void OnProducerStreamManagerKeyFrameRequested(uint32_t mappedSsrc) override + { + this->keyFrameRequestCount++; + this->keyFrameRequestedSsrcs.push_back(mappedSsrc); + } + + void OnProducerStreamManagerNeedBitrateChange() override + { + } + + void OnProducerStreamManagerLayersChanged() override + { + } + + void OnProducerStreamManagerClearRetransmissionBuffer() override + { + } + + void OnProducerStreamManagerScore() override + { + } + + public: + bool isActive{ true }; + int keyFrameRequestCount{ 0 }; + std::vector keyFrameRequestedSsrcs; + }; + + class MockPayloadDescriptorHandler : public RTC::RTP::Codecs::PayloadDescriptorHandler + { + public: + explicit MockPayloadDescriptorHandler() = default; + ~MockPayloadDescriptorHandler() override = default; + void Dump(int /*indentation*/) const override + { + } + bool Process( + RTC::RTP::Codecs::EncodingContext* /*context*/, + RTC::RTP::Packet* /*packet*/, + bool& /*marker*/) override + { + return true; + } + void RtpPacketChanged(RTC::RTP::Packet* /*packet*/) override + { + } + std::unique_ptr GetEncoder() const override + { + return nullptr; + } + void Encode( + RTC::RTP::Packet* /*packet*/, RTC::RTP::Codecs::PayloadDescriptor::Encoder* /*encoder*/) override + { + } + void Restore(RTC::RTP::Packet* /*packet*/) override + { + } + uint8_t GetSpatialLayer() const override + { + return 0; + } + uint8_t GetTemporalLayer() const override + { + return 0; + } + bool IsKeyFrame() const override + { + return this->isKeyFrame; + } + + public: + bool isKeyFrame{ false }; + }; + + mocks::MockShared mockShared( // NOLINT(readability-identifier-naming) + []() + { + return DepLibUV::GetTimeMs(); + }); + + std::unique_ptr createManager( + MockListener* listener, + const std::vector& ssrcs = { mappedSsrc0, mappedSsrc1, mappedSsrc2 }, + bool keyFrameSupported = true, + RTC::Media::Kind kind = RTC::Media::Kind::VIDEO) + { + std::vector consumableRtpEncodings; + + for (auto ssrc : ssrcs) + { + RTC::RtpEncodingParameters encoding; + encoding.ssrc = ssrc; + consumableRtpEncodings.push_back(encoding); + } + + RTC::ConsumerTypes::VideoLayers preferredLayers; + + return std::make_unique( + consumableRtpEncodings, preferredLayers, nullptr, kind, keyFrameSupported, listener, &mockShared); + } +} // namespace + +SCENARIO("PipeProducerStreamManager", "[rtp][producer-stream-manager][pipe]") +{ + std::unique_ptr packet( + RTC::RTP::Packet::Factory(rtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer))); + + packet->SetPayloadType(1); + packet->SetSsrc(mappedSsrc0); + packet->SetPayloadLength(40); + + SECTION("IsActive() returns listener state") + { + MockListener listener; + auto manager = createManager(&listener); + + REQUIRE(manager->IsActive() == true); + + listener.isActive = false; + + REQUIRE(manager->IsActive() == false); + } + + SECTION("ProcessRtpPacket() returns FORWARD with isSyncPacket on first packet after connect") + { + MockListener listener; + auto manager = createManager(&listener, { mappedSsrc0 }, /*keyFrameSupported*/ false); + + manager->OnTransportConnected(); + + packet->SetSsrc(mappedSsrc0); + packet->SetSequenceNumber(10); + + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 0, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result.isSyncPacket == true); + REQUIRE(result.syncSeqValue == 9); + REQUIRE(result.tsOffset == 0u); + + // Second packet forwards normally, no sync. + packet->SetSequenceNumber(11); + + result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 0, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result.isSyncPacket == false); + } + + SECTION("ProcessRtpPacket() returns BUFFER when sync required and packet is not a key frame") + { + MockListener listener; + auto manager = createManager(&listener); + + manager->OnTransportConnected(); + + packet->SetSsrc(mappedSsrc0); + packet->SetSequenceNumber(10); + + auto result = manager->ProcessRtpPacket( + packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 0, /*maxPacketTs*/ 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::BUFFER); + } + + SECTION("ProcessRtpPacket() syncs independently per stream") + { + MockListener listener; + auto manager = + createManager(&listener, { mappedSsrc0, mappedSsrc1 }, /*keyFrameSupported*/ false); + + manager->OnTransportConnected(); + + // Sync stream 0. + packet->SetSsrc(mappedSsrc0); + packet->SetSequenceNumber(100); + + auto result0 = manager->ProcessRtpPacket(packet.get(), false, 0, 0); + + REQUIRE(result0.type == RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result0.isSyncPacket == true); + REQUIRE(result0.syncSeqValue == 99); + + // Stream 1 syncs independently. + packet->SetSsrc(mappedSsrc1); + packet->SetSequenceNumber(200); + + auto result1 = manager->ProcessRtpPacket(packet.get(), false, 0, 0); + + REQUIRE(result1.type == RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result1.isSyncPacket == true); + REQUIRE(result1.syncSeqValue == 199); + + // Stream 0 next packet is no longer a sync packet. + packet->SetSsrc(mappedSsrc0); + packet->SetSequenceNumber(101); + + auto result2 = manager->ProcessRtpPacket(packet.get(), false, 0, 0); + + REQUIRE(result2.type == RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result2.isSyncPacket == false); + } + + SECTION("ProcessRtpPacket() returns FORWARD with sendBufferedPackets on key frame sync") + { + MockListener listener; + auto manager = createManager(&listener); + + manager->OnTransportConnected(); + + packet->SetSsrc(mappedSsrc0); + packet->SetSequenceNumber(50); + + auto* handler = new MockPayloadDescriptorHandler(); + handler->isKeyFrame = true; + packet->SetPayloadDescriptorHandler(handler); + + auto result = manager->ProcessRtpPacket(packet.get(), false, 0, 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result.isSyncPacket == true); + REQUIRE(result.syncSeqValue == 49); + REQUIRE(result.sendBufferedPackets == true); + } + + SECTION("ProcessRtpPacket() returns DROP for empty payload packets") + { + MockListener listener; + auto manager = createManager(&listener, { mappedSsrc0 }, /*keyFrameSupported*/ false); + + manager->OnTransportConnected(); + + // Complete sync first. + packet->SetSsrc(mappedSsrc0); + packet->SetSequenceNumber(1); + manager->ProcessRtpPacket(packet.get(), false, 0, 0); + + // Padding-only packet. + packet->SetSequenceNumber(2); + packet->RemovePayload(); + + auto result = manager->ProcessRtpPacket(packet.get(), false, 0, 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::DROP); + } + + SECTION("ProcessRtpPacket() preserves original marker bit") + { + MockListener listener; + auto manager = createManager(&listener, { mappedSsrc0 }, /*keyFrameSupported*/ false); + + manager->OnTransportConnected(); + + packet->SetSsrc(mappedSsrc0); + packet->SetSequenceNumber(1); + packet->SetMarker(true); + + auto result = manager->ProcessRtpPacket(packet.get(), false, 0, 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result.marker == true); + + packet->SetSequenceNumber(2); + packet->SetMarker(false); + + result = manager->ProcessRtpPacket(packet.get(), false, 0, 0); + + REQUIRE(result.marker == false); + } + + SECTION("OnTransportConnected() sets sync required for all streams and requests key frames") + { + MockListener listener; + auto manager = createManager(&listener, { mappedSsrc0, mappedSsrc1, mappedSsrc2 }); + + manager->OnTransportConnected(); + + REQUIRE(listener.keyFrameRequestCount == 3); + REQUIRE( + std::find( + listener.keyFrameRequestedSsrcs.begin(), listener.keyFrameRequestedSsrcs.end(), mappedSsrc0) != + listener.keyFrameRequestedSsrcs.end()); + REQUIRE( + std::find( + listener.keyFrameRequestedSsrcs.begin(), listener.keyFrameRequestedSsrcs.end(), mappedSsrc1) != + listener.keyFrameRequestedSsrcs.end()); + REQUIRE( + std::find( + listener.keyFrameRequestedSsrcs.begin(), listener.keyFrameRequestedSsrcs.end(), mappedSsrc2) != + listener.keyFrameRequestedSsrcs.end()); + + // All streams require a key frame before forwarding. + packet->SetSequenceNumber(1); + + for (auto ssrc : { mappedSsrc0, mappedSsrc1, mappedSsrc2 }) + { + packet->SetSsrc(ssrc); + + auto result = manager->ProcessRtpPacket(packet.get(), false, 0, 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::BUFFER); + } + } + + SECTION("OnTransportConnected() does not request key frames when not active") + { + MockListener listener; + listener.isActive = false; + auto manager = createManager(&listener); + + manager->OnTransportConnected(); + + REQUIRE(listener.keyFrameRequestCount == 0); + } + + SECTION("OnTransportConnected() does not request key frames for audio") + { + MockListener listener; + auto manager = createManager( + &listener, { mappedSsrc0 }, /*keyFrameSupported*/ false, RTC::Media::Kind::AUDIO); + + manager->OnTransportConnected(); + + REQUIRE(listener.keyFrameRequestCount == 0); + } + + SECTION("OnTransportDisconnected() clears sync required for all streams") + { + MockListener listener; + auto manager = + createManager(&listener, { mappedSsrc0, mappedSsrc1 }, /*keyFrameSupported*/ false); + + manager->OnTransportConnected(); + manager->OnTransportDisconnected(); + + // Sync cleared — packets forward immediately without waiting for key frame. + packet->SetSequenceNumber(1); + + for (auto ssrc : { mappedSsrc0, mappedSsrc1 }) + { + packet->SetSsrc(ssrc); + + auto result = manager->ProcessRtpPacket(packet.get(), false, 0, 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result.isSyncPacket == false); + } + } + + SECTION("OnPaused() clears sync required for all streams") + { + MockListener listener; + auto manager = createManager(&listener, { mappedSsrc0 }, /*keyFrameSupported*/ false); + + manager->OnTransportConnected(); + manager->OnPaused(); + + packet->SetSsrc(mappedSsrc0); + packet->SetSequenceNumber(1); + + auto result = manager->ProcessRtpPacket(packet.get(), false, 0, 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::FORWARD); + REQUIRE(result.isSyncPacket == false); + } + + SECTION("OnResumed() sets sync required and requests key frames for all streams") + { + MockListener listener; + auto manager = createManager(&listener, { mappedSsrc0, mappedSsrc1 }); + + manager->OnTransportConnected(); + + const int countBefore = listener.keyFrameRequestCount; + + manager->OnResumed(); + + REQUIRE(listener.keyFrameRequestCount == countBefore + 2); + + // All streams need sync again. + packet->SetSequenceNumber(1); + + for (auto ssrc : { mappedSsrc0, mappedSsrc1 }) + { + packet->SetSsrc(ssrc); + + auto result = manager->ProcessRtpPacket(packet.get(), false, 0, 0); + + REQUIRE(result.type == RtpPacketProcessResult::Type::BUFFER); + } + } + + SECTION("RequestKeyFrame() requests for all video streams") + { + MockListener listener; + auto manager = createManager(&listener, { mappedSsrc0, mappedSsrc1, mappedSsrc2 }); + + manager->RequestKeyFrame(); + + REQUIRE(listener.keyFrameRequestCount == 3); + } + + SECTION("RequestKeyFrame() is no-op for audio") + { + MockListener listener; + auto manager = createManager( + &listener, { mappedSsrc0 }, /*keyFrameSupported*/ false, RTC::Media::Kind::AUDIO); + + manager->RequestKeyFrame(); + + REQUIRE(listener.keyFrameRequestCount == 0); + } + + SECTION("RecalculateTargetLayers() always returns false") + { + MockListener listener; + auto manager = createManager(&listener); + + RTC::ConsumerTypes::VideoLayers newTargetLayers; + + REQUIRE(manager->RecalculateTargetLayers(newTargetLayers) == false); + } + + SECTION("IncreaseLayer() returns 0 (no BWE)") + { + MockListener listener; + auto manager = createManager(&listener); + + manager->SetExternallyManagedBitrate(); + + const auto used = manager->IncreaseLayer(1000000u, false, 0.0f, DepLibUV::GetTimeMs()); + + REQUIRE(used == 0u); + } + + SECTION("GetDesiredBitrate() returns 0 (no BWE)") + { + MockListener listener; + auto manager = createManager(&listener); + + manager->SetExternallyManagedBitrate(); + + REQUIRE(manager->GetDesiredBitrate(DepLibUV::GetTimeMs()) == 0u); + } +} From 51cb67eb4987b2ded17d0bed2be97b2fd431d073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Fri, 15 May 2026 16:59:19 +0200 Subject: [PATCH 31/37] format --- worker/src/RTC/OldConsumer.cpp | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/worker/src/RTC/OldConsumer.cpp b/worker/src/RTC/OldConsumer.cpp index 1042365ff6..a750366257 100644 --- a/worker/src/RTC/OldConsumer.cpp +++ b/worker/src/RTC/OldConsumer.cpp @@ -445,7 +445,8 @@ namespace RTC UserOnPaused(); } - this->shared->GetChannelNotifier()->Emit(this->id, FBS::Notification::Event::CONSUMER_PRODUCER_PAUSE); + this->shared->GetChannelNotifier()->Emit( + this->id, FBS::Notification::Event::CONSUMER_PRODUCER_PAUSE); } void OldConsumer::ProducerResumed() @@ -466,7 +467,8 @@ namespace RTC UserOnResumed(); } - this->shared->GetChannelNotifier()->Emit(this->id, FBS::Notification::Event::CONSUMER_PRODUCER_RESUME); + this->shared->GetChannelNotifier()->Emit( + this->id, FBS::Notification::Event::CONSUMER_PRODUCER_RESUME); } void OldConsumer::ProducerRtpStreamScores(const std::vector* scores) @@ -487,7 +489,8 @@ namespace RTC MS_DEBUG_DEV("Producer closed [consumerId:%s]", this->id.c_str()); - this->shared->GetChannelNotifier()->Emit(this->id, FBS::Notification::Event::CONSUMER_PRODUCER_CLOSE); + this->shared->GetChannelNotifier()->Emit( + this->id, FBS::Notification::Event::CONSUMER_PRODUCER_CLOSE); this->listener->OnConsumerProducerClosed(this); } @@ -499,7 +502,7 @@ namespace RTC if (this->traceEventTypes.keyframe && packet->IsKeyFrame()) { auto rtpPacketDump = packet->FillBuffer(this->shared->GetChannelNotifier()->GetBufferBuilder()); - auto traceInfo = FBS::Consumer::CreateKeyFrameTraceInfo( + auto traceInfo = FBS::Consumer::CreateKeyFrameTraceInfo( this->shared->GetChannelNotifier()->GetBufferBuilder(), rtpPacketDump, isRtx); auto notification = FBS::Consumer::CreateTraceNotification( @@ -515,7 +518,7 @@ namespace RTC else if (this->traceEventTypes.rtp) { auto rtpPacketDump = packet->FillBuffer(this->shared->GetChannelNotifier()->GetBufferBuilder()); - auto traceInfo = FBS::Consumer::CreateRtpTraceInfo( + auto traceInfo = FBS::Consumer::CreateRtpTraceInfo( this->shared->GetChannelNotifier()->GetBufferBuilder(), rtpPacketDump, isRtx); auto notification = FBS::Consumer::CreateTraceNotification( @@ -539,8 +542,8 @@ namespace RTC return; } - auto traceInfo = - FBS::Consumer::CreatePliTraceInfo(this->shared->GetChannelNotifier()->GetBufferBuilder(), ssrc); + auto traceInfo = FBS::Consumer::CreatePliTraceInfo( + this->shared->GetChannelNotifier()->GetBufferBuilder(), ssrc); auto notification = FBS::Consumer::CreateTraceNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), @@ -562,8 +565,8 @@ namespace RTC return; } - auto traceInfo = - FBS::Consumer::CreateFirTraceInfo(this->shared->GetChannelNotifier()->GetBufferBuilder(), ssrc); + auto traceInfo = FBS::Consumer::CreateFirTraceInfo( + this->shared->GetChannelNotifier()->GetBufferBuilder(), ssrc); auto notification = FBS::Consumer::CreateTraceNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), From ef22ed4050effdf165fc1ca1ffabbabb0f08e425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Mon, 18 May 2026 09:37:06 +0200 Subject: [PATCH 32/37] fix compilation --- worker/include/RTC/ProducerStreamManager.hpp | 3 --- worker/src/RTC/Transport.cpp | 4 +--- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/worker/include/RTC/ProducerStreamManager.hpp b/worker/include/RTC/ProducerStreamManager.hpp index 4cc040c73c..14fae16c4c 100644 --- a/worker/include/RTC/ProducerStreamManager.hpp +++ b/worker/include/RTC/ProducerStreamManager.hpp @@ -71,7 +71,6 @@ namespace RTC encodingContext(std::move(encodingContext)), preferredLayers(preferredLayers) { - MS_TRACE(); } virtual ~ProducerStreamManager() = default; @@ -129,8 +128,6 @@ namespace RTC void MayChangeLayers(bool force) { - MS_TRACE(); - RTC::ConsumerTypes::VideoLayers newTargetLayers; if (RecalculateTargetLayers(newTargetLayers)) diff --git a/worker/src/RTC/Transport.cpp b/worker/src/RTC/Transport.cpp index a63da2071f..2c6bab07c4 100644 --- a/worker/src/RTC/Transport.cpp +++ b/worker/src/RTC/Transport.cpp @@ -910,10 +910,8 @@ namespace RTC MS_THROW_ERROR("a Consumer with same consumerId already exists"); } - auto type = RTC::RtpParameters::Type(body->type()); - // This may throw. - RTC::Consumer* consumer = new RTC::Consumer(this->shared, consumerId, producerId, this, body); + auto* consumer = new RTC::Consumer(this->shared, consumerId, producerId, this, body); // Notify the listener. // This may throw if no Producer is found. From 91b3550def441ecd24a84b5d168a901915aed6ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Mon, 18 May 2026 10:46:36 +0200 Subject: [PATCH 33/37] tidy --- .../src/RTC/TestPipeProducerStreamManager.cpp | 13 +++++----- .../RTC/TestSimpleProducerStreamManager.cpp | 25 ++++++++++--------- .../TestSimulcastProducerStreamManager.cpp | 17 +++++++------ .../src/RTC/TestSvcProducerStreamManager.cpp | 21 ++++++++-------- 4 files changed, 40 insertions(+), 36 deletions(-) diff --git a/worker/test/src/RTC/TestPipeProducerStreamManager.cpp b/worker/test/src/RTC/TestPipeProducerStreamManager.cpp index 2f741d6514..568086a67d 100644 --- a/worker/test/src/RTC/TestPipeProducerStreamManager.cpp +++ b/worker/test/src/RTC/TestPipeProducerStreamManager.cpp @@ -97,11 +97,12 @@ namespace bool isKeyFrame{ false }; }; - mocks::MockShared mockShared( // NOLINT(readability-identifier-naming) - []() - { - return DepLibUV::GetTimeMs(); - }); + // NOLINTNEXTLINE(readability-identifier-naming) + mocks::MockShared shared(/*getTimeMs*/ + []() + { + return DepLibUV::GetTimeMs(); + }); std::unique_ptr createManager( MockListener* listener, @@ -121,7 +122,7 @@ namespace RTC::ConsumerTypes::VideoLayers preferredLayers; return std::make_unique( - consumableRtpEncodings, preferredLayers, nullptr, kind, keyFrameSupported, listener, &mockShared); + consumableRtpEncodings, preferredLayers, nullptr, kind, keyFrameSupported, listener, &shared); } } // namespace diff --git a/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp b/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp index e5956fd4da..73de86b2b7 100644 --- a/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp +++ b/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp @@ -135,11 +135,13 @@ namespace // RtpStreamRecvListener must outlive the RtpStreamRecv. RtpStreamRecvListener streamRecvListener; // NOLINT(readability-identifier-naming) - mocks::MockShared mockShared( - []() - { - return DepLibUV::GetTimeMs(); - }); // NOLINT(readability-identifier-naming) + + // NOLINTNEXTLINE(readability-identifier-naming) + mocks::MockShared shared(/*getTimeMs*/ + []() + { + return DepLibUV::GetTimeMs(); + }); // NOLINT(readability-identifier-naming) std::unique_ptr createManager( MockListener* listener, @@ -149,9 +151,9 @@ namespace { RTC::RtpEncodingParameters encoding; encoding.ssrc = mappedSsrc; - std::vector consumableRtpEncodings{ encoding }; - RTC::ConsumerTypes::VideoLayers preferredLayers; + const std::vector consumableRtpEncodings{ encoding }; + const RTC::ConsumerTypes::VideoLayers preferredLayers; return std::make_unique( consumableRtpEncodings, @@ -160,7 +162,7 @@ namespace kind, keyFrameSupported, listener, - &mockShared); + &shared); } std::unique_ptr createRtpStreamRecv() @@ -170,8 +172,7 @@ namespace params.ssrc = mappedSsrc; params.clockRate = 90000; - return std::make_unique( - &streamRecvListener, &mockShared, params, 0u, false); + return std::make_unique(&streamRecvListener, &shared, params, 0u, false); } // Feed packets into the RtpStreamRecv so GetBitrate() returns non-zero. @@ -377,7 +378,7 @@ SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager][simple]" manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); manager->OnTransportConnected(); - int keyFrameCount = listener.keyFrameRequestCount; + const int keyFrameCount = listener.keyFrameRequestCount; // Call OnResumed — should set syncRequired and request keyframe again. manager->OnResumed(); @@ -428,7 +429,7 @@ SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager][simple]" feedRtpStreamRecv(rtpStream.get(), packet.get(), 100); auto nowMs = DepLibUV::GetTimeMs(); - auto streamBitrate = rtpStream->GetBitrate(nowMs); + const auto streamBitrate = rtpStream->GetBitrate(nowMs); uint32_t availableBitrate = streamBitrate - 1; auto usedBitrate = manager->IncreaseLayer( /*bitrate*/ availableBitrate, /*considerLoss*/ false, /*lossPercentage*/ 0.0f, nowMs); diff --git a/worker/test/src/RTC/TestSimulcastProducerStreamManager.cpp b/worker/test/src/RTC/TestSimulcastProducerStreamManager.cpp index b5fb04778a..2c94a5891f 100644 --- a/worker/test/src/RTC/TestSimulcastProducerStreamManager.cpp +++ b/worker/test/src/RTC/TestSimulcastProducerStreamManager.cpp @@ -144,11 +144,13 @@ namespace // RtpStreamRecvListener must outlive the RtpStreamRecv. RtpStreamRecvListener streamRecvListener; // NOLINT(readability-identifier-naming) - mocks::MockShared mockShared( - []() - { - return DepLibUV::GetTimeMs(); - }); // NOLINT(readability-identifier-naming) + + // NOLINTNEXTLINE(readability-identifier-naming) + mocks::MockShared shared(/*getTimeMs*/ + []() + { + return DepLibUV::GetTimeMs(); + }); // NOLINT(readability-identifier-naming) std::unique_ptr createManager( MockListener* listener, @@ -179,7 +181,7 @@ namespace kind, keyFrameSupported, listener, - &mockShared); + &shared); } std::unique_ptr createRtpStreamRecv(uint32_t ssrc) @@ -189,8 +191,7 @@ namespace params.ssrc = ssrc; params.clockRate = 90000; - return std::make_unique( - &streamRecvListener, &mockShared, params, 0u, false); + return std::make_unique(&streamRecvListener, &shared, params, 0u, false); } // Feed packets into the RtpStreamRecv so GetBitrate() returns non-zero. diff --git a/worker/test/src/RTC/TestSvcProducerStreamManager.cpp b/worker/test/src/RTC/TestSvcProducerStreamManager.cpp index 2c2a06890f..604a47bf1e 100644 --- a/worker/test/src/RTC/TestSvcProducerStreamManager.cpp +++ b/worker/test/src/RTC/TestSvcProducerStreamManager.cpp @@ -150,11 +150,13 @@ namespace // RtpStreamRecvListener must outlive the RtpStreamRecv. RtpStreamRecvListener streamRecvListener; // NOLINT(readability-identifier-naming) - mocks::MockShared mockShared( - []() - { - return DepLibUV::GetTimeMs(); - }); // NOLINT(readability-identifier-naming) + + // NOLINTNEXTLINE(readability-identifier-naming) + mocks::MockShared shared(/*getTimeMs*/ + []() + { + return DepLibUV::GetTimeMs(); + }); // NOLINT(readability-identifier-naming) std::unique_ptr createManager( MockListener* listener, @@ -179,7 +181,7 @@ namespace kind, keyFrameSupported, listener, - &mockShared); + &shared); } std::unique_ptr createRtpStreamRecv( @@ -192,8 +194,7 @@ namespace params.spatialLayers = spatialLayers; params.temporalLayers = temporalLayers; - return std::make_unique( - &streamRecvListener, &mockShared, params, 0u, false); + return std::make_unique(&streamRecvListener, &shared, params, 0u, false); } // Feed packets into the RtpStreamRecv so GetBitrate() returns non-zero. @@ -813,7 +814,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") // Disconnect: target and current reset to {-1,-1}. manager->OnTransportDisconnected(); - int keyFrameCountBefore = listener.keyFrameRequestCount; + const int keyFrameCountBefore = listener.keyFrameRequestCount; // OnResumed sets syncRequired and calls MayChangeLayers → RecalculateTargetLayers // returns {0,0} which differs from {-1,-1} → UpdateTargetLayers(0,0) → @@ -1137,7 +1138,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") auto manager = createManager(&listener); RTC::ConsumerTypes::VideoLayers newTargetLayers; - bool changed = manager->RecalculateTargetLayers(newTargetLayers); + const bool changed = manager->RecalculateTargetLayers(newTargetLayers); // No change from initial -1,-1 target. REQUIRE(changed == false); From 8b689d40342915f644862ce5433eb37f1b88fbdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Mon, 18 May 2026 10:57:19 +0200 Subject: [PATCH 34/37] remove unused imports --- worker/scripts/clang-scripts.mjs | 12 ++++++++---- worker/src/RTC/Transport.cpp | 3 --- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/worker/scripts/clang-scripts.mjs b/worker/scripts/clang-scripts.mjs index 0cef56d873..f5b1ab2cec 100644 --- a/worker/scripts/clang-scripts.mjs +++ b/worker/scripts/clang-scripts.mjs @@ -24,10 +24,14 @@ const CLANG_FORMAT_PATHS = [ ]; const CLANG_TIDY_PATHS = [ - '../src/**/*.cpp', - '../test/src/**/*.cpp', - '../fuzzer/src/**/*.cpp', - '../mocks/src/**/*.cpp', + // '../src/**/*.cpp', + // '../test/src/**/*.cpp', + // '../fuzzer/src/**/*.cpp', + // '../mocks/src/**/*.cpp', + `../test/src/RTC/TestSimpleProducerStreamManager.cpp`, + `../test/src/RTC/TestSimulcastProducerStreamManager.cpp`, + `../test/src/RTC/TestSvcProducerStreamManager.cpp`, + `../test/src/RTC/TestPipeProducerStreamManager.cpp`, ]; const task = process.argv.slice(2).join(' '); diff --git a/worker/src/RTC/Transport.cpp b/worker/src/RTC/Transport.cpp index 2c6bab07c4..cfdf151608 100644 --- a/worker/src/RTC/Transport.cpp +++ b/worker/src/RTC/Transport.cpp @@ -23,9 +23,6 @@ #include "RTC/RtpDictionaries.hpp" #include "RTC/SCTP/association/Association.hpp" #include "RTC/SCTP/public/SctpOptions.hpp" -#include "RTC/SimpleConsumer.hpp" -#include "RTC/SimulcastConsumer.hpp" -#include "RTC/SvcConsumer.hpp" #ifdef MS_RTC_LOGGER_RTP #include "RTC/RtcLogger.hpp" #endif From c7bc3852aff8f078d06c26fdf03244a456e932ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Mon, 18 May 2026 10:59:04 +0200 Subject: [PATCH 35/37] revert local change --- worker/scripts/clang-scripts.mjs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/worker/scripts/clang-scripts.mjs b/worker/scripts/clang-scripts.mjs index f5b1ab2cec..0cef56d873 100644 --- a/worker/scripts/clang-scripts.mjs +++ b/worker/scripts/clang-scripts.mjs @@ -24,14 +24,10 @@ const CLANG_FORMAT_PATHS = [ ]; const CLANG_TIDY_PATHS = [ - // '../src/**/*.cpp', - // '../test/src/**/*.cpp', - // '../fuzzer/src/**/*.cpp', - // '../mocks/src/**/*.cpp', - `../test/src/RTC/TestSimpleProducerStreamManager.cpp`, - `../test/src/RTC/TestSimulcastProducerStreamManager.cpp`, - `../test/src/RTC/TestSvcProducerStreamManager.cpp`, - `../test/src/RTC/TestPipeProducerStreamManager.cpp`, + '../src/**/*.cpp', + '../test/src/**/*.cpp', + '../fuzzer/src/**/*.cpp', + '../mocks/src/**/*.cpp', ]; const task = process.argv.slice(2).join(' '); From 5d3cd887673375e73973c62244adf9a106dede50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Mon, 18 May 2026 10:59:19 +0200 Subject: [PATCH 36/37] .clang-format: remove unknown key BreakAfterOpenBracketBracedList --- worker/.clang-format | 3 --- 1 file changed, 3 deletions(-) diff --git a/worker/.clang-format b/worker/.clang-format index 7ca24bba9b..485d42de89 100644 --- a/worker/.clang-format +++ b/worker/.clang-format @@ -17,9 +17,6 @@ AlwaysBreakBeforeMultilineStrings: true AlwaysBreakTemplateDeclarations: Yes BinPackArguments: false BinPackParameters: false -# NOTE: This doesn't do anything because it requires Cpp11BracedListStyle: true, -# which we don't want. -BreakAfterOpenBracketBracedList: true BraceWrapping: AfterClass: true AfterControlStatement: Always From f72eda38f2f375135a97f4931311afba5089aaa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Mon, 18 May 2026 12:50:53 +0200 Subject: [PATCH 37/37] tidy --- worker/src/RTC/SimulcastProducerStreamManager.cpp | 3 ++- worker/test/src/RTC/TestPipeProducerStreamManager.cpp | 2 +- worker/test/src/RTC/TestSimpleProducerStreamManager.cpp | 8 ++++---- worker/test/src/RTC/TestSvcProducerStreamManager.cpp | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/worker/src/RTC/SimulcastProducerStreamManager.cpp b/worker/src/RTC/SimulcastProducerStreamManager.cpp index cb4851a6c7..e0b9f2d376 100644 --- a/worker/src/RTC/SimulcastProducerStreamManager.cpp +++ b/worker/src/RTC/SimulcastProducerStreamManager.cpp @@ -3,6 +3,7 @@ #include "RTC/SimulcastProducerStreamManager.hpp" #include "Logger.hpp" +#include namespace RTC { @@ -303,7 +304,7 @@ namespace RTC temporalLayer = 0; // Check bitrate of every temporal layer. - for (; temporalLayer < producerRtpStream->GetTemporalLayers(); ++temporalLayer) + for (; std::cmp_less(temporalLayer, producerRtpStream->GetTemporalLayers()); ++temporalLayer) { // Ignore temporal layers lower than the one we already have (taking // into account the spatial layer too). diff --git a/worker/test/src/RTC/TestPipeProducerStreamManager.cpp b/worker/test/src/RTC/TestPipeProducerStreamManager.cpp index 568086a67d..fce3fc793b 100644 --- a/worker/test/src/RTC/TestPipeProducerStreamManager.cpp +++ b/worker/test/src/RTC/TestPipeProducerStreamManager.cpp @@ -119,7 +119,7 @@ namespace consumableRtpEncodings.push_back(encoding); } - RTC::ConsumerTypes::VideoLayers preferredLayers; + const RTC::ConsumerTypes::VideoLayers preferredLayers; return std::make_unique( consumableRtpEncodings, preferredLayers, nullptr, kind, keyFrameSupported, listener, &shared); diff --git a/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp b/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp index 73de86b2b7..324da1cdb6 100644 --- a/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp +++ b/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp @@ -428,10 +428,10 @@ SCENARIO("SimpleProducerStreamManager", "[rtp][producer-stream-manager][simple]" // Feed packets so the stream has non-zero bitrate. feedRtpStreamRecv(rtpStream.get(), packet.get(), 100); - auto nowMs = DepLibUV::GetTimeMs(); - const auto streamBitrate = rtpStream->GetBitrate(nowMs); - uint32_t availableBitrate = streamBitrate - 1; - auto usedBitrate = manager->IncreaseLayer( + auto nowMs = DepLibUV::GetTimeMs(); + const auto streamBitrate = rtpStream->GetBitrate(nowMs); + const uint32_t availableBitrate = streamBitrate - 1; + auto usedBitrate = manager->IncreaseLayer( /*bitrate*/ availableBitrate, /*considerLoss*/ false, /*lossPercentage*/ 0.0f, nowMs); REQUIRE(usedBitrate == availableBitrate); diff --git a/worker/test/src/RTC/TestSvcProducerStreamManager.cpp b/worker/test/src/RTC/TestSvcProducerStreamManager.cpp index 604a47bf1e..d61b50ef8f 100644 --- a/worker/test/src/RTC/TestSvcProducerStreamManager.cpp +++ b/worker/test/src/RTC/TestSvcProducerStreamManager.cpp @@ -536,7 +536,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") manager->ProducerRtpStream(rtpStream.get(), mappedSsrc); - const int countBefore = listener.layersChangedCount; + const auto countBefore = listener.layersChangedCount; manager->UpdateTargetLayers(-1, -1); @@ -814,7 +814,7 @@ SCENARIO("SvcProducerStreamManager", "[rtp][producer-stream-manager][svc]") // Disconnect: target and current reset to {-1,-1}. manager->OnTransportDisconnected(); - const int keyFrameCountBefore = listener.keyFrameRequestCount; + const auto keyFrameCountBefore = listener.keyFrameRequestCount; // OnResumed sets syncRequired and calls MayChangeLayers → RecalculateTargetLayers // returns {0,0} which differs from {-1,-1} → UpdateTargetLayers(0,0) →