From 458edbc532f58d9b062d396e4a6ee86be9ad5a8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Cervi=C3=B1o?= Date: Thu, 17 Oct 2019 13:39:33 +0200 Subject: [PATCH] Add new Padding Manager in WebRtcConnection (#1476) --- erizo/src/erizo/MediaDefinitions.h | 8 +- erizo/src/erizo/MediaStream.cpp | 29 ++- erizo/src/erizo/MediaStream.h | 7 + erizo/src/erizo/WebRtcConnection.cpp | 15 +- erizo/src/erizo/WebRtcConnection.h | 1 + .../bandwidth/ConnectionQualityCheck.cpp | 13 +- .../erizo/bandwidth/ConnectionQualityCheck.h | 2 + .../bandwidth/TargetVideoBWDistributor.cpp | 27 +- .../bandwidth/TargetVideoBWDistributor.h | 3 +- .../erizo/rtp/RtpPaddingGeneratorHandler.cpp | 82 +----- .../erizo/rtp/RtpPaddingGeneratorHandler.h | 7 +- .../erizo/rtp/RtpPaddingManagerHandler.cpp | 186 ++++++++++++++ .../src/erizo/rtp/RtpPaddingManagerHandler.h | 53 ++++ erizo/src/erizo/rtp/RtpUtils.cpp | 4 +- .../rtp/SenderBandwidthEstimantionHandler.cpp | 2 +- .../rtp/SenderBandwidthEstimationHandler.h | 2 + erizo/src/test/WebRtcConnectionTest.cpp | 91 ++++--- .../bandwidth/TargetVideoBWDistributor.cpp | 10 +- .../rtp/RtpPaddingGeneratorHandlerTest.cpp | 41 +-- .../test/rtp/RtpPaddingManagerHandlerTest.cpp | 239 ++++++++++++++++++ erizo/src/test/utils/Mocks.h | 7 + erizo/src/test/utils/Tools.h | 1 + .../erizoAgent/log4cxx.properties | 1 + extras/basic_example/public/script.js | 2 +- 24 files changed, 652 insertions(+), 181 deletions(-) create mode 100644 erizo/src/erizo/rtp/RtpPaddingManagerHandler.cpp create mode 100644 erizo/src/erizo/rtp/RtpPaddingManagerHandler.h create mode 100644 erizo/src/test/rtp/RtpPaddingManagerHandlerTest.cpp diff --git a/erizo/src/erizo/MediaDefinitions.h b/erizo/src/erizo/MediaDefinitions.h index a5b288888a..3dc501ad03 100644 --- a/erizo/src/erizo/MediaDefinitions.h +++ b/erizo/src/erizo/MediaDefinitions.h @@ -11,6 +11,7 @@ #include "lib/Clock.h" #include "lib/ClockUtils.h" +#include "rtp/RtpHeaders.h" namespace erizo { @@ -25,19 +26,19 @@ struct DataPacket { DataPacket(int comp_, const char *data_, int length_, packetType type_, uint64_t received_time_ms_) : comp{comp_}, length{length_}, type{type_}, received_time_ms{received_time_ms_}, is_keyframe{false}, - ending_of_layer_frame{false}, picture_id{-1}, tl0_pic_idx{-1} { + ending_of_layer_frame{false}, picture_id{-1}, tl0_pic_idx{-1}, is_padding{false} { memcpy(data, data_, length_); } DataPacket(int comp_, const char *data_, int length_, packetType type_) : comp{comp_}, length{length_}, type{type_}, received_time_ms{ClockUtils::timePointToMs(clock::now())}, - is_keyframe{false}, ending_of_layer_frame{false}, picture_id{-1}, tl0_pic_idx{-1} { + is_keyframe{false}, ending_of_layer_frame{false}, picture_id{-1}, tl0_pic_idx{-1}, is_padding{false} { memcpy(data, data_, length_); } DataPacket(int comp_, const unsigned char *data_, int length_) : comp{comp_}, length{length_}, type{VIDEO_PACKET}, received_time_ms{ClockUtils::timePointToMs(clock::now())}, - is_keyframe{false}, ending_of_layer_frame{false}, picture_id{-1}, tl0_pic_idx{-1} { + is_keyframe{false}, ending_of_layer_frame{false}, picture_id{-1}, tl0_pic_idx{-1}, is_padding{false} { memcpy(data, data_, length_); } @@ -70,6 +71,7 @@ struct DataPacket { int tl0_pic_idx; std::string codec; unsigned int clock_rate = 0; + bool is_padding; }; class Monitor { diff --git a/erizo/src/erizo/MediaStream.cpp b/erizo/src/erizo/MediaStream.cpp index 83fd901112..ef863512b7 100644 --- a/erizo/src/erizo/MediaStream.cpp +++ b/erizo/src/erizo/MediaStream.cpp @@ -44,6 +44,7 @@ DEFINE_LOGGER(MediaStream, "MediaStream"); log4cxx::LoggerPtr MediaStream::statsLogger = log4cxx::Logger::getLogger("StreamStats"); static constexpr auto kStreamStatsPeriod = std::chrono::seconds(120); +static constexpr uint64_t kInitialBitrate = 300000; MediaStream::MediaStream(std::shared_ptr worker, std::shared_ptr connection, @@ -64,7 +65,8 @@ MediaStream::MediaStream(std::shared_ptr worker, simulcast_{false}, bitrate_from_max_quality_layer_{0}, video_bitrate_{0}, - random_generator_{random_device_()} { + random_generator_{random_device_()}, + target_padding_bitrate_{0} { if (is_publisher) { setVideoSinkSSRC(kDefaultVideoSinkSSRC); setAudioSinkSSRC(kDefaultAudioSinkSSRC); @@ -637,6 +639,31 @@ void MediaStream::setSlideShowMode(bool state) { notifyUpdateToHandlers(); } +void MediaStream::setTargetPaddingBitrate(uint64_t target_padding_bitrate) { + target_padding_bitrate_ = target_padding_bitrate; + notifyUpdateToHandlers(); +} + +uint32_t MediaStream::getTargetVideoBitrate() { + bool slide_show_mode = isSlideShowModeEnabled(); + bool is_simulcast = isSimulcast(); + uint32_t bitrate_sent = getVideoBitrate(); + uint32_t max_bitrate = getMaxVideoBW(); + uint32_t bitrate_from_max_quality_layer = getBitrateFromMaxQualityLayer(); + + uint32_t target_bitrate = max_bitrate; + if (is_simulcast) { + target_bitrate = std::min(bitrate_from_max_quality_layer, max_bitrate); + } + if (slide_show_mode || !is_simulcast) { + target_bitrate = std::min(bitrate_sent, max_bitrate); + } + if (target_bitrate == 0) { + target_bitrate = kInitialBitrate; + } + return target_bitrate; +} + void MediaStream::muteStream(bool mute_video, bool mute_audio) { asyncTask([mute_audio, mute_video] (std::shared_ptr media_stream) { ELOG_DEBUG("%s message: muteStream, mute_video: %u, mute_audio: %u", media_stream->toLog(), mute_video, mute_audio); diff --git a/erizo/src/erizo/MediaStream.h b/erizo/src/erizo/MediaStream.h index d24d4489e2..5dc638b047 100644 --- a/erizo/src/erizo/MediaStream.h +++ b/erizo/src/erizo/MediaStream.h @@ -155,6 +155,12 @@ class MediaStream: public MediaSink, public MediaSource, public FeedbackSink, bool isSinkSSRC(uint32_t ssrc); void parseIncomingPayloadType(char *buf, int len, packetType type); void parseIncomingExtensionId(char *buf, int len, packetType type); + virtual void setTargetPaddingBitrate(uint64_t bitrate); + virtual uint64_t getTargetPaddingBitrate() { + return target_padding_bitrate_; + } + + virtual uint32_t getTargetVideoBitrate(); bool isPipelineInitialized() { return pipeline_initialized_; } bool isRunning() { return pipeline_initialized_ && sending_; } @@ -223,6 +229,7 @@ class MediaStream: public MediaSink, public MediaSource, public FeedbackSink, std::atomic video_bitrate_; std::random_device random_device_; std::mt19937 random_generator_; + uint64_t target_padding_bitrate_; protected: std::shared_ptr remote_sdp_; }; diff --git a/erizo/src/erizo/WebRtcConnection.cpp b/erizo/src/erizo/WebRtcConnection.cpp index 5d16bc2d7c..a7d525332e 100644 --- a/erizo/src/erizo/WebRtcConnection.cpp +++ b/erizo/src/erizo/WebRtcConnection.cpp @@ -35,6 +35,7 @@ #include "rtp/QualityManager.h" #include "rtp/PliPacerHandler.h" #include "rtp/RtpPaddingGeneratorHandler.h" +#include "rtp/RtpPaddingManagerHandler.h" #include "rtp/RtpUtils.h" namespace erizo { @@ -121,6 +122,7 @@ void WebRtcConnection::initializePipeline() { pipeline_->addFront(std::make_shared(this)); pipeline_->addFront(std::make_shared()); + pipeline_->addFront(std::make_shared()); pipeline_->addFront(std::make_shared(this)); pipeline_->finalize(); @@ -128,6 +130,11 @@ void WebRtcConnection::initializePipeline() { } void WebRtcConnection::notifyUpdateToHandlers() { + asyncTask([] (std::shared_ptr conn) { + if (conn && conn->pipeline_ && conn->pipeline_initialized_) { + conn->pipeline_->notifyUpdate(); + } + }); } boost::future WebRtcConnection::createOffer(bool video_enabled, bool audio_enabled, bool bundle) { @@ -209,6 +216,10 @@ ConnectionQualityLevel WebRtcConnection::getConnectionQualityLevel() { return connection_quality_check_.getLevel(); } +bool WebRtcConnection::werePacketLossesRecently() { + return connection_quality_check_.werePacketLossesRecently(); +} + boost::future WebRtcConnection::addMediaStream(std::shared_ptr media_stream) { return asyncTask([media_stream] (std::shared_ptr connection) { boost::mutex::scoped_lock lock(connection->update_state_mutex_); @@ -281,9 +292,7 @@ boost::future WebRtcConnection::setRemoteSdpInfo( return; } connection->remote_sdp_ = sdp; - if (connection->pipeline_initialized_ && connection->pipeline_) { - connection->pipeline_->notifyUpdate(); - } + connection->notifyUpdateToHandlers(); boost::future future = connection->processRemoteSdp().then( [task_promise] (boost::future) { task_promise->set_value(); diff --git a/erizo/src/erizo/WebRtcConnection.h b/erizo/src/erizo/WebRtcConnection.h index a03ec18bf9..76f3efff9b 100644 --- a/erizo/src/erizo/WebRtcConnection.h +++ b/erizo/src/erizo/WebRtcConnection.h @@ -171,6 +171,7 @@ class WebRtcConnection: public TransportListener, public LogContext, public Hand void write(std::shared_ptr packet); void notifyUpdateToHandlers() override; ConnectionQualityLevel getConnectionQualityLevel(); + bool werePacketLossesRecently(); void getJSONStats(std::function callback); private: diff --git a/erizo/src/erizo/bandwidth/ConnectionQualityCheck.cpp b/erizo/src/erizo/bandwidth/ConnectionQualityCheck.cpp index 91fc8f1b28..8d22887e65 100644 --- a/erizo/src/erizo/bandwidth/ConnectionQualityCheck.cpp +++ b/erizo/src/erizo/bandwidth/ConnectionQualityCheck.cpp @@ -19,7 +19,7 @@ constexpr uint8_t ConnectionQualityCheck::kLowVideoFractionLostThreshold; constexpr size_t ConnectionQualityCheck::kNumberOfPacketsPerStream; ConnectionQualityCheck::ConnectionQualityCheck() - : quality_level_{ConnectionQualityLevel::GOOD}, audio_buffer_{1}, video_buffer_{1} { + : quality_level_{ConnectionQualityLevel::GOOD}, audio_buffer_{1}, video_buffer_{1}, recent_packet_losses_{false} { } void ConnectionQualityCheck::onFeedback(std::shared_ptr packet, @@ -59,6 +59,10 @@ void ConnectionQualityCheck::onFeedback(std::shared_ptr packet, } } +bool ConnectionQualityCheck::werePacketLossesRecently() { + return recent_packet_losses_; +} + void ConnectionQualityCheck::maybeNotifyMediaStreamsAboutConnectionQualityLevel( const std::vector> &streams) { if (!audio_buffer_.full() || !video_buffer_.full()) { @@ -76,6 +80,13 @@ void ConnectionQualityCheck::maybeNotifyMediaStreamsAboutConnectionQualityLevel( } uint8_t audio_fraction_lost = audio_buffer_size > 0 ? total_audio_fraction_lost / audio_buffer_size : 0; uint8_t video_fraction_lost = video_buffer_size > 0 ? total_video_fraction_lost / video_buffer_size : 0; + + if (audio_fraction_lost == 0 && video_fraction_lost == 0) { + recent_packet_losses_ = false; + } else { + recent_packet_losses_ = true; + } + ConnectionQualityLevel level = ConnectionQualityLevel::GOOD; if (audio_fraction_lost >= kHighAudioFractionLostThreshold) { level = ConnectionQualityLevel::HIGH_LOSSES; diff --git a/erizo/src/erizo/bandwidth/ConnectionQualityCheck.h b/erizo/src/erizo/bandwidth/ConnectionQualityCheck.h index bcc3cf24e3..a19769eaa2 100644 --- a/erizo/src/erizo/bandwidth/ConnectionQualityCheck.h +++ b/erizo/src/erizo/bandwidth/ConnectionQualityCheck.h @@ -47,12 +47,14 @@ class ConnectionQualityCheck { virtual ~ConnectionQualityCheck() {} void onFeedback(std::shared_ptr packet, const std::vector> &streams); ConnectionQualityLevel getLevel() { return quality_level_; } + bool werePacketLossesRecently(); private: void maybeNotifyMediaStreamsAboutConnectionQualityLevel(const std::vector> &streams); private: ConnectionQualityLevel quality_level_; circular_buffer audio_buffer_; circular_buffer video_buffer_; + bool recent_packet_losses_; }; } // namespace erizo diff --git a/erizo/src/erizo/bandwidth/TargetVideoBWDistributor.cpp b/erizo/src/erizo/bandwidth/TargetVideoBWDistributor.cpp index c2d61dd4a2..a0136a6f89 100644 --- a/erizo/src/erizo/bandwidth/TargetVideoBWDistributor.cpp +++ b/erizo/src/erizo/bandwidth/TargetVideoBWDistributor.cpp @@ -22,20 +22,21 @@ void TargetVideoBWDistributor::distribute(uint32_t remb, uint32_t ssrc, stream->isSlideShowModeEnabled(), stream->getVideoBitrate(), stream->getMaxVideoBW(), - stream->getBitrateFromMaxQualityLayer()}); + stream->getBitrateFromMaxQualityLayer(), + stream->getTargetVideoBitrate()}); }); std::sort(stream_infos.begin(), stream_infos.end(), - [this](const MediaStreamInfo &i, const MediaStreamInfo &j) { - return getTargetVideoBW(i) < getTargetVideoBW(j); + [](const MediaStreamInfo &i, const MediaStreamInfo &j) { + return i.target_video_bitrate < j.target_video_bitrate; }); uint8_t remaining_streams = streams.size(); uint32_t remaining_bitrate = remb; std::for_each(stream_infos.begin(), stream_infos.end(), - [&remaining_bitrate, &remaining_streams, transport, ssrc, this](const MediaStreamInfo &stream) { + [&remaining_bitrate, &remaining_streams, transport, ssrc](const MediaStreamInfo &stream) { uint32_t max_bitrate = stream.max_video_bw; - uint32_t target_bitrate = getTargetVideoBW(stream); + uint32_t target_bitrate = stream.target_video_bitrate; uint32_t remaining_avg_bitrate = remaining_bitrate / remaining_streams; uint32_t bitrate = std::min(target_bitrate, remaining_avg_bitrate); @@ -48,20 +49,4 @@ void TargetVideoBWDistributor::distribute(uint32_t remb, uint32_t ssrc, }); } -uint32_t TargetVideoBWDistributor::getTargetVideoBW(const MediaStreamInfo &stream) { - bool slide_show_mode = stream.is_slideshow; - bool is_simulcast = stream.is_simulcast; - uint32_t bitrate_sent = stream.bitrate_sent; - uint32_t max_bitrate = stream.max_video_bw; - - uint32_t target_bitrate = max_bitrate; - if (is_simulcast) { - target_bitrate = std::min(stream.bitrate_from_max_quality_layer, max_bitrate); - } - if (slide_show_mode) { - target_bitrate = std::min(bitrate_sent, max_bitrate); - } - return target_bitrate; -} - } // namespace erizo diff --git a/erizo/src/erizo/bandwidth/TargetVideoBWDistributor.h b/erizo/src/erizo/bandwidth/TargetVideoBWDistributor.h index 9702689907..5b9069569a 100644 --- a/erizo/src/erizo/bandwidth/TargetVideoBWDistributor.h +++ b/erizo/src/erizo/bandwidth/TargetVideoBWDistributor.h @@ -15,6 +15,7 @@ struct MediaStreamInfo { uint32_t bitrate_sent; uint32_t max_video_bw; uint32_t bitrate_from_max_quality_layer; + uint32_t target_video_bitrate; }; class TargetVideoBWDistributor : public BandwidthDistributionAlgorithm { @@ -23,8 +24,6 @@ class TargetVideoBWDistributor : public BandwidthDistributionAlgorithm { virtual ~TargetVideoBWDistributor() {} void distribute(uint32_t remb, uint32_t ssrc, std::vector> streams, Transport *transport) override; - private: - uint32_t getTargetVideoBW(const MediaStreamInfo &stream); }; } // namespace erizo diff --git a/erizo/src/erizo/rtp/RtpPaddingGeneratorHandler.cpp b/erizo/src/erizo/rtp/RtpPaddingGeneratorHandler.cpp index 92f13f252d..b6144343f0 100644 --- a/erizo/src/erizo/rtp/RtpPaddingGeneratorHandler.cpp +++ b/erizo/src/erizo/rtp/RtpPaddingGeneratorHandler.cpp @@ -12,7 +12,6 @@ namespace erizo { DEFINE_LOGGER(RtpPaddingGeneratorHandler, "rtp.RtpPaddingGeneratorHandler"); -constexpr duration kStatsPeriod = std::chrono::milliseconds(100); constexpr uint8_t kMaxPaddingSize = 255; constexpr uint64_t kMinMarkerRate = 3; constexpr uint64_t kInitialBitrate = 300000; @@ -20,10 +19,10 @@ constexpr uint8_t kMaxBurstPackets = 200; constexpr uint8_t kSlideShowBurstPackets = 20; RtpPaddingGeneratorHandler::RtpPaddingGeneratorHandler(std::shared_ptr the_clock) : - clock_{the_clock}, stream_{nullptr}, max_video_bw_{0}, higher_sequence_number_{0}, + clock_{the_clock}, stream_{nullptr}, higher_sequence_number_{0}, video_sink_ssrc_{0}, audio_source_ssrc_{0}, number_of_full_padding_packets_{0}, last_padding_packet_size_{0}, - last_rate_calculation_time_{clock_->now()}, started_at_{clock_->now()}, + started_at_{clock_->now()}, enabled_{false}, first_packet_received_{false}, slideshow_mode_active_ {false}, marker_rate_{std::chrono::milliseconds(100), 20, 1., clock_}, @@ -32,8 +31,6 @@ RtpPaddingGeneratorHandler::RtpPaddingGeneratorHandler(std::shared_ptr()} { } - - void RtpPaddingGeneratorHandler::enable() { } @@ -51,18 +48,13 @@ void RtpPaddingGeneratorHandler::notifyUpdate() { MovingIntervalRateStat{std::chrono::milliseconds(100), 30, 8., clock_}); } - auto quality_manager = pipeline->getService(); - - if (quality_manager->isPaddingEnabled() && !enabled_) { - enablePadding(); - } else if (!quality_manager->isPaddingEnabled() && enabled_) { - disablePadding(); + if (!stream_) { + return; } - auto processor = pipeline->getService(); - if (processor) { - max_video_bw_ = processor->getMaxVideoBW(); - } + uint64_t target_padding_bitrate = stream_->getTargetPaddingBitrate(); + + recalculatePaddingRate(target_padding_bitrate); slideshow_mode_active_ = stream_->isSlideShowModeEnabled(); } @@ -145,49 +137,25 @@ void RtpPaddingGeneratorHandler::onVideoPacket(std::shared_ptr packe return; } - recalculatePaddingRate(); - RtpHeader *rtp_header = reinterpret_cast(packet->data); if (rtp_header->getMarker()) { onPacketWithMarkerSet(std::move(packet)); } } -uint64_t RtpPaddingGeneratorHandler::getStat(std::string stat_name) { - if (stats_->getNode()[video_sink_ssrc_].hasChild(stat_name)) { - StatNode & stat = stats_->getNode()[video_sink_ssrc_][stat_name]; - return static_cast(stat).value(); - } - return 0; -} - -bool RtpPaddingGeneratorHandler::isTimeToCalculateBitrate() { - return (clock_->now() - last_rate_calculation_time_) >= kStatsPeriod; -} - -void RtpPaddingGeneratorHandler::recalculatePaddingRate() { - if (!isTimeToCalculateBitrate()) { - return; - } - - last_rate_calculation_time_ = clock_->now(); - - int64_t total_bitrate = getStat("bitrateCalculated"); - int64_t padding_bitrate = stats_->getNode()["total"]["paddingBitrate"].value(); - int64_t media_bitrate = std::max(total_bitrate - padding_bitrate, int64_t(0)); - - uint64_t target_bitrate = getTargetBitrate(); - - int64_t target_padding_bitrate = target_bitrate - media_bitrate; +void RtpPaddingGeneratorHandler::recalculatePaddingRate(uint64_t target_padding_bitrate) { // TODO(pedro): figure out a burst size that makes sense here - bucket_.reset(std::max(target_padding_bitrate, int64_t(0)), getBurstSize()); + bucket_.reset(target_padding_bitrate, getBurstSize()); - if (target_padding_bitrate <= 0) { + if (target_padding_bitrate == 0) { + enabled_ = false; number_of_full_padding_packets_ = 0; last_padding_packet_size_ = 0; return; } + enabled_ = true; + uint64_t marker_rate = marker_rate_.value(std::chrono::milliseconds(500)); marker_rate = std::max(marker_rate, kMinMarkerRate); // TODO(javier): There are arithmetic exceptions in the line following this if clause, so I'm @@ -200,19 +168,6 @@ void RtpPaddingGeneratorHandler::recalculatePaddingRate() { last_padding_packet_size_ = bytes_per_marker % (kMaxPaddingSize + rtp_header_length_) - rtp_header_length_; } -uint64_t RtpPaddingGeneratorHandler::getTargetBitrate() { - uint64_t target_bitrate = kInitialBitrate; - - if (stats_->getNode()["total"].hasChild("senderBitrateEstimation")) { - target_bitrate = static_cast(stats_->getNode()["total"]["senderBitrateEstimation"]).value(); - } - - if (max_video_bw_ > 0) { - target_bitrate = std::min(target_bitrate, max_video_bw_); - } - return target_bitrate; -} - uint64_t RtpPaddingGeneratorHandler::getBurstSize() { uint64_t burstPackets = kSlideShowBurstPackets; if (!slideshow_mode_active_) { @@ -221,15 +176,4 @@ uint64_t RtpPaddingGeneratorHandler::getBurstSize() { return burstPackets * kMaxPaddingSize; } -void RtpPaddingGeneratorHandler::enablePadding() { - enabled_ = true; - number_of_full_padding_packets_ = 0; - last_padding_packet_size_ = 0; - last_rate_calculation_time_ = clock_->now(); -} - -void RtpPaddingGeneratorHandler::disablePadding() { - enabled_ = false; -} - } // namespace erizo diff --git a/erizo/src/erizo/rtp/RtpPaddingGeneratorHandler.h b/erizo/src/erizo/rtp/RtpPaddingGeneratorHandler.h index 2d96d21178..4d5791ae2d 100644 --- a/erizo/src/erizo/rtp/RtpPaddingGeneratorHandler.h +++ b/erizo/src/erizo/rtp/RtpPaddingGeneratorHandler.h @@ -38,12 +38,9 @@ class RtpPaddingGeneratorHandler: public Handler, public std::enable_shared_from bool isHigherSequenceNumber(std::shared_ptr packet); void onVideoPacket(std::shared_ptr packet); - uint64_t getStat(std::string stat_name); - uint64_t getTargetBitrate(); uint64_t getBurstSize(); - bool isTimeToCalculateBitrate(); - void recalculatePaddingRate(); + void recalculatePaddingRate(uint64_t target_padding_bitrate); void enablePadding(); void disablePadding(); @@ -53,13 +50,11 @@ class RtpPaddingGeneratorHandler: public Handler, public std::enable_shared_from SequenceNumberTranslator translator_; MediaStream* stream_; std::shared_ptr stats_; - uint64_t max_video_bw_; uint16_t higher_sequence_number_; uint32_t video_sink_ssrc_; uint32_t audio_source_ssrc_; uint64_t number_of_full_padding_packets_; uint8_t last_padding_packet_size_; - time_point last_rate_calculation_time_; time_point started_at_; bool enabled_; bool first_packet_received_; diff --git a/erizo/src/erizo/rtp/RtpPaddingManagerHandler.cpp b/erizo/src/erizo/rtp/RtpPaddingManagerHandler.cpp new file mode 100644 index 0000000000..4810a5e0a4 --- /dev/null +++ b/erizo/src/erizo/rtp/RtpPaddingManagerHandler.cpp @@ -0,0 +1,186 @@ +#include "rtp/RtpPaddingManagerHandler.h" + +#include +#include +#include + +#include "./MediaDefinitions.h" +#include "./WebRtcConnection.h" +#include "./MediaStream.h" +#include "./RtpUtils.h" + +namespace erizo { + +DEFINE_LOGGER(RtpPaddingManagerHandler, "rtp.RtpPaddingManagerHandler"); + +static constexpr duration kStatsPeriod = std::chrono::milliseconds(100); +static constexpr duration kMinDurationToSendPaddingAfterPacketLosses = std::chrono::seconds(180); +static constexpr double kBitrateComparisonMargin = 1.3; +static constexpr uint64_t kInitialBitrate = 300000; + +RtpPaddingManagerHandler::RtpPaddingManagerHandler(std::shared_ptr the_clock) : + initialized_{false}, + clock_{the_clock}, + last_rate_calculation_time_{clock_->now()}, + last_time_with_packet_losses_{clock_->now()}, + connection_{nullptr}, + last_estimated_bandwidth_{0} { +} + +void RtpPaddingManagerHandler::enable() { +} + +void RtpPaddingManagerHandler::disable() { +} + +void RtpPaddingManagerHandler::notifyUpdate() { + if (initialized_) { + return; + } + + auto pipeline = getContext()->getPipelineShared(); + if (pipeline && !connection_) { + stats_ = pipeline->getService(); + if (!stats_) { + return; + } + connection_ = pipeline->getService().get(); + if (!connection_) { + return; + } + stats_->getNode()["total"].insertStat("paddingBitrate", + MovingIntervalRateStat{std::chrono::milliseconds(100), 30, 8., clock_}); + stats_->getNode()["total"].insertStat("videoBitrate", + MovingIntervalRateStat{std::chrono::milliseconds(100), 30, 8., clock_}); + } + + if (!connection_) { + return; + } + + initialized_ = true; +} + +bool RtpPaddingManagerHandler::isTimeToCalculateBitrate() { + return initialized_ && (clock_->now() - last_rate_calculation_time_) >= kStatsPeriod; +} + +void RtpPaddingManagerHandler::read(Context *ctx, std::shared_ptr packet) { + ctx->fireRead(std::move(packet)); +} + +void RtpPaddingManagerHandler::write(Context *ctx, std::shared_ptr packet) { + RtcpHeader *chead = reinterpret_cast(packet->data); + if (packet->is_padding) { + stats_->getNode()["total"]["paddingBitrate"] += packet->length; + } else if (packet->type == VIDEO_PACKET && !chead->isRtcp()) { + stats_->getNode()["total"]["videoBitrate"] += packet->length; + } + + recalculatePaddingRate(); + + ctx->fireWrite(packet); +} + +void RtpPaddingManagerHandler::recalculatePaddingRate() { + if (!isTimeToCalculateBitrate()) { + return; + } + + StatNode &total = stats_->getNode()["total"]; + + if (!total.hasChild("senderBitrateEstimation") || + !total.hasChild("videoBitrate")) { + return; + } + + last_rate_calculation_time_ = clock_->now(); + + int64_t media_bitrate = total["videoBitrate"].value(); + int64_t estimated_bandwidth = total["senderBitrateEstimation"].value(); + + int64_t target_bitrate = getTotalTargetBitrate(); + + if (target_bitrate == 0) { + target_bitrate = kInitialBitrate; + } + + int64_t target_padding_bitrate = std::max(target_bitrate - media_bitrate, int64_t(0)); + int64_t available_bw = std::max(estimated_bandwidth - media_bitrate, int64_t(0)); + + target_padding_bitrate = std::min(target_padding_bitrate, available_bw); + + bool can_send_more_bitrate = (kBitrateComparisonMargin * media_bitrate) < estimated_bandwidth; + bool estimated_is_high_enough = estimated_bandwidth > (target_bitrate * kBitrateComparisonMargin); + if (estimated_is_high_enough) { + target_padding_bitrate = 0; + } + + // Still try sending padding while there are no packet losses. + if (!can_send_more_bitrate) { + bool were_packet_losses_recently = connection_->werePacketLossesRecently(); + bool remb_is_decreasing = estimated_bandwidth < last_estimated_bandwidth_; + last_estimated_bandwidth_ = estimated_bandwidth; + duration time_without_packet_losses = clock_->now() - last_time_with_packet_losses_; + if (were_packet_losses_recently || remb_is_decreasing) { + target_padding_bitrate = 0; + last_time_with_packet_losses_ = clock_->now(); + } else if (time_without_packet_losses > kMinDurationToSendPaddingAfterPacketLosses) { + double step = 1.0; + if (time_without_packet_losses < 2 * kMinDurationToSendPaddingAfterPacketLosses) { + step = (time_without_packet_losses - kMinDurationToSendPaddingAfterPacketLosses) / + kMinDurationToSendPaddingAfterPacketLosses; + } + target_padding_bitrate = std::min(kInitialBitrate * step, kInitialBitrate * 1.0); + } + } + + ELOG_DEBUG("%s Calculated: target %d, bwe %d, media %d, target %d, can send more %d, bwe enough %d", + connection_->toLog(), + target_padding_bitrate, + estimated_bandwidth, + media_bitrate, + target_bitrate, + can_send_more_bitrate, + estimated_is_high_enough); + distributeTotalTargetPaddingBitrate(target_padding_bitrate); +} + +void RtpPaddingManagerHandler::distributeTotalTargetPaddingBitrate(int64_t bitrate) { + size_t num_streams = 0; + connection_->forEachMediaStream([&num_streams] + (std::shared_ptr media_stream) { + if (!media_stream->isPublisher()) { + num_streams++; + } + }); + stats_->getNode()["total"].insertStat("numberOfStreams", + CumulativeStat{static_cast(num_streams)}); + if (num_streams == 0) { + return; + } + int64_t bitrate_per_stream = bitrate / num_streams; + connection_->forEachMediaStreamAsync([bitrate_per_stream] + (std::shared_ptr media_stream) { + if (media_stream->isPublisher()) { + return; + } + media_stream->setTargetPaddingBitrate(bitrate_per_stream); + }); +} + +int64_t RtpPaddingManagerHandler::getTotalTargetBitrate() { + int64_t target_bitrate = 0; + connection_->forEachMediaStream([&target_bitrate] + (std::shared_ptr media_stream) { + if (media_stream->isPublisher()) { + return; + } + target_bitrate += media_stream->getTargetVideoBitrate(); + }); + stats_->getNode()["total"].insertStat("targetBitrate", + CumulativeStat{static_cast(target_bitrate)}); + + return target_bitrate; +} +} // namespace erizo diff --git a/erizo/src/erizo/rtp/RtpPaddingManagerHandler.h b/erizo/src/erizo/rtp/RtpPaddingManagerHandler.h new file mode 100644 index 0000000000..9d8ee6cfad --- /dev/null +++ b/erizo/src/erizo/rtp/RtpPaddingManagerHandler.h @@ -0,0 +1,53 @@ +#ifndef ERIZO_SRC_ERIZO_RTP_RTPPADDINGMANAGERHANDLER_H_ +#define ERIZO_SRC_ERIZO_RTP_RTPPADDINGMANAGERHANDLER_H_ + +#include + +#include "./logger.h" +#include "pipeline/Handler.h" +#include "lib/Clock.h" +#include "lib/TokenBucket.h" +#include "thread/Worker.h" +#include "rtp/SequenceNumberTranslator.h" +#include "./Stats.h" + +namespace erizo { + +class WebRtcConnection; + +class RtpPaddingManagerHandler: public Handler, public std::enable_shared_from_this { + DECLARE_LOGGER(); + + public: + explicit RtpPaddingManagerHandler(std::shared_ptr the_clock = std::make_shared()); + + void enable() override; + void disable() override; + + std::string getName() override { + return "padding-calculator"; + } + + void read(Context *ctx, std::shared_ptr packet) override; + void write(Context *ctx, std::shared_ptr packet) override; + void notifyUpdate() override; + + private: + bool isTimeToCalculateBitrate(); + void recalculatePaddingRate(); + void distributeTotalTargetPaddingBitrate(int64_t bitrate); + int64_t getTotalTargetBitrate(); + + private: + bool initialized_; + std::shared_ptr clock_; + time_point last_rate_calculation_time_; + time_point last_time_with_packet_losses_; + WebRtcConnection* connection_; + std::shared_ptr stats_; + int64_t last_estimated_bandwidth_; +}; + +} // namespace erizo + +#endif // ERIZO_SRC_ERIZO_RTP_RTPPADDINGMANAGERHANDLER_H_ diff --git a/erizo/src/erizo/rtp/RtpUtils.cpp b/erizo/src/erizo/rtp/RtpUtils.cpp index 119237d8f2..2af51df6d2 100644 --- a/erizo/src/erizo/rtp/RtpUtils.cpp +++ b/erizo/src/erizo/rtp/RtpUtils.cpp @@ -169,7 +169,9 @@ std::shared_ptr RtpUtils::makePaddingPacket(std::shared_ptrsetMarker(false); packet_buffer[packet_length - 1] = padding_size; - return std::make_shared(packet->comp, packet_buffer, packet_length, packet->type); + auto padding_packet = std::make_shared(packet->comp, packet_buffer, packet_length, packet->type); + padding_packet->is_padding = true; + return padding_packet; } std::shared_ptr RtpUtils::makeVP8BlackKeyframePacket(std::shared_ptr packet) { diff --git a/erizo/src/erizo/rtp/SenderBandwidthEstimantionHandler.cpp b/erizo/src/erizo/rtp/SenderBandwidthEstimantionHandler.cpp index 226f94d6b3..5a82381d65 100644 --- a/erizo/src/erizo/rtp/SenderBandwidthEstimantionHandler.cpp +++ b/erizo/src/erizo/rtp/SenderBandwidthEstimantionHandler.cpp @@ -13,7 +13,7 @@ SenderBandwidthEstimationHandler::SenderBandwidthEstimationHandler(std::shared_p connection_{nullptr}, bwe_listener_{nullptr}, clock_{the_clock}, initialized_{false}, enabled_{true}, received_remb_{false}, period_packets_sent_{0}, estimated_bitrate_{0}, estimated_loss_{0}, estimated_rtt_{0}, last_estimate_update_{clock::now()}, sender_bwe_{new SendSideBandwidthEstimation()} { - sender_bwe_->SetSendBitrate(kStartSendBitrate); + sender_bwe_->SetBitrates(kStartSendBitrate, kMinSendBitrate, kMaxSendBitrate); }; SenderBandwidthEstimationHandler::SenderBandwidthEstimationHandler(const SenderBandwidthEstimationHandler&& handler) : // NOLINT diff --git a/erizo/src/erizo/rtp/SenderBandwidthEstimationHandler.h b/erizo/src/erizo/rtp/SenderBandwidthEstimationHandler.h index 33242f3c61..3d2a4369b3 100644 --- a/erizo/src/erizo/rtp/SenderBandwidthEstimationHandler.h +++ b/erizo/src/erizo/rtp/SenderBandwidthEstimationHandler.h @@ -25,6 +25,8 @@ class SenderBandwidthEstimationHandler : public Handler, public: static const uint16_t kMaxSrListSize = 20; static const uint32_t kStartSendBitrate = 300000; + static const uint32_t kMinSendBitrate = 30000; + static const uint32_t kMaxSendBitrate = 1000000000; static constexpr duration kMinUpdateEstimateInterval = std::chrono::milliseconds(25); public: diff --git a/erizo/src/test/WebRtcConnectionTest.cpp b/erizo/src/test/WebRtcConnectionTest.cpp index 24626ef740..036358be6f 100644 --- a/erizo/src/test/WebRtcConnectionTest.cpp +++ b/erizo/src/test/WebRtcConnectionTest.cpp @@ -17,6 +17,8 @@ using testing::Return; using testing::Eq; using testing::Args; using testing::AtLeast; +using testing::ResultOf; +using testing::Invoke; using erizo::DataPacket; using erizo::ExtMap; using erizo::IceConfig; @@ -24,12 +26,12 @@ using erizo::RtpMap; using erizo::RtpUtils; using erizo::WebRtcConnection; -typedef std::vector MaxList; +typedef std::vector BitrateList; typedef std::vector EnabledList; typedef std::vector ExpectedList; class WebRtcConnectionTest : - public ::testing::TestWithParam> { @@ -48,7 +50,7 @@ class WebRtcConnectionTest : connection->setTransport(transport); connection->updateState(TRANSPORT_READY, transport.get()); connection->init(); - max_video_bw_list = std::tr1::get<0>(GetParam()); + video_bitrate_list = std::tr1::get<0>(GetParam()); bitrate_value = std::tr1::get<1>(GetParam()); add_to_remb_list = std::tr1::get<2>(GetParam()); expected_bitrates = std::tr1::get<3>(GetParam()); @@ -57,12 +59,12 @@ class WebRtcConnectionTest : } void setUpStreams() { - for (uint32_t max_video_bw : max_video_bw_list) { - streams.push_back(addMediaStream(false, max_video_bw)); + for (uint32_t video_bitrate : video_bitrate_list) { + streams.push_back(addMediaStream(false, video_bitrate)); } } - std::shared_ptr addMediaStream(bool is_publisher, uint32_t max_video_bw) { + std::shared_ptr addMediaStream(bool is_publisher, uint32_t video_bitrate) { std::string id = std::to_string(index); std::string label = std::to_string(index); uint32_t video_sink_ssrc = getSsrcFromIndex(index); @@ -77,7 +79,13 @@ class WebRtcConnectionTest : media_stream->setAudioSourceSSRC(audio_source_ssrc); connection->addMediaStream(media_stream); simulated_worker->executeTasks(); - EXPECT_CALL(*media_stream, getMaxVideoBW()).Times(AtLeast(0)).WillRepeatedly(Return(max_video_bw)); + EXPECT_CALL(*media_stream, isSlideShowModeEnabled()).WillRepeatedly(Return(false)); + EXPECT_CALL(*media_stream, isSimulcast()).WillRepeatedly(Return(false)); + EXPECT_CALL(*media_stream, getVideoBitrate()).WillRepeatedly(Return(video_bitrate)); + EXPECT_CALL(*media_stream, getMaxVideoBW()).WillRepeatedly(Return(video_bitrate)); + EXPECT_CALL(*media_stream, getBitrateFromMaxQualityLayer()).WillRepeatedly(Return(0)); + EXPECT_CALL(*media_stream, getTargetVideoBitrate()).WillRepeatedly( + Invoke(media_stream.get(), &erizo::MockMediaStream::MediaStream_getTargetVideoBitrate)); index++; return media_stream; } @@ -117,7 +125,7 @@ class WebRtcConnectionTest : } std::vector> streams; - MaxList max_video_bw_list; + BitrateList video_bitrate_list; uint32_t bitrate_value; EnabledList add_to_remb_list; ExpectedList expected_bitrates; @@ -134,12 +142,16 @@ class WebRtcConnectionTest : std::queue> packet_queue; }; +uint32_t HasRembWithValue(std::tuple> arg) { + return (reinterpret_cast(std::get<0>(arg)->data))->getREMBBitRate(); +} + TEST_P(WebRtcConnectionTest, forwardRembToStreams_When_StreamTheyExist) { uint32_t index = 0; for (int32_t expected_bitrate : expected_bitrates) { if (expected_bitrate > 0) { EXPECT_CALL(*(streams[index]), onTransportData(_, _)) - .With(Args<0>(erizo::RembHasBitrateValue(static_cast(expected_bitrate)))).Times(1); + .With(Args<0>(ResultOf(&HasRembWithValue, Eq(static_cast(expected_bitrate))))).Times(1); } else { EXPECT_CALL(*streams[index], onTransportData(_, _)).Times(0); } @@ -151,33 +163,34 @@ TEST_P(WebRtcConnectionTest, forwardRembToStreams_When_StreamTheyExist) { INSTANTIATE_TEST_CASE_P( REMB_values, WebRtcConnectionTest, testing::Values( - std::make_tuple(MaxList{300}, 100, EnabledList{1}, ExpectedList{100}), - std::make_tuple(MaxList{300}, 600, EnabledList{1}, ExpectedList{300}), - - std::make_tuple(MaxList{300, 300}, 300, EnabledList{1, 0}, ExpectedList{300, -1}), - std::make_tuple(MaxList{300, 300}, 300, EnabledList{0, 1}, ExpectedList{-1, 300}), - std::make_tuple(MaxList{300, 300}, 300, EnabledList{1, 1}, ExpectedList{150, 150}), - std::make_tuple(MaxList{100, 300}, 300, EnabledList{1, 1}, ExpectedList{100, 200}), - std::make_tuple(MaxList{300, 100}, 300, EnabledList{1, 1}, ExpectedList{200, 100}), - std::make_tuple(MaxList{100, 100}, 300, EnabledList{1, 1}, ExpectedList{100, 100}), - - std::make_tuple(MaxList{300, 300, 300}, 300, EnabledList{1, 0, 0}, ExpectedList{300, -1, -1}), - std::make_tuple(MaxList{300, 300, 300}, 300, EnabledList{0, 1, 0}, ExpectedList{ -1, 300, -1}), - std::make_tuple(MaxList{300, 300, 300}, 300, EnabledList{1, 1, 0}, ExpectedList{150, 150, -1}), - std::make_tuple(MaxList{100, 300, 300}, 300, EnabledList{1, 1, 0}, ExpectedList{100, 200, -1}), - std::make_tuple(MaxList{300, 100, 300}, 300, EnabledList{1, 1, 0}, ExpectedList{200, 100, -1}), - std::make_tuple(MaxList{100, 100, 300}, 300, EnabledList{1, 1, 0}, ExpectedList{100, 100, -1}), - - std::make_tuple(MaxList{300, 300, 300}, 300, EnabledList{0, 1, 0}, ExpectedList{-1, 300, -1}), - std::make_tuple(MaxList{300, 300, 300}, 300, EnabledList{0, 0, 1}, ExpectedList{-1, -1, 300}), - std::make_tuple(MaxList{300, 300, 300}, 300, EnabledList{0, 1, 1}, ExpectedList{-1, 150, 150}), - std::make_tuple(MaxList{300, 100, 300}, 300, EnabledList{0, 1, 1}, ExpectedList{-1, 100, 200}), - std::make_tuple(MaxList{300, 300, 100}, 300, EnabledList{0, 1, 1}, ExpectedList{-1, 200, 100}), - std::make_tuple(MaxList{300, 100, 100}, 300, EnabledList{0, 1, 1}, ExpectedList{-1, 100, 100}), - - std::make_tuple(MaxList{100, 100, 100}, 300, EnabledList{1, 1, 1}, ExpectedList{100, 100, 100}), - std::make_tuple(MaxList{100, 100, 100}, 600, EnabledList{1, 1, 1}, ExpectedList{100, 100, 100}), - std::make_tuple(MaxList{300, 300, 300}, 600, EnabledList{1, 1, 1}, ExpectedList{200, 200, 200}), - std::make_tuple(MaxList{100, 200, 300}, 600, EnabledList{1, 1, 1}, ExpectedList{100, 200, 300}), - std::make_tuple(MaxList{300, 200, 100}, 600, EnabledList{1, 1, 1}, ExpectedList{300, 200, 100}), - std::make_tuple(MaxList{100, 500, 500}, 800, EnabledList{1, 1, 1}, ExpectedList{100, 350, 350}))); + // bitrate_list remb streams enabled, expected remb + std::make_tuple(BitrateList{300}, 100, EnabledList{1}, ExpectedList{100}), + std::make_tuple(BitrateList{300}, 600, EnabledList{1}, ExpectedList{300}), + + std::make_tuple(BitrateList{300, 300}, 300, EnabledList{1, 0}, ExpectedList{300, -1}), + std::make_tuple(BitrateList{300, 300}, 300, EnabledList{0, 1}, ExpectedList{-1, 300}), + std::make_tuple(BitrateList{300, 300}, 300, EnabledList{1, 1}, ExpectedList{150, 150}), + std::make_tuple(BitrateList{100, 300}, 300, EnabledList{1, 1}, ExpectedList{100, 200}), + std::make_tuple(BitrateList{300, 100}, 300, EnabledList{1, 1}, ExpectedList{200, 100}), + std::make_tuple(BitrateList{100, 100}, 300, EnabledList{1, 1}, ExpectedList{100, 100}), + + std::make_tuple(BitrateList{300, 300, 300}, 300, EnabledList{1, 0, 0}, ExpectedList{300, -1, -1}), + std::make_tuple(BitrateList{300, 300, 300}, 300, EnabledList{0, 1, 0}, ExpectedList{ -1, 300, -1}), + std::make_tuple(BitrateList{300, 300, 300}, 300, EnabledList{1, 1, 0}, ExpectedList{150, 150, -1}), + std::make_tuple(BitrateList{100, 300, 300}, 300, EnabledList{1, 1, 0}, ExpectedList{100, 200, -1}), + std::make_tuple(BitrateList{300, 100, 300}, 300, EnabledList{1, 1, 0}, ExpectedList{200, 100, -1}), + std::make_tuple(BitrateList{100, 100, 300}, 300, EnabledList{1, 1, 0}, ExpectedList{100, 100, -1}), + + std::make_tuple(BitrateList{300, 300, 300}, 300, EnabledList{0, 1, 0}, ExpectedList{-1, 300, -1}), + std::make_tuple(BitrateList{300, 300, 300}, 300, EnabledList{0, 0, 1}, ExpectedList{-1, -1, 300}), + std::make_tuple(BitrateList{300, 300, 300}, 300, EnabledList{0, 1, 1}, ExpectedList{-1, 150, 150}), + std::make_tuple(BitrateList{300, 100, 300}, 300, EnabledList{0, 1, 1}, ExpectedList{-1, 100, 200}), + std::make_tuple(BitrateList{300, 300, 100}, 300, EnabledList{0, 1, 1}, ExpectedList{-1, 200, 100}), + std::make_tuple(BitrateList{300, 100, 100}, 300, EnabledList{0, 1, 1}, ExpectedList{-1, 100, 100}), + + std::make_tuple(BitrateList{100, 100, 100}, 300, EnabledList{1, 1, 1}, ExpectedList{100, 100, 100}), + std::make_tuple(BitrateList{100, 100, 100}, 600, EnabledList{1, 1, 1}, ExpectedList{100, 100, 100}), + std::make_tuple(BitrateList{300, 300, 300}, 600, EnabledList{1, 1, 1}, ExpectedList{200, 200, 200}), + std::make_tuple(BitrateList{100, 200, 300}, 600, EnabledList{1, 1, 1}, ExpectedList{100, 200, 300}), + std::make_tuple(BitrateList{300, 200, 100}, 600, EnabledList{1, 1, 1}, ExpectedList{300, 200, 100}), + std::make_tuple(BitrateList{100, 500, 500}, 800, EnabledList{1, 1, 1}, ExpectedList{100, 350, 350}))); diff --git a/erizo/src/test/bandwidth/TargetVideoBWDistributor.cpp b/erizo/src/test/bandwidth/TargetVideoBWDistributor.cpp index 07b45f96bf..44c5b277c4 100644 --- a/erizo/src/test/bandwidth/TargetVideoBWDistributor.cpp +++ b/erizo/src/test/bandwidth/TargetVideoBWDistributor.cpp @@ -17,6 +17,8 @@ using testing::Return; using testing::Eq; using testing::Args; using testing::AtLeast; +using testing::ResultOf; +using testing::Invoke; using erizo::DataPacket; using erizo::ExtMap; using erizo::IceConfig; @@ -69,6 +71,8 @@ class BasicTargetVideoBWDistributor { .WillRepeatedly(Return(config.max_quality_bitrate)); EXPECT_CALL(*media_stream, isSlideShowModeEnabled()).Times(AtLeast(0)).WillRepeatedly(Return(config.slideshow)); EXPECT_CALL(*media_stream, isSimulcast()).Times(AtLeast(0)).WillRepeatedly(Return(config.simulcast)); + EXPECT_CALL(*media_stream, getTargetVideoBitrate()).WillRepeatedly( + Invoke(media_stream.get(), &erizo::MockMediaStream::MediaStream_getTargetVideoBitrate)); index++; return media_stream; @@ -147,12 +151,16 @@ class TargetVideoBWDistributorTest : public BasicTargetVideoBWDistributor, } }; +uint32_t HasRembWithValue2(std::tuple> arg) { + return (reinterpret_cast(std::get<0>(arg)->data))->getREMBBitRate(); +} + TEST_P(TargetVideoBWDistributorTest, forwardRembToStreams_When_TheyExist) { uint32_t index = 0; for (int32_t expected_bitrate : expected_bitrates) { if (expected_bitrate > 0) { EXPECT_CALL(*(streams[index]), onTransportData(_, _)) - .With(Args<0>(erizo::RembHasBitrateValue(static_cast(expected_bitrate)))).Times(1); + .With(Args<0>(ResultOf(&HasRembWithValue2, Eq(static_cast(expected_bitrate))))).Times(1); } else { EXPECT_CALL(*streams[index], onTransportData(_, _)).Times(0); } diff --git a/erizo/src/test/rtp/RtpPaddingGeneratorHandlerTest.cpp b/erizo/src/test/rtp/RtpPaddingGeneratorHandlerTest.cpp index 029e225e1c..69bfcd8791 100644 --- a/erizo/src/test/rtp/RtpPaddingGeneratorHandlerTest.cpp +++ b/erizo/src/test/rtp/RtpPaddingGeneratorHandlerTest.cpp @@ -42,22 +42,11 @@ class RtpPaddingGeneratorHandlerTest : public erizo::HandlerTest { protected: void setHandler() { - EXPECT_CALL(*processor.get(), getMaxVideoBW()).Times(AtLeast(0)); - EXPECT_CALL(*quality_manager.get(), isPaddingEnabled()).Times(AtLeast(0)); clock = std::make_shared(); padding_generator_handler = std::make_shared(clock); pipeline->addBack(padding_generator_handler); - - stats->getNode()[erizo::kVideoSsrc].insertStat("bitrateCalculated", - MovingIntervalRateStat{std::chrono::milliseconds(100), 10, 1., clock}); - stats->getNode()["total"].insertStat("senderBitrateEstimation", - MovingIntervalRateStat{std::chrono::milliseconds(100), 10, 1., clock}); - - EXPECT_CALL(*quality_manager.get(), isPaddingEnabled()).WillRepeatedly(Return(true)); - - EXPECT_CALL(*processor.get(), getMaxVideoBW()).WillRepeatedly(Return(100000)); - stats->getNode()[erizo::kVideoSsrc]["bitrateCalculated"] += 40000 * 2 / 10; - stats->getNode()["total"]["senderBitrateEstimation"] += 60000 * 2 / 10; + EXPECT_CALL(*media_stream.get(), getTargetPaddingBitrate()).WillRepeatedly(Return(0)); + EXPECT_CALL(*media_stream.get(), isSlideShowModeEnabled()).WillRepeatedly(Return(false)); } std::shared_ptr padding_generator_handler; @@ -83,6 +72,9 @@ TEST_F(RtpPaddingGeneratorHandlerTest, basicBehaviourShouldWritePackets) { TEST_F(RtpPaddingGeneratorHandlerTest, shouldSendPaddingWhenEnabled) { EXPECT_CALL(*writer.get(), write(_, _)).Times(AtLeast(3)); + EXPECT_CALL(*media_stream.get(), getTargetPaddingBitrate()).WillRepeatedly(Return(uint64_t(10000))); + pipeline->notifyUpdate(); + pipeline->write(erizo::PacketTools::createVP8Packet(erizo::kArbitrarySeqNumber, true, true)); clock->advanceTime(std::chrono::milliseconds(200)); @@ -91,7 +83,7 @@ TEST_F(RtpPaddingGeneratorHandlerTest, shouldSendPaddingWhenEnabled) { TEST_F(RtpPaddingGeneratorHandlerTest, shouldNotSendPaddingWhenDisabled) { EXPECT_CALL(*writer.get(), write(_, _)).Times(2); - EXPECT_CALL(*quality_manager.get(), isPaddingEnabled()).WillRepeatedly(Return(false)); + EXPECT_CALL(*media_stream.get(), getTargetPaddingBitrate()).WillRepeatedly(Return(0)); pipeline->notifyUpdate(); pipeline->write(erizo::PacketTools::createVP8Packet(erizo::kArbitrarySeqNumber, true, true)); @@ -102,26 +94,11 @@ TEST_F(RtpPaddingGeneratorHandlerTest, shouldNotSendPaddingWhenDisabled) { TEST_F(RtpPaddingGeneratorHandlerTest, shouldNotSendPaddingAfterNotMarkers) { EXPECT_CALL(*writer.get(), write(_, _)).Times(2); + EXPECT_CALL(*media_stream.get(), getTargetPaddingBitrate()).WillRepeatedly(Return(uint64_t(10000))); + pipeline->notifyUpdate(); - pipeline->write(erizo::PacketTools::createVP8Packet(erizo::kArbitrarySeqNumber, true, true)); + pipeline->write(erizo::PacketTools::createVP8Packet(erizo::kArbitrarySeqNumber, true, false)); clock->advanceTime(std::chrono::milliseconds(200)); pipeline->write(erizo::PacketTools::createVP8Packet(erizo::kArbitrarySeqNumber + 1, true, false)); } - -TEST_F(RtpPaddingGeneratorHandlerTest, shouldNotSendPaddingIfBitrateIsHigherThanBitrateEstimation) { - const uint32_t kFractionLost = .1 * 255; - EXPECT_CALL(*writer.get(), write(_, _)).Times(2); - - stats->getNode()[erizo::kVideoSsrc]["bitrateCalculated"] += 70000; - stats->getNode()["total"]["senderBitrateEstimation"] += 60000; - - pipeline->read(erizo::PacketTools::createReceiverReport(erizo::kAudioSsrc, erizo::kAudioSsrc, - erizo::kArbitrarySeqNumber, VIDEO_PACKET, 0, kFractionLost)); - - pipeline->write(erizo::PacketTools::createVP8Packet(erizo::kArbitrarySeqNumber, true, true)); - - clock->advanceTime(std::chrono::milliseconds(1000)); - - pipeline->write(erizo::PacketTools::createVP8Packet(erizo::kArbitrarySeqNumber + 1, true, true)); -} diff --git a/erizo/src/test/rtp/RtpPaddingManagerHandlerTest.cpp b/erizo/src/test/rtp/RtpPaddingManagerHandlerTest.cpp new file mode 100644 index 0000000000..627250e478 --- /dev/null +++ b/erizo/src/test/rtp/RtpPaddingManagerHandlerTest.cpp @@ -0,0 +1,239 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../utils/Mocks.h" +#include "../utils/Tools.h" +#include "../utils/Matchers.h" + +using ::testing::_; +using ::testing::IsNull; +using ::testing::Args; +using ::testing::Return; +using ::testing::AtLeast; +using erizo::DataPacket; +using erizo::packetType; +using erizo::AUDIO_PACKET; +using erizo::VIDEO_PACKET; +using erizo::MovingIntervalRateStat; +using erizo::IceConfig; +using erizo::RtpMap; +using erizo::RtpPaddingManagerHandler; +using erizo::WebRtcConnection; +using erizo::Pipeline; +using erizo::InboundHandler; +using erizo::OutboundHandler; +using erizo::CumulativeStat; +using erizo::Worker; +using std::queue; +using erizo::MediaStream; + + + +class RtpPaddingManagerHandlerBaseTest : public erizo::BaseHandlerTest { + public: + RtpPaddingManagerHandlerBaseTest() {} + + protected: + void internalSetHandler() { + clock = std::make_shared(); + padding_calculator_handler = std::make_shared(clock); + pipeline->addBack(padding_calculator_handler); + } + + void whenSubscribersWithTargetBitrate(std::vector subscriber_bitrates) { + int i = 0; + std::for_each(subscriber_bitrates.begin(), subscriber_bitrates.end(), [this, &i](uint32_t bitrate) { + addMediaStreamToConnection("sub" + std::to_string(i), false, bitrate); + simulated_worker->executeTasks(); + i++; + }); + } + + void whenPublishers(uint num_publishers) { + for (uint i = 0; i < num_publishers; i++) { + addMediaStreamToConnection("pub" + std::to_string(i), true, 0); + simulated_worker->executeTasks(); + } + } + + void whenBandwidthEstimationIs(uint32_t bitrate) { + stats->getNode()["total"].insertStat("senderBitrateEstimation", CumulativeStat{bitrate}); + } + + void whenCurrentTotalVideoBitrateIs(uint32_t bitrate) { + stats->getNode()["total"].insertStat("videoBitrate", CumulativeStat{bitrate}); + } + + void internalTearDown() { + std::for_each(subscribers.begin(), subscribers.end(), + [this](const std::shared_ptr &stream) { + connection->removeMediaStream(stream->getId()); + }); + std::for_each(publishers.begin(), publishers.end(), + [this](const std::shared_ptr &stream) { + connection->removeMediaStream(stream->getId()); + }); + simulated_worker->executeTasks(); + } + + std::shared_ptr addMediaStreamToConnection(std::string id, + bool is_publisher, uint32_t bitrate) { + auto media_stream = + std::make_shared(simulated_worker, connection, id, id, rtp_maps, is_publisher); + std::shared_ptr stream_ptr = std::dynamic_pointer_cast(media_stream); + connection->addMediaStream(stream_ptr); + EXPECT_CALL(*media_stream.get(), getTargetVideoBitrate()).WillRepeatedly(Return(bitrate)); + if (is_publisher) { + publishers.push_back(media_stream); + } else { + subscribers.push_back(media_stream); + } + + return media_stream; + } + + void expectPaddingBitrate(uint64_t bitrate) { + std::for_each(subscribers.begin(), subscribers.end(), + [bitrate](const std::shared_ptr &stream) { + EXPECT_CALL(*stream.get(), setTargetPaddingBitrate(testing::Eq(bitrate))).Times(1); + }); + + std::for_each(publishers.begin(), publishers.end(), + [bitrate](const std::shared_ptr &stream) { + EXPECT_CALL(*stream.get(), setTargetPaddingBitrate(_)).Times(0); + }); + } + + std::vector> subscribers; + std::vector> publishers; + std::shared_ptr padding_calculator_handler; + std::shared_ptr clock; +}; + +class RtpPaddingManagerHandlerTest : public ::testing::Test, public RtpPaddingManagerHandlerBaseTest { + public: + RtpPaddingManagerHandlerTest() {} + + void setHandler() override { + internalSetHandler(); + } + + protected: + virtual void SetUp() { + internalSetUp(); + } + + void TearDown() override { + internalTearDown(); + } +}; + +TEST_F(RtpPaddingManagerHandlerTest, basicBehaviourShouldReadPackets) { + auto packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber, AUDIO_PACKET); + + EXPECT_CALL(*reader.get(), read(_, _)). + With(Args<1>(erizo::RtpHasSequenceNumber(erizo::kArbitrarySeqNumber))).Times(1); + pipeline->read(packet); +} + +TEST_F(RtpPaddingManagerHandlerTest, basicBehaviourShouldWritePackets) { + auto packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber, AUDIO_PACKET); + + EXPECT_CALL(*writer.get(), write(_, _)). + With(Args<1>(erizo::RtpHasSequenceNumber(erizo::kArbitrarySeqNumber))).Times(1); + pipeline->write(packet); +} + +TEST_F(RtpPaddingManagerHandlerTest, shouldDistributePaddingEvenlyAmongStreamsWithoutPublishers) { + auto packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber, AUDIO_PACKET); + + whenSubscribersWithTargetBitrate({200, 200, 200, 200, 200}); + whenPublishers(0); + whenBandwidthEstimationIs(600); + whenCurrentTotalVideoBitrateIs(100); + + expectPaddingBitrate(100); + + clock->advanceTime(std::chrono::milliseconds(200)); + pipeline->write(packet); +} + +typedef std::vector SubscriberBitratesList; + +class RtpPaddingManagerHandlerTestWithParam : public RtpPaddingManagerHandlerBaseTest, + public ::testing::TestWithParam> { + public: + RtpPaddingManagerHandlerTestWithParam() { + subscribers = std::tr1::get<0>(GetParam()); + bw_estimation = std::tr1::get<1>(GetParam()); + video_bitrate = std::tr1::get<2>(GetParam()); + expected_padding_bitrate = std::tr1::get<3>(GetParam()); + } + + protected: + void setHandler() override { + internalSetHandler(); + } + + virtual void SetUp() { + internalSetUp(); + } + + void TearDown() override { + internalTearDown(); + } + + SubscriberBitratesList subscribers; + uint32_t bw_estimation; + uint32_t video_bitrate; + uint64_t expected_padding_bitrate; +}; + +TEST_P(RtpPaddingManagerHandlerTestWithParam, shouldDistributePaddingWithPublishers) { + auto packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber, AUDIO_PACKET); + + whenSubscribersWithTargetBitrate(subscribers); + whenPublishers(10); + whenBandwidthEstimationIs(bw_estimation); + whenCurrentTotalVideoBitrateIs(video_bitrate); + + expectPaddingBitrate(expected_padding_bitrate); + + clock->advanceTime(std::chrono::milliseconds(200)); + pipeline->write(packet); +} + +TEST_P(RtpPaddingManagerHandlerTestWithParam, shouldDistributePaddingWithNoPublishers) { + auto packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber, AUDIO_PACKET); + + whenSubscribersWithTargetBitrate(subscribers); + whenPublishers(0); + whenBandwidthEstimationIs(bw_estimation); + whenCurrentTotalVideoBitrateIs(video_bitrate); + + expectPaddingBitrate(expected_padding_bitrate); + + clock->advanceTime(std::chrono::milliseconds(200)); + pipeline->write(packet); +} + +INSTANTIATE_TEST_CASE_P( + Padding_values, RtpPaddingManagerHandlerTestWithParam, testing::Values( + // targetBitrates, bwe, bitrate, expectedPaddingBitrate + std::make_tuple(SubscriberBitratesList{200, 200, 200, 200, 200}, 600, 100, 100), + std::make_tuple(SubscriberBitratesList{200, 200, 200, 200, 200}, 1500, 100, 0), + std::make_tuple(SubscriberBitratesList{200, 200, 200, 200, 200}, 99, 100, 0), + std::make_tuple(SubscriberBitratesList{200, 200, 200, 200, 200}, 600, 600, 0), + std::make_tuple(SubscriberBitratesList{200, 200, 200, 200, 200}, 0, 100, 0), + std::make_tuple(SubscriberBitratesList{200, 200, 200, 200, 200}, 1200, 0, 200))); diff --git a/erizo/src/test/utils/Mocks.h b/erizo/src/test/utils/Mocks.h index 269db9a690..bd1404ef1a 100644 --- a/erizo/src/test/utils/Mocks.h +++ b/erizo/src/test/utils/Mocks.h @@ -116,11 +116,18 @@ class MockMediaStream: public MediaStream { MOCK_METHOD0(isSimulcast, bool()); MOCK_METHOD2(onTransportData, void(std::shared_ptr, Transport*)); MOCK_METHOD1(deliverEventInternal, void(MediaEventPtr)); + MOCK_METHOD0(getTargetPaddingBitrate, uint64_t()); + MOCK_METHOD1(setTargetPaddingBitrate, void(uint64_t)); + MOCK_METHOD0(getTargetVideoBitrate, uint32_t()); int deliverEvent_(MediaEventPtr event) override { deliverEventInternal(event); return 0; } + + uint32_t MediaStream_getTargetVideoBitrate() { + return MediaStream::getTargetVideoBitrate(); + } }; class Reader : public InboundHandler { diff --git a/erizo/src/test/utils/Tools.h b/erizo/src/test/utils/Tools.h index 6ada488b42..57190f8edd 100644 --- a/erizo/src/test/utils/Tools.h +++ b/erizo/src/test/utils/Tools.h @@ -316,6 +316,7 @@ class BaseHandlerTest { std::shared_ptr connection_ptr = std::dynamic_pointer_cast(connection); std::shared_ptr stream_ptr = std::dynamic_pointer_cast(media_stream); + pipeline->addService(connection_ptr); pipeline->addService(stream_ptr); pipeline->addService(std::dynamic_pointer_cast(processor)); pipeline->addService(std::dynamic_pointer_cast(quality_manager)); diff --git a/erizo_controller/erizoAgent/log4cxx.properties b/erizo_controller/erizoAgent/log4cxx.properties index b38a15d785..b84b8bf17b 100644 --- a/erizo_controller/erizoAgent/log4cxx.properties +++ b/erizo_controller/erizoAgent/log4cxx.properties @@ -70,4 +70,5 @@ log4j.logger.rtp.StatsCalculator=WARN log4j.logger.rtp.LayerDetectorHandler=WARN log4j.logger.rtp.PliPacerHandler=WARN log4j.logger.rtp.RtpPaddingGeneratorHandler=WARN +log4j.logger.rtp.RtpPaddingManagerHandler=WARN log4j.logger.rtp.PacketCodecParser=WARN diff --git a/extras/basic_example/public/script.js b/extras/basic_example/public/script.js index c567e62479..c71ede216b 100644 --- a/extras/basic_example/public/script.js +++ b/extras/basic_example/public/script.js @@ -101,7 +101,7 @@ const startBasicExample = () => { req.send(JSON.stringify(roomData)); }; - const roomData = { username: 'user', + const roomData = { username: `user ${parseInt(Math.random() * 100, 10)}`, role: 'presenter', room: roomName, type: roomType,