Add Slice method to CopyOnWriteBuffer and use it in FEC code.

This avoids unnecessary memcpy calls.

Bug: webrtc:10750
Change-Id: I73fe8f1c9659f2c5e59d7fb97b80349a3504a34a
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/145320
Reviewed-by: Karl Wiberg <kwiberg@webrtc.org>
Reviewed-by: Rasmus Brandt <brandtr@webrtc.org>
Reviewed-by: Danil Chapovalov <danilchap@webrtc.org>
Commit-Queue: Ilya Nikolaevskiy <ilnik@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#29315}
diff --git a/modules/rtp_rtcp/source/flexfec_receiver.cc b/modules/rtp_rtcp/source/flexfec_receiver.cc
index ba09db9..28c8b26 100644
--- a/modules/rtp_rtcp/source/flexfec_receiver.cc
+++ b/modules/rtp_rtcp/source/flexfec_receiver.cc
@@ -109,10 +109,8 @@
     // Insert packet payload into erasure code.
     received_packet->pkt = rtc::scoped_refptr<ForwardErrorCorrection::Packet>(
         new ForwardErrorCorrection::Packet());
-    // TODO(ilnik): after slice capability is added to COW, use it here instead
-    // of initializing COW buffer with ArrayView.
-    auto payload = packet.payload();
-    received_packet->pkt->data.SetData(payload.data(), payload.size());
+    received_packet->pkt->data =
+        packet.Buffer().Slice(packet.headers_size(), packet.payload_size());
   } else {
     // This is a media packet, or a FlexFEC packet belonging to some
     // other FlexFEC stream.
diff --git a/modules/rtp_rtcp/source/ulpfec_receiver_impl.cc b/modules/rtp_rtcp/source/ulpfec_receiver_impl.cc
index 43ce2b0..24348f3 100644
--- a/modules/rtp_rtcp/source/ulpfec_receiver_impl.cc
+++ b/modules/rtp_rtcp/source/ulpfec_receiver_impl.cc
@@ -119,13 +119,14 @@
     packet_counter_.first_packet_time_ms = rtc::TimeMillis();
   }
 
-  auto red_payload = rtp_packet.payload().subview(kRedHeaderLength);
   if (received_packet->is_fec) {
     ++packet_counter_.num_fec_packets;
-
     // everything behind the RED header
-    received_packet->pkt->data.SetData(red_payload.data(), red_payload.size());
+    received_packet->pkt->data =
+        rtp_packet.Buffer().Slice(rtp_packet.headers_size() + kRedHeaderLength,
+                                  rtp_packet.payload_size() - kRedHeaderLength);
   } else {
+    auto red_payload = rtp_packet.payload().subview(kRedHeaderLength);
     received_packet->pkt->data.EnsureCapacity(rtp_packet.headers_size() +
                                               red_payload.size());
     // Copy RTP header.
@@ -170,6 +171,7 @@
       crit_sect_.Enter();
       // Create a packet with the buffer to modify it.
       RtpPacketReceived rtp_packet;
+      const uint8_t* const original_data = packet->data.cdata();
       rtp_packet.Parse(packet->data);
       rtp_packet.IdentifyExtensions(extensions_);
       // Reset buffer reference, so zeroing would work on a buffer with a
@@ -177,6 +179,8 @@
       packet->data = rtc::CopyOnWriteBuffer(0);
       rtp_packet.ZeroMutableExtensions();
       packet->data = rtp_packet.Buffer();
+      // Ensure that zeroing of extensions was done in place.
+      RTC_DCHECK_EQ(packet->data.cdata(), original_data);
     }
     fec_->DecodeFec(*received_packet, &recovered_packets_);
   }
diff --git a/rtc_base/copy_on_write_buffer.cc b/rtc_base/copy_on_write_buffer.cc
index de003f2..73182a1 100644
--- a/rtc_base/copy_on_write_buffer.cc
+++ b/rtc_base/copy_on_write_buffer.cc
@@ -14,40 +14,47 @@
 
 namespace rtc {
 
-CopyOnWriteBuffer::CopyOnWriteBuffer() {
+CopyOnWriteBuffer::CopyOnWriteBuffer() : offset_(0), size_(0) {
   RTC_DCHECK(IsConsistent());
 }
 
 CopyOnWriteBuffer::CopyOnWriteBuffer(const CopyOnWriteBuffer& buf)
-    : buffer_(buf.buffer_) {}
+    : buffer_(buf.buffer_), offset_(buf.offset_), size_(buf.size_) {}
 
 CopyOnWriteBuffer::CopyOnWriteBuffer(CopyOnWriteBuffer&& buf)
-    : buffer_(std::move(buf.buffer_)) {}
+    : buffer_(std::move(buf.buffer_)), offset_(buf.offset_), size_(buf.size_) {
+  buf.offset_ = 0;
+  buf.size_ = 0;
+  RTC_DCHECK(IsConsistent());
+}
 
 CopyOnWriteBuffer::CopyOnWriteBuffer(const std::string& s)
     : CopyOnWriteBuffer(s.data(), s.length()) {}
 
 CopyOnWriteBuffer::CopyOnWriteBuffer(size_t size)
-    : buffer_(size > 0 ? new RefCountedObject<Buffer>(size) : nullptr) {
+    : buffer_(size > 0 ? new RefCountedObject<Buffer>(size) : nullptr),
+      offset_(0),
+      size_(size) {
   RTC_DCHECK(IsConsistent());
 }
 
 CopyOnWriteBuffer::CopyOnWriteBuffer(size_t size, size_t capacity)
     : buffer_(size > 0 || capacity > 0
                   ? new RefCountedObject<Buffer>(size, capacity)
-                  : nullptr) {
+                  : nullptr),
+      offset_(0),
+      size_(size) {
   RTC_DCHECK(IsConsistent());
 }
 
 CopyOnWriteBuffer::~CopyOnWriteBuffer() = default;
 
 bool CopyOnWriteBuffer::operator==(const CopyOnWriteBuffer& buf) const {
-  // Must either use the same buffer internally or have the same contents.
+  // Must either be the same view of the same buffer or have the same contents.
   RTC_DCHECK(IsConsistent());
   RTC_DCHECK(buf.IsConsistent());
-  return buffer_.get() == buf.buffer_.get() ||
-         (buffer_.get() && buf.buffer_.get() &&
-          *buffer_.get() == *buf.buffer_.get());
+  return size_ == buf.size_ &&
+         (cdata() == buf.cdata() || memcmp(cdata(), buf.cdata(), size_) == 0);
 }
 
 void CopyOnWriteBuffer::SetSize(size_t size) {
@@ -55,35 +62,39 @@
   if (!buffer_) {
     if (size > 0) {
       buffer_ = new RefCountedObject<Buffer>(size);
+      offset_ = 0;
+      size_ = size;
     }
     RTC_DCHECK(IsConsistent());
     return;
   }
 
-  // Clone data if referenced.
-  if (!buffer_->HasOneRef()) {
-    buffer_ = new RefCountedObject<Buffer>(buffer_->data(),
-                                           std::min(buffer_->size(), size),
-                                           std::max(buffer_->capacity(), size));
+  if (size <= size_) {
+    size_ = size;
+    return;
   }
-  buffer_->SetSize(size);
+
+  UnshareAndEnsureCapacity(std::max(capacity(), size));
+  buffer_->SetSize(size + offset_);
+  size_ = size;
   RTC_DCHECK(IsConsistent());
 }
 
-void CopyOnWriteBuffer::EnsureCapacity(size_t capacity) {
+void CopyOnWriteBuffer::EnsureCapacity(size_t new_capacity) {
   RTC_DCHECK(IsConsistent());
   if (!buffer_) {
-    if (capacity > 0) {
-      buffer_ = new RefCountedObject<Buffer>(0, capacity);
+    if (new_capacity > 0) {
+      buffer_ = new RefCountedObject<Buffer>(0, new_capacity);
+      offset_ = 0;
+      size_ = 0;
     }
     RTC_DCHECK(IsConsistent());
     return;
-  } else if (capacity <= buffer_->capacity()) {
+  } else if (new_capacity <= capacity()) {
     return;
   }
 
-  CloneDataIfReferenced(std::max(buffer_->capacity(), capacity));
-  buffer_->EnsureCapacity(capacity);
+  UnshareAndEnsureCapacity(new_capacity);
   RTC_DCHECK(IsConsistent());
 }
 
@@ -94,18 +105,21 @@
   if (buffer_->HasOneRef()) {
     buffer_->Clear();
   } else {
-    buffer_ = new RefCountedObject<Buffer>(0, buffer_->capacity());
+    buffer_ = new RefCountedObject<Buffer>(0, capacity());
   }
+  offset_ = 0;
+  size_ = 0;
   RTC_DCHECK(IsConsistent());
 }
 
-void CopyOnWriteBuffer::CloneDataIfReferenced(size_t new_capacity) {
-  if (buffer_->HasOneRef()) {
+void CopyOnWriteBuffer::UnshareAndEnsureCapacity(size_t new_capacity) {
+  if (buffer_->HasOneRef() && new_capacity <= capacity()) {
     return;
   }
 
-  buffer_ = new RefCountedObject<Buffer>(buffer_->data(), buffer_->size(),
+  buffer_ = new RefCountedObject<Buffer>(buffer_->data() + offset_, size_,
                                          new_capacity);
+  offset_ = 0;
   RTC_DCHECK(IsConsistent());
 }
 
diff --git a/rtc_base/copy_on_write_buffer.h b/rtc_base/copy_on_write_buffer.h
index c60e78b..ea4868f 100644
--- a/rtc_base/copy_on_write_buffer.h
+++ b/rtc_base/copy_on_write_buffer.h
@@ -56,6 +56,8 @@
       : CopyOnWriteBuffer(size, capacity) {
     if (buffer_) {
       std::memcpy(buffer_->data(), data, size);
+      offset_ = 0;
+      size_ = size;
     }
   }
 
@@ -88,8 +90,8 @@
     if (!buffer_) {
       return nullptr;
     }
-    CloneDataIfReferenced(buffer_->capacity());
-    return buffer_->data<T>();
+    UnshareAndEnsureCapacity(capacity());
+    return buffer_->data<T>() + offset_;
   }
 
   // Get const pointer to the data. This will not create a copy of the
@@ -102,17 +104,17 @@
     if (!buffer_) {
       return nullptr;
     }
-    return buffer_->data<T>();
+    return buffer_->data<T>() + offset_;
   }
 
   size_t size() const {
     RTC_DCHECK(IsConsistent());
-    return buffer_ ? buffer_->size() : 0;
+    return size_;
   }
 
   size_t capacity() const {
     RTC_DCHECK(IsConsistent());
-    return buffer_ ? buffer_->capacity() : 0;
+    return buffer_ ? buffer_->capacity() - offset_ : 0;
   }
 
   CopyOnWriteBuffer& operator=(const CopyOnWriteBuffer& buf) {
@@ -120,6 +122,8 @@
     RTC_DCHECK(buf.IsConsistent());
     if (&buf != this) {
       buffer_ = buf.buffer_;
+      offset_ = buf.offset_;
+      size_ = buf.size_;
     }
     return *this;
   }
@@ -128,6 +132,10 @@
     RTC_DCHECK(IsConsistent());
     RTC_DCHECK(buf.IsConsistent());
     buffer_ = std::move(buf.buffer_);
+    offset_ = buf.offset_;
+    size_ = buf.size_;
+    buf.offset_ = 0;
+    buf.size_ = 0;
     return *this;
   }
 
@@ -157,10 +165,13 @@
     if (!buffer_) {
       buffer_ = size > 0 ? new RefCountedObject<Buffer>(data, size) : nullptr;
     } else if (!buffer_->HasOneRef()) {
-      buffer_ = new RefCountedObject<Buffer>(data, size, buffer_->capacity());
+      buffer_ = new RefCountedObject<Buffer>(data, size, capacity());
     } else {
       buffer_->SetData(data, size);
     }
+    offset_ = 0;
+    size_ = size;
+
     RTC_DCHECK(IsConsistent());
   }
 
@@ -177,6 +188,8 @@
     RTC_DCHECK(buf.IsConsistent());
     if (&buf != this) {
       buffer_ = buf.buffer_;
+      offset_ = buf.offset_;
+      size_ = buf.size_;
     }
   }
 
@@ -188,13 +201,19 @@
     RTC_DCHECK(IsConsistent());
     if (!buffer_) {
       buffer_ = new RefCountedObject<Buffer>(data, size);
+      offset_ = 0;
+      size_ = size;
       RTC_DCHECK(IsConsistent());
       return;
     }
 
-    CloneDataIfReferenced(
-        std::max(buffer_->capacity(), buffer_->size() + size));
+    UnshareAndEnsureCapacity(std::max(capacity(), size_ + size));
+
+    buffer_->SetSize(offset_ +
+                     size_);  // Remove data to the right of the slice.
     buffer_->AppendData(data, size);
+    size_ += size;
+
     RTC_DCHECK(IsConsistent());
   }
 
@@ -228,18 +247,41 @@
   // Swaps two buffers.
   friend void swap(CopyOnWriteBuffer& a, CopyOnWriteBuffer& b) {
     std::swap(a.buffer_, b.buffer_);
+    std::swap(a.offset_, b.offset_);
+    std::swap(a.size_, b.size_);
+  }
+
+  CopyOnWriteBuffer Slice(size_t offset, size_t length) const {
+    CopyOnWriteBuffer slice(*this);
+    RTC_DCHECK_LE(offset, size_);
+    RTC_DCHECK_LE(length + offset, size_);
+    slice.offset_ += offset;
+    slice.size_ = length;
+    return slice;
   }
 
  private:
   // Create a copy of the underlying data if it is referenced from other Buffer
-  // objects.
-  void CloneDataIfReferenced(size_t new_capacity);
+  // objects or there is not enough capacity.
+  void UnshareAndEnsureCapacity(size_t new_capacity);
 
   // Pre- and postcondition of all methods.
-  bool IsConsistent() const { return (!buffer_ || buffer_->capacity() > 0); }
+  bool IsConsistent() const {
+    if (buffer_) {
+      return buffer_->capacity() > 0 && offset_ <= buffer_->size() &&
+             offset_ + size_ <= buffer_->size();
+    } else {
+      return size_ == 0 && offset_ == 0;
+    }
+  }
 
   // buffer_ is either null, or points to an rtc::Buffer with capacity > 0.
   scoped_refptr<RefCountedObject<Buffer>> buffer_;
+  // This buffer may represent a slice of a original data.
+  size_t offset_;  // Offset of a current slice in the original data in buffer_.
+                   // Should be 0 if the buffer_ is empty.
+  size_t size_;    // Size of a current slice in the original data in buffer_.
+                   // Should be 0 if the buffer_ is empty.
 };
 
 }  // namespace rtc
diff --git a/rtc_base/copy_on_write_buffer_unittest.cc b/rtc_base/copy_on_write_buffer_unittest.cc
index fc569bd..b35cd79 100644
--- a/rtc_base/copy_on_write_buffer_unittest.cc
+++ b/rtc_base/copy_on_write_buffer_unittest.cc
@@ -319,4 +319,50 @@
   EXPECT_EQ(0, memcmp(buf2.cdata(), kTestData, 3));
 }
 
+TEST(CopyOnWriteBufferTest, CreateSlice) {
+  CopyOnWriteBuffer buf(kTestData, 10, 10);
+  CopyOnWriteBuffer slice = buf.Slice(3, 4);
+  EXPECT_EQ(slice.size(), 4u);
+  EXPECT_EQ(0, memcmp(buf.cdata() + 3, slice.cdata(), 4));
+}
+
+TEST(CopyOnWriteBufferTest, NoCopyDataOnSlice) {
+  CopyOnWriteBuffer buf(kTestData, 10, 10);
+  CopyOnWriteBuffer slice = buf.Slice(3, 4);
+  EXPECT_EQ(buf.cdata() + 3, slice.cdata());
+}
+
+TEST(CopyOnWriteBufferTest, WritingCopiesData) {
+  CopyOnWriteBuffer buf(kTestData, 10, 10);
+  CopyOnWriteBuffer slice = buf.Slice(3, 4);
+  slice[0] = 0xaa;
+  EXPECT_NE(buf.cdata() + 3, slice.cdata());
+  EXPECT_EQ(0, memcmp(buf.cdata(), kTestData, 10));
+}
+
+TEST(CopyOnWriteBufferTest, WritingToBufferDoesntAffectsSlice) {
+  CopyOnWriteBuffer buf(kTestData, 10, 10);
+  CopyOnWriteBuffer slice = buf.Slice(3, 4);
+  buf[0] = 0xaa;
+  EXPECT_NE(buf.cdata() + 3, slice.cdata());
+  EXPECT_EQ(0, memcmp(slice.cdata(), kTestData + 3, 4));
+}
+
+TEST(CopyOnWriteBufferTest, SliceOfASlice) {
+  CopyOnWriteBuffer buf(kTestData, 10, 10);
+  CopyOnWriteBuffer slice = buf.Slice(3, 7);
+  CopyOnWriteBuffer slice2 = slice.Slice(2, 3);
+  EXPECT_EQ(slice2.size(), 3u);
+  EXPECT_EQ(slice.cdata() + 2, slice2.cdata());
+  EXPECT_EQ(buf.cdata() + 5, slice2.cdata());
+}
+
+TEST(CopyOnWriteBufferTest, SlicesAreIndependent) {
+  CopyOnWriteBuffer buf(kTestData, 10, 10);
+  CopyOnWriteBuffer slice = buf.Slice(3, 7);
+  CopyOnWriteBuffer slice2 = buf.Slice(3, 7);
+  slice2[0] = 0xaa;
+  EXPECT_EQ(buf.cdata() + 3, slice.cdata());
+}
+
 }  // namespace rtc