diff --git a/src/media/Makefile.am b/src/media/Makefile.am index 72aa6621d7b8da069a5b10d84f64e81afd8f7c3b..97fc2c112a2027d7ef4df01422509135480eedc5 100644 --- a/src/media/Makefile.am +++ b/src/media/Makefile.am @@ -22,7 +22,8 @@ libmedia_la_SOURCES = \ media_filter.cpp \ media_recorder.cpp \ localrecorder.cpp \ - localrecordermanager.cpp + localrecordermanager.cpp \ + remb.cpp noinst_HEADERS = \ rtp_session.h \ @@ -43,7 +44,8 @@ noinst_HEADERS = \ media_stream.h \ media_recorder.h \ localrecorder.h \ - localrecordermanager.h + localrecordermanager.h \ + remb.h libmedia_la_LIBADD = \ ./audio/libaudio.la diff --git a/src/media/remb.cpp b/src/media/remb.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5a3f1a786ab9345268e773d7a31a388e9bcb7dcd --- /dev/null +++ b/src/media/remb.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2004-2019 Savoir-faire Linux Inc. + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Author: Pierre Lespagnol <pierre.lespagnol@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "logger.h" +#include "media/remb.h" + +#include <cstdint> +#include <utility> + + +static constexpr uint8_t packetVersion = 2; +static constexpr uint8_t packetFMT = 15; +static constexpr uint8_t packetType = 206; +static constexpr uint32_t uniqueIdentifier = 0x52454D42; // 'R' 'E' 'M' 'B'. + + +namespace jami { namespace video { +// Receiver Estimated Max Bitrate (REMB) (draft-alvestrand-rmcat-remb). +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| FMT=15 | PT=206 | length | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// 0 | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 4 | Unused = 0 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 8 | Unique identifier 'R' 'E' 'M' 'B' | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 12 | Num SSRC | BR Exp | BR Mantissa | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 16 | SSRC feedback | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : ... : + +Remb::Remb() +{} + +Remb::~Remb() +{} + +static void +insert2Byte(std::vector<uint8_t> *v, uint16_t val) +{ + v->insert(v->end(), val >> 8); + v->insert(v->end(), val & 0xff); +} + +static void +insert4Byte(std::vector<uint8_t> *v, uint32_t val) +{ + v->insert(v->end(), val >> 24); + v->insert(v->end(), (val >> 16) & 0x00ff); + v->insert(v->end(), (val >> 8) & 0x0000ff); + v->insert(v->end(), val & 0x000000ff); +} + + +uint64_t +Remb::parseREMB(rtcpREMBHeader* packet) +{ + if(packet->fmt != 15 || packet->pt != 206) { + JAMI_ERR("Unable to parse REMB packet."); + return 0; + } + uint64_t bitrate_bps = (packet->br_mantis << packet->br_exp); + bool shift_overflow = (bitrate_bps >> packet->br_exp) != packet->br_mantis; + if (shift_overflow) { + JAMI_ERR("Invalid remb bitrate value : %u*2^%u", packet->br_mantis, packet->br_exp); + return false; + } + return bitrate_bps; +} + +std::vector<uint8_t> +Remb::createREMB(uint64_t bitrate_bps) +{ + std::vector<uint8_t> remb; + + remb.insert(remb.end(), packetVersion << 4 | packetFMT); + remb.insert(remb.end(), packetType); + insert2Byte(&remb, 5); // (sizeof(rtcpREMBHeader)/4)-1 -> not safe + insert4Byte(&remb, 0x12345678); //ssrc + insert4Byte(&remb, 0x0); //ssrc source + insert4Byte(&remb, uniqueIdentifier); //uid + remb.insert(remb.end(), 1); //n_ssrc + + const uint32_t maxMantissa = 0x3ffff; // 18 bits. + uint64_t mantissa = bitrate_bps; + uint8_t exponenta = 0; + while (mantissa > maxMantissa) { + mantissa >>= 1; + ++exponenta; + } + + remb.insert(remb.end(), (exponenta << 2) | (mantissa >> 16)); + insert2Byte(&remb, mantissa & 0xffff); + insert4Byte(&remb, 0x2345678b); + + return std::move(remb); +} + +}} // namespace jami::video \ No newline at end of file diff --git a/src/media/remb.h b/src/media/remb.h new file mode 100644 index 0000000000000000000000000000000000000000..8b9f30e71904fe3fb15c9d11da4f4796fd0556e3 --- /dev/null +++ b/src/media/remb.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2004-2019 Savoir-faire Linux Inc. + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Author: Pierre Lespagnol <pierre.lespagnol@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_REMB_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_REMB_H_ + +#include <vector> +#include <cstdint> + +#include "socket_pair.h" + +namespace jami { namespace video { + +// Receiver Estimated Max Bitrate (REMB) (draft-alvestrand-rmcat-remb). +class Remb { +public: + Remb(); + ~Remb(); + + static uint64_t parseREMB(rtcpREMBHeader* packet); + std::vector<uint8_t> createREMB(uint64_t bitrate_bps); + +private: + +}; + +}} // namespace jami::video +#endif diff --git a/src/media/socket_pair.cpp b/src/media/socket_pair.cpp index 3f6cb1555cf545f1caddcb330f063170a9f6bf52..b0773132d7fcf950c7e0208e2065462027b905cc 100644 --- a/src/media/socket_pair.cpp +++ b/src/media/socket_pair.cpp @@ -84,6 +84,8 @@ static constexpr int RTP_MAX_PACKET_LENGTH = 2048; static constexpr auto UDP_HEADER_SIZE = 8; static constexpr auto SRTP_OVERHEAD = 10; static constexpr uint32_t RTCP_RR_FRACTION_MASK = 0xFF000000; +static constexpr unsigned MINIMUM_RTP_HEADER_SIZE = 16; + enum class DataType : unsigned { RTP=1<<0, RTCP=1<<1 }; @@ -218,12 +220,12 @@ SocketPair::waitForRTCP(std::chrono::seconds interval) { std::unique_lock<std::mutex> lock(rtcpInfo_mutex_); return cvRtcpPacketReadyToRead_.wait_for(lock, interval, [this]{ - return interrupted_ or not listRtcpHeader_.empty(); + return interrupted_ or not listRtcpRRHeader_.empty() or not listRtcpREMBHeader_.empty(); }); } void -SocketPair::saveRtcpPacket(uint8_t* buf, size_t len) +SocketPair::saveRtcpRRPacket(uint8_t* buf, size_t len) { if (len < sizeof(rtcpRRHeader)) return; @@ -234,20 +236,49 @@ SocketPair::saveRtcpPacket(uint8_t* buf, size_t len) std::lock_guard<std::mutex> lock(rtcpInfo_mutex_); - if (listRtcpHeader_.size() >= MAX_LIST_SIZE) { - listRtcpHeader_.pop_front(); + if (listRtcpRRHeader_.size() >= MAX_LIST_SIZE) { + listRtcpRRHeader_.pop_front(); + } + + listRtcpRRHeader_.emplace_back(*header); + + cvRtcpPacketReadyToRead_.notify_one(); +} + + +void +SocketPair::saveRtcpREMBPacket(uint8_t* buf, size_t len) +{ + if (len < sizeof(rtcpREMBHeader)) + return; + + auto header = reinterpret_cast<rtcpREMBHeader*>(buf); + if(header->pt != 206) //206 = REMB PT + return; + + std::lock_guard<std::mutex> lock(rtcpInfo_mutex_); + + if (listRtcpREMBHeader_.size() >= MAX_LIST_SIZE) { + listRtcpREMBHeader_.pop_front(); } - listRtcpHeader_.push_back(*header); + listRtcpREMBHeader_.push_back(*header); cvRtcpPacketReadyToRead_.notify_one(); } std::list<rtcpRRHeader> -SocketPair::getRtcpInfo() +SocketPair::getRtcpRR() +{ + std::lock_guard<std::mutex> lock(rtcpInfo_mutex_); + return std::move(listRtcpRRHeader_); +} + +std::list<rtcpREMBHeader> +SocketPair::getRtcpREMB() { std::lock_guard<std::mutex> lock(rtcpInfo_mutex_); - return std::move(listRtcpHeader_); + return std::move(listRtcpREMBHeader_); } void @@ -430,18 +461,21 @@ SocketPair::readCallback(uint8_t* buf, int buf_size) int len = 0; bool fromRTCP = false; - auto header = reinterpret_cast<rtcpRRHeader*>(buf); - if(header->pt == 201) //201 = RR PT - { - lastDLSR_ = Swap4Bytes(header->dlsr); - //JAMI_WARN("Read RR, lastDLSR : %d", lastDLSR_); - lastRR_time = std::chrono::steady_clock::now(); - } - - // Priority to RTCP as its less invasive in bandwidth if (datatype & static_cast<int>(DataType::RTCP)) { len = readRtcpData(buf, buf_size); - saveRtcpPacket(buf, len); + auto header = reinterpret_cast<rtcpRRHeader*>(buf); + if(header->pt == 201) //201 = RR PT + { + lastDLSR_ = Swap4Bytes(header->dlsr); + //JAMI_WARN("Read RR, lastDLSR : %d", lastDLSR_); + lastRR_time = std::chrono::steady_clock::now(); + saveRtcpRRPacket(buf, len); + } + else if(header->pt == 206) //206 = REMB PT + { + JAMI_ERR("Receive Remb !!"); + saveRtcpREMBPacket(buf, len); + } fromRTCP = true; } @@ -454,11 +488,25 @@ SocketPair::readCallback(uint8_t* buf, int buf_size) if (len <= 0) return len; + if (not fromRTCP && (buf_size < MINIMUM_RTP_HEADER_SIZE)) + return len; + // SRTP decrypt if (not fromRTCP and srtpContext_ and srtpContext_->srtp_in.aes) { + uint32_t curentSendTS = buf[4] << 24 | buf[5] << 16 | buf[6] << 8 | buf[7]; + int32_t delay = 0; + float abs = 0.0f; + bool marker = (buf[1] & 0x80) >> 7; + bool res = getOneWayDelayGradient2(abs, marker, &delay); + + if (res) + rtpDelayCallback_(delay); + auto err = ff_srtp_decrypt(&srtpContext_->srtp_in, buf, &len); - if(packetLossCallback_ and (buf[2] << 8 | buf[3]) != lastSeqNum_+1) - packetLossCallback_(); + if(packetLossCallback_ and (buf[2] << 8 | buf[3]) != lastSeqNum_+1) { + JAMI_ERR("RTP missed !"); + // packetLossCallback_(); + } lastSeqNum_ = buf[2] << 8 | buf[3]; if (err < 0) JAMI_WARN("decrypt error %d", err); @@ -590,4 +638,105 @@ SocketPair::setPacketLossCallback(std::function<void(void)> cb) packetLossCallback_ = std::move(cb); } +void +SocketPair::setRtpDelayCallback(std::function<void(int)> cb) +{ + rtpDelayCallback_ = std::move(cb); +} + +int32_t +SocketPair::getOneWayDelayGradient(uint32_t sendTS) +{ + if (not lastSendTS_) { + lastSendTS_ = sendTS; + lastReceiveTS_ = std::chrono::steady_clock::now(); + return 0; + } + + if (sendTS == lastSendTS_) + return 0; + + uint32_t deltaS = ((sendTS - lastSendTS_) / 90000.0) * 1000000; // microseconds + lastSendTS_ = sendTS; + + std::chrono::steady_clock::time_point arrival_TS = std::chrono::steady_clock::now(); + auto deltaR = std::chrono::duration_cast<std::chrono::microseconds>(arrival_TS - lastReceiveTS_).count(); + lastReceiveTS_ = arrival_TS; + + return deltaR - deltaS; +} + +bool +SocketPair::getOneWayDelayGradient2(float sendTS, bool marker, int32_t* gradient) +{ + // Keep only last packet of each frame + if (not marker) { + return 0; + } + + // 1st frame + if (not lastSendTS_) { + lastSendTS_ = sendTS; + lastReceiveTS_ = std::chrono::steady_clock::now(); + return 0; + } + + uint32_t deltaS = (sendTS - lastSendTS_) * 1000; // milliseconds + if(deltaS < 0) + deltaS += 64000; + lastSendTS_ = sendTS; + + std::chrono::steady_clock::time_point arrival_TS = std::chrono::steady_clock::now(); + auto deltaR = std::chrono::duration_cast<std::chrono::milliseconds>(arrival_TS - lastReceiveTS_).count(); + lastReceiveTS_ = arrival_TS; + + *gradient = deltaR - deltaS; + + return true; +} + +bool +SocketPair::getOneWayDelayGradient3(uint32_t sendTS, int32_t* gradient) +{ + // First sample, fill Ts0 and Tr0 + if(not svgTS.send_ts) { + svgTS.send_ts = sendTS; + svgTS.receive_ts = std::chrono::steady_clock::now(); + return false; + } + + // new frame + if(svgTS.send_ts != sendTS) { + // Second sample, fill Ts1 and Tr1 and replace Ts0 and Tr0 + if(not svgTS.last_send_ts) { + svgTS.last_send_ts = svgTS.send_ts; + svgTS.send_ts = sendTS; + + svgTS.last_receive_ts = svgTS.receive_ts; + svgTS.receive_ts = std::chrono::steady_clock::now(); + return false; + } + uint32_t deltaS = ((svgTS.send_ts - svgTS.last_send_ts) / 90000.0) * 1000000; // microseconds + auto deltaR = std::chrono::duration_cast<std::chrono::microseconds>(svgTS.receive_ts - svgTS.last_receive_ts).count(); + + *gradient = deltaR - deltaS; + + // JAMI_ERR("[PL] delR:%ld, delS:%ld, gradient:%d", deltaR, deltaS, *gradient); + + // fill Ts1 and Tr1 and replace Ts0 and Tr0 + svgTS.last_send_ts = svgTS.send_ts; + svgTS.send_ts = sendTS; + + svgTS.last_receive_ts = svgTS.receive_ts; + svgTS.receive_ts = std::chrono::steady_clock::now(); + } + else { + // Update receive TS + svgTS.receive_ts = std::chrono::steady_clock::now(); + return false; + } + + return true; +} + } // namespace jami diff --git a/src/media/socket_pair.h b/src/media/socket_pair.h index 593996cd97e4fbaef4804cf65923fa24cee56a1e..6024aa9d139bb1bfac6839d73eae2cba4ef93666 100644 --- a/src/media/socket_pair.h +++ b/src/media/socket_pair.h @@ -97,6 +97,37 @@ typedef struct { } rtcpSRHeader; +typedef struct { +#ifdef WORDS_BIGENDIAN + uint32_t version:2; /* protocol version */ + uint32_t p:1; /* padding flag always 0 */ + uint32_t fmt:5; /* Feedback message type always 15 */ + +#else + uint32_t fmt:5; /* Feedback message type always 15 */ + uint32_t p:1; /* padding flag always 0 */ + uint32_t version:2; /* protocol version */ +#endif + uint32_t pt:8; /* payload type */ + uint32_t len:16; /* length of RTCP packet */ + uint32_t ssrc; /* synchronization source identifier of packet sender */ + uint32_t ssrc_source; /* synchronization source identifier of first source alway 0*/ + uint32_t uid; /* Unique identifier Always ‘R’ ‘E’ ‘M’ ‘B’ (4 ASCII characters). */ + uint32_t n_ssrc:8; /* Number of SSRCs in this message. */ + uint32_t br_exp:6; /* BR Exp */ + uint32_t br_mantis:18; /* BR Mantissa */ + uint32_t f_ssrc; /* SSRC feedback */ +} rtcpREMBHeader; + + + +typedef struct { + uint64_t last_send_ts; + std::chrono::steady_clock::time_point last_receive_ts; + uint64_t send_ts; + std::chrono::steady_clock::time_point receive_ts; +} TS_Frame; + class SocketPair { public: SocketPair(const char* uri, int localPort); @@ -131,12 +162,16 @@ class SocketPair { const char* in_suite, const char* in_params); void stopSendOp(bool state = true); - std::list<rtcpRRHeader> getRtcpInfo(); + std::list<rtcpRRHeader> getRtcpRR(); + std::list<rtcpREMBHeader> getRtcpREMB(); bool waitForRTCP(std::chrono::seconds interval); double getLastLatency(); void setPacketLossCallback(std::function<void (void)> cb); + void setRtpDelayCallback(std::function<void (int)> cb); + + int writeData(uint8_t* buf, int buf_size); private: NON_COPYABLE(SocketPair); @@ -147,8 +182,8 @@ class SocketPair { int waitForData(); int readRtpData(void* buf, int buf_size); int readRtcpData(void* buf, int buf_size); - int writeData(uint8_t* buf, int buf_size); - void saveRtcpPacket(uint8_t* buf, size_t len); + void saveRtcpRRPacket(uint8_t* buf, size_t len); + void saveRtcpREMBPacket(uint8_t* buf, size_t len); std::mutex dataBuffMutex_; std::condition_variable cv_; @@ -166,8 +201,13 @@ class SocketPair { std::atomic_bool noWrite_ {false}; std::unique_ptr<SRTPProtoContext> srtpContext_; std::function<void(void)> packetLossCallback_; + std::function<void(int)> rtpDelayCallback_; + int32_t getOneWayDelayGradient(uint32_t sendTS); + bool getOneWayDelayGradient2(float sendTS, bool marker, int32_t* gradient); + bool getOneWayDelayGradient3(uint32_t sendTS, int32_t* gradient); - std::list<rtcpRRHeader> listRtcpHeader_; + std::list<rtcpRRHeader> listRtcpRRHeader_; + std::list<rtcpREMBHeader> listRtcpREMBHeader_; std::mutex rtcpInfo_mutex_; std::condition_variable cvRtcpPacketReadyToRead_; static constexpr unsigned MAX_LIST_SIZE {10}; @@ -180,6 +220,13 @@ class SocketPair { std::chrono::steady_clock::time_point lastRR_time; uint16_t lastSeqNum_ {0}; + uint32_t lastSendTS_ {0}; + bool lastMarker_ {false}; + std::chrono::steady_clock::time_point lastReceiveTS_ {}; + std::chrono::steady_clock::time_point arrival_TS {}; + + TS_Frame svgTS = {}; + }; diff --git a/src/media/srtp.h b/src/media/srtp.h index 2bfae60623e8eeb6e3e9c259d639acfece0e9e25..c8b3fcca6281d7ecbb3fa358663dcfd660950dfa 100644 --- a/src/media/srtp.h +++ b/src/media/srtp.h @@ -54,7 +54,8 @@ enum RTCPType { RTCP_FIR = 192, RTCP_IJ = 195, RTCP_SR = 200, - RTCP_TOKEN = 210 + RTCP_TOKEN = 210, + RTCP_REMB = 206 }; #define RTP_PT_IS_RTCP(x) (((x) >= RTCP_FIR && (x) <= RTCP_IJ) || \ diff --git a/src/media/video/video_rtp_session.cpp b/src/media/video/video_rtp_session.cpp index a553461246dc0b1f582e046a7989cc6391b15879..88a1cc6674a2eff6b4f43f3d8af694c740db4959 100644 --- a/src/media/video/video_rtp_session.cpp +++ b/src/media/video/video_rtp_session.cpp @@ -33,6 +33,7 @@ #include "string_utils.h" #include "call.h" #include "conference.h" +#include "remb.h" #include "account_const.h" @@ -46,8 +47,17 @@ namespace jami { namespace video { using std::string; +static constexpr unsigned MAX_SIZE_HISTO_QUALITY {30}; +static constexpr unsigned MAX_SIZE_HISTO_BITRATE {100}; +static constexpr unsigned MAX_SIZE_HISTO_JITTER {50}; +static constexpr unsigned MAX_SIZE_HISTO_DELAY {25}; + constexpr auto DELAY_AFTER_RESTART = std::chrono::milliseconds(1000); -constexpr auto EXPIRY_TIME_RTCP = std::chrono::milliseconds(1000); +constexpr auto DELAY_AFTER_INCREASE = std::chrono::seconds(5); +constexpr auto EXPIRY_TIME_RTCP = std::chrono::seconds(2); +constexpr auto DELAY_AFTER_REMB = std::chrono::milliseconds(300); + +constexpr int THRESHOLD_CONGESTION {1000}; VideoRtpSession::VideoRtpSession(const string &callID, const DeviceParams& localVideoParams) : @@ -75,7 +85,7 @@ VideoRtpSession::updateMedia(const MediaDescription& send, const MediaDescriptio void VideoRtpSession::setRequestKeyFrameCallback(std::function<void(void)> cb) { - if(socketPair_) + if (socketPair_) socketPair_->setPacketLossCallback(std::move(cb)); else JAMI_ERR("No socket pair, keyframe request callback not possible"); @@ -133,12 +143,18 @@ void VideoRtpSession::startSender() send_.enabled = false; } lastMediaRestart_ = clock::now(); + lastIncrease_ = clock::now(); + lastREMB_ = clock::now(); auto codecVideo = std::static_pointer_cast<jami::AccountVideoCodecInfo>(send_.codec); auto autoQuality = codecVideo->isAutoQualityEnabled; if (autoQuality and not rtcpCheckerThread_.isRunning()) rtcpCheckerThread_.start(); else if (not autoQuality and rtcpCheckerThread_.isRunning()) rtcpCheckerThread_.join(); + + socketPair_->setRtpDelayCallback([&](int delay) { + this->delayMonitor(delay); + }); } } @@ -345,9 +361,9 @@ void VideoRtpSession::exitConference() } bool -VideoRtpSession::checkMediumRCTPInfo(RTCPInfo& rtcpi) +VideoRtpSession::check_RCTP_Info_RR(RTCPInfo& rtcpi) { - auto rtcpInfoVect = socketPair_->getRtcpInfo(); + auto rtcpInfoVect = socketPair_->getRtcpRR(); unsigned totalLost = 0; unsigned totalJitter = 0; unsigned nbDropNotNull = 0; @@ -355,7 +371,7 @@ VideoRtpSession::checkMediumRCTPInfo(RTCPInfo& rtcpi) if (vectSize != 0) { for (const auto& it : rtcpInfoVect) { - if(it.fraction_lost != 0) // Exclude null drop + if (it.fraction_lost != 0) // Exclude null drop nbDropNotNull++; totalLost += it.fraction_lost; totalJitter += ntohl(it.jitter); @@ -371,6 +387,20 @@ VideoRtpSession::checkMediumRCTPInfo(RTCPInfo& rtcpi) return false; } +bool +VideoRtpSession::check_RCTP_Info_REMB(uint64_t* br) +{ + auto rtcpInfoVect = socketPair_->getRtcpREMB(); + + if (!rtcpInfoVect.empty()) { + for (auto& it : rtcpInfoVect) { + *br = Remb::parseREMB(&it); + } + return true; + } + return false; +} + unsigned VideoRtpSession::getLowerQuality() { @@ -410,41 +440,89 @@ VideoRtpSession::adaptQualityAndBitrate() { setupVideoBitrateInfo(); + uint64_t br; + if (check_RCTP_Info_REMB(&br)) { + JAMI_WARN("[AutoAdapt] New REMB !"); + delayProcessing(br); + } + RTCPInfo rtcpi {}; - if (not checkMediumRCTPInfo(rtcpi)) { - JAMI_DBG("[AutoAdapt] Sample not ready"); - return; + if (check_RCTP_Info_RR(rtcpi)) { + // JAMI_WARN("[AutoAdapt] New RR !"); + dropProcessing(&rtcpi); } +} +void +VideoRtpSession::dropProcessing(RTCPInfo* rtcpi) +{ // If bitrate has changed, let time to receive fresh RTCP packets auto now = clock::now(); auto restartTimer = now - lastMediaRestart_; + auto increaseTimer = now - lastIncrease_; if (restartTimer < DELAY_AFTER_RESTART) { - //JAMI_DBG("[AutoAdapt] Waiting for delay %ld ms", std::chrono::duration_cast<std::chrono::milliseconds>(restartTimer)); return; } - if (rtcpi.jitter > 1000) { - //JAMI_DBG("[AutoAdapt] Jitter too high"); + //Do nothing if jitter is more than 1 second + if (rtcpi->jitter > 1000) { return; } + auto pondLoss = getPonderateLoss(rtcpi->packetLoss); auto oldBitrate = videoBitrateInfo_.videoBitrateCurrent; + int newBitrate = oldBitrate; + + // JAMI_DBG("[AutoAdapt] pond loss: %f%, last loss: %f%, last jitter: %dms, jitterAvg: %fms, jitterDev: %fms" , pondLoss, rtcpi->packetLoss, rtcpi->jitter, jitterAvg, jitterDev); + + // Fill histoLoss and histoJitter_ with samples + if (restartTimer < DELAY_AFTER_RESTART + std::chrono::seconds(1)) { + return; + } + else { + // If ponderate drops are inferior to 10% that mean drop are not from congestion but from network... + // ... we can increase + if (pondLoss >= 10.0f && rtcpi->packetLoss > 0.0f) { + newBitrate *= 1.0f - rtcpi->packetLoss/150.0f; + histoLoss_.clear(); + JAMI_DBG("[AutoAdapt] pondLoss: %f%%, packet loss rate: %f%%, decrease bitrate from %d Kbps to %d Kbps, ratio %f", pondLoss, rtcpi->packetLoss, oldBitrate, newBitrate, (float) newBitrate / oldBitrate); + } + else { + if (increaseTimer < DELAY_AFTER_INCREASE) + return; - //Take action only when two successive drop superior to 5% are catched... - //and when jitter is less than 1 seconds - auto pondLoss = getPonderateLoss(rtcpi.packetLoss); - //JAMI_DBG("[AutoAdapt] Pondloss: %f%%, last loss: %f%%", pondLoss, rtcpi.packetLoss); - if(pondLoss >= 5.0f) { - videoBitrateInfo_.videoBitrateCurrent = videoBitrateInfo_.videoBitrateCurrent * (1.0f - rtcpi.packetLoss/200.0f); - JAMI_DBG("[AutoAdapt] pondLoss: %f%%, packet loss rate: %f%%, decrease bitrate from %d Kbps to %d Kbps, ratio %f", pondLoss, rtcpi.packetLoss, oldBitrate, videoBitrateInfo_.videoBitrateCurrent, (float) videoBitrateInfo_.videoBitrateCurrent / oldBitrate); - histoLoss_.clear(); + newBitrate += 50.0f; + JAMI_DBG("[AutoAdapt] pondLoss: %f%%, packet loss rate: %f%%, increase bitrate from %d Kbps to %d Kbps, ratio %f", pondLoss, rtcpi->packetLoss, oldBitrate, newBitrate, (float) newBitrate / oldBitrate); + histoLoss_.clear(); + } } - videoBitrateInfo_.videoBitrateCurrent = std::max(videoBitrateInfo_.videoBitrateCurrent, videoBitrateInfo_.videoBitrateMin); - videoBitrateInfo_.videoBitrateCurrent = std::min(videoBitrateInfo_.videoBitrateCurrent, videoBitrateInfo_.videoBitrateMax); + setNewBitrate(newBitrate); +} - if(oldBitrate != videoBitrateInfo_.videoBitrateCurrent) { +void +VideoRtpSession::delayProcessing(int br) +{ + // If bitrate has changed, let time to receive fresh RTCP packets + + auto oldBitrate = videoBitrateInfo_.videoBitrateCurrent; + int newBitrate = oldBitrate; + + newBitrate *= 0.90f; + setNewBitrate(newBitrate); +} + + +void +VideoRtpSession::setNewBitrate(unsigned int newBR) +{ + newBR = std::max(newBR, videoBitrateInfo_.videoBitrateMin); + newBR = std::min(newBR, videoBitrateInfo_.videoBitrateMax); + + auto now = clock::now(); + + if (videoBitrateInfo_.videoBitrateCurrent != newBR) { + videoBitrateInfo_.videoBitrateCurrent = newBR; storeVideoBitrateInfo(); #if __ANDROID__ @@ -453,8 +531,11 @@ VideoRtpSession::adaptQualityAndBitrate() #endif // If encoder no longer exist do nothing - if(sender_ && sender_->setBitrate(videoBitrateInfo_.videoBitrateCurrent) == 0) + if (sender_ && sender_->setBitrate(newBR) == 0) { lastMediaRestart_ = now; + // Reset increase timer for each bitrate change + lastIncrease_ = now; + } } } @@ -494,10 +575,10 @@ VideoRtpSession::storeVideoBitrateInfo() { {DRing::Account::ConfProperties::CodecInfo::MAX_QUALITY, std::to_string(videoBitrateInfo_.videoQualityMax)} }); - if (histoQuality_.size() > MAX_SIZE_HISTO_QUALITY_) + if (histoQuality_.size() > MAX_SIZE_HISTO_QUALITY) histoQuality_.pop_front(); - if (histoBitrate_.size() > MAX_SIZE_HISTO_BITRATE_) + if (histoBitrate_.size() > MAX_SIZE_HISTO_BITRATE) histoBitrate_.pop_front(); histoQuality_.push_back(videoBitrateInfo_.videoQualityCurrent); @@ -556,8 +637,8 @@ float VideoRtpSession::getPonderateLoss(float lastLoss) { float pond = 0.0f, pondLoss = 0.0f, totalPond = 0.0f; - constexpr float coefficient_a = -1/1000.0f; - constexpr float coefficient_b = 1.0f; + constexpr float coefficient_a = -1/100.0f; + constexpr float coefficient_b = 100.0f; auto now = clock::now(); @@ -569,10 +650,12 @@ VideoRtpSession::getPonderateLoss(float lastLoss) //JAMI_WARN("now - it.first: %ld", std::chrono::duration_cast<std::chrono::milliseconds>(delay)); // 1ms -> 100% - // 1000ms -> 1 - if(delay <= EXPIRY_TIME_RTCP) - { - pond = std::min(delay.count() * coefficient_a + coefficient_b, 1.0f); + // 2000ms -> 80% + if (delay <= EXPIRY_TIME_RTCP) { + if (it->second == 0.0f) + pond = 20.0f; // Reduce weight of null drop + else + pond = std::min(delay.count() * coefficient_a + coefficient_b, 100.0f); totalPond += pond; pondLoss += it->second * pond; ++it; @@ -580,8 +663,89 @@ VideoRtpSession::getPonderateLoss(float lastLoss) else it = histoLoss_.erase(it); } + if (totalPond == 0) + return 0.0f; + return pondLoss / totalPond; } +std::pair<float, float> +VideoRtpSession::getDelayAvg() +{ + if (histoDelay_.size() != MAX_SIZE_HISTO_DELAY) + return std::make_pair(0.0f, 0.0f); + + auto middle = std::next(histoDelay_.begin(), histoDelay_.size() / 2); + float totDelayInf = 0.0f; + float totDelaySup = 0.0f; + unsigned cntInf = 0; + unsigned cntSup = 0; + + for (auto it = histoDelay_.begin(); it != middle; ++it) { + totDelayInf += *it; + cntInf++; + } + + for (auto it = middle; it != histoDelay_.end(); ++it) { + totDelaySup += *it; + cntSup++; + } + + return std::make_pair(totDelayInf/cntInf, totDelaySup/cntSup); +} + +std::pair<float, float> +VideoRtpSession::getDelayMedian() +{ + if (histoDelay_.size() != MAX_SIZE_HISTO_DELAY) + return std::make_pair(0.0f, 0.0f); + + auto middle = std::next(histoDelay_.begin(), histoDelay_.size() / 2); + std::list<int> lst2( histoDelay_.begin(), middle ); + std::list<int> lst3( middle, histoDelay_.end() ); + + lst2.sort(); lst3.sort(); + + auto mediumInf = *(std::next(lst2.begin(), lst2.size() / 2)); + auto mediumSup = *(std::next(lst3.begin(), lst3.size() / 2)); + + JAMI_WARN("Last medium delay: %d, current medium delay: %d", mediumInf, mediumSup); + + return std::make_pair(mediumInf, mediumSup); +} + +float +VideoRtpSession::getRollingAvg() +{ + if (histoDelay_.size() < MAX_SIZE_HISTO_DELAY) + return 0.0f; + + float totDelay = 0; + + for (auto it = histoDelay_.begin(); it != histoDelay_.end(); ++it) { + totDelay += (float)*it; + } + + return totDelay / histoDelay_.size(); +} + +int +VideoRtpSession::getRollingMedian() +{ + if (histoDelay_.size() < MAX_SIZE_HISTO_DELAY) + return 0; + + auto delay_bkp = histoDelay_; + delay_bkp.sort(); + auto middle = *(std::next(delay_bkp.begin(), delay_bkp.size() / 2)); + + return middle; +} + +void +VideoRtpSession::delayMonitor(int delay) +{ + JAMI_WARN("delay: %d", delay); +} }} // namespace jami::video diff --git a/src/media/video/video_rtp_session.h b/src/media/video/video_rtp_session.h index 75ea08b28d709c182db9eb1697600ecde18696bd..06050a0d73c9d882fe3c93295789c407046e5afd 100644 --- a/src/media/video/video_rtp_session.h +++ b/src/media/video/video_rtp_session.h @@ -121,7 +121,8 @@ private: std::function<void (void)> requestKeyFrameCallback_; - bool checkMediumRCTPInfo(RTCPInfo&); + bool check_RCTP_Info_RR(RTCPInfo&); + bool check_RCTP_Info_REMB(uint64_t*); unsigned getLowerQuality(); unsigned getLowerBitrate(); void adaptQualityAndBitrate(); @@ -129,17 +130,22 @@ private: void setupVideoBitrateInfo(); void checkReceiver(); float getPonderateLoss(float lastLoss); + void delayMonitor(int delay); + void dropProcessing(RTCPInfo* rtcpi); + void delayProcessing(int br); + void setNewBitrate(unsigned int newBR); // no packet loss can be calculated as no data in input static constexpr float NO_INFO_CALCULATED {-1.0}; // bitrate and quality info struct VideoBitrateInfo videoBitrateInfo_; - // previous quality and bitrate used if quality or bitrate need to be decreased + // previous quality, bitrate, jitter and loss used if quality or bitrate need to be decreased std::list<unsigned> histoQuality_ {}; std::list<unsigned> histoBitrate_ {}; + std::list<unsigned> histoJitter_ {}; + std::list<int> histoDelay_ {}; + std::list< std::pair<time_point, float> > histoLoss_; // max size of quality and bitrate historic - static constexpr unsigned MAX_SIZE_HISTO_QUALITY_ {30}; - static constexpr unsigned MAX_SIZE_HISTO_BITRATE_ {100}; // 5 tries in a row static constexpr unsigned MAX_ADAPTATIVE_BITRATE_ITERATION {5}; @@ -156,8 +162,16 @@ private: std::chrono::seconds rtcp_checking_interval {4}; time_point lastMediaRestart_ {time_point::min()}; + time_point lastIncrease_ {time_point::min()}; + time_point lastREMB_ {time_point::min()}; + + int lastDelayAVG_ {0}; + unsigned cnt_delay_callback_ {0}; + std::pair<float, float> getDelayAvg(); + std::pair<float, float> getDelayMedian(); + float getRollingAvg(); + int getRollingMedian(); - std::list< std::pair<time_point, float> > histoLoss_; }; }} // namespace jami::video