# libwebrtc Congestion Control and BWE *NOTE:* Using libwebrtc M77 branch. ## Resources * [Quality with Real-Time OTT media? It’s all about the feedback!](http://webrtcbydralex.com/index.php/2019/07/30/quality-with-real-time-ott-media-its-all-about-the-feedback/) (by Dr Alex) * [WebRTC Congestion Control Algorithm - Introduction to GCC](http://www.programmersought.com/article/5754684436/) * [WebRTC based on GCC congestion control analysis](http://www.programmersought.com/article/26011549416/) * [Bandwidth Estimation in WebRTC (and the new Sender Side BWE)](http://www.rtcbits.com/2017/01/bandwidth-estimation-in-webrtc-and-new.html) (by Gustavo) * [libwebrtc Congestion Controller and Bandwidth Estimator (Notes)](https://gist.github.com/ibc/77ed0355ea617a47823cf70d13a065dd) ## Modules and Classes Here the identified libwebrtc modules and classes must be exposed, with a brief description about their purpose and exposed API, and whether they are "public" classes or internally used ones by others. ### Classes implementing `RtcpBandwidthObserver` In `modules/rtp_rtcp/include/rtp_rtcp_defines.h`: ```c++ class RtcpBandwidthObserver { public: // REMB or TMMBR virtual void OnReceivedEstimatedBitrate(uint32_t bitrate) = 0; virtual void OnReceivedRtcpReceiverReport( const ReportBlockList& report_blocks, int64_t rtt, int64_t now_ms) = 0; virtual ~RtcpBandwidthObserver() {} }; ``` In `audio/channel_send.cc`: ```c++ class VoERtcpObserver : public RtcpBandwidthObserver ```` In `call/rtp_transport_controller_send.h` (*NOTE:* this is the one that `RTCPReceiver` uses): ```c++ class RtpTransportControllerSend final : public RtpTransportControllerSendInterface, public RtcpBandwidthObserver, public TransportFeedbackObserver { ``` In `call/rtp_transport_controller_send.h`: ```c++ std::unique_ptr<NetworkControllerInterface> controller_ ``` ### Classes implementing `TransportFeedbackObserver` In `modules/rtp_rtcp/include/rtp_rtcp_defines.h`: ```c++ class TransportFeedbackObserver { public: TransportFeedbackObserver() {} virtual ~TransportFeedbackObserver() {} virtual void OnAddPacket(const RtpPacketSendInfo& packet_info) = 0; virtual void OnTransportFeedback(const rtcp::TransportFeedback& feedback) = 0; }; ``` In `audio/channel_send.cc`: ```c++ class TransportFeedbackProxy : public TransportFeedbackObserver { public: TransportFeedbackProxy() : feedback_observer_(nullptr) { pacer_thread_.Detach(); network_thread_.Detach(); } ```` In `call/rtp_transport_controller_send.h`: ```c++ class RtpTransportControllerSend final : public RtpTransportControllerSendInterface, public RtcpBandwidthObserver, public TransportFeedbackObserver, public NetworkStateEstimateObserver { ``` ### Classes implementing `NetworkControllerInterface` In `modules/congestion_controller/goog_cc/goog_cc_network_control.h`: ```c++ class GoogCcNetworkController : public NetworkControllerInterface { public: GoogCcNetworkController(NetworkControllerConfig config, GoogCcConfig goog_cc_config); ```` (similar classes in `pcc` and `bbr` congestion control implementations). ### `PacketRouter` class In `src/modules/pacing/packet_router.h`. It inherits from `PacedSender::PacketSender` (in `src/modules/pacing/paced_sender.h`). ```c++ class PacedSender : public Pacer { public: class PacketSender { public: // Note: packets sent as a result of a callback should not pass by this // module again. // Called when it's time to send a queued packet. // Returns false if packet cannot be sent. virtual bool TimeToSendPacket(uint32_t ssrc, uint16_t sequence_number, int64_t capture_time_ms, bool retransmission, const PacedPacketInfo& cluster_info) = 0; // Called when it's a good time to send a padding data. // Returns the number of bytes sent. virtual size_t TimeToSendPadding(size_t bytes, const PacedPacketInfo& cluster_info) = 0; ``` ## Flows ### Incoming RTCP REMB flow In `modules/rtp_rtcp/source/rtcp_receiver.hpp`: ```c++ RtcpBandwidthObserver* const rtcp_bandwidth_observer_ ``` In `modules/rtp_rtcp/source/rtcp_receiver.cpp`: ```c++ RTCPReceiver::IncomingPacket( const uint8_t* packet, size_t packet_size) // => RTCPReceiver::TriggerCallbacksFromRtcpPacket( packet_information) // => // REMB or TMMBR RTCP packet. rtcp_bandwidth_observer_->OnReceivedEstimatedBitrate( packet_information.receiver_estimated_max_bitrate_bps); ``` In `call/rtp_transport_controller_send.cc` (the implementation of `RtcpBandwidthObserver` in use): ```c++ void RtpTransportControllerSend::OnReceivedEstimatedBitrate(uint32_t bitrate) // => if (controller_) PostUpdates(controller_->OnRemoteBitrateReport(msg)); ```` In `modules/congestion_controller/goog_cc/goog_cc_network_control.cc` (the implementation of `NetworkControllerInterface`) in use: ```c++ NetworkControlUpdate GoogCcNetworkController::OnRemoteBitrateReport(msg) // => bandwidth_estimation_->UpdateReceiverEstimate( msg.receive_time, msg.bandwidth); ```` In `modules/bitrate_controller/send_side_bandwidth_estimation.cc`: ```c++ void SendSideBandwidthEstimation::UpdateReceiverEstimate( Timestamp at_time, DataRate bandwidth) // => // Cap |bitrate| to [min_bitrate_configured_, max_bitrate_configured_] and // set |current_bitrate_| to the capped value and updates the event log. CapBitrateToThresholds(at_time, current_bitrate_); ``` ### Incoming RTCP TransportFeedback flow In `modules/rtp_rtcp/source/rtcp_receiver.h`: ```c++ TransportFeedbackObserver* const; ``` In `modules/rtp_rtcp/source/rtcp_receiver.cc`: ```c++ transport_feedback_observer_->OnTransportFeedback( *packet_information.transport_feedback); ``` In `call/rtp_transport_controller_send.cc`: ```c++ void RtpTransportControllerSend::OnTransportFeedback( const rtcp::TransportFeedback& feedback) { RTC_DCHECK_RUNS_SERIALIZED(&worker_race_); absl::optional<TransportPacketsFeedback> feedback_msg = transport_feedback_adapter_.ProcessTransportFeedback( feedback, Timestamp::ms(clock_->TimeInMilliseconds())); if (feedback_msg) { task_queue_.PostTask([this, feedback_msg]() { RTC_DCHECK_RUN_ON(&task_queue_); if (controller_) PostUpdates(controller_->OnTransportPacketsFeedback(*feedback_msg)); }); } pacer_.UpdateOutstandingData( transport_feedback_adapter_.GetOutstandingData().bytes()); } ``` In `congestion_controller/rtp/transport_feedback_adapter.cc`: ```c++ absl::optional<TransportPacketsFeedback> TransportFeedbackAdapter::ProcessTransportFeedback( const rtcp::TransportFeedback& feedback, Timestamp feedback_receive_time) { DataSize prior_in_flight = GetOutstandingData(); // (much more...) ``` ### Ougoing RTP Packet flow **NOTE:** Somewhere on RTP packet sent process it arrives here. In `modules/rtp_rtcp/source/rtp_sender.cc`: ```c++ bool RTPSender::PrepareAndSendPacket(std::unique_ptr<RtpPacketToSend> packet, bool send_over_rtx, bool is_retransmit, const PacedPacketInfo& pacing_info) // (more) packet_to_send->SetExtension<AbsoluteSendTime>( AbsoluteSendTime::MsTo24Bits(now_ms)); // => AddPacketToTransportFeedback( options.packet_id, *packet_to_send, pacing_info); ``` ```c++ void RTPSender::AddPacketToTransportFeedback( uint16_t packet_id, const RtpPacketToSend& packet, const PacedPacketInfo& pacing_info) { // => RtpPacketSendInfo packet_info; packet_info.ssrc = SSRC(); packet_info.transport_sequence_number = packet_id; packet_info.has_rtp_sequence_number = true; packet_info.rtp_sequence_number = packet.SequenceNumber(); packet_info.length = packet_size; packet_info.pacing_info = pacing_info; transport_feedback_observer_->OnAddPacket(packet_info); ``` In `call/rtp_transport_controller_send.cc`: ```c++ void RtpTransportControllerSend::OnAddPacket( const RtpPacketSendInfo& packet_info) { transport_feedback_adapter_.AddPacket( packet_info, send_side_bwe_with_overhead_ ? transport_overhead_bytes_per_packet_.load() : 0, Timestamp::ms(clock_->TimeInMilliseconds())); } // [...] void RtpTransportControllerSend::OnSentPacket( const rtc::SentPacket& sent_packet) { absl::optional<SentPacket> packet_msg = transport_feedback_adapter_.ProcessSentPacket(sent_packet); if (packet_msg) { task_queue_.PostTask([this, packet_msg]() { RTC_DCHECK_RUN_ON(&task_queue_); if (controller_) PostUpdates(controller_->OnSentPacket(*packet_msg)); }); } pacer_.UpdateOutstandingData( transport_feedback_adapter_.GetOutstandingData().bytes()); } ``` In `modules/congestion_controller/rtp/transport_feedback_adapter.cc`: ```c++ void TransportFeedbackAdapter::AddPacket(const RtpPacketSendInfo& packet_info, size_t overhead_bytes, Timestamp creation_time) { { rtc::CritScope cs(&lock_); PacketFeedback packet_feedback( creation_time.ms(), packet_info.transport_sequence_number, packet_info.length + overhead_bytes, local_net_id_, remote_net_id_, packet_info.pacing_info); if (packet_info.has_rtp_sequence_number) { packet_feedback.ssrc = packet_info.ssrc; packet_feedback.rtp_sequence_number = packet_info.rtp_sequence_number; } send_time_history_.RemoveOld(creation_time.ms()); send_time_history_.AddNewPacket(std::move(packet_feedback)); } { rtc::CritScope cs(&observers_lock_); for (auto* observer : observers_) { observer->OnPacketAdded(packet_info.ssrc, packet_info.transport_sequence_number); } } } ``` In `call/rtp_video_sender.h`: ```c++ void OnPacketAdded(uint32_t ssrc, uint16_t seq_num) override {} ``` *NOTE:* So it does nothing. ## Getting the new target bitrate Whenever the `controller_` is feed via any of its public methods, which all return a `NetworkControlUpdate`object , the following method is called: ```c++ RtpTransportControllerSend::PostUpdates(NetworkControlUpdate update) ``` Such method calls the private method: ```c++ void RtpTransportControllerSend::UpdateControlState() ```` Which: ```c++ // Calculates and retrieves the target transfer reate!! // Generates the log: RTC_LOG(LS_INFO) << "Bitrate estimate state changed, BWE: " << ToString(log_target_rate) << "."; absl::optional<TargetTransferRate> update = control_handler_->GetUpdate(); // Notifies the observer about the new trasnfer rate!! observer_->OnTargetTransferRate(*update); ``` `observer_` is `Call`: ```c++ void Call::OnTargetTransferRate(TargetTransferRate msg) { // TODO(bugs.webrtc.org/9719) // Call::OnTargetTransferRate requires that on target transfer rate is invoked // from the worker queue (because bitrate_allocator_ requires it). Media // transport does not guarantee the callback on the worker queue. // When the threading model for MediaTransportInterface is update, reconsider // changing this implementation. if (!transport_send_ptr_->GetWorkerQueue()->IsCurrent()) { transport_send_ptr_->GetWorkerQueue()->PostTask( [this, msg] { this->OnTargetTransferRate(msg); }); return; } uint32_t target_bitrate_bps = msg.target_rate.bps(); int loss_ratio_255 = msg.network_estimate.loss_rate_ratio * 255; uint8_t fraction_loss = rtc::dchecked_cast<uint8_t>(rtc::SafeClamp(loss_ratio_255, 0, 255)); int64_t rtt_ms = msg.network_estimate.round_trip_time.ms(); int64_t probing_interval_ms = msg.network_estimate.bwe_period.ms(); uint32_t bandwidth_bps = msg.network_estimate.bandwidth.bps(); { rtc::CritScope cs(&last_bandwidth_bps_crit_); last_bandwidth_bps_ = bandwidth_bps; } // For controlling the rate of feedback messages. receive_side_cc_.OnBitrateChanged(target_bitrate_bps); bitrate_allocator_->OnNetworkChanged(target_bitrate_bps, bandwidth_bps, fraction_loss, rtt_ms, probing_interval_ms); // Ignore updates if bitrate is zero (the aggregate network state is down). if (target_bitrate_bps == 0) { rtc::CritScope lock(&bitrate_crit_); estimated_send_bitrate_kbps_counter_.ProcessAndPause(); pacer_bitrate_kbps_counter_.ProcessAndPause(); return; } bool sending_video; { ReadLockScoped read_lock(*send_crit_); sending_video = !video_send_streams_.empty(); } rtc::CritScope lock(&bitrate_crit_); if (!sending_video) { // Do not update the stats if we are not sending video. estimated_send_bitrate_kbps_counter_.ProcessAndPause(); pacer_bitrate_kbps_counter_.ProcessAndPause(); return; } estimated_send_bitrate_kbps_counter_.Add(target_bitrate_bps / 1000); // Pacer bitrate may be higher than bitrate estimate if enforcing min bitrate. uint32_t pacer_bitrate_bps = std::max(target_bitrate_bps, min_allocated_send_bitrate_bps_); pacer_bitrate_kbps_counter_.Add(pacer_bitrate_bps / 1000); } ``` The following method distributes the bandwdith within its `bitrate_observers_configs_` and ```c++ void BitrateAllocator::OnNetworkChanged(uint32_t target_bitrate_bps, uint32_t link_capacity_bps, uint8_t fraction_loss, int64_t rtt, int64_t bwe_period_ms) { ... // Calculate a new allocation and update all observers. ObserverAllocation allocation = AllocateBitrates(last_target_bps_); ObserverAllocation bandwidth_allocation = AllocateBitrates(last_link_capacity_bps_); for (auto& config : bitrate_observer_configs_) { uint32_t allocated_bitrate = allocation[config.observer]; uint32_t bandwidth = bandwidth_allocation[config.observer]; BitrateAllocationUpdate update; update.target_bitrate = DataRate::bps(allocated_bitrate); update.link_capacity = DataRate::bps(bandwidth); update.packet_loss_ratio = last_fraction_loss_ / 256.0; update.round_trip_time = TimeDelta::ms(last_rtt_); update.bwe_period = TimeDelta::ms(last_bwe_period_ms_); uint32_t protection_bitrate = config.observer->OnBitrateUpdated(update); config.allocated_bitrate_bps = allocated_bitrate; if (allocated_bitrate > 0) config.media_ratio = MediaRatio(allocated_bitrate, protection_bitrate); } ... } ``` ## Implementation at mediasoup *TODO:* Let's see. It seems that the `WebRtcTransport` class should have an instance of `RtpTransportControllerSend` that is defined/declared at: * `call/rtp_transport_controller_send.h` * `call/rtp_transport_controller_send.cc` We must also check the interface definition: * `call/rtp_transport_controller_send_interface.h` The user of `RtpTransportControllerSendInterface` are: * `call/call.h` * `call/call.cc` * `video/video_send_stream.h` * `video/video_send_stream_impl.h` * `video/video_send_stream_impl.cc` * `audio/channel_send.h` * `audio/channel_send.cc` * `audio/audio_send_stream.h` * `audio/audio_send_stream.cc` * `call/rtp_video_sender.h` * `call/rtp_video_sender.cc` It seems that, instead of directly calling `TimeUntilNextProcess()` in modules (most above classes inherit from `Module`), libwebrtc uses `modules/utility/source/process_thread_impl.cc`: ```c++ int64_t GetNextCallbackTime(Module* module, int64_t time_now) { int64_t interval = module->TimeUntilNextProcess(); if (interval < 0) { // Falling behind, we should call the callback now. return time_now; } return time_now + interval; } ``` (Check also the `ProcessThreadImpl::Process()` method). ## Files related to client-side BWE * `PacketFeedback` struct: * Defined in `modules/rtp_rtcp/include/rtp_rtcp_defines.h`. * Holds info about sent packets and their corresponding receive feedbacks. * It has these members: ```c++ // Time corresponding to when this object was created. int64_t creation_time_ms; // Time corresponding to when the packet was received. Timestamped with the // receiver's clock. For unreceived packet, the sentinel value kNotReceived // is used. int64_t arrival_time_ms; // Time corresponding to when the packet was sent, timestamped with the // sender's clock. int64_t send_time_ms; // Packet identifier, incremented with 1 for every packet generated by the // sender. uint16_t sequence_number; // Session unique packet identifier, incremented with 1 for every packet // generated by the sender. int64_t long_sequence_number; // Size of the packet excluding RTP headers. size_t payload_size; // Size of preceeding packets that are not part of feedback. size_t unacknowledged_data; // The network route ids that this packet is associated with. uint16_t local_net_id; uint16_t remote_net_id; // Pacing information about this packet. PacedPacketInfo pacing_info; // The SSRC and RTP sequence number of the packet this feedback refers to. absl::optional<uint32_t> ssrc; uint16_t rtp_sequence_number; ``` * `PacedPacketInfo` struct: * Defined in `api/transport/network_types.h`. * It has these members: ```c++ int send_bitrate_bps = -1; int probe_cluster_id = kNotAProbe; int probe_cluster_min_probes = -1; int probe_cluster_min_bytes = -1; ``` * `PacketResult` struct: * Defined in `api/transport/network_types.h`. * It has these members: ```c++ SentPacket sent_packet; Timestamp receive_time = Timestamp::PlusInfinity(); ```