Repository navigation
Conversation
# Details - Related to #1895. - Integrate `SenderTransportCongestionController` in `Transport` under the `MS_USE_BUILTIN_BWE` macro condition as usual. - Do not advertise support for the "abs-send-time" RTP extension in audio, since mediasoup never uses received audio packets for receiver-side BWE (REMB) and never includes this extension in outgoing audio packets, as other REMB estimators do not use it for audio either (such as libwebrtc). - Advertise support for "transport-wide-cc-01" RTP extension also for sending in audio because we need it in the new BWE. - Many changes in other clases (mandatory changes).
jmillan
previously approved these changes
Oct 16, 2026
jmillan
self-requested a review
October 16, 2026 08:38
39 of 42 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Details
SenderTransportCongestionControllerinTransportunder theMS_USE_BUILTIN_BWEmacro condition as usual.DirectTransportbecause it doesn't make sense (obviously).DirectTransport: Don't negotiate BWE related RTCP feedback nor RTP header extensions inproduce()andconsume().PipeTransport: Don't negotiate BWE related RTCP feedback nor RTP header extensions inproduce()(it was already done inconsume()).Notes
Externally or non externally managed bitrate in
ConsumersIn OLD BWE, "externally managed bitrate" means that there is BWE, so the old
TransportCongestionControlClientexists in theTransportand it redistributes the estimated bitrate across allConsumersevery time it reports a new available bitrate. TheTransportsets the flag on everyConsumerat the very moment it creates that client, and the client is only created once a videoConsumerthat supports transport-cc or REMB is added.In OLD BWE, "non externally managed bitrate" means that there is no BWE, so there is no
TransportCongestionControlClientin theTransport.In both cases, a
Consumer(or itsXxxProducerStreamManager) detects that a simulcast stream or a spatial/temporal layer is turned off or on, and both cases go through the same method,ProducerStreamManager::MayChangeLayers(), which is where the two behaviours part:OnProducerStreamManagerNeedBitrateChange()so that theTransportredistributes the available downlink bitrate and theConsumergets a chance to re-evaluate which simulcast stream or spatial/temporal layer to consume (otherwise it could get stuck on a stream/layer that is no longer active). It only does so when the spatial layer it works out has changed, since the temporal one follows from the bitrate it is given.UpdateTargetLayers()itself and applies the new stream/layer right there, without theTransporttaking any part.This PR removes the "non externally managed bitrate" concept from the
ConsumerandProducerStreamManagerclasses, soProducerStreamManager::MayChangeLayers()never applies its calculations anymore, it only asks theTransportfor a new bitrate round. The logic becomes:PipeTransportand aDirectTransportnever run BWE (IsCongestionControlSupported()returns false in both), so noBWE::SenderTransportCongestionControlleris ever built for them and nobody answers a bitrate round.PipeTransportthat is harmless, becausePipeProducerStreamManager::RecalculateTargetLayers()always returns false, so such aConsumernever asks for a round in the first place and delivers all packets regardless. Note that a pipedConsumercan also live on a non-pipe transport (consume({ pipe: true })), and there it is just traffic that cannot be moved, taken off the top of the available bitrate.DirectTransportit is not harmless, and it is the open problem: a simulcast or SVCConsumerthere uses a regularSimulcastProducerStreamManagerorSvcProducerStreamManager, which do ask for rounds. With nobody to answer, their target layers stay at-1and theConsumerforwards nothing. This needs the second half of the old flag back in some shape: not "who manages the bitrate" but "apply your own calculation when there is no one to ask".Transport::MayCreateOrUpdateSenderTransportCongestionController()builds theBWE::SenderTransportCongestionControllerwhen the transport is connected and has at least oneConsumer, and is called both fromTransport::Connected()and when aConsumeris added. It is never built for a transport with nothing to send, since telling it that the network is available is what starts the RTP probing. Such a controller can work with transport-cc, with REMB, or with nothing but the loss reported in RTCP Receiver Reports. This was not true in the old BWE, where transport-cc or REMB was required forTransportCongestionControlClientto exist at all.Consumerasks for a round,Transport::OnConsumerNeedBitrateChange()runsDistributeAvailableOutgoingBitrate()there and then, with whatever target the controller holds, which with no feedback at all is still the initial one. What the endpoint reporting buys is the target moving: without transport-cc, REMB or Receiver Reports the target never leavesinitialAvailableOutgoingBitrate.TODO
Consumeron aDirectTransportis now stuck:ProducerStreamManager::MayChangeLayers()only asks theTransportfor a bitrate round and there is noBWE::SenderTransportCongestionControllerto answer it, so its target layers stay at-1and it forwards nothing. The oldexternallyManagedBitrateflag meant two things, "there is no ceiling" and "apply your own calculation", and only the first one is covered. Note that a latched flag is not the right shape, since what matters is whether there is someone to answer right now, andTransport::IsCongestionControlSupported()is not the right predicate either, since it is true from aWebRtcTransport's constructor while the controller only appears later.PlainTransportwhose receiving endpoint reports nothing (no transport-cc, no REMB, no RTCP Receiver Reports) keeps handing outinitialAvailableOutgoingBitratefor ever, so itsConsumersare capped at whatever fits in it. The old BWE had no ceiling at all there. Decide what such a transport should hand out.ortc.tsandortc.rs: Must revisit Dependency Descriptor RTP header extension forPipeTransportandDirectTransport.Consumer(and/or in itsPipeProducerStreamManager) methods such asMayChangeLayers(),OnProducerStreamManagerNeedBitrateChange(), etc. should never be even invoked because it's a pipeConsumerso we want to deliver all RTP packets no matter what happens to the streams or layers of theProducer, and also because there is noSenderTransportCongestionControllerto deal with those events.initialAvailableOutgoingBitrateoption only exists inWebRtcTransportOptions. Why not inPlainTransport? NOTE: If we change this we also need to update the API doc in the website.