diff --git a/logging/rtc_event_log/logged_events.h b/logging/rtc_event_log/logged_events.h index 30b1086c2a..b8c925639f 100644 --- a/logging/rtc_event_log/logged_events.h +++ b/logging/rtc_event_log/logged_events.h @@ -345,6 +345,8 @@ struct LoggedRtcpPacketRemb { rtcp::Remb remb; }; +// TODO(eladalon): Add LossNotification. + struct LoggedRtcpPacketNack { LoggedRtcpPacketNack() = default; LoggedRtcpPacketNack(int64_t timestamp_us, const rtcp::Nack& nack) diff --git a/modules/rtp_rtcp/BUILD.gn b/modules/rtp_rtcp/BUILD.gn index 3ea8666c22..80f9e135d0 100644 --- a/modules/rtp_rtcp/BUILD.gn +++ b/modules/rtp_rtcp/BUILD.gn @@ -25,6 +25,7 @@ rtc_source_set("rtp_rtcp_format") { "source/rtcp_packet/extended_jitter_report.h", "source/rtcp_packet/extended_reports.h", "source/rtcp_packet/fir.h", + "source/rtcp_packet/loss_notification.h", "source/rtcp_packet/nack.h", "source/rtcp_packet/pli.h", "source/rtcp_packet/psfb.h", @@ -59,6 +60,7 @@ rtc_source_set("rtp_rtcp_format") { "source/rtcp_packet/extended_jitter_report.cc", "source/rtcp_packet/extended_reports.cc", "source/rtcp_packet/fir.cc", + "source/rtcp_packet/loss_notification.cc", "source/rtcp_packet/nack.cc", "source/rtcp_packet/pli.cc", "source/rtcp_packet/psfb.cc", @@ -97,6 +99,7 @@ rtc_source_set("rtp_rtcp_format") { "../../rtc_base:checks", "../../rtc_base:deprecation", "../../rtc_base:rtc_base_approved", + "../../rtc_base/system:unused", "../../system_wrappers", "../video_coding:codec_globals_headers", "//third_party/abseil-cpp/absl/types:optional", @@ -374,6 +377,7 @@ if (rtc_include_tests) { "source/rtcp_packet/extended_jitter_report_unittest.cc", "source/rtcp_packet/extended_reports_unittest.cc", "source/rtcp_packet/fir_unittest.cc", + "source/rtcp_packet/loss_notification_unittest.cc", "source/rtcp_packet/nack_unittest.cc", "source/rtcp_packet/pli_unittest.cc", "source/rtcp_packet/rapid_resync_request_unittest.cc", diff --git a/modules/rtp_rtcp/include/rtp_rtcp_defines.h b/modules/rtp_rtcp/include/rtp_rtcp_defines.h index ab4fcaecc7..539a584723 100644 --- a/modules/rtp_rtcp/include/rtp_rtcp_defines.h +++ b/modules/rtp_rtcp/include/rtp_rtcp_defines.h @@ -124,6 +124,7 @@ enum RTCPPacketType : uint32_t { kRtcpTmmbn = 0x0200, kRtcpSrReq = 0x0400, kRtcpApp = 0x1000, + kRtcpLossNotification = 0x2000, kRtcpRemb = 0x10000, kRtcpTransmissionTimeOffset = 0x20000, kRtcpXrReceiverReferenceTime = 0x40000, diff --git a/modules/rtp_rtcp/source/rtcp_packet/loss_notification.cc b/modules/rtp_rtcp/source/rtcp_packet/loss_notification.cc new file mode 100644 index 0000000000..bd7017d80b --- /dev/null +++ b/modules/rtp_rtcp/source/rtcp_packet/loss_notification.cc @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2019 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 "modules/rtp_rtcp/source/rtcp_packet/loss_notification.h" + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t LossNotification::kFeedbackMessageType; + +// Loss Notification +// ----------------- +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| FMT=15 | PT=206 | length | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// 0 | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 4 | SSRC of media source | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 8 | Unique identifier 'L' 'N' 'T' 'F' | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 12 | Last Decoded Sequence Number | Last Received SeqNum Delta |D| +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +LossNotification::LossNotification() + : last_decoded_(0), last_received_(0), decodability_flag_(false) {} + +LossNotification::LossNotification(const LossNotification& rhs) = default; + +LossNotification::~LossNotification() = default; + +size_t LossNotification::BlockLength() const { + return kHeaderLength + kCommonFeedbackLength + kLossNotificationPayloadLength; +} + +bool LossNotification::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + while (*index + BlockLength() > max_length) { + if (!OnBufferFull(packet, index, callback)) + return false; + } + + const size_t index_end = *index + BlockLength(); + + // Note: |index| updated by the function below. + CreateHeader(kFeedbackMessageType, kPacketType, HeaderLength(), packet, + index); + + CreateCommonFeedback(packet + *index); + *index += kCommonFeedbackLength; + + ByteWriter::WriteBigEndian(packet + *index, kUniqueIdentifier); + *index += sizeof(uint32_t); + + ByteWriter::WriteBigEndian(packet + *index, last_decoded_); + *index += sizeof(uint16_t); + + const uint16_t last_received_delta = last_received_ - last_decoded_; + RTC_DCHECK_LE(last_received_delta, 0x7fff); + const uint16_t last_received_delta_and_decodability = + (last_received_delta << 1) | (decodability_flag_ ? 0x0001 : 0x0000); + + ByteWriter::WriteBigEndian(packet + *index, + last_received_delta_and_decodability); + *index += sizeof(uint16_t); + + RTC_DCHECK_EQ(index_end, *index); + return true; +} + +bool LossNotification::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + RTC_DCHECK_EQ(packet.fmt(), kFeedbackMessageType); + + if (packet.payload_size_bytes() < + kCommonFeedbackLength + kLossNotificationPayloadLength) { + return false; + } + + const uint8_t* const payload = packet.payload(); + + if (ByteReader::ReadBigEndian(&payload[8]) != kUniqueIdentifier) { + return false; + } + + ParseCommonFeedback(payload); + + last_decoded_ = ByteReader::ReadBigEndian(&payload[12]); + + const uint16_t last_received_delta_and_decodability = + ByteReader::ReadBigEndian(&payload[14]); + last_received_ = last_decoded_ + (last_received_delta_and_decodability >> 1); + decodability_flag_ = (last_received_delta_and_decodability & 0x0001); + + return true; +} + +bool LossNotification::Set(uint16_t last_decoded, + uint16_t last_received, + bool decodability_flag) { + const uint16_t delta = last_received - last_decoded; + if (delta > 0x7fff) { + return false; + } + last_received_ = last_received; + last_decoded_ = last_decoded; + decodability_flag_ = decodability_flag; + return true; +} + +} // namespace rtcp +} // namespace webrtc diff --git a/modules/rtp_rtcp/source/rtcp_packet/loss_notification.h b/modules/rtp_rtcp/source/rtcp_packet/loss_notification.h new file mode 100644 index 0000000000..7224cdb4ec --- /dev/null +++ b/modules/rtp_rtcp/source/rtcp_packet/loss_notification.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2019 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 MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_LOSS_NOTIFICATION_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_LOSS_NOTIFICATION_H_ + +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "modules/rtp_rtcp/source/rtcp_packet/psfb.h" +#include "rtc_base/system/unused.h" + +namespace webrtc { +namespace rtcp { + +class LossNotification : public Psfb { + public: + static constexpr uint8_t kFeedbackMessageType = 15; + + LossNotification(); + LossNotification(const LossNotification& other); + ~LossNotification() override; + + size_t BlockLength() const override; + + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override + RTC_WARN_UNUSED_RESULT; + + // Parse assumes header is already parsed and validated. + bool Parse(const CommonHeader& packet) RTC_WARN_UNUSED_RESULT; + + // Set all of the values transmitted by the loss notification message. + // If the values may not be represented by a loss notification message, + // false is returned, and no change is made to the object; this happens + // when |last_recieved| is ahead of |last_decoded| by more than 0x7fff. + // This is because |last_recieved| is represented on the wire as a delta, + // and only 15 bits are available for that delta. + bool Set(uint16_t last_decoded, + uint16_t last_received, + bool decodability_flag) RTC_WARN_UNUSED_RESULT; + + // RTP sequence number of the first packet belong to the last decoded + // non-discardable frame. + uint16_t last_decoded() const { return last_decoded_; } + + // RTP sequence number of the last received packet. + uint16_t last_received() const { return last_received_; } + + // A decodability flag, whose specific meaning depends on the last-received + // RTP sequence number. The decodability flag is true if and only if all of + // the frame's dependencies are known to be decodable, and the frame itself + // is not yet known to be unassemblable. + // * Clarification #1: In a multi-packet frame, the first packet's + // dependencies are known, but it is not yet known whether all parts + // of the current frame will be received. + // * Clarification #2: In a multi-packet frame, the dependencies would be + // unknown if the first packet was not received. Then, the packet will + // be known-unassemblable. + bool decodability_flag() const { return decodability_flag_; } + + private: + static constexpr uint32_t kUniqueIdentifier = 0x4C4E5446; // 'L' 'N' 'T' 'F'. + static constexpr size_t kLossNotificationPayloadLength = 8; + + uint16_t last_decoded_; + uint16_t last_received_; + bool decodability_flag_; +}; +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_LOSS_NOTIFICATION_H_ diff --git a/modules/rtp_rtcp/source/rtcp_packet/loss_notification_unittest.cc b/modules/rtp_rtcp/source/rtcp_packet/loss_notification_unittest.cc new file mode 100644 index 0000000000..6d74225df0 --- /dev/null +++ b/modules/rtp_rtcp/source/rtcp_packet/loss_notification_unittest.cc @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2019 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 "modules/rtp_rtcp/source/rtcp_packet/loss_notification.h" + +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +namespace webrtc { + +using ::testing::ElementsAreArray; +using ::testing::make_tuple; +using ::webrtc::rtcp::LossNotification; + +TEST(RtcpPacketLossNotificationTest, SetWithIllegalValuesFails) { + constexpr uint16_t kLastDecoded = 0x3c7b; + constexpr uint16_t kLastReceived = kLastDecoded + 0x7fff + 1; + constexpr bool kDecodabilityFlag = true; + LossNotification loss_notification; + EXPECT_FALSE( + loss_notification.Set(kLastDecoded, kLastReceived, kDecodabilityFlag)); +} + +TEST(RtcpPacketLossNotificationTest, SetWithLegalValuesSucceeds) { + constexpr uint16_t kLastDecoded = 0x3c7b; + constexpr uint16_t kLastReceived = kLastDecoded + 0x7fff; + constexpr bool kDecodabilityFlag = true; + LossNotification loss_notification; + EXPECT_TRUE( + loss_notification.Set(kLastDecoded, kLastReceived, kDecodabilityFlag)); +} + +TEST(RtcpPacketLossNotificationTest, CreateProducesExpectedWireFormat) { + // Note that (0x6542 >> 1) is used just to make the pattern in kPacket + // more apparent; there's nothing truly special about the value, + // it's only an implementation detail that last-received is represented + // as a delta from last-decoded, and that this delta is shifted before + // it's put on the wire. + constexpr uint16_t kLastDecoded = 0x3c7b; + constexpr uint16_t kLastReceived = kLastDecoded + (0x6542 >> 1); + constexpr bool kDecodabilityFlag = true; + + const uint8_t kPacket[] = {0x8f, 206, 0x00, 0x04, 0x12, 0x34, 0x56, 0x78, // + 0xab, 0xcd, 0xef, 0x01, 'L', 'N', 'T', 'F', // + 0x3c, 0x7b, 0x65, 0x43}; + + LossNotification loss_notification; + loss_notification.SetSenderSsrc(0x12345678); + loss_notification.SetMediaSsrc(0xabcdef01); + ASSERT_TRUE( + loss_notification.Set(kLastDecoded, kLastReceived, kDecodabilityFlag)); + + rtc::Buffer packet = loss_notification.Build(); + + EXPECT_THAT(make_tuple(packet.data(), packet.size()), + ElementsAreArray(kPacket)); +} + +TEST(RtcpPacketLossNotificationTest, + ParseFailsOnTooSmallPacketToBeLossNotification) { + uint8_t packet[] = {0x8f, 206, 0x00, 0x04, 0x12, 0x34, 0x56, 0x78, // + 0xab, 0xcd, 0xef, 0x01, 'L', 'N', 'T', 'F', // + 0x3c, 0x7b, 0x65, 0x43}; + size_t packet_length_bytes = sizeof(packet); + + LossNotification loss_notification; + + // First, prove that the failure we're expecting to see happens because of + // the length, by showing that before the modification to the length, + // the packet was correctly parsed. + ASSERT_TRUE( + test::ParseSinglePacket(packet, packet_length_bytes, &loss_notification)); + + // Show that after shaving off a word, the packet is no longer parsable. + packet[3] -= 1; // Change the |length| field of the RTCP packet. + packet_length_bytes -= 4; // Effectively forget the last 32-bit word. + EXPECT_FALSE( + test::ParseSinglePacket(packet, packet_length_bytes, &loss_notification)); +} + +TEST(RtcpPacketLossNotificationTest, + ParseFailsWhenUniqueIdentifierIsNotLossNotification) { + uint8_t packet[] = {0x8f, 206, 0x00, 0x04, 0x12, 0x34, 0x56, 0x78, // + 0xab, 0xcd, 0xef, 0x01, 'L', 'N', 'T', 'F', // + 0x3c, 0x7b, 0x65, 0x43}; + + LossNotification loss_notification; + + // First, prove that the failure we're expecting to see happens because of + // the identifier, by showing that before the modification to the identifier, + // the packet was correctly parsed. + ASSERT_TRUE(test::ParseSinglePacket(packet, &loss_notification)); + + // Show that after changing the identifier, the packet is no longer parsable. + RTC_DCHECK_EQ(packet[12], 'L'); + RTC_DCHECK_EQ(packet[13], 'N'); + RTC_DCHECK_EQ(packet[14], 'T'); + RTC_DCHECK_EQ(packet[15], 'F'); + packet[14] = 'x'; + EXPECT_FALSE(test::ParseSinglePacket(packet, &loss_notification)); +} + +TEST(RtcpPacketLossNotificationTest, + ParseLegalLossNotificationMessagesCorrectly) { + // Note that (0x6542 >> 1) is used just to make the pattern in kPacket + // more apparent; there's nothing truly special about the value, + // it's only an implementation detail that last-received is represented + // as a delta from last-decoded, and that this delta is shifted before + // it's put on the wire. + constexpr uint16_t kLastDecoded = 0x3c7b; + constexpr uint16_t kLastReceived = kLastDecoded + (0x6542 >> 1); + constexpr bool kDecodabilityFlag = true; + + const uint8_t kPacket[] = {0x8f, 206, 0x00, 0x04, 0x12, 0x34, 0x56, 0x78, // + 0xab, 0xcd, 0xef, 0x01, 'L', 'N', 'T', 'F', // + 0x3c, 0x7b, 0x65, 0x43}; + + LossNotification loss_notification; + EXPECT_TRUE(test::ParseSinglePacket(kPacket, &loss_notification)); + + EXPECT_EQ(loss_notification.sender_ssrc(), 0x12345678u); + EXPECT_EQ(loss_notification.media_ssrc(), 0xabcdef01u); + EXPECT_EQ(loss_notification.last_decoded(), kLastDecoded); + EXPECT_EQ(loss_notification.last_received(), kLastReceived); + EXPECT_EQ(loss_notification.decodability_flag(), kDecodabilityFlag); +} + +} // namespace webrtc diff --git a/modules/rtp_rtcp/source/rtcp_packet/remb.cc b/modules/rtp_rtcp/source/rtcp_packet/remb.cc index bda50f5d25..87b03c63f8 100644 --- a/modules/rtp_rtcp/source/rtcp_packet/remb.cc +++ b/modules/rtp_rtcp/source/rtcp_packet/remb.cc @@ -57,7 +57,6 @@ bool Remb::Parse(const CommonHeader& packet) { } const uint8_t* const payload = packet.payload(); if (kUniqueIdentifier != ByteReader::ReadBigEndian(&payload[8])) { - RTC_LOG(LS_INFO) << "REMB identifier not found, not a REMB packet."; return false; } uint8_t number_of_ssrcs = payload[12]; diff --git a/modules/rtp_rtcp/source/rtcp_receiver.cc b/modules/rtp_rtcp/source/rtcp_receiver.cc index 383f785dfe..e047e0a48e 100644 --- a/modules/rtp_rtcp/source/rtcp_receiver.cc +++ b/modules/rtp_rtcp/source/rtcp_receiver.cc @@ -18,6 +18,7 @@ #include #include +#include "absl/memory/memory.h" #include "api/video/video_bitrate_allocation.h" #include "api/video/video_bitrate_allocator.h" #include "common_types.h" // NOLINT(build/include) @@ -26,6 +27,7 @@ #include "modules/rtp_rtcp/source/rtcp_packet/compound_packet.h" #include "modules/rtp_rtcp/source/rtcp_packet/extended_reports.h" #include "modules/rtp_rtcp/source/rtcp_packet/fir.h" +#include "modules/rtp_rtcp/source/rtcp_packet/loss_notification.h" #include "modules/rtp_rtcp/source/rtcp_packet/nack.h" #include "modules/rtp_rtcp/source/rtcp_packet/pli.h" #include "modules/rtp_rtcp/source/rtcp_packet/rapid_resync_request.h" @@ -73,6 +75,7 @@ struct RTCPReceiver::PacketInformation { uint32_t receiver_estimated_max_bitrate_bps = 0; std::unique_ptr transport_feedback; absl::optional target_bitrate_allocation; + std::unique_ptr loss_notification; }; // Structure for handing TMMBR and TMMBN rtcp messages (RFC5104, section 3.5.4). @@ -869,13 +872,27 @@ void RTCPReceiver::HandleSrReq(const CommonHeader& rtcp_block, void RTCPReceiver::HandlePsfbApp(const CommonHeader& rtcp_block, PacketInformation* packet_information) { - rtcp::Remb remb; - if (remb.Parse(rtcp_block)) { - packet_information->packet_type_flags |= kRtcpRemb; - packet_information->receiver_estimated_max_bitrate_bps = remb.bitrate_bps(); - return; + { + rtcp::Remb remb; + if (remb.Parse(rtcp_block)) { + packet_information->packet_type_flags |= kRtcpRemb; + packet_information->receiver_estimated_max_bitrate_bps = + remb.bitrate_bps(); + return; + } } + { + auto loss_notification = absl::make_unique(); + if (loss_notification->Parse(rtcp_block)) { + packet_information->packet_type_flags |= kRtcpLossNotification; + packet_information->loss_notification = std::move(loss_notification); + return; + } + } + + RTC_LOG(LS_WARNING) << "Unknown PSFB-APP packet."; + ++num_skipped_packets_; } @@ -1012,6 +1029,16 @@ void RTCPReceiver::TriggerCallbacksFromRtcpPacket( rtcp_bandwidth_observer_->OnReceivedEstimatedBitrate( packet_information.receiver_estimated_max_bitrate_bps); } + if (packet_information.packet_type_flags & kRtcpLossNotification) { + rtcp::LossNotification* loss_notification = + packet_information.loss_notification.get(); + RTC_DCHECK(loss_notification); + RTC_LOG(LS_VERBOSE) << "Incoming Loss Notification: (" + << loss_notification->last_decoded() << ", " + << loss_notification->last_received() << ", " + << loss_notification->decodability_flag() << ")."; + // TODO(eladalon): Notify observer. + } if ((packet_information.packet_type_flags & kRtcpSr) || (packet_information.packet_type_flags & kRtcpRr)) { int64_t now_ms = clock_->TimeInMilliseconds();