Move webrtc_sdp serialization methods and SessionDescriptionInterface

...to the jsep target.

This resolves the circular dependency by moving the definitions and implementation of the SDP serialization and deserialization functions and `SessionDescriptionInterface` functions from `pc/` to `api/` and including them with the `jsep` target and in alignment with the JSEP API header.

The webrtc_sdp build target lives on for a bit longer while there may be downstream code that includes the webrtc_sdp.h header.

Bug: webrtc:42222470
Change-Id: If1417455729e128361575f2da161444bfed8b21e
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/415745
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Tomas Gunnarsson <tommi@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#45909}
diff --git a/api/BUILD.gn b/api/BUILD.gn
index 6dfb63c..9579cec 100644
--- a/api/BUILD.gn
+++ b/api/BUILD.gn
@@ -320,17 +320,42 @@
     "jsep_ice_candidate.cc",
     "jsep_ice_candidate.h",
     "jsep_session_description.h",
+    "webrtc_sdp.cc",
+    "webrtc_sdp.h",
   ]
   deps = [
     ":candidate",
     ":ref_count",
     ":rtc_error",
+    ":rtp_parameters",
+    ":rtp_transceiver_direction",
+    ":sctp_transport_interface",
     ":sequence_checker",
+    "../media:codec",
+    "../media:media_constants",
+    "../media:rid_description",
+    "../media:rtp_utils",
+    "../media:stream_params",
+    "../p2p:p2p_constants",
+    "../p2p:transport_description",
+    "../p2p:transport_info",
+    "../pc:media_protocol_names",
+    "../pc:session_description",
+    "../pc:simulcast_description",
+    "../pc:simulcast_sdp_serializer",
     "../rtc_base:checks",
+    "../rtc_base:crypto_random",
+    "../rtc_base:ip_address",
     "../rtc_base:logging",
     "../rtc_base:macromagic",
+    "../rtc_base:net_helper",
+    "../rtc_base:net_helpers",
+    "../rtc_base:socket_address",
+    "../rtc_base:ssl",
+    "../rtc_base:stringutils",
     "../rtc_base/system:no_unique_address",
     "../rtc_base/system:rtc_export",
+    "audio:audio_frame_api",
     "//third_party/abseil-cpp/absl/algorithm:container",
     "//third_party/abseil-cpp/absl/base:nullability",
     "//third_party/abseil-cpp/absl/memory",
@@ -1694,6 +1719,7 @@
       "scoped_refptr_unittest.cc",
       "sequence_checker_unittest.cc",
       "test/peerconnection_quality_test_fixture_unittest.cc",
+      "webrtc_sdp_unittest.cc",
     ]
 
     deps = [
@@ -1709,16 +1735,29 @@
       ":rtp_headers",
       ":rtp_packet_info",
       ":rtp_parameters",
+      ":rtp_transceiver_direction",
       ":scoped_refptr",
       ":sequence_checker",
+      "../media:codec",
+      "../media:media_constants",
+      "../media:rid_description",
+      "../media:stream_params",
       "../p2p:p2p_constants",
+      "../p2p:transport_description",
+      "../p2p:transport_info",
+      "../pc:media_protocol_names",
+      "../pc:media_session",
+      "../pc:session_description",
+      "../pc:simulcast_description",
       "../rtc_base:buffer",
       "../rtc_base:checks",
+      "../rtc_base:digest",
       "../rtc_base:logging",
       "../rtc_base:macromagic",
       "../rtc_base:platform_thread",
       "../rtc_base:rtc_event",
       "../rtc_base:socket_address",
+      "../rtc_base:ssl",
       "../rtc_base:task_queue_for_test",
       "../rtc_base/containers:flat_set",
       "../rtc_base/synchronization:sequence_checker_internal",
@@ -1741,7 +1780,10 @@
       "video/corruption_detection:frame_instrumentation_data_unittest",
       "video/corruption_detection:frame_instrumentation_evaluation_unittest",
       "video/corruption_detection:frame_instrumentation_generator_unittest",
+      "//third_party/abseil-cpp/absl/algorithm:container",
+      "//third_party/abseil-cpp/absl/base:nullability",
       "//third_party/abseil-cpp/absl/functional:any_invocable",
+      "//third_party/abseil-cpp/absl/memory",
       "//third_party/abseil-cpp/absl/strings",
       "//third_party/abseil-cpp/absl/strings:string_view",
     ]
@@ -1749,6 +1791,10 @@
     if (rtc_use_h265) {
       deps += [ "video:rtp_video_frame_h265_assembler_unittests" ]
     }
+
+    if (is_android) {
+      deps += [ "../pc:android_black_magic" ]
+    }
   }
 
   rtc_library("compile_all_headers") {
diff --git a/api/OWNERS b/api/OWNERS
index 49dac85..ebaea3b 100644
--- a/api/OWNERS
+++ b/api/OWNERS
@@ -11,3 +11,11 @@
 per-file DEPS=mbonadei@webrtc.org
 
 per-file uma_metrics.h=kron@webrtc.org
+
+# Adding features via SDP munging requires approval from SDP owners
+per-file webrtc_sdp.cc = set noparent
+per-file webrtc_sdp.cc = hta@webrtc.org
+per-file webrtc_sdp.cc = hbos@webrtc.org
+# If none of the above are present, may also try one of these
+per-file webrtc_sdp.cc = tommi@webrtc.org
+per-file webrtc_sdp.cc = guidou@webrtc.org
diff --git a/api/jsep.cc b/api/jsep.cc
index c2e2919..b33155d 100644
--- a/api/jsep.cc
+++ b/api/jsep.cc
@@ -10,13 +10,104 @@
 
 #include "api/jsep.h"
 
+#include <algorithm>
+#include <cstddef>
+#include <iterator>
 #include <memory>
 #include <optional>
 #include <string>
+#include <utility>
+#include <vector>
 
+#include "absl/memory/memory.h"
+#include "absl/strings/string_view.h"
 #include "api/candidate.h"
+#include "api/sequence_checker.h"
+#include "api/webrtc_sdp.h"
+#include "p2p/base/p2p_constants.h"
+#include "p2p/base/transport_info.h"
+#include "pc/session_description.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/ip_address.h"
+#include "rtc_base/net_helper.h"
+#include "rtc_base/net_helpers.h"
+#include "rtc_base/socket_address.h"
 
 namespace webrtc {
+namespace {
+constexpr char kDummyAddress[] = "0.0.0.0";
+constexpr int kDummyPort = 9;
+
+// Update the connection address for the MediaContentDescription based on the
+// candidates.
+void UpdateConnectionAddress(
+    const JsepCandidateCollection& candidate_collection,
+    MediaContentDescription* media_desc) {
+  int port = kDummyPort;
+  std::string ip = kDummyAddress;
+  std::string hostname;
+  int current_preference = 0;  // Start with lowest preference.
+  int current_family = AF_UNSPEC;
+  for (size_t i = 0; i < candidate_collection.count(); ++i) {
+    const IceCandidate* jsep_candidate = candidate_collection.at(i);
+    if (jsep_candidate->candidate().component() !=
+        ICE_CANDIDATE_COMPONENT_RTP) {
+      continue;
+    }
+    // Default destination should be UDP only.
+    if (jsep_candidate->candidate().protocol() != UDP_PROTOCOL_NAME) {
+      continue;
+    }
+    const int preference = jsep_candidate->candidate().type_preference();
+    const int family = jsep_candidate->candidate().address().ipaddr().family();
+    // See if this candidate is more preferable then the current one if it's the
+    // same family. Or if the current family is IPv4 already so we could safely
+    // ignore all IPv6 ones. WebRTC bug 4269.
+    // http://code.google.com/p/webrtc/issues/detail?id=4269
+    if ((preference <= current_preference && current_family == family) ||
+        (current_family == AF_INET && family == AF_INET6)) {
+      continue;
+    }
+    current_preference = preference;
+    current_family = family;
+    const SocketAddress& candidate_addr = jsep_candidate->candidate().address();
+    port = candidate_addr.port();
+    ip = candidate_addr.ipaddr().ToString();
+    hostname = candidate_addr.hostname();
+  }
+  SocketAddress connection_addr(ip, port);
+  if (IPIsUnspec(connection_addr.ipaddr()) && !hostname.empty()) {
+    // When a hostname candidate becomes the (default) connection address,
+    // we use the dummy address 0.0.0.0 and port 9 in the c= and the m= lines.
+    //
+    // We have observed in deployment that with a FQDN in a c= line, SDP parsing
+    // could fail in other JSEP implementations. We note that the wildcard
+    // addresses (0.0.0.0 or ::) with port 9 are given the exception as the
+    // connection address that will not result in an ICE mismatch
+    // (draft-ietf-mmusic-ice-sip-sdp). Also, 0.0.0.0 or :: can be used as the
+    // connection address in the initial offer or answer with trickle ICE
+    // if the offerer or answerer does not want to include the host IP address
+    // (draft-ietf-mmusic-trickle-ice-sip), and in particular 0.0.0.0 has been
+    // widely deployed for this use without outstanding compatibility issues.
+    // Combining the above considerations, we use 0.0.0.0 with port 9 to
+    // populate the c= and the m= lines. See `BuildMediaDescription` in
+    // webrtc_sdp.cc for the SDP generation with
+    // `media_desc->connection_address()`.
+    connection_addr = SocketAddress(kDummyAddress, kDummyPort);
+  }
+  media_desc->set_connection_address(connection_addr);
+}
+
+std::vector<IceCandidateCollection> CloneCandidateCollection(
+    const std::vector<IceCandidateCollection>& original) {
+  std::vector<IceCandidateCollection> ret;
+  ret.reserve(original.size());
+  for (const auto& collection : original) {
+    ret.push_back(collection.Clone());
+  }
+  return ret;
+}
+}  // namespace
 
 const char SessionDescriptionInterface::kOffer[] = "offer";
 const char SessionDescriptionInterface::kPrAnswer[] = "pranswer";
@@ -64,4 +155,213 @@
   return IceCandidate::Create(sdp_mid, sdp_mline_index, sdp, error).release();
 }
 
+std::unique_ptr<SessionDescriptionInterface> CreateSessionDescription(
+    SdpType type,
+    const std::string& sdp) {
+  return CreateSessionDescription(type, sdp, nullptr);
+}
+
+std::unique_ptr<SessionDescriptionInterface> CreateSessionDescription(
+    SdpType type,
+    const std::string& sdp,
+    SdpParseError* error_out) {
+  if (type == SdpType::kRollback) {
+    return CreateRollbackSessionDescription();
+  }
+  return SdpDeserialize(type, sdp, error_out);
+}
+
+std::unique_ptr<SessionDescriptionInterface> CreateSessionDescription(
+    SdpType type,
+    const std::string& session_id,
+    const std::string& session_version,
+    std::unique_ptr<SessionDescription> description) {
+  return SessionDescriptionInterface::Create(type, std::move(description),
+                                             session_id, session_version);
+}
+
+std::unique_ptr<SessionDescriptionInterface> CreateRollbackSessionDescription(
+    absl::string_view session_id,
+    absl::string_view session_version) {
+  return SessionDescriptionInterface::Create(
+      SdpType::kRollback, /*description=*/nullptr, session_id, session_version);
+}
+
+// static
+std::unique_ptr<SessionDescriptionInterface>
+SessionDescriptionInterface::Create(
+    SdpType type,
+    std::unique_ptr<SessionDescription> description,
+    absl::string_view id,
+    absl::string_view version,
+    std::vector<IceCandidateCollection> candidates) {
+  if (!description && type != SdpType::kRollback)
+    return nullptr;
+  return absl::WrapUnique(new SessionDescriptionInterface(
+      type, std::move(description), id, version, std::move(candidates)));
+}
+
+SessionDescriptionInternal::SessionDescriptionInternal(
+    SdpType type,
+    std::unique_ptr<SessionDescription> description,
+    absl::string_view id,
+    absl::string_view version)
+    : sdp_type_(type),
+      id_(id),
+      version_(version),
+      description_(std::move(description)) {
+  RTC_DCHECK(description_ || sdp_type_ == SdpType::kRollback);
+}
+
+SessionDescriptionInternal::~SessionDescriptionInternal() = default;
+
+size_t SessionDescriptionInternal::mediasection_count() const {
+  return description_ ? description_->contents().size() : 0u;
+}
+
+void SessionDescriptionInterface::RelinquishThreadOwnership() {
+  // Ideally we should require that the method can only be called from the
+  // thread that the sequence checker is currently attached to. However that's
+  // not compatible with some cases outside of webrtc where initializations
+  // happens on one thread and then the object is moved to a second thread (e.g.
+  // signaling) where a call is made into webrtc. At that point we'd hit a
+  // dcheck like this in webrtc: RTC_DCHECK_RUN_ON(&sequence_checker_);
+  sequence_checker_.Detach();
+  // Tie the checker to the current thread, which permits iterating
+  // `candidate_collection_`
+  RTC_DCHECK_RUN_ON(sequence_checker());
+  for (IceCandidateCollection& collection : candidate_collection_) {
+    collection.RelinquishThreadOwnership();
+  }
+  sequence_checker_.Detach();  // Unties the checker from the current thread.
+}
+
+SessionDescriptionInterface::SessionDescriptionInterface(
+    SdpType type,
+    std::unique_ptr<SessionDescription> desc,
+    absl::string_view id,
+    absl::string_view version,
+    std::vector<IceCandidateCollection> candidates)
+    : SessionDescriptionInternal(type, std::move(desc), id, version),
+      candidate_collection_(std::move(candidates)) {
+  RTC_DCHECK(description() || type == SdpType::kRollback);
+  RTC_DCHECK(candidate_collection_.empty() ||
+             candidate_collection_.size() == number_of_mediasections());
+  candidate_collection_.resize(number_of_mediasections());
+}
+
+std::unique_ptr<SessionDescriptionInterface>
+SessionDescriptionInterface::Clone() const {
+  RTC_DCHECK_RUN_ON(sequence_checker());
+  return SessionDescriptionInterface::Create(
+      sdp_type(), description() ? description()->Clone() : nullptr, id(),
+      version(), CloneCandidateCollection(candidate_collection_));
+}
+
+bool SessionDescriptionInterface::AddCandidate(const IceCandidate* candidate) {
+  RTC_DCHECK_RUN_ON(sequence_checker());
+  if (!candidate)
+    return false;
+  size_t index = 0;
+  if (!GetMediasectionIndex(candidate, &index)) {
+    return false;
+  }
+  ContentInfo& content = description()->contents()[index];
+  const TransportInfo* transport_info =
+      description()->GetTransportInfoByName(content.mid());
+  if (!transport_info) {
+    return false;
+  }
+
+  Candidate updated_candidate = candidate->candidate();
+  if (updated_candidate.username().empty()) {
+    updated_candidate.set_username(transport_info->description.ice_ufrag);
+  }
+  if (updated_candidate.password().empty()) {
+    updated_candidate.set_password(transport_info->description.ice_pwd);
+  }
+
+  // Use `content.mid()` as the mid for the updated candidate. The
+  // `candidate->sdp_mid()` property *should* be the same. However, in some
+  // cases specifying an empty mid but a valid index is a way to add a candidate
+  // without knowing (or caring about) the mid. This is done in several tests.
+  RTC_DCHECK(candidate->sdp_mid().empty() ||
+             candidate->sdp_mid() == content.mid())
+      << "sdp_mid='" << candidate->sdp_mid() << "' content.mid()='"
+      << content.mid() << "'";
+  auto updated_candidate_wrapper = std::make_unique<IceCandidate>(
+      content.mid(), static_cast<int>(index), updated_candidate);
+  IceCandidateCollection& candidates = candidate_collection_[index];
+  if (!candidates.HasCandidate(updated_candidate_wrapper.get())) {
+    candidates.add(std::move(updated_candidate_wrapper));
+    UpdateConnectionAddress(candidates, content.media_description());
+  }
+
+  return true;
+}
+
+bool SessionDescriptionInterface::RemoveCandidate(
+    const IceCandidate* candidate) {
+  RTC_DCHECK_RUN_ON(sequence_checker());
+  size_t index = 0u;
+  if (!GetMediasectionIndex(candidate, &index)) {
+    return false;
+  }
+  IceCandidateCollection& candidates = candidate_collection_[index];
+  if (!candidates.remove(candidate)) {
+    return false;
+  }
+  UpdateConnectionAddress(candidates,
+                          description()->contents()[index].media_description());
+  return true;
+}
+
+const IceCandidateCollection* SessionDescriptionInterface::candidates(
+    size_t mediasection_index) const {
+  RTC_DCHECK_RUN_ON(sequence_checker());
+  if (mediasection_index >= candidate_collection_.size())
+    return nullptr;
+  return &candidate_collection_[mediasection_index];
+}
+
+bool SessionDescriptionInterface::ToString(std::string* out) const {
+  if (!description() || !out) {
+    return false;
+  }
+  *out = SdpSerialize(*this);
+  return !out->empty();
+}
+
+bool SessionDescriptionInterface::IsValidMLineIndex(int index) const {
+  RTC_DCHECK(description());
+  return index >= 0 &&
+         index < static_cast<int>(description()->contents().size());
+}
+
+bool SessionDescriptionInterface::GetMediasectionIndex(
+    const IceCandidate* candidate,
+    size_t* index) const {
+  if (!candidate || !index || !description()) {
+    return false;
+  }
+
+  auto mid = candidate->sdp_mid();
+  if (!mid.empty()) {
+    *index = GetMediasectionIndex(mid);
+  } else {
+    // An sdp_mline_index of -1 will be treated as invalid.
+    *index = static_cast<size_t>(candidate->sdp_mline_index());
+  }
+  return IsValidMLineIndex(*index);
+}
+
+int SessionDescriptionInterface::GetMediasectionIndex(
+    absl::string_view mid) const {
+  const auto& contents = description()->contents();
+  auto it =
+      std::find_if(contents.begin(), contents.end(),
+                   [&](const auto& content) { return mid == content.mid(); });
+  return it == contents.end() ? -1 : std::distance(contents.begin(), it);
+}
+
 }  // namespace webrtc
diff --git a/pc/webrtc_sdp.cc b/api/webrtc_sdp.cc
similarity index 98%
rename from pc/webrtc_sdp.cc
rename to api/webrtc_sdp.cc
index 08d598c..5dc3d04 100644
--- a/pc/webrtc_sdp.cc
+++ b/api/webrtc_sdp.cc
@@ -8,7 +8,7 @@
  *  be found in the AUTHORS file in the root of the source tree.
  */
 
-#include "pc/webrtc_sdp.h"
+#include "api/webrtc_sdp.h"
 
 #include <algorithm>
 #include <cctype>
@@ -46,7 +46,6 @@
 #include "p2p/base/transport_description.h"
 #include "p2p/base/transport_info.h"
 #include "pc/media_protocol_names.h"
-#include "pc/media_session.h"
 #include "pc/session_description.h"
 #include "pc/simulcast_description.h"
 #include "pc/simulcast_sdp_serializer.h"
@@ -3108,6 +3107,28 @@
   return true;
 }
 
+bool IsMediaType(const MediaContentDescription* description, MediaType type) {
+  return description && description->type() == type;
+}
+
+const ContentInfo* GetFirstMediaContent(const ContentInfos& contents,
+                                        MediaType type) {
+  for (const ContentInfo& content : contents) {
+    if (IsMediaType(content.media_description(), type)) {
+      return &content;
+    }
+  }
+  return nullptr;
+}
+
+const ContentInfo* LegacyGetFirstAudioContent(const SessionDescription& sdesc) {
+  return GetFirstMediaContent(sdesc.contents(), MediaType::AUDIO);
+}
+
+const ContentInfo* LegacyGetFirstVideoContent(const SessionDescription& sdesc) {
+  return GetFirstMediaContent(sdesc.contents(), MediaType::VIDEO);
+}
+
 }  // namespace
 
 std::string SdpSerialize(const SessionDescriptionInterface& jdesc) {
@@ -3171,11 +3192,11 @@
     // audio/video content. Fixing that might result in much larger SDP and the
     // msid-semantic line should eventually go away so this is not worth fixing.
     std::set<std::string> media_stream_ids;
-    const ContentInfo* audio_content = GetFirstAudioContent(desc);
+    const ContentInfo* audio_content = LegacyGetFirstAudioContent(*desc);
     if (audio_content)
       GetMediaStreamIds(audio_content, &media_stream_ids);
 
-    const ContentInfo* video_content = GetFirstVideoContent(desc);
+    const ContentInfo* video_content = LegacyGetFirstVideoContent(*desc);
     if (video_content)
       GetMediaStreamIds(video_content, &media_stream_ids);
 
diff --git a/api/webrtc_sdp.h b/api/webrtc_sdp.h
new file mode 100644
index 0000000..c3d06b9
--- /dev/null
+++ b/api/webrtc_sdp.h
@@ -0,0 +1,50 @@
+/*
+ *  Copyright 2011 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.
+ */
+
+// This file contain functions for parsing and serializing SDP messages.
+// Related RFC/draft including:
+// * RFC 4566 - SDP
+// * RFC 5245 - ICE
+// * RFC 3388 - Grouping of Media Lines in SDP
+// * RFC 4568 - SDP Security Descriptions for Media Streams
+// * draft-lennox-mmusic-sdp-source-selection-02 -
+//   Mechanisms for Media Source Selection in SDP
+
+#ifndef API_WEBRTC_SDP_H_
+#define API_WEBRTC_SDP_H_
+
+#include <memory>
+#include <string>
+
+#include "absl/base/nullability.h"
+#include "absl/strings/string_view.h"
+#include "api/jsep.h"
+
+namespace webrtc {
+// Serializes the passed in SessionDescriptionInterface.
+// Serialize SessionDescription including candidates if
+// SessionDescriptionInterface has candidates.
+// jdesc - The SessionDescriptionInterface object to be serialized.
+// return - SDP string serialized from the arguments.
+std::string SdpSerialize(const SessionDescriptionInterface& jdesc);
+
+// Deserializes the `sdp` to construct a SessionDescriptionInterface object.
+// sdp_type - The type of session description object that should be constructed.
+// sdp - The SDP string to be Deserialized.
+// error - Optional detail error information when parsing fails.
+// return - A new session description object if successful, otherwise nullptr.
+absl_nullable std::unique_ptr<SessionDescriptionInterface> SdpDeserialize(
+    SdpType sdp_type,
+    absl::string_view sdp,
+    SdpParseError* absl_nullable error = nullptr);
+
+}  // namespace webrtc
+
+#endif  // API_WEBRTC_SDP_H_
diff --git a/pc/webrtc_sdp_unittest.cc b/api/webrtc_sdp_unittest.cc
similarity index 99%
rename from pc/webrtc_sdp_unittest.cc
rename to api/webrtc_sdp_unittest.cc
index 6b220d4..911623f 100644
--- a/pc/webrtc_sdp_unittest.cc
+++ b/api/webrtc_sdp_unittest.cc
@@ -52,7 +52,7 @@
 #ifdef WEBRTC_ANDROID
 #include "pc/test/android_test_initializer.h"
 #endif
-#include "pc/webrtc_sdp.h"
+#include "api/webrtc_sdp.h"
 
 namespace webrtc {
 
diff --git a/pc/BUILD.gn b/pc/BUILD.gn
index e911ad2..f0a9f37 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -829,7 +829,11 @@
 }
 
 rtc_library("media_protocol_names") {
-  visibility = [ ":*" ]
+  visibility = [
+    ":*",
+    "../api:jsep",
+    "../api:rtc_api_unittests",
+  ]
   sources = [
     "media_protocol_names.cc",
     "media_protocol_names.h",
@@ -1313,7 +1317,10 @@
 }
 
 rtc_library("simulcast_sdp_serializer") {
-  visibility = [ ":*" ]
+  visibility = [
+    ":*",
+    "../api:jsep",
+  ]
 
   sources = [
     "simulcast_sdp_serializer.cc",
@@ -1429,54 +1436,14 @@
     "../rtc_base:threading",
   ]
 }
-rtc_library("webrtc_sdp") {
-  # TODO(bugs.webrtc.org/13661): Reduce visibility if possible
-  visibility = [ "*" ]  # Used by Chrome and more
 
-  sources = [
-    "jsep_session_description.cc",
-    "webrtc_sdp.cc",
-    "webrtc_sdp.h",
-  ]
-  deps = [
-    ":media_protocol_names",
-    ":media_session",
-    ":session_description",
-    ":simulcast_description",
-    ":simulcast_sdp_serializer",
-    "../api:candidate",
-    "../api:jsep",
-    "../api:rtc_error",
-    "../api:rtp_parameters",
-    "../api:rtp_transceiver_direction",
-    "../api:sctp_transport_interface",
-    "../api:sequence_checker",
-    "../api/audio:audio_frame_api",
-    "../media:codec",
-    "../media:media_constants",
-    "../media:rid_description",
-    "../media:rtp_utils",
-    "../media:stream_params",
-    "../p2p:p2p_constants",
-    "../p2p:transport_description",
-    "../p2p:transport_info",
-    "../rtc_base:checks",
-    "../rtc_base:crypto_random",
-    "../rtc_base:ip_address",
-    "../rtc_base:logging",
-    "../rtc_base:net_helper",
-    "../rtc_base:net_helpers",
-    "../rtc_base:socket_address",
-    "../rtc_base:ssl",
-    "../rtc_base:stringutils",
-    "../rtc_base/system:rtc_export",
-    "//third_party/abseil-cpp/absl/algorithm:container",
-    "//third_party/abseil-cpp/absl/base:nullability",
-    "//third_party/abseil-cpp/absl/memory",
-    "//third_party/abseil-cpp/absl/strings",
-    "//third_party/abseil-cpp/absl/strings:string_view",
-  ]
+# TODO(bugs.webrtc.org/42222470): Remove this target.
+rtc_library("webrtc_sdp") {
+  visibility = [ "*" ]  # Used by Chrome and more
+  sources = [ "webrtc_sdp.h" ]
+  deps = [ "../api:jsep" ]
 }
+
 rtc_library("webrtc_session_description_factory") {
   visibility = [ ":*" ]
   sources = [
@@ -2509,7 +2476,6 @@
       "track_media_info_map_unittest.cc",
       "video_rtp_track_source_unittest.cc",
       "video_track_unittest.cc",
-      "webrtc_sdp_unittest.cc",
     ]
 
     deps = [
diff --git a/pc/OWNERS b/pc/OWNERS
index 8f054a6..c194b50 100644
--- a/pc/OWNERS
+++ b/pc/OWNERS
@@ -3,11 +3,3 @@
 perkj@webrtc.org
 tommi@webrtc.org
 deadbeef@webrtc.org
-
-# Adding features via SDP munging requires approval from SDP owners
-per-file webrtc_sdp.cc = set noparent
-per-file webrtc_sdp.cc = hta@webrtc.org
-per-file webrtc_sdp.cc = hbos@webrtc.org
-# If none of the above are present, may also try one of these
-per-file webrtc_sdp.cc = tommi@webrtc.org
-per-file webrtc_sdp.cc = guidou@webrtc.org
diff --git a/pc/jsep_session_description.cc b/pc/jsep_session_description.cc
deleted file mode 100644
index 63b6dfa..0000000
--- a/pc/jsep_session_description.cc
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- *  Copyright 2012 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 "api/jsep_session_description.h"
-
-#include <algorithm>
-#include <cstddef>
-#include <iterator>
-#include <memory>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "absl/memory/memory.h"
-#include "absl/strings/string_view.h"
-#include "api/candidate.h"
-#include "api/jsep.h"
-#include "api/sequence_checker.h"
-#include "p2p/base/p2p_constants.h"
-#include "p2p/base/transport_description.h"
-#include "p2p/base/transport_info.h"
-#include "pc/media_session.h"  // IWYU pragma: keep
-#include "pc/session_description.h"
-#include "pc/webrtc_sdp.h"
-#include "rtc_base/checks.h"
-#include "rtc_base/ip_address.h"
-#include "rtc_base/net_helper.h"
-#include "rtc_base/net_helpers.h"
-#include "rtc_base/socket_address.h"
-
-using webrtc::Candidate;
-using ::webrtc::SessionDescription;
-
-namespace webrtc {
-namespace {
-
-constexpr char kDummyAddress[] = "0.0.0.0";
-constexpr int kDummyPort = 9;
-
-// Update the connection address for the MediaContentDescription based on the
-// candidates.
-void UpdateConnectionAddress(
-    const JsepCandidateCollection& candidate_collection,
-    MediaContentDescription* media_desc) {
-  int port = kDummyPort;
-  std::string ip = kDummyAddress;
-  std::string hostname;
-  int current_preference = 0;  // Start with lowest preference.
-  int current_family = AF_UNSPEC;
-  for (size_t i = 0; i < candidate_collection.count(); ++i) {
-    const IceCandidate* jsep_candidate = candidate_collection.at(i);
-    if (jsep_candidate->candidate().component() !=
-        ICE_CANDIDATE_COMPONENT_RTP) {
-      continue;
-    }
-    // Default destination should be UDP only.
-    if (jsep_candidate->candidate().protocol() != UDP_PROTOCOL_NAME) {
-      continue;
-    }
-    const int preference = jsep_candidate->candidate().type_preference();
-    const int family = jsep_candidate->candidate().address().ipaddr().family();
-    // See if this candidate is more preferable then the current one if it's the
-    // same family. Or if the current family is IPv4 already so we could safely
-    // ignore all IPv6 ones. WebRTC bug 4269.
-    // http://code.google.com/p/webrtc/issues/detail?id=4269
-    if ((preference <= current_preference && current_family == family) ||
-        (current_family == AF_INET && family == AF_INET6)) {
-      continue;
-    }
-    current_preference = preference;
-    current_family = family;
-    const SocketAddress& candidate_addr = jsep_candidate->candidate().address();
-    port = candidate_addr.port();
-    ip = candidate_addr.ipaddr().ToString();
-    hostname = candidate_addr.hostname();
-  }
-  SocketAddress connection_addr(ip, port);
-  if (IPIsUnspec(connection_addr.ipaddr()) && !hostname.empty()) {
-    // When a hostname candidate becomes the (default) connection address,
-    // we use the dummy address 0.0.0.0 and port 9 in the c= and the m= lines.
-    //
-    // We have observed in deployment that with a FQDN in a c= line, SDP parsing
-    // could fail in other JSEP implementations. We note that the wildcard
-    // addresses (0.0.0.0 or ::) with port 9 are given the exception as the
-    // connection address that will not result in an ICE mismatch
-    // (draft-ietf-mmusic-ice-sip-sdp). Also, 0.0.0.0 or :: can be used as the
-    // connection address in the initial offer or answer with trickle ICE
-    // if the offerer or answerer does not want to include the host IP address
-    // (draft-ietf-mmusic-trickle-ice-sip), and in particular 0.0.0.0 has been
-    // widely deployed for this use without outstanding compatibility issues.
-    // Combining the above considerations, we use 0.0.0.0 with port 9 to
-    // populate the c= and the m= lines. See `BuildMediaDescription` in
-    // webrtc_sdp.cc for the SDP generation with
-    // `media_desc->connection_address()`.
-    connection_addr = SocketAddress(kDummyAddress, kDummyPort);
-  }
-  media_desc->set_connection_address(connection_addr);
-}
-
-std::vector<IceCandidateCollection> CloneCandidateCollection(
-    const std::vector<IceCandidateCollection>& original) {
-  std::vector<IceCandidateCollection> ret;
-  ret.reserve(original.size());
-  for (const auto& collection : original) {
-    ret.push_back(collection.Clone());
-  }
-  return ret;
-}
-
-}  // namespace
-
-std::unique_ptr<SessionDescriptionInterface> CreateSessionDescription(
-    SdpType type,
-    const std::string& sdp) {
-  return CreateSessionDescription(type, sdp, nullptr);
-}
-
-std::unique_ptr<SessionDescriptionInterface> CreateSessionDescription(
-    SdpType type,
-    const std::string& sdp,
-    SdpParseError* error_out) {
-  if (type == SdpType::kRollback) {
-    return CreateRollbackSessionDescription();
-  }
-  return SdpDeserialize(type, sdp, error_out);
-}
-
-std::unique_ptr<SessionDescriptionInterface> CreateSessionDescription(
-    SdpType type,
-    const std::string& session_id,
-    const std::string& session_version,
-    std::unique_ptr<SessionDescription> description) {
-  return SessionDescriptionInterface::Create(type, std::move(description),
-                                             session_id, session_version);
-}
-
-std::unique_ptr<SessionDescriptionInterface> CreateRollbackSessionDescription(
-    absl::string_view session_id,
-    absl::string_view session_version) {
-  return SessionDescriptionInterface::Create(
-      SdpType::kRollback, /*description=*/nullptr, session_id, session_version);
-}
-
-// static
-std::unique_ptr<SessionDescriptionInterface>
-SessionDescriptionInterface::Create(
-    SdpType type,
-    std::unique_ptr<SessionDescription> description,
-    absl::string_view id,
-    absl::string_view version,
-    std::vector<IceCandidateCollection> candidates) {
-  if (!description && type != SdpType::kRollback)
-    return nullptr;
-  return absl::WrapUnique(new SessionDescriptionInterface(
-      type, std::move(description), id, version, std::move(candidates)));
-}
-
-SessionDescriptionInternal::SessionDescriptionInternal(
-    SdpType type,
-    std::unique_ptr<SessionDescription> description,
-    absl::string_view id,
-    absl::string_view version)
-    : sdp_type_(type),
-      id_(id),
-      version_(version),
-      description_(std::move(description)) {
-  RTC_DCHECK(description_ || sdp_type_ == SdpType::kRollback);
-}
-
-SessionDescriptionInternal::~SessionDescriptionInternal() = default;
-
-size_t SessionDescriptionInternal::mediasection_count() const {
-  return description_ ? description_->contents().size() : 0u;
-}
-
-void SessionDescriptionInterface::RelinquishThreadOwnership() {
-  // Ideally we should require that the method can only be called from the
-  // thread that the sequence checker is currently attached to. However that's
-  // not compatible with some cases outside of webrtc where initializations
-  // happens on one thread and then the object is moved to a second thread (e.g.
-  // signaling) where a call is made into webrtc. At that point we'd hit a
-  // dcheck like this in webrtc: RTC_DCHECK_RUN_ON(&sequence_checker_);
-  sequence_checker_.Detach();
-  // Tie the checker to the current thread, which permits iterating
-  // `candidate_collection_`
-  RTC_DCHECK_RUN_ON(sequence_checker());
-  for (IceCandidateCollection& collection : candidate_collection_) {
-    collection.RelinquishThreadOwnership();
-  }
-  sequence_checker_.Detach();  // Unties the checker from the current thread.
-}
-
-SessionDescriptionInterface::SessionDescriptionInterface(
-    SdpType type,
-    std::unique_ptr<SessionDescription> desc,
-    absl::string_view id,
-    absl::string_view version,
-    std::vector<IceCandidateCollection> candidates)
-    : SessionDescriptionInternal(type, std::move(desc), id, version),
-      candidate_collection_(std::move(candidates)) {
-  RTC_DCHECK(description() || type == SdpType::kRollback);
-  RTC_DCHECK(candidate_collection_.empty() ||
-             candidate_collection_.size() == number_of_mediasections());
-  candidate_collection_.resize(number_of_mediasections());
-}
-
-std::unique_ptr<SessionDescriptionInterface>
-SessionDescriptionInterface::Clone() const {
-  RTC_DCHECK_RUN_ON(sequence_checker());
-  return SessionDescriptionInterface::Create(
-      sdp_type(), description() ? description()->Clone() : nullptr, id(),
-      version(), CloneCandidateCollection(candidate_collection_));
-}
-
-bool SessionDescriptionInterface::AddCandidate(const IceCandidate* candidate) {
-  RTC_DCHECK_RUN_ON(sequence_checker());
-  if (!candidate)
-    return false;
-  size_t index = 0;
-  if (!GetMediasectionIndex(candidate, &index)) {
-    return false;
-  }
-  ContentInfo& content = description()->contents()[index];
-  const TransportInfo* transport_info =
-      description()->GetTransportInfoByName(content.mid());
-  if (!transport_info) {
-    return false;
-  }
-
-  Candidate updated_candidate = candidate->candidate();
-  if (updated_candidate.username().empty()) {
-    updated_candidate.set_username(transport_info->description.ice_ufrag);
-  }
-  if (updated_candidate.password().empty()) {
-    updated_candidate.set_password(transport_info->description.ice_pwd);
-  }
-
-  // Use `content.mid()` as the mid for the updated candidate. The
-  // `candidate->sdp_mid()` property *should* be the same. However, in some
-  // cases specifying an empty mid but a valid index is a way to add a candidate
-  // without knowing (or caring about) the mid. This is done in several tests.
-  RTC_DCHECK(candidate->sdp_mid().empty() ||
-             candidate->sdp_mid() == content.mid())
-      << "sdp_mid='" << candidate->sdp_mid() << "' content.mid()='"
-      << content.mid() << "'";
-  auto updated_candidate_wrapper = std::make_unique<IceCandidate>(
-      content.mid(), static_cast<int>(index), updated_candidate);
-  IceCandidateCollection& candidates = candidate_collection_[index];
-  if (!candidates.HasCandidate(updated_candidate_wrapper.get())) {
-    candidates.add(std::move(updated_candidate_wrapper));
-    UpdateConnectionAddress(candidates, content.media_description());
-  }
-
-  return true;
-}
-
-bool SessionDescriptionInterface::RemoveCandidate(
-    const IceCandidate* candidate) {
-  RTC_DCHECK_RUN_ON(sequence_checker());
-  size_t index = 0u;
-  if (!GetMediasectionIndex(candidate, &index)) {
-    return false;
-  }
-  IceCandidateCollection& candidates = candidate_collection_[index];
-  if (!candidates.remove(candidate)) {
-    return false;
-  }
-  UpdateConnectionAddress(candidates,
-                          description()->contents()[index].media_description());
-  return true;
-}
-
-const IceCandidateCollection* SessionDescriptionInterface::candidates(
-    size_t mediasection_index) const {
-  RTC_DCHECK_RUN_ON(sequence_checker());
-  if (mediasection_index >= candidate_collection_.size())
-    return nullptr;
-  return &candidate_collection_[mediasection_index];
-}
-
-bool SessionDescriptionInterface::ToString(std::string* out) const {
-  if (!description() || !out) {
-    return false;
-  }
-  *out = SdpSerialize(*this);
-  return !out->empty();
-}
-
-bool SessionDescriptionInterface::IsValidMLineIndex(int index) const {
-  RTC_DCHECK(description());
-  return index >= 0 &&
-         index < static_cast<int>(description()->contents().size());
-}
-
-bool SessionDescriptionInterface::GetMediasectionIndex(
-    const IceCandidate* candidate,
-    size_t* index) const {
-  if (!candidate || !index || !description()) {
-    return false;
-  }
-
-  auto mid = candidate->sdp_mid();
-  if (!mid.empty()) {
-    *index = GetMediasectionIndex(mid);
-  } else {
-    // An sdp_mline_index of -1 will be treated as invalid.
-    *index = static_cast<size_t>(candidate->sdp_mline_index());
-  }
-  return IsValidMLineIndex(*index);
-}
-
-int SessionDescriptionInterface::GetMediasectionIndex(
-    absl::string_view mid) const {
-  const auto& contents = description()->contents();
-  auto it =
-      std::find_if(contents.begin(), contents.end(),
-                   [&](const auto& content) { return mid == content.mid(); });
-  return it == contents.end() ? -1 : std::distance(contents.begin(), it);
-}
-
-}  // namespace webrtc
diff --git a/pc/jsep_session_description_unittest.cc b/pc/jsep_session_description_unittest.cc
index 31e9bc9..47e9137 100644
--- a/pc/jsep_session_description_unittest.cc
+++ b/pc/jsep_session_description_unittest.cc
@@ -8,8 +8,6 @@
  *  be found in the AUTHORS file in the root of the source tree.
  */
 
-#include "api/jsep_session_description.h"
-
 #include <cstddef>
 #include <cstdint>
 #include <memory>
@@ -20,12 +18,12 @@
 #include "absl/strings/str_cat.h"
 #include "api/candidate.h"
 #include "api/jsep.h"
+#include "api/webrtc_sdp.h"
 #include "media/base/codec.h"
 #include "p2p/base/p2p_constants.h"
 #include "p2p/base/transport_description.h"
 #include "p2p/base/transport_info.h"
 #include "pc/session_description.h"
-#include "pc/webrtc_sdp.h"
 #include "rtc_base/crypto_random.h"
 #include "rtc_base/net_helper.h"
 #include "rtc_base/socket_address.h"
diff --git a/pc/peer_connection_histogram_unittest.cc b/pc/peer_connection_histogram_unittest.cc
index ace3889..6208aa4 100644
--- a/pc/peer_connection_histogram_unittest.cc
+++ b/pc/peer_connection_histogram_unittest.cc
@@ -26,12 +26,12 @@
 #include "api/test/mock_async_dns_resolver.h"
 #include "api/test/rtc_error_matchers.h"
 #include "api/units/time_delta.h"
+#include "api/webrtc_sdp.h"
 #include "pc/peer_connection.h"
 #include "pc/peer_connection_wrapper.h"
 #include "pc/test/enable_fake_media.h"
 #include "pc/test/mock_peer_connection_observers.h"
 #include "pc/usage_pattern.h"
-#include "pc/webrtc_sdp.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/fake_mdns_responder.h"
 #include "rtc_base/fake_network.h"
diff --git a/pc/simulcast_sdp_serializer.h b/pc/simulcast_sdp_serializer.h
index 84d28dd..66f0608 100644
--- a/pc/simulcast_sdp_serializer.h
+++ b/pc/simulcast_sdp_serializer.h
@@ -28,8 +28,8 @@
 //     format without knowing about the SDP attribute details (a=simulcast:)
 // Usage:
 //     Consider the SDP attribute for simulcast a=simulcast:<configuration>.
-//     The SDP serializtion code (webrtc_sdp.h) should use `SdpSerializer` to
-//     serialize and deserialize the <configuration> section.
+//     The SDP serialization code (webrtc_sdp.h) should use `SdpSerialize`
+//     to serialize and deserialize the <configuration> section.
 // This class will allow testing the serialization of components without
 // having to serialize the entire SDP while hiding implementation details
 // from callers of sdp serialization (webrtc_sdp.h).
diff --git a/pc/webrtc_sdp.h b/pc/webrtc_sdp.h
index fa4d722..86f2825 100644
--- a/pc/webrtc_sdp.h
+++ b/pc/webrtc_sdp.h
@@ -8,43 +8,9 @@
  *  be found in the AUTHORS file in the root of the source tree.
  */
 
-// This file contain functions for parsing and serializing SDP messages.
-// Related RFC/draft including:
-// * RFC 4566 - SDP
-// * RFC 5245 - ICE
-// * RFC 3388 - Grouping of Media Lines in SDP
-// * RFC 4568 - SDP Security Descriptions for Media Streams
-// * draft-lennox-mmusic-sdp-source-selection-02 -
-//   Mechanisms for Media Source Selection in SDP
-
 #ifndef PC_WEBRTC_SDP_H_
 #define PC_WEBRTC_SDP_H_
 
-#include <memory>
-#include <string>
-
-#include "absl/base/nullability.h"
-#include "absl/strings/string_view.h"
-#include "api/jsep.h"
-
-namespace webrtc {
-// Serializes the passed in SessionDescriptionInterface.
-// Serialize SessionDescription including candidates if
-// SessionDescriptionInterface has candidates.
-// jdesc - The SessionDescriptionInterface object to be serialized.
-// return - SDP string serialized from the arguments.
-std::string SdpSerialize(const SessionDescriptionInterface& jdesc);
-
-// Deserializes the `sdp` to construct a SessionDescriptionInterface object.
-// sdp_type - The type of session description object that should be constructed.
-// sdp - The SDP string to be Deserialized.
-// error - Optional detail error information when parsing fails.
-// return - A new session description object if successful, otherwise nullptr.
-absl_nullable std::unique_ptr<SessionDescriptionInterface> SdpDeserialize(
-    SdpType sdp_type,
-    absl::string_view sdp,
-    SdpParseError* absl_nullable error = nullptr);
-
-}  // namespace webrtc
+#include "api/webrtc_sdp.h"  // IWYU pragma: keep
 
 #endif  // PC_WEBRTC_SDP_H_
diff --git a/sdk/android/src/jni/pc/ice_candidate.cc b/sdk/android/src/jni/pc/ice_candidate.cc
index 1a76d13..aa57497 100644
--- a/sdk/android/src/jni/pc/ice_candidate.cc
+++ b/sdk/android/src/jni/pc/ice_candidate.cc
@@ -14,7 +14,7 @@
 
 #include "absl/strings/string_view.h"
 #include "api/jsep.h"
-#include "pc/webrtc_sdp.h"
+#include "api/webrtc_sdp.h"
 #include "sdk/android/generated_peerconnection_jni/IceCandidate_jni.h"
 #include "sdk/android/native_api/jni/java_types.h"
 #include "sdk/android/src/jni/pc/media_stream_track.h"