diff --git a/modules/rtp_rtcp/source/rtp_packet.cc b/modules/rtp_rtcp/source/rtp_packet.cc index b07341d2f8..5f919ff24e 100644 --- a/modules/rtp_rtcp/source/rtp_packet.cc +++ b/modules/rtp_rtcp/source/rtp_packet.cc @@ -18,6 +18,7 @@ #include "rtc_base/checks.h" #include "rtc_base/logging.h" #include "rtc_base/numerics/safe_conversions.h" +#include "rtc_base/strings/string_builder.h" namespace webrtc { namespace { @@ -627,4 +628,72 @@ bool RtpPacket::IsExtensionReserved(ExtensionType type) const { return FindExtensionInfo(id) != nullptr; } +bool RtpPacket::RemoveExtension(ExtensionType type) { + uint8_t id_to_remove = extensions_.GetId(type); + if (id_to_remove == ExtensionManager::kInvalidId) { + // Extension not registered. + RTC_LOG(LS_ERROR) << "Extension not registered, type=" << type + << ", packet=" << ToString(); + return false; + } + + // Rebuild new packet from scratch. + RtpPacket new_packet; + + new_packet.SetMarker(Marker()); + new_packet.SetPayloadType(PayloadType()); + new_packet.SetSequenceNumber(SequenceNumber()); + new_packet.SetTimestamp(Timestamp()); + new_packet.SetSsrc(Ssrc()); + new_packet.IdentifyExtensions(extensions_); + + // Copy all extensions, except the one we are removing. + bool found_extension = false; + for (const ExtensionInfo& ext : extension_entries_) { + if (ext.id == id_to_remove) { + found_extension = true; + } else { + auto extension_data = new_packet.AllocateRawExtension(ext.id, ext.length); + if (extension_data.size() != ext.length) { + RTC_LOG(LS_ERROR) << "Failed to allocate extension id=" << ext.id + << ", length=" << ext.length + << ", packet=" << ToString(); + return false; + } + + // Copy extension data to new packet. + memcpy(extension_data.data(), ReadAt(ext.offset), ext.length); + } + } + + if (!found_extension) { + RTC_LOG(LS_WARNING) << "Extension not present in RTP packet, type=" << type + << ", packet=" << ToString(); + return false; + } + + // Copy payload data to new packet. + memcpy(new_packet.AllocatePayload(payload_size()), payload().data(), + payload_size()); + + // Allocate padding -- must be last! + new_packet.SetPadding(padding_size()); + + // Success, replace current packet with newly built packet. + *this = new_packet; + return true; +} + +std::string RtpPacket::ToString() const { + rtc::StringBuilder result; + result << "{payload_type=" << payload_type_ << "marker=" << marker_ + << ", sequence_number=" << sequence_number_ + << ", padding_size=" << padding_size_ << ", timestamp=" << timestamp_ + << ", ssrc=" << ssrc_ << ", payload_offset=" << payload_offset_ + << ", payload_size=" << payload_size_ << ", total_size=" << size() + << "}"; + + return result.Release(); +} + } // namespace webrtc diff --git a/modules/rtp_rtcp/source/rtp_packet.h b/modules/rtp_rtcp/source/rtp_packet.h index 67d8df4894..c49e0709a3 100644 --- a/modules/rtp_rtcp/source/rtp_packet.h +++ b/modules/rtp_rtcp/source/rtp_packet.h @@ -10,6 +10,7 @@ #ifndef MODULES_RTP_RTCP_SOURCE_RTP_PACKET_H_ #define MODULES_RTP_RTCP_SOURCE_RTP_PACKET_H_ +#include #include #include "absl/types/optional.h" @@ -38,6 +39,9 @@ class RtpPacket { RtpPacket& operator=(const RtpPacket&) = default; // Parse and copy given buffer into Packet. + // Does not require extension map to be registered (map is only required to + // read or allocate extensions in methods GetExtension, AllocateExtension, + // etc.) bool Parse(const uint8_t* buffer, size_t size); bool Parse(rtc::ArrayView packet); @@ -89,6 +93,12 @@ class RtpPacket { // which are modified after FEC protection is generated. void CopyAndZeroMutableExtensions(rtc::ArrayView buffer) const; + // Removes extension of given |type|, returns false is extension was not + // registered in packet's extension map or not present in the packet. Only + // extension that should be removed must be registered, other extensions may + // not be registered and will be preserved as is. + bool RemoveExtension(ExtensionType type); + // Writes csrc list. Assumes: // a) There is enough room left in buffer. // b) Extension headers, payload or padding data has not already been added. @@ -134,6 +144,9 @@ class RtpPacket { bool SetPadding(size_t padding_size); + // Returns debug string of RTP packet (without detailed extension info). + std::string ToString() const; + private: struct ExtensionInfo { explicit ExtensionInfo(uint8_t id) : ExtensionInfo(id, 0, 0) {} @@ -168,6 +181,7 @@ class RtpPacket { uint8_t* WriteAt(size_t offset) { return buffer_.data() + offset; } void WriteAt(size_t offset, uint8_t byte) { buffer_.data()[offset] = byte; } + const uint8_t* ReadAt(size_t offset) const { return buffer_.data() + offset; } // Header. bool marker_; diff --git a/modules/rtp_rtcp/source/rtp_packet_unittest.cc b/modules/rtp_rtcp/source/rtp_packet_unittest.cc index 9148c67beb..b5834778d5 100644 --- a/modules/rtp_rtcp/source/rtp_packet_unittest.cc +++ b/modules/rtp_rtcp/source/rtp_packet_unittest.cc @@ -1031,4 +1031,91 @@ TEST(RtpPacketTest, IsExtensionReserved) { EXPECT_TRUE(packet.IsExtensionReserved()); } +// Tests that RtpPacket::RemoveExtension can successfully remove extensions. +TEST(RtpPacketTest, RemoveMultipleExtensions) { + RtpPacketToSend::ExtensionManager extensions; + extensions.Register(kRtpExtensionTransmissionTimeOffset, + kTransmissionOffsetExtensionId); + extensions.Register(kRtpExtensionAudioLevel, kAudioLevelExtensionId); + RtpPacketToSend packet(&extensions); + packet.SetPayloadType(kPayloadType); + packet.SetSequenceNumber(kSeqNum); + packet.SetTimestamp(kTimestamp); + packet.SetSsrc(kSsrc); + packet.SetExtension(kTimeOffset); + packet.SetExtension(kVoiceActive, kAudioLevel); + + EXPECT_THAT(kPacketWithTOAndAL, + ElementsAreArray(packet.data(), packet.size())); + + // Remove one of two extensions. + EXPECT_TRUE(packet.RemoveExtension(kRtpExtensionAudioLevel)); + + EXPECT_THAT(kPacketWithTO, ElementsAreArray(packet.data(), packet.size())); + + // Remove remaining extension. + EXPECT_TRUE(packet.RemoveExtension(kRtpExtensionTransmissionTimeOffset)); + + EXPECT_THAT(kMinimumPacket, ElementsAreArray(packet.data(), packet.size())); +} + +// Tests that RtpPacket::RemoveExtension can successfully remove extension when +// other extensions are present but not registered. +TEST(RtpPacketTest, RemoveExtensionPreservesOtherUnregisteredExtensions) { + RtpPacketToSend::ExtensionManager extensions; + extensions.Register(kRtpExtensionTransmissionTimeOffset, + kTransmissionOffsetExtensionId); + extensions.Register(kRtpExtensionAudioLevel, kAudioLevelExtensionId); + RtpPacketToSend packet(&extensions); + packet.SetPayloadType(kPayloadType); + packet.SetSequenceNumber(kSeqNum); + packet.SetTimestamp(kTimestamp); + packet.SetSsrc(kSsrc); + packet.SetExtension(kTimeOffset); + packet.SetExtension(kVoiceActive, kAudioLevel); + + EXPECT_THAT(kPacketWithTOAndAL, + ElementsAreArray(packet.data(), packet.size())); + + // "Unregister" kRtpExtensionTransmissionTimeOffset. + RtpPacketToSend::ExtensionManager extensions1; + extensions1.Register(kRtpExtensionAudioLevel, kAudioLevelExtensionId); + packet.IdentifyExtensions(extensions1); + + // Make sure we can not delete extension which is set but not registered. + EXPECT_FALSE(packet.RemoveExtension(kRtpExtensionTransmissionTimeOffset)); + + // Remove registered extension. + EXPECT_TRUE(packet.RemoveExtension(kRtpExtensionAudioLevel)); + + EXPECT_THAT(kPacketWithTO, ElementsAreArray(packet.data(), packet.size())); +} + +// Tests that RtpPacket::RemoveExtension fails if extension is not present or +// not registered and does not modify packet. +TEST(RtpPacketTest, RemoveExtensionFailure) { + RtpPacketToSend::ExtensionManager extensions; + extensions.Register(kRtpExtensionTransmissionTimeOffset, + kTransmissionOffsetExtensionId); + extensions.Register(kRtpExtensionAudioLevel, kAudioLevelExtensionId); + RtpPacketToSend packet(&extensions); + packet.SetPayloadType(kPayloadType); + packet.SetSequenceNumber(kSeqNum); + packet.SetTimestamp(kTimestamp); + packet.SetSsrc(kSsrc); + packet.SetExtension(kTimeOffset); + + EXPECT_THAT(kPacketWithTO, ElementsAreArray(packet.data(), packet.size())); + + // Try to remove extension, which was registered, but not set. + EXPECT_FALSE(packet.RemoveExtension(kRtpExtensionAudioLevel)); + + EXPECT_THAT(kPacketWithTO, ElementsAreArray(packet.data(), packet.size())); + + // Try to remove extension, which was not registered. + EXPECT_FALSE(packet.RemoveExtension(kRtpExtensionPlayoutDelay)); + + EXPECT_THAT(kPacketWithTO, ElementsAreArray(packet.data(), packet.size())); +} + } // namespace webrtc