diff --git a/src/media/media_codec.h b/src/media/media_codec.h
index 256448757797520b691ca40214e0aa5b5ee4e8f9..489ee7e6a9f49b7e5c9ea8d0ea2d04a4ed9463a7 100644
--- a/src/media/media_codec.h
+++ b/src/media/media_codec.h
@@ -60,7 +60,7 @@ struct SystemCodecInfo
 {
     static constexpr unsigned DEFAULT_CODEC_QUALITY {30};
 #ifdef ENABLE_VIDEO
-    static constexpr unsigned DEFAULT_H264_MIN_QUALITY {35};
+    static constexpr unsigned DEFAULT_H264_MIN_QUALITY {40};
     static constexpr unsigned DEFAULT_H264_MAX_QUALITY {20};
     static constexpr unsigned DEFAULT_VP8_MIN_QUALITY {50};
     static constexpr unsigned DEFAULT_VP8_MAX_QUALITY {20};
@@ -70,8 +70,8 @@ struct SystemCodecInfo
     // indicates that the codec does not use quality factor
     static constexpr unsigned DEFAULT_NO_QUALITY {0};
 
-    static constexpr unsigned DEFAULT_MIN_BITRATE {250};
-    static constexpr unsigned DEFAULT_MAX_BITRATE {3000};
+    static constexpr unsigned DEFAULT_MIN_BITRATE {220};
+    static constexpr unsigned DEFAULT_MAX_BITRATE {6000};
 
     SystemCodecInfo(unsigned avcodecId, const std::string& name,
                     const std::string& libName, MediaType mediaType,
diff --git a/src/media/media_encoder.cpp b/src/media/media_encoder.cpp
index bedecd9099b1dee391a4c9e922f3500992bc3788..e6722717a96c379709e41c6ad500e85754d04b1e 100644
--- a/src/media/media_encoder.cpp
+++ b/src/media/media_encoder.cpp
@@ -52,6 +52,9 @@ extern "C" {
 
 namespace jami {
 
+constexpr double LOGREG_PARAM_A {114.40432};
+constexpr double LOGREG_PARAM_B {-6.049181};
+
 MediaEncoder::MediaEncoder()
     : outputCtx_(avformat_alloc_context())
 {}
@@ -218,9 +221,9 @@ MediaEncoder::initStream(const SystemCodecInfo& systemCodecInfo, AVBufferRef* fr
     }
 #endif
 
-    auto maxBitrate = 1000 * std::atoi(libav_utils::getDictValue(options_, "max_rate"));
-    auto bufSize = 2 * maxBitrate; // as recommended (TODO: make it customizable)
-    auto crf = std::atoi(libav_utils::getDictValue(options_, "crf"));
+    uint64_t maxBitrate = 1000 * std::atoi(libav_utils::getDictValue(options_, "max_rate"));
+    uint8_t crf = (uint8_t) std::round(LOGREG_PARAM_A + log(pow(maxBitrate, LOGREG_PARAM_B)));     // CRF = A + B*ln(maxBitrate)
+    uint64_t bufSize = 2 * maxBitrate;
 
     /* let x264 preset override our encoder settings */
     if (systemCodecInfo.avcodecId == AV_CODEC_ID_H264) {
@@ -245,7 +248,7 @@ MediaEncoder::initStream(const SystemCodecInfo& systemCodecInfo, AVBufferRef* fr
         if (crf == SystemCodecInfo::DEFAULT_NO_QUALITY)
             crf = 30; // good value for H264-720p@30
         JAMI_DBG("H264 encoder setup: crf=%u, maxrate=%u, bufsize=%u", crf, maxBitrate, bufSize);
-
+        libav_utils::setDictValue(&options_, "crf", std::to_string(crf));
         av_opt_set_int(encoderCtx, "crf", crf, AV_OPT_SEARCH_CHILDREN);
         encoderCtx->rc_buffer_size = bufSize;
         encoderCtx->rc_max_rate = maxBitrate;
@@ -651,7 +654,7 @@ MediaEncoder::extractProfileLevelID(const std::string &parameters,
     // From RFC3984:
     // If no profile-level-id is present, the Baseline Profile without
     // additional constraints at Level 1 MUST be implied.
-    ctx->profile = FF_PROFILE_H264_BASELINE;
+    ctx->profile = FF_PROFILE_H264_CONSTRAINED_BASELINE;
     ctx->level = 0x0d;
     // ctx->level = 0x0d; // => 13 aka 1.3
     if (parameters.empty())
diff --git a/src/media/socket_pair.cpp b/src/media/socket_pair.cpp
index 2fe7fb068033a2b5861a4a9911d1b561e562565d..31065c8a0ec8a5ce9c0b2abaabe57237426789dd 100644
--- a/src/media/socket_pair.cpp
+++ b/src/media/socket_pair.cpp
@@ -59,6 +59,24 @@ extern "C" {
 #include <fcntl.h>
 #endif
 
+
+// Swap 2 byte, 16 bit values:
+#define Swap2Bytes(val) \
+	 ( (((val) >> 8) & 0x00FF) | (((val) << 8) & 0xFF00) )
+
+// Swap 4 byte, 32 bit values:
+#define Swap4Bytes(val) \
+    ( (((val) >> 24) & 0x000000FF) | (((val) >>  8) & 0x0000FF00) | \
+    (((val) <<  8) & 0x00FF0000) | (((val) << 24) & 0xFF000000) )
+
+// Swap 8 byte, 64 bit values:
+#define Swap8Bytes(val) \
+    ( (((val) >> 56) & 0x00000000000000FF) | (((val) >> 40) & 0x000000000000FF00) | \
+    (((val) >> 24) & 0x0000000000FF0000) | (((val) >>  8) & 0x00000000FF000000) | \
+    (((val) <<  8) & 0x000000FF00000000) | (((val) << 24) & 0x0000FF0000000000) | \
+    (((val) << 40) & 0x00FF000000000000) | (((val) << 56) & 0xFF00000000000000) )
+
+
 namespace jami {
 
 static constexpr int NET_POLL_TIMEOUT = 100; /* poll() timeout in ms */
@@ -195,6 +213,15 @@ SocketPair::~SocketPair()
     closeSockets();
 }
 
+bool
+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();
+    });
+}
+
 void
 SocketPair::saveRtcpPacket(uint8_t* buf, size_t len)
 {
@@ -212,19 +239,15 @@ SocketPair::saveRtcpPacket(uint8_t* buf, size_t len)
     }
 
     listRtcpHeader_.push_back(*header);
+
+    cvRtcpPacketReadyToRead_.notify_one();
 }
 
-std::vector<rtcpRRHeader>
+std::list<rtcpRRHeader>
 SocketPair::getRtcpInfo()
 {
-    decltype(listRtcpHeader_) l;
-    {
-        std::lock_guard<std::mutex> lock(rtcpInfo_mutex_);
-        if (listRtcpHeader_.empty())
-            return {};
-        l = std::move(listRtcpHeader_);
-    }
-    return {std::make_move_iterator(l.begin()), std::make_move_iterator(l.end())};
+    std::lock_guard<std::mutex> lock(rtcpInfo_mutex_);
+    return std::move(listRtcpHeader_);
 }
 
 void
@@ -406,6 +429,14 @@ 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);
@@ -478,6 +509,8 @@ SocketPair::writeCallback(uint8_t* buf, int buf_size)
 {
     int ret;
     bool isRTCP = RTP_PT_IS_RTCP(buf[1]);
+    unsigned int ts_LSB, ts_MSB;
+    double currentSRTS, currentLatency;
 
     // Encrypt?
     if (not isRTCP and srtpContext_ and srtpContext_->srtp_out.aes) {
@@ -505,15 +538,46 @@ SocketPair::writeCallback(uint8_t* buf, int buf_size)
         ret = writeData(buf, buf_size);
     } while (ret < 0 and errno == EAGAIN);
 
+    if(buf[1] == 200) //Sender Report
+    {
+        auto header = reinterpret_cast<rtcpSRHeader*>(buf);
+        ts_LSB = Swap4Bytes(header->timestampLSB);
+        ts_MSB = Swap4Bytes(header->timestampMSB);
+
+        currentSRTS = ts_MSB + (ts_LSB / pow(2,32));
+
+        if(lastSRTS_ != 0 && lastDLSR_ != 0)
+        {
+            if (histoLatency_.size() >= MAX_LIST_SIZE)
+                histoLatency_.pop_front();
+
+            currentLatency = (currentSRTS - lastSRTS_) / 2;
+            //JAMI_WARN("Current Latency : %f from sender %X", currentLatency, header->ssrc);
+            histoLatency_.push_back(currentLatency);
+        }
+
+        lastSRTS_ = currentSRTS;
+
+        // JAMI_WARN("SENDING NEW RTCP SR !! ");
+
+    }
+    else if(buf[1] == 201) //Receiver Report
+    {
+        //auto header = reinterpret_cast<rtcpRRHeader*>(buf);
+        //JAMI_WARN("SENDING NEW RTCP RR !! ");
+
+    }
+
     return ret < 0 ? -errno : ret;
 }
 
-bool
-SocketPair::rtcpPacketLossDetected() const
+double
+SocketPair::getLastLatency()
 {
-    // set to false on checking packet loss to avoid burst of keyframe requests
-    bool b = true;
-    return rtcpPacketLoss_.compare_exchange_strong(b, false);
+    if(not histoLatency_.empty())
+        return histoLatency_.back();
+    else
+        return -1;
 }
 
 } // namespace jami
diff --git a/src/media/socket_pair.h b/src/media/socket_pair.h
index f761c270e41ba4067a257020a44282677a100239..e620202e9fd6027875d37b870c740d3363d433ad 100644
--- a/src/media/socket_pair.h
+++ b/src/media/socket_pair.h
@@ -66,12 +66,37 @@ typedef struct {
     uint32_t pt:8;      /* payload type */
     uint32_t len:16;    /* length of RTCP packet */
     uint32_t ssrc;      /* synchronization source identifier of packet send */
-    uint32_t ssrc_1;    /* synchronization source identifier of first source */
-    uint32_t fraction_lost; /* 8 bits of fraction, 24 bits of total packets lost */
-    uint32_t last_seq;  /*last sequence number */
-    uint32_t jitter;    /*jitter */
+    uint32_t id;        /* synchronization source identifier of first source */
+    uint32_t fraction_lost:8;       /* 8 bits of fraction, 24 bits of total packets lost */
+    uint32_t cum_lost_packet:24;    /* cumulative number packet lost */
+    uint32_t ext_high;  /* Extended highest sequence number received */
+    uint32_t jitter;    /* jitter */
+    uint32_t lsr;       /* last SR timestamp */
+    uint32_t dlsr;      /* Delay since last SR timestamp */
 } rtcpRRHeader;
 
+typedef struct {
+#ifdef WORDS_BIGENDIAN
+    uint32_t version:2; /* protocol version */
+    uint32_t p:1;       /* padding flag */
+    uint32_t rc:5;      /* reception report count must be 201 for report */
+
+#else
+    uint32_t rc:5;      /* reception report count must be 201 for report */
+    uint32_t p:1;       /* padding flag */
+    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 send */
+    uint32_t timestampMSB;      /* timestamp MSB */
+    uint32_t timestampLSB;      /* timestamp LSB */
+    uint32_t timestampRTP;      /* RTP timestamp */
+    uint32_t spc;       /* Sender's packet count */
+    uint32_t soc;       /* Sender's octet count */
+} rtcpSRHeader;
+
+
 class SocketPair {
     public:
         SocketPair(const char* uri, int localPort);
@@ -106,8 +131,10 @@ class SocketPair {
                         const char* in_suite, const char* in_params);
 
         void stopSendOp(bool state = true);
-        std::vector<rtcpRRHeader> getRtcpInfo();
-        bool rtcpPacketLossDetected() const;
+        std::list<rtcpRRHeader> getRtcpInfo();
+
+        bool waitForRTCP(std::chrono::seconds interval);
+        double getLastLatency();
 
     private:
         NON_COPYABLE(SocketPair);
@@ -139,12 +166,17 @@ class SocketPair {
 
         std::list<rtcpRRHeader> listRtcpHeader_;
         std::mutex rtcpInfo_mutex_;
-        static constexpr unsigned MAX_LIST_SIZE {20};
+        std::condition_variable cvRtcpPacketReadyToRead_;
+        static constexpr unsigned MAX_LIST_SIZE {10};
 
         mutable std::atomic_bool rtcpPacketLoss_ {false};
-};
+        double lastSRTS_;
+        uint32_t lastDLSR_;
 
+        std::list<double> histoLatency_;
 
+        std::chrono::steady_clock::time_point lastRR_time;
+};
 
 
 } // namespace jami
diff --git a/src/media/video/video_receive_thread.cpp b/src/media/video/video_receive_thread.cpp
index 1df7177708603c85e65653f15b2fe12997ea733d..f7354e3f35ca958658b67c9d3d6f518082d65989 100644
--- a/src/media/video/video_receive_thread.cpp
+++ b/src/media/video/video_receive_thread.cpp
@@ -40,6 +40,8 @@ namespace jami { namespace video {
 
 using std::string;
 
+constexpr auto MS_BETWEEN_2_KEYFRAME_REQUEST = std::chrono::milliseconds(500);
+
 VideoReceiveThread::VideoReceiveThread(const std::string& id,
                                        const std::string &sdp,
                                        uint16_t mtu) :
@@ -199,7 +201,14 @@ bool VideoReceiveThread::decodeFrame()
         case MediaDecoder::Status::DecodeError:
             JAMI_WARN("video decoding failure");
             if (requestKeyFrameCallback_)
-                requestKeyFrameCallback_();
+            {
+                auto keyFrameCheckTimer = std::chrono::steady_clock::now() - lastKeyFrameTime_;
+                if (keyFrameCheckTimer >= MS_BETWEEN_2_KEYFRAME_REQUEST)
+                {
+                    lastKeyFrameTime_ = std::chrono::steady_clock::now();
+                    requestKeyFrameCallback_();
+                }
+            }
             break;
 
         case MediaDecoder::Status::ReadError:
diff --git a/src/media/video/video_receive_thread.h b/src/media/video/video_receive_thread.h
index 436ab5496d9988d5419d59ae3167bf253777c78d..413c529857bc45ed9d61a4ee22ea95d4c1cb5221 100644
--- a/src/media/video/video_receive_thread.h
+++ b/src/media/video/video_receive_thread.h
@@ -90,6 +90,7 @@ private:
     uint16_t mtu_;
     int rotation_;
     std::shared_ptr<AVBufferRef> displayMatrix_;
+    std::chrono::steady_clock::time_point lastKeyFrameTime_;
 
     std::function<void(void)> requestKeyFrameCallback_;
     void openDecoder();
diff --git a/src/media/video/video_rtp_session.cpp b/src/media/video/video_rtp_session.cpp
index d0f362a9b28492bd3c6329ac2ef41c8c59792fa4..28d951e4d241bae03320a45832c0b357b6e72fc8 100644
--- a/src/media/video/video_rtp_session.cpp
+++ b/src/media/video/video_rtp_session.cpp
@@ -45,21 +45,16 @@ namespace jami { namespace video {
 
 using std::string;
 
-// how long (in seconds) to wait before rechecking for packet loss
-static constexpr auto RTCP_PACKET_LOSS_INTERVAL = std::chrono::milliseconds(1000);
+constexpr auto DELAY_AFTER_RESTART = std::chrono::seconds(2);
+constexpr auto EXPIRY_TIME_RTCP = std::chrono::milliseconds(2000);
 
 VideoRtpSession::VideoRtpSession(const string &callID,
                                  const DeviceParams& localVideoParams) :
     RtpSession(callID), localVideoParams_(localVideoParams)
-    , lastRTCPCheck_(std::chrono::system_clock::now())
-    , lastLongRTCPCheck_(std::chrono::system_clock::now())
     , videoBitrateInfo_ {}
     , rtcpCheckerThread_([] { return true; },
             [this]{ processRtcpChecker(); },
             []{})
-    , packetLossThread_([] { return true; },
-            [this]{ processPacketLoss(); },
-            []{})
 {
     setupVideoBitrateInfo(); // reset bitrate
 }
@@ -133,6 +128,7 @@ void VideoRtpSession::startSender()
             JAMI_ERR("%s", e.what());
             send_.enabled = false;
         }
+        lastMediaRestart_ = clock::now();
         auto codecVideo = std::static_pointer_cast<jami::AccountVideoCodecInfo>(send_.codec);
         auto autoQuality = codecVideo->isAutoQualityEnabled;
         if (autoQuality and not rtcpCheckerThread_.isRunning())
@@ -168,13 +164,11 @@ void VideoRtpSession::startReceiver()
         receiveThread_->setRequestKeyFrameCallback(requestKeyFrameCallback_);
         receiveThread_->addIOContext(*socketPair_);
         receiveThread_->startLoop();
-        packetLossThread_.start();
     } else {
         JAMI_DBG("Video receiving disabled");
         if (receiveThread_)
             receiveThread_->detach(videoMixer_.get());
         receiveThread_.reset();
-        packetLossThread_.join();
     }
 }
 
@@ -214,8 +208,6 @@ void VideoRtpSession::start(std::unique_ptr<IceSocket> rtp_sock,
 void VideoRtpSession::stop()
 {
     std::lock_guard<std::recursive_mutex> lock(mutex_);
-    rtcpCheckerThread_.join();
-    packetLossThread_.join();
     if (videoLocal_)
         videoLocal_->detach(sender_.get());
 
@@ -228,6 +220,8 @@ void VideoRtpSession::stop()
     if (socketPair_)
         socketPair_->interrupt();
 
+    rtcpCheckerThread_.join();
+
     // reset default video quality if exist
     if (videoBitrateInfo_.videoQualityCurrent != SystemCodecInfo::DEFAULT_NO_QUALITY)
         videoBitrateInfo_.videoQualityCurrent = SystemCodecInfo::DEFAULT_CODEC_QUALITY;
@@ -336,23 +330,29 @@ void VideoRtpSession::exitConference()
     conference_ = nullptr;
 }
 
-float
-VideoRtpSession::checkPeerPacketLoss()
+bool
+VideoRtpSession::checkMediumRCTPInfo(RTCPInfo& rtcpi)
 {
     auto rtcpInfoVect = socketPair_->getRtcpInfo();
     unsigned totalLost = 0;
-    unsigned fract = 0;
+    unsigned totalJitter = 0;
+    unsigned nbDropNotNull = 0;
     auto vectSize = rtcpInfoVect.size();
 
-    for (const auto& it : rtcpInfoVect) {
-        fract = (ntohl(it.fraction_lost) & 0xff000000)  >> 24;
-        totalLost += fract;
+    if (vectSize != 0) {
+        for (const auto& it : rtcpInfoVect) {
+            if(it.fraction_lost != 0)               // Exclude null drop
+                nbDropNotNull++;
+            totalLost += it.fraction_lost;
+            totalJitter += ntohl(it.jitter);
+        }
+        rtcpi.packetLoss = nbDropNotNull ? (float)( 100 * totalLost) / (256.0 * nbDropNotNull) : 0;
+        rtcpi.jitter = totalJitter / vectSize / 16;  // millisecond
+        rtcpi.nb_sample = vectSize;
+        rtcpi.latency = socketPair_->getLastLatency();
+        return true;
     }
-
-    if (vectSize != 0)
-        return (float)( 100 * totalLost) / (256.0 * vectSize);
-    else
-        return NO_PACKET_LOSS_CALCULATED;
+    return false;
 }
 
 unsigned
@@ -392,117 +392,54 @@ VideoRtpSession::getLowerBitrate()
 void
 VideoRtpSession::adaptQualityAndBitrate()
 {
-    bool needToCheckQuality = false;
-    bool mediaRestartNeeded = false;
-    float packetLostRate = 0.0;
-
-    auto rtcpCheckTimer = std::chrono::duration_cast<std::chrono::seconds> (std::chrono::system_clock::now() - lastRTCPCheck_);
-    auto rtcpLongCheckTimer = std::chrono::duration_cast<std::chrono::seconds> (std::chrono::system_clock::now() - lastLongRTCPCheck_);
+    setupVideoBitrateInfo();
 
-    if (rtcpCheckTimer.count() >= RTCP_CHECKING_INTERVAL) {
-        needToCheckQuality = true;
-        lastRTCPCheck_ = std::chrono::system_clock::now();
+    RTCPInfo rtcpi {};
+    if (not checkMediumRCTPInfo(rtcpi)) {
+        JAMI_DBG("[AutoAdapt] Sample not ready");
+        return;
     }
 
-    if (rtcpLongCheckTimer.count() >= RTCP_LONG_CHECKING_INTERVAL) {
-        needToCheckQuality = true;
-        hasReachMaxQuality_ = false;
-        lastLongRTCPCheck_ = std::chrono::system_clock::now();
-        // we force iterative bitrate adaptation
-        videoBitrateInfo_.cptBitrateChecking = 0;
+    auto now = clock::now();
+    auto restartTimer = now - lastMediaRestart_;
+    //Sleep 3 seconds while the media restart
+    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 > 5000) {
+        JAMI_DBG("[AutoAdapt] Jitter too high");
+        return;
+    }
 
-    if (needToCheckQuality) {
-
-        videoBitrateInfo_.cptBitrateChecking++;
-
-        // packetLostRate is not already available. Do nothing
-        if ((packetLostRate = checkPeerPacketLoss()) == NO_PACKET_LOSS_CALCULATED) {
-            // we force iterative bitrate adaptation
-            videoBitrateInfo_.cptBitrateChecking = 0;
-
-        // too much packet lost : decrease quality and bitrate
-        } else if (packetLostRate >= videoBitrateInfo_.packetLostThreshold) {
-
-            // calculate new quality by dichotomie
-            videoBitrateInfo_.videoQualityCurrent = getLowerQuality();
-
-            // calculate new bitrate by dichotomie
-            videoBitrateInfo_.videoBitrateCurrent =  getLowerBitrate();
-
-            // boundaries low
-            if (videoBitrateInfo_.videoQualityCurrent > videoBitrateInfo_.videoQualityMin)
-                videoBitrateInfo_.videoQualityCurrent = videoBitrateInfo_.videoQualityMin;
-
-            if (videoBitrateInfo_.videoBitrateCurrent < videoBitrateInfo_.videoBitrateMin)
-                videoBitrateInfo_.videoBitrateCurrent = videoBitrateInfo_.videoBitrateMin;
-
-
-            // we force iterative bitrate and quality adaptation
-            videoBitrateInfo_.cptBitrateChecking = 0;
-
-            // asynchronous A/V media restart
-            // we give priority to quality
-            if (((videoBitrateInfo_.videoQualityCurrent != SystemCodecInfo::DEFAULT_NO_QUALITY) &&
-                    (videoBitrateInfo_.videoQualityCurrent !=  (histoQuality_.empty() ? 0 : histoQuality_.back()))) ||
-                ((videoBitrateInfo_.videoQualityCurrent == SystemCodecInfo::DEFAULT_NO_QUALITY) &&
-                    (videoBitrateInfo_.videoBitrateCurrent !=  (histoBitrate_.empty() ? 0 : histoBitrate_.back())))) {
-                mediaRestartNeeded = true;
-                hasReachMaxQuality_ = true;
-            }
-
-
-        // no packet lost: increase quality and bitrate
-        } else if ((videoBitrateInfo_.cptBitrateChecking <= videoBitrateInfo_.maxBitrateChecking)
-                   and not hasReachMaxQuality_) {
-
-            // calculate new quality by dichotomie
-            videoBitrateInfo_.videoQualityCurrent =
-                (videoBitrateInfo_.videoQualityCurrent + videoBitrateInfo_.videoQualityMax) / 2;
-
-            // calculate new bitrate by dichotomie
-            videoBitrateInfo_.videoBitrateCurrent =
-                ( videoBitrateInfo_.videoBitrateCurrent + videoBitrateInfo_.videoBitrateMax) / 2;
-
-            // boundaries high
-            if (videoBitrateInfo_.videoQualityCurrent < videoBitrateInfo_.videoQualityMax)
-                videoBitrateInfo_.videoQualityCurrent = videoBitrateInfo_.videoQualityMax;
-
-            if (videoBitrateInfo_.videoBitrateCurrent > videoBitrateInfo_.videoBitrateMax)
-                videoBitrateInfo_.videoBitrateCurrent = videoBitrateInfo_.videoBitrateMax;
-
-            // asynchronous A/V media restart
-            // we give priority to quality
-            if (((videoBitrateInfo_.videoQualityCurrent != SystemCodecInfo::DEFAULT_NO_QUALITY) &&
-                    (videoBitrateInfo_.videoQualityCurrent !=  (histoQuality_.empty() ? 0 : histoQuality_.back()))) ||
-                ((videoBitrateInfo_.videoQualityCurrent == SystemCodecInfo::DEFAULT_NO_QUALITY) &&
-                    (videoBitrateInfo_.videoBitrateCurrent !=  (histoBitrate_.empty() ? 0 : histoBitrate_.back()))))
-                mediaRestartNeeded = true;
-
-            if (videoBitrateInfo_.cptBitrateChecking == videoBitrateInfo_.maxBitrateChecking)
-                lastLongRTCPCheck_ = std::chrono::system_clock::now();
+    auto oldBitrate = videoBitrateInfo_.videoBitrateCurrent;
 
-        } else {
-            // nothing we reach maximal tries
-        }
+    //Take action only when two successive drop superior to 1% are catched...
+    //and when jitter is less than 5 seconds
+    auto pondLoss = getPonderateLoss(rtcpi.packetLoss);
+    //JAMI_DBG("[AutoAdapt] Ponderate packet loss rate: %f%, Last packet loss rate: %f%, Medium Jitter: %dms" , pondLoss, rtcpi.packetLoss, rtcpi.jitter);
+    if(pondLoss >= 2.0f)
+    {
+        videoBitrateInfo_.videoBitrateCurrent =  videoBitrateInfo_.videoBitrateCurrent / ((rtcpi.packetLoss / 20)+1);
+        JAMI_WARN("[AutoAdapt] packet loss rate: %f%%, decrease bitrate from %d Kbps to %d Kbps", rtcpi.packetLoss, oldBitrate, videoBitrateInfo_.videoBitrateCurrent);
     }
 
-    if (mediaRestartNeeded) {
+    videoBitrateInfo_.videoBitrateCurrent = std::max(videoBitrateInfo_.videoBitrateCurrent, videoBitrateInfo_.videoBitrateMin);
+    videoBitrateInfo_.videoBitrateCurrent = std::min(videoBitrateInfo_.videoBitrateCurrent, videoBitrateInfo_.videoBitrateMax);
+
+    if(oldBitrate != videoBitrateInfo_.videoBitrateCurrent) {
         storeVideoBitrateInfo();
-        const auto& cid = callID_;
+        JAMI_DBG("[AutoAdapt] Restart media sender");
 
-        JAMI_WARN("[%u/%u] packetLostRate=%f -> change quality to %d bitrate to %d",
-                videoBitrateInfo_.cptBitrateChecking,
-                videoBitrateInfo_.maxBitrateChecking,
-                packetLostRate,
-                videoBitrateInfo_.videoQualityCurrent,
-                videoBitrateInfo_.videoBitrateCurrent);
+        const auto& cid = callID_;
 
         runOnMainThread([cid]{
             if (auto call = Manager::instance().callFactory.getCall(cid))
                 call->restartMediaSender();
             });
+
+        lastMediaRestart_ = now;
     }
 }
 
@@ -550,7 +487,6 @@ VideoRtpSession::storeVideoBitrateInfo() {
 
         histoQuality_.push_back(videoBitrateInfo_.videoQualityCurrent);
         histoBitrate_.push_back(videoBitrateInfo_.videoBitrateCurrent);
-
     }
 }
 
@@ -558,17 +494,7 @@ void
 VideoRtpSession::processRtcpChecker()
 {
     adaptQualityAndBitrate();
-    rtcpCheckerThread_.wait_for(std::chrono::seconds(RTCP_CHECKING_INTERVAL));
-}
-
-void
-VideoRtpSession::processPacketLoss()
-{
-    if (packetLossThread_.wait_for(RTCP_PACKET_LOSS_INTERVAL,
-                                   [this]{return socketPair_->rtcpPacketLossDetected();})) {
-        if (requestKeyFrameCallback_)
-            requestKeyFrameCallback_();
-    }
+    socketPair_->waitForRTCP(std::chrono::seconds(rtcp_checking_interval));
 }
 
 void
@@ -607,4 +533,39 @@ VideoRtpSession::setChangeOrientationCallback(std::function<void(int)> cb)
     changeOrientationCallback_ = std::move(cb);
 }
 
+float
+VideoRtpSession::getPonderateLoss(float lastLoss)
+{
+    float pond  = 0.0f, pondLoss  = 0.0f, totalPond = 0.0f;
+    constexpr float coefficient_a = -1/2000.0f;
+    constexpr float coefficient_b = 1.0f;
+
+    auto now = clock::now();
+
+    histoLoss_.emplace_back(now, lastLoss);
+
+    //for(auto& it : histoLoss_)
+    for (auto it = histoLoss_.begin(); it != histoLoss_.end();) {
+        auto delay = now - it->first;
+
+        //JAMI_WARN("now - it.first: %ld", std::chrono::duration_cast<std::chrono::milliseconds>(delay));
+
+        // 1ms      -> 100%
+        // 2000ms   -> 1
+        if(delay <= EXPIRY_TIME_RTCP)
+        {
+            pond = std::min(delay.count() * coefficient_a + coefficient_b, 1.0f);
+            totalPond += pond;
+            pondLoss += it->second * pond;
+            ++it;
+        }
+        else
+            it = histoLoss_.erase(it);
+    }
+    return pondLoss / totalPond;
+}
+
+
 }} // namespace jami::video
+
+
diff --git a/src/media/video/video_rtp_session.h b/src/media/video/video_rtp_session.h
index 2e589331f7ebd1bc12bd8d15ddd26410c84eb3f5..75ea08b28d709c182db9eb1697600ecde18696bd 100644
--- a/src/media/video/video_rtp_session.h
+++ b/src/media/video/video_rtp_session.h
@@ -41,6 +41,13 @@ class VideoMixer;
 class VideoSender;
 class VideoReceiveThread;
 
+struct RTCPInfo {
+    float packetLoss;
+    unsigned int jitter;
+    unsigned int nb_sample;
+    float latency;
+};
+
 struct VideoBitrateInfo {
     unsigned videoBitrateCurrent;
     unsigned videoBitrateMin;
@@ -99,13 +106,12 @@ private:
     void setupVideoPipeline();
     void startSender();
     void startReceiver();
+    using clock = std::chrono::steady_clock;
+    using time_point = clock::time_point;
 
     std::string input_;
     DeviceParams localVideoParams_;
 
-    std::chrono::time_point<std::chrono::system_clock>  lastRTCPCheck_;
-    std::chrono::time_point<std::chrono::system_clock>  lastLongRTCPCheck_;
-
     std::unique_ptr<VideoSender> sender_;
     std::unique_ptr<VideoReceiveThread> receiveThread_;
     Conference* conference_ {nullptr};
@@ -115,20 +121,17 @@ private:
 
     std::function<void (void)> requestKeyFrameCallback_;
 
-    float checkPeerPacketLoss();
+    bool checkMediumRCTPInfo(RTCPInfo&);
     unsigned getLowerQuality();
     unsigned getLowerBitrate();
     void adaptQualityAndBitrate();
     void storeVideoBitrateInfo();
     void setupVideoBitrateInfo();
     void checkReceiver();
+    float getPonderateLoss(float lastLoss);
 
-    // interval in seconds between RTCP checkings
-    const unsigned RTCP_CHECKING_INTERVAL {4};
-    // long interval in seconds between RTCP checkings
-    const unsigned RTCP_LONG_CHECKING_INTERVAL {30};
     // no packet loss can be calculated as no data in input
-    static constexpr float NO_PACKET_LOSS_CALCULATED {-1.0};
+    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
@@ -147,10 +150,14 @@ private:
     InterruptedThreadLoop rtcpCheckerThread_;
     void processRtcpChecker();
 
-    InterruptedThreadLoop packetLossThread_;
-    void processPacketLoss();
-
     std::function<void(int)> changeOrientationCallback_;
+
+    // interval in seconds between RTCP checkings
+    std::chrono::seconds rtcp_checking_interval {4};
+
+    time_point lastMediaRestart_ {time_point::min()};
+
+    std::list< std::pair<time_point, float> > histoLoss_;
 };
 
 }} // namespace jami::video