Reland RtpTransceiverInterface: introduce SetOfferedRtpHeaderExtensions.

This change adds exposure of a new transceiver method for
modifying the extensions offered in the next SDP negotiation,
following spec details in https://w3c.github.io/webrtc-extensions/#rtcrtptransceiver-interface.

Features:
- The interface allows to control the negotiated direction as
  per https://tools.ietf.org/html/rfc5285#page-7.
- The interface allows to remove an extension from SDP
  negotiation by modifying the direction to
  RtpTransceiverDirection::kStopped.

Note: support for signalling directionality of header extensions
in the SDP isn't implemented yet.

https://chromestatus.com/feature/5680189201711104.
Intent to prototype: https://groups.google.com/a/chromium.org/g/blink-dev/c/65YdUi02yZk

Tested: new unit tests in CL and manual tests with downstream project.
Bug: chromium:1051821
Change-Id: I7a4c2f979a5e50e88d49598eacb76d24e81c7c7a
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/177348
Commit-Queue: Markus Handell <handellm@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#31554}
diff --git a/api/rtp_parameters.h b/api/rtp_parameters.h
index addd406..11335e9 100644
--- a/api/rtp_parameters.h
+++ b/api/rtp_parameters.h
@@ -222,7 +222,7 @@
   bool preferred_encrypt = false;
 
   // The direction of the extension. The kStopped value is only used with
-  // RtpTransceiverInterface::header_extensions_offered() and
+  // RtpTransceiverInterface::HeaderExtensionsToOffer() and
   // SetOfferedRtpHeaderExtensions().
   RtpTransceiverDirection direction = RtpTransceiverDirection::kSendRecv;
 
diff --git a/api/rtp_transceiver_interface.cc b/api/rtp_transceiver_interface.cc
index d4e2b26..e795e51 100644
--- a/api/rtp_transceiver_interface.cc
+++ b/api/rtp_transceiver_interface.cc
@@ -41,4 +41,10 @@
   return {};
 }
 
+webrtc::RTCError RtpTransceiverInterface::SetOfferedRtpHeaderExtensions(
+    rtc::ArrayView<const RtpHeaderExtensionCapability>
+        header_extensions_to_offer) {
+  return webrtc::RTCError(webrtc::RTCErrorType::UNSUPPORTED_OPERATION);
+}
+
 }  // namespace webrtc
diff --git a/api/rtp_transceiver_interface.h b/api/rtp_transceiver_interface.h
index 9dbafd4..13277d9 100644
--- a/api/rtp_transceiver_interface.h
+++ b/api/rtp_transceiver_interface.h
@@ -133,6 +133,13 @@
   virtual std::vector<RtpHeaderExtensionCapability> HeaderExtensionsToOffer()
       const;
 
+  // The SetOfferedRtpHeaderExtensions method modifies the next SDP negotiation
+  // so that it negotiates use of header extensions which are not kStopped.
+  // https://w3c.github.io/webrtc-extensions/#rtcrtptransceiver-interface
+  virtual webrtc::RTCError SetOfferedRtpHeaderExtensions(
+      rtc::ArrayView<const RtpHeaderExtensionCapability>
+          header_extensions_to_offer);
+
  protected:
   ~RtpTransceiverInterface() override = default;
 };
diff --git a/pc/media_session.cc b/pc/media_session.cc
index 38e74fb..c03b1be 100644
--- a/pc/media_session.cc
+++ b/pc/media_session.cc
@@ -55,6 +55,57 @@
   }
 }
 
+webrtc::RtpExtension RtpExtensionFromCapability(
+    const webrtc::RtpHeaderExtensionCapability& capability) {
+  return webrtc::RtpExtension(capability.uri,
+                              capability.preferred_id.value_or(1));
+}
+
+cricket::RtpHeaderExtensions RtpHeaderExtensionsFromCapabilities(
+    const std::vector<webrtc::RtpHeaderExtensionCapability>& capabilities) {
+  cricket::RtpHeaderExtensions exts;
+  for (const auto& capability : capabilities) {
+    exts.push_back(RtpExtensionFromCapability(capability));
+  }
+  return exts;
+}
+
+std::vector<webrtc::RtpHeaderExtensionCapability>
+UnstoppedRtpHeaderExtensionCapabilities(
+    std::vector<webrtc::RtpHeaderExtensionCapability> capabilities) {
+  capabilities.erase(
+      std::remove_if(
+          capabilities.begin(), capabilities.end(),
+          [](const webrtc::RtpHeaderExtensionCapability& capability) {
+            return capability.direction == RtpTransceiverDirection::kStopped;
+          }),
+      capabilities.end());
+  return capabilities;
+}
+
+bool IsCapabilityPresent(const webrtc::RtpHeaderExtensionCapability& capability,
+                         const cricket::RtpHeaderExtensions& extensions) {
+  return std::find_if(extensions.begin(), extensions.end(),
+                      [&capability](const webrtc::RtpExtension& extension) {
+                        return capability.uri == extension.uri;
+                      }) != extensions.end();
+}
+
+cricket::RtpHeaderExtensions UnstoppedOrPresentRtpHeaderExtensions(
+    const std::vector<webrtc::RtpHeaderExtensionCapability>& capabilities,
+    const cricket::RtpHeaderExtensions& unencrypted,
+    const cricket::RtpHeaderExtensions& encrypted) {
+  cricket::RtpHeaderExtensions extensions;
+  for (const auto& capability : capabilities) {
+    if (capability.direction != RtpTransceiverDirection::kStopped ||
+        IsCapabilityPresent(capability, unencrypted) ||
+        IsCapabilityPresent(capability, encrypted)) {
+      extensions.push_back(RtpExtensionFromCapability(capability));
+    }
+  }
+  return extensions;
+}
+
 }  // namespace
 
 namespace cricket {
@@ -630,7 +681,21 @@
   if (offer->type() == cricket::MEDIA_TYPE_VIDEO) {
     offer->set_rtcp_reduced_size(true);
   }
-  offer->set_rtp_header_extensions(rtp_extensions);
+
+  // Build the vector of header extensions with directions for this
+  // media_description's options.
+  RtpHeaderExtensions extensions;
+  for (auto extension_with_id : rtp_extensions) {
+    for (const auto& extension : media_description_options.header_extensions) {
+      if (extension_with_id.uri == extension.uri) {
+        // TODO(crbug.com/1051821): Configure the extension direction from
+        // the information in the media_description_options extension
+        // capability.
+        extensions.push_back(extension_with_id);
+      }
+    }
+  }
+  offer->set_rtp_header_extensions(extensions);
 
   AddSimulcastToMediaDescription(media_description_options, offer);
 
@@ -1160,7 +1225,7 @@
     const MediaSessionOptions& session_options,
     const SecurePolicy& sdes_policy,
     const CryptoParamsVec* current_cryptos,
-    const RtpHeaderExtensions& local_rtp_extenstions,
+    const RtpHeaderExtensions& local_rtp_extensions,
     UniqueRandomIdGenerator* ssrc_generator,
     bool enable_encrypted_rtp_header_extensions,
     StreamParamsVec* current_streams,
@@ -1169,7 +1234,7 @@
   answer->set_extmap_allow_mixed_enum(offer->extmap_allow_mixed_enum());
   RtpHeaderExtensions negotiated_rtp_extensions;
   NegotiateRtpHeaderExtensions(
-      local_rtp_extenstions, offer->rtp_header_extensions(),
+      local_rtp_extensions, offer->rtp_header_extensions(),
       enable_encrypted_rtp_header_extensions, &negotiated_rtp_extensions);
   answer->set_rtp_header_extensions(negotiated_rtp_extensions);
 
@@ -1344,12 +1409,8 @@
     : MediaSessionDescriptionFactory(transport_desc_factory, ssrc_generator) {
   channel_manager->GetSupportedAudioSendCodecs(&audio_send_codecs_);
   channel_manager->GetSupportedAudioReceiveCodecs(&audio_recv_codecs_);
-  audio_rtp_extensions_ =
-      channel_manager->GetDefaultEnabledAudioRtpHeaderExtensions();
   channel_manager->GetSupportedVideoSendCodecs(&video_send_codecs_);
   channel_manager->GetSupportedVideoReceiveCodecs(&video_recv_codecs_);
-  video_rtp_extensions_ =
-      channel_manager->GetDefaultEnabledVideoRtpHeaderExtensions();
   channel_manager->GetSupportedDataCodecs(&rtp_data_codecs_);
   ComputeAudioCodecsIntersectionAndUnion();
   ComputeVideoCodecsIntersectionAndUnion();
@@ -1412,22 +1473,11 @@
 }
 
 RtpHeaderExtensions
-MediaSessionDescriptionFactory::audio_rtp_header_extensions() const {
-  RtpHeaderExtensions extensions = audio_rtp_extensions_;
+MediaSessionDescriptionFactory::filtered_rtp_header_extensions(
+    RtpHeaderExtensions extensions) const {
   if (!is_unified_plan_) {
     RemoveUnifiedPlanExtensions(&extensions);
   }
-
-  return extensions;
-}
-
-RtpHeaderExtensions
-MediaSessionDescriptionFactory::video_rtp_header_extensions() const {
-  RtpHeaderExtensions extensions = video_rtp_extensions_;
-  if (!is_unified_plan_) {
-    RemoveUnifiedPlanExtensions(&extensions);
-  }
-
   return extensions;
 }
 
@@ -1462,12 +1512,10 @@
     // If application doesn't want CN codecs in offer.
     StripCNCodecs(&offer_audio_codecs);
   }
-
-  RtpHeaderExtensions audio_rtp_extensions;
-  RtpHeaderExtensions video_rtp_extensions;
-  GetRtpHdrExtsToOffer(current_active_contents,
-                       session_options.offer_extmap_allow_mixed,
-                       &audio_rtp_extensions, &video_rtp_extensions);
+  AudioVideoRtpHeaderExtensions extensions_with_ids =
+      GetOfferedRtpHeaderExtensionsWithIds(
+          current_active_contents, session_options.offer_extmap_allow_mixed,
+          session_options.media_description_options);
 
   auto offer = std::make_unique<SessionDescription>();
 
@@ -1487,18 +1535,20 @@
     }
     switch (media_description_options.type) {
       case MEDIA_TYPE_AUDIO:
-        if (!AddAudioContentForOffer(
-                media_description_options, session_options, current_content,
-                current_description, audio_rtp_extensions, offer_audio_codecs,
-                &current_streams, offer.get(), &ice_credentials)) {
+        if (!AddAudioContentForOffer(media_description_options, session_options,
+                                     current_content, current_description,
+                                     extensions_with_ids.audio,
+                                     offer_audio_codecs, &current_streams,
+                                     offer.get(), &ice_credentials)) {
           return nullptr;
         }
         break;
       case MEDIA_TYPE_VIDEO:
-        if (!AddVideoContentForOffer(
-                media_description_options, session_options, current_content,
-                current_description, video_rtp_extensions, offer_video_codecs,
-                &current_streams, offer.get(), &ice_credentials)) {
+        if (!AddVideoContentForOffer(media_description_options, session_options,
+                                     current_content, current_description,
+                                     extensions_with_ids.video,
+                                     offer_video_codecs, &current_streams,
+                                     offer.get(), &ice_credentials)) {
           return nullptr;
         }
         break;
@@ -1633,13 +1683,16 @@
         msection_index < current_description->contents().size()) {
       current_content = &current_description->contents()[msection_index];
     }
+    RtpHeaderExtensions header_extensions = RtpHeaderExtensionsFromCapabilities(
+        UnstoppedRtpHeaderExtensionCapabilities(
+            media_description_options.header_extensions));
     switch (media_description_options.type) {
       case MEDIA_TYPE_AUDIO:
         if (!AddAudioContentForAnswer(
                 media_description_options, session_options, offer_content,
                 offer, current_content, current_description,
-                bundle_transport.get(), answer_audio_codecs, &current_streams,
-                answer.get(), &ice_credentials)) {
+                bundle_transport.get(), answer_audio_codecs, header_extensions,
+                &current_streams, answer.get(), &ice_credentials)) {
           return nullptr;
         }
         break;
@@ -1647,8 +1700,8 @@
         if (!AddVideoContentForAnswer(
                 media_description_options, session_options, offer_content,
                 offer, current_content, current_description,
-                bundle_transport.get(), answer_video_codecs, &current_streams,
-                answer.get(), &ice_credentials)) {
+                bundle_transport.get(), answer_video_codecs, header_extensions,
+                &current_streams, answer.get(), &ice_credentials)) {
           return nullptr;
         }
         break;
@@ -1941,11 +1994,12 @@
                          &used_pltypes);
 }
 
-void MediaSessionDescriptionFactory::GetRtpHdrExtsToOffer(
+MediaSessionDescriptionFactory::AudioVideoRtpHeaderExtensions
+MediaSessionDescriptionFactory::GetOfferedRtpHeaderExtensionsWithIds(
     const std::vector<const ContentInfo*>& current_active_contents,
     bool extmap_allow_mixed,
-    RtpHeaderExtensions* offer_audio_extensions,
-    RtpHeaderExtensions* offer_video_extensions) const {
+    const std::vector<MediaDescriptionOptions>& media_description_options)
+    const {
   // All header extensions allocated from the same range to avoid potential
   // issues when using BUNDLE.
 
@@ -1959,6 +2013,7 @@
   RtpHeaderExtensions all_regular_extensions;
   RtpHeaderExtensions all_encrypted_extensions;
 
+  AudioVideoRtpHeaderExtensions offered_extensions;
   // First - get all extensions from the current description if the media type
   // is used.
   // Add them to |used_ids| so the local ids are not reused if a new media
@@ -1967,36 +2022,45 @@
     if (IsMediaContentOfType(content, MEDIA_TYPE_AUDIO)) {
       const AudioContentDescription* audio =
           content->media_description()->as_audio();
-      MergeRtpHdrExts(audio->rtp_header_extensions(), offer_audio_extensions,
+      MergeRtpHdrExts(audio->rtp_header_extensions(), &offered_extensions.audio,
                       &all_regular_extensions, &all_encrypted_extensions,
                       &used_ids);
     } else if (IsMediaContentOfType(content, MEDIA_TYPE_VIDEO)) {
       const VideoContentDescription* video =
           content->media_description()->as_video();
-      MergeRtpHdrExts(video->rtp_header_extensions(), offer_video_extensions,
+      MergeRtpHdrExts(video->rtp_header_extensions(), &offered_extensions.video,
                       &all_regular_extensions, &all_encrypted_extensions,
                       &used_ids);
     }
   }
 
-  // Add our default RTP header extensions that are not in the current
-  // description.
-  MergeRtpHdrExts(audio_rtp_header_extensions(), offer_audio_extensions,
-                  &all_regular_extensions, &all_encrypted_extensions,
-                  &used_ids);
-  MergeRtpHdrExts(video_rtp_header_extensions(), offer_video_extensions,
-                  &all_regular_extensions, &all_encrypted_extensions,
-                  &used_ids);
+  // Add all encountered header extensions in the media description options that
+  // are not in the current description.
 
+  for (const auto& entry : media_description_options) {
+    RtpHeaderExtensions filtered_extensions =
+        filtered_rtp_header_extensions(UnstoppedOrPresentRtpHeaderExtensions(
+            entry.header_extensions, all_regular_extensions,
+            all_encrypted_extensions));
+    if (entry.type == MEDIA_TYPE_AUDIO)
+      MergeRtpHdrExts(filtered_extensions, &offered_extensions.audio,
+                      &all_regular_extensions, &all_encrypted_extensions,
+                      &used_ids);
+    else if (entry.type == MEDIA_TYPE_VIDEO)
+      MergeRtpHdrExts(filtered_extensions, &offered_extensions.video,
+                      &all_regular_extensions, &all_encrypted_extensions,
+                      &used_ids);
+  }
   // TODO(jbauch): Support adding encrypted header extensions to existing
   // sessions.
   if (enable_encrypted_rtp_header_extensions_ &&
       current_active_contents.empty()) {
-    AddEncryptedVersionsOfHdrExts(offer_audio_extensions,
+    AddEncryptedVersionsOfHdrExts(&offered_extensions.audio,
                                   &all_encrypted_extensions, &used_ids);
-    AddEncryptedVersionsOfHdrExts(offer_video_extensions,
+    AddEncryptedVersionsOfHdrExts(&offered_extensions.video,
                                   &all_encrypted_extensions, &used_ids);
   }
+  return offered_extensions;
 }
 
 bool MediaSessionDescriptionFactory::AddTransportOffer(
@@ -2371,6 +2435,7 @@
     const SessionDescription* current_description,
     const TransportInfo* bundle_transport,
     const AudioCodecs& audio_codecs,
+    const RtpHeaderExtensions& default_audio_rtp_header_extensions,
     StreamParamsVec* current_streams,
     SessionDescription* answer,
     IceCredentialsIterator* ice_credentials) const {
@@ -2443,9 +2508,9 @@
   if (!CreateMediaContentAnswer(
           offer_audio_description, media_description_options, session_options,
           sdes_policy, GetCryptos(current_content),
-          audio_rtp_header_extensions(), ssrc_generator_,
-          enable_encrypted_rtp_header_extensions_, current_streams,
-          bundle_enabled, audio_answer.get())) {
+          filtered_rtp_header_extensions(default_audio_rtp_header_extensions),
+          ssrc_generator_, enable_encrypted_rtp_header_extensions_,
+          current_streams, bundle_enabled, audio_answer.get())) {
     return false;  // Fails the session setup.
   }
 
@@ -2481,6 +2546,7 @@
     const SessionDescription* current_description,
     const TransportInfo* bundle_transport,
     const VideoCodecs& video_codecs,
+    const RtpHeaderExtensions& default_video_rtp_header_extensions,
     StreamParamsVec* current_streams,
     SessionDescription* answer,
     IceCredentialsIterator* ice_credentials) const {
@@ -2561,9 +2627,9 @@
   if (!CreateMediaContentAnswer(
           offer_video_description, media_description_options, session_options,
           sdes_policy, GetCryptos(current_content),
-          video_rtp_header_extensions(), ssrc_generator_,
-          enable_encrypted_rtp_header_extensions_, current_streams,
-          bundle_enabled, video_answer.get())) {
+          filtered_rtp_header_extensions(default_video_rtp_header_extensions),
+          ssrc_generator_, enable_encrypted_rtp_header_extensions_,
+          current_streams, bundle_enabled, video_answer.get())) {
     return false;  // Failed the sessin setup.
   }
   bool secure = bundle_transport ? bundle_transport->description.secure()
diff --git a/pc/media_session.h b/pc/media_session.h
index 1c4b618..f305a62 100644
--- a/pc/media_session.h
+++ b/pc/media_session.h
@@ -78,6 +78,7 @@
   // stream information goes in the local descriptions.
   std::vector<SenderOptions> sender_options;
   std::vector<webrtc::RtpCodecCapability> codec_preferences;
+  std::vector<webrtc::RtpHeaderExtensionCapability> header_extensions;
 
  private:
   // Doesn't DCHECK on |type|.
@@ -146,19 +147,13 @@
   const AudioCodecs& audio_recv_codecs() const;
   void set_audio_codecs(const AudioCodecs& send_codecs,
                         const AudioCodecs& recv_codecs);
-  void set_audio_rtp_header_extensions(const RtpHeaderExtensions& extensions) {
-    audio_rtp_extensions_ = extensions;
-  }
-  RtpHeaderExtensions audio_rtp_header_extensions() const;
   const VideoCodecs& video_sendrecv_codecs() const;
   const VideoCodecs& video_send_codecs() const;
   const VideoCodecs& video_recv_codecs() const;
   void set_video_codecs(const VideoCodecs& send_codecs,
                         const VideoCodecs& recv_codecs);
-  void set_video_rtp_header_extensions(const RtpHeaderExtensions& extensions) {
-    video_rtp_extensions_ = extensions;
-  }
-  RtpHeaderExtensions video_rtp_header_extensions() const;
+  RtpHeaderExtensions filtered_rtp_header_extensions(
+      RtpHeaderExtensions extensions) const;
   const RtpDataCodecs& rtp_data_codecs() const { return rtp_data_codecs_; }
   void set_rtp_data_codecs(const RtpDataCodecs& codecs) {
     rtp_data_codecs_ = codecs;
@@ -183,6 +178,11 @@
       const SessionDescription* current_description) const;
 
  private:
+  struct AudioVideoRtpHeaderExtensions {
+    RtpHeaderExtensions audio;
+    RtpHeaderExtensions video;
+  };
+
   const AudioCodecs& GetAudioCodecsForOffer(
       const webrtc::RtpTransceiverDirection& direction) const;
   const AudioCodecs& GetAudioCodecsForAnswer(
@@ -204,11 +204,11 @@
       AudioCodecs* audio_codecs,
       VideoCodecs* video_codecs,
       RtpDataCodecs* rtp_data_codecs) const;
-  void GetRtpHdrExtsToOffer(
+  AudioVideoRtpHeaderExtensions GetOfferedRtpHeaderExtensionsWithIds(
       const std::vector<const ContentInfo*>& current_active_contents,
       bool extmap_allow_mixed,
-      RtpHeaderExtensions* audio_extensions,
-      RtpHeaderExtensions* video_extensions) const;
+      const std::vector<MediaDescriptionOptions>& media_description_options)
+      const;
   bool AddTransportOffer(const std::string& content_name,
                          const TransportOptions& transport_options,
                          const SessionDescription* current_desc,
@@ -292,6 +292,7 @@
       const SessionDescription* current_description,
       const TransportInfo* bundle_transport,
       const AudioCodecs& audio_codecs,
+      const RtpHeaderExtensions& default_audio_rtp_header_extensions,
       StreamParamsVec* current_streams,
       SessionDescription* answer,
       IceCredentialsIterator* ice_credentials) const;
@@ -305,6 +306,7 @@
       const SessionDescription* current_description,
       const TransportInfo* bundle_transport,
       const VideoCodecs& video_codecs,
+      const RtpHeaderExtensions& default_video_rtp_header_extensions,
       StreamParamsVec* current_streams,
       SessionDescription* answer,
       IceCredentialsIterator* ice_credentials) const;
@@ -333,14 +335,12 @@
   AudioCodecs audio_sendrecv_codecs_;
   // Union of send and recv.
   AudioCodecs all_audio_codecs_;
-  RtpHeaderExtensions audio_rtp_extensions_;
   VideoCodecs video_send_codecs_;
   VideoCodecs video_recv_codecs_;
   // Intersection of send and recv.
   VideoCodecs video_sendrecv_codecs_;
   // Union of send and recv.
   VideoCodecs all_video_codecs_;
-  RtpHeaderExtensions video_rtp_extensions_;
   RtpDataCodecs rtp_data_codecs_;
   // This object is not owned by the channel so it must outlive it.
   rtc::UniqueRandomIdGenerator* const ssrc_generator_;
diff --git a/pc/media_session_unittest.cc b/pc/media_session_unittest.cc
index ab3fc84..ac949fb 100644
--- a/pc/media_session_unittest.cc
+++ b/pc/media_session_unittest.cc
@@ -747,13 +747,10 @@
       const cricket::RtpHeaderExtensions& expectedAnswer) {
     MediaSessionOptions opts;
     AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);
-    f1_.set_audio_rtp_header_extensions(offered);
-    f1_.set_video_rtp_header_extensions(offered);
-    f2_.set_audio_rtp_header_extensions(local);
-    f2_.set_video_rtp_header_extensions(local);
-
+    SetAudioVideoRtpHeaderExtensions(offered, offered, &opts);
     std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, NULL);
     ASSERT_TRUE(offer.get() != NULL);
+    SetAudioVideoRtpHeaderExtensions(local, local, &opts);
     std::unique_ptr<SessionDescription> answer =
         f2_.CreateAnswer(offer.get(), opts, NULL);
 
@@ -765,6 +762,38 @@
         GetFirstVideoContentDescription(answer.get())->rtp_header_extensions());
   }
 
+  std::vector<webrtc::RtpHeaderExtensionCapability>
+  HeaderExtensionCapabilitiesFromRtpExtensions(
+      cricket::RtpHeaderExtensions extensions) {
+    std::vector<webrtc::RtpHeaderExtensionCapability> capabilities;
+    for (const auto& extension : extensions) {
+      webrtc::RtpHeaderExtensionCapability capability(
+          extension.uri, extension.id,
+          webrtc::RtpTransceiverDirection::kSendRecv);
+      capabilities.push_back(capability);
+    }
+    return capabilities;
+  }
+
+  void SetAudioVideoRtpHeaderExtensions(cricket::RtpHeaderExtensions audio_exts,
+                                        cricket::RtpHeaderExtensions video_exts,
+                                        MediaSessionOptions* opts) {
+    auto audio_caps = HeaderExtensionCapabilitiesFromRtpExtensions(audio_exts);
+    auto video_caps = HeaderExtensionCapabilitiesFromRtpExtensions(video_exts);
+    for (auto& entry : opts->media_description_options) {
+      switch (entry.type) {
+        case MEDIA_TYPE_AUDIO:
+          entry.header_extensions = audio_caps;
+          break;
+        case MEDIA_TYPE_VIDEO:
+          entry.header_extensions = video_caps;
+          break;
+        default:
+          break;
+      }
+    }
+  }
+
  protected:
   UniqueRandomIdGenerator ssrc_generator1;
   UniqueRandomIdGenerator ssrc_generator2;
@@ -1652,13 +1681,13 @@
 TEST_F(MediaSessionDescriptionFactoryTest, TestOfferAnswerWithRtpExtensions) {
   MediaSessionOptions opts;
   AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);
-  f1_.set_audio_rtp_header_extensions(MAKE_VECTOR(kAudioRtpExtension1));
-  f1_.set_video_rtp_header_extensions(MAKE_VECTOR(kVideoRtpExtension1));
-  f2_.set_audio_rtp_header_extensions(MAKE_VECTOR(kAudioRtpExtension2));
-  f2_.set_video_rtp_header_extensions(MAKE_VECTOR(kVideoRtpExtension2));
+  SetAudioVideoRtpHeaderExtensions(MAKE_VECTOR(kAudioRtpExtension1),
+                                   MAKE_VECTOR(kVideoRtpExtension1), &opts);
 
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, NULL);
   ASSERT_TRUE(offer.get() != NULL);
+  SetAudioVideoRtpHeaderExtensions(MAKE_VECTOR(kAudioRtpExtension2),
+                                   MAKE_VECTOR(kVideoRtpExtension2), &opts);
   std::unique_ptr<SessionDescription> answer =
       f2_.CreateAnswer(offer.get(), opts, NULL);
 
@@ -1707,21 +1736,21 @@
   MediaSessionOptions opts;
   AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);
 
-  const auto offered = MAKE_VECTOR(kRtpExtensionGenericFrameDescriptorUri00);
-  f1_.set_audio_rtp_header_extensions(offered);
-  f1_.set_video_rtp_header_extensions(offered);
-  const auto local = MAKE_VECTOR(kRtpExtensionTransportSequenceNumber01);
-  f2_.set_audio_rtp_header_extensions(local);
-  f2_.set_video_rtp_header_extensions(local);
+  SetAudioVideoRtpHeaderExtensions(
+      MAKE_VECTOR(kRtpExtensionGenericFrameDescriptorUri00),
+      MAKE_VECTOR(kRtpExtensionGenericFrameDescriptorUri00), &opts);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, nullptr);
+  SetAudioVideoRtpHeaderExtensions(
+      MAKE_VECTOR(kRtpExtensionTransportSequenceNumber01),
+      MAKE_VECTOR(kRtpExtensionTransportSequenceNumber01), &opts);
   std::unique_ptr<SessionDescription> answer =
       f2_.CreateAnswer(offer.get(), opts, nullptr);
   EXPECT_THAT(
       GetFirstAudioContentDescription(answer.get())->rtp_header_extensions(),
-      ElementsAreArray(offered));
+      ElementsAreArray(kRtpExtensionGenericFrameDescriptorUri00));
   EXPECT_THAT(
       GetFirstVideoContentDescription(answer.get())->rtp_header_extensions(),
-      ElementsAreArray(offered));
+      ElementsAreArray(kRtpExtensionGenericFrameDescriptorUri00));
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest,
@@ -1729,21 +1758,18 @@
   MediaSessionOptions opts;
   AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);
 
-  const auto offered = MAKE_VECTOR(kRtpExtensionGenericFrameDescriptorUri00);
-  f1_.set_audio_rtp_header_extensions(offered);
-  f1_.set_video_rtp_header_extensions(offered);
-  const auto local = MAKE_VECTOR(kRtpExtensionGenericFrameDescriptorUri00);
-  f2_.set_audio_rtp_header_extensions(local);
-  f2_.set_video_rtp_header_extensions(local);
+  SetAudioVideoRtpHeaderExtensions(
+      MAKE_VECTOR(kRtpExtensionGenericFrameDescriptorUri00),
+      MAKE_VECTOR(kRtpExtensionGenericFrameDescriptorUri00), &opts);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, nullptr);
   std::unique_ptr<SessionDescription> answer =
       f2_.CreateAnswer(offer.get(), opts, nullptr);
   EXPECT_THAT(
       GetFirstAudioContentDescription(answer.get())->rtp_header_extensions(),
-      ElementsAreArray(offered));
+      ElementsAreArray(kRtpExtensionGenericFrameDescriptorUri00));
   EXPECT_THAT(
       GetFirstVideoContentDescription(answer.get())->rtp_header_extensions(),
-      ElementsAreArray(offered));
+      ElementsAreArray(kRtpExtensionGenericFrameDescriptorUri00));
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest,
@@ -1752,10 +1778,10 @@
   AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);
 
   RtpExtension offer_dd(RtpExtension::kDependencyDescriptorUri, 7);
-  RtpExtension local_tsn(RtpExtension::kTransportSequenceNumberUri, 5);
-  f1_.set_video_rtp_header_extensions({offer_dd});
-  f2_.set_video_rtp_header_extensions({local_tsn});
+  SetAudioVideoRtpHeaderExtensions({}, {offer_dd}, &opts);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, nullptr);
+  RtpExtension local_tsn(RtpExtension::kTransportSequenceNumberUri, 5);
+  SetAudioVideoRtpHeaderExtensions({}, {local_tsn}, &opts);
   std::unique_ptr<SessionDescription> answer =
       f2_.CreateAnswer(offer.get(), opts, nullptr);
   EXPECT_THAT(
@@ -1770,9 +1796,9 @@
 
   RtpExtension offer_dd(RtpExtension::kDependencyDescriptorUri, 7);
   RtpExtension local_dd(RtpExtension::kDependencyDescriptorUri, 5);
-  f1_.set_video_rtp_header_extensions({offer_dd});
-  f2_.set_video_rtp_header_extensions({local_dd});
+  SetAudioVideoRtpHeaderExtensions({}, {offer_dd}, &opts);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, nullptr);
+  SetAudioVideoRtpHeaderExtensions({}, {local_dd}, &opts);
   std::unique_ptr<SessionDescription> answer =
       f2_.CreateAnswer(offer.get(), opts, nullptr);
   EXPECT_THAT(
@@ -1789,12 +1815,10 @@
       RtpExtension(RtpExtension::kAbsoluteCaptureTimeUri, 7)};
   const cricket::RtpHeaderExtensions local_extensions = {
       RtpExtension(RtpExtension::kTransportSequenceNumberUri, 5)};
-  f1_.set_video_rtp_header_extensions(offered_extensions);
-  f1_.set_audio_rtp_header_extensions(offered_extensions);
-  f2_.set_video_rtp_header_extensions(local_extensions);
-  f2_.set_audio_rtp_header_extensions(local_extensions);
-
+  SetAudioVideoRtpHeaderExtensions(offered_extensions, offered_extensions,
+                                   &opts);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, nullptr);
+  SetAudioVideoRtpHeaderExtensions(local_extensions, local_extensions, &opts);
   std::unique_ptr<SessionDescription> answer =
       f2_.CreateAnswer(offer.get(), opts, nullptr);
   EXPECT_THAT(
@@ -1814,12 +1838,10 @@
       RtpExtension(RtpExtension::kAbsoluteCaptureTimeUri, 7)};
   const cricket::RtpHeaderExtensions local_extensions = {
       RtpExtension(RtpExtension::kAbsoluteCaptureTimeUri, 5)};
-  f1_.set_video_rtp_header_extensions(offered_extensions);
-  f1_.set_audio_rtp_header_extensions(offered_extensions);
-  f2_.set_video_rtp_header_extensions(local_extensions);
-  f2_.set_audio_rtp_header_extensions(local_extensions);
-
+  SetAudioVideoRtpHeaderExtensions(offered_extensions, offered_extensions,
+                                   &opts);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, nullptr);
+  SetAudioVideoRtpHeaderExtensions(local_extensions, local_extensions, &opts);
   std::unique_ptr<SessionDescription> answer =
       f2_.CreateAnswer(offer.get(), opts, nullptr);
   EXPECT_THAT(
@@ -1839,12 +1861,10 @@
       RtpExtension(RtpExtension::kTransportSequenceNumberUri, 7)};
   const cricket::RtpHeaderExtensions local_extensions = {
       RtpExtension(RtpExtension::kAbsoluteCaptureTimeUri, 5)};
-  f1_.set_video_rtp_header_extensions(offered_extensions);
-  f1_.set_audio_rtp_header_extensions(offered_extensions);
-  f2_.set_video_rtp_header_extensions(local_extensions);
-  f2_.set_audio_rtp_header_extensions(local_extensions);
-
+  SetAudioVideoRtpHeaderExtensions(offered_extensions, offered_extensions,
+                                   &opts);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, nullptr);
+  SetAudioVideoRtpHeaderExtensions(local_extensions, local_extensions, &opts);
   std::unique_ptr<SessionDescription> answer =
       f2_.CreateAnswer(offer.get(), opts, nullptr);
   EXPECT_THAT(
@@ -1856,6 +1876,203 @@
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest,
+       OffersUnstoppedExtensionsWithAudioVideoExtensionStopped) {
+  MediaSessionOptions opts;
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
+  opts.media_description_options.back().header_extensions = {
+      webrtc::RtpHeaderExtensionCapability("uri1", 1,
+                                           RtpTransceiverDirection::kStopped),
+      webrtc::RtpHeaderExtensionCapability("uri2", 3,
+                                           RtpTransceiverDirection::kSendOnly)};
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video1",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
+  opts.media_description_options.back().header_extensions = {
+      webrtc::RtpHeaderExtensionCapability("uri1", 1,
+                                           RtpTransceiverDirection::kStopped),
+      webrtc::RtpHeaderExtensionCapability("uri3", 7,
+                                           RtpTransceiverDirection::kSendOnly)};
+  auto offer = f1_.CreateOffer(opts, nullptr);
+  EXPECT_THAT(
+      offer->contents(),
+      ElementsAre(
+          Property(&ContentInfo::media_description,
+                   Pointee(Property(
+                       &MediaContentDescription::rtp_header_extensions,
+                       ElementsAre(Field(&RtpExtension::uri, "uri2"))))),
+          Property(&ContentInfo::media_description,
+                   Pointee(Property(
+                       &MediaContentDescription::rtp_header_extensions,
+                       ElementsAre(Field(&RtpExtension::uri, "uri3")))))));
+}
+
+TEST_F(MediaSessionDescriptionFactoryTest,
+       OffersUnstoppedExtensionsWithAudioExtensionStopped) {
+  MediaSessionOptions opts;
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
+  opts.media_description_options.back().header_extensions = {
+      webrtc::RtpHeaderExtensionCapability("uri1", 1,
+                                           RtpTransceiverDirection::kSendOnly),
+      webrtc::RtpHeaderExtensionCapability("uri2", 3,
+                                           RtpTransceiverDirection::kStopped)};
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video1",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
+  opts.media_description_options.back().header_extensions = {
+      webrtc::RtpHeaderExtensionCapability("uri42", 42,
+                                           RtpTransceiverDirection::kSendRecv),
+      webrtc::RtpHeaderExtensionCapability("uri3", 7,
+                                           RtpTransceiverDirection::kSendOnly)};
+  auto offer = f1_.CreateOffer(opts, nullptr);
+  EXPECT_THAT(
+      offer->contents(),
+      ElementsAre(
+          Property(&ContentInfo::media_description,
+                   Pointee(Property(
+                       &MediaContentDescription::rtp_header_extensions,
+                       ElementsAre(Field(&RtpExtension::uri, "uri1"))))),
+          Property(
+              &ContentInfo::media_description,
+              Pointee(Property(
+                  &MediaContentDescription::rtp_header_extensions,
+                  UnorderedElementsAre(Field(&RtpExtension::uri, "uri3"),
+                                       Field(&RtpExtension::uri, "uri42")))))));
+}
+
+TEST_F(MediaSessionDescriptionFactoryTest,
+       OffersUnstoppedExtensionsWithVideoExtensionStopped) {
+  MediaSessionOptions opts;
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
+  opts.media_description_options.back().header_extensions = {
+      webrtc::RtpHeaderExtensionCapability("uri1", 5,
+                                           RtpTransceiverDirection::kSendOnly),
+      webrtc::RtpHeaderExtensionCapability("uri2", 7,
+                                           RtpTransceiverDirection::kSendRecv)};
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video1",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
+  opts.media_description_options.back().header_extensions = {
+      webrtc::RtpHeaderExtensionCapability("uri42", 42,
+                                           RtpTransceiverDirection::kSendRecv),
+      webrtc::RtpHeaderExtensionCapability("uri3", 7,
+                                           RtpTransceiverDirection::kStopped)};
+  auto offer = f1_.CreateOffer(opts, nullptr);
+  EXPECT_THAT(
+      offer->contents(),
+      ElementsAre(
+          Property(
+              &ContentInfo::media_description,
+              Pointee(Property(
+                  &MediaContentDescription::rtp_header_extensions,
+                  UnorderedElementsAre(Field(&RtpExtension::uri, "uri1"),
+                                       Field(&RtpExtension::uri, "uri2"))))),
+          Property(&ContentInfo::media_description,
+                   Pointee(Property(
+                       &MediaContentDescription::rtp_header_extensions,
+                       ElementsAre(Field(&RtpExtension::uri, "uri42")))))));
+}
+
+TEST_F(MediaSessionDescriptionFactoryTest, AnswersUnstoppedExtensions) {
+  MediaSessionOptions opts;
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
+  opts.media_description_options.back().header_extensions = {
+      webrtc::RtpHeaderExtensionCapability("uri1", 4,
+                                           RtpTransceiverDirection::kStopped),
+      webrtc::RtpHeaderExtensionCapability("uri2", 3,
+                                           RtpTransceiverDirection::kSendOnly),
+      webrtc::RtpHeaderExtensionCapability("uri3", 2,
+                                           RtpTransceiverDirection::kRecvOnly),
+      webrtc::RtpHeaderExtensionCapability("uri4", 1,
+                                           RtpTransceiverDirection::kSendRecv)};
+  auto offer = f1_.CreateOffer(opts, nullptr);
+  opts.media_description_options.back().header_extensions = {
+      webrtc::RtpHeaderExtensionCapability("uri1", 4,
+                                           RtpTransceiverDirection::kSendOnly),
+      webrtc::RtpHeaderExtensionCapability("uri2", 3,
+                                           RtpTransceiverDirection::kRecvOnly),
+      webrtc::RtpHeaderExtensionCapability("uri3", 2,
+                                           RtpTransceiverDirection::kStopped),
+      webrtc::RtpHeaderExtensionCapability("uri4", 1,
+                                           RtpTransceiverDirection::kSendRecv)};
+  auto answer = f2_.CreateAnswer(offer.get(), opts, nullptr);
+  EXPECT_THAT(
+      answer->contents(),
+      ElementsAre(Property(
+          &ContentInfo::media_description,
+          Pointee(Property(&MediaContentDescription::rtp_header_extensions,
+                           ElementsAre(Field(&RtpExtension::uri, "uri2"),
+                                       Field(&RtpExtension::uri, "uri4")))))));
+}
+
+TEST_F(MediaSessionDescriptionFactoryTest,
+       AppendsUnstoppedExtensionsToCurrentDescription) {
+  MediaSessionOptions opts;
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
+  opts.media_description_options.back().header_extensions = {
+      webrtc::RtpHeaderExtensionCapability("uri1", 1,
+                                           RtpTransceiverDirection::kSendRecv)};
+  auto offer = f1_.CreateOffer(opts, nullptr);
+  opts.media_description_options.back().header_extensions = {
+      webrtc::RtpHeaderExtensionCapability("uri1", 2,
+                                           RtpTransceiverDirection::kSendRecv),
+      webrtc::RtpHeaderExtensionCapability("uri2", 3,
+                                           RtpTransceiverDirection::kRecvOnly),
+      webrtc::RtpHeaderExtensionCapability("uri3", 5,
+                                           RtpTransceiverDirection::kStopped),
+      webrtc::RtpHeaderExtensionCapability("uri4", 6,
+                                           RtpTransceiverDirection::kSendRecv)};
+  auto offer2 = f1_.CreateOffer(opts, offer.get());
+  EXPECT_THAT(
+      offer2->contents(),
+      ElementsAre(Property(
+          &ContentInfo::media_description,
+          Pointee(Property(&MediaContentDescription::rtp_header_extensions,
+                           ElementsAre(Field(&RtpExtension::uri, "uri1"),
+                                       Field(&RtpExtension::uri, "uri2"),
+                                       Field(&RtpExtension::uri, "uri4")))))));
+}
+
+TEST_F(MediaSessionDescriptionFactoryTest,
+       AppendsStoppedExtensionIfKnownAndPresentInTheOffer) {
+  MediaSessionOptions opts;
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
+  opts.media_description_options.back().header_extensions = {
+      webrtc::RtpHeaderExtensionCapability("uri1", 1,
+                                           RtpTransceiverDirection::kSendRecv),
+      webrtc::RtpHeaderExtensionCapability("uri2", 1,
+                                           RtpTransceiverDirection::kSendRecv)};
+  auto offer = f1_.CreateOffer(opts, nullptr);
+
+  // Now add "uri2" as stopped to the options verify that the offer contains
+  // uri2 since it's already present since before.
+  opts.media_description_options.back().header_extensions = {
+      webrtc::RtpHeaderExtensionCapability("uri1", 1,
+                                           RtpTransceiverDirection::kSendRecv),
+      webrtc::RtpHeaderExtensionCapability("uri2", 2,
+                                           RtpTransceiverDirection::kStopped)};
+  auto offer2 = f1_.CreateOffer(opts, offer.get());
+  EXPECT_THAT(
+      offer2->contents(),
+      ElementsAre(Property(
+          &ContentInfo::media_description,
+          Pointee(Property(&MediaContentDescription::rtp_header_extensions,
+                           ElementsAre(Field(&RtpExtension::uri, "uri1"),
+                                       Field(&RtpExtension::uri, "uri2")))))));
+}
+
+TEST_F(MediaSessionDescriptionFactoryTest,
        TestOfferAnswerWithEncryptedRtpExtensionsBoth) {
   MediaSessionOptions opts;
   AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);
@@ -1863,13 +2080,12 @@
   f1_.set_enable_encrypted_rtp_header_extensions(true);
   f2_.set_enable_encrypted_rtp_header_extensions(true);
 
-  f1_.set_audio_rtp_header_extensions(MAKE_VECTOR(kAudioRtpExtension1));
-  f1_.set_video_rtp_header_extensions(MAKE_VECTOR(kVideoRtpExtension1));
-  f2_.set_audio_rtp_header_extensions(MAKE_VECTOR(kAudioRtpExtension2));
-  f2_.set_video_rtp_header_extensions(MAKE_VECTOR(kVideoRtpExtension2));
-
+  SetAudioVideoRtpHeaderExtensions(MAKE_VECTOR(kAudioRtpExtension1),
+                                   MAKE_VECTOR(kVideoRtpExtension1), &opts);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, NULL);
   ASSERT_TRUE(offer.get() != NULL);
+  SetAudioVideoRtpHeaderExtensions(MAKE_VECTOR(kAudioRtpExtension2),
+                                   MAKE_VECTOR(kVideoRtpExtension2), &opts);
   std::unique_ptr<SessionDescription> answer =
       f2_.CreateAnswer(offer.get(), opts, NULL);
 
@@ -1894,13 +2110,12 @@
 
   f1_.set_enable_encrypted_rtp_header_extensions(true);
 
-  f1_.set_audio_rtp_header_extensions(MAKE_VECTOR(kAudioRtpExtension1));
-  f1_.set_video_rtp_header_extensions(MAKE_VECTOR(kVideoRtpExtension1));
-  f2_.set_audio_rtp_header_extensions(MAKE_VECTOR(kAudioRtpExtension2));
-  f2_.set_video_rtp_header_extensions(MAKE_VECTOR(kVideoRtpExtension2));
-
+  SetAudioVideoRtpHeaderExtensions(MAKE_VECTOR(kAudioRtpExtension1),
+                                   MAKE_VECTOR(kVideoRtpExtension1), &opts);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, NULL);
   ASSERT_TRUE(offer.get() != NULL);
+  SetAudioVideoRtpHeaderExtensions(MAKE_VECTOR(kAudioRtpExtension2),
+                                   MAKE_VECTOR(kVideoRtpExtension2), &opts);
   std::unique_ptr<SessionDescription> answer =
       f2_.CreateAnswer(offer.get(), opts, NULL);
 
@@ -1925,13 +2140,12 @@
 
   f2_.set_enable_encrypted_rtp_header_extensions(true);
 
-  f1_.set_audio_rtp_header_extensions(MAKE_VECTOR(kAudioRtpExtension1));
-  f1_.set_video_rtp_header_extensions(MAKE_VECTOR(kVideoRtpExtension1));
-  f2_.set_audio_rtp_header_extensions(MAKE_VECTOR(kAudioRtpExtension2));
-  f2_.set_video_rtp_header_extensions(MAKE_VECTOR(kVideoRtpExtension2));
-
+  SetAudioVideoRtpHeaderExtensions(MAKE_VECTOR(kAudioRtpExtension1),
+                                   MAKE_VECTOR(kVideoRtpExtension1), &opts);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, NULL);
   ASSERT_TRUE(offer.get() != NULL);
+  SetAudioVideoRtpHeaderExtensions(MAKE_VECTOR(kAudioRtpExtension2),
+                                   MAKE_VECTOR(kVideoRtpExtension2), &opts);
   std::unique_ptr<SessionDescription> answer =
       f2_.CreateAnswer(offer.get(), opts, NULL);
 
@@ -3333,12 +3547,11 @@
   MediaSessionOptions opts;
   AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);
 
-  f1_.set_audio_rtp_header_extensions(MAKE_VECTOR(kAudioRtpExtension1));
-  f1_.set_video_rtp_header_extensions(MAKE_VECTOR(kVideoRtpExtension1));
-  f2_.set_audio_rtp_header_extensions(MAKE_VECTOR(kAudioRtpExtension2));
-  f2_.set_video_rtp_header_extensions(MAKE_VECTOR(kVideoRtpExtension2));
-
+  SetAudioVideoRtpHeaderExtensions(MAKE_VECTOR(kAudioRtpExtension1),
+                                   MAKE_VECTOR(kVideoRtpExtension1), &opts);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, NULL);
+  SetAudioVideoRtpHeaderExtensions(MAKE_VECTOR(kAudioRtpExtension2),
+                                   MAKE_VECTOR(kVideoRtpExtension2), &opts);
   std::unique_ptr<SessionDescription> answer =
       f2_.CreateAnswer(offer.get(), opts, NULL);
 
@@ -3389,9 +3602,8 @@
   MediaSessionOptions opts;
   AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);
 
-  f1_.set_audio_rtp_header_extensions(MAKE_VECTOR(kAudioRtpExtension3));
-  f1_.set_video_rtp_header_extensions(MAKE_VECTOR(kVideoRtpExtension3));
-
+  SetAudioVideoRtpHeaderExtensions(MAKE_VECTOR(kAudioRtpExtension3),
+                                   MAKE_VECTOR(kVideoRtpExtension3), &opts);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, NULL);
 
   // Since the audio extensions used ID 3 for "both_audio_and_video", so should
@@ -3428,11 +3640,9 @@
   f1_.set_enable_encrypted_rtp_header_extensions(true);
   f2_.set_enable_encrypted_rtp_header_extensions(true);
 
-  f1_.set_audio_rtp_header_extensions(
-      MAKE_VECTOR(kAudioRtpExtension3ForEncryption));
-  f1_.set_video_rtp_header_extensions(
-      MAKE_VECTOR(kVideoRtpExtension3ForEncryption));
-
+  SetAudioVideoRtpHeaderExtensions(
+      MAKE_VECTOR(kAudioRtpExtension3ForEncryption),
+      MAKE_VECTOR(kVideoRtpExtension3ForEncryption), &opts);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, NULL);
 
   // The extensions that are shared between audio and video should use the same
diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc
index 367a50e..3def31a 100644
--- a/pc/peer_connection.cc
+++ b/pc/peer_connection.cc
@@ -4854,21 +4854,21 @@
 
   // Add audio/video/data m= sections to the end if needed.
   if (!audio_index && offer_new_audio_description) {
-    session_options->media_description_options.push_back(
-        cricket::MediaDescriptionOptions(
-            cricket::MEDIA_TYPE_AUDIO, cricket::CN_AUDIO,
-            RtpTransceiverDirectionFromSendRecv(send_audio, recv_audio),
-            false));
-
+    cricket::MediaDescriptionOptions options(
+        cricket::MEDIA_TYPE_AUDIO, cricket::CN_AUDIO,
+        RtpTransceiverDirectionFromSendRecv(send_audio, recv_audio), false);
+    options.header_extensions =
+        channel_manager()->GetSupportedAudioRtpHeaderExtensions();
+    session_options->media_description_options.push_back(options);
     audio_index = session_options->media_description_options.size() - 1;
   }
   if (!video_index && offer_new_video_description) {
-    session_options->media_description_options.push_back(
-        cricket::MediaDescriptionOptions(
-            cricket::MEDIA_TYPE_VIDEO, cricket::CN_VIDEO,
-            RtpTransceiverDirectionFromSendRecv(send_video, recv_video),
-            false));
-
+    cricket::MediaDescriptionOptions options(
+        cricket::MEDIA_TYPE_VIDEO, cricket::CN_VIDEO,
+        RtpTransceiverDirectionFromSendRecv(send_video, recv_video), false);
+    options.header_extensions =
+        channel_manager()->GetSupportedVideoRtpHeaderExtensions();
+    session_options->media_description_options.push_back(options);
     video_index = session_options->media_description_options.size() - 1;
   }
   if (!data_index && offer_new_data_description) {
@@ -4900,6 +4900,8 @@
       transceiver->stopped());
   media_description_options.codec_preferences =
       transceiver->codec_preferences();
+  media_description_options.header_extensions =
+      transceiver->HeaderExtensionsToOffer();
   // This behavior is specified in JSEP. The gist is that:
   // 1. The MSID is included if the RtpTransceiver's direction is sendonly or
   //    sendrecv.
@@ -5212,6 +5214,8 @@
                                              stopped));
         *audio_index = session_options->media_description_options.size() - 1;
       }
+      session_options->media_description_options.back().header_extensions =
+          channel_manager()->GetSupportedAudioRtpHeaderExtensions();
     } else if (IsVideoContent(&content)) {
       // If we already have an video m= section, reject this extra one.
       if (*video_index) {
@@ -5227,6 +5231,8 @@
                                              stopped));
         *video_index = session_options->media_description_options.size() - 1;
       }
+      session_options->media_description_options.back().header_extensions =
+          channel_manager()->GetSupportedVideoRtpHeaderExtensions();
     } else {
       RTC_DCHECK(IsDataContent(&content));
       // If we already have an data m= section, reject this extra one.
diff --git a/pc/peer_connection_header_extension_unittest.cc b/pc/peer_connection_header_extension_unittest.cc
index 3f44d4f..62fda592 100644
--- a/pc/peer_connection_header_extension_unittest.cc
+++ b/pc/peer_connection_header_extension_unittest.cc
@@ -33,16 +33,31 @@
     : public ::testing::TestWithParam<
           std::tuple<cricket::MediaType, SdpSemantics>> {
  protected:
+  PeerConnectionHeaderExtensionTest()
+      : extensions_(
+            {RtpHeaderExtensionCapability("uri1",
+                                          1,
+                                          RtpTransceiverDirection::kStopped),
+             RtpHeaderExtensionCapability("uri2",
+                                          2,
+                                          RtpTransceiverDirection::kSendOnly),
+             RtpHeaderExtensionCapability("uri3",
+                                          3,
+                                          RtpTransceiverDirection::kRecvOnly),
+             RtpHeaderExtensionCapability(
+                 "uri4",
+                 4,
+                 RtpTransceiverDirection::kSendRecv)}) {}
+
   std::unique_ptr<PeerConnectionWrapper> CreatePeerConnection(
       cricket::MediaType media_type,
-      absl::optional<SdpSemantics> semantics,
-      std::vector<RtpHeaderExtensionCapability> extensions) {
+      absl::optional<SdpSemantics> semantics) {
     auto voice = std::make_unique<cricket::FakeVoiceEngine>();
     auto video = std::make_unique<cricket::FakeVideoEngine>();
     if (media_type == cricket::MediaType::MEDIA_TYPE_AUDIO)
-      voice->SetRtpHeaderExtensions(extensions);
+      voice->SetRtpHeaderExtensions(extensions_);
     else
-      video->SetRtpHeaderExtensions(extensions);
+      video->SetRtpHeaderExtensions(extensions_);
     auto media_engine = std::make_unique<cricket::CompositeMediaEngine>(
         std::move(voice), std::move(video));
     PeerConnectionFactoryDependencies factory_dependencies;
@@ -71,6 +86,8 @@
     return std::make_unique<PeerConnectionWrapper>(pc_factory, pc,
                                                    std::move(observer));
   }
+
+  std::vector<RtpHeaderExtensionCapability> extensions_;
 };
 
 TEST_P(PeerConnectionHeaderExtensionTest, TransceiverOffersHeaderExtensions) {
@@ -79,19 +96,10 @@
   std::tie(media_type, semantics) = GetParam();
   if (semantics != SdpSemantics::kUnifiedPlan)
     return;
-  std::vector<RtpHeaderExtensionCapability> extensions(
-      {RtpHeaderExtensionCapability("uri1", 1,
-                                    RtpTransceiverDirection::kStopped),
-       RtpHeaderExtensionCapability("uri2", 2,
-                                    RtpTransceiverDirection::kSendOnly),
-       RtpHeaderExtensionCapability("uri3", 3,
-                                    RtpTransceiverDirection::kRecvOnly),
-       RtpHeaderExtensionCapability("uri4", 4,
-                                    RtpTransceiverDirection::kSendRecv)});
   std::unique_ptr<PeerConnectionWrapper> wrapper =
-      CreatePeerConnection(media_type, semantics, extensions);
+      CreatePeerConnection(media_type, semantics);
   auto transceiver = wrapper->AddTransceiver(media_type);
-  EXPECT_EQ(transceiver->HeaderExtensionsToOffer(), extensions);
+  EXPECT_EQ(transceiver->HeaderExtensionsToOffer(), extensions_);
 }
 
 TEST_P(PeerConnectionHeaderExtensionTest,
@@ -99,20 +107,14 @@
   cricket::MediaType media_type;
   SdpSemantics semantics;
   std::tie(media_type, semantics) = GetParam();
-  std::unique_ptr<PeerConnectionWrapper> wrapper = CreatePeerConnection(
-      media_type, semantics,
-      std::vector<RtpHeaderExtensionCapability>(
-          {RtpHeaderExtensionCapability("uri1", 1,
-                                        RtpTransceiverDirection::kSendRecv),
-           RtpHeaderExtensionCapability("uri2", 2,
-                                        RtpTransceiverDirection::kStopped),
-           RtpHeaderExtensionCapability("uri3", 3,
-                                        RtpTransceiverDirection::kRecvOnly)}));
+  std::unique_ptr<PeerConnectionWrapper> wrapper =
+      CreatePeerConnection(media_type, semantics);
   EXPECT_THAT(wrapper->pc_factory()
                   ->GetRtpSenderCapabilities(media_type)
                   .header_extensions,
-              ElementsAre(Field(&RtpHeaderExtensionCapability::uri, "uri1"),
-                          Field(&RtpHeaderExtensionCapability::uri, "uri3")));
+              ElementsAre(Field(&RtpHeaderExtensionCapability::uri, "uri2"),
+                          Field(&RtpHeaderExtensionCapability::uri, "uri3"),
+                          Field(&RtpHeaderExtensionCapability::uri, "uri4")));
   EXPECT_EQ(wrapper->pc_factory()
                 ->GetRtpReceiverCapabilities(media_type)
                 .header_extensions,
@@ -121,6 +123,49 @@
                 .header_extensions);
 }
 
+TEST_P(PeerConnectionHeaderExtensionTest, OffersUnstoppedDefaultExtensions) {
+  cricket::MediaType media_type;
+  SdpSemantics semantics;
+  std::tie(media_type, semantics) = GetParam();
+  if (semantics != SdpSemantics::kUnifiedPlan)
+    return;
+  std::unique_ptr<PeerConnectionWrapper> wrapper =
+      CreatePeerConnection(media_type, semantics);
+  auto transceiver = wrapper->AddTransceiver(media_type);
+  auto session_description = wrapper->CreateOffer();
+  EXPECT_THAT(session_description->description()
+                  ->contents()[0]
+                  .media_description()
+                  ->rtp_header_extensions(),
+              ElementsAre(Field(&RtpExtension::uri, "uri2"),
+                          Field(&RtpExtension::uri, "uri3"),
+                          Field(&RtpExtension::uri, "uri4")));
+}
+
+TEST_P(PeerConnectionHeaderExtensionTest, OffersUnstoppedModifiedExtensions) {
+  cricket::MediaType media_type;
+  SdpSemantics semantics;
+  std::tie(media_type, semantics) = GetParam();
+  if (semantics != SdpSemantics::kUnifiedPlan)
+    return;
+  std::unique_ptr<PeerConnectionWrapper> wrapper =
+      CreatePeerConnection(media_type, semantics);
+  auto transceiver = wrapper->AddTransceiver(media_type);
+  auto modified_extensions = transceiver->HeaderExtensionsToOffer();
+  modified_extensions[0].direction = RtpTransceiverDirection::kSendRecv;
+  modified_extensions[3].direction = RtpTransceiverDirection::kStopped;
+  EXPECT_TRUE(
+      transceiver->SetOfferedRtpHeaderExtensions(modified_extensions).ok());
+  auto session_description = wrapper->CreateOffer();
+  EXPECT_THAT(session_description->description()
+                  ->contents()[0]
+                  .media_description()
+                  ->rtp_header_extensions(),
+              ElementsAre(Field(&RtpExtension::uri, "uri1"),
+                          Field(&RtpExtension::uri, "uri2"),
+                          Field(&RtpExtension::uri, "uri3")));
+}
+
 INSTANTIATE_TEST_SUITE_P(
     ,
     PeerConnectionHeaderExtensionTest,
diff --git a/pc/rtp_transceiver.cc b/pc/rtp_transceiver.cc
index d6e5ff4..b4e500b 100644
--- a/pc/rtp_transceiver.cc
+++ b/pc/rtp_transceiver.cc
@@ -114,7 +114,7 @@
     : unified_plan_(true),
       media_type_(sender->media_type()),
       channel_manager_(channel_manager),
-      HeaderExtensionsToOffer_(std::move(header_extensions_offered)) {
+      header_extensions_to_offer_(std::move(header_extensions_offered)) {
   RTC_DCHECK(media_type_ == cricket::MEDIA_TYPE_AUDIO ||
              media_type_ == cricket::MEDIA_TYPE_VIDEO);
   RTC_DCHECK_EQ(sender->media_type(), receiver->media_type());
@@ -356,7 +356,51 @@
 
 std::vector<RtpHeaderExtensionCapability>
 RtpTransceiver::HeaderExtensionsToOffer() const {
-  return HeaderExtensionsToOffer_;
+  return header_extensions_to_offer_;
+}
+
+RTCError RtpTransceiver::SetOfferedRtpHeaderExtensions(
+    rtc::ArrayView<const RtpHeaderExtensionCapability>
+        header_extensions_to_offer) {
+  for (const auto& entry : header_extensions_to_offer) {
+    // Handle unsupported requests for mandatory extensions as per
+    // https://w3c.github.io/webrtc-extensions/#rtcrtptransceiver-interface.
+    // Note:
+    // - We do not handle setOfferedRtpHeaderExtensions algorithm step 2.1,
+    //   this has to be checked on a higher level. We naturally error out
+    //   in the handling of Step 2.2 if an unset URI is encountered.
+
+    // Step 2.2.
+    // Handle unknown extensions.
+    auto it = std::find_if(
+        header_extensions_to_offer_.begin(), header_extensions_to_offer_.end(),
+        [&entry](const auto& offered) { return entry.uri == offered.uri; });
+    if (it == header_extensions_to_offer_.end()) {
+      return RTCError(RTCErrorType::INVALID_PARAMETER,
+                      "Attempted to modify an unoffered extension.");
+    }
+
+    // Step 2.4-2.5.
+    // - Use of the transceiver interface indicates unified plan is in effect,
+    //   hence the MID extension needs to be enabled.
+    // - Also handle the mandatory video orientation extensions.
+    if ((entry.uri == RtpExtension::kMidUri ||
+         entry.uri == RtpExtension::kVideoRotationUri) &&
+        entry.direction != RtpTransceiverDirection::kSendRecv) {
+      return RTCError(RTCErrorType::INVALID_MODIFICATION,
+                      "Attempted to stop a mandatory extension.");
+    }
+  }
+
+  // Apply mutation after error checking.
+  for (const auto& entry : header_extensions_to_offer) {
+    auto it = std::find_if(
+        header_extensions_to_offer_.begin(), header_extensions_to_offer_.end(),
+        [&entry](const auto& offered) { return entry.uri == offered.uri; });
+    it->direction = entry.direction;
+  }
+
+  return RTCError::OK();
 }
 
 }  // namespace webrtc
diff --git a/pc/rtp_transceiver.h b/pc/rtp_transceiver.h
index 0668447..be46ccf 100644
--- a/pc/rtp_transceiver.h
+++ b/pc/rtp_transceiver.h
@@ -195,6 +195,9 @@
   }
   std::vector<RtpHeaderExtensionCapability> HeaderExtensionsToOffer()
       const override;
+  RTCError SetOfferedRtpHeaderExtensions(
+      rtc::ArrayView<const RtpHeaderExtensionCapability>
+          header_extensions_to_offer) override;
 
  private:
   void OnFirstPacketReceived(cricket::ChannelInterface* channel);
@@ -220,7 +223,7 @@
   cricket::ChannelInterface* channel_ = nullptr;
   cricket::ChannelManager* channel_manager_ = nullptr;
   std::vector<RtpCodecCapability> codec_preferences_;
-  std::vector<RtpHeaderExtensionCapability> HeaderExtensionsToOffer_;
+  std::vector<RtpHeaderExtensionCapability> header_extensions_to_offer_;
 };
 
 BEGIN_SIGNALING_PROXY_MAP(RtpTransceiver)
@@ -241,6 +244,9 @@
 PROXY_CONSTMETHOD0(std::vector<RtpCodecCapability>, codec_preferences)
 PROXY_CONSTMETHOD0(std::vector<RtpHeaderExtensionCapability>,
                    HeaderExtensionsToOffer)
+PROXY_METHOD1(webrtc::RTCError,
+              SetOfferedRtpHeaderExtensions,
+              rtc::ArrayView<const RtpHeaderExtensionCapability>)
 END_PROXY_MAP()
 
 }  // namespace webrtc
diff --git a/pc/rtp_transceiver_unittest.cc b/pc/rtp_transceiver_unittest.cc
index 5e34573..e3f05c4 100644
--- a/pc/rtp_transceiver_unittest.cc
+++ b/pc/rtp_transceiver_unittest.cc
@@ -25,6 +25,7 @@
 using ::testing::Eq;
 using ::testing::Field;
 using ::testing::Not;
+using ::testing::Property;
 using ::testing::Return;
 using ::testing::ReturnRef;
 
@@ -78,27 +79,95 @@
   EXPECT_EQ(nullptr, transceiver.channel());
 }
 
-TEST(RtpTransceiverTest,
-     InitsWithChannelManagerRtpHeaderExtensionCapabilities) {
-  cricket::ChannelManager channel_manager(
-      std::make_unique<cricket::FakeMediaEngine>(),
-      std::make_unique<cricket::FakeDataEngine>(), rtc::Thread::Current(),
-      rtc::Thread::Current());
-  std::vector<RtpHeaderExtensionCapability> extensions({
-      RtpHeaderExtensionCapability("uri1", 1,
-                                   RtpTransceiverDirection::kSendRecv),
-      RtpHeaderExtensionCapability("uri2", 2,
-                                   RtpTransceiverDirection::kRecvOnly),
-  });
-  RtpTransceiver transceiver(
-      RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
-          rtc::Thread::Current(),
-          new rtc::RefCountedObject<MockRtpSenderInternal>()),
-      RtpReceiverProxyWithInternal<RtpReceiverInternal>::Create(
-          rtc::Thread::Current(),
-          new rtc::RefCountedObject<MockRtpReceiverInternal>()),
-      &channel_manager, extensions);
-  EXPECT_EQ(transceiver.HeaderExtensionsToOffer(), extensions);
+class RtpTransceiverTestForHeaderExtensions : public ::testing::Test {
+ public:
+  RtpTransceiverTestForHeaderExtensions()
+      : channel_manager_(std::make_unique<cricket::FakeMediaEngine>(),
+                         std::make_unique<cricket::FakeDataEngine>(),
+                         rtc::Thread::Current(),
+                         rtc::Thread::Current()),
+        extensions_(
+            {RtpHeaderExtensionCapability("uri1",
+                                          1,
+                                          RtpTransceiverDirection::kSendOnly),
+             RtpHeaderExtensionCapability("uri2",
+                                          2,
+                                          RtpTransceiverDirection::kRecvOnly),
+             RtpHeaderExtensionCapability(RtpExtension::kMidUri,
+                                          3,
+                                          RtpTransceiverDirection::kSendRecv),
+             RtpHeaderExtensionCapability(RtpExtension::kVideoRotationUri,
+                                          4,
+                                          RtpTransceiverDirection::kSendRecv)}),
+        transceiver_(RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
+                         rtc::Thread::Current(),
+                         new rtc::RefCountedObject<MockRtpSenderInternal>()),
+                     RtpReceiverProxyWithInternal<RtpReceiverInternal>::Create(
+                         rtc::Thread::Current(),
+                         new rtc::RefCountedObject<MockRtpReceiverInternal>()),
+                     &channel_manager_,
+                     extensions_) {}
+
+  cricket::ChannelManager channel_manager_;
+  std::vector<RtpHeaderExtensionCapability> extensions_;
+  RtpTransceiver transceiver_;
+};
+
+TEST_F(RtpTransceiverTestForHeaderExtensions, OffersChannelManagerList) {
+  EXPECT_EQ(transceiver_.HeaderExtensionsToOffer(), extensions_);
+}
+
+TEST_F(RtpTransceiverTestForHeaderExtensions, ModifiesDirection) {
+  auto modified_extensions = extensions_;
+  modified_extensions[0].direction = RtpTransceiverDirection::kSendOnly;
+  EXPECT_TRUE(
+      transceiver_.SetOfferedRtpHeaderExtensions(modified_extensions).ok());
+  EXPECT_EQ(transceiver_.HeaderExtensionsToOffer(), modified_extensions);
+  modified_extensions[0].direction = RtpTransceiverDirection::kRecvOnly;
+  EXPECT_TRUE(
+      transceiver_.SetOfferedRtpHeaderExtensions(modified_extensions).ok());
+  EXPECT_EQ(transceiver_.HeaderExtensionsToOffer(), modified_extensions);
+  modified_extensions[0].direction = RtpTransceiverDirection::kSendRecv;
+  EXPECT_TRUE(
+      transceiver_.SetOfferedRtpHeaderExtensions(modified_extensions).ok());
+  EXPECT_EQ(transceiver_.HeaderExtensionsToOffer(), modified_extensions);
+  modified_extensions[0].direction = RtpTransceiverDirection::kInactive;
+  EXPECT_TRUE(
+      transceiver_.SetOfferedRtpHeaderExtensions(modified_extensions).ok());
+  EXPECT_EQ(transceiver_.HeaderExtensionsToOffer(), modified_extensions);
+}
+
+TEST_F(RtpTransceiverTestForHeaderExtensions, AcceptsStoppedExtension) {
+  auto modified_extensions = extensions_;
+  modified_extensions[0].direction = RtpTransceiverDirection::kStopped;
+  EXPECT_TRUE(
+      transceiver_.SetOfferedRtpHeaderExtensions(modified_extensions).ok());
+  EXPECT_EQ(transceiver_.HeaderExtensionsToOffer(), modified_extensions);
+}
+
+TEST_F(RtpTransceiverTestForHeaderExtensions, RejectsUnsupportedExtension) {
+  std::vector<RtpHeaderExtensionCapability> modified_extensions(
+      {RtpHeaderExtensionCapability("uri3", 1,
+                                    RtpTransceiverDirection::kSendRecv)});
+  EXPECT_THAT(transceiver_.SetOfferedRtpHeaderExtensions(modified_extensions),
+              Property(&RTCError::type, RTCErrorType::INVALID_PARAMETER));
+  EXPECT_EQ(transceiver_.HeaderExtensionsToOffer(), extensions_);
+}
+
+TEST_F(RtpTransceiverTestForHeaderExtensions,
+       RejectsStoppedMandatoryExtensions) {
+  std::vector<RtpHeaderExtensionCapability> modified_extensions = extensions_;
+  // Attempting to stop the mandatory MID extension.
+  modified_extensions[2].direction = RtpTransceiverDirection::kStopped;
+  EXPECT_THAT(transceiver_.SetOfferedRtpHeaderExtensions(modified_extensions),
+              Property(&RTCError::type, RTCErrorType::INVALID_MODIFICATION));
+  EXPECT_EQ(transceiver_.HeaderExtensionsToOffer(), extensions_);
+  modified_extensions = extensions_;
+  // Attempting to stop the mandatory video orientation extension.
+  modified_extensions[3].direction = RtpTransceiverDirection::kStopped;
+  EXPECT_THAT(transceiver_.SetOfferedRtpHeaderExtensions(modified_extensions),
+              Property(&RTCError::type, RTCErrorType::INVALID_MODIFICATION));
+  EXPECT_EQ(transceiver_.HeaderExtensionsToOffer(), extensions_);
 }
 
 }  // namespace webrtc