From 460f384d668e26cae6327b0e96ad4654b153c337 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Cervi=C3=B1o?= Date: Tue, 18 Sep 2018 11:39:25 +0200 Subject: [PATCH] Add a new algorithm to allocate better bandwidth in Single PC (#1296) --- erizo/src/erizo/MediaStream.cpp | 14 +- erizo/src/erizo/MediaStream.h | 12 +- erizo/src/erizo/WebRtcConnection.cpp | 21 +- erizo/src/erizo/WebRtcConnection.h | 3 + .../BandwidthDistributionAlgorithm.h | 22 ++ .../erizo/bandwidth/MaxVideoBWDistributor.cpp | 35 ++ .../erizo/bandwidth/MaxVideoBWDistributor.h | 18 + .../bandwidth/TargetVideoBWDistributor.cpp | 55 +++ .../bandwidth/TargetVideoBWDistributor.h | 22 ++ erizo/src/erizo/rtp/QualityManager.cpp | 3 + .../test/bandwidth/MaxVideoBWDistributor.cpp | 174 +++++++++ .../bandwidth/TargetVideoBWDistributor.cpp | 365 ++++++++++++++++++ erizo/src/test/utils/Mocks.h | 4 + 13 files changed, 729 insertions(+), 19 deletions(-) create mode 100644 erizo/src/erizo/bandwidth/BandwidthDistributionAlgorithm.h create mode 100644 erizo/src/erizo/bandwidth/MaxVideoBWDistributor.cpp create mode 100644 erizo/src/erizo/bandwidth/MaxVideoBWDistributor.h create mode 100644 erizo/src/erizo/bandwidth/TargetVideoBWDistributor.cpp create mode 100644 erizo/src/erizo/bandwidth/TargetVideoBWDistributor.h create mode 100644 erizo/src/test/bandwidth/MaxVideoBWDistributor.cpp create mode 100644 erizo/src/test/bandwidth/TargetVideoBWDistributor.cpp diff --git a/erizo/src/erizo/MediaStream.cpp b/erizo/src/erizo/MediaStream.cpp index f710445450..20f5f4d28a 100644 --- a/erizo/src/erizo/MediaStream.cpp +++ b/erizo/src/erizo/MediaStream.cpp @@ -59,7 +59,9 @@ MediaStream::MediaStream(std::shared_ptr worker, worker_{std::move(worker)}, audio_muted_{false}, video_muted_{false}, pipeline_initialized_{false}, - is_publisher_{is_publisher} { + is_publisher_{is_publisher}, + simulcast_{false}, + bitrate_from_max_quality_layer_{0} { setVideoSinkSSRC(kDefaultVideoSinkSSRC); setAudioSinkSSRC(kDefaultAudioSinkSSRC); ELOG_INFO("%s message: constructor, id: %s", @@ -95,6 +97,16 @@ uint32_t MediaStream::getMaxVideoBW() { return bitrate; } +uint32_t MediaStream::getBitrateSent() { + uint32_t bitrate = 0; + std::string video_ssrc = std::to_string(is_publisher_ ? getVideoSourceSSRC() : getVideoSinkSSRC()); + if (stats_->getNode().hasChild(video_ssrc) && + stats_->getNode()[video_ssrc].hasChild("bitrateCalculated")) { + bitrate = stats_->getNode()[video_ssrc]["bitrateCalculated"].value(); + } + return bitrate; +} + void MediaStream::setMaxVideoBW(uint32_t max_video_bw) { asyncTask([max_video_bw] (std::shared_ptr stream) { if (stream->rtcp_processor_) { diff --git a/erizo/src/erizo/MediaStream.h b/erizo/src/erizo/MediaStream.h index 31e8e45d99..95d0b69a30 100644 --- a/erizo/src/erizo/MediaStream.h +++ b/erizo/src/erizo/MediaStream.h @@ -4,6 +4,7 @@ #include +#include #include #include #include @@ -69,6 +70,8 @@ class MediaStream: public MediaSink, public MediaSource, public FeedbackSink, bool init(); void close() override; virtual uint32_t getMaxVideoBW(); + virtual uint32_t getBitrateFromMaxQualityLayer() { return bitrate_from_max_quality_layer_; } + virtual uint32_t getBitrateSent(); void setMaxVideoBW(uint32_t max_video_bw); void syncClose(); bool setRemoteSdp(std::shared_ptr sdp); @@ -134,7 +137,10 @@ class MediaStream: public MediaSink, public MediaSource, public FeedbackSink, SdpInfo* getRemoteSdpInfo() { return remote_sdp_.get(); } - bool isSlideShowModeEnabled() { return slide_show_mode_; } + virtual bool isSlideShowModeEnabled() { return slide_show_mode_; } + + virtual bool isSimulcast() { return simulcast_; } + void setSimulcast(bool simulcast) { simulcast_ = simulcast; } RtpExtensionProcessor& getRtpExtensionProcessor() { return connection_->getRtpExtensionProcessor(); } std::shared_ptr getWorker() { return worker_; } @@ -150,6 +156,7 @@ class MediaStream: public MediaSink, public MediaSource, public FeedbackSink, bool isRunning() { return pipeline_initialized_ && sending_; } Pipeline::Ptr getPipeline() { return pipeline_; } bool isPublisher() { return is_publisher_; } + void setBitrateFromMaxQualityLayer(uint64_t bitrate) { bitrate_from_max_quality_layer_ = bitrate; } inline std::string toLog() { return "id: " + stream_id_ + ", role:" + (is_publisher_ ? "publisher" : "subscriber") + ", " + printLogContext(); @@ -202,6 +209,9 @@ class MediaStream: public MediaSink, public MediaSource, public FeedbackSink, bool pipeline_initialized_; bool is_publisher_; + + std::atomic_bool simulcast_; + std::atomic bitrate_from_max_quality_layer_; protected: std::shared_ptr remote_sdp_; std::shared_ptr local_sdp_; diff --git a/erizo/src/erizo/WebRtcConnection.cpp b/erizo/src/erizo/WebRtcConnection.cpp index e4c7f9904d..1b998a1d7e 100644 --- a/erizo/src/erizo/WebRtcConnection.cpp +++ b/erizo/src/erizo/WebRtcConnection.cpp @@ -13,6 +13,8 @@ #include "MediaStream.h" #include "DtlsTransport.h" #include "SdpInfo.h" +#include "bandwidth/MaxVideoBWDistributor.h" +#include "bandwidth/TargetVideoBWDistributor.h" #include "rtp/RtpHeaders.h" #include "rtp/RtpVP8Parser.h" #include "rtp/RtcpAggregator.h" @@ -52,6 +54,7 @@ WebRtcConnection::WebRtcConnection(std::shared_ptr worker, std::shared_p ELOG_INFO("%s message: constructor, stunserver: %s, stunPort: %d, minPort: %d, maxPort: %d", toLog(), ice_config.stun_server.c_str(), ice_config.stun_port, ice_config.min_port, ice_config.max_port); stats_ = std::make_shared(); + distributor_ = std::unique_ptr(new TargetVideoBWDistributor()); global_state_ = CONN_INITIAL; trickle_enabled_ = ice_config_.should_trickle; @@ -498,23 +501,7 @@ void WebRtcConnection::onREMBFromTransport(RtcpHeader *chead, Transport *transpo }); } - std::sort(streams.begin(), streams.end(), - [](const std::shared_ptr &i, const std::shared_ptr &j) { - return i->getMaxVideoBW() < j->getMaxVideoBW(); - }); - - uint8_t remaining_streams = streams.size(); - uint32_t remaining_bitrate = chead->getREMBBitRate(); - std::for_each(streams.begin(), streams.end(), - [&remaining_bitrate, &remaining_streams, transport, chead](const std::shared_ptr &stream) { - uint32_t max_bitrate = stream->getMaxVideoBW(); - uint32_t remaining_avg_bitrate = remaining_bitrate / remaining_streams; - uint32_t bitrate = std::min(max_bitrate, remaining_avg_bitrate); - auto generated_remb = RtpUtils::createREMB(chead->getSSRC(), {stream->getVideoSinkSSRC()}, bitrate); - stream->onTransportData(generated_remb, transport); - remaining_bitrate -= bitrate; - remaining_streams--; - }); + distributor_->distribute(chead->getREMBBitRate(), chead->getSSRC(), streams, transport); } void WebRtcConnection::onRtcpFromTransport(std::shared_ptr packet, Transport *transport) { diff --git a/erizo/src/erizo/WebRtcConnection.h b/erizo/src/erizo/WebRtcConnection.h index 4afeadff2b..4da0552a74 100644 --- a/erizo/src/erizo/WebRtcConnection.h +++ b/erizo/src/erizo/WebRtcConnection.h @@ -12,6 +12,7 @@ #include "./MediaDefinitions.h" #include "./Transport.h" #include "./Stats.h" +#include "bandwidth/BandwidthDistributionAlgorithm.h" #include "pipeline/Pipeline.h" #include "thread/Worker.h" #include "thread/IOWorker.h" @@ -192,6 +193,8 @@ class WebRtcConnection: public TransportListener, public LogContext, bool audio_muted_; bool video_muted_; bool first_remote_sdp_processed_; + + std::unique_ptr distributor_; }; } // namespace erizo diff --git a/erizo/src/erizo/bandwidth/BandwidthDistributionAlgorithm.h b/erizo/src/erizo/bandwidth/BandwidthDistributionAlgorithm.h new file mode 100644 index 0000000000..8bb4347724 --- /dev/null +++ b/erizo/src/erizo/bandwidth/BandwidthDistributionAlgorithm.h @@ -0,0 +1,22 @@ +#ifndef ERIZO_SRC_ERIZO_BANDWIDTH_BANDWIDTHDISTRIBUTIONALGORITHM_H_ +#define ERIZO_SRC_ERIZO_BANDWIDTH_BANDWIDTHDISTRIBUTIONALGORITHM_H_ + +#include +#include + +namespace erizo { + +class MediaStream; +class Transport; + +class BandwidthDistributionAlgorithm { + public: + BandwidthDistributionAlgorithm() {} + virtual ~BandwidthDistributionAlgorithm() {} + virtual void distribute(uint32_t remb, uint32_t ssrc, std::vector> streams, + Transport *transport) = 0; +}; + +} // namespace erizo + +#endif // ERIZO_SRC_ERIZO_BANDWIDTH_BANDWIDTHDISTRIBUTIONALGORITHM_H_ diff --git a/erizo/src/erizo/bandwidth/MaxVideoBWDistributor.cpp b/erizo/src/erizo/bandwidth/MaxVideoBWDistributor.cpp new file mode 100644 index 0000000000..76bf1e05e0 --- /dev/null +++ b/erizo/src/erizo/bandwidth/MaxVideoBWDistributor.cpp @@ -0,0 +1,35 @@ +/* + * MaxVideoBWDistributor.cpp + */ + +#include + +#include "MaxVideoBWDistributor.h" +#include "MediaStream.h" +#include "Transport.h" +#include "rtp/RtpUtils.h" + +namespace erizo { + +void MaxVideoBWDistributor::distribute(uint32_t remb, uint32_t ssrc, + std::vector> streams, Transport *transport) { + std::sort(streams.begin(), streams.end(), + [](const std::shared_ptr &i, const std::shared_ptr &j) { + return i->getMaxVideoBW() < j->getMaxVideoBW(); + }); + + uint8_t remaining_streams = streams.size(); + uint32_t remaining_bitrate = remb; + std::for_each(streams.begin(), streams.end(), + [&remaining_bitrate, &remaining_streams, transport, ssrc](const std::shared_ptr &stream) { + uint32_t max_bitrate = stream->getMaxVideoBW(); + uint32_t remaining_avg_bitrate = remaining_bitrate / remaining_streams; + uint32_t bitrate = std::min(max_bitrate, remaining_avg_bitrate); + auto generated_remb = RtpUtils::createREMB(ssrc, {stream->getVideoSinkSSRC()}, bitrate); + stream->onTransportData(generated_remb, transport); + remaining_bitrate -= bitrate; + remaining_streams--; + }); +} + +} // namespace erizo diff --git a/erizo/src/erizo/bandwidth/MaxVideoBWDistributor.h b/erizo/src/erizo/bandwidth/MaxVideoBWDistributor.h new file mode 100644 index 0000000000..ad4b50433e --- /dev/null +++ b/erizo/src/erizo/bandwidth/MaxVideoBWDistributor.h @@ -0,0 +1,18 @@ +#ifndef ERIZO_SRC_ERIZO_BANDWIDTH_MAXVIDEOBWDISTRIBUTOR_H_ +#define ERIZO_SRC_ERIZO_BANDWIDTH_MAXVIDEOBWDISTRIBUTOR_H_ + +#include "bandwidth/BandwidthDistributionAlgorithm.h" + +namespace erizo { + +class MaxVideoBWDistributor : public BandwidthDistributionAlgorithm { + public: + MaxVideoBWDistributor() {} + virtual ~MaxVideoBWDistributor() {} + void distribute(uint32_t remb, uint32_t ssrc, std::vector> streams, + Transport *transport) override; +}; + +} // namespace erizo + +#endif // ERIZO_SRC_ERIZO_BANDWIDTH_MAXVIDEOBWDISTRIBUTOR_H_ diff --git a/erizo/src/erizo/bandwidth/TargetVideoBWDistributor.cpp b/erizo/src/erizo/bandwidth/TargetVideoBWDistributor.cpp new file mode 100644 index 0000000000..03eb6b9067 --- /dev/null +++ b/erizo/src/erizo/bandwidth/TargetVideoBWDistributor.cpp @@ -0,0 +1,55 @@ +/* + * TargetVideoBWDistributor.cpp + */ + +#include + +#include "TargetVideoBWDistributor.h" +#include "MediaStream.h" +#include "Transport.h" +#include "rtp/RtpUtils.h" + +namespace erizo { + +void TargetVideoBWDistributor::distribute(uint32_t remb, uint32_t ssrc, + std::vector> streams, Transport *transport) { + std::sort(streams.begin(), streams.end(), + [this](const std::shared_ptr &i, const std::shared_ptr &j) { + return getTargetVideoBW(i) < getTargetVideoBW(j); + }); + uint8_t remaining_streams = streams.size(); + uint32_t remaining_bitrate = remb; + std::for_each(streams.begin(), streams.end(), + [&remaining_bitrate, &remaining_streams, transport, ssrc, this](const std::shared_ptr &stream) { + uint32_t max_bitrate = stream->getMaxVideoBW(); + + uint32_t target_bitrate = getTargetVideoBW(stream); + + uint32_t remaining_avg_bitrate = remaining_bitrate / remaining_streams; + uint32_t bitrate = std::min(target_bitrate, remaining_avg_bitrate); + uint32_t remb = std::min(max_bitrate, remaining_avg_bitrate); + auto generated_remb = RtpUtils::createREMB(ssrc, {stream->getVideoSinkSSRC()}, remb); + stream->onTransportData(generated_remb, transport); + + remaining_bitrate -= bitrate; + remaining_streams--; + }); +} + +uint32_t TargetVideoBWDistributor::getTargetVideoBW(const std::shared_ptr &stream) { + bool slide_show_mode = stream->isSlideShowModeEnabled(); + bool is_simulcast = stream->isSimulcast(); + uint32_t bitrate_sent = stream->getBitrateSent(); + uint32_t max_bitrate = stream->getMaxVideoBW(); + + uint32_t target_bitrate = max_bitrate; + if (is_simulcast) { + target_bitrate = std::min(stream->getBitrateFromMaxQualityLayer(), 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 new file mode 100644 index 0000000000..517a40d9cc --- /dev/null +++ b/erizo/src/erizo/bandwidth/TargetVideoBWDistributor.h @@ -0,0 +1,22 @@ +#ifndef ERIZO_SRC_ERIZO_BANDWIDTH_TARGETVIDEOBWDISTRIBUTOR_H_ +#define ERIZO_SRC_ERIZO_BANDWIDTH_TARGETVIDEOBWDISTRIBUTOR_H_ + +#include "bandwidth/BandwidthDistributionAlgorithm.h" + +namespace erizo { + +class MediaStream; + +class TargetVideoBWDistributor : public BandwidthDistributionAlgorithm { + public: + TargetVideoBWDistributor() {} + virtual ~TargetVideoBWDistributor() {} + void distribute(uint32_t remb, uint32_t ssrc, std::vector> streams, + Transport *transport) override; + private: + uint32_t getTargetVideoBW(const std::shared_ptr &stream); +}; + +} // namespace erizo + +#endif // ERIZO_SRC_ERIZO_BANDWIDTH_TARGETVIDEOBWDISTRIBUTOR_H_ diff --git a/erizo/src/erizo/rtp/QualityManager.cpp b/erizo/src/erizo/rtp/QualityManager.cpp index 5962563478..4b3d3486e2 100644 --- a/erizo/src/erizo/rtp/QualityManager.cpp +++ b/erizo/src/erizo/rtp/QualityManager.cpp @@ -129,6 +129,7 @@ void QualityManager::selectLayer(bool try_higher_layers) { if (!initialized_ || !stats_->getNode().hasChild("qualityLayers")) { return; } + stream_->setSimulcast(true); last_quality_check_ = clock_->now(); int min_valid_spatial_layer = std::min(min_desired_spatial_layer_, max_active_spatial_layer_); int aux_temporal_layer = 0; @@ -223,6 +224,8 @@ void QualityManager::calculateMaxActiveLayer() { max_active_spatial_layer_ = max_active_spatial_layer; max_active_temporal_layer_ = max_active_temporal_layer; + + stream_->setBitrateFromMaxQualityLayer(getInstantLayerBitrate(max_active_spatial_layer, max_active_temporal_layer)); } uint64_t QualityManager::getInstantLayerBitrate(int spatial_layer, int temporal_layer) { diff --git a/erizo/src/test/bandwidth/MaxVideoBWDistributor.cpp b/erizo/src/test/bandwidth/MaxVideoBWDistributor.cpp new file mode 100644 index 0000000000..e17b64688a --- /dev/null +++ b/erizo/src/test/bandwidth/MaxVideoBWDistributor.cpp @@ -0,0 +1,174 @@ +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "../utils/Mocks.h" +#include "../utils/Matchers.h" + +using testing::_; +using testing::Return; +using testing::Eq; +using testing::Args; +using testing::AtLeast; +using erizo::DataPacket; +using erizo::ExtMap; +using erizo::IceConfig; +using erizo::RtpMap; +using erizo::RtpUtils; +using erizo::MediaStream; +using erizo::MaxVideoBWDistributor; + +typedef std::vector MaxList; +typedef std::vector EnabledList; +typedef std::vector ExpectedList; + +class MaxVideoBWDistributorTest : + public ::testing::TestWithParam> { + protected: + virtual void SetUp() { + index = 0; + max_video_bw_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()); + + distributor = std::make_shared(); + + setUpStreams(); + } + + void setUpStreams() { + for (uint32_t max_video_bw : max_video_bw_list) { + streams.push_back(addMediaStream(false, max_video_bw)); + } + } + + std::shared_ptr addMediaStream(bool is_publisher, uint32_t max_video_bw) { + std::string id = std::to_string(index); + std::string label = std::to_string(index); + uint32_t video_sink_ssrc = getSsrcFromIndex(index); + uint32_t audio_sink_ssrc = getSsrcFromIndex(index) + 1; + uint32_t video_source_ssrc = getSsrcFromIndex(index) + 2; + uint32_t audio_source_ssrc = getSsrcFromIndex(index) + 3; + auto media_stream = std::make_shared(nullptr, nullptr, id, label, + rtp_maps, is_publisher); + media_stream->setVideoSinkSSRC(video_sink_ssrc); + media_stream->setAudioSinkSSRC(audio_sink_ssrc); + media_stream->setVideoSourceSSRC(video_source_ssrc); + media_stream->setAudioSourceSSRC(audio_source_ssrc); + EXPECT_CALL(*media_stream, getMaxVideoBW()).Times(AtLeast(0)).WillRepeatedly(Return(max_video_bw)); + index++; + return media_stream; + } + + void onRembReceived(uint32_t bitrate, std::vector ids) { + std::transform(ids.begin(), ids.end(), ids.begin(), [](uint32_t id) { + return id * 1000; + }); + std::vector> enabled_streams; + for (uint8_t index = 0; index < ids.size(); index++) { + uint32_t ssrc_feed = ids[index]; + std::for_each(streams.begin(), streams.end(), [ssrc_feed, &enabled_streams] + (const std::shared_ptr &stream) { + if (stream->isSinkSSRC(ssrc_feed)) { + enabled_streams.push_back(stream); + } + }); + } + distributor->distribute(bitrate, ids[0], enabled_streams, nullptr); + } + + void onRembReceived() { + uint32_t index = 0; + std::vector ids; + for (bool enabled : add_to_remb_list) { + if (enabled) { + ids.push_back(index); + } + index++; + } + onRembReceived(bitrate_value, ids); + } + + uint32_t getIndexFromSsrc(uint32_t ssrc) { + return ssrc / 1000; + } + + uint32_t getSsrcFromIndex(uint32_t index) { + return index * 1000; + } + + virtual void TearDown() { + streams.clear(); + } + + std::vector> streams; + MaxList max_video_bw_list; + uint32_t bitrate_value; + EnabledList add_to_remb_list; + ExpectedList expected_bitrates; + IceConfig ice_config; + std::vector rtp_maps; + std::vector ext_maps; + uint32_t index; + std::shared_ptr distributor; + std::queue> packet_queue; +}; + +TEST_P(MaxVideoBWDistributorTest, 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); + } else { + EXPECT_CALL(*streams[index], onTransportData(_, _)).Times(0); + } + index++; + } + + onRembReceived(); +} + +INSTANTIATE_TEST_CASE_P( + REMB_values, MaxVideoBWDistributorTest, 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}))); diff --git a/erizo/src/test/bandwidth/TargetVideoBWDistributor.cpp b/erizo/src/test/bandwidth/TargetVideoBWDistributor.cpp new file mode 100644 index 0000000000..5fa7ad3c57 --- /dev/null +++ b/erizo/src/test/bandwidth/TargetVideoBWDistributor.cpp @@ -0,0 +1,365 @@ +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "../utils/Mocks.h" +#include "../utils/Matchers.h" + +using testing::_; +using testing::Return; +using testing::Eq; +using testing::Args; +using testing::AtLeast; +using erizo::DataPacket; +using erizo::ExtMap; +using erizo::IceConfig; +using erizo::RtpMap; +using erizo::RtpUtils; +using erizo::MediaStream; +using erizo::TargetVideoBWDistributor; + +using std::make_tuple; + +struct StreamConfig { + uint32_t max_video_bw; + uint32_t bitrate_sent; + uint32_t max_quality_bitrate; + bool slideshow; + bool simulcast; +}; + +typedef std::vector StreamConfigList; +typedef std::vector MinList; +typedef std::vector TargetList; +typedef std::vector EnabledList; +typedef std::vector ExpectedList; + +class BasicTargetVideoBWDistributor { + protected: + void setUpStreams() { + for (StreamConfig stream_config : stream_config_list) { + streams.push_back(addMediaStream(false, stream_config)); + } + } + + std::shared_ptr addMediaStream(bool is_publisher, StreamConfig config) { + std::string id = std::to_string(index); + std::string label = std::to_string(index); + uint32_t video_sink_ssrc = getSsrcFromIndex(index); + uint32_t audio_sink_ssrc = getSsrcFromIndex(index) + 1; + uint32_t video_source_ssrc = getSsrcFromIndex(index) + 2; + uint32_t audio_source_ssrc = getSsrcFromIndex(index) + 3; + auto media_stream = std::make_shared(nullptr, nullptr, id, label, + rtp_maps, is_publisher); + media_stream->setVideoSinkSSRC(video_sink_ssrc); + media_stream->setAudioSinkSSRC(audio_sink_ssrc); + media_stream->setVideoSourceSSRC(video_source_ssrc); + media_stream->setAudioSourceSSRC(audio_source_ssrc); + + EXPECT_CALL(*media_stream, getMaxVideoBW()).Times(AtLeast(0)).WillRepeatedly(Return(config.max_video_bw)); + EXPECT_CALL(*media_stream, getBitrateSent()).Times(AtLeast(0)).WillRepeatedly(Return(config.bitrate_sent)); + EXPECT_CALL(*media_stream, getBitrateFromMaxQualityLayer()).Times(AtLeast(0)) + .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)); + + index++; + return media_stream; + } + + void onRembReceived(uint32_t bitrate, std::vector ids) { + std::transform(ids.begin(), ids.end(), ids.begin(), [](uint32_t id) { + return id * 1000; + }); + std::vector> enabled_streams; + for (uint8_t index = 0; index < ids.size(); index++) { + uint32_t ssrc_feed = ids[index]; + std::for_each(streams.begin(), streams.end(), [ssrc_feed, &enabled_streams] + (const std::shared_ptr &stream) { + if (stream->isSinkSSRC(ssrc_feed)) { + enabled_streams.push_back(stream); + } + }); + } + distributor->distribute(bitrate, ids[0], enabled_streams, nullptr); + } + + void onRembReceived() { + uint32_t index = 0; + std::vector ids; + for (bool enabled : add_to_remb_list) { + if (enabled) { + ids.push_back(index); + } + index++; + } + onRembReceived(bitrate_value, ids); + } + + uint32_t getIndexFromSsrc(uint32_t ssrc) { + return ssrc / 1000; + } + + uint32_t getSsrcFromIndex(uint32_t index) { + return index * 1000; + } + + std::vector> streams; + StreamConfigList stream_config_list; + uint32_t bitrate_value; + EnabledList add_to_remb_list; + ExpectedList expected_bitrates; + IceConfig ice_config; + std::vector rtp_maps; + std::vector ext_maps; + uint32_t index; + std::shared_ptr distributor; + std::queue> packet_queue; +}; + +class TargetVideoBWDistributorTest : public BasicTargetVideoBWDistributor, + public ::testing::TestWithParam> { + protected: + virtual void SetUp() { + index = 0; + stream_config_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()); + + distributor = std::make_shared(); + + setUpStreams(); + } + + virtual void TearDown() { + streams.clear(); + } +}; + +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); + } else { + EXPECT_CALL(*streams[index], onTransportData(_, _)).Times(0); + } + index++; + } + + onRembReceived(); +} + +INSTANTIATE_TEST_CASE_P( + REMB_values, TargetVideoBWDistributorTest, testing::Values( + // max sent qua slides sim remb enabled exp + + // Test common cases with 1 stream (maxBW > remb) + make_tuple(StreamConfigList{{300, 300, 300, false, false}}, 100, EnabledList{1}, ExpectedList{100}), + make_tuple(StreamConfigList{{300, 40, 300, true, false}}, 100, EnabledList{1}, ExpectedList{100}), + make_tuple(StreamConfigList{{300, 40, 300, true, true}}, 100, EnabledList{1}, ExpectedList{100}), + + // Test common cases with 1 stream (maxBW < remb) + make_tuple(StreamConfigList{{300, 300, 300, false, false}}, 400, EnabledList{1}, ExpectedList{300}), + make_tuple(StreamConfigList{{300, 40, 300, true, false}}, 400, EnabledList{1}, ExpectedList{300}), + make_tuple(StreamConfigList{{300, 40, 300, true, true}}, 400, EnabledList{1}, ExpectedList{300}), + + // Check other simulcast boundaries + make_tuple(StreamConfigList{{300, 40, 400, true, true}}, 400, EnabledList{1}, ExpectedList{300}), + make_tuple(StreamConfigList{{400, 40, 300, true, true}}, 400, EnabledList{1}, ExpectedList{400}), + make_tuple(StreamConfigList{{300, 200, 400, false, true}}, 400, EnabledList{1}, ExpectedList{300}), + make_tuple(StreamConfigList{{400, 200, 300, false, true}}, 400, EnabledList{1}, ExpectedList{400}), + + // Check other slideshow boundaries (remb < bitrate) + make_tuple(StreamConfigList{{300, 40, 300, true, false}}, 10, EnabledList{1}, ExpectedList{10}), + make_tuple(StreamConfigList{{300, 40, 300, true, true}}, 10, EnabledList{1}, ExpectedList{10}), + + make_tuple(StreamConfigList{{300, 300, 300, false, false}, + {300, 300, 300, false, false}}, + 200, EnabledList{1, 1}, ExpectedList{100, 100}), + make_tuple(StreamConfigList{{300, 300, 300, false, false}, + {300, 300, 300, false, false}}, + 200, EnabledList{1, 0}, ExpectedList{200, -1}), + make_tuple(StreamConfigList{{300, 300, 300, false, false}, + {300, 300, 300, false, false}}, + 200, EnabledList{0, 1}, ExpectedList{ -1, 200}), + + make_tuple(StreamConfigList{{300, 40, 300, true, false}, + {300, 300, 300, false, false}}, + 200, EnabledList{1, 1}, ExpectedList{100, 160}), + make_tuple(StreamConfigList{{300, 300, 300, false, false}, + {300, 40, 300, true, false}}, + 200, EnabledList{1, 1}, ExpectedList{160, 100}), + make_tuple(StreamConfigList{{300, 300, 300, false, false}, + {300, 300, 300, false, false}}, + 300, EnabledList{1, 0}, ExpectedList{300, -1}), + make_tuple(StreamConfigList{{300, 300, 300, false, false}, + {300, 300, 300, false, false}}, + 300, EnabledList{1, 1}, ExpectedList{150, 150}), + make_tuple(StreamConfigList{{100, 100, 100, false, false}, + {300, 300, 300, false, false}}, + 300, EnabledList{1, 1}, ExpectedList{100, 200}), + make_tuple(StreamConfigList{{300, 300, 300, false, false}, + {100, 100, 100, false, false}}, + 300, EnabledList{1, 1}, ExpectedList{200, 100}), + make_tuple(StreamConfigList{{100, 100, 100, false, false}, + {100, 100, 100, false, false}}, + 300, EnabledList{1, 1}, ExpectedList{100, 100}), + + make_tuple(StreamConfigList{{900, 100, 300, false, true}, + {900, 100, 300, false, true}}, + 300, EnabledList{1, 0}, ExpectedList{300, -1}), + make_tuple(StreamConfigList{{900, 100, 300, false, true}, + {900, 100, 300, false, true}}, + 300, EnabledList{1, 1}, ExpectedList{150, 150}), + make_tuple(StreamConfigList{{900, 100, 100, false, true}, + {900, 100, 300, false, true}}, + 300, EnabledList{1, 1}, ExpectedList{150, 200}), + make_tuple(StreamConfigList{{900, 100, 300, false, true}, + {900, 100, 100, false, true}}, + 300, EnabledList{1, 1}, ExpectedList{200, 150}), + make_tuple(StreamConfigList{{900, 100, 100, false, true}, + {900, 100, 100, false, true}}, + 300, EnabledList{1, 1}, ExpectedList{150, 200}), + make_tuple(StreamConfigList{{200, 100, 900, false, true}, + {200, 100, 900, false, true}}, + 300, EnabledList{1, 1}, ExpectedList{150, 150}), + make_tuple(StreamConfigList{{200, 100, 900, false, true}, + {200, 100, 900, false, true}}, + 300, EnabledList{1, 0}, ExpectedList{200, -1}), + make_tuple(StreamConfigList{{200, 100, 900, false, true}, + {200, 100, 900, false, true}}, + 300, EnabledList{0, 1}, ExpectedList{ -1, 200}), + + make_tuple(StreamConfigList{{300, 300, 300, false, true}, + {300, 300, 300, false, true}, + {300, 300, 300, false, true}}, + 300, EnabledList{1, 1, 1}, ExpectedList{100, 100, 100}), + make_tuple(StreamConfigList{{300, 300, 300, false, true}, + {300, 300, 300, false, true}, + {300, 300, 300, false, true}}, + 300, EnabledList{1, 0, 0}, ExpectedList{300, -1, -1}), + make_tuple(StreamConfigList{{300, 300, 300, false, true}, + {300, 300, 300, false, true}, + {300, 300, 300, false, true}}, + 300, EnabledList{0, 1, 0}, ExpectedList{ -1, 300, -1}), + make_tuple(StreamConfigList{{300, 300, 300, false, true}, + {300, 300, 300, false, true}, + {300, 300, 300, false, true}}, + 300, EnabledList{1, 1, 0}, ExpectedList{150, 150, -1}), + make_tuple(StreamConfigList{{100, 100, 100, false, true}, + {300, 300, 300, false, true}, + {300, 300, 300, false, true}}, + 300, EnabledList{1, 1, 0}, ExpectedList{100, 200, -1}), + make_tuple(StreamConfigList{{300, 300, 300, false, true}, + {100, 100, 100, false, true}, + {300, 300, 300, false, true}}, + 300, EnabledList{1, 1, 0}, ExpectedList{200, 100, -1}), + make_tuple(StreamConfigList{{100, 100, 100, false, true}, + {100, 100, 100, false, true}, + {300, 300, 300, false, true}}, + 300, EnabledList{1, 1, 0}, ExpectedList{100, 100, -1}), + + make_tuple(StreamConfigList{{900, 300, 300, false, true}, + {900, 300, 300, false, true}, + {900, 300, 300, false, true}}, + 300, EnabledList{1, 1, 1}, ExpectedList{100, 100, 100}), + make_tuple(StreamConfigList{{900, 300, 300, false, true}, + {900, 300, 300, false, true}, + {900, 300, 300, false, true}}, + 300, EnabledList{1, 0, 0}, ExpectedList{300, -1, -1}), + make_tuple(StreamConfigList{{900, 300, 300, false, true}, + {900, 300, 300, false, true}, + {900, 300, 300, false, true}}, + 300, EnabledList{0, 1, 0}, ExpectedList{ -1, 300, -1}), + make_tuple(StreamConfigList{{900, 300, 300, false, true}, + {900, 300, 300, false, true}, + {900, 300, 300, false, true}}, + 300, EnabledList{1, 1, 0}, ExpectedList{150, 150, -1}), + make_tuple(StreamConfigList{{100, 100, 100, false, true}, + {900, 300, 300, false, true}, + {900, 300, 300, false, true}}, + 300, EnabledList{1, 1, 0}, ExpectedList{100, 200, -1}), + make_tuple(StreamConfigList{{900, 300, 300, false, true}, + {100, 100, 100, false, true}, + {900, 300, 300, false, true}}, + 300, EnabledList{1, 1, 0}, ExpectedList{200, 100, -1}), + make_tuple(StreamConfigList{{100, 100, 100, false, true}, + {100, 100, 100, false, true}, + {900, 300, 300, false, true}}, + 300, EnabledList{1, 1, 0}, ExpectedList{100, 100, -1}), + make_tuple(StreamConfigList{{900, 300, 300, false, true}, + {900, 300, 300, false, true}, + {900, 300, 300, false, true}}, + 1000, EnabledList{1, 1, 1}, ExpectedList{333, 350, 400}), + make_tuple(StreamConfigList{{900, 300, 100, false, true}, + {900, 300, 200, false, true}, + {900, 300, 300, false, true}}, + 1000, EnabledList{1, 1, 1}, ExpectedList{333, 450, 700}), + make_tuple(StreamConfigList{{900, 300, 100, false, true}, + {900, 300, 500, false, true}, + {900, 300, 500, false, true}}, + 800, EnabledList{1, 1, 1}, ExpectedList{266, 350, 350}), + make_tuple(StreamConfigList{{900, 300, 300, false, true}, + {900, 300, 300, false, true}, + {900, 300, 300, false, true}}, + 600, EnabledList{1, 1, 1}, ExpectedList{200, 200, 200}), + make_tuple(StreamConfigList{{900, 40, 300, true, true}, + {900, 300, 300, false, true}, + {900, 300, 300, false, true}}, + 500, EnabledList{1, 1, 1}, ExpectedList{166, 230, 230}), + make_tuple(StreamConfigList{{900, 40, 300, true, true}, + {100, 100, 100, false, true}, + {100, 100, 100, false, true}}, + 500, EnabledList{1, 1, 1}, ExpectedList{166, 100, 100}))); + +class MultipleTargetVideoBWDistributorTest : public BasicTargetVideoBWDistributor, + public ::testing::Test { + protected: + virtual void SetUp() { + index = 0; + stream_config_list = StreamConfigList{}; + bitrate_value = 2000; + add_to_remb_list = EnabledList{}; + expected_bitrates = ExpectedList{}; + for (int index = 0; index < 200; index++) { + stream_config_list.push_back({900, 100, 100, true, true}); + add_to_remb_list.push_back(1); + expected_bitrates.push_back(10); + } + distributor = std::make_shared(); + + setUpStreams(); + } + + virtual void TearDown() { + streams.clear(); + } +}; + +TEST_F(MultipleTargetVideoBWDistributorTest, 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); + } else { + EXPECT_CALL(*streams[index], onTransportData(_, _)).Times(0); + } + index++; + } + + onRembReceived(); +} diff --git a/erizo/src/test/utils/Mocks.h b/erizo/src/test/utils/Mocks.h index 3a1761777c..5f95f848df 100644 --- a/erizo/src/test/utils/Mocks.h +++ b/erizo/src/test/utils/Mocks.h @@ -108,6 +108,10 @@ class MockMediaStream: public MediaStream { } MOCK_METHOD0(getMaxVideoBW, uint32_t()); + MOCK_METHOD0(getBitrateSent, uint32_t()); + MOCK_METHOD0(getBitrateFromMaxQualityLayer, uint32_t()); + MOCK_METHOD0(isSlideShowModeEnabled, bool()); + MOCK_METHOD0(isSimulcast, bool()); MOCK_METHOD2(onTransportData, void(std::shared_ptr, Transport*)); };