From dd5dcf8b75f977a370e17b8896e98e6e71eaffd5 Mon Sep 17 00:00:00 2001 From: Pedro Rodriguez Date: Mon, 27 Feb 2017 13:10:03 +0100 Subject: [PATCH] Add/nack generation (#780) --- erizo/src/erizo/WebRtcConnection.cpp | 4 +- erizo/src/erizo/rtp/RRGenerationHandler.cpp | 244 ------------------ erizo/src/erizo/rtp/RRGenerationHandler.h | 84 ------ .../rtp/RtcpFeedbackGenerationHandler.cpp | 113 ++++++++ .../erizo/rtp/RtcpFeedbackGenerationHandler.h | 57 ++++ erizo/src/erizo/rtp/RtcpNackGenerator.cpp | 146 +++++++++++ erizo/src/erizo/rtp/RtcpNackGenerator.h | 51 ++++ erizo/src/erizo/rtp/RtcpRrGenerator.cpp | 166 ++++++++++++ erizo/src/erizo/rtp/RtcpRrGenerator.h | 77 ++++++ erizo/src/erizo/rtp/RtpHeaders.h | 46 +++- .../erizo/rtp/RtpRetransmissionHandler.cpp | 20 +- erizo/src/erizo/rtp/RtpUtils.cpp | 15 +- erizo/src/test/log4cxx.properties | 4 +- .../src/test/rtp/RRGenerationHandlerTest.cpp | 132 ---------- .../rtp/RtcpFeedbackGenerationHandlerTest.cpp | 69 +++++ erizo/src/test/rtp/RtcpNackGeneratorTest.cpp | 204 +++++++++++++++ erizo/src/test/rtp/RtcpRrGeneratorTest.cpp | 118 +++++++++ .../erizoAgent/log4cxx.properties | 4 +- 18 files changed, 1067 insertions(+), 487 deletions(-) delete mode 100644 erizo/src/erizo/rtp/RRGenerationHandler.cpp delete mode 100644 erizo/src/erizo/rtp/RRGenerationHandler.h create mode 100644 erizo/src/erizo/rtp/RtcpFeedbackGenerationHandler.cpp create mode 100644 erizo/src/erizo/rtp/RtcpFeedbackGenerationHandler.h create mode 100644 erizo/src/erizo/rtp/RtcpNackGenerator.cpp create mode 100644 erizo/src/erizo/rtp/RtcpNackGenerator.h create mode 100644 erizo/src/erizo/rtp/RtcpRrGenerator.cpp create mode 100644 erizo/src/erizo/rtp/RtcpRrGenerator.h delete mode 100644 erizo/src/test/rtp/RRGenerationHandlerTest.cpp create mode 100644 erizo/src/test/rtp/RtcpFeedbackGenerationHandlerTest.cpp create mode 100644 erizo/src/test/rtp/RtcpNackGeneratorTest.cpp create mode 100644 erizo/src/test/rtp/RtcpRrGeneratorTest.cpp diff --git a/erizo/src/erizo/WebRtcConnection.cpp b/erizo/src/erizo/WebRtcConnection.cpp index 3944d32d91..7b160bd98a 100644 --- a/erizo/src/erizo/WebRtcConnection.cpp +++ b/erizo/src/erizo/WebRtcConnection.cpp @@ -22,8 +22,8 @@ #include "rtp/FecReceiverHandler.h" #include "rtp/RtcpProcessorHandler.h" #include "rtp/RtpRetransmissionHandler.h" +#include "rtp/RtcpFeedbackGenerationHandler.h" #include "rtp/StatsHandler.h" -#include "rtp/RRGenerationHandler.h" #include "rtp/SRPacketHandler.h" #include "rtp/SenderBandwidthEstimationHandler.h" #include "rtp/LayerDetectorHandler.h" @@ -257,7 +257,7 @@ void WebRtcConnection::initializePipeline() { pipeline_->addFront(RtpAudioMuteHandler()); pipeline_->addFront(RtpSlideShowHandler()); pipeline_->addFront(BandwidthEstimationHandler()); - pipeline_->addFront(RRGenerationHandler()); + pipeline_->addFront(RtcpFeedbackGenerationHandler()); pipeline_->addFront(RtpRetransmissionHandler()); pipeline_->addFront(SRPacketHandler()); pipeline_->addFront(SenderBandwidthEstimationHandler()); diff --git a/erizo/src/erizo/rtp/RRGenerationHandler.cpp b/erizo/src/erizo/rtp/RRGenerationHandler.cpp deleted file mode 100644 index 2c968f89ec..0000000000 --- a/erizo/src/erizo/rtp/RRGenerationHandler.cpp +++ /dev/null @@ -1,244 +0,0 @@ -#include "rtp/RRGenerationHandler.h" -#include "./WebRtcConnection.h" -#include "lib/ClockUtils.h" - -namespace erizo { - -DEFINE_LOGGER(RRGenerationHandler, "rtp.RRGenerationHandler"); - -RRGenerationHandler::RRGenerationHandler(bool use_timing) - : connection_{nullptr}, enabled_{true}, initialized_{false}, use_timing_{use_timing}, - generator_{random_device_()} {} - -RRGenerationHandler::RRGenerationHandler(const RRGenerationHandler&& handler) : // NOLINT - connection_{handler.connection_}, - enabled_{handler.enabled_}, - initialized_{handler.initialized_}, - use_timing_{handler.use_timing_}, - rr_info_map_{std::move(handler.rr_info_map_)} {} - -void RRGenerationHandler::enable() { - enabled_ = true; -} - -void RRGenerationHandler::disable() { - enabled_ = false; -} - -bool RRGenerationHandler::isRetransmitOfOldPacket(std::shared_ptr packet, - std::shared_ptr rr_info) { - RtpHeader *head = reinterpret_cast(packet->data); - if (!rtpSequenceLessThan(head->getSeqNumber(), rr_info->max_seq) || rr_info->jitter.jitter == 0) { - return false; - } - int64_t time_diff_ms = static_cast(packet->received_time_ms) - rr_info->last_recv_ts; - int64_t timestamp_diff = static_cast(head->getTimestamp() - rr_info->last_rtp_ts); - uint16_t clock_rate = rr_info->type == VIDEO_PACKET ? getVideoClockRate(head->getPayloadType()) : - getAudioClockRate(head->getPayloadType()); - int64_t rtp_time_stamp_diff_ms = timestamp_diff / clock_rate; - int64_t max_delay_ms = ((2 * rr_info->jitter.jitter) / clock_rate); - return time_diff_ms > rtp_time_stamp_diff_ms + max_delay_ms; -} - -bool RRGenerationHandler::rtpSequenceLessThan(uint16_t x, uint16_t y) { - int diff = y - x; - if (diff > 0) { - return (diff < 0x8000); - } else if (diff < 0) { - return (diff < -0x8000); - } else { - return false; - } -} - -// TODO(kekkokk) Consider add more payload types -int RRGenerationHandler::getAudioClockRate(uint8_t payload_type) { - if (payload_type == OPUS_48000_PT) { - return 48; - } else { - return 8; - } -} - -// TODO(kekkokk) ATM we only use 90Khz video type so always return 90 -int RRGenerationHandler::getVideoClockRate(uint8_t payload_type) { - return 90; -} - -void RRGenerationHandler::handleRtpPacket(std::shared_ptr packet) { - RtpHeader *head = reinterpret_cast(packet->data); - auto rr_packet_pair = rr_info_map_.find(head->getSSRC()); - if (rr_packet_pair == rr_info_map_.end()) { - ELOG_DEBUG("%s message: handleRtpPacket ssrc not found, ssrc: %u", connection_->toLog(), head->getSSRC()); - return; - } - std::shared_ptr selected_packet_info = rr_packet_pair->second; - uint16_t seq_num = head->getSeqNumber(); - selected_packet_info->packets_received++; - if (selected_packet_info->base_seq == -1) { - selected_packet_info->ssrc = head->getSSRC(); - selected_packet_info->base_seq = head->getSeqNumber(); - } - if (selected_packet_info->max_seq == -1) { - selected_packet_info->max_seq = seq_num; - } else if (!rtpSequenceLessThan(seq_num, selected_packet_info->max_seq)) { - if (seq_num < selected_packet_info->max_seq) { - selected_packet_info->cycle++; - } - selected_packet_info->max_seq = seq_num; - } - selected_packet_info->extended_seq = (selected_packet_info->cycle << 16) | selected_packet_info->max_seq; - - uint16_t clock_rate = selected_packet_info->type == VIDEO_PACKET ? getVideoClockRate(head->getPayloadType()) : - getAudioClockRate(head->getPayloadType()); - if (head->getTimestamp() != selected_packet_info->last_rtp_ts && - !isRetransmitOfOldPacket(packet, selected_packet_info)) { - int transit_time = static_cast((packet->received_time_ms * clock_rate) - head->getTimestamp()); - int delta = abs(transit_time - selected_packet_info->jitter.transit_time); - if (selected_packet_info->jitter.transit_time != 0 && delta < MAX_DELAY) { - selected_packet_info->jitter.jitter += - (1. / 16.) * (static_cast(delta) - selected_packet_info->jitter.jitter); - } - selected_packet_info->jitter.transit_time = transit_time; - } - selected_packet_info->last_rtp_ts = head->getTimestamp(); - selected_packet_info->last_recv_ts = static_cast(packet->received_time_ms); - uint64_t now = ClockUtils::timePointToMs(clock::now()); - if (selected_packet_info->next_packet_ms == 0) { // Schedule the first packet - uint16_t selected_interval = selectInterval(selected_packet_info); - selected_packet_info->next_packet_ms = now + selected_interval; - return; - } - - if (now >= selected_packet_info->next_packet_ms) { - sendRR(selected_packet_info); - } -} - -void RRGenerationHandler::sendRR(std::shared_ptr selected_packet_info) { - if (selected_packet_info->ssrc != 0) { - uint64_t now = ClockUtils::timePointToMs(clock::now()); - uint64_t delay_since_last_sr = selected_packet_info->last_sr_ts == 0 ? - 0 : (now - selected_packet_info->last_sr_ts) * 65536 / 1000; - RtcpHeader rtcp_head; - rtcp_head.setPacketType(RTCP_Receiver_PT); - rtcp_head.setSSRC(selected_packet_info->ssrc); - rtcp_head.setSourceSSRC(selected_packet_info->ssrc); - rtcp_head.setHighestSeqnum(selected_packet_info->extended_seq); - rtcp_head.setSeqnumCycles(selected_packet_info->cycle); - rtcp_head.setLostPackets(selected_packet_info->lost); - rtcp_head.setFractionLost(selected_packet_info->frac_lost); - rtcp_head.setJitter(static_cast(selected_packet_info->jitter.jitter)); - rtcp_head.setDelaySinceLastSr(static_cast(delay_since_last_sr)); - rtcp_head.setLastSr(selected_packet_info->last_sr_mid_ntp); - rtcp_head.setLength(7); - rtcp_head.setBlockCount(1); - int length = (rtcp_head.getLength() + 1) * 4; - - memcpy(packet_, reinterpret_cast(&rtcp_head), length); - getContext()->fireWrite(std::make_shared(0, reinterpret_cast(&packet_), length, OTHER_PACKET)); - selected_packet_info->last_rr_ts = now; - - ELOG_DEBUG("%s, message: Sending RR, ssrc: %u, type: %u lost: %u, frac: %u, cycle: %u, highseq: %u, jitter: %u, " - "dlsr: %u, lsr: %u", connection_->toLog(), selected_packet_info->ssrc, selected_packet_info->type, - rtcp_head.getLostPackets(), rtcp_head.getFractionLost(), rtcp_head.getSeqnumCycles(), - rtcp_head.getHighestSeqnum(), rtcp_head.getJitter(), rtcp_head.getDelaySinceLastSr(), - rtcp_head.getLastSr()); - - uint16_t selected_interval = selectInterval(selected_packet_info); - selected_packet_info->next_packet_ms = now + getRandomValue(0.5 * selected_interval, 1.5 * selected_interval); - } -} - - -void RRGenerationHandler::handleSR(std::shared_ptr packet) { - RtcpHeader *chead = reinterpret_cast(packet->data); - auto rr_packet_pair = rr_info_map_.find(chead->getSSRC()); - if (rr_packet_pair == rr_info_map_.end()) { - ELOG_DEBUG("%s message: handleRtpPacket ssrc not found, ssrc: %u", connection_->toLog(), chead->getSSRC()); - return; - } - std::shared_ptr selected_packet_info = rr_packet_pair->second; - - selected_packet_info->last_sr_mid_ntp = chead->get32MiddleNtp(); - selected_packet_info->last_sr_ts = packet->received_time_ms; - uint32_t expected = selected_packet_info->extended_seq - selected_packet_info->base_seq + 1; - selected_packet_info->lost = expected - selected_packet_info->packets_received; - - uint8_t fraction = 0; - uint32_t expected_interval = expected - selected_packet_info->expected_prior; - selected_packet_info->expected_prior = expected; - uint32_t received_interval = selected_packet_info->packets_received - selected_packet_info->received_prior; - - selected_packet_info->received_prior = selected_packet_info->packets_received; - uint32_t lost_interval = expected_interval - received_interval; - if (expected_interval != 0 && lost_interval > 0) { - fraction = (lost_interval << 8) / expected_interval; - } - - selected_packet_info->frac_lost = fraction; - if (!use_timing_) { - sendRR(selected_packet_info); - } -} - -void RRGenerationHandler::read(Context *ctx, std::shared_ptr packet) { - RtcpHeader *chead = reinterpret_cast(packet->data); - if (!chead->isRtcp() && enabled_) { - handleRtpPacket(packet); - } else if (chead->packettype == RTCP_Sender_PT && enabled_) { - handleSR(packet); - } - ctx->fireRead(packet); -} - -void RRGenerationHandler::write(Context *ctx, std::shared_ptr packet) { - ctx->fireWrite(packet); -} - -void RRGenerationHandler::notifyUpdate() { - if (initialized_) { - return; - } - - auto pipeline = getContext()->getPipelineShared(); - if (!pipeline) { - return; - } - - connection_ = pipeline->getService().get(); - if (!connection_) { - return; - } - std::vector video_ssrc_list = connection_->getVideoSourceSSRCList(); - std::for_each(video_ssrc_list.begin(), video_ssrc_list.end(), [this] (uint32_t video_ssrc){ - if (video_ssrc != 0) { - auto video_packets = std::make_shared(); - video_packets->ssrc = video_ssrc; - video_packets->type = VIDEO_PACKET; - rr_info_map_[video_ssrc] = video_packets; - ELOG_DEBUG("%s, message: Initialized video, ssrc: %u", connection_->toLog(), video_ssrc); - initialized_ = true; - } - }); - uint32_t audio_ssrc = connection_->getAudioSourceSSRC(); - if (audio_ssrc != 0) { - auto audio_packets = std::make_shared(); - audio_packets->ssrc = audio_ssrc; - audio_packets->type = AUDIO_PACKET; - rr_info_map_[audio_ssrc] = audio_packets; - initialized_ = true; - ELOG_DEBUG("%s, message: Initialized audio, ssrc: %u", connection_->toLog(), audio_ssrc); - } -} - -uint16_t RRGenerationHandler::selectInterval(std::shared_ptr packet_info) { - return (packet_info->type == VIDEO_PACKET ? RTCP_VIDEO_INTERVAL : RTCP_AUDIO_INTERVAL); -} - -uint16_t RRGenerationHandler::getRandomValue(uint16_t min, uint16_t max) { - std::uniform_int_distribution<> distr(min, max); - return std::round(distr(generator_)); -} - -} // namespace erizo diff --git a/erizo/src/erizo/rtp/RRGenerationHandler.h b/erizo/src/erizo/rtp/RRGenerationHandler.h deleted file mode 100644 index bec80ee7e8..0000000000 --- a/erizo/src/erizo/rtp/RRGenerationHandler.h +++ /dev/null @@ -1,84 +0,0 @@ -#ifndef ERIZO_SRC_ERIZO_RTP_RRGENERATIONHANDLER_H_ -#define ERIZO_SRC_ERIZO_RTP_RRGENERATIONHANDLER_H_ - -#include -#include -#include -#include - -#include "./logger.h" -#include "pipeline/Handler.h" - -#define MAX_DELAY 450000 - -namespace erizo { - -class WebRtcConnection; - - -class RRGenerationHandler: public Handler, public std::enable_shared_from_this { - DECLARE_LOGGER(); - - - public: - explicit RRGenerationHandler(bool use_timing = true); - - explicit RRGenerationHandler(const RRGenerationHandler&& handler); // NOLINT - - void enable() override; - void disable() override; - - std::string getName() override { - return "rr_generation"; - } - - void read(Context *ctx, std::shared_ptr packet) override; - void write(Context *ctx, std::shared_ptr packet) override; - void notifyUpdate() override; - - private: - struct Jitter; - struct RRPackets; - - bool rtpSequenceLessThan(uint16_t x, uint16_t y); - bool isRetransmitOfOldPacket(std::shared_ptr packet, std::shared_ptr rr_info); - void handleRtpPacket(std::shared_ptr packet); - void handleSR(std::shared_ptr packet); - int getAudioClockRate(uint8_t payload_type); - int getVideoClockRate(uint8_t payload_type); - void sendRR(std::shared_ptr selected_packet_info); - uint16_t selectInterval(std::shared_ptr packet_info); - uint16_t getRandomValue(uint16_t min, uint16_t max); - - private: - struct Jitter { - Jitter() : transit_time(0), jitter(0) {} - int transit_time; - double jitter; - }; - - struct RRPackets { - RRPackets() : last_sr_ts{0}, next_packet_ms{0}, ssrc{0}, last_sr_mid_ntp{0}, last_rr_ts{0}, - last_rtp_ts{0}, packets_received{0}, extended_seq{0}, lost{0}, expected_prior{0}, received_prior{0}, - last_recv_ts{0}, max_seq{-1}, base_seq{-1}, cycle{0}, frac_lost{0}, type{OTHER_PACKET} {} - uint64_t last_sr_ts, next_packet_ms; - uint32_t ssrc, last_sr_mid_ntp, last_rr_ts, last_rtp_ts, - packets_received, extended_seq, lost, expected_prior, received_prior, last_recv_ts; - int32_t max_seq, base_seq; // are really uint16_t, we're using the sign for unitialized values - uint16_t cycle; - uint8_t frac_lost; - Jitter jitter; - packetType type; - }; - - WebRtcConnection *connection_; - - uint8_t packet_[128]; - bool enabled_, initialized_, use_timing_; - std::map> rr_info_map_; - std::random_device random_device_; - std::mt19937 generator_; -}; -} // namespace erizo - -#endif // ERIZO_SRC_ERIZO_RTP_RRGENERATIONHANDLER_H_ diff --git a/erizo/src/erizo/rtp/RtcpFeedbackGenerationHandler.cpp b/erizo/src/erizo/rtp/RtcpFeedbackGenerationHandler.cpp new file mode 100644 index 0000000000..902244d501 --- /dev/null +++ b/erizo/src/erizo/rtp/RtcpFeedbackGenerationHandler.cpp @@ -0,0 +1,113 @@ +#include "rtp/RtcpFeedbackGenerationHandler.h" +#include "./WebRtcConnection.h" + +namespace erizo { + +DEFINE_LOGGER(RtcpFeedbackGenerationHandler, "rtp.RtcpFeedbackGenerationHandler"); + +RtcpFeedbackGenerationHandler::RtcpFeedbackGenerationHandler(bool nacks_enabled, + std::shared_ptr the_clock) + : connection_{nullptr}, enabled_{true}, initialized_{false}, nacks_enabled_{nacks_enabled}, clock_{the_clock} {} + +void RtcpFeedbackGenerationHandler::enable() { + enabled_ = true; +} + +void RtcpFeedbackGenerationHandler::disable() { + enabled_ = false; +} + +void RtcpFeedbackGenerationHandler::read(Context *ctx, std::shared_ptr packet) { + // Pass packets to RR and NACK Generator + RtcpHeader *chead = reinterpret_cast(packet->data); + + if (!initialized_) { + ctx->fireRead(packet); + return; + } + + if (chead->getPacketType() == RTCP_Sender_PT) { + uint32_t ssrc = chead->getSSRC(); + auto generator_it = generators_map_.find(ssrc); + if (generator_it != generators_map_.end()) { + generator_it->second->rr_generator->handleSr(packet); + } else { + ELOG_DEBUG("message: no RrGenerator found, ssrc: %u", ssrc); + } + ctx->fireRead(packet); + return; + } + bool should_send_rr = false; + bool should_send_nack = false; + + if (!chead->isRtcp()) { + RtpHeader *head = reinterpret_cast(packet->data); + uint32_t ssrc = head->getSSRC(); + auto generator_it = generators_map_.find(ssrc); + if (generator_it != generators_map_.end()) { + should_send_rr = generator_it->second->rr_generator->handleRtpPacket(packet); + if (nacks_enabled_) { + should_send_nack = generator_it->second->nack_generator->handleRtpPacket(packet); + } + } else { + ELOG_DEBUG("message: no Generator found, ssrc: %u", ssrc); + } + + if (should_send_rr || should_send_nack) { + ELOG_DEBUG("message: Should send Rtcp, ssrc %u", ssrc); + std::shared_ptr rtcp_packet = generator_it->second->rr_generator->generateReceiverReport(); + if (nacks_enabled_ && generator_it->second->nack_generator != nullptr) { + generator_it->second->nack_generator->addNackPacketToRr(rtcp_packet); + } + ctx->fireWrite(rtcp_packet); + } + } + ctx->fireRead(packet); +} + +void RtcpFeedbackGenerationHandler::write(Context *ctx, std::shared_ptr packet) { + ctx->fireWrite(packet); +} + +void RtcpFeedbackGenerationHandler::notifyUpdate() { + if (initialized_) { + return; + } + + auto pipeline = getContext()->getPipelineShared(); + if (!pipeline) { + return; + } + + connection_ = pipeline->getService().get(); + if (!connection_) { + return; + } + // TODO(pedro) detect if nacks are enabled here with the negotiated SDP scanning the rtp_mappings + std::vector video_ssrc_list = connection_->getVideoSourceSSRCList(); + std::for_each(video_ssrc_list.begin(), video_ssrc_list.end(), [this] (uint32_t video_ssrc) { + if (video_ssrc != 0) { + auto video_generator = std::make_shared(); + generators_map_[video_ssrc] = video_generator; + auto video_rr = std::make_shared(video_ssrc, VIDEO_PACKET, clock_); + video_generator->rr_generator = video_rr; + ELOG_DEBUG("%s, message: Initialized video rrGenerator, ssrc: %u", connection_->toLog(), video_ssrc); + if (nacks_enabled_) { + ELOG_DEBUG("%s, message: Initialized video nack generator, ssrc %u", connection_->toLog(), video_ssrc); + auto video_nack = std::make_shared(video_ssrc, clock_); + video_generator->nack_generator = video_nack; + } + } + }); + uint32_t audio_ssrc = connection_->getAudioSourceSSRC(); + if (audio_ssrc != 0) { + auto audio_generator = std::make_shared(); + generators_map_[audio_ssrc] = audio_generator; + auto audio_rr = std::make_shared(audio_ssrc, AUDIO_PACKET, clock_); + audio_generator->rr_generator = audio_rr; + ELOG_DEBUG("%s, message: Initialized audio, ssrc: %u", connection_->toLog(), audio_ssrc); + } + initialized_ = true; +} + +} // namespace erizo diff --git a/erizo/src/erizo/rtp/RtcpFeedbackGenerationHandler.h b/erizo/src/erizo/rtp/RtcpFeedbackGenerationHandler.h new file mode 100644 index 0000000000..88fcb036e3 --- /dev/null +++ b/erizo/src/erizo/rtp/RtcpFeedbackGenerationHandler.h @@ -0,0 +1,57 @@ +#ifndef ERIZO_SRC_ERIZO_RTP_RTCPFEEDBACKGENERATIONHANDLER_H_ +#define ERIZO_SRC_ERIZO_RTP_RTCPFEEDBACKGENERATIONHANDLER_H_ + +#include +#include +#include + +#include "./logger.h" +#include "pipeline/Handler.h" +#include "rtp/RtcpRrGenerator.h" +#include "rtp/RtcpNackGenerator.h" +#include "lib/ClockUtils.h" + +#define MAX_DELAY 450000 + +namespace erizo { + +class WebRtcConnection; + +class RtcpGeneratorPair { + public: + std::shared_ptr rr_generator; + std::shared_ptr nack_generator; +}; + + +class RtcpFeedbackGenerationHandler: public Handler { + DECLARE_LOGGER(); + + + public: + explicit RtcpFeedbackGenerationHandler(bool nacks_enabled = true, + std::shared_ptr the_clock = std::make_shared()); + + + void enable() override; + void disable() override; + + std::string getName() override { + return "rtcp_feedback_generation"; + } + + void read(Context *ctx, std::shared_ptr packet) override; + void write(Context *ctx, std::shared_ptr packet) override; + void notifyUpdate() override; + + private: + WebRtcConnection *connection_; + std::map> generators_map_; + + bool enabled_, initialized_; + bool nacks_enabled_; + std::shared_ptr clock_; +}; +} // namespace erizo + +#endif // ERIZO_SRC_ERIZO_RTP_RTCPFEEDBACKGENERATIONHANDLER_H_ diff --git a/erizo/src/erizo/rtp/RtcpNackGenerator.cpp b/erizo/src/erizo/rtp/RtcpNackGenerator.cpp new file mode 100644 index 0000000000..2741b6c156 --- /dev/null +++ b/erizo/src/erizo/rtp/RtcpNackGenerator.cpp @@ -0,0 +1,146 @@ +#include +#include "rtp/RtcpNackGenerator.h" +#include "rtp/RtpUtils.h" +#include "./WebRtcConnection.h" + +namespace erizo { + +DEFINE_LOGGER(RtcpNackGenerator, "rtp.RtcpNackGenerator"); + +static const int kMaxRetransmits = 2; +static const int kMaxNacks = 150; +static const int kMinNackDelayMs = 20; +static const int kNackCommonHeaderLengthRtcp = kNackCommonHeaderLengthBytes/4 - 1; + +RtcpNackGenerator::RtcpNackGenerator(uint32_t ssrc, std::shared_ptr the_clock) : + initialized_{false}, highest_seq_num_{0}, ssrc_{ssrc}, clock_{the_clock} {} + +bool RtcpNackGenerator::handleRtpPacket(std::shared_ptr packet) { + if (packet->type != VIDEO_PACKET) { + return false; + } + RtpHeader *head = reinterpret_cast(packet->data); + uint16_t seq_num = head->getSeqNumber(); + if (head->getSSRC() != ssrc_) { + ELOG_DEBUG("message: handleRtpPacket Unknown SSRC, ssrc: %u", head->getSSRC()); + return false; + } + if (!initialized_) { + highest_seq_num_ = seq_num; + initialized_ = true; + return 0; + } + if (seq_num == highest_seq_num_) { + return false; + } + // TODO(pedro) Consider clearing the nack list if this is a keyframe + if (RtpUtils::sequenceNumberLessThan(seq_num, highest_seq_num_)) { + ELOG_DEBUG("message: packet out of order, ssrc: %u, seq_num: %u, highest_seq_num: %u", + seq_num, highest_seq_num_, ssrc_); + // Look for it in nack list, remove it if its there + auto nack_info = std::find_if(nack_info_list_.begin(), nack_info_list_.end(), + [seq_num](NackInfo& current_nack) { + return (current_nack.seq_num == seq_num); + }); + if (nack_info != nack_info_list_.end()) { + ELOG_DEBUG("message: Recovered Packet %u", seq_num); + nack_info_list_.erase(nack_info); + } + return false; + } + bool available_nacks = addNacks(seq_num); + highest_seq_num_ = seq_num; + return available_nacks; +} + +bool RtcpNackGenerator::addNacks(uint16_t seq_num) { + for (uint16_t current_seq_num = highest_seq_num_ + 1; current_seq_num != seq_num; current_seq_num++) { + ELOG_DEBUG("message: Inserting a new Nack in list, ssrc: %u, seq_num: %u", ssrc_, current_seq_num); + nack_info_list_.push_back(NackInfo{current_seq_num}); + } + while (nack_info_list_.size() > kMaxNacks) { + nack_info_list_.erase(nack_info_list_.end() - 1); + } + return !nack_info_list_.empty(); +} + +bool RtcpNackGenerator::addNackPacketToRr(std::shared_ptr rr_packet) { + // Goes through the list adds blocks of 16 in compound packets (adds more PID/BLP blocks) max is 10 blocks + // Only does it if it's time (> 100 ms since last NACK) + std::vector nack_vector; + ELOG_DEBUG("message: Adding nacks to RR, nack_info_list_.size(): %lu", nack_info_list_.size()); + uint64_t now_ms = ClockUtils::timePointToMs(clock_->now()); + for (uint16_t index = 0; index < nack_info_list_.size(); index++) { + NackInfo& base_nack_info = nack_info_list_[index]; + if (!isTimeToRetransmit(base_nack_info, now_ms)) { + ELOG_DEBUG("It's not time to retransmit %lu, now %lu, diff %lu", base_nack_info.sent_time, now_ms, + now_ms - base_nack_info.sent_time); + continue; + } + if (base_nack_info.retransmits >= kMaxRetransmits) { + ELOG_DEBUG("message: Removing Nack in list too many retransmits, ssrc: %u, seq_num: %u", + ssrc_, base_nack_info.seq_num); + nack_info_list_.erase(nack_info_list_.begin() + index); + continue; + } + ELOG_DEBUG("message: PID, seq_num %u", base_nack_info.seq_num); + uint16_t pid = base_nack_info.seq_num; + uint16_t blp = 0; + base_nack_info.sent_time = now_ms; + base_nack_info.retransmits++; + while (index < nack_info_list_.size()) { + index++; + NackInfo& blp_nack_info = nack_info_list_[index]; + uint16_t distance = blp_nack_info.seq_num - pid -1; + if (distance <= 15) { + if (!isTimeToRetransmit(blp_nack_info, now_ms)) { + continue; + } + if (blp_nack_info.retransmits >= kMaxRetransmits) { + ELOG_DEBUG("message: Removing Nack in list too many retransmits, ssrc: %u, seq_num: %u", + ssrc_, blp_nack_info.seq_num); + nack_info_list_.erase(nack_info_list_.begin() + index); + continue; + } + ELOG_DEBUG("message: Adding Nack to BLP, seq_num: %u", blp_nack_info.seq_num); + blp |= (1 << distance); + blp_nack_info.sent_time = now_ms; + blp_nack_info.retransmits++; + } else { + break; + } + } + NackBlock block; + block.setNackPid(pid); + block.setNackBlp(blp); + nack_vector.push_back(block); + } + if (nack_vector.size() == 0) { + return false; + } + + char* buffer = rr_packet->data; + buffer += rr_packet->length; + + RtcpHeader nack_packet; + nack_packet.setPacketType(RTCP_RTP_Feedback_PT); + nack_packet.setBlockCount(1); + nack_packet.setSSRC(ssrc_); + nack_packet.setSourceSSRC(ssrc_); + nack_packet.setLength(kNackCommonHeaderLengthRtcp + nack_vector.size()); + memcpy(buffer, reinterpret_cast(&nack_packet), kNackCommonHeaderLengthBytes); + RtcpHeader* chead_test = reinterpret_cast (buffer); + buffer += kNackCommonHeaderLengthBytes; + + memcpy(buffer, &nack_vector[0], nack_vector.size()*4); + int nack_length = (nack_packet.getLength()+1)*4; + + rr_packet->length += nack_length; + return true; +} + +bool RtcpNackGenerator::isTimeToRetransmit(const NackInfo& nack_info, uint64_t current_time_ms) { + return (nack_info.sent_time == 0 || (current_time_ms - nack_info.sent_time) > kMinNackDelayMs); +} + +} // namespace erizo diff --git a/erizo/src/erizo/rtp/RtcpNackGenerator.h b/erizo/src/erizo/rtp/RtcpNackGenerator.h new file mode 100644 index 0000000000..10b59e9d57 --- /dev/null +++ b/erizo/src/erizo/rtp/RtcpNackGenerator.h @@ -0,0 +1,51 @@ +#ifndef ERIZO_SRC_ERIZO_RTP_RTCPNACKGENERATOR_H_ +#define ERIZO_SRC_ERIZO_RTP_RTCPNACKGENERATOR_H_ + +#include +#include +#include + +#include "./logger.h" +#include "pipeline/Handler.h" +#include "lib/ClockUtils.h" + +#define MAX_DELAY 450000 + +namespace erizo { + +class WebRtcConnection; + + +class NackInfo { + public: + NackInfo(): seq_num{0}, retransmits{0}, sent_time{0} {} + explicit NackInfo(uint16_t seq_num): seq_num{seq_num}, retransmits{0}, sent_time{0} {} + uint16_t seq_num; + uint16_t retransmits; + uint64_t sent_time; +}; + +class RtcpNackGenerator{ + DECLARE_LOGGER(); + + public: + explicit RtcpNackGenerator(uint32_t ssrc_, + std::shared_ptr the_clock = std::make_shared()); + bool handleRtpPacket(std::shared_ptr packet); + bool addNackPacketToRr(std::shared_ptr rr_packet); + + private: + bool addNacks(uint16_t seq_num); + bool isTimeToRetransmit(const NackInfo& nack_info, uint64_t current_time_ms); + + private: + bool initialized_; + uint16_t highest_seq_num_; + uint32_t ssrc_; + NackInfo nack_info_; + std::vector nack_info_list_; + std::shared_ptr clock_; +}; +} // namespace erizo + +#endif // ERIZO_SRC_ERIZO_RTP_RTCPNACKGENERATOR_H_ diff --git a/erizo/src/erizo/rtp/RtcpRrGenerator.cpp b/erizo/src/erizo/rtp/RtcpRrGenerator.cpp new file mode 100644 index 0000000000..3a952aca1a --- /dev/null +++ b/erizo/src/erizo/rtp/RtcpRrGenerator.cpp @@ -0,0 +1,166 @@ +#include "rtp/RtcpRrGenerator.h" +#include "./WebRtcConnection.h" +#include "lib/ClockUtils.h" +#include "rtp/RtpUtils.h" + +namespace erizo { + +DEFINE_LOGGER(RtcpRrGenerator, "rtp.RtcpRrGenerator"); + +RtcpRrGenerator::RtcpRrGenerator(uint32_t ssrc, packetType type, std::shared_ptr the_clock) + : rr_info_{RrPacketInfo(ssrc)}, ssrc_{ssrc}, type_{type}, + random_generator_{random_device_()}, clock_{the_clock} {} + +RtcpRrGenerator::RtcpRrGenerator(const RtcpRrGenerator&& generator) : // NOLINT + rr_info_{std::move(generator.rr_info_)}, + ssrc_{generator.ssrc_}, + type_{generator.type_} {} + +bool RtcpRrGenerator::isRetransmitOfOldPacket(std::shared_ptr packet) { + RtpHeader *head = reinterpret_cast(packet->data); + if (!RtpUtils::sequenceNumberLessThan(head->getSeqNumber(), rr_info_.max_seq) || rr_info_.jitter.jitter == 0) { + return false; + } + int64_t time_diff_ms = static_cast(packet->received_time_ms) - rr_info_.last_recv_ts; + int64_t timestamp_diff = static_cast(head->getTimestamp() - rr_info_.last_rtp_ts); + uint16_t clock_rate = type_ == VIDEO_PACKET ? getVideoClockRate(head->getPayloadType()) : + getAudioClockRate(head->getPayloadType()); + int64_t rtp_time_stamp_diff_ms = timestamp_diff / clock_rate; + int64_t max_delay_ms = ((2 * rr_info_.jitter.jitter) / clock_rate); + return time_diff_ms > rtp_time_stamp_diff_ms + max_delay_ms; +} + +// TODO(kekkokk) Consider add more payload types +int RtcpRrGenerator::getAudioClockRate(uint8_t payload_type) { + if (payload_type == OPUS_48000_PT) { + return 48; + } else { + return 8; + } +} + +// TODO(kekkokk) ATM we only use 90Khz video type so always return 90 +int RtcpRrGenerator::getVideoClockRate(uint8_t payload_type) { + return 90; +} + +bool RtcpRrGenerator::handleRtpPacket(std::shared_ptr packet) { + RtpHeader *head = reinterpret_cast(packet->data); + if (ssrc_ != head->getSSRC()) { + ELOG_DEBUG("message: handleRtpPacket ssrc not found, ssrc: %u", head->getSSRC()); + return false; + } + uint16_t seq_num = head->getSeqNumber(); + rr_info_.packets_received++; + if (rr_info_.base_seq == -1) { + rr_info_.base_seq = head->getSeqNumber(); + } + if (rr_info_.max_seq == -1) { + rr_info_.max_seq = seq_num; + } else if (!RtpUtils::sequenceNumberLessThan(seq_num, rr_info_.max_seq)) { + if (seq_num < rr_info_.max_seq) { + rr_info_.cycle++; + } + rr_info_.max_seq = seq_num; + } + rr_info_.extended_seq = (rr_info_.cycle << 16) | rr_info_.max_seq; + + uint16_t clock_rate = type_ == VIDEO_PACKET ? getVideoClockRate(head->getPayloadType()) : + getAudioClockRate(head->getPayloadType()); + if (head->getTimestamp() != rr_info_.last_rtp_ts && + !isRetransmitOfOldPacket(packet)) { + int transit_time = static_cast((packet->received_time_ms * clock_rate) - head->getTimestamp()); + int delta = abs(transit_time - rr_info_.jitter.transit_time); + if (rr_info_.jitter.transit_time != 0 && delta < MAX_DELAY) { + rr_info_.jitter.jitter += + (1. / 16.) * (static_cast(delta) - rr_info_.jitter.jitter); + } + rr_info_.jitter.transit_time = transit_time; + } + rr_info_.last_rtp_ts = head->getTimestamp(); + rr_info_.last_recv_ts = static_cast(packet->received_time_ms); + uint64_t now = ClockUtils::timePointToMs(clock_->now()); + if (rr_info_.next_packet_ms == 0) { // Schedule the first packet + uint16_t selected_interval = selectInterval(); + rr_info_.next_packet_ms = now + selected_interval; + return false; + } + + if (now >= rr_info_.next_packet_ms) { + ELOG_DEBUG("message: should send packet, ssrc: %u", ssrc_); + return true; + } + return false; +} + +std::shared_ptr RtcpRrGenerator::generateReceiverReport() { + uint64_t now = ClockUtils::timePointToMs(clock_->now()); + uint64_t delay_since_last_sr = rr_info_.last_sr_ts == 0 ? + 0 : (now - rr_info_.last_sr_ts) * 65536 / 1000; + uint32_t expected = rr_info_.extended_seq - rr_info_.base_seq + 1; + rr_info_.lost = expected - rr_info_.packets_received; + + uint8_t fraction = 0; + uint32_t expected_interval = expected - rr_info_.expected_prior; + rr_info_.expected_prior = expected; + uint32_t received_interval = rr_info_.packets_received - rr_info_.received_prior; + + rr_info_.received_prior = rr_info_.packets_received; + uint32_t lost_interval = expected_interval - received_interval; + if (expected_interval != 0 && lost_interval > 0) { + fraction = (lost_interval << 8) / expected_interval; + } + + rr_info_.frac_lost = fraction; + RtcpHeader rtcp_head; + rtcp_head.setPacketType(RTCP_Receiver_PT); + rtcp_head.setSSRC(ssrc_); + rtcp_head.setSourceSSRC(ssrc_); + rtcp_head.setHighestSeqnum(rr_info_.extended_seq); + rtcp_head.setSeqnumCycles(rr_info_.cycle); + rtcp_head.setLostPackets(rr_info_.lost); + rtcp_head.setFractionLost(rr_info_.frac_lost); + rtcp_head.setJitter(static_cast(rr_info_.jitter.jitter)); + rtcp_head.setDelaySinceLastSr(static_cast(delay_since_last_sr)); + rtcp_head.setLastSr(rr_info_.last_sr_mid_ntp); + rtcp_head.setLength(7); + rtcp_head.setBlockCount(1); + int length = (rtcp_head.getLength() + 1) * 4; + + memcpy(packet_, reinterpret_cast(&rtcp_head), length); + rr_info_.last_rr_ts = now; + + ELOG_DEBUG("message: Sending RR, ssrc: %u, type: %u lost: %u, frac: %u, cycle: %u, highseq: %u, jitter: %u, " + "dlsr: %u, lsr: %u", ssrc_, type_, + rtcp_head.getLostPackets(), rtcp_head.getFractionLost(), rtcp_head.getSeqnumCycles(), + rtcp_head.getHighestSeqnum(), rtcp_head.getJitter(), rtcp_head.getDelaySinceLastSr(), + rtcp_head.getLastSr()); + + uint16_t selected_interval = selectInterval(); + rr_info_.next_packet_ms = now + getRandomValue(0.5 * selected_interval, 1.5 * selected_interval); + rr_info_.last_packet_ms = now; + return (std::make_shared(0, reinterpret_cast(&packet_), length, type_)); +} + + +void RtcpRrGenerator::handleSr(std::shared_ptr packet) { + RtcpHeader* chead = reinterpret_cast(packet->data); + if (ssrc_ != chead->getSSRC()) { + ELOG_DEBUG("message: handleRtpPacket ssrc not found, ssrc: %u", chead->getSSRC()); + return; + } + + rr_info_.last_sr_mid_ntp = chead->get32MiddleNtp(); + rr_info_.last_sr_ts = packet->received_time_ms; +} + +uint16_t RtcpRrGenerator::selectInterval() { + return (type_ == VIDEO_PACKET ? RTCP_VIDEO_INTERVAL : RTCP_AUDIO_INTERVAL); +} + +uint16_t RtcpRrGenerator::getRandomValue(uint16_t min, uint16_t max) { + std::uniform_int_distribution<> distr(min, max); + return std::round(distr(random_generator_)); +} + +} // namespace erizo diff --git a/erizo/src/erizo/rtp/RtcpRrGenerator.h b/erizo/src/erizo/rtp/RtcpRrGenerator.h new file mode 100644 index 0000000000..cf6b087b50 --- /dev/null +++ b/erizo/src/erizo/rtp/RtcpRrGenerator.h @@ -0,0 +1,77 @@ +#ifndef ERIZO_SRC_ERIZO_RTP_RTCPRRGENERATOR_H_ +#define ERIZO_SRC_ERIZO_RTP_RTCPRRGENERATOR_H_ + +#include +#include +#include +#include + +#include "./logger.h" +#include "pipeline/Handler.h" +#include "lib/ClockUtils.h" + +#define MAX_DELAY 450000 + +namespace erizo { + +class WebRtcConnection; + + +class RtcpRrGenerator { + DECLARE_LOGGER(); + + + public: + explicit RtcpRrGenerator(uint32_t ssrc, packetType type, + std::shared_ptr the_clock = std::make_shared()); + + explicit RtcpRrGenerator(const RtcpRrGenerator&& handler); // NOLINT + bool handleRtpPacket(std::shared_ptr packet); + void handleSr(std::shared_ptr packet); + std::shared_ptr generateReceiverReport(); + + private: + bool isRetransmitOfOldPacket(std::shared_ptr packet); + int getAudioClockRate(uint8_t payload_type); + int getVideoClockRate(uint8_t payload_type); + uint16_t selectInterval(); + uint16_t getRandomValue(uint16_t min, uint16_t max); + + private: + class Jitter { + public: + Jitter() : transit_time(0), jitter(0) {} + int transit_time; + double jitter; + }; + + class RrPacketInfo { + public: + RrPacketInfo() : last_sr_ts{0}, next_packet_ms{0}, last_packet_ms{0}, ssrc{0}, last_sr_mid_ntp{0}, last_rr_ts{0}, + last_rtp_ts{0}, packets_received{0}, extended_seq{0}, lost{0}, expected_prior{0}, received_prior{0}, + last_recv_ts{0}, max_seq{-1}, base_seq{-1}, cycle{0}, frac_lost{0} {} + explicit RrPacketInfo(uint32_t rr_ssrc) : last_sr_ts{0}, next_packet_ms{0}, last_packet_ms{0}, ssrc{rr_ssrc}, + last_sr_mid_ntp{0}, last_rr_ts{0}, last_rtp_ts{0}, packets_received{0}, extended_seq{0}, lost{0}, + expected_prior{0}, received_prior{0}, last_recv_ts{0}, max_seq{-1}, base_seq{-1}, cycle{0}, + frac_lost{0} {} + uint64_t last_sr_ts, next_packet_ms, last_packet_ms; + uint32_t ssrc, last_sr_mid_ntp, last_rr_ts, last_rtp_ts, + packets_received, extended_seq, lost, expected_prior, received_prior, last_recv_ts; + int32_t max_seq, base_seq; // are really uint16_t, we're using the sign for unitialized values + uint16_t cycle; + uint8_t frac_lost; + Jitter jitter; + }; + + uint8_t packet_[128]; + bool enabled_, initialized_; + RrPacketInfo rr_info_; + uint32_t ssrc_; + packetType type_; + std::random_device random_device_; + std::mt19937 random_generator_; + std::shared_ptr clock_; +}; +} // namespace erizo + +#endif // ERIZO_SRC_ERIZO_RTP_RTCPRRGENERATOR_H_ diff --git a/erizo/src/erizo/rtp/RtpHeaders.h b/erizo/src/erizo/rtp/RtpHeaders.h index a2d76bfa38..43490d7b28 100644 --- a/erizo/src/erizo/rtp/RtpHeaders.h +++ b/erizo/src/erizo/rtp/RtpHeaders.h @@ -42,6 +42,8 @@ namespace erizo { #define RTCP_AUDIO_INTERVAL 5000 #define RTCP_VIDEO_INTERVAL 1000 + +static const uint16_t kNackCommonHeaderLengthBytes = 12; // 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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -200,6 +202,33 @@ class RtpRtxHeader { } }; + +// Generic NACK RTCP_RTP_FB + (FMT 1)rfc4585 +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | PID | BLP | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +class NackBlock { + public: + uint32_t pid:16; + uint32_t blp:16; + + inline uint16_t getNackPid() { + return ntohs(pid); + } + inline void setNackPid(uint16_t new_pid) { + pid = htons(new_pid); + } + inline uint16_t getNackBlp() { + return ntohs(blp); + } + inline void setNackBlp(uint16_t new_blp) { + blp = htons(new_blp); + } +}; + // 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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -304,16 +333,9 @@ class RtcpHeader { struct receiverReport_t rrlist[1]; } senderReport; - // Generic NACK RTCP_RTP_FB + (FMT 1)rfc4585 - // 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 - // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - // | PID | BLP | - // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ struct genericNack_t { uint32_t ssrcsource; - uint32_t pid:16; - uint32_t blp:16; + NackBlock nack_block; } nackPacket; struct remb_t { @@ -436,16 +458,16 @@ class RtcpHeader { return ntohl(middle); } inline uint16_t getNackPid() { - return ntohs(report.nackPacket.pid); + return report.nackPacket.nack_block.getNackPid(); } inline void setNackPid(uint16_t pid) { - report.nackPacket.pid = htons(pid); + report.nackPacket.nack_block.setNackPid(pid); } inline uint16_t getNackBlp() { - return report.nackPacket.blp; + return report.nackPacket.nack_block.getNackBlp(); } inline void setNackBlp(uint16_t blp) { - report.nackPacket.blp = blp; + report.nackPacket.nack_block.setNackBlp(blp); } inline void setREMBBitRate(uint64_t bitRate) { uint64_t max = 0x3FFFF; // 18 bits diff --git a/erizo/src/erizo/rtp/RtpRetransmissionHandler.cpp b/erizo/src/erizo/rtp/RtpRetransmissionHandler.cpp index 0e4c322ba2..43eee21280 100644 --- a/erizo/src/erizo/rtp/RtpRetransmissionHandler.cpp +++ b/erizo/src/erizo/rtp/RtpRetransmissionHandler.cpp @@ -1,4 +1,5 @@ #include "rtp/RtpRetransmissionHandler.h" +#include "rtp/RtpUtils.h" namespace erizo { @@ -41,14 +42,16 @@ void RtpRetransmissionHandler::read(Context *ctx, std::shared_ptr pa if (chead->packettype == RTCP_RTP_Feedback_PT) { contains_nack = true; - uint16_t initial_seq_num = chead->getNackPid(); - uint16_t plb = chead->getNackBlp(); - for (int i = -1; i <= kNackBlpSize; i++) { - uint16_t seq_num = initial_seq_num + i + 1; - bool packet_nacked = i == -1 || (plb >> i) & 0x0001; + RtpUtils::forEachNack(chead, [this, chead, &is_fully_recovered](uint16_t new_seq_num, uint16_t new_plb) { + uint16_t initial_seq_num = new_seq_num; + uint16_t plb = new_plb; - if (packet_nacked) { + for (int i = -1; i <= kNackBlpSize; i++) { + uint16_t seq_num = initial_seq_num + i + 1; + bool packet_nacked = i == -1 || (plb >> i) & 0x0001; + + if (packet_nacked) { std::shared_ptr recovered; if (connection_->getVideoSinkSSRC() == chead->getSourceSSRC()) { @@ -60,15 +63,16 @@ void RtpRetransmissionHandler::read(Context *ctx, std::shared_ptr pa if (recovered.get()) { RtpHeader *recovered_head = reinterpret_cast (recovered->data); if (recovered_head->getSeqNumber() == seq_num) { - ctx->fireWrite(recovered); + getContext()->fireWrite(recovered); continue; } } ELOG_DEBUG("Packet missed in buffer %d", seq_num); is_fully_recovered = false; break; + } } - } + }); } } while (total_length < packet->length); } diff --git a/erizo/src/erizo/rtp/RtpUtils.cpp b/erizo/src/erizo/rtp/RtpUtils.cpp index 88c3393291..3b9fe2b690 100644 --- a/erizo/src/erizo/rtp/RtpUtils.cpp +++ b/erizo/src/erizo/rtp/RtpUtils.cpp @@ -20,9 +20,18 @@ void RtpUtils::updateREMB(RtcpHeader *chead, uint bitrate) { void RtpUtils::forEachNack(RtcpHeader *chead, std::function f) { if (chead->packettype == RTCP_RTP_Feedback_PT) { - uint16_t initial_seq_num = chead->getNackPid(); - uint16_t plb = chead->getNackBlp(); - f(initial_seq_num, plb); + int length = (chead->getLength() + 1)*4; + int current_position = kNackCommonHeaderLengthBytes; + uint8_t* aux_pointer = reinterpret_cast(chead); + RtcpHeader* aux_chead; + while (current_position < length) { + aux_chead = reinterpret_cast(aux_pointer); + uint16_t initial_seq_num = aux_chead->getNackPid(); + uint16_t plb = aux_chead->getNackBlp(); + f(initial_seq_num, plb); + current_position += 4; + aux_pointer += 4; + } } } diff --git a/erizo/src/test/log4cxx.properties b/erizo/src/test/log4cxx.properties index a67cf58355..03c036911f 100644 --- a/erizo/src/test/log4cxx.properties +++ b/erizo/src/test/log4cxx.properties @@ -50,7 +50,9 @@ log4j.logger.rtp.RtpVP8SlideShowHandler=ERROR log4j.logger.rtp.RtpAudioMuteHandler=ERROR log4j.logger.rtp.RtpSink=ERROR log4j.logger.rtp.RtpSource=ERROR -log4j.logger.rtp.RRGenerationHandler=ERROR +log4j.logger.rtp.RtcpFeedbackGenerationHandler=ERROR +log4j.logger.rtp.RtcpRrGenerator=ERROR +log4j.logger.rtp.RtcpNackGenerator=ERROR log4j.logger.rtp.SRPacketHandler=ERROR log4j.logger.rtp.SenderBandwidthEstimationHandler=ERROR log4j.logger.rtp.StatsCalculator=ERROR diff --git a/erizo/src/test/rtp/RRGenerationHandlerTest.cpp b/erizo/src/test/rtp/RRGenerationHandlerTest.cpp deleted file mode 100644 index 83509ea510..0000000000 --- a/erizo/src/test/rtp/RRGenerationHandlerTest.cpp +++ /dev/null @@ -1,132 +0,0 @@ -#include -#include - -#include -#include -#include -#include -#include - -#include -#include - -#include "../utils/Mocks.h" -#include "../utils/Tools.h" -#include "../utils/Matchers.h" - -using ::testing::_; -using ::testing::IsNull; -using ::testing::Args; -using ::testing::Return; -using ::testing::AllOf; -using erizo::dataPacket; -using erizo::packetType; -using erizo::AUDIO_PACKET; -using erizo::VIDEO_PACKET; -using erizo::IceConfig; -using erizo::RtpMap; -using erizo::RRGenerationHandler; -using erizo::WebRtcConnection; -using erizo::Pipeline; -using erizo::InboundHandler; -using erizo::OutboundHandler; -using erizo::Worker; - -class RRGenerationHandlerTest : public erizo::HandlerTest { - public: - RRGenerationHandlerTest() {} - - protected: - void setHandler() { - rr_handler = std::make_shared(false); - pipeline->addBack(rr_handler); - } - - std::shared_ptr rr_handler; -}; - -TEST_F(RRGenerationHandlerTest, basicBehaviourShouldReadPackets) { - auto packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber, VIDEO_PACKET); - - EXPECT_CALL(*reader.get(), read(_, _)). - With(Args<1>(erizo::RtpHasSequenceNumber(erizo::kArbitrarySeqNumber))).Times(1); - pipeline->read(packet); -} - -TEST_F(RRGenerationHandlerTest, basicBehaviourShouldWritePackets) { - auto packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber, VIDEO_PACKET); - - EXPECT_CALL(*writer.get(), write(_, _)). - With(Args<1>(erizo::RtpHasSequenceNumber(erizo::kArbitrarySeqNumber))).Times(1); - pipeline->write(packet); -} - -TEST_F(RRGenerationHandlerTest, shouldReportPacketLoss) { - uint16_t kArbitraryPacketsLost = 8; - auto first_packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber, VIDEO_PACKET); - auto second_packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber + kArbitraryPacketsLost + 1, - VIDEO_PACKET); - auto sender_report = erizo::PacketTools::createSenderReport(erizo::kVideoSsrc, VIDEO_PACKET); - - EXPECT_CALL(*reader.get(), read(_, _)).Times(3); - EXPECT_CALL(*writer.get(), write(_, _)) - .With(Args<1>(erizo::ReceiverReportHasLostPacketsValue(kArbitraryPacketsLost))) - .Times(1); - - pipeline->read(first_packet); - pipeline->read(second_packet); - pipeline->read(sender_report); -} - -TEST_F(RRGenerationHandlerTest, shouldReportFractionLost) { - uint16_t kArbitraryPacketsLost = 2; - uint16_t kPercentagePacketLoss = 50; - uint8_t kFractionLost = kPercentagePacketLoss * 256/100; - auto first_packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber, VIDEO_PACKET); - auto second_packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber + kArbitraryPacketsLost + 1, - VIDEO_PACKET); - auto sender_report = erizo::PacketTools::createSenderReport(erizo::kVideoSsrc, VIDEO_PACKET); - - EXPECT_CALL(*reader.get(), read(_, _)).Times(3); - EXPECT_CALL(*writer.get(), write(_, _)) - .With(Args<1>(erizo::ReceiverReportHasFractionLostValue(kFractionLost))) - .Times(1); - - pipeline->read(first_packet); - pipeline->read(second_packet); - pipeline->read(sender_report); -} - -TEST_F(RRGenerationHandlerTest, shouldReportHighestSeqnum) { - auto first_packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber, VIDEO_PACKET); - auto sender_report = erizo::PacketTools::createSenderReport(erizo::kVideoSsrc, VIDEO_PACKET); - - EXPECT_CALL(*reader.get(), read(_, _)).Times(2); - EXPECT_CALL(*writer.get(), write(_, _)) - .With(Args<1>(erizo::ReceiverReportHasSequenceNumber(erizo::kArbitrarySeqNumber))) - .Times(1); - - pipeline->read(first_packet); - pipeline->read(sender_report); -} - -TEST_F(RRGenerationHandlerTest, shouldReportHighestSeqnumWithRollover) { - uint16_t kMaxSeqnum = 65534; - uint16_t kArbitraryNumberOfPackets = 4; - uint16_t kSeqnumCyclesExpected = 1; - uint16_t kNewSeqNum = kMaxSeqnum + kArbitraryNumberOfPackets; - - auto first_packet = erizo::PacketTools::createDataPacket(kMaxSeqnum, VIDEO_PACKET); - auto second_packet = erizo::PacketTools::createDataPacket(kMaxSeqnum + kArbitraryNumberOfPackets, VIDEO_PACKET); - auto sender_report = erizo::PacketTools::createSenderReport(erizo::kVideoSsrc, VIDEO_PACKET); - - EXPECT_CALL(*reader.get(), read(_, _)).Times(3); - EXPECT_CALL(*writer.get(), write(_, _)) - .With(Args<1>(AllOf(erizo::ReceiverReportHasSeqnumCycles(kSeqnumCyclesExpected), - erizo::ReceiverReportHasSequenceNumber(kNewSeqNum)))) - .Times(1); - - pipeline->read(first_packet); - pipeline->read(second_packet); - pipeline->read(sender_report); -} diff --git a/erizo/src/test/rtp/RtcpFeedbackGenerationHandlerTest.cpp b/erizo/src/test/rtp/RtcpFeedbackGenerationHandlerTest.cpp new file mode 100644 index 0000000000..47498c1804 --- /dev/null +++ b/erizo/src/test/rtp/RtcpFeedbackGenerationHandlerTest.cpp @@ -0,0 +1,69 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../utils/Mocks.h" +#include "../utils/Tools.h" +#include "../utils/Matchers.h" + +using ::testing::_; +using ::testing::IsNull; +using ::testing::Args; +using ::testing::Return; +using ::testing::AllOf; +using erizo::dataPacket; +using erizo::packetType; +using erizo::AUDIO_PACKET; +using erizo::VIDEO_PACKET; +using erizo::IceConfig; +using erizo::RtpMap; +using erizo::RtcpFeedbackGenerationHandler; +using erizo::WebRtcConnection; +using erizo::Pipeline; +using erizo::InboundHandler; +using erizo::OutboundHandler; +using erizo::Worker; +using erizo::SimulatedClock; + +class RtcpFeedbackRrGenerationTest : public erizo::HandlerTest { + public: + RtcpFeedbackRrGenerationTest(): clock{std::make_shared()} {} + + protected: + void setHandler() { + rr_handler = std::make_shared(false, clock); + pipeline->addBack(rr_handler); + } + + void advanceClockMs(int time_ms) { + clock->advanceTime(std::chrono::milliseconds(time_ms)); + } + + std::shared_ptr rr_handler; + std::shared_ptr clock; +}; + +TEST_F(RtcpFeedbackRrGenerationTest, basicBehaviourShouldReadPackets) { + auto packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber, VIDEO_PACKET); + + EXPECT_CALL(*reader.get(), read(_, _)). + With(Args<1>(erizo::RtpHasSequenceNumber(erizo::kArbitrarySeqNumber))).Times(1); + pipeline->read(packet); +} + +TEST_F(RtcpFeedbackRrGenerationTest, basicBehaviourShouldWritePackets) { + auto packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber, VIDEO_PACKET); + + EXPECT_CALL(*writer.get(), write(_, _)). + With(Args<1>(erizo::RtpHasSequenceNumber(erizo::kArbitrarySeqNumber))).Times(1); + pipeline->write(packet); +} diff --git a/erizo/src/test/rtp/RtcpNackGeneratorTest.cpp b/erizo/src/test/rtp/RtcpNackGeneratorTest.cpp new file mode 100644 index 0000000000..5a0256cbbe --- /dev/null +++ b/erizo/src/test/rtp/RtcpNackGeneratorTest.cpp @@ -0,0 +1,204 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../utils/Mocks.h" +#include "../utils/Tools.h" +#include "../utils/Matchers.h" + +using ::testing::_; +using ::testing::IsNull; +using ::testing::Args; +using ::testing::Return; +using ::testing::AllOf; +using erizo::dataPacket; +using erizo::packetType; +using erizo::AUDIO_PACKET; +using erizo::VIDEO_PACKET; +using erizo::RtcpNackGenerator; +using erizo::RtcpHeader; +using erizo::WebRtcConnection; +using erizo::SimulatedClock; + +class RtcpNackGeneratorTest :public ::testing::Test { + public: + RtcpNackGeneratorTest(): clock{std::make_shared()}, nack_generator{erizo::kVideoSsrc, + clock} {} + + protected: + void advanceClockMs(int time_ms) { + clock->advanceTime(std::chrono::milliseconds(time_ms)); + } + + std::shared_ptr generateRrWithNack() { + std::shared_ptr new_receiver_report = erizo::PacketTools::createReceiverReport(erizo::kVideoSsrc, + erizo::kVideoSsrc, 0, VIDEO_PACKET); + nack_generator.addNackPacketToRr(new_receiver_report); + return new_receiver_report; + } + + bool RtcpPacketContainsNackSeqNum(std::shared_ptr rtcp_packet, uint16_t lost_seq_num) { + char* moving_buf = rtcp_packet->data; + int rtcp_length = 0; + int total_length = 0; + bool found_nack = false; + do { + moving_buf += rtcp_length; + RtcpHeader* chead = reinterpret_cast(moving_buf); + rtcp_length = (ntohs(chead->length) + 1) * 4; + total_length += rtcp_length; + + if (chead->packettype == RTCP_RTP_Feedback_PT) { + erizo::RtpUtils::forEachNack(chead, [chead, lost_seq_num, &found_nack](uint16_t seq_num, uint16_t plb) { + uint16_t initial_seq_num = seq_num; + if (initial_seq_num == lost_seq_num) { + found_nack = true; + return; + } + uint16_t kNackBlpSize = 16; + for (int i = -1; i <= kNackBlpSize; i++) { + if ((plb >> i) & 0x0001) { + uint16_t seq_num = initial_seq_num + i + 1; + if (seq_num == lost_seq_num) { + found_nack = true; + return; + } + } + } + }); + } + } while (total_length < rtcp_packet->length); + return found_nack; + } + + std::shared_ptr clock; + std::shared_ptr receiver_report; + RtcpNackGenerator nack_generator; + const uint16_t kMaxSeqnum = 65534; +}; + +TEST_F(RtcpNackGeneratorTest, shouldNotGenerateNackForConsecutivePackets) { + auto first_packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber, VIDEO_PACKET); + auto second_packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber + 1, + VIDEO_PACKET); + nack_generator.handleRtpPacket(first_packet); + EXPECT_FALSE(nack_generator.handleRtpPacket(second_packet)); +} + +TEST_F(RtcpNackGeneratorTest, shouldNotGenerateNackForConsecutivePacketsWithRollOver) { + auto first_packet = erizo::PacketTools::createDataPacket(kMaxSeqnum, VIDEO_PACKET); + auto second_packet = erizo::PacketTools::createDataPacket(kMaxSeqnum + 1, + VIDEO_PACKET); + nack_generator.handleRtpPacket(first_packet); + EXPECT_FALSE(nack_generator.handleRtpPacket(second_packet)); +} + +TEST_F(RtcpNackGeneratorTest, shouldGenerateNackOnLostPacket) { + uint16_t kArbitraryNumberOfPackets = 4; + auto first_packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber, VIDEO_PACKET); + auto second_packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber + kArbitraryNumberOfPackets, + VIDEO_PACKET); + nack_generator.handleRtpPacket(first_packet); + EXPECT_TRUE(nack_generator.handleRtpPacket(second_packet)); +} + +TEST_F(RtcpNackGeneratorTest, shouldGenerateNackOnLostPacketWithRollOver) { + uint16_t kArbitraryNumberOfPackets = 4; + auto first_packet = erizo::PacketTools::createDataPacket(kMaxSeqnum, VIDEO_PACKET); + auto second_packet = erizo::PacketTools::createDataPacket(kMaxSeqnum + kArbitraryNumberOfPackets, + VIDEO_PACKET); + nack_generator.handleRtpPacket(first_packet); + EXPECT_TRUE(nack_generator.handleRtpPacket(second_packet)); +} + +TEST_F(RtcpNackGeneratorTest, nackShouldContainLostPackets) { + uint16_t kArbitraryNumberOfPackets = 4; + auto first_packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber, VIDEO_PACKET); + auto second_packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber + kArbitraryNumberOfPackets, + VIDEO_PACKET); + nack_generator.handleRtpPacket(first_packet); + + EXPECT_TRUE(nack_generator.handleRtpPacket(second_packet)); + + receiver_report = generateRrWithNack(); + EXPECT_TRUE(RtcpPacketContainsNackSeqNum(receiver_report, erizo::kArbitrarySeqNumber + 1)); +} + +TEST_F(RtcpNackGeneratorTest, nackShouldContainLostPacketsInMoreThanOneBlock) { + uint16_t kArbitraryNumberOfPackets = 30; // Bigger than 16 - one block + auto first_packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber, VIDEO_PACKET); + auto second_packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber + kArbitraryNumberOfPackets, + VIDEO_PACKET); + nack_generator.handleRtpPacket(first_packet); + + EXPECT_TRUE(nack_generator.handleRtpPacket(second_packet)); + + receiver_report = generateRrWithNack(); + EXPECT_TRUE(RtcpPacketContainsNackSeqNum(receiver_report, 35)); +} + +TEST_F(RtcpNackGeneratorTest, shouldNotRetransmitNacksInmediately) { + uint16_t kArbitraryNumberOfPackets = 4; + auto first_packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber, VIDEO_PACKET); + auto second_packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber + kArbitraryNumberOfPackets, + VIDEO_PACKET); + nack_generator.handleRtpPacket(first_packet); + nack_generator.handleRtpPacket(second_packet); + + receiver_report = generateRrWithNack(); + + EXPECT_TRUE(RtcpPacketContainsNackSeqNum(receiver_report, erizo::kArbitrarySeqNumber + 1)); + + receiver_report = generateRrWithNack(); + + EXPECT_FALSE(RtcpPacketContainsNackSeqNum(receiver_report, erizo::kArbitrarySeqNumber + 1)); +} + +TEST_F(RtcpNackGeneratorTest, shouldRetransmitNacksAfterTime) { + uint16_t kArbitraryNumberOfPackets = 4; + auto first_packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber, VIDEO_PACKET); + auto second_packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber + kArbitraryNumberOfPackets, + VIDEO_PACKET); + nack_generator.handleRtpPacket(first_packet); + nack_generator.handleRtpPacket(second_packet); + + receiver_report = generateRrWithNack(); + + EXPECT_TRUE(RtcpPacketContainsNackSeqNum(receiver_report, erizo::kArbitrarySeqNumber + 1)); + + advanceClockMs(100); + receiver_report = generateRrWithNack(); + + EXPECT_TRUE(RtcpPacketContainsNackSeqNum(receiver_report, erizo::kArbitrarySeqNumber + 1)); +} + +TEST_F(RtcpNackGeneratorTest, shouldNotRetransmitNacksMoreThanTwice) { + uint16_t kArbitraryNumberOfPackets = 4; + auto first_packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber, VIDEO_PACKET); + auto second_packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber + kArbitraryNumberOfPackets, + VIDEO_PACKET); + nack_generator.handleRtpPacket(first_packet); + nack_generator.handleRtpPacket(second_packet); + + receiver_report = generateRrWithNack(); + + EXPECT_TRUE(RtcpPacketContainsNackSeqNum(receiver_report, erizo::kArbitrarySeqNumber + 1)); + + advanceClockMs(100); + receiver_report = generateRrWithNack(); + EXPECT_TRUE(RtcpPacketContainsNackSeqNum(receiver_report, erizo::kArbitrarySeqNumber + 1)); + + advanceClockMs(100); + receiver_report = generateRrWithNack(); + EXPECT_FALSE(RtcpPacketContainsNackSeqNum(receiver_report, erizo::kArbitrarySeqNumber + 1)); +} + diff --git a/erizo/src/test/rtp/RtcpRrGeneratorTest.cpp b/erizo/src/test/rtp/RtcpRrGeneratorTest.cpp new file mode 100644 index 0000000000..aa8faf01a0 --- /dev/null +++ b/erizo/src/test/rtp/RtcpRrGeneratorTest.cpp @@ -0,0 +1,118 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../utils/Mocks.h" +#include "../utils/Tools.h" +#include "../utils/Matchers.h" + +using ::testing::_; +using ::testing::IsNull; +using ::testing::Args; +using ::testing::Return; +using ::testing::AllOf; +using erizo::dataPacket; +using erizo::packetType; +using erizo::AUDIO_PACKET; +using erizo::VIDEO_PACKET; +using erizo::RtcpRrGenerator; +using erizo::RtcpHeader; +using erizo::WebRtcConnection; +using erizo::SimulatedClock; + +class RtcpRrGeneratorTest :public ::testing::Test { + public: + RtcpRrGeneratorTest(): clock{std::make_shared()}, rr_generator{erizo::kVideoSsrc, + VIDEO_PACKET, clock} {} + + protected: + void advanceClockMs(int time_ms) { + clock->advanceTime(std::chrono::milliseconds(time_ms)); + } + + std::shared_ptr clock; + RtcpRrGenerator rr_generator; +}; + +TEST_F(RtcpRrGeneratorTest, shouldReportPacketLoss) { + uint16_t kArbitraryPacketsLost = 8; + auto first_packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber, VIDEO_PACKET); + auto second_packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber + kArbitraryPacketsLost + 1, + VIDEO_PACKET); + rr_generator.handleRtpPacket(first_packet); + rr_generator.handleRtpPacket(second_packet); + std::shared_ptr rr_packet = rr_generator.generateReceiverReport(); + RtcpHeader* rtcp_header = reinterpret_cast(rr_packet->data); + EXPECT_EQ(rtcp_header->getLostPackets(), kArbitraryPacketsLost); +} + +TEST_F(RtcpRrGeneratorTest, shouldReportFractionLost) { + uint16_t kArbitraryPacketsLost = 2; + uint16_t kPercentagePacketLoss = 50; + uint8_t kFractionLost = kPercentagePacketLoss * 256/100; + auto first_packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber, VIDEO_PACKET); + auto second_packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber + kArbitraryPacketsLost + 1, + VIDEO_PACKET); + rr_generator.handleRtpPacket(first_packet); + rr_generator.handleRtpPacket(second_packet); + + std::shared_ptr rr_packet = rr_generator.generateReceiverReport(); + RtcpHeader* rtcp_header = reinterpret_cast(rr_packet->data); + EXPECT_EQ(rtcp_header->getFractionLost(), kFractionLost); +} + +TEST_F(RtcpRrGeneratorTest, shouldReportHighestSeqnum) { + uint16_t kArbitraryNumberOfPackets = 4; + auto first_packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber, VIDEO_PACKET); + auto second_packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber + kArbitraryNumberOfPackets, + VIDEO_PACKET); + + rr_generator.handleRtpPacket(first_packet); + rr_generator.handleRtpPacket(second_packet); + + std::shared_ptr rr_packet = rr_generator.generateReceiverReport(); + RtcpHeader* rtcp_header = reinterpret_cast(rr_packet->data); + EXPECT_EQ(rtcp_header->getHighestSeqnum(), erizo::kArbitrarySeqNumber + kArbitraryNumberOfPackets); +} + +TEST_F(RtcpRrGeneratorTest, shouldReportHighestSeqnumWithRollover) { + uint16_t kMaxSeqnum = 65534; + uint16_t kArbitraryNumberOfPackets = 4; + uint16_t kSeqnumCyclesExpected = 1; + uint16_t kNewSeqNum = kMaxSeqnum + kArbitraryNumberOfPackets; + + auto first_packet = erizo::PacketTools::createDataPacket(kMaxSeqnum, VIDEO_PACKET); + auto second_packet = erizo::PacketTools::createDataPacket(kMaxSeqnum + kArbitraryNumberOfPackets, VIDEO_PACKET); + + rr_generator.handleRtpPacket(first_packet); + rr_generator.handleRtpPacket(second_packet); + + std::shared_ptr rr_packet = rr_generator.generateReceiverReport(); + RtcpHeader* rtcp_header = reinterpret_cast(rr_packet->data); + EXPECT_EQ(rtcp_header->getSeqnumCycles(), kSeqnumCyclesExpected); + EXPECT_EQ(rtcp_header->getHighestSeqnum(), kNewSeqNum); +} + + +TEST_F(RtcpRrGeneratorTest, shouldReportDelaySinceLastSr) { + int kArbitraryTimePassedInMs = 500; + int kArbitratyTimePassed = kArbitraryTimePassedInMs * 65536/1000; + auto first_packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber, VIDEO_PACKET); + auto sender_report = erizo::PacketTools::createSenderReport(erizo::kVideoSsrc, VIDEO_PACKET); + rr_generator.handleRtpPacket(first_packet); + rr_generator.handleSr(sender_report); + advanceClockMs(kArbitraryTimePassedInMs); + + std::shared_ptr rr_packet = rr_generator.generateReceiverReport(); + RtcpHeader* rtcp_header = reinterpret_cast(rr_packet->data); + EXPECT_EQ(rtcp_header->getDelaySinceLastSr(), kArbitratyTimePassed); +} diff --git a/erizo_controller/erizoAgent/log4cxx.properties b/erizo_controller/erizoAgent/log4cxx.properties index 67e6b1da65..3bd66a44cc 100644 --- a/erizo_controller/erizoAgent/log4cxx.properties +++ b/erizo_controller/erizoAgent/log4cxx.properties @@ -50,7 +50,9 @@ log4j.logger.rtp.RtpVP8SlideShowHandler=WARN log4j.logger.rtp.RtpAudioMuteHandler=WARN log4j.logger.rtp.RtpSink=WARN log4j.logger.rtp.RtpSource=WARN -log4j.logger.rtp.RRGenerationHandler=WARN +log4j.logger.rtp.RtcpFeedbackGenerationHandler=WARN +log4j.logger.rtp.RtcpRrGenerator=WARN +log4j.logger.rtp.RtcpNackGenerator=WARN log4j.logger.rtp.SRPacketHandler=WARN log4j.logger.rtp.BandwidthEstimationHandler=WARN log4j.logger.rtp.SenderBandwidthEstimationHandler=WARN