diff --git a/webrtc/modules/BUILD.gn b/webrtc/modules/BUILD.gn index 8ee981061b..6a1564b7d2 100644 --- a/webrtc/modules/BUILD.gn +++ b/webrtc/modules/BUILD.gn @@ -442,6 +442,7 @@ if (rtc_include_tests) { "rtp_rtcp/source/rtcp_packet/sdes_unittest.cc", "rtp_rtcp/source/rtcp_packet/sender_report_unittest.cc", "rtp_rtcp/source/rtcp_packet/sli_unittest.cc", + "rtp_rtcp/source/rtcp_packet/target_bitrate_unittest.cc", "rtp_rtcp/source/rtcp_packet/tmmbn_unittest.cc", "rtp_rtcp/source/rtcp_packet/tmmbr_unittest.cc", "rtp_rtcp/source/rtcp_packet/transport_feedback_unittest.cc", diff --git a/webrtc/modules/rtp_rtcp/BUILD.gn b/webrtc/modules/rtp_rtcp/BUILD.gn index 0a580a4edc..dcdb43ed94 100644 --- a/webrtc/modules/rtp_rtcp/BUILD.gn +++ b/webrtc/modules/rtp_rtcp/BUILD.gn @@ -85,6 +85,8 @@ rtc_static_library("rtp_rtcp") { "source/rtcp_packet/sender_report.h", "source/rtcp_packet/sli.cc", "source/rtcp_packet/sli.h", + "source/rtcp_packet/target_bitrate.cc", + "source/rtcp_packet/target_bitrate.h", "source/rtcp_packet/tmmb_item.cc", "source/rtcp_packet/tmmb_item.h", "source/rtcp_packet/tmmbn.cc", diff --git a/webrtc/modules/rtp_rtcp/rtp_rtcp.gypi b/webrtc/modules/rtp_rtcp/rtp_rtcp.gypi index ca9de8029c..cd8d715c3f 100644 --- a/webrtc/modules/rtp_rtcp/rtp_rtcp.gypi +++ b/webrtc/modules/rtp_rtcp/rtp_rtcp.gypi @@ -81,6 +81,8 @@ 'source/rtcp_packet/sender_report.h', 'source/rtcp_packet/sli.cc', 'source/rtcp_packet/sli.h', + 'source/rtcp_packet/target_bitrate.cc', + 'source/rtcp_packet/target_bitrate.h', 'source/rtcp_packet/tmmb_item.cc', 'source/rtcp_packet/tmmb_item.h', 'source/rtcp_packet/tmmbn.cc', diff --git a/webrtc/modules/rtp_rtcp/source/rtcp_packet/dlrr.cc b/webrtc/modules/rtp_rtcp/source/rtcp_packet/dlrr.cc index cc44886966..808a4d0d78 100644 --- a/webrtc/modules/rtp_rtcp/source/rtcp_packet/dlrr.cc +++ b/webrtc/modules/rtp_rtcp/source/rtcp_packet/dlrr.cc @@ -32,6 +32,7 @@ namespace rtcp { // | SSRC_2 (SSRC of second receiver) | sub- // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block // : ... : 2 + bool Dlrr::Parse(const uint8_t* buffer, uint16_t block_length_32bits) { RTC_DCHECK(buffer[0] == kBlockType); // kReserved = buffer[1]; diff --git a/webrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports.cc b/webrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports.cc index 5775dd4ffc..4c02c1ab1a 100644 --- a/webrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports.cc +++ b/webrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports.cc @@ -55,6 +55,7 @@ bool ExtendedReports::Parse(const CommonHeader& packet) { rrtr_block_.reset(); dlrr_block_.ClearItems(); voip_metric_block_.reset(); + target_bitrate_ = rtc::Optional(); const uint8_t* current_block = packet.payload() + kXrBaseLength; const uint8_t* const packet_end = @@ -80,6 +81,9 @@ bool ExtendedReports::Parse(const CommonHeader& packet) { case VoipMetric::kBlockType: ParseVoipMetricBlock(current_block, block_length); break; + case TargetBitrate::kBlockType: + ParseTargetBitrateBlock(current_block, block_length); + break; default: // Unknown block, ignore. LOG(LS_WARNING) << "Unknown extended report block type " << block_type; @@ -107,6 +111,13 @@ void ExtendedReports::SetVoipMetric(const VoipMetric& voip_metric) { voip_metric_block_.emplace(voip_metric); } +void ExtendedReports::SetTargetBitrate(const TargetBitrate& bitrate) { + if (target_bitrate_) + LOG(LS_WARNING) << "TargetBitrate already set, overwriting."; + + target_bitrate_ = rtc::Optional(bitrate); +} + bool ExtendedReports::Create(uint8_t* packet, size_t* index, size_t max_length, @@ -132,10 +143,20 @@ bool ExtendedReports::Create(uint8_t* packet, voip_metric_block_->Create(packet + *index); *index += VoipMetric::kLength; } + if (target_bitrate_) { + target_bitrate_->Create(packet + *index); + *index += target_bitrate_->BlockLength(); + } RTC_CHECK_EQ(*index, index_end); return true; } +size_t ExtendedReports::TargetBitrateLength() const { + if (target_bitrate_) + return target_bitrate_->BlockLength(); + return 0; +} + void ExtendedReports::ParseRrtrBlock(const uint8_t* block, uint16_t block_length) { if (block_length != Rrtr::kBlockLength) { @@ -175,5 +196,12 @@ void ExtendedReports::ParseVoipMetricBlock(const uint8_t* block, voip_metric_block_.emplace(); voip_metric_block_->Parse(block); } + +void ExtendedReports::ParseTargetBitrateBlock(const uint8_t* block, + uint16_t block_length) { + target_bitrate_ = rtc::Optional(TargetBitrate()); + if (!target_bitrate_->Parse(block, block_length)) + target_bitrate_ = rtc::Optional(); +} } // namespace rtcp } // namespace webrtc diff --git a/webrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports.h b/webrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports.h index c433477707..592ac3fe43 100644 --- a/webrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports.h +++ b/webrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports.h @@ -18,6 +18,7 @@ #include "webrtc/modules/rtp_rtcp/source/rtcp_packet.h" #include "webrtc/modules/rtp_rtcp/source/rtcp_packet/dlrr.h" #include "webrtc/modules/rtp_rtcp/source/rtcp_packet/rrtr.h" +#include "webrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate.h" #include "webrtc/modules/rtp_rtcp/source/rtcp_packet/voip_metric.h" namespace webrtc { @@ -40,6 +41,7 @@ class ExtendedReports : public RtcpPacket { void SetRrtr(const Rrtr& rrtr); void AddDlrrItem(const ReceiveTimeInfo& time_info); void SetVoipMetric(const VoipMetric& voip_metric); + void SetTargetBitrate(const TargetBitrate& target_bitrate); uint32_t sender_ssrc() const { return sender_ssrc_; } const rtc::Optional& rrtr() const { return rrtr_block_; } @@ -47,6 +49,9 @@ class ExtendedReports : public RtcpPacket { const rtc::Optional& voip_metric() const { return voip_metric_block_; } + const rtc::Optional& target_bitrate() { + return target_bitrate_; + } protected: bool Create(uint8_t* packet, @@ -59,7 +64,7 @@ class ExtendedReports : public RtcpPacket { size_t BlockLength() const override { return kHeaderLength + kXrBaseLength + RrtrLength() + DlrrLength() + - VoipMetricLength(); + VoipMetricLength() + TargetBitrateLength(); } size_t RrtrLength() const { return rrtr_block_ ? Rrtr::kLength : 0; } @@ -67,15 +72,18 @@ class ExtendedReports : public RtcpPacket { size_t VoipMetricLength() const { return voip_metric_block_ ? VoipMetric::kLength : 0; } + size_t TargetBitrateLength() const; void ParseRrtrBlock(const uint8_t* block, uint16_t block_length); void ParseDlrrBlock(const uint8_t* block, uint16_t block_length); void ParseVoipMetricBlock(const uint8_t* block, uint16_t block_length); + void ParseTargetBitrateBlock(const uint8_t* block, uint16_t block_length); uint32_t sender_ssrc_; rtc::Optional rrtr_block_; Dlrr dlrr_block_; // Dlrr without items treated same as no dlrr block. rtc::Optional voip_metric_block_; + rtc::Optional target_bitrate_; RTC_DISALLOW_COPY_AND_ASSIGN(ExtendedReports); }; diff --git a/webrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate.cc b/webrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate.cc new file mode 100644 index 0000000000..e3a813a854 --- /dev/null +++ b/webrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate.cc @@ -0,0 +1,134 @@ +/* + * 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/rtcp_packet/target_bitrate.h" + +#include "webrtc/base/checks.h" +#include "webrtc/base/logging.h" +#include "webrtc/modules/rtp_rtcp/source/byte_io.h" + +namespace webrtc { +namespace rtcp { +constexpr size_t kTargetBitrateHeaderSizeBytes = 4; +constexpr uint8_t TargetBitrate::kBlockType; +const size_t TargetBitrate::kBitrateItemSizeBytes = 4; + +TargetBitrate::BitrateItem::BitrateItem() + : spatial_layer(0), temporal_layer(0), target_bitrate_kbps(0) {} + +TargetBitrate::BitrateItem::BitrateItem(uint8_t spatial_layer, + uint8_t temporal_layer, + uint32_t target_bitrate_kbps) + : spatial_layer(spatial_layer), + temporal_layer(temporal_layer), + target_bitrate_kbps(target_bitrate_kbps) {} + +// RFC 4585: Feedback format. +// +// Common packet format: +// +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | BT=42 | reserved | block length | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// Target bitrate item (repeat as many times as necessary). +// +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | S | T | Target Bitrate | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : ... : +// +// Spatial Layer (S): 4 bits +// Indicates which temporal layer this bitrate concerns. +// +// Temporal Layer (T): 4 bits +// Indicates which temporal layer this bitrate concerns. +// +// Target Bitrate: 24 bits +// The encoder target bitrate for this layer, in kbps. +// +// As an example of how S and T are intended to be used, VP8 simulcast will +// use a separate TargetBitrate message per stream, since they are transmitted +// on separate SSRCs, with temporal layers grouped by stream. +// If VP9 SVC is used, there will be only one SSRC, so each spatial and +// temporal layer combo used shall be specified in the TargetBitrate packet. + +TargetBitrate::TargetBitrate() {} +TargetBitrate::~TargetBitrate() {} + +void TargetBitrate::Create(uint8_t* buffer) const { + buffer[0] = kBlockType; + buffer[1] = 0; // Reserved. + const size_t block_length_words = (BlockLength() / 4) - 1; + ByteWriter::WriteBigEndian(&buffer[2], block_length_words); + + size_t index = kTargetBitrateHeaderSizeBytes; + for (const BitrateItem& item : bitrates_) { + buffer[index] = (item.spatial_layer << 4) | item.temporal_layer; + ByteWriter::WriteBigEndian(&buffer[index + 1], + item.target_bitrate_kbps); + index += kBitrateItemSizeBytes; + } +} + +bool TargetBitrate::Parse(const uint8_t* block, uint16_t block_length) { + if (block_length < 1) { + LOG(LS_WARNING) + << "Cannot parse TargetBitrate RTCP packet: Too little payload data (" + << kTargetBitrateHeaderSizeBytes << " bytes needed, got " + << block_length * 4 << ")."; + return false; + } + + // Validate block header (should already have been parsed and checked). + RTC_DCHECK_EQ(block[0], kBlockType); + RTC_DCHECK_EQ(block_length, ByteReader::ReadBigEndian(&block[2])); + + // Header specifies block length - 1, but since we ignore the header, which + // occupies exactly on block, we can just treat this as payload length. + const size_t payload_bytes = block_length * 4; + const size_t num_items = payload_bytes / kBitrateItemSizeBytes; + size_t index = kTargetBitrateHeaderSizeBytes; + bitrates_.clear(); + for (size_t i = 0; i < num_items; ++i) { + uint8_t layers = block[index]; + uint32_t bitrate_kbps = + ByteReader::ReadBigEndian(&block[index + 1]); + index += kBitrateItemSizeBytes; + AddTargetBitrate((layers >> 4) & 0x0F, layers & 0x0F, bitrate_kbps); + } + + return true; +} + +void TargetBitrate::AddTargetBitrate(uint8_t spatial_layer, + uint8_t temporal_layer, + uint32_t target_bitrate_kbps) { + RTC_DCHECK_LE(spatial_layer, 0x0F); + RTC_DCHECK_LE(temporal_layer, 0x0F); + RTC_DCHECK_LE(target_bitrate_kbps, 0x00FFFFFFU); + bitrates_.push_back( + BitrateItem(spatial_layer, temporal_layer, target_bitrate_kbps)); +} + +const std::vector& +TargetBitrate::GetTargetBitrates() const { + return bitrates_; +} + +size_t TargetBitrate::BlockLength() const { + return kTargetBitrateHeaderSizeBytes + + bitrates_.size() * kBitrateItemSizeBytes; +} + +} // namespace rtcp +} // namespace webrtc diff --git a/webrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate.h b/webrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate.h new file mode 100644 index 0000000000..013c222f8e --- /dev/null +++ b/webrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate.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_RTCP_PACKET_TARGET_BITRATE_H_ +#define WEBRTC_MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TARGET_BITRATE_H_ + +#include + +#include "webrtc/base/basictypes.h" + +namespace webrtc { +namespace rtcp { + +class TargetBitrate { + public: + // TODO(sprang): This block type is just a place holder. We need to get an + // id assigned by IANA. + static constexpr uint8_t kBlockType = 42; + static const size_t kBitrateItemSizeBytes; + + struct BitrateItem { + BitrateItem(); + BitrateItem(uint8_t spatial_layer, + uint8_t temporal_layer, + uint32_t target_bitrate_kbps); + + uint8_t spatial_layer; + uint8_t temporal_layer; + uint32_t target_bitrate_kbps; + }; + + TargetBitrate(); + ~TargetBitrate(); + + void AddTargetBitrate(uint8_t spatial_layer, + uint8_t temporal_layer, + uint32_t target_bitrate_kbps); + + const std::vector& GetTargetBitrates() const; + + bool Parse(const uint8_t* block, uint16_t block_length); + + void Create(uint8_t* buffer) const; + + size_t BlockLength() const; + + private: + std::vector bitrates_; +}; + +} // namespace rtcp +} // namespace webrtc +#endif // WEBRTC_MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TARGET_BITRATE_H_ diff --git a/webrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate_unittest.cc b/webrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate_unittest.cc new file mode 100644 index 0000000000..1c8f39de61 --- /dev/null +++ b/webrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate_unittest.cc @@ -0,0 +1,89 @@ +/* + * 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/rtcp_packet/target_bitrate.h" + +#include "webrtc/base/buffer.h" +#include "webrtc/test/gtest.h" +#include "webrtc/test/rtcp_packet_parser.h" +#include "webrtc/modules/rtp_rtcp/source/byte_io.h" +#include "webrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports.h" + +namespace webrtc { +namespace { +using BitrateItem = rtcp::TargetBitrate::BitrateItem; +using rtcp::TargetBitrate; +using test::ParseSinglePacket; + +constexpr uint32_t kSsrc = 0x12345678; + +// clang-format off +const uint8_t kPacket[] = { TargetBitrate::kBlockType, // Block ID. + 0x00, // Reserved. + 0x00, 0x04, // Length = 4 words. + 0x00, 0x01, 0x02, 0x03, // S0T0 0x010203 kbps. + 0x01, 0x02, 0x03, 0x04, // S0T1 0x020304 kbps. + 0x10, 0x03, 0x04, 0x05, // S1T0 0x030405 kbps. + 0x11, 0x04, 0x05, 0x06 }; // S1T1 0x040506 kbps. +constexpr size_t kPacketLengthBlocks = ((sizeof(kPacket) + 3) / 4) - 1; +// clang-format on + +void ExpectBirateItemEquals(const BitrateItem& expected, + const BitrateItem& actual) { + EXPECT_EQ(expected.spatial_layer, actual.spatial_layer); + EXPECT_EQ(expected.temporal_layer, actual.temporal_layer); + EXPECT_EQ(expected.target_bitrate_kbps, actual.target_bitrate_kbps); +} + +void CheckBitrateItems(const std::vector& bitrates) { + EXPECT_EQ(4U, bitrates.size()); + ExpectBirateItemEquals(BitrateItem(0, 0, 0x010203), bitrates[0]); + ExpectBirateItemEquals(BitrateItem(0, 1, 0x020304), bitrates[1]); + ExpectBirateItemEquals(BitrateItem(1, 0, 0x030405), bitrates[2]); + ExpectBirateItemEquals(BitrateItem(1, 1, 0x040506), bitrates[3]); +} + +} // namespace + +TEST(TargetBitrateTest, Parse) { + TargetBitrate target_bitrate; + EXPECT_TRUE(target_bitrate.Parse(kPacket, kPacketLengthBlocks)); + CheckBitrateItems(target_bitrate.GetTargetBitrates()); +} + +TEST(TargetBitrateTest, FullPacket) { + const size_t kXRHeaderSize = 8; // RTCP header (4) + SSRC (4). + const size_t kTotalSize = kXRHeaderSize + sizeof(kPacket); + uint8_t kRtcpPacket[kTotalSize] = {2 << 6, 207, 0x00, (kTotalSize / 4) - 1, + 0x12, 0x34, 0x56, 0x78}; // SSRC. + memcpy(&kRtcpPacket[kXRHeaderSize], kPacket, sizeof(kPacket)); + rtcp::ExtendedReports xr; + EXPECT_TRUE(ParseSinglePacket(kRtcpPacket, &xr)); + EXPECT_EQ(kSsrc, xr.sender_ssrc()); + const rtc::Optional& target_bitrate = xr.target_bitrate(); + ASSERT_TRUE(static_cast(target_bitrate)); + CheckBitrateItems(target_bitrate->GetTargetBitrates()); +} + +TEST(TargetBitrateTest, Create) { + TargetBitrate target_bitrate; + target_bitrate.AddTargetBitrate(0, 0, 0x010203); + target_bitrate.AddTargetBitrate(0, 1, 0x020304); + target_bitrate.AddTargetBitrate(1, 0, 0x030405); + target_bitrate.AddTargetBitrate(1, 1, 0x040506); + + uint8_t buffer[sizeof(kPacket)] = {}; + ASSERT_EQ(sizeof(kPacket), target_bitrate.BlockLength()); + target_bitrate.Create(buffer); + + EXPECT_EQ(0, memcmp(kPacket, buffer, sizeof(kPacket))); +} + +} // namespace webrtc