diff --git a/webrtc/modules/BUILD.gn b/webrtc/modules/BUILD.gn index 28b8c383e0..8e90a799fc 100644 --- a/webrtc/modules/BUILD.gn +++ b/webrtc/modules/BUILD.gn @@ -405,6 +405,7 @@ if (rtc_include_tests) { "rtp_rtcp/source/fec_test_helper.cc", "rtp_rtcp/source/fec_test_helper.h", "rtp_rtcp/source/flexfec_header_reader_writer_unittest.cc", + "rtp_rtcp/source/flexfec_receiver_unittest.cc", "rtp_rtcp/source/mock/mock_rtp_payload_strategy.h", "rtp_rtcp/source/nack_rtx_unittest.cc", "rtp_rtcp/source/packet_loss_stats_unittest.cc", diff --git a/webrtc/modules/rtp_rtcp/BUILD.gn b/webrtc/modules/rtp_rtcp/BUILD.gn index 16a3717092..e8b7bbd437 100644 --- a/webrtc/modules/rtp_rtcp/BUILD.gn +++ b/webrtc/modules/rtp_rtcp/BUILD.gn @@ -11,6 +11,7 @@ import("../../build/webrtc.gni") rtc_static_library("rtp_rtcp") { sources = [ "include/fec_receiver.h", + "include/flexfec_receiver.h", "include/receive_statistics.h", "include/remote_ntp_time_estimator.h", "include/rtp_header_parser.h", @@ -28,6 +29,8 @@ rtc_static_library("rtp_rtcp") { "source/fec_receiver_impl.h", "source/flexfec_header_reader_writer.cc", "source/flexfec_header_reader_writer.h", + "source/flexfec_receiver_impl.cc", + "source/flexfec_receiver_impl.h", "source/forward_error_correction.cc", "source/forward_error_correction.h", "source/forward_error_correction_internal.cc", diff --git a/webrtc/modules/rtp_rtcp/include/flexfec_receiver.h b/webrtc/modules/rtp_rtcp/include/flexfec_receiver.h new file mode 100644 index 0000000000..f3894f5d77 --- /dev/null +++ b/webrtc/modules/rtp_rtcp/include/flexfec_receiver.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_RTP_RTCP_INCLUDE_FLEXFEC_RECEIVER_H_ +#define WEBRTC_MODULES_RTP_RTCP_INCLUDE_FLEXFEC_RECEIVER_H_ + +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/modules/rtp_rtcp/include/fec_receiver.h" + +namespace webrtc { + +// Callback interface for packets recovered by FlexFEC. +class RecoveredPacketReceiver { + public: + virtual bool OnRecoveredPacket(const uint8_t* packet, size_t length) = 0; + + protected: + virtual ~RecoveredPacketReceiver() = default; +}; + +class FlexfecReceiver { + public: + static std::unique_ptr Create( + uint32_t flexfec_ssrc, + uint32_t protected_media_ssrc, + RecoveredPacketReceiver* callback); + virtual ~FlexfecReceiver(); + + // Inserts a received packet (can be either media or FlexFEC) into the + // internal buffer, and sends the received packets to the erasure code. + // All newly recovered packets are sent back through the callback. + virtual bool AddAndProcessReceivedPacket(const uint8_t* packet, + size_t packet_length) = 0; + + // Returns a counter describing the added and recovered packets. + virtual FecPacketCounter GetPacketCounter() const = 0; +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_RTP_RTCP_INCLUDE_FLEXFEC_RECEIVER_H_ diff --git a/webrtc/modules/rtp_rtcp/mocks/mock_recovered_packet_receiver.h b/webrtc/modules/rtp_rtcp/mocks/mock_recovered_packet_receiver.h new file mode 100644 index 0000000000..fcc637b044 --- /dev/null +++ b/webrtc/modules/rtp_rtcp/mocks/mock_recovered_packet_receiver.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_RTP_RTCP_MOCKS_MOCK_RECOVERED_PACKET_RECEIVER_H_ +#define WEBRTC_MODULES_RTP_RTCP_MOCKS_MOCK_RECOVERED_PACKET_RECEIVER_H_ + +#include "webrtc/base/basictypes.h" +#include "webrtc/modules/rtp_rtcp/include/flexfec_receiver.h" +#include "webrtc/test/gmock.h" + +namespace webrtc { + +class MockRecoveredPacketReceiver : public RecoveredPacketReceiver { + public: + MOCK_METHOD2(OnRecoveredPacket, bool(const uint8_t* packet, size_t length)); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_RTP_RTCP_MOCKS_MOCK_RECOVERED_PACKET_RECEIVER_H_ diff --git a/webrtc/modules/rtp_rtcp/rtp_rtcp.gypi b/webrtc/modules/rtp_rtcp/rtp_rtcp.gypi index d98831bf73..0be12fe4f3 100644 --- a/webrtc/modules/rtp_rtcp/rtp_rtcp.gypi +++ b/webrtc/modules/rtp_rtcp/rtp_rtcp.gypi @@ -19,6 +19,7 @@ 'sources': [ # Common 'include/fec_receiver.h', + 'include/flexfec_receiver.h', 'include/receive_statistics.h', 'include/remote_ntp_time_estimator.h', 'include/rtp_header_parser.h', @@ -29,6 +30,8 @@ 'source/byte_io.h', 'source/fec_receiver_impl.cc', 'source/fec_receiver_impl.h', + 'source/flexfec_receiver_impl.cc', + 'source/flexfec_receiver_impl.h', 'source/packet_loss_stats.cc', 'source/packet_loss_stats.h', 'source/playout_delay_oracle.cc', @@ -164,6 +167,7 @@ 'source/vp8_partition_aggregator.h', # Mocks 'mocks/mock_rtp_rtcp.h', + 'mocks/mock_recovered_packet_receiver.h', 'source/mock/mock_rtp_payload_strategy.h', ], # source 'conditions': [ diff --git a/webrtc/modules/rtp_rtcp/source/flexfec_receiver_impl.cc b/webrtc/modules/rtp_rtcp/source/flexfec_receiver_impl.cc new file mode 100644 index 0000000000..e9dcd6f109 --- /dev/null +++ b/webrtc/modules/rtp_rtcp/source/flexfec_receiver_impl.cc @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/rtp_rtcp/source/flexfec_receiver_impl.h" + +#include + +#include "webrtc/base/logging.h" +#include "webrtc/base/scoped_ref_ptr.h" +#include "webrtc/modules/rtp_rtcp/source/rtp_packet_received.h" + +namespace webrtc { + +namespace { + +using Packet = ForwardErrorCorrection::Packet; +using ReceivedPacket = ForwardErrorCorrection::ReceivedPacket; + +// Minimum header size (in bytes) of a well-formed non-singular FlexFEC packet. +constexpr size_t kMinFlexfecHeaderSize = 20; + +// How often to log the recovered packets to the text log. +constexpr int kPacketLogIntervalMs = 10000; + +} // namespace + +std::unique_ptr FlexfecReceiver::Create( + uint32_t flexfec_ssrc, + uint32_t protected_media_ssrc, + RecoveredPacketReceiver* callback) { + return std::unique_ptr( + new FlexfecReceiverImpl(flexfec_ssrc, protected_media_ssrc, callback)); +} + +FlexfecReceiver::~FlexfecReceiver() = default; + +FlexfecReceiverImpl::FlexfecReceiverImpl(uint32_t flexfec_ssrc, + uint32_t protected_media_ssrc, + RecoveredPacketReceiver* callback) + : flexfec_ssrc_(flexfec_ssrc), + protected_media_ssrc_(protected_media_ssrc), + erasure_code_(ForwardErrorCorrection::CreateFlexfec()), + callback_(callback), + clock_(Clock::GetRealTimeClock()), + last_recovered_packet_ms_(-1) { + // It's OK to create this object on a different thread/task queue than + // the one used during main operation. + sequence_checker_.Detach(); +} + +FlexfecReceiverImpl::~FlexfecReceiverImpl() = default; + +bool FlexfecReceiverImpl::AddAndProcessReceivedPacket(const uint8_t* packet, + size_t packet_length) { + RTC_DCHECK(sequence_checker_.CalledSequentially()); + + if (!AddReceivedPacket(packet, packet_length)) { + return false; + } + return ProcessReceivedPackets(); +} + +FecPacketCounter FlexfecReceiverImpl::GetPacketCounter() const { + RTC_DCHECK(sequence_checker_.CalledSequentially()); + return packet_counter_; +} + +bool FlexfecReceiverImpl::AddReceivedPacket(const uint8_t* packet, + size_t packet_length) { + RTC_DCHECK(sequence_checker_.CalledSequentially()); + + // RTP packets with a full base header (12 bytes), but without payload, + // could conceivably be useful in the decoding. Therefore we check + // with a strict inequality here. + if (packet_length < kRtpHeaderSize) { + LOG(LS_WARNING) << "Truncated packet, discarding."; + return false; + } + + // TODO(brandtr): Consider how to handle received FlexFEC packets and + // the bandwidth estimator. + RtpPacketReceived parsed_packet; + if (!parsed_packet.Parse(packet, packet_length)) { + return false; + } + + // Demultiplex based on SSRC, and insert into erasure code decoder. + std::unique_ptr received_packet(new ReceivedPacket()); + received_packet->seq_num = parsed_packet.SequenceNumber(); + received_packet->ssrc = parsed_packet.Ssrc(); + if (received_packet->ssrc == flexfec_ssrc_) { + // This is a FEC packet belonging to this FlexFEC stream. + if (parsed_packet.payload_size() < kMinFlexfecHeaderSize) { + LOG(LS_WARNING) << "Truncated FlexFEC packet, discarding."; + return false; + } + received_packet->is_fec = true; + ++packet_counter_.num_fec_packets; + // Insert packet payload into erasure code. + // TODO(brandtr): Remove this memcpy when the FEC packet classes + // are using COW buffers internally. + received_packet->pkt = rtc::scoped_refptr(new Packet()); + memcpy(received_packet->pkt->data, parsed_packet.payload(), + parsed_packet.payload_size()); + received_packet->pkt->length = parsed_packet.payload_size(); + } else { + // This is a media packet, or a FlexFEC packet belonging to some + // other FlexFEC stream. + if (received_packet->ssrc != protected_media_ssrc_) { + return false; + } + received_packet->is_fec = false; + // Insert entire packet into erasure code. + // TODO(brandtr): Remove this memcpy too. + received_packet->pkt = rtc::scoped_refptr(new Packet()); + memcpy(received_packet->pkt->data, parsed_packet.data(), + parsed_packet.size()); + received_packet->pkt->length = parsed_packet.size(); + } + received_packets_.push_back(std::move(received_packet)); + ++packet_counter_.num_packets; + + return true; +} + +// Note that the implementation of this member function and the implementation +// in FecReceiver::ProcessReceivedFec() are slightly different. +// This implementation only returns _recovered_ media packets through the +// callback, whereas the implementation in FecReceiver returns _all inserted_ +// media packets through the callback. The latter behaviour makes sense +// for ULPFEC, since the ULPFEC receiver is owned by the RtpStreamReceiver. +// Here, however, the received media pipeline is more decoupled from the +// FlexFEC decoder, and we therefore do not interfere with the reception +// of non-recovered media packets. +bool FlexfecReceiverImpl::ProcessReceivedPackets() { + RTC_DCHECK(sequence_checker_.CalledSequentially()); + + // Decode. + if (!received_packets_.empty()) { + if (erasure_code_->DecodeFec(&received_packets_, &recovered_packets_) != + 0) { + return false; + } + } + // Return recovered packets through callback. + for (const auto& recovered_packet : recovered_packets_) { + if (recovered_packet->returned) { + continue; + } + ++packet_counter_.num_recovered_packets; + if (!callback_->OnRecoveredPacket(recovered_packet->pkt->data, + recovered_packet->pkt->length)) { + return false; + } + recovered_packet->returned = true; + // Periodically log the incoming packets. + int64_t now_ms = clock_->TimeInMilliseconds(); + if (now_ms - last_recovered_packet_ms_ > kPacketLogIntervalMs) { + uint32_t media_ssrc = + ForwardErrorCorrection::ParseSsrc(recovered_packet->pkt->data); + std::stringstream ss; + ss << "Recovered media packet with SSRC: " << media_ssrc + << " from FlexFEC stream with SSRC: " << flexfec_ssrc_ << "."; + LOG(LS_INFO) << ss.str(); + last_recovered_packet_ms_ = now_ms; + } + } + return true; +} + +} // namespace webrtc diff --git a/webrtc/modules/rtp_rtcp/source/flexfec_receiver_impl.h b/webrtc/modules/rtp_rtcp/source/flexfec_receiver_impl.h new file mode 100644 index 0000000000..9550e9596a --- /dev/null +++ b/webrtc/modules/rtp_rtcp/source/flexfec_receiver_impl.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_RTP_RTCP_SOURCE_FLEXFEC_RECEIVER_IMPL_H_ +#define WEBRTC_MODULES_RTP_RTCP_SOURCE_FLEXFEC_RECEIVER_IMPL_H_ + +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/sequenced_task_checker.h" +#include "webrtc/call.h" +#include "webrtc/modules/rtp_rtcp/include/flexfec_receiver.h" +#include "webrtc/modules/rtp_rtcp/source/forward_error_correction.h" +#include "webrtc/system_wrappers/include/clock.h" + +namespace webrtc { + +class FlexfecReceiverImpl : public FlexfecReceiver { + public: + FlexfecReceiverImpl(uint32_t flexfec_ssrc, + uint32_t protected_media_ssrc, + RecoveredPacketReceiver* callback); + ~FlexfecReceiverImpl(); + + // Implements FlexfecReceiver. + bool AddAndProcessReceivedPacket(const uint8_t* packet, size_t packet_length); + FecPacketCounter GetPacketCounter() const; + + private: + bool AddReceivedPacket(const uint8_t* packet, size_t packet_length); + bool ProcessReceivedPackets(); + + // Config. + const uint32_t flexfec_ssrc_; + const uint32_t protected_media_ssrc_; + + // Erasure code interfacing and callback. + std::unique_ptr erasure_code_; + ForwardErrorCorrection::ReceivedPacketList received_packets_; + ForwardErrorCorrection::RecoveredPacketList recovered_packets_; + RecoveredPacketReceiver* const callback_; + + // Logging and stats. + Clock* const clock_; + int64_t last_recovered_packet_ms_; + FecPacketCounter packet_counter_; + + rtc::SequencedTaskChecker sequence_checker_; +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_RTP_RTCP_SOURCE_FLEXFEC_RECEIVER_IMPL_H_ diff --git a/webrtc/modules/rtp_rtcp/source/flexfec_receiver_unittest.cc b/webrtc/modules/rtp_rtcp/source/flexfec_receiver_unittest.cc new file mode 100644 index 0000000000..8efa63ab58 --- /dev/null +++ b/webrtc/modules/rtp_rtcp/source/flexfec_receiver_unittest.cc @@ -0,0 +1,525 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/modules/rtp_rtcp/include/flexfec_receiver.h" +#include "webrtc/modules/rtp_rtcp/mocks/mock_recovered_packet_receiver.h" +#include "webrtc/modules/rtp_rtcp/source/fec_test_helper.h" +#include "webrtc/modules/rtp_rtcp/source/forward_error_correction.h" +#include "webrtc/test/gmock.h" +#include "webrtc/test/gtest.h" + +namespace webrtc { + +namespace { + +using ::testing::_; +using ::testing::Args; +using ::testing::ElementsAreArray; +using ::testing::Return; + +using test::fec::FlexfecPacketGenerator; +using Packet = ForwardErrorCorrection::Packet; +using PacketList = ForwardErrorCorrection::PacketList; + +constexpr size_t kPayloadLength = 500; +constexpr uint32_t kFlexfecSsrc = 42984; +constexpr uint32_t kMediaSsrc = 8353; + +} // namespace + +class FlexfecReceiverTest : public ::testing::Test { + protected: + FlexfecReceiverTest() + : receiver_(FlexfecReceiver::Create(kFlexfecSsrc, + kMediaSsrc, + &recovered_packet_receiver_)), + erasure_code_(ForwardErrorCorrection::CreateFlexfec()), + packet_generator_(kMediaSsrc, kFlexfecSsrc) {} + + // Generates |num_media_packets| corresponding to a single frame. + void PacketizeFrame(size_t num_media_packets, + size_t frame_offset, + PacketList* media_packets); + + // Generates |num_fec_packets| FEC packets, given |media_packets|. + std::list EncodeFec(const PacketList& media_packets, + size_t num_fec_packets); + + std::unique_ptr receiver_; + std::unique_ptr erasure_code_; + + FlexfecPacketGenerator packet_generator_; + testing::StrictMock recovered_packet_receiver_; +}; + +void FlexfecReceiverTest::PacketizeFrame(size_t num_media_packets, + size_t frame_offset, + PacketList* media_packets) { + packet_generator_.NewFrame(num_media_packets); + for (size_t i = 0; i < num_media_packets; ++i) { + std::unique_ptr next_packet( + packet_generator_.NextPacket(frame_offset + i, kPayloadLength)); + media_packets->push_back(std::move(next_packet)); + } +} + +std::list FlexfecReceiverTest::EncodeFec( + const PacketList& media_packets, + size_t num_fec_packets) { + const uint8_t protection_factor = + num_fec_packets * 255 / media_packets.size(); + constexpr int kNumImportantPackets = 0; + constexpr bool kUseUnequalProtection = false; + constexpr FecMaskType kFecMaskType = kFecMaskRandom; + std::list fec_packets; + EXPECT_EQ(0, erasure_code_->EncodeFec( + media_packets, protection_factor, kNumImportantPackets, + kUseUnequalProtection, kFecMaskType, &fec_packets)); + EXPECT_EQ(num_fec_packets, fec_packets.size()); + return fec_packets; +} + +TEST_F(FlexfecReceiverTest, ReceivesMediaPacket) { + packet_generator_.NewFrame(1); + std::unique_ptr media_packet( + packet_generator_.NextPacket(0, kPayloadLength)); + + EXPECT_TRUE(receiver_->AddAndProcessReceivedPacket(media_packet->data, + media_packet->length)); +} + +TEST_F(FlexfecReceiverTest, FailsOnTruncatedMediaPacket) { + const size_t kNoPayload = 0; + + packet_generator_.NewFrame(1); + std::unique_ptr media_packet( + packet_generator_.NextPacket(0, kNoPayload)); + // Simulate truncated media packet. + media_packet->length = kRtpHeaderSize - 1; + + EXPECT_FALSE(receiver_->AddAndProcessReceivedPacket(media_packet->data, + media_packet->length)); +} + +TEST_F(FlexfecReceiverTest, ReceivesMediaAndFecPackets) { + const size_t kNumMediaPackets = 1; + const size_t kNumFecPackets = 1; + + PacketList media_packets; + PacketizeFrame(kNumMediaPackets, 0, &media_packets); + std::list fec_packets = EncodeFec(media_packets, kNumFecPackets); + auto media_packet = media_packets.front().get(); + auto fec_packet = packet_generator_.BuildFlexfecPacket(*fec_packets.front()); + + EXPECT_TRUE(receiver_->AddAndProcessReceivedPacket(media_packet->data, + media_packet->length)); + EXPECT_TRUE(receiver_->AddAndProcessReceivedPacket(fec_packet->data, + fec_packet->length)); +} + +TEST_F(FlexfecReceiverTest, FailsOnTruncatedFecPacket) { + const size_t kNumMediaPackets = 1; + const size_t kNumFecPackets = 1; + + PacketList media_packets; + PacketizeFrame(kNumMediaPackets, 0, &media_packets); + std::list fec_packets = EncodeFec(media_packets, kNumFecPackets); + auto media_packet = media_packets.front().get(); + // Simulate truncated FlexFEC payload. + fec_packets.front()->length = 1; + auto fec_packet = packet_generator_.BuildFlexfecPacket(*fec_packets.front()); + + EXPECT_TRUE(receiver_->AddAndProcessReceivedPacket(media_packet->data, + media_packet->length)); + EXPECT_FALSE(receiver_->AddAndProcessReceivedPacket(fec_packet->data, + fec_packet->length)); +} + +TEST_F(FlexfecReceiverTest, FailsOnUnknownMediaSsrc) { + const size_t kNumMediaPackets = 1; + + PacketList media_packets; + PacketizeFrame(kNumMediaPackets, 0, &media_packets); + auto media_packet = media_packets.front().get(); + // Corrupt the SSRC. + media_packet->data[8] = 0; + media_packet->data[9] = 1; + media_packet->data[10] = 2; + media_packet->data[11] = 3; + + EXPECT_FALSE(receiver_->AddAndProcessReceivedPacket(media_packet->data, + media_packet->length)); +} + +TEST_F(FlexfecReceiverTest, FailsOnUnknownFecSsrc) { + const size_t kNumMediaPackets = 1; + const size_t kNumFecPackets = 1; + + PacketList media_packets; + PacketizeFrame(kNumMediaPackets, 0, &media_packets); + std::list fec_packets = EncodeFec(media_packets, kNumFecPackets); + auto media_packet = media_packets.front().get(); + auto fec_packet = packet_generator_.BuildFlexfecPacket(*fec_packets.front()); + // Corrupt the SSRC. + fec_packet->data[8] = 4; + fec_packet->data[9] = 5; + fec_packet->data[10] = 6; + fec_packet->data[11] = 7; + + EXPECT_TRUE(receiver_->AddAndProcessReceivedPacket(media_packet->data, + media_packet->length)); + EXPECT_FALSE(receiver_->AddAndProcessReceivedPacket(fec_packet->data, + fec_packet->length)); +} + +TEST_F(FlexfecReceiverTest, ReceivesMultiplePackets) { + const size_t kNumMediaPackets = 2; + const size_t kNumFecPackets = 1; + + PacketList media_packets; + PacketizeFrame(kNumMediaPackets, 0, &media_packets); + std::list fec_packets = EncodeFec(media_packets, kNumFecPackets); + + // Receive all media packets. + for (const auto& media_packet : media_packets) { + EXPECT_TRUE(receiver_->AddAndProcessReceivedPacket(media_packet->data, + media_packet->length)); + } + + // Receive FEC packet. + auto fec_packet = fec_packets.front(); + std::unique_ptr packet_with_rtp_header = + packet_generator_.BuildFlexfecPacket(*fec_packet); + EXPECT_TRUE(receiver_->AddAndProcessReceivedPacket( + packet_with_rtp_header->data, packet_with_rtp_header->length)); +} + +TEST_F(FlexfecReceiverTest, RecoversFromSingleMediaLoss) { + const size_t kNumMediaPackets = 2; + const size_t kNumFecPackets = 1; + + PacketList media_packets; + PacketizeFrame(kNumMediaPackets, 0, &media_packets); + std::list fec_packets = EncodeFec(media_packets, kNumFecPackets); + + // Receive first media packet but drop second. + auto media_it = media_packets.begin(); + EXPECT_TRUE(receiver_->AddAndProcessReceivedPacket((*media_it)->data, + (*media_it)->length)); + + // Receive FEC packet and ensure recovery of lost media packet. + auto fec_it = fec_packets.begin(); + std::unique_ptr packet_with_rtp_header = + packet_generator_.BuildFlexfecPacket(**fec_it); + media_it++; + EXPECT_CALL(recovered_packet_receiver_, + OnRecoveredPacket(_, (*media_it)->length)) + .With( + Args<0, 1>(ElementsAreArray((*media_it)->data, (*media_it)->length))) + .WillOnce(Return(true)); + EXPECT_TRUE(receiver_->AddAndProcessReceivedPacket( + packet_with_rtp_header->data, packet_with_rtp_header->length)); +} + +TEST_F(FlexfecReceiverTest, RecoversFromDoubleMediaLoss) { + const size_t kNumMediaPackets = 2; + const size_t kNumFecPackets = 2; + + PacketList media_packets; + PacketizeFrame(kNumMediaPackets, 0, &media_packets); + std::list fec_packets = EncodeFec(media_packets, kNumFecPackets); + + // Drop both media packets. + + // Receive first FEC packet and recover first lost media packet. + auto fec_it = fec_packets.begin(); + std::unique_ptr packet_with_rtp_header = + packet_generator_.BuildFlexfecPacket(**fec_it); + auto media_it = media_packets.begin(); + EXPECT_CALL(recovered_packet_receiver_, + OnRecoveredPacket(_, (*media_it)->length)) + .With( + Args<0, 1>(ElementsAreArray((*media_it)->data, (*media_it)->length))) + .WillOnce(Return(true)); + EXPECT_TRUE(receiver_->AddAndProcessReceivedPacket( + packet_with_rtp_header->data, packet_with_rtp_header->length)); + + // Receive second FEC packet and recover second lost media packet. + fec_it++; + packet_with_rtp_header = packet_generator_.BuildFlexfecPacket(**fec_it); + media_it++; + EXPECT_CALL(recovered_packet_receiver_, + OnRecoveredPacket(_, (*media_it)->length)) + .With( + Args<0, 1>(ElementsAreArray((*media_it)->data, (*media_it)->length))) + .WillOnce(Return(true)); + EXPECT_TRUE(receiver_->AddAndProcessReceivedPacket( + packet_with_rtp_header->data, packet_with_rtp_header->length)); +} + +TEST_F(FlexfecReceiverTest, DoesNotRecoverFromMediaAndFecLoss) { + const size_t kNumMediaPackets = 2; + const size_t kNumFecPackets = 1; + + PacketList media_packets; + PacketizeFrame(kNumMediaPackets, 0, &media_packets); + std::list fec_packets = EncodeFec(media_packets, kNumFecPackets); + + // Receive first media packet. + auto media_it = media_packets.begin(); + EXPECT_TRUE(receiver_->AddAndProcessReceivedPacket((*media_it)->data, + (*media_it)->length)); + + // Drop second media packet and FEC packet. Do not expect call back. +} + +TEST_F(FlexfecReceiverTest, DoesNotCallbackTwice) { + const size_t kNumMediaPackets = 2; + const size_t kNumFecPackets = 1; + + PacketList media_packets; + PacketizeFrame(kNumMediaPackets, 0, &media_packets); + std::list fec_packets = EncodeFec(media_packets, kNumFecPackets); + + // Receive first media packet but drop second. + auto media_it = media_packets.begin(); + EXPECT_TRUE(receiver_->AddAndProcessReceivedPacket((*media_it)->data, + (*media_it)->length)); + + // Receive FEC packet and ensure recovery of lost media packet. + auto fec_it = fec_packets.begin(); + std::unique_ptr packet_with_rtp_header = + packet_generator_.BuildFlexfecPacket(**fec_it); + media_it++; + EXPECT_CALL(recovered_packet_receiver_, + OnRecoveredPacket(_, (*media_it)->length)) + .With( + Args<0, 1>(ElementsAreArray((*media_it)->data, (*media_it)->length))) + .WillOnce(Return(true)); + EXPECT_TRUE(receiver_->AddAndProcessReceivedPacket( + packet_with_rtp_header->data, packet_with_rtp_header->length)); + + // Receive FEC packet again. + EXPECT_TRUE(receiver_->AddAndProcessReceivedPacket( + packet_with_rtp_header->data, packet_with_rtp_header->length)); + + // Do not call back again. +} + +// Here we are implicitly assuming packet masks that are suitable for +// this type of 50% correlated loss. If we are changing our precomputed +// packet masks, this test might need to be updated. +TEST_F(FlexfecReceiverTest, RecoversFrom50PercentLoss) { + const size_t kNumFecPackets = 5; + const size_t kNumFrames = 2 * kNumFecPackets; + const size_t kNumMediaPacketsPerFrame = 1; + + PacketList media_packets; + for (size_t i = 0; i < kNumFrames; ++i) { + PacketizeFrame(kNumMediaPacketsPerFrame, i, &media_packets); + } + std::list fec_packets = EncodeFec(media_packets, kNumFecPackets); + + // Drop every second media packet. + auto media_it = media_packets.begin(); + while (media_it != media_packets.end()) { + EXPECT_TRUE(receiver_->AddAndProcessReceivedPacket((*media_it)->data, + (*media_it)->length)); + ++media_it; + if (media_it == media_packets.end()) { + break; + } + ++media_it; + } + + // Receive all FEC packets. + media_it = media_packets.begin(); + for (const auto& fec_packet : fec_packets) { + std::unique_ptr fec_packet_with_rtp_header = + packet_generator_.BuildFlexfecPacket(*fec_packet); + ++media_it; + if (media_it == media_packets.end()) { + break; + } + EXPECT_CALL(recovered_packet_receiver_, + OnRecoveredPacket(_, (*media_it)->length)) + .With(Args<0, 1>( + ElementsAreArray((*media_it)->data, (*media_it)->length))) + .WillOnce(Return(true)); + EXPECT_TRUE(receiver_->AddAndProcessReceivedPacket( + fec_packet_with_rtp_header->data, fec_packet_with_rtp_header->length)); + ++media_it; + } +} + +TEST_F(FlexfecReceiverTest, DelayedFecPacketDoesHelp) { + // These values need to be updated if the underlying erasure code + // implementation changes. + const size_t kNumFrames = 48; + const size_t kNumMediaPacketsPerFrame = 1; + const size_t kNumFecPackets = 1; + + PacketList media_packets; + PacketizeFrame(kNumMediaPacketsPerFrame, 0, &media_packets); + PacketizeFrame(kNumMediaPacketsPerFrame, 1, &media_packets); + // Protect two first frames. + std::list fec_packets = EncodeFec(media_packets, kNumFecPackets); + for (size_t i = 2; i < kNumFrames; ++i) { + PacketizeFrame(kNumMediaPacketsPerFrame, i, &media_packets); + } + + // Drop first media packet and delay FEC packet. + auto media_it = media_packets.begin(); + ++media_it; + + // Receive all other media packets. + while (media_it != media_packets.end()) { + EXPECT_TRUE(receiver_->AddAndProcessReceivedPacket((*media_it)->data, + (*media_it)->length)); + ++media_it; + } + + // Receive FEC packet and recover first media packet. + auto fec_it = fec_packets.begin(); + std::unique_ptr packet_with_rtp_header = + packet_generator_.BuildFlexfecPacket(**fec_it); + media_it = media_packets.begin(); + EXPECT_CALL(recovered_packet_receiver_, + OnRecoveredPacket(_, (*media_it)->length)) + .With( + Args<0, 1>(ElementsAreArray((*media_it)->data, (*media_it)->length))) + .WillOnce(Return(true)); + EXPECT_TRUE(receiver_->AddAndProcessReceivedPacket( + packet_with_rtp_header->data, packet_with_rtp_header->length)); +} + +TEST_F(FlexfecReceiverTest, TooDelayedFecPacketDoesNotHelp) { + // These values need to be updated if the underlying erasure code + // implementation changes. + const size_t kNumFrames = 49; + const size_t kNumMediaPacketsPerFrame = 1; + const size_t kNumFecPackets = 1; + + PacketList media_packets; + PacketizeFrame(kNumMediaPacketsPerFrame, 0, &media_packets); + PacketizeFrame(kNumMediaPacketsPerFrame, 1, &media_packets); + // Protect two first frames. + std::list fec_packets = EncodeFec(media_packets, kNumFecPackets); + for (size_t i = 2; i < kNumFrames; ++i) { + PacketizeFrame(kNumMediaPacketsPerFrame, i, &media_packets); + } + + // Drop first media packet and delay FEC packet. + auto media_it = media_packets.begin(); + ++media_it; + + // Receive all other media packets. + while (media_it != media_packets.end()) { + EXPECT_TRUE(receiver_->AddAndProcessReceivedPacket((*media_it)->data, + (*media_it)->length)); + ++media_it; + } + + // Receive FEC packet. + auto fec_it = fec_packets.begin(); + std::unique_ptr packet_with_rtp_header = + packet_generator_.BuildFlexfecPacket(**fec_it); + EXPECT_TRUE(receiver_->AddAndProcessReceivedPacket( + packet_with_rtp_header->data, packet_with_rtp_header->length)); + + // Do not expect a call back. +} + +TEST_F(FlexfecReceiverTest, RecoversWithMediaPacketsOutOfOrder) { + const size_t kNumMediaPackets = 6; + const size_t kNumFecPackets = 2; + + PacketList media_packets; + PacketizeFrame(kNumMediaPackets, 0, &media_packets); + std::list fec_packets = EncodeFec(media_packets, kNumFecPackets); + + // Lose two media packets, and receive the others out of order. + auto media_it = media_packets.begin(); + auto media_packet0 = media_it++; + auto media_packet1 = media_it++; + auto media_packet2 = media_it++; + auto media_packet3 = media_it++; + auto media_packet4 = media_it++; + auto media_packet5 = media_it++; + EXPECT_TRUE(receiver_->AddAndProcessReceivedPacket((*media_packet5)->data, + (*media_packet5)->length)); + EXPECT_TRUE(receiver_->AddAndProcessReceivedPacket((*media_packet2)->data, + (*media_packet2)->length)); + EXPECT_TRUE(receiver_->AddAndProcessReceivedPacket((*media_packet3)->data, + (*media_packet3)->length)); + EXPECT_TRUE(receiver_->AddAndProcessReceivedPacket((*media_packet0)->data, + (*media_packet0)->length)); + + // Expect to recover lost media packets. + EXPECT_CALL(recovered_packet_receiver_, + OnRecoveredPacket(_, (*media_packet1)->length)) + .With(Args<0, 1>( + ElementsAreArray((*media_packet1)->data, (*media_packet1)->length))) + .WillOnce(Return(true)); + EXPECT_CALL(recovered_packet_receiver_, + OnRecoveredPacket(_, (*media_packet4)->length)) + .With(Args<0, 1>( + ElementsAreArray((*media_packet4)->data, (*media_packet4)->length))) + .WillOnce(Return(true)); + + // Add FEC packets. + auto fec_it = fec_packets.begin(); + std::unique_ptr packet_with_rtp_header; + while (fec_it != fec_packets.end()) { + packet_with_rtp_header = packet_generator_.BuildFlexfecPacket(**fec_it); + EXPECT_TRUE(receiver_->AddAndProcessReceivedPacket( + packet_with_rtp_header->data, packet_with_rtp_header->length)); + ++fec_it; + } +} + +TEST_F(FlexfecReceiverTest, CalculatesNumberOfPackets) { + const size_t kNumMediaPackets = 2; + const size_t kNumFecPackets = 1; + + PacketList media_packets; + PacketizeFrame(kNumMediaPackets, 0, &media_packets); + std::list fec_packets = EncodeFec(media_packets, kNumFecPackets); + + // Receive first media packet but drop second. + auto media_it = media_packets.begin(); + EXPECT_TRUE(receiver_->AddAndProcessReceivedPacket((*media_it)->data, + (*media_it)->length)); + + // Receive FEC packet and ensure recovery of lost media packet. + auto fec_it = fec_packets.begin(); + std::unique_ptr packet_with_rtp_header = + packet_generator_.BuildFlexfecPacket(**fec_it); + media_it++; + EXPECT_CALL(recovered_packet_receiver_, + OnRecoveredPacket(_, (*media_it)->length)) + .With( + Args<0, 1>(ElementsAreArray((*media_it)->data, (*media_it)->length))) + .WillOnce(Return(true)); + EXPECT_TRUE(receiver_->AddAndProcessReceivedPacket( + packet_with_rtp_header->data, packet_with_rtp_header->length)); + + // Check stats calculations. + FecPacketCounter packet_counter = receiver_->GetPacketCounter(); + EXPECT_EQ(2U, packet_counter.num_packets); + EXPECT_EQ(1U, packet_counter.num_fec_packets); + EXPECT_EQ(1U, packet_counter.num_recovered_packets); +} + +} // namespace webrtc