Version 2 "Refactoring DataContentDescription class"

(substantial changes since version 1)

This CL splits the cricket::DataContentDescription class into
two classes: cricket::RtpDataContentDescription (used for RTP data)
and cricket::SctpDataContentDescription (used for SCTP only).

SctpDataContentDescription no longer inherits from
MediaContentDescriptionImpl, and no longer contains "codecs".

Due to usage of internal interfaces by consumers, shimming the old
DataContentDescription API is needed.

A new cricket::DataContentDescription class is defined, which is
a shim over RtpDataContentDescription and SctpDataContentDescription.
It exposes as little functionality as possible, but supports the
concerned consumer's usage

Design document:
https://docs.google.com/document/d/1H5LfQxJA2ikMWTQ8FZ3_GAmaXM7knfVQWiSz6ph8VQ0/edit#

Version 1 reviewed-on: https://webrtc-review.googlesource.com/c/src/+/132700

Bug: webrtc:10358
Change-Id: Icf95fb7308244d6f2ebfdb403aaffc544e358580
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/133900
Commit-Queue: Harald Alvestrand <hta@webrtc.org>
Reviewed-by: Steve Anton <steveanton@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#27853}
diff --git a/media/base/codec.cc b/media/base/codec.cc
index d0ca29b..4380514 100644
--- a/media/base/codec.cc
+++ b/media/base/codec.cc
@@ -334,22 +334,22 @@
   return true;
 }
 
-DataCodec::DataCodec(int id, const std::string& name)
+RtpDataCodec::RtpDataCodec(int id, const std::string& name)
     : Codec(id, name, kDataCodecClockrate) {}
 
-DataCodec::DataCodec() : Codec() {
+RtpDataCodec::RtpDataCodec() : Codec() {
   clockrate = kDataCodecClockrate;
 }
 
-DataCodec::DataCodec(const DataCodec& c) = default;
-DataCodec::DataCodec(DataCodec&& c) = default;
-DataCodec& DataCodec::operator=(const DataCodec& c) = default;
-DataCodec& DataCodec::operator=(DataCodec&& c) = default;
+RtpDataCodec::RtpDataCodec(const RtpDataCodec& c) = default;
+RtpDataCodec::RtpDataCodec(RtpDataCodec&& c) = default;
+RtpDataCodec& RtpDataCodec::operator=(const RtpDataCodec& c) = default;
+RtpDataCodec& RtpDataCodec::operator=(RtpDataCodec&& c) = default;
 
-std::string DataCodec::ToString() const {
+std::string RtpDataCodec::ToString() const {
   char buf[256];
   rtc::SimpleStringBuilder sb(buf);
-  sb << "DataCodec[" << id << ":" << name << "]";
+  sb << "RtpDataCodec[" << id << ":" << name << "]";
   return sb.str();
 }
 
diff --git a/media/base/codec.h b/media/base/codec.h
index 091adb6..bbb147d 100644
--- a/media/base/codec.h
+++ b/media/base/codec.h
@@ -192,19 +192,23 @@
   void SetDefaultParameters();
 };
 
-struct DataCodec : public Codec {
-  DataCodec(int id, const std::string& name);
-  DataCodec();
-  DataCodec(const DataCodec& c);
-  DataCodec(DataCodec&& c);
-  ~DataCodec() override = default;
+struct RtpDataCodec : public Codec {
+  RtpDataCodec(int id, const std::string& name);
+  RtpDataCodec();
+  RtpDataCodec(const RtpDataCodec& c);
+  RtpDataCodec(RtpDataCodec&& c);
+  ~RtpDataCodec() override = default;
 
-  DataCodec& operator=(const DataCodec& c);
-  DataCodec& operator=(DataCodec&& c);
+  RtpDataCodec& operator=(const RtpDataCodec& c);
+  RtpDataCodec& operator=(RtpDataCodec&& c);
 
   std::string ToString() const;
 };
 
+// For backwards compatibility
+// TODO(bugs.webrtc.org/10597): Remove when no longer needed.
+typedef RtpDataCodec DataCodec;
+
 // Get the codec setting associated with |payload_type|. If there
 // is no codec associated with that payload type it returns nullptr.
 template <class Codec>
diff --git a/media/base/rtp_data_engine.h b/media/base/rtp_data_engine.h
index a4647ae..b8bfca2 100644
--- a/media/base/rtp_data_engine.h
+++ b/media/base/rtp_data_engine.h
@@ -16,6 +16,7 @@
 #include <string>
 #include <vector>
 
+#include "media/base/codec.h"
 #include "media/base/media_channel.h"
 #include "media/base/media_constants.h"
 #include "media/base/media_engine.h"
@@ -26,8 +27,6 @@
 
 namespace cricket {
 
-struct DataCodec;
-
 class RtpDataEngine : public DataEngineInterface {
  public:
   RtpDataEngine();
diff --git a/pc/BUILD.gn b/pc/BUILD.gn
index d406520..e93fa7a 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -72,6 +72,7 @@
   ]
 
   deps = [
+    ":media_protocol_names",
     "../api:array_view",
     "../api:audio_options_api",
     "../api:call_api",
@@ -121,6 +122,13 @@
   ]
 }
 
+rtc_source_set("media_protocol_names") {
+  sources = [
+    "media_protocol_names.cc",
+    "media_protocol_names.h",
+  ]
+}
+
 rtc_static_library("peerconnection") {
   visibility = [ "*" ]
   cflags = []
diff --git a/pc/channel.cc b/pc/channel.cc
index 647663e..82de7de 100644
--- a/pc/channel.cc
+++ b/pc/channel.cc
@@ -1143,7 +1143,7 @@
 }
 
 bool RtpDataChannel::CheckDataChannelTypeFromContent(
-    const DataContentDescription* content,
+    const RtpDataContentDescription* content,
     std::string* error_desc) {
   bool is_sctp = ((content->protocol() == kMediaProtocolSctp) ||
                   (content->protocol() == kMediaProtocolDtlsSctp));
@@ -1169,7 +1169,7 @@
     return false;
   }
 
-  const DataContentDescription* data = content->as_data();
+  const RtpDataContentDescription* data = content->as_rtp_data();
 
   if (!CheckDataChannelTypeFromContent(data, error_desc)) {
     return false;
@@ -1223,7 +1223,12 @@
     return false;
   }
 
-  const DataContentDescription* data = content->as_data();
+  const RtpDataContentDescription* data = content->as_rtp_data();
+
+  if (!data) {
+    RTC_LOG(LS_INFO) << "Accepting and ignoring non-RTP content description";
+    return true;
+  }
 
   // If the remote data doesn't have codecs, it must be empty, so ignore it.
   if (!data->has_codecs()) {
diff --git a/pc/channel.h b/pc/channel.h
index 1a4cc72..9747ec2 100644
--- a/pc/channel.h
+++ b/pc/channel.h
@@ -518,7 +518,7 @@
 
   // overrides from BaseChannel
   // Checks that data channel type is RTP.
-  bool CheckDataChannelTypeFromContent(const DataContentDescription* content,
+  bool CheckDataChannelTypeFromContent(const RtpDataContentDescription* content,
                                        std::string* error_desc);
   bool SetLocalContent_w(const MediaContentDescription* content,
                          webrtc::SdpType type,
diff --git a/pc/channel_unittest.cc b/pc/channel_unittest.cc
index 9c5f82b..e31ab53 100644
--- a/pc/channel_unittest.cc
+++ b/pc/channel_unittest.cc
@@ -94,8 +94,8 @@
 
 class DataTraits : public Traits<cricket::RtpDataChannel,
                                  cricket::FakeDataMediaChannel,
-                                 cricket::DataContentDescription,
-                                 cricket::DataCodec,
+                                 cricket::RtpDataContentDescription,
+                                 cricket::RtpDataCodec,
                                  cricket::DataMediaInfo,
                                  cricket::DataOptions> {};
 
@@ -2308,15 +2308,15 @@
     int flags,
     const cricket::AudioCodec& audio_codec,
     const cricket::VideoCodec& video_codec,
-    cricket::DataContentDescription* data) {
+    cricket::RtpDataContentDescription* data) {
   data->AddCodec(kGoogleDataCodec);
   data->set_rtcp_mux((flags & RTCP_MUX) != 0);
 }
 
 template <>
 void ChannelTest<DataTraits>::CopyContent(
-    const cricket::DataContentDescription& source,
-    cricket::DataContentDescription* data) {
+    const cricket::RtpDataContentDescription& source,
+    cricket::RtpDataContentDescription* data) {
   *data = source;
 }
 
@@ -2330,7 +2330,7 @@
 void ChannelTest<DataTraits>::AddLegacyStreamInContent(
     uint32_t ssrc,
     int flags,
-    cricket::DataContentDescription* data) {
+    cricket::RtpDataContentDescription* data) {
   data->AddLegacyStream(ssrc);
 }
 
diff --git a/pc/jsep_transport_controller_unittest.cc b/pc/jsep_transport_controller_unittest.cc
index e81b667..c0927b9d 100644
--- a/pc/jsep_transport_controller_unittest.cc
+++ b/pc/jsep_transport_controller_unittest.cc
@@ -175,8 +175,9 @@
                       cricket::IceMode ice_mode,
                       cricket::ConnectionRole conn_role,
                       rtc::scoped_refptr<rtc::RTCCertificate> cert) {
-    std::unique_ptr<cricket::DataContentDescription> data(
-        new cricket::DataContentDescription());
+    RTC_CHECK(protocol_type == cricket::MediaProtocolType::kSctp);
+    std::unique_ptr<cricket::SctpDataContentDescription> data(
+        new cricket::SctpDataContentDescription());
     data->set_rtcp_mux(true);
     description->AddContent(mid, protocol_type,
                             /*rejected=*/false, data.release());
diff --git a/pc/media_protocol_names.cc b/pc/media_protocol_names.cc
new file mode 100644
index 0000000..6ce2f02
--- /dev/null
+++ b/pc/media_protocol_names.cc
@@ -0,0 +1,41 @@
+/*
+ *  Copyright 2019 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 "pc/media_protocol_names.h"
+
+namespace cricket {
+
+const char kMediaProtocolRtpPrefix[] = "RTP/";
+
+const char kMediaProtocolSctp[] = "SCTP";
+const char kMediaProtocolDtlsSctp[] = "DTLS/SCTP";
+const char kMediaProtocolUdpDtlsSctp[] = "UDP/DTLS/SCTP";
+const char kMediaProtocolTcpDtlsSctp[] = "TCP/DTLS/SCTP";
+
+bool IsDtlsSctp(const std::string& protocol) {
+  return protocol == kMediaProtocolDtlsSctp ||
+         protocol == kMediaProtocolUdpDtlsSctp ||
+         protocol == kMediaProtocolTcpDtlsSctp;
+}
+
+bool IsPlainSctp(const std::string& protocol) {
+  return protocol == kMediaProtocolSctp;
+}
+
+bool IsRtpProtocol(const std::string& protocol) {
+  return protocol.empty() ||
+         (protocol.find(cricket::kMediaProtocolRtpPrefix) != std::string::npos);
+}
+
+bool IsSctpProtocol(const std::string& protocol) {
+  return IsPlainSctp(protocol) || IsDtlsSctp(protocol);
+}
+
+}  // namespace cricket
diff --git a/pc/media_protocol_names.h b/pc/media_protocol_names.h
new file mode 100644
index 0000000..88f1c46
--- /dev/null
+++ b/pc/media_protocol_names.h
@@ -0,0 +1,35 @@
+/*
+ *  Copyright 2019 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef PC_MEDIA_PROTOCOL_NAMES_H_
+#define PC_MEDIA_PROTOCOL_NAMES_H_
+
+#include <string>
+
+namespace cricket {
+
+// Names or name prefixes of protocols as defined by SDP specifications.
+extern const char kMediaProtocolRtpPrefix[];
+extern const char kMediaProtocolSctp[];
+extern const char kMediaProtocolDtlsSctp[];
+extern const char kMediaProtocolUdpDtlsSctp[];
+extern const char kMediaProtocolTcpDtlsSctp[];
+
+bool IsDtlsSctp(const std::string& protocol);
+bool IsPlainSctp(const std::string& protocol);
+
+// Returns true if the given media section protocol indicates use of RTP.
+bool IsRtpProtocol(const std::string& protocol);
+// Returns true if the given media section protocol indicates use of SCTP.
+bool IsSctpProtocol(const std::string& protocol);
+
+}  // namespace cricket
+
+#endif  // PC_MEDIA_PROTOCOL_NAMES_H_
diff --git a/pc/media_session.cc b/pc/media_session.cc
index 0eace22..9c03a1e 100644
--- a/pc/media_session.cc
+++ b/pc/media_session.cc
@@ -27,6 +27,7 @@
 #include "media/base/media_constants.h"
 #include "p2p/base/p2p_constants.h"
 #include "pc/channel_manager.h"
+#include "pc/media_protocol_names.h"
 #include "pc/rtp_media_utils.h"
 #include "pc/srtp_filter.h"
 #include "rtc_base/checks.h"
@@ -68,13 +69,6 @@
 // but we tolerate "RTP/SAVPF" in offers we receive, for compatibility.
 const char kMediaProtocolSavpf[] = "RTP/SAVPF";
 
-const char kMediaProtocolRtpPrefix[] = "RTP/";
-
-const char kMediaProtocolSctp[] = "SCTP";
-const char kMediaProtocolDtlsSctp[] = "DTLS/SCTP";
-const char kMediaProtocolUdpDtlsSctp[] = "UDP/DTLS/SCTP";
-const char kMediaProtocolTcpDtlsSctp[] = "TCP/DTLS/SCTP";
-
 // Note that the below functions support some protocol strings purely for
 // legacy compatibility, as required by JSEP in Section 5.1.2, Profile Names
 // and Interoperability.
@@ -91,20 +85,6 @@
          protocol == "RTP/SAVP" || protocol == "RTP/AVP";
 }
 
-static bool IsDtlsSctp(const std::string& protocol) {
-  return protocol == kMediaProtocolDtlsSctp ||
-         protocol == kMediaProtocolUdpDtlsSctp ||
-         protocol == kMediaProtocolTcpDtlsSctp;
-}
-
-static bool IsPlainSctp(const std::string& protocol) {
-  return protocol == kMediaProtocolSctp;
-}
-
-static bool IsSctp(const std::string& protocol) {
-  return IsPlainSctp(protocol) || IsDtlsSctp(protocol);
-}
-
 static RtpTransceiverDirection NegotiateRtpTransceiverDirection(
     RtpTransceiverDirection offer,
     RtpTransceiverDirection wants) {
@@ -489,7 +469,7 @@
     StreamParamsVec* current_streams,
     MediaContentDescriptionImpl<C>* content_description) {
   // SCTP streams are not negotiated using SDP/ContentDescriptions.
-  if (IsSctp(content_description->protocol())) {
+  if (IsSctpProtocol(content_description->protocol())) {
     return true;
   }
 
@@ -608,11 +588,6 @@
       target_cryptos->end());
 }
 
-bool IsRtpProtocol(const std::string& protocol) {
-  return protocol.empty() ||
-         (protocol.find(cricket::kMediaProtocolRtpPrefix) != std::string::npos);
-}
-
 static bool IsRtpContent(SessionDescription* sdesc,
                          const std::string& content_name) {
   bool is_rtp = false;
@@ -741,32 +716,22 @@
 // crypto (in current_cryptos) and it is enabled (in secure_policy), crypto is
 // created (according to crypto_suites). The created content is added to the
 // offer.
-template <class C>
-static bool CreateMediaContentOffer(
+static bool CreateContentOffer(
     const MediaDescriptionOptions& media_description_options,
     const MediaSessionOptions& session_options,
-    const std::vector<C>& codecs,
     const SecurePolicy& secure_policy,
     const CryptoParamsVec* current_cryptos,
     const std::vector<std::string>& crypto_suites,
     const RtpHeaderExtensions& rtp_extensions,
     UniqueRandomIdGenerator* ssrc_generator,
     StreamParamsVec* current_streams,
-    MediaContentDescriptionImpl<C>* offer) {
-  offer->AddCodecs(codecs);
-
+    MediaContentDescription* offer) {
   offer->set_rtcp_mux(session_options.rtcp_mux_enabled);
   if (offer->type() == cricket::MEDIA_TYPE_VIDEO) {
     offer->set_rtcp_reduced_size(true);
   }
   offer->set_rtp_header_extensions(rtp_extensions);
 
-  if (!AddStreamParams(media_description_options.sender_options,
-                       session_options.rtcp_cname, ssrc_generator,
-                       current_streams, offer)) {
-    return false;
-  }
-
   AddSimulcastToMediaDescription(media_description_options, offer);
 
   if (secure_policy != SEC_DISABLED) {
@@ -785,6 +750,30 @@
   }
   return true;
 }
+template <class C>
+static bool CreateMediaContentOffer(
+    const MediaDescriptionOptions& media_description_options,
+    const MediaSessionOptions& session_options,
+    const std::vector<C>& codecs,
+    const SecurePolicy& secure_policy,
+    const CryptoParamsVec* current_cryptos,
+    const std::vector<std::string>& crypto_suites,
+    const RtpHeaderExtensions& rtp_extensions,
+    UniqueRandomIdGenerator* ssrc_generator,
+    StreamParamsVec* current_streams,
+    MediaContentDescriptionImpl<C>* offer) {
+  offer->AddCodecs(codecs);
+  if (!AddStreamParams(media_description_options.sender_options,
+                       session_options.rtcp_cname, ssrc_generator,
+                       current_streams, offer)) {
+    return false;
+  }
+
+  return CreateContentOffer(media_description_options, session_options,
+                            secure_policy, current_cryptos, crypto_suites,
+                            rtp_extensions, ssrc_generator, current_streams,
+                            offer);
+}
 
 template <class C>
 static bool ReferencedCodecsMatch(const std::vector<C>& codecs1,
@@ -1186,6 +1175,28 @@
                       audio_codecs->end());
 }
 
+template <class C>
+static bool SetCodecsInAnswer(
+    const MediaContentDescriptionImpl<C>* offer,
+    const std::vector<C>& local_codecs,
+    const MediaDescriptionOptions& media_description_options,
+    const MediaSessionOptions& session_options,
+    UniqueRandomIdGenerator* ssrc_generator,
+    StreamParamsVec* current_streams,
+    MediaContentDescriptionImpl<C>* answer) {
+  std::vector<C> negotiated_codecs;
+  NegotiateCodecs(local_codecs, offer->codecs(), &negotiated_codecs,
+                  media_description_options.codec_preferences.empty());
+  answer->AddCodecs(negotiated_codecs);
+  answer->set_protocol(offer->protocol());
+  if (!AddStreamParams(media_description_options.sender_options,
+                       session_options.rtcp_cname, ssrc_generator,
+                       current_streams, answer)) {
+    return false;  // Something went seriously wrong.
+  }
+  return true;
+}
+
 // Create a media content to be answered for the given |sender_options|
 // according to the given session_options.rtcp_mux, session_options.streams,
 // codecs, crypto, and current_streams.  If we don't currently have crypto (in
@@ -1193,12 +1204,10 @@
 // (according to crypto_suites). The codecs, rtcp_mux, and crypto are all
 // negotiated with the offer. If the negotiation fails, this method returns
 // false.  The created content is added to the offer.
-template <class C>
 static bool CreateMediaContentAnswer(
-    const MediaContentDescriptionImpl<C>* offer,
+    const MediaContentDescription* offer,
     const MediaDescriptionOptions& media_description_options,
     const MediaSessionOptions& session_options,
-    const std::vector<C>& local_codecs,
     const SecurePolicy& sdes_policy,
     const CryptoParamsVec* current_cryptos,
     const RtpHeaderExtensions& local_rtp_extenstions,
@@ -1206,13 +1215,7 @@
     bool enable_encrypted_rtp_header_extensions,
     StreamParamsVec* current_streams,
     bool bundle_enabled,
-    MediaContentDescriptionImpl<C>* answer) {
-  std::vector<C> negotiated_codecs;
-  NegotiateCodecs(local_codecs, offer->codecs(), &negotiated_codecs,
-                  media_description_options.codec_preferences.empty());
-  answer->AddCodecs(negotiated_codecs);
-  answer->set_protocol(offer->protocol());
-
+    MediaContentDescription* answer) {
   answer->set_extmap_allow_mixed_enum(offer->extmap_allow_mixed_enum());
   RtpHeaderExtensions negotiated_rtp_extensions;
   NegotiateRtpHeaderExtensions(
@@ -1240,12 +1243,6 @@
     return false;
   }
 
-  if (!AddStreamParams(media_description_options.sender_options,
-                       session_options.rtcp_cname, ssrc_generator,
-                       current_streams, answer)) {
-    return false;  // Something went seriously wrong.
-  }
-
   AddSimulcastToMediaDescription(media_description_options, answer);
 
   answer->set_direction(NegotiateRtpTransceiverDirection(
@@ -1397,7 +1394,7 @@
   channel_manager->GetSupportedAudioRtpHeaderExtensions(&audio_rtp_extensions_);
   channel_manager->GetSupportedVideoCodecs(&video_codecs_);
   channel_manager->GetSupportedVideoRtpHeaderExtensions(&video_rtp_extensions_);
-  channel_manager->GetSupportedDataCodecs(&data_codecs_);
+  channel_manager->GetSupportedDataCodecs(&rtp_data_codecs_);
   ComputeAudioCodecsIntersectionAndUnion();
 }
 
@@ -1484,15 +1481,15 @@
 
   AudioCodecs offer_audio_codecs;
   VideoCodecs offer_video_codecs;
-  DataCodecs offer_data_codecs;
+  RtpDataCodecs offer_rtp_data_codecs;
   GetCodecsForOffer(current_active_contents, &offer_audio_codecs,
-                    &offer_video_codecs, &offer_data_codecs);
+                    &offer_video_codecs, &offer_rtp_data_codecs);
 
   if (!session_options.vad_enabled) {
     // If application doesn't want CN codecs in offer.
     StripCNCodecs(&offer_audio_codecs);
   }
-  FilterDataCodecs(&offer_data_codecs,
+  FilterDataCodecs(&offer_rtp_data_codecs,
                    session_options.data_channel_type == DCT_SCTP);
 
   RtpHeaderExtensions audio_rtp_extensions;
@@ -1536,7 +1533,7 @@
       case MEDIA_TYPE_DATA:
         if (!AddDataContentForOffer(media_description_options, session_options,
                                     current_content, current_description,
-                                    offer_data_codecs, &current_streams,
+                                    offer_rtp_data_codecs, &current_streams,
                                     offer.get(), &ice_credentials)) {
           return nullptr;
         }
@@ -1634,15 +1631,15 @@
   // sections.
   AudioCodecs answer_audio_codecs;
   VideoCodecs answer_video_codecs;
-  DataCodecs answer_data_codecs;
+  RtpDataCodecs answer_rtp_data_codecs;
   GetCodecsForAnswer(current_active_contents, *offer, &answer_audio_codecs,
-                     &answer_video_codecs, &answer_data_codecs);
+                     &answer_video_codecs, &answer_rtp_data_codecs);
 
   if (!session_options.vad_enabled) {
     // If application doesn't want CN codecs in answer.
     StripCNCodecs(&answer_audio_codecs);
   }
-  FilterDataCodecs(&answer_data_codecs,
+  FilterDataCodecs(&answer_rtp_data_codecs,
                    session_options.data_channel_type == DCT_SCTP);
 
   auto answer = absl::make_unique<SessionDescription>();
@@ -1695,8 +1692,8 @@
         if (!AddDataContentForAnswer(
                 media_description_options, session_options, offer_content,
                 offer, current_content, current_description,
-                bundle_transport.get(), answer_data_codecs, &current_streams,
-                answer.get(), &ice_credentials)) {
+                bundle_transport.get(), answer_rtp_data_codecs,
+                &current_streams, answer.get(), &ice_credentials)) {
           return nullptr;
         }
         break;
@@ -1816,7 +1813,7 @@
     const std::vector<const ContentInfo*>& current_active_contents,
     AudioCodecs* audio_codecs,
     VideoCodecs* video_codecs,
-    DataCodecs* data_codecs,
+    RtpDataCodecs* rtp_data_codecs,
     UsedPayloadTypes* used_pltypes) {
   for (const ContentInfo* content : current_active_contents) {
     if (IsMediaContentOfType(content, MEDIA_TYPE_AUDIO)) {
@@ -1828,9 +1825,13 @@
           content->media_description()->as_video();
       MergeCodecs<VideoCodec>(video->codecs(), video_codecs, used_pltypes);
     } else if (IsMediaContentOfType(content, MEDIA_TYPE_DATA)) {
-      const DataContentDescription* data =
-          content->media_description()->as_data();
-      MergeCodecs<DataCodec>(data->codecs(), data_codecs, used_pltypes);
+      const RtpDataContentDescription* data =
+          content->media_description()->as_rtp_data();
+      if (data) {
+        // Only relevant for RTP datachannels
+        MergeCodecs<RtpDataCodec>(data->codecs(), rtp_data_codecs,
+                                  used_pltypes);
+      }
     }
   }
 }
@@ -1845,18 +1846,18 @@
     const std::vector<const ContentInfo*>& current_active_contents,
     AudioCodecs* audio_codecs,
     VideoCodecs* video_codecs,
-    DataCodecs* data_codecs) const {
+    RtpDataCodecs* rtp_data_codecs) const {
   // First - get all codecs from the current description if the media type
   // is used. Add them to |used_pltypes| so the payload type is not reused if a
   // new media type is added.
   UsedPayloadTypes used_pltypes;
   MergeCodecsFromDescription(current_active_contents, audio_codecs,
-                             video_codecs, data_codecs, &used_pltypes);
+                             video_codecs, rtp_data_codecs, &used_pltypes);
 
   // Add our codecs that are not in the current description.
   MergeCodecs<AudioCodec>(all_audio_codecs_, audio_codecs, &used_pltypes);
   MergeCodecs<VideoCodec>(video_codecs_, video_codecs, &used_pltypes);
-  MergeCodecs<DataCodec>(data_codecs_, data_codecs, &used_pltypes);
+  MergeCodecs<DataCodec>(rtp_data_codecs_, rtp_data_codecs, &used_pltypes);
 }
 
 // Getting codecs for an answer involves these steps:
@@ -1871,18 +1872,18 @@
     const SessionDescription& remote_offer,
     AudioCodecs* audio_codecs,
     VideoCodecs* video_codecs,
-    DataCodecs* data_codecs) const {
+    RtpDataCodecs* rtp_data_codecs) const {
   // First - get all codecs from the current description if the media type
   // is used. Add them to |used_pltypes| so the payload type is not reused if a
   // new media type is added.
   UsedPayloadTypes used_pltypes;
   MergeCodecsFromDescription(current_active_contents, audio_codecs,
-                             video_codecs, data_codecs, &used_pltypes);
+                             video_codecs, rtp_data_codecs, &used_pltypes);
 
   // Second - filter out codecs that we don't support at all and should ignore.
   AudioCodecs filtered_offered_audio_codecs;
   VideoCodecs filtered_offered_video_codecs;
-  DataCodecs filtered_offered_data_codecs;
+  RtpDataCodecs filtered_offered_rtp_data_codecs;
   for (const ContentInfo& content : remote_offer.contents()) {
     if (IsMediaContentOfType(&content, MEDIA_TYPE_AUDIO)) {
       const AudioContentDescription* audio =
@@ -1909,15 +1910,19 @@
         }
       }
     } else if (IsMediaContentOfType(&content, MEDIA_TYPE_DATA)) {
-      const DataContentDescription* data =
-          content.media_description()->as_data();
-      for (const DataCodec& offered_data_codec : data->codecs()) {
-        if (!FindMatchingCodec<DataCodec>(data->codecs(),
-                                          filtered_offered_data_codecs,
-                                          offered_data_codec, nullptr) &&
-            FindMatchingCodec<DataCodec>(data->codecs(), data_codecs_,
-                                         offered_data_codec, nullptr)) {
-          filtered_offered_data_codecs.push_back(offered_data_codec);
+      const RtpDataContentDescription* data =
+          content.media_description()->as_rtp_data();
+      if (data) {
+        // RTP data. This part is inactive for SCTP data.
+        for (const RtpDataCodec& offered_rtp_data_codec : data->codecs()) {
+          if (!FindMatchingCodec<RtpDataCodec>(
+                  data->codecs(), filtered_offered_rtp_data_codecs,
+                  offered_rtp_data_codec, nullptr) &&
+              FindMatchingCodec<RtpDataCodec>(data->codecs(), rtp_data_codecs_,
+                                              offered_rtp_data_codec,
+                                              nullptr)) {
+            filtered_offered_rtp_data_codecs.push_back(offered_rtp_data_codec);
+          }
         }
       }
     }
@@ -1929,7 +1934,7 @@
                           &used_pltypes);
   MergeCodecs<VideoCodec>(filtered_offered_video_codecs, video_codecs,
                           &used_pltypes);
-  MergeCodecs<DataCodec>(filtered_offered_data_codecs, data_codecs,
+  MergeCodecs<DataCodec>(filtered_offered_rtp_data_codecs, rtp_data_codecs,
                          &used_pltypes);
 }
 
@@ -2206,18 +2211,101 @@
   return true;
 }
 
+bool MediaSessionDescriptionFactory::AddSctpDataContentForOffer(
+    const MediaDescriptionOptions& media_description_options,
+    const MediaSessionOptions& session_options,
+    const ContentInfo* current_content,
+    const SessionDescription* current_description,
+    StreamParamsVec* current_streams,
+    SessionDescription* desc,
+    IceCredentialsIterator* ice_credentials) const {
+  std::unique_ptr<SctpDataContentDescription> data(
+      new SctpDataContentDescription());
+
+  bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED);
+
+  cricket::SecurePolicy sdes_policy =
+      IsDtlsActive(current_content, current_description) ? cricket::SEC_DISABLED
+                                                         : secure();
+  std::vector<std::string> crypto_suites;
+  // SDES doesn't make sense for SCTP, so we disable it, and we only
+  // get SDES crypto suites for RTP-based data channels.
+  sdes_policy = cricket::SEC_DISABLED;
+  // Unlike SetMediaProtocol below, we need to set the protocol
+  // before we call CreateMediaContentOffer.  Otherwise,
+  // CreateMediaContentOffer won't know this is SCTP and will
+  // generate SSRCs rather than SIDs.
+  // TODO(deadbeef): Offer kMediaProtocolUdpDtlsSctp (or TcpDtlsSctp), once
+  // it's safe to do so. Older versions of webrtc would reject these
+  // protocols; see https://bugs.chromium.org/p/webrtc/issues/detail?id=7706.
+  data->set_protocol(secure_transport ? kMediaProtocolDtlsSctp
+                                      : kMediaProtocolSctp);
+
+  if (!CreateContentOffer(media_description_options, session_options,
+                          sdes_policy, GetCryptos(current_content),
+                          crypto_suites, RtpHeaderExtensions(), ssrc_generator_,
+                          current_streams, data.get())) {
+    return false;
+  }
+
+  desc->AddContent(media_description_options.mid, MediaProtocolType::kSctp,
+                   data.release());
+  if (!AddTransportOffer(media_description_options.mid,
+                         media_description_options.transport_options,
+                         current_description, desc, ice_credentials)) {
+    return false;
+  }
+  return true;
+}
+
+bool MediaSessionDescriptionFactory::AddRtpDataContentForOffer(
+    const MediaDescriptionOptions& media_description_options,
+    const MediaSessionOptions& session_options,
+    const ContentInfo* current_content,
+    const SessionDescription* current_description,
+    const RtpDataCodecs& rtp_data_codecs,
+    StreamParamsVec* current_streams,
+    SessionDescription* desc,
+    IceCredentialsIterator* ice_credentials) const {
+  std::unique_ptr<RtpDataContentDescription> data(
+      new RtpDataContentDescription());
+  bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED);
+
+  cricket::SecurePolicy sdes_policy =
+      IsDtlsActive(current_content, current_description) ? cricket::SEC_DISABLED
+                                                         : secure();
+  std::vector<std::string> crypto_suites;
+  GetSupportedDataSdesCryptoSuiteNames(session_options.crypto_options,
+                                       &crypto_suites);
+  if (!CreateMediaContentOffer(media_description_options, session_options,
+                               rtp_data_codecs, sdes_policy,
+                               GetCryptos(current_content), crypto_suites,
+                               RtpHeaderExtensions(), ssrc_generator_,
+                               current_streams, data.get())) {
+    return false;
+  }
+
+  data->set_bandwidth(kDataMaxBandwidth);
+  SetMediaProtocol(secure_transport, data.get());
+  desc->AddContent(media_description_options.mid, MediaProtocolType::kRtp,
+                   media_description_options.stopped, data.release());
+  if (!AddTransportOffer(media_description_options.mid,
+                         media_description_options.transport_options,
+                         current_description, desc, ice_credentials)) {
+    return false;
+  }
+  return true;
+}
+
 bool MediaSessionDescriptionFactory::AddDataContentForOffer(
     const MediaDescriptionOptions& media_description_options,
     const MediaSessionOptions& session_options,
     const ContentInfo* current_content,
     const SessionDescription* current_description,
-    const DataCodecs& data_codecs,
+    const RtpDataCodecs& rtp_data_codecs,
     StreamParamsVec* current_streams,
     SessionDescription* desc,
     IceCredentialsIterator* ice_credentials) const {
-  bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED);
-
-  std::unique_ptr<DataContentDescription> data(new DataContentDescription());
   bool is_sctp = (session_options.data_channel_type == DCT_SCTP);
   // If the DataChannel type is not specified, use the DataChannel type in
   // the current description.
@@ -2226,52 +2314,16 @@
     is_sctp = (current_content->media_description()->protocol() ==
                kMediaProtocolSctp);
   }
-
-  cricket::SecurePolicy sdes_policy =
-      IsDtlsActive(current_content, current_description) ? cricket::SEC_DISABLED
-                                                         : secure();
-  std::vector<std::string> crypto_suites;
   if (is_sctp) {
-    // SDES doesn't make sense for SCTP, so we disable it, and we only
-    // get SDES crypto suites for RTP-based data channels.
-    sdes_policy = cricket::SEC_DISABLED;
-    // Unlike SetMediaProtocol below, we need to set the protocol
-    // before we call CreateMediaContentOffer.  Otherwise,
-    // CreateMediaContentOffer won't know this is SCTP and will
-    // generate SSRCs rather than SIDs.
-    // TODO(deadbeef): Offer kMediaProtocolUdpDtlsSctp (or TcpDtlsSctp), once
-    // it's safe to do so. Older versions of webrtc would reject these
-    // protocols; see https://bugs.chromium.org/p/webrtc/issues/detail?id=7706.
-    data->set_protocol(secure_transport ? kMediaProtocolDtlsSctp
-                                        : kMediaProtocolSctp);
+    return AddSctpDataContentForOffer(
+        media_description_options, session_options, current_content,
+        current_description, current_streams, desc, ice_credentials);
   } else {
-    GetSupportedDataSdesCryptoSuiteNames(session_options.crypto_options,
-                                         &crypto_suites);
+    return AddRtpDataContentForOffer(media_description_options, session_options,
+                                     current_content, current_description,
+                                     rtp_data_codecs, current_streams, desc,
+                                     ice_credentials);
   }
-
-  // Even SCTP uses a "codec".
-  if (!CreateMediaContentOffer(
-          media_description_options, session_options, data_codecs, sdes_policy,
-          GetCryptos(current_content), crypto_suites, RtpHeaderExtensions(),
-          ssrc_generator_, current_streams, data.get())) {
-    return false;
-  }
-
-  if (is_sctp) {
-    desc->AddContent(media_description_options.mid, MediaProtocolType::kSctp,
-                     data.release());
-  } else {
-    data->set_bandwidth(kDataMaxBandwidth);
-    SetMediaProtocol(secure_transport, data.get());
-    desc->AddContent(media_description_options.mid, MediaProtocolType::kRtp,
-                     media_description_options.stopped, data.release());
-  }
-  if (!AddTransportOffer(media_description_options.mid,
-                         media_description_options.transport_options,
-                         current_description, desc, ice_credentials)) {
-    return false;
-  }
-  return true;
 }
 
 // |audio_codecs| = set of all possible codecs that can be used, with correct
@@ -2359,9 +2411,15 @@
   // Do not require or create SDES cryptos if DTLS is used.
   cricket::SecurePolicy sdes_policy =
       audio_transport->secure() ? cricket::SEC_DISABLED : secure();
+  if (!SetCodecsInAnswer(offer_audio_description, filtered_codecs,
+                         media_description_options, session_options,
+                         ssrc_generator_, current_streams,
+                         audio_answer.get())) {
+    return false;
+  }
   if (!CreateMediaContentAnswer(
           offer_audio_description, media_description_options, session_options,
-          filtered_codecs, sdes_policy, GetCryptos(current_content),
+          sdes_policy, GetCryptos(current_content),
           audio_rtp_header_extensions(), ssrc_generator_,
           enable_encrypted_rtp_header_extensions_, current_streams,
           bundle_enabled, audio_answer.get())) {
@@ -2454,9 +2512,15 @@
   // Do not require or create SDES cryptos if DTLS is used.
   cricket::SecurePolicy sdes_policy =
       video_transport->secure() ? cricket::SEC_DISABLED : secure();
+  if (!SetCodecsInAnswer(offer_video_description, filtered_codecs,
+                         media_description_options, session_options,
+                         ssrc_generator_, current_streams,
+                         video_answer.get())) {
+    return false;
+  }
   if (!CreateMediaContentAnswer(
           offer_video_description, media_description_options, session_options,
-          filtered_codecs, sdes_policy, GetCryptos(current_content),
+          sdes_policy, GetCryptos(current_content),
           video_rtp_header_extensions(), ssrc_generator_,
           enable_encrypted_rtp_header_extensions_, current_streams,
           bundle_enabled, video_answer.get())) {
@@ -2492,7 +2556,7 @@
     const ContentInfo* current_content,
     const SessionDescription* current_description,
     const TransportInfo* bundle_transport,
-    const DataCodecs& data_codecs,
+    const RtpDataCodecs& rtp_data_codecs,
     StreamParamsVec* current_streams,
     SessionDescription* answer,
     IceCredentialsIterator* ice_credentials) const {
@@ -2504,28 +2568,51 @@
     return false;
   }
 
-  std::unique_ptr<DataContentDescription> data_answer(
-      new DataContentDescription());
   // Do not require or create SDES cryptos if DTLS is used.
   cricket::SecurePolicy sdes_policy =
       data_transport->secure() ? cricket::SEC_DISABLED : secure();
   bool bundle_enabled = offer_description->HasGroup(GROUP_TYPE_BUNDLE) &&
                         session_options.bundle_enabled;
   RTC_CHECK(IsMediaContentOfType(offer_content, MEDIA_TYPE_DATA));
-  const DataContentDescription* offer_data_description =
-      offer_content->media_description()->as_data();
-  if (!CreateMediaContentAnswer(
-          offer_data_description, media_description_options, session_options,
-          data_codecs, sdes_policy, GetCryptos(current_content),
-          RtpHeaderExtensions(), ssrc_generator_,
-          enable_encrypted_rtp_header_extensions_, current_streams,
-          bundle_enabled, data_answer.get())) {
-    return false;  // Fails the session setup.
-  }
+  std::unique_ptr<MediaContentDescription> data_answer;
+  if (offer_content->media_description()->as_sctp()) {
+    // SCTP data content
+    data_answer = absl::make_unique<SctpDataContentDescription>();
+    const SctpDataContentDescription* offer_data_description =
+        offer_content->media_description()->as_sctp();
+    // Respond with the offerer's proto, whatever it is.
+    data_answer->as_sctp()->set_protocol(offer_data_description->protocol());
+    if (!CreateMediaContentAnswer(
+            offer_data_description, media_description_options, session_options,
+            sdes_policy, GetCryptos(current_content), RtpHeaderExtensions(),
+            ssrc_generator_, enable_encrypted_rtp_header_extensions_,
+            current_streams, bundle_enabled, data_answer.get())) {
+      return false;  // Fails the session setup.
+    }
+    // Respond with sctpmap if the offer uses sctpmap.
+    bool offer_uses_sctpmap = offer_data_description->use_sctpmap();
+    data_answer->as_sctp()->set_use_sctpmap(offer_uses_sctpmap);
+  } else {
+    // RTP offer
+    data_answer = absl::make_unique<RtpDataContentDescription>();
 
-  // Respond with sctpmap if the offer uses sctpmap.
-  bool offer_uses_sctpmap = offer_data_description->use_sctpmap();
-  data_answer->set_use_sctpmap(offer_uses_sctpmap);
+    const RtpDataContentDescription* offer_data_description =
+        offer_content->media_description()->as_rtp_data();
+    RTC_CHECK(offer_data_description);
+    if (!SetCodecsInAnswer(offer_data_description, rtp_data_codecs,
+                           media_description_options, session_options,
+                           ssrc_generator_, current_streams,
+                           data_answer->as_rtp_data())) {
+      return false;
+    }
+    if (!CreateMediaContentAnswer(
+            offer_data_description, media_description_options, session_options,
+            sdes_policy, GetCryptos(current_content), RtpHeaderExtensions(),
+            ssrc_generator_, enable_encrypted_rtp_header_extensions_,
+            current_streams, bundle_enabled, data_answer.get())) {
+      return false;  // Fails the session setup.
+    }
+  }
 
   bool secure = bundle_transport ? bundle_transport->description.secure()
                                  : data_transport->secure();
@@ -2649,20 +2736,35 @@
 
 const AudioContentDescription* GetFirstAudioContentDescription(
     const SessionDescription* sdesc) {
-  return static_cast<const AudioContentDescription*>(
-      GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_AUDIO));
+  auto desc = GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_AUDIO);
+  return desc ? desc->as_audio() : nullptr;
 }
 
 const VideoContentDescription* GetFirstVideoContentDescription(
     const SessionDescription* sdesc) {
-  return static_cast<const VideoContentDescription*>(
-      GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_VIDEO));
+  auto desc = GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_VIDEO);
+  return desc ? desc->as_video() : nullptr;
 }
 
+const RtpDataContentDescription* GetFirstRtpDataContentDescription(
+    const SessionDescription* sdesc) {
+  auto desc = GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_DATA);
+  return desc ? desc->as_rtp_data() : nullptr;
+}
+
+const SctpDataContentDescription* GetFirstSctpDataContentDescription(
+    const SessionDescription* sdesc) {
+  auto desc = GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_DATA);
+  return desc ? desc->as_sctp() : nullptr;
+}
+
+// Returns a shim representing either an SctpDataContentDescription
+// or an RtpDataContentDescription, as appropriate.
+// TODO(bugs.webrtc.org/10597): Remove together with shim.
 const DataContentDescription* GetFirstDataContentDescription(
     const SessionDescription* sdesc) {
-  return static_cast<const DataContentDescription*>(
-      GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_DATA));
+  auto desc = GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_DATA);
+  return desc ? desc->as_data() : nullptr;
 }
 
 //
@@ -2721,20 +2823,33 @@
 
 AudioContentDescription* GetFirstAudioContentDescription(
     SessionDescription* sdesc) {
-  return static_cast<AudioContentDescription*>(
-      GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_AUDIO));
+  auto desc = GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_AUDIO);
+  return desc ? desc->as_audio() : nullptr;
 }
 
 VideoContentDescription* GetFirstVideoContentDescription(
     SessionDescription* sdesc) {
-  return static_cast<VideoContentDescription*>(
-      GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_VIDEO));
+  auto desc = GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_VIDEO);
+  return desc ? desc->as_video() : nullptr;
 }
 
+RtpDataContentDescription* GetFirstRtpDataContentDescription(
+    SessionDescription* sdesc) {
+  auto desc = GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_DATA);
+  return desc ? desc->as_rtp_data() : nullptr;
+}
+
+SctpDataContentDescription* GetFirstSctpDataContentDescription(
+    SessionDescription* sdesc) {
+  auto desc = GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_DATA);
+  return desc ? desc->as_sctp() : nullptr;
+}
+
+// Returns shim
 DataContentDescription* GetFirstDataContentDescription(
     SessionDescription* sdesc) {
-  return static_cast<DataContentDescription*>(
-      GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_DATA));
+  auto desc = GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_DATA);
+  return desc ? desc->as_data() : nullptr;
 }
 
 }  // namespace cricket
diff --git a/pc/media_session.h b/pc/media_session.h
index a369756..dc889b2 100644
--- a/pc/media_session.h
+++ b/pc/media_session.h
@@ -24,6 +24,7 @@
 #include "p2p/base/ice_credentials_iterator.h"
 #include "p2p/base/transport_description_factory.h"
 #include "pc/jsep_transport.h"
+#include "pc/media_protocol_names.h"
 #include "pc/session_description.h"
 #include "rtc_base/unique_id_generator.h"
 
@@ -154,8 +155,10 @@
     video_rtp_extensions_ = extensions;
   }
   RtpHeaderExtensions video_rtp_header_extensions() const;
-  const DataCodecs& data_codecs() const { return data_codecs_; }
-  void set_data_codecs(const DataCodecs& codecs) { data_codecs_ = codecs; }
+  const RtpDataCodecs& rtp_data_codecs() const { return rtp_data_codecs_; }
+  void set_rtp_data_codecs(const RtpDataCodecs& codecs) {
+    rtp_data_codecs_ = codecs;
+  }
   SecurePolicy secure() const { return secure_; }
   void set_secure(SecurePolicy s) { secure_ = s; }
 
@@ -185,13 +188,13 @@
       const std::vector<const ContentInfo*>& current_active_contents,
       AudioCodecs* audio_codecs,
       VideoCodecs* video_codecs,
-      DataCodecs* data_codecs) const;
+      RtpDataCodecs* rtp_data_codecs) const;
   void GetCodecsForAnswer(
       const std::vector<const ContentInfo*>& current_active_contents,
       const SessionDescription& remote_offer,
       AudioCodecs* audio_codecs,
       VideoCodecs* video_codecs,
-      DataCodecs* data_codecs) const;
+      RtpDataCodecs* rtp_data_codecs) const;
   void GetRtpHdrExtsToOffer(
       const std::vector<const ContentInfo*>& current_active_contents,
       RtpHeaderExtensions* audio_extensions,
@@ -240,12 +243,32 @@
       SessionDescription* desc,
       IceCredentialsIterator* ice_credentials) const;
 
+  bool AddSctpDataContentForOffer(
+      const MediaDescriptionOptions& media_description_options,
+      const MediaSessionOptions& session_options,
+      const ContentInfo* current_content,
+      const SessionDescription* current_description,
+      StreamParamsVec* current_streams,
+      SessionDescription* desc,
+      IceCredentialsIterator* ice_credentials) const;
+  bool AddRtpDataContentForOffer(
+      const MediaDescriptionOptions& media_description_options,
+      const MediaSessionOptions& session_options,
+      const ContentInfo* current_content,
+      const SessionDescription* current_description,
+      const RtpDataCodecs& rtp_data_codecs,
+      StreamParamsVec* current_streams,
+      SessionDescription* desc,
+      IceCredentialsIterator* ice_credentials) const;
+  // This function calls either AddRtpDataContentForOffer or
+  // AddSctpDataContentForOffer depending on protocol.
+  // The codecs argument is ignored for SCTP.
   bool AddDataContentForOffer(
       const MediaDescriptionOptions& media_description_options,
       const MediaSessionOptions& session_options,
       const ContentInfo* current_content,
       const SessionDescription* current_description,
-      const DataCodecs& data_codecs,
+      const RtpDataCodecs& rtp_data_codecs,
       StreamParamsVec* current_streams,
       SessionDescription* desc,
       IceCredentialsIterator* ice_credentials) const;
@@ -284,7 +307,7 @@
       const ContentInfo* current_content,
       const SessionDescription* current_description,
       const TransportInfo* bundle_transport,
-      const DataCodecs& data_codecs,
+      const RtpDataCodecs& rtp_data_codecs,
       StreamParamsVec* current_streams,
       SessionDescription* answer,
       IceCredentialsIterator* ice_credentials) const;
@@ -301,7 +324,7 @@
   RtpHeaderExtensions audio_rtp_extensions_;
   VideoCodecs video_codecs_;
   RtpHeaderExtensions video_rtp_extensions_;
-  DataCodecs data_codecs_;
+  RtpDataCodecs rtp_data_codecs_;
   // This object is not owned by the channel so it must outlive it.
   rtc::UniqueRandomIdGenerator* const ssrc_generator_;
   bool enable_encrypted_rtp_header_extensions_ = false;
@@ -330,6 +353,11 @@
     const SessionDescription* sdesc);
 const VideoContentDescription* GetFirstVideoContentDescription(
     const SessionDescription* sdesc);
+const RtpDataContentDescription* GetFirstRtpDataContentDescription(
+    const SessionDescription* sdesc);
+const SctpDataContentDescription* GetFirstSctpDataContentDescription(
+    const SessionDescription* sdesc);
+// Returns shim. Deprecated - ask for the right protocol instead.
 const DataContentDescription* GetFirstDataContentDescription(
     const SessionDescription* sdesc);
 // Non-const versions of the above functions.
@@ -347,6 +375,10 @@
     SessionDescription* sdesc);
 VideoContentDescription* GetFirstVideoContentDescription(
     SessionDescription* sdesc);
+RtpDataContentDescription* GetFirstRtpDataContentDescription(
+    SessionDescription* sdesc);
+SctpDataContentDescription* GetFirstSctpDataContentDescription(
+    SessionDescription* sdesc);
 DataContentDescription* GetFirstDataContentDescription(
     SessionDescription* sdesc);
 
@@ -370,9 +402,6 @@
     const webrtc::CryptoOptions& crypto_options,
     std::vector<std::string>* crypto_suite_names);
 
-// Returns true if the given media section protocol indicates use of RTP.
-bool IsRtpProtocol(const std::string& protocol);
-
 }  // namespace cricket
 
 #endif  // PC_MEDIA_SESSION_H_
diff --git a/pc/media_session_unittest.cc b/pc/media_session_unittest.cc
index 1136607..b69ded3 100644
--- a/pc/media_session_unittest.cc
+++ b/pc/media_session_unittest.cc
@@ -42,12 +42,10 @@
 using cricket::AudioContentDescription;
 using cricket::ContentInfo;
 using cricket::CryptoParamsVec;
-using cricket::DataCodec;
-using cricket::DataContentDescription;
 using cricket::GetFirstAudioContent;
 using cricket::GetFirstAudioContentDescription;
 using cricket::GetFirstDataContent;
-using cricket::GetFirstDataContentDescription;
+using cricket::GetFirstRtpDataContentDescription;
 using cricket::GetFirstVideoContent;
 using cricket::GetFirstVideoContentDescription;
 using cricket::kAutoBandwidth;
@@ -62,6 +60,9 @@
 using cricket::MediaType;
 using cricket::RidDescription;
 using cricket::RidDirection;
+using cricket::RtpDataCodec;
+using cricket::RtpDataContentDescription;
+using cricket::SctpDataContentDescription;
 using cricket::SEC_DISABLED;
 using cricket::SEC_ENABLED;
 using cricket::SEC_REQUIRED;
@@ -126,14 +127,14 @@
 
 static const VideoCodec kVideoCodecsAnswer[] = {VideoCodec(97, "H264")};
 
-static const DataCodec kDataCodecs1[] = {DataCodec(98, "binary-data"),
-                                         DataCodec(99, "utf8-text")};
+static const RtpDataCodec kDataCodecs1[] = {RtpDataCodec(98, "binary-data"),
+                                            RtpDataCodec(99, "utf8-text")};
 
-static const DataCodec kDataCodecs2[] = {DataCodec(126, "binary-data"),
-                                         DataCodec(127, "utf8-text")};
+static const RtpDataCodec kDataCodecs2[] = {RtpDataCodec(126, "binary-data"),
+                                            RtpDataCodec(127, "utf8-text")};
 
-static const DataCodec kDataCodecsAnswer[] = {DataCodec(98, "binary-data"),
-                                              DataCodec(99, "utf8-text")};
+static const RtpDataCodec kDataCodecsAnswer[] = {
+    RtpDataCodec(98, "binary-data"), RtpDataCodec(99, "utf8-text")};
 
 static const RtpExtension kAudioRtpExtension1[] = {
     RtpExtension("urn:ietf:params:rtp-hdrext:ssrc-audio-level", 8),
@@ -412,11 +413,11 @@
     f1_.set_audio_codecs(MAKE_VECTOR(kAudioCodecs1),
                          MAKE_VECTOR(kAudioCodecs1));
     f1_.set_video_codecs(MAKE_VECTOR(kVideoCodecs1));
-    f1_.set_data_codecs(MAKE_VECTOR(kDataCodecs1));
+    f1_.set_rtp_data_codecs(MAKE_VECTOR(kDataCodecs1));
     f2_.set_audio_codecs(MAKE_VECTOR(kAudioCodecs2),
                          MAKE_VECTOR(kAudioCodecs2));
     f2_.set_video_codecs(MAKE_VECTOR(kVideoCodecs2));
-    f2_.set_data_codecs(MAKE_VECTOR(kDataCodecs2));
+    f2_.set_rtp_data_codecs(MAKE_VECTOR(kDataCodecs2));
     tdf1_.set_certificate(rtc::RTCCertificate::Create(
         std::unique_ptr<rtc::SSLIdentity>(new rtc::FakeSSLIdentity("id1"))));
     tdf2_.set_certificate(rtc::RTCCertificate::Create(
@@ -801,7 +802,7 @@
 TEST_F(MediaSessionDescriptionFactoryTest, TestBundleOfferWithSameCodecPlType) {
   const VideoCodec& offered_video_codec = f2_.video_codecs()[0];
   const AudioCodec& offered_audio_codec = f2_.audio_sendrecv_codecs()[0];
-  const DataCodec& offered_data_codec = f2_.data_codecs()[0];
+  const RtpDataCodec& offered_data_codec = f2_.rtp_data_codecs()[0];
   ASSERT_EQ(offered_video_codec.id, offered_audio_codec.id);
   ASSERT_EQ(offered_video_codec.id, offered_data_codec.id);
 
@@ -814,8 +815,8 @@
       GetFirstVideoContentDescription(offer.get());
   const AudioContentDescription* acd =
       GetFirstAudioContentDescription(offer.get());
-  const DataContentDescription* dcd =
-      GetFirstDataContentDescription(offer.get());
+  const RtpDataContentDescription* dcd =
+      GetFirstRtpDataContentDescription(offer.get());
   ASSERT_TRUE(NULL != vcd);
   ASSERT_TRUE(NULL != acd);
   ASSERT_TRUE(NULL != dcd);
@@ -858,8 +859,8 @@
       GetFirstAudioContentDescription(updated_offer.get());
   const VideoContentDescription* vcd =
       GetFirstVideoContentDescription(updated_offer.get());
-  const DataContentDescription* dcd =
-      GetFirstDataContentDescription(updated_offer.get());
+  const RtpDataContentDescription* dcd =
+      GetFirstRtpDataContentDescription(updated_offer.get());
   EXPECT_TRUE(NULL != vcd);
   EXPECT_TRUE(NULL != acd);
   EXPECT_TRUE(NULL != dcd);
@@ -887,7 +888,7 @@
   EXPECT_EQ(MediaProtocolType::kRtp, ac->type);
   EXPECT_EQ(MediaProtocolType::kRtp, dc->type);
   const AudioContentDescription* acd = ac->media_description()->as_audio();
-  const DataContentDescription* dcd = dc->media_description()->as_data();
+  const RtpDataContentDescription* dcd = dc->media_description()->as_rtp_data();
   EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
   EXPECT_EQ(f1_.audio_sendrecv_codecs(), acd->codecs());
   EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attched.
@@ -896,7 +897,7 @@
   ASSERT_CRYPTO(acd, 1U, kDefaultSrtpCryptoSuite);
   EXPECT_EQ(cricket::kMediaProtocolSavpf, acd->protocol());
   EXPECT_EQ(MEDIA_TYPE_DATA, dcd->type());
-  EXPECT_EQ(f1_.data_codecs(), dcd->codecs());
+  EXPECT_EQ(f1_.rtp_data_codecs(), dcd->codecs());
   EXPECT_EQ(0U, dcd->first_ssrc());  // no sender is attached.
   EXPECT_EQ(cricket::kDataMaxBandwidth,
             dcd->bandwidth());   // default bandwidth (auto)
@@ -1280,7 +1281,7 @@
   EXPECT_EQ(MediaProtocolType::kRtp, ac->type);
   EXPECT_EQ(MediaProtocolType::kRtp, dc->type);
   const AudioContentDescription* acd = ac->media_description()->as_audio();
-  const DataContentDescription* dcd = dc->media_description()->as_data();
+  const RtpDataContentDescription* dcd = dc->media_description()->as_rtp_data();
   EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
   EXPECT_THAT(acd->codecs(), ElementsAreArray(kAudioCodecsAnswer));
   EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // negotiated auto bw
@@ -1312,7 +1313,7 @@
   EXPECT_EQ(MediaProtocolType::kRtp, ac->type);
   EXPECT_EQ(MediaProtocolType::kRtp, dc->type);
   const AudioContentDescription* acd = ac->media_description()->as_audio();
-  const DataContentDescription* dcd = dc->media_description()->as_data();
+  const RtpDataContentDescription* dcd = dc->media_description()->as_rtp_data();
   EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
   EXPECT_THAT(acd->codecs(), ElementsAreArray(kAudioCodecsAnswer));
   EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // negotiated auto bw
@@ -1336,15 +1337,16 @@
   ASSERT_TRUE(offer.get() != NULL);
   ContentInfo* dc_offer = offer->GetContentByName("data");
   ASSERT_TRUE(dc_offer != NULL);
-  DataContentDescription* dcd_offer = dc_offer->media_description()->as_data();
+  SctpDataContentDescription* dcd_offer =
+      dc_offer->media_description()->as_sctp();
   EXPECT_TRUE(dcd_offer->use_sctpmap());
 
   std::unique_ptr<SessionDescription> answer =
       f2_.CreateAnswer(offer.get(), opts, NULL);
   const ContentInfo* dc_answer = answer->GetContentByName("data");
   ASSERT_TRUE(dc_answer != NULL);
-  const DataContentDescription* dcd_answer =
-      dc_answer->media_description()->as_data();
+  const SctpDataContentDescription* dcd_answer =
+      dc_answer->media_description()->as_sctp();
   EXPECT_TRUE(dcd_answer->use_sctpmap());
 }
 
@@ -1356,15 +1358,16 @@
   ASSERT_TRUE(offer.get() != NULL);
   ContentInfo* dc_offer = offer->GetContentByName("data");
   ASSERT_TRUE(dc_offer != NULL);
-  DataContentDescription* dcd_offer = dc_offer->media_description()->as_data();
+  SctpDataContentDescription* dcd_offer =
+      dc_offer->media_description()->as_sctp();
   dcd_offer->set_use_sctpmap(false);
 
   std::unique_ptr<SessionDescription> answer =
       f2_.CreateAnswer(offer.get(), opts, NULL);
   const ContentInfo* dc_answer = answer->GetContentByName("data");
   ASSERT_TRUE(dc_answer != NULL);
-  const DataContentDescription* dcd_answer =
-      dc_answer->media_description()->as_data();
+  const SctpDataContentDescription* dcd_answer =
+      dc_answer->media_description()->as_sctp();
   EXPECT_FALSE(dcd_answer->use_sctpmap());
 }
 
@@ -1385,7 +1388,9 @@
   ASSERT_TRUE(offer.get() != nullptr);
   ContentInfo* dc_offer = offer->GetContentByName("data");
   ASSERT_TRUE(dc_offer != nullptr);
-  DataContentDescription* dcd_offer = dc_offer->media_description()->as_data();
+  SctpDataContentDescription* dcd_offer =
+      dc_offer->media_description()->as_sctp();
+  ASSERT_TRUE(dcd_offer);
 
   std::vector<std::string> protos = {"DTLS/SCTP", "UDP/DTLS/SCTP",
                                      "TCP/DTLS/SCTP"};
@@ -1395,8 +1400,8 @@
         f2_.CreateAnswer(offer.get(), opts, nullptr);
     const ContentInfo* dc_answer = answer->GetContentByName("data");
     ASSERT_TRUE(dc_answer != nullptr);
-    const DataContentDescription* dcd_answer =
-        dc_answer->media_description()->as_data();
+    const SctpDataContentDescription* dcd_answer =
+        dc_answer->media_description()->as_sctp();
     EXPECT_FALSE(dc_answer->rejected);
     EXPECT_EQ(proto, dcd_answer->protocol());
   }
@@ -1478,9 +1483,11 @@
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, NULL);
   ContentInfo* dc_offer = offer->GetContentByName("data");
   ASSERT_TRUE(dc_offer != NULL);
-  DataContentDescription* dcd_offer = dc_offer->media_description()->as_data();
+  RtpDataContentDescription* dcd_offer =
+      dc_offer->media_description()->as_rtp_data();
   ASSERT_TRUE(dcd_offer != NULL);
-  std::string protocol = "a weird unknown protocol";
+  // Offer must be acceptable as an RTP protocol in order to be set.
+  std::string protocol = "RTP/a weird unknown protocol";
   dcd_offer->set_protocol(protocol);
 
   std::unique_ptr<SessionDescription> answer =
@@ -1489,8 +1496,8 @@
   const ContentInfo* dc_answer = answer->GetContentByName("data");
   ASSERT_TRUE(dc_answer != NULL);
   EXPECT_TRUE(dc_answer->rejected);
-  const DataContentDescription* dcd_answer =
-      dc_answer->media_description()->as_data();
+  const RtpDataContentDescription* dcd_answer =
+      dc_answer->media_description()->as_rtp_data();
   ASSERT_TRUE(dcd_answer != NULL);
   EXPECT_EQ(protocol, dcd_answer->protocol());
 }
@@ -1688,7 +1695,7 @@
   ASSERT_TRUE(vc != NULL);
   const AudioContentDescription* acd = ac->media_description()->as_audio();
   const VideoContentDescription* vcd = vc->media_description()->as_video();
-  const DataContentDescription* dcd = dc->media_description()->as_data();
+  const RtpDataContentDescription* dcd = dc->media_description()->as_rtp_data();
 
   EXPECT_FALSE(acd->has_ssrcs());  // No StreamParams.
   EXPECT_FALSE(vcd->has_ssrcs());  // No StreamParams.
@@ -1716,16 +1723,16 @@
   answer = f2_.CreateAnswer(offer.get(), answer_opts, NULL);
   ASSERT_TRUE(NULL != GetFirstAudioContentDescription(offer.get()));
   ASSERT_TRUE(NULL != GetFirstVideoContentDescription(offer.get()));
-  ASSERT_TRUE(NULL != GetFirstDataContentDescription(offer.get()));
+  ASSERT_TRUE(NULL != GetFirstRtpDataContentDescription(offer.get()));
   ASSERT_TRUE(NULL != GetFirstAudioContentDescription(answer.get()));
   ASSERT_TRUE(NULL != GetFirstVideoContentDescription(answer.get()));
-  ASSERT_TRUE(NULL != GetFirstDataContentDescription(answer.get()));
+  ASSERT_TRUE(NULL != GetFirstRtpDataContentDescription(answer.get()));
   EXPECT_TRUE(GetFirstAudioContentDescription(offer.get())->rtcp_mux());
   EXPECT_TRUE(GetFirstVideoContentDescription(offer.get())->rtcp_mux());
-  EXPECT_TRUE(GetFirstDataContentDescription(offer.get())->rtcp_mux());
+  EXPECT_TRUE(GetFirstRtpDataContentDescription(offer.get())->rtcp_mux());
   EXPECT_TRUE(GetFirstAudioContentDescription(answer.get())->rtcp_mux());
   EXPECT_TRUE(GetFirstVideoContentDescription(answer.get())->rtcp_mux());
-  EXPECT_TRUE(GetFirstDataContentDescription(answer.get())->rtcp_mux());
+  EXPECT_TRUE(GetFirstRtpDataContentDescription(answer.get())->rtcp_mux());
 
   offer_opts.rtcp_mux_enabled = true;
   answer_opts.rtcp_mux_enabled = false;
@@ -1733,16 +1740,16 @@
   answer = f2_.CreateAnswer(offer.get(), answer_opts, NULL);
   ASSERT_TRUE(NULL != GetFirstAudioContentDescription(offer.get()));
   ASSERT_TRUE(NULL != GetFirstVideoContentDescription(offer.get()));
-  ASSERT_TRUE(NULL != GetFirstDataContentDescription(offer.get()));
+  ASSERT_TRUE(NULL != GetFirstRtpDataContentDescription(offer.get()));
   ASSERT_TRUE(NULL != GetFirstAudioContentDescription(answer.get()));
   ASSERT_TRUE(NULL != GetFirstVideoContentDescription(answer.get()));
-  ASSERT_TRUE(NULL != GetFirstDataContentDescription(answer.get()));
+  ASSERT_TRUE(NULL != GetFirstRtpDataContentDescription(answer.get()));
   EXPECT_TRUE(GetFirstAudioContentDescription(offer.get())->rtcp_mux());
   EXPECT_TRUE(GetFirstVideoContentDescription(offer.get())->rtcp_mux());
-  EXPECT_TRUE(GetFirstDataContentDescription(offer.get())->rtcp_mux());
+  EXPECT_TRUE(GetFirstRtpDataContentDescription(offer.get())->rtcp_mux());
   EXPECT_FALSE(GetFirstAudioContentDescription(answer.get())->rtcp_mux());
   EXPECT_FALSE(GetFirstVideoContentDescription(answer.get())->rtcp_mux());
-  EXPECT_FALSE(GetFirstDataContentDescription(answer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstRtpDataContentDescription(answer.get())->rtcp_mux());
 
   offer_opts.rtcp_mux_enabled = false;
   answer_opts.rtcp_mux_enabled = true;
@@ -1750,16 +1757,16 @@
   answer = f2_.CreateAnswer(offer.get(), answer_opts, NULL);
   ASSERT_TRUE(NULL != GetFirstAudioContentDescription(offer.get()));
   ASSERT_TRUE(NULL != GetFirstVideoContentDescription(offer.get()));
-  ASSERT_TRUE(NULL != GetFirstDataContentDescription(offer.get()));
+  ASSERT_TRUE(NULL != GetFirstRtpDataContentDescription(offer.get()));
   ASSERT_TRUE(NULL != GetFirstAudioContentDescription(answer.get()));
   ASSERT_TRUE(NULL != GetFirstVideoContentDescription(answer.get()));
-  ASSERT_TRUE(NULL != GetFirstDataContentDescription(answer.get()));
+  ASSERT_TRUE(NULL != GetFirstRtpDataContentDescription(answer.get()));
   EXPECT_FALSE(GetFirstAudioContentDescription(offer.get())->rtcp_mux());
   EXPECT_FALSE(GetFirstVideoContentDescription(offer.get())->rtcp_mux());
-  EXPECT_FALSE(GetFirstDataContentDescription(offer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstRtpDataContentDescription(offer.get())->rtcp_mux());
   EXPECT_FALSE(GetFirstAudioContentDescription(answer.get())->rtcp_mux());
   EXPECT_FALSE(GetFirstVideoContentDescription(answer.get())->rtcp_mux());
-  EXPECT_FALSE(GetFirstDataContentDescription(answer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstRtpDataContentDescription(answer.get())->rtcp_mux());
 
   offer_opts.rtcp_mux_enabled = false;
   answer_opts.rtcp_mux_enabled = false;
@@ -1767,16 +1774,16 @@
   answer = f2_.CreateAnswer(offer.get(), answer_opts, NULL);
   ASSERT_TRUE(NULL != GetFirstAudioContentDescription(offer.get()));
   ASSERT_TRUE(NULL != GetFirstVideoContentDescription(offer.get()));
-  ASSERT_TRUE(NULL != GetFirstDataContentDescription(offer.get()));
+  ASSERT_TRUE(NULL != GetFirstRtpDataContentDescription(offer.get()));
   ASSERT_TRUE(NULL != GetFirstAudioContentDescription(answer.get()));
   ASSERT_TRUE(NULL != GetFirstVideoContentDescription(answer.get()));
-  ASSERT_TRUE(NULL != GetFirstDataContentDescription(answer.get()));
+  ASSERT_TRUE(NULL != GetFirstRtpDataContentDescription(answer.get()));
   EXPECT_FALSE(GetFirstAudioContentDescription(offer.get())->rtcp_mux());
   EXPECT_FALSE(GetFirstVideoContentDescription(offer.get())->rtcp_mux());
-  EXPECT_FALSE(GetFirstDataContentDescription(offer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstRtpDataContentDescription(offer.get())->rtcp_mux());
   EXPECT_FALSE(GetFirstAudioContentDescription(answer.get())->rtcp_mux());
   EXPECT_FALSE(GetFirstVideoContentDescription(answer.get())->rtcp_mux());
-  EXPECT_FALSE(GetFirstDataContentDescription(answer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstRtpDataContentDescription(answer.get())->rtcp_mux());
 }
 
 // Create an audio-only answer to a video offer.
@@ -1948,7 +1955,7 @@
   ASSERT_TRUE(dc != NULL);
   const AudioContentDescription* acd = ac->media_description()->as_audio();
   const VideoContentDescription* vcd = vc->media_description()->as_video();
-  const DataContentDescription* dcd = dc->media_description()->as_data();
+  const RtpDataContentDescription* dcd = dc->media_description()->as_rtp_data();
   EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
   EXPECT_EQ(f1_.audio_sendrecv_codecs(), acd->codecs());
 
@@ -1978,7 +1985,7 @@
   EXPECT_TRUE(vcd->rtcp_mux());                 // rtcp-mux defaults on
 
   EXPECT_EQ(MEDIA_TYPE_DATA, dcd->type());
-  EXPECT_EQ(f1_.data_codecs(), dcd->codecs());
+  EXPECT_EQ(f1_.rtp_data_codecs(), dcd->codecs());
   ASSERT_CRYPTO(dcd, 1U, kDefaultSrtpCryptoSuite);
 
   const StreamParamsVec& data_streams = dcd->streams();
@@ -2020,8 +2027,8 @@
       ac->media_description()->as_audio();
   const VideoContentDescription* updated_vcd =
       vc->media_description()->as_video();
-  const DataContentDescription* updated_dcd =
-      dc->media_description()->as_data();
+  const RtpDataContentDescription* updated_dcd =
+      dc->media_description()->as_rtp_data();
 
   EXPECT_EQ(acd->type(), updated_acd->type());
   EXPECT_EQ(acd->codecs(), updated_acd->codecs());
@@ -2307,7 +2314,7 @@
   ASSERT_TRUE(dc != NULL);
   const AudioContentDescription* acd = ac->media_description()->as_audio();
   const VideoContentDescription* vcd = vc->media_description()->as_video();
-  const DataContentDescription* dcd = dc->media_description()->as_data();
+  const RtpDataContentDescription* dcd = dc->media_description()->as_rtp_data();
   ASSERT_CRYPTO(acd, 1U, kDefaultSrtpCryptoSuite);
   ASSERT_CRYPTO(vcd, 1U, kDefaultSrtpCryptoSuite);
   ASSERT_CRYPTO(dcd, 1U, kDefaultSrtpCryptoSuite);
@@ -2375,8 +2382,8 @@
       ac->media_description()->as_audio();
   const VideoContentDescription* updated_vcd =
       vc->media_description()->as_video();
-  const DataContentDescription* updated_dcd =
-      dc->media_description()->as_data();
+  const RtpDataContentDescription* updated_dcd =
+      dc->media_description()->as_rtp_data();
 
   ASSERT_CRYPTO(updated_acd, 1U, kDefaultSrtpCryptoSuite);
   EXPECT_TRUE(CompareCryptoParams(acd->cryptos(), updated_acd->cryptos()));
@@ -3536,8 +3543,8 @@
   const VideoContentDescription* video_offer =
       GetFirstVideoContentDescription(offer.get());
   ASSERT_TRUE(video_offer->cryptos().empty());
-  const DataContentDescription* data_offer =
-      GetFirstDataContentDescription(offer.get());
+  const RtpDataContentDescription* data_offer =
+      GetFirstRtpDataContentDescription(offer.get());
   ASSERT_TRUE(data_offer->cryptos().empty());
 
   const cricket::TransportDescription* audio_offer_trans_desc =
@@ -4068,11 +4075,11 @@
     f1_.set_audio_codecs(MAKE_VECTOR(kAudioCodecs1),
                          MAKE_VECTOR(kAudioCodecs1));
     f1_.set_video_codecs(MAKE_VECTOR(kVideoCodecs1));
-    f1_.set_data_codecs(MAKE_VECTOR(kDataCodecs1));
+    f1_.set_rtp_data_codecs(MAKE_VECTOR(kDataCodecs1));
     f2_.set_audio_codecs(MAKE_VECTOR(kAudioCodecs2),
                          MAKE_VECTOR(kAudioCodecs2));
     f2_.set_video_codecs(MAKE_VECTOR(kVideoCodecs2));
-    f2_.set_data_codecs(MAKE_VECTOR(kDataCodecs2));
+    f2_.set_rtp_data_codecs(MAKE_VECTOR(kDataCodecs2));
     f1_.set_secure(SEC_ENABLED);
     f2_.set_secure(SEC_ENABLED);
     tdf1_.set_certificate(rtc::RTCCertificate::Create(
diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc
index 8a6d0e5..7cdd983 100644
--- a/pc/peer_connection.cc
+++ b/pc/peer_connection.cc
@@ -559,24 +559,13 @@
 // Get the SCTP port out of a SessionDescription.
 // Return -1 if not found.
 int GetSctpPort(const SessionDescription* session_description) {
-  const cricket::DataContentDescription* data_desc =
-      GetFirstDataContentDescription(session_description);
+  const cricket::SctpDataContentDescription* data_desc =
+      GetFirstSctpDataContentDescription(session_description);
   RTC_DCHECK(data_desc);
   if (!data_desc) {
     return -1;
   }
-  std::string value;
-  cricket::DataCodec match_pattern(cricket::kGoogleSctpDataCodecPlType,
-                                   cricket::kGoogleSctpDataCodecName);
-  for (const cricket::DataCodec& codec : data_desc->codecs()) {
-    if (!codec.Matches(match_pattern)) {
-      continue;
-    }
-    if (codec.GetParam(cricket::kCodecParamPort, &value)) {
-      return rtc::FromString<int>(value);
-    }
-  }
-  return -1;
+  return data_desc->port();
 }
 
 // Returns true if |new_desc| requests an ICE restart (i.e., new ufrag/pwd).
@@ -2423,11 +2412,11 @@
   const cricket::ContentInfo* data_content =
       GetFirstDataContent(local_description()->description());
   if (data_content) {
-    const cricket::DataContentDescription* data_desc =
-        data_content->media_description()->as_data();
-    if (absl::StartsWith(data_desc->protocol(),
-                         cricket::kMediaProtocolRtpPrefix)) {
-      UpdateLocalRtpDataChannels(data_desc->streams());
+    const cricket::RtpDataContentDescription* rtp_data_desc =
+        data_content->media_description()->as_rtp_data();
+    // rtp_data_desc will be null if this is an SCTP description.
+    if (rtp_data_desc) {
+      UpdateLocalRtpDataChannels(rtp_data_desc->streams());
     }
   }
 
@@ -2833,8 +2822,8 @@
       GetFirstAudioContentDescription(remote_description()->description());
   const cricket::VideoContentDescription* video_desc =
       GetFirstVideoContentDescription(remote_description()->description());
-  const cricket::DataContentDescription* data_desc =
-      GetFirstDataContentDescription(remote_description()->description());
+  const cricket::RtpDataContentDescription* rtp_data_desc =
+      GetFirstRtpDataContentDescription(remote_description()->description());
 
   // Check if the descriptions include streams, just in case the peer supports
   // MSID, but doesn't indicate so with "a=msid-semantic".
@@ -2887,12 +2876,10 @@
       }
     }
 
-    // Update the DataChannels with the information from the remote peer.
-    if (data_desc) {
-      if (absl::StartsWith(data_desc->protocol(),
-                           cricket::kMediaProtocolRtpPrefix)) {
-        UpdateRemoteRtpDataChannels(GetActiveStreams(data_desc));
-      }
+    // If this is an RTP data transport, update the DataChannels with the
+    // information from the remote peer.
+    if (rtp_data_desc) {
+      UpdateRemoteRtpDataChannels(GetActiveStreams(rtp_data_desc));
     }
 
     // Iterate new_streams and notify the observer about new MediaStreams.
diff --git a/pc/peer_connection_data_channel_unittest.cc b/pc/peer_connection_data_channel_unittest.cc
index ad3817e..4080dd9 100644
--- a/pc/peer_connection_data_channel_unittest.cc
+++ b/pc/peer_connection_data_channel_unittest.cc
@@ -193,14 +193,11 @@
   // Changes the SCTP data channel port on the given session description.
   void ChangeSctpPortOnDescription(cricket::SessionDescription* desc,
                                    int port) {
-    cricket::DataCodec sctp_codec(cricket::kGoogleSctpDataCodecPlType,
-                                  cricket::kGoogleSctpDataCodecName);
-    sctp_codec.SetParam(cricket::kCodecParamPort, port);
-
     auto* data_content = cricket::GetFirstDataContent(desc);
     RTC_DCHECK(data_content);
-    auto* data_desc = data_content->media_description()->as_data();
-    data_desc->set_codecs({sctp_codec});
+    auto* data_desc = data_content->media_description()->as_sctp();
+    RTC_DCHECK(data_desc);
+    data_desc->set_port(port);
   }
 
   std::unique_ptr<rtc::VirtualSocketServer> vss_;
diff --git a/pc/peer_connection_integrationtest.cc b/pc/peer_connection_integrationtest.cc
index 6087f0f..e84ffe0 100644
--- a/pc/peer_connection_integrationtest.cc
+++ b/pc/peer_connection_integrationtest.cc
@@ -3450,8 +3450,8 @@
 }
 
 static void MakeSpecCompliantSctpOffer(cricket::SessionDescription* desc) {
-  cricket::DataContentDescription* dcd_offer =
-      GetFirstDataContentDescription(desc);
+  cricket::SctpDataContentDescription* dcd_offer =
+      GetFirstSctpDataContentDescription(desc);
   ASSERT_TRUE(dcd_offer);
   dcd_offer->set_use_sctpmap(false);
   dcd_offer->set_protocol("UDP/DTLS/SCTP");
diff --git a/pc/session_description.cc b/pc/session_description.cc
index d4ccb50..925acb6 100644
--- a/pc/session_description.cc
+++ b/pc/session_description.cc
@@ -15,6 +15,7 @@
 
 #include "absl/algorithm/container.h"
 #include "absl/memory/memory.h"
+#include "pc/media_protocol_names.h"
 #include "rtc_base/checks.h"
 
 namespace cricket {
@@ -183,6 +184,24 @@
 }
 
 void SessionDescription::AddContent(ContentInfo* content) {
+  // Unwrap the as_data shim layer before using.
+  auto* description = content->media_description();
+  bool should_delete = false;
+  if (description->as_rtp_data()) {
+    if (description->as_rtp_data() != description) {
+      content->set_media_description(
+          description->as_data()->Unshim(&should_delete));
+    }
+  }
+  if (description->as_sctp()) {
+    if (description->as_sctp() != description) {
+      content->set_media_description(
+          description->as_data()->Unshim(&should_delete));
+    }
+  }
+  if (should_delete) {
+    delete description;
+  }
   if (extmap_allow_mixed()) {
     // Mixed support on session level overrides setting on media level.
     content->description->set_extmap_allow_mixed_enum(
@@ -272,4 +291,404 @@
   return NULL;
 }
 
+// DataContentDescription shim creation
+DataContentDescription* RtpDataContentDescription::as_data() {
+  if (!shim_) {
+    shim_.reset(new DataContentDescription(this));
+  }
+  return shim_.get();
+}
+
+const DataContentDescription* RtpDataContentDescription::as_data() const {
+  return const_cast<RtpDataContentDescription*>(this)->as_data();
+}
+
+DataContentDescription* SctpDataContentDescription::as_data() {
+  if (!shim_) {
+    shim_.reset(new DataContentDescription(this));
+  }
+  return shim_.get();
+}
+
+const DataContentDescription* SctpDataContentDescription::as_data() const {
+  return const_cast<SctpDataContentDescription*>(this)->as_data();
+}
+
+DataContentDescription::DataContentDescription() {
+  // In this case, we will initialize |owned_description_| as soon as
+  // we are told what protocol to use via set_protocol or another function
+  // calling CreateShimTarget.
+}
+
+DataContentDescription::DataContentDescription(
+    SctpDataContentDescription* wrapped)
+    : real_description_(wrapped) {
+  // SctpDataContentDescription doesn't contain codecs, but code
+  // using DataContentDescription expects to see one.
+  Super::AddCodec(
+      cricket::DataCodec(kGoogleSctpDataCodecPlType, kGoogleSctpDataCodecName));
+}
+
+DataContentDescription::DataContentDescription(
+    RtpDataContentDescription* wrapped)
+    : real_description_(wrapped) {}
+
+DataContentDescription::DataContentDescription(
+    const DataContentDescription* o) {
+  if (o->real_description_) {
+    owned_description_ = absl::WrapUnique(o->real_description_->Copy());
+    real_description_ = owned_description_.get();
+  }
+}
+
+void DataContentDescription::CreateShimTarget(bool is_sctp) {
+  RTC_LOG(LS_INFO) << "Creating shim target, is_sctp is " << is_sctp;
+  RTC_CHECK(!owned_description_.get());
+  if (is_sctp) {
+    owned_description_ = absl::make_unique<SctpDataContentDescription>();
+    // Copy all information collected so far, except codecs.
+    owned_description_->MediaContentDescription::operator=(*this);
+  } else {
+    owned_description_ = absl::make_unique<RtpDataContentDescription>();
+    // Copy all information collected so far, including codecs.
+    owned_description_->as_rtp_data()
+        ->MediaContentDescriptionImpl<RtpDataCodec>::operator=(*this);
+  }
+  real_description_ = owned_description_.get();
+}
+
+MediaContentDescription* DataContentDescription::Unshim(bool* should_delete) {
+  if (owned_description_) {
+    // Pass ownership to caller, and remove myself.
+    // Since caller can't know if I was owner or owned, tell them.
+    MediaContentDescription* to_return = owned_description_.release();
+    *should_delete = true;
+    return to_return;
+  }
+  // Real object is owner, and presumably referenced from elsewhere.
+  *should_delete = false;
+  return real_description_;
+}
+
+void DataContentDescription::set_protocol(const std::string& protocol) {
+  if (real_description_) {
+    real_description_->set_protocol(protocol);
+  } else {
+    CreateShimTarget(IsSctpProtocol(protocol));
+  }
+}
+
+bool DataContentDescription::IsSctp() const {
+  return (real_description_ && real_description_->as_sctp());
+}
+
+void DataContentDescription::EnsureIsRtp() {
+  RTC_CHECK(real_description_);
+  RTC_CHECK(real_description_->as_rtp_data());
+}
+
+RtpDataContentDescription* DataContentDescription::as_rtp_data() {
+  if (real_description_) {
+    return real_description_->as_rtp_data();
+  }
+  return nullptr;
+}
+
+SctpDataContentDescription* DataContentDescription::as_sctp() {
+  if (real_description_) {
+    return real_description_->as_sctp();
+  }
+  return nullptr;
+}
+
+// Override all methods defined in MediaContentDescription.
+bool DataContentDescription::has_codecs() const {
+  if (!real_description_) {
+    return Super::has_codecs();
+  }
+  return real_description_->has_codecs();
+}
+std::string DataContentDescription::protocol() const {
+  if (!real_description_) {
+    return Super::protocol();
+  }
+  return real_description_->protocol();
+}
+
+webrtc::RtpTransceiverDirection DataContentDescription::direction() const {
+  if (!real_description_) {
+    return Super::direction();
+  }
+  return real_description_->direction();
+}
+void DataContentDescription::set_direction(
+    webrtc::RtpTransceiverDirection direction) {
+  if (!real_description_) {
+    return Super::set_direction(direction);
+  }
+  return real_description_->set_direction(direction);
+}
+bool DataContentDescription::rtcp_mux() const {
+  if (!real_description_) {
+    return Super::rtcp_mux();
+  }
+  return real_description_->rtcp_mux();
+}
+void DataContentDescription::set_rtcp_mux(bool mux) {
+  if (!real_description_) {
+    Super::set_rtcp_mux(mux);
+    return;
+  }
+  real_description_->set_rtcp_mux(mux);
+}
+bool DataContentDescription::rtcp_reduced_size() const {
+  if (!real_description_) {
+    return Super::rtcp_reduced_size();
+  }
+  return real_description_->rtcp_reduced_size();
+}
+void DataContentDescription::set_rtcp_reduced_size(bool reduced_size) {
+  if (!real_description_) {
+    return Super::set_rtcp_reduced_size(reduced_size);
+  }
+
+  return real_description_->set_rtcp_reduced_size(reduced_size);
+}
+int DataContentDescription::bandwidth() const {
+  if (!real_description_) {
+    return Super::bandwidth();
+  }
+
+  return real_description_->bandwidth();
+}
+void DataContentDescription::set_bandwidth(int bandwidth) {
+  if (!real_description_) {
+    return Super::set_bandwidth(bandwidth);
+  }
+
+  return real_description_->set_bandwidth(bandwidth);
+}
+const std::vector<CryptoParams>& DataContentDescription::cryptos() const {
+  if (!real_description_) {
+    return Super::cryptos();
+  }
+
+  return real_description_->cryptos();
+}
+void DataContentDescription::AddCrypto(const CryptoParams& params) {
+  if (!real_description_) {
+    return Super::AddCrypto(params);
+  }
+
+  return real_description_->AddCrypto(params);
+}
+void DataContentDescription::set_cryptos(
+    const std::vector<CryptoParams>& cryptos) {
+  if (!real_description_) {
+    return Super::set_cryptos(cryptos);
+  }
+
+  return real_description_->set_cryptos(cryptos);
+}
+const RtpHeaderExtensions& DataContentDescription::rtp_header_extensions()
+    const {
+  if (!real_description_) {
+    return Super::rtp_header_extensions();
+  }
+
+  return real_description_->rtp_header_extensions();
+}
+void DataContentDescription::set_rtp_header_extensions(
+    const RtpHeaderExtensions& extensions) {
+  if (!real_description_) {
+    return Super::set_rtp_header_extensions(extensions);
+  }
+
+  return real_description_->set_rtp_header_extensions(extensions);
+}
+void DataContentDescription::AddRtpHeaderExtension(
+    const webrtc::RtpExtension& ext) {
+  if (!real_description_) {
+    return Super::AddRtpHeaderExtension(ext);
+  }
+  return real_description_->AddRtpHeaderExtension(ext);
+}
+void DataContentDescription::AddRtpHeaderExtension(
+    const cricket::RtpHeaderExtension& ext) {
+  if (!real_description_) {
+    return Super::AddRtpHeaderExtension(ext);
+  }
+  return real_description_->AddRtpHeaderExtension(ext);
+}
+void DataContentDescription::ClearRtpHeaderExtensions() {
+  if (!real_description_) {
+    return Super::ClearRtpHeaderExtensions();
+  }
+  return real_description_->ClearRtpHeaderExtensions();
+}
+bool DataContentDescription::rtp_header_extensions_set() const {
+  if (!real_description_) {
+    return Super::rtp_header_extensions_set();
+  }
+  return real_description_->rtp_header_extensions_set();
+}
+const StreamParamsVec& DataContentDescription::streams() const {
+  if (!real_description_) {
+    return Super::streams();
+  }
+  return real_description_->streams();
+}
+StreamParamsVec& DataContentDescription::mutable_streams() {
+  if (!real_description_) {
+    return Super::mutable_streams();
+  }
+  EnsureIsRtp();
+  return real_description_->mutable_streams();
+}
+void DataContentDescription::AddStream(const StreamParams& stream) {
+  if (!real_description_) {
+    return Super::AddStream(stream);
+  }
+  EnsureIsRtp();
+  return real_description_->AddStream(stream);
+}
+void DataContentDescription::SetCnameIfEmpty(const std::string& cname) {
+  if (!real_description_) {
+    return Super::SetCnameIfEmpty(cname);
+  }
+  return real_description_->SetCnameIfEmpty(cname);
+}
+uint32_t DataContentDescription::first_ssrc() const {
+  if (!real_description_) {
+    return Super::first_ssrc();
+  }
+  return real_description_->first_ssrc();
+}
+bool DataContentDescription::has_ssrcs() const {
+  if (!real_description_) {
+    return Super::has_ssrcs();
+  }
+  return real_description_->has_ssrcs();
+}
+void DataContentDescription::set_conference_mode(bool enable) {
+  if (!real_description_) {
+    return Super::set_conference_mode(enable);
+  }
+  return real_description_->set_conference_mode(enable);
+}
+bool DataContentDescription::conference_mode() const {
+  if (!real_description_) {
+    return Super::conference_mode();
+  }
+  return real_description_->conference_mode();
+}
+void DataContentDescription::set_connection_address(
+    const rtc::SocketAddress& address) {
+  if (!real_description_) {
+    return Super::set_connection_address(address);
+  }
+  return real_description_->set_connection_address(address);
+}
+const rtc::SocketAddress& DataContentDescription::connection_address() const {
+  if (!real_description_) {
+    return Super::connection_address();
+  }
+  return real_description_->connection_address();
+}
+void DataContentDescription::set_extmap_allow_mixed_enum(
+    ExtmapAllowMixed mixed) {
+  if (!real_description_) {
+    return Super::set_extmap_allow_mixed_enum(mixed);
+  }
+  return real_description_->set_extmap_allow_mixed_enum(mixed);
+}
+MediaContentDescription::ExtmapAllowMixed
+DataContentDescription::extmap_allow_mixed_enum() const {
+  if (!real_description_) {
+    return Super::extmap_allow_mixed_enum();
+  }
+  return real_description_->extmap_allow_mixed_enum();
+}
+bool DataContentDescription::HasSimulcast() const {
+  if (!real_description_) {
+    return Super::HasSimulcast();
+  }
+  return real_description_->HasSimulcast();
+}
+SimulcastDescription& DataContentDescription::simulcast_description() {
+  if (!real_description_) {
+    return Super::simulcast_description();
+  }
+  return real_description_->simulcast_description();
+}
+const SimulcastDescription& DataContentDescription::simulcast_description()
+    const {
+  if (!real_description_) {
+    return Super::simulcast_description();
+  }
+  return real_description_->simulcast_description();
+}
+void DataContentDescription::set_simulcast_description(
+    const SimulcastDescription& simulcast) {
+  if (!real_description_) {
+    return Super::set_simulcast_description(simulcast);
+  }
+  return real_description_->set_simulcast_description(simulcast);
+}
+
+// Methods defined in MediaContentDescriptionImpl.
+// For SCTP, we implement codec handling.
+// For RTP, we pass the codecs.
+// In the cases where type hasn't been decided yet, we return dummies.
+
+const std::vector<DataCodec>& DataContentDescription::codecs() const {
+  if (IsSctp() || !real_description_) {
+    return Super::codecs();
+  }
+  return real_description_->as_rtp_data()->codecs();
+}
+
+void DataContentDescription::set_codecs(const std::vector<DataCodec>& codecs) {
+  if (IsSctp() || !real_description_) {
+    Super::set_codecs(codecs);
+  } else {
+    EnsureIsRtp();
+    real_description_->as_rtp_data()->set_codecs(codecs);
+  }
+}
+
+bool DataContentDescription::HasCodec(int id) {
+  if (IsSctp() || !real_description_) {
+    return Super::HasCodec(id);
+  }
+  return real_description_->as_rtp_data()->HasCodec(id);
+}
+
+void DataContentDescription::AddCodec(const DataCodec& codec) {
+  if (IsSctp() || !real_description_) {
+    Super::AddCodec(codec);
+  } else {
+    EnsureIsRtp();
+    real_description_->as_rtp_data()->AddCodec(codec);
+  }
+}
+
+void DataContentDescription::AddOrReplaceCodec(const DataCodec& codec) {
+  if (IsSctp() || real_description_) {
+    Super::AddOrReplaceCodec(codec);
+  } else {
+    EnsureIsRtp();
+    real_description_->as_rtp_data()->AddOrReplaceCodec(codec);
+  }
+}
+
+void DataContentDescription::AddCodecs(const std::vector<DataCodec>& codecs) {
+  if (IsSctp() || !real_description_) {
+    Super::AddCodecs(codecs);
+  } else {
+    EnsureIsRtp();
+    real_description_->as_rtp_data()->AddCodecs(codecs);
+  }
+}
+
 }  // namespace cricket
diff --git a/pc/session_description.h b/pc/session_description.h
index 7b70ddf..60c3d6b 100644
--- a/pc/session_description.h
+++ b/pc/session_description.h
@@ -18,6 +18,7 @@
 #include <string>
 #include <vector>
 
+#include "absl/memory/memory.h"
 #include "api/crypto_params.h"
 #include "api/media_types.h"
 #include "api/rtp_parameters.h"
@@ -26,6 +27,7 @@
 #include "media/base/stream_params.h"
 #include "p2p/base/transport_description.h"
 #include "p2p/base/transport_info.h"
+#include "pc/media_protocol_names.h"
 #include "pc/simulcast_description.h"
 #include "rtc_base/socket_address.h"
 
@@ -33,7 +35,7 @@
 
 typedef std::vector<AudioCodec> AudioCodecs;
 typedef std::vector<VideoCodec> VideoCodecs;
-typedef std::vector<DataCodec> DataCodecs;
+typedef std::vector<RtpDataCodec> RtpDataCodecs;
 typedef std::vector<CryptoParams> CryptoParamsVec;
 typedef std::vector<webrtc::RtpExtension> RtpHeaderExtensions;
 
@@ -44,19 +46,15 @@
 
 extern const char kMediaProtocolDtlsSavpf[];
 
-extern const char kMediaProtocolRtpPrefix[];
-
-extern const char kMediaProtocolSctp[];
-extern const char kMediaProtocolDtlsSctp[];
-extern const char kMediaProtocolUdpDtlsSctp[];
-extern const char kMediaProtocolTcpDtlsSctp[];
 
 // Options to control how session descriptions are generated.
 const int kAutoBandwidth = -1;
 
 class AudioContentDescription;
-class DataContentDescription;
 class VideoContentDescription;
+class DataContentDescription;
+class RtpDataContentDescription;
+class SctpDataContentDescription;
 
 // Describes a session description media section. There are subclasses for each
 // media type (audio, video, data) that will have additional information.
@@ -77,61 +75,77 @@
   virtual VideoContentDescription* as_video() { return nullptr; }
   virtual const VideoContentDescription* as_video() const { return nullptr; }
 
-  // Try to cast this media description to a DataContentDescription. Returns
-  // nullptr if the cast fails.
+  // Backwards compatible shim: Return a shim object that allows
+  // callers to ignore the distinction between RtpDataContentDescription
+  // and SctpDataContentDescription objects.
   virtual DataContentDescription* as_data() { return nullptr; }
   virtual const DataContentDescription* as_data() const { return nullptr; }
 
+  virtual RtpDataContentDescription* as_rtp_data() { return nullptr; }
+  virtual const RtpDataContentDescription* as_rtp_data() const {
+    return nullptr;
+  }
+
+  virtual SctpDataContentDescription* as_sctp() { return nullptr; }
+  virtual const SctpDataContentDescription* as_sctp() const { return nullptr; }
+
   virtual bool has_codecs() const = 0;
 
   virtual MediaContentDescription* Copy() const = 0;
 
   // |protocol| is the expected media transport protocol, such as RTP/AVPF,
   // RTP/SAVPF or SCTP/DTLS.
-  std::string protocol() const { return protocol_; }
-  void set_protocol(const std::string& protocol) { protocol_ = protocol; }
+  virtual std::string protocol() const { return protocol_; }
+  virtual void set_protocol(const std::string& protocol) {
+    protocol_ = protocol;
+  }
 
-  webrtc::RtpTransceiverDirection direction() const { return direction_; }
-  void set_direction(webrtc::RtpTransceiverDirection direction) {
+  virtual webrtc::RtpTransceiverDirection direction() const {
+    return direction_;
+  }
+  virtual void set_direction(webrtc::RtpTransceiverDirection direction) {
     direction_ = direction;
   }
 
-  bool rtcp_mux() const { return rtcp_mux_; }
-  void set_rtcp_mux(bool mux) { rtcp_mux_ = mux; }
+  virtual bool rtcp_mux() const { return rtcp_mux_; }
+  virtual void set_rtcp_mux(bool mux) { rtcp_mux_ = mux; }
 
-  bool rtcp_reduced_size() const { return rtcp_reduced_size_; }
-  void set_rtcp_reduced_size(bool reduced_size) {
+  virtual bool rtcp_reduced_size() const { return rtcp_reduced_size_; }
+  virtual void set_rtcp_reduced_size(bool reduced_size) {
     rtcp_reduced_size_ = reduced_size;
   }
 
-  int bandwidth() const { return bandwidth_; }
-  void set_bandwidth(int bandwidth) { bandwidth_ = bandwidth; }
+  virtual int bandwidth() const { return bandwidth_; }
+  virtual void set_bandwidth(int bandwidth) { bandwidth_ = bandwidth; }
 
-  const std::vector<CryptoParams>& cryptos() const { return cryptos_; }
-  void AddCrypto(const CryptoParams& params) { cryptos_.push_back(params); }
-  void set_cryptos(const std::vector<CryptoParams>& cryptos) {
+  virtual const std::vector<CryptoParams>& cryptos() const { return cryptos_; }
+  virtual void AddCrypto(const CryptoParams& params) {
+    cryptos_.push_back(params);
+  }
+  virtual void set_cryptos(const std::vector<CryptoParams>& cryptos) {
     cryptos_ = cryptos;
   }
 
-  const RtpHeaderExtensions& rtp_header_extensions() const {
+  virtual const RtpHeaderExtensions& rtp_header_extensions() const {
     return rtp_header_extensions_;
   }
-  void set_rtp_header_extensions(const RtpHeaderExtensions& extensions) {
+  virtual void set_rtp_header_extensions(
+      const RtpHeaderExtensions& extensions) {
     rtp_header_extensions_ = extensions;
     rtp_header_extensions_set_ = true;
   }
-  void AddRtpHeaderExtension(const webrtc::RtpExtension& ext) {
+  virtual void AddRtpHeaderExtension(const webrtc::RtpExtension& ext) {
     rtp_header_extensions_.push_back(ext);
     rtp_header_extensions_set_ = true;
   }
-  void AddRtpHeaderExtension(const cricket::RtpHeaderExtension& ext) {
+  virtual void AddRtpHeaderExtension(const cricket::RtpHeaderExtension& ext) {
     webrtc::RtpExtension webrtc_extension;
     webrtc_extension.uri = ext.uri;
     webrtc_extension.id = ext.id;
     rtp_header_extensions_.push_back(webrtc_extension);
     rtp_header_extensions_set_ = true;
   }
-  void ClearRtpHeaderExtensions() {
+  virtual void ClearRtpHeaderExtensions() {
     rtp_header_extensions_.clear();
     rtp_header_extensions_set_ = true;
   }
@@ -140,62 +154,65 @@
   // signal them. For now we assume an empty list means no signaling, but
   // provide the ClearRtpHeaderExtensions method to allow "no support" to be
   // clearly indicated (i.e. when derived from other information).
-  bool rtp_header_extensions_set() const { return rtp_header_extensions_set_; }
-  const StreamParamsVec& streams() const { return send_streams_; }
+  virtual bool rtp_header_extensions_set() const {
+    return rtp_header_extensions_set_;
+  }
+  virtual const StreamParamsVec& streams() const { return send_streams_; }
   // TODO(pthatcher): Remove this by giving mediamessage.cc access
   // to MediaContentDescription
-  StreamParamsVec& mutable_streams() { return send_streams_; }
-  void AddStream(const StreamParams& stream) {
+  virtual StreamParamsVec& mutable_streams() { return send_streams_; }
+  virtual void AddStream(const StreamParams& stream) {
     send_streams_.push_back(stream);
   }
   // Legacy streams have an ssrc, but nothing else.
   void AddLegacyStream(uint32_t ssrc) {
-    send_streams_.push_back(StreamParams::CreateLegacy(ssrc));
+    AddStream(StreamParams::CreateLegacy(ssrc));
   }
   void AddLegacyStream(uint32_t ssrc, uint32_t fid_ssrc) {
     StreamParams sp = StreamParams::CreateLegacy(ssrc);
     sp.AddFidSsrc(ssrc, fid_ssrc);
-    send_streams_.push_back(sp);
+    AddStream(sp);
   }
 
   // Sets the CNAME of all StreamParams if it have not been set.
-  void SetCnameIfEmpty(const std::string& cname) {
+  virtual void SetCnameIfEmpty(const std::string& cname) {
     for (cricket::StreamParamsVec::iterator it = send_streams_.begin();
          it != send_streams_.end(); ++it) {
       if (it->cname.empty())
         it->cname = cname;
     }
   }
-  uint32_t first_ssrc() const {
+  virtual uint32_t first_ssrc() const {
     if (send_streams_.empty()) {
       return 0;
     }
     return send_streams_[0].first_ssrc();
   }
-  bool has_ssrcs() const {
+  virtual bool has_ssrcs() const {
     if (send_streams_.empty()) {
       return false;
     }
     return send_streams_[0].has_ssrcs();
   }
 
-  void set_conference_mode(bool enable) { conference_mode_ = enable; }
-  bool conference_mode() const { return conference_mode_; }
+  virtual void set_conference_mode(bool enable) { conference_mode_ = enable; }
+  virtual bool conference_mode() const { return conference_mode_; }
 
   // https://tools.ietf.org/html/rfc4566#section-5.7
   // May be present at the media or session level of SDP. If present at both
   // levels, the media-level attribute overwrites the session-level one.
-  void set_connection_address(const rtc::SocketAddress& address) {
+  virtual void set_connection_address(const rtc::SocketAddress& address) {
     connection_address_ = address;
   }
-  const rtc::SocketAddress& connection_address() const {
+  virtual const rtc::SocketAddress& connection_address() const {
     return connection_address_;
   }
 
   // Determines if it's allowed to mix one- and two-byte rtp header extensions
   // within the same rtp stream.
   enum ExtmapAllowMixed { kNo, kSession, kMedia };
-  void set_extmap_allow_mixed_enum(ExtmapAllowMixed new_extmap_allow_mixed) {
+  virtual void set_extmap_allow_mixed_enum(
+      ExtmapAllowMixed new_extmap_allow_mixed) {
     if (new_extmap_allow_mixed == kMedia &&
         extmap_allow_mixed_enum_ == kSession) {
       // Do not downgrade from session level to media level.
@@ -203,10 +220,12 @@
     }
     extmap_allow_mixed_enum_ = new_extmap_allow_mixed;
   }
-  ExtmapAllowMixed extmap_allow_mixed_enum() const {
+  virtual ExtmapAllowMixed extmap_allow_mixed_enum() const {
     return extmap_allow_mixed_enum_;
   }
-  bool extmap_allow_mixed() const { return extmap_allow_mixed_enum_ != kNo; }
+  virtual bool extmap_allow_mixed() const {
+    return extmap_allow_mixed_enum_ != kNo;
+  }
 
   // Simulcast functionality.
   virtual bool HasSimulcast() const { return !simulcast_.empty(); }
@@ -247,13 +266,18 @@
 template <class C>
 class MediaContentDescriptionImpl : public MediaContentDescription {
  public:
+  void set_protocol(const std::string& protocol) override {
+    RTC_DCHECK(IsRtpProtocol(protocol));
+    protocol_ = protocol;
+  }
+
   typedef C CodecType;
 
   // Codecs should be in preference order (most preferred codec first).
-  const std::vector<C>& codecs() const { return codecs_; }
-  void set_codecs(const std::vector<C>& codecs) { codecs_ = codecs; }
-  virtual bool has_codecs() const { return !codecs_.empty(); }
-  bool HasCodec(int id) {
+  virtual const std::vector<C>& codecs() const { return codecs_; }
+  virtual void set_codecs(const std::vector<C>& codecs) { codecs_ = codecs; }
+  bool has_codecs() const override { return !codecs_.empty(); }
+  virtual bool HasCodec(int id) {
     bool found = false;
     for (typename std::vector<C>::iterator iter = codecs_.begin();
          iter != codecs_.end(); ++iter) {
@@ -264,8 +288,8 @@
     }
     return found;
   }
-  void AddCodec(const C& codec) { codecs_.push_back(codec); }
-  void AddOrReplaceCodec(const C& codec) {
+  virtual void AddCodec(const C& codec) { codecs_.push_back(codec); }
+  virtual void AddOrReplaceCodec(const C& codec) {
     for (typename std::vector<C>::iterator iter = codecs_.begin();
          iter != codecs_.end(); ++iter) {
       if (iter->id == codec.id) {
@@ -275,7 +299,7 @@
     }
     AddCodec(codec);
   }
-  void AddCodecs(const std::vector<C>& codecs) {
+  virtual void AddCodecs(const std::vector<C>& codecs) {
     typename std::vector<C>::const_iterator codec;
     for (codec = codecs.begin(); codec != codecs.end(); ++codec) {
       AddCodec(*codec);
@@ -308,22 +332,173 @@
   virtual const VideoContentDescription* as_video() const { return this; }
 };
 
+// The DataContentDescription is a shim over the RtpDataContentDescription
+// and SctpDataContentDescription classes that is used for external callers
+// into this internal API.
+// It is a templated derivation of MediaContentDescriptionImpl because
+// that's what the external caller expects it to be.
+// TODO(bugs.webrtc.org/10597): Declare this class obsolete and remove it
+// once external callers have been updated.
 class DataContentDescription : public MediaContentDescriptionImpl<DataCodec> {
  public:
-  DataContentDescription() {}
+  DataContentDescription();
+  MediaType type() const override { return MEDIA_TYPE_DATA; }
+  DataContentDescription* as_data() override { return this; }
+  const DataContentDescription* as_data() const override { return this; }
 
-  virtual DataContentDescription* Copy() const {
-    return new DataContentDescription(*this);
+  // Override all methods defined in MediaContentDescription.
+  bool has_codecs() const override;
+  DataContentDescription* Copy() const override {
+    return new DataContentDescription(this);
   }
-  virtual MediaType type() const { return MEDIA_TYPE_DATA; }
-  virtual DataContentDescription* as_data() { return this; }
-  virtual const DataContentDescription* as_data() const { return this; }
+  std::string protocol() const override;
+  void set_protocol(const std::string& protocol) override;
+  webrtc::RtpTransceiverDirection direction() const override;
+  void set_direction(webrtc::RtpTransceiverDirection direction) override;
+  bool rtcp_mux() const override;
+  void set_rtcp_mux(bool mux) override;
+  bool rtcp_reduced_size() const override;
+  void set_rtcp_reduced_size(bool) override;
+  int bandwidth() const override;
+  void set_bandwidth(int bandwidth) override;
+  const std::vector<CryptoParams>& cryptos() const override;
+  void AddCrypto(const CryptoParams& params) override;
+  void set_cryptos(const std::vector<CryptoParams>& cryptos) override;
+  const RtpHeaderExtensions& rtp_header_extensions() const override;
+  void set_rtp_header_extensions(
+      const RtpHeaderExtensions& extensions) override;
+  void AddRtpHeaderExtension(const webrtc::RtpExtension& ext) override;
+  void AddRtpHeaderExtension(const cricket::RtpHeaderExtension& ext) override;
+  void ClearRtpHeaderExtensions() override;
+  bool rtp_header_extensions_set() const override;
+  const StreamParamsVec& streams() const override;
+  StreamParamsVec& mutable_streams() override;
+  void AddStream(const StreamParams& stream) override;
+  void SetCnameIfEmpty(const std::string& cname) override;
+  uint32_t first_ssrc() const override;
+  bool has_ssrcs() const override;
+  void set_conference_mode(bool enable) override;
+  bool conference_mode() const override;
+  void set_connection_address(const rtc::SocketAddress& address) override;
+  const rtc::SocketAddress& connection_address() const override;
+  void set_extmap_allow_mixed_enum(ExtmapAllowMixed) override;
+  ExtmapAllowMixed extmap_allow_mixed_enum() const override;
+  bool HasSimulcast() const override;
+  SimulcastDescription& simulcast_description() override;
+  const SimulcastDescription& simulcast_description() const override;
+  void set_simulcast_description(
+      const SimulcastDescription& simulcast) override;
+
+  // Override all methods defined in MediaContentDescriptionImpl.
+  const std::vector<CodecType>& codecs() const override;
+  void set_codecs(const std::vector<CodecType>& codecs) override;
+  bool HasCodec(int id) override;
+  void AddCodec(const CodecType& codec) override;
+  void AddOrReplaceCodec(const CodecType& codec) override;
+  void AddCodecs(const std::vector<CodecType>& codec) override;
+
+ private:
+  typedef MediaContentDescriptionImpl<DataCodec> Super;
+  // Friend classes are allowed to create proxies for themselves.
+  friend class RtpDataContentDescription;  // for constructors
+  friend class SctpDataContentDescription;
+  friend class SessionDescription;  // for Unshim()
+  // Copy constructor. A copy results in an object that owns its
+  // real description, which is a copy of the original description
+  // (whether that was owned or not).
+  explicit DataContentDescription(const DataContentDescription* o);
+
+  explicit DataContentDescription(RtpDataContentDescription*);
+  explicit DataContentDescription(SctpDataContentDescription*);
+
+  // Exposed for internal use - new clients should not use this class.
+  RtpDataContentDescription* as_rtp_data() override;
+  SctpDataContentDescription* as_sctp() override;
+
+  // Create a shimmed object, owned by the shim.
+  void CreateShimTarget(bool is_sctp);
+
+  // Return the shimmed object, passing ownership if owned, and set
+  // |should_delete| to true if it was the owner. If |should_delete|
+  // is true on return, the caller should immediately delete the
+  // DataContentDescription object.
+  MediaContentDescription* Unshim(bool* should_delete);
+
+  // Returns whether SCTP is in use. False when it's not decided.
+  bool IsSctp() const;
+  // Check function for use when caller obviously assumes RTP.
+  void EnsureIsRtp();
+
+  MediaContentDescription* real_description_ = nullptr;
+  std::unique_ptr<MediaContentDescription> owned_description_;
+};
+
+class RtpDataContentDescription
+    : public MediaContentDescriptionImpl<RtpDataCodec> {
+ public:
+  RtpDataContentDescription() {}
+  RtpDataContentDescription(const RtpDataContentDescription& o)
+      : MediaContentDescriptionImpl<RtpDataCodec>(o), shim_(nullptr) {}
+  RtpDataContentDescription& operator=(const RtpDataContentDescription& o) {
+    this->MediaContentDescriptionImpl<RtpDataCodec>::operator=(o);
+    // Do not copy the shim.
+    return *this;
+  }
+
+  RtpDataContentDescription* Copy() const override {
+    return new RtpDataContentDescription(*this);
+  }
+  MediaType type() const override { return MEDIA_TYPE_DATA; }
+  RtpDataContentDescription* as_rtp_data() override { return this; }
+  const RtpDataContentDescription* as_rtp_data() const override { return this; }
+  // Shim support
+  DataContentDescription* as_data() override;
+  const DataContentDescription* as_data() const override;
+
+ private:
+  std::unique_ptr<DataContentDescription> shim_;
+};
+
+class SctpDataContentDescription : public MediaContentDescription {
+ public:
+  SctpDataContentDescription() {}
+  SctpDataContentDescription(const SctpDataContentDescription& o)
+      : MediaContentDescription(o),
+        use_sctpmap_(o.use_sctpmap_),
+        port_(o.port_),
+        max_message_size_(o.max_message_size_),
+        shim_(nullptr) {}
+  SctpDataContentDescription* Copy() const override {
+    return new SctpDataContentDescription(*this);
+  }
+  MediaType type() const override { return MEDIA_TYPE_DATA; }
+  SctpDataContentDescription* as_sctp() override { return this; }
+  const SctpDataContentDescription* as_sctp() const override { return this; }
+  // Shim support
+  DataContentDescription* as_data() override;
+  const DataContentDescription* as_data() const override;
+
+  bool has_codecs() const override { return false; }
+  void set_protocol(const std::string& protocol) override {
+    RTC_DCHECK(IsSctpProtocol(protocol));
+    protocol_ = protocol;
+  }
 
   bool use_sctpmap() const { return use_sctpmap_; }
   void set_use_sctpmap(bool enable) { use_sctpmap_ = enable; }
+  int port() const { return port_; }
+  void set_port(int port) { port_ = port; }
+  int max_message_size() const { return max_message_size_; }
+  void set_max_message_size(int max_message_size) {
+    max_message_size_ = max_message_size;
+  }
 
  private:
-  bool use_sctpmap_ = true;
+  bool use_sctpmap_ = true;  // Note: "true" is no longer conformant.
+  // Defaults should be constants imported from SCTP. Quick hack.
+  int port_ = 5000;
+  int max_message_size_ = 256 * 1024;
+  std::unique_ptr<DataContentDescription> shim_;
 };
 
 // Protocol used for encoding media. This is the "top level" protocol that may
diff --git a/pc/session_description_unittest.cc b/pc/session_description_unittest.cc
index 3b05dca..9797ed5 100644
--- a/pc/session_description_unittest.cc
+++ b/pc/session_description_unittest.cc
@@ -9,6 +9,7 @@
  */
 #include "pc/session_description.h"
 
+#include "absl/memory/memory.h"
 #include "test/gtest.h"
 
 namespace cricket {
@@ -121,11 +122,69 @@
             video_desc->extmap_allow_mixed_enum());
 
   // Session level setting overrides media level when new content is added.
-  MediaContentDescription* data_desc = new DataContentDescription;
+  MediaContentDescription* data_desc = new RtpDataContentDescription;
   data_desc->set_extmap_allow_mixed_enum(MediaContentDescription::kMedia);
   session_desc.AddContent("data", MediaProtocolType::kRtp, data_desc);
   EXPECT_EQ(MediaContentDescription::kSession,
             data_desc->extmap_allow_mixed_enum());
 }
 
+TEST(SessionDescriptionTest, DataContentDescriptionCanAddStream) {
+  auto description = absl::make_unique<DataContentDescription>();
+  // Adding a stream without setting protocol first should work.
+  description->AddLegacyStream(1234);
+  EXPECT_EQ(1UL, description->streams().size());
+}
+
+TEST(SessionDescriptionTest, DataContentDescriptionCopyWorks) {
+  auto description = absl::make_unique<RtpDataContentDescription>();
+  auto shim_description = description->as_data();
+  auto shim_copy = shim_description->Copy();
+  delete shim_copy;
+}
+
+TEST(SessionDescriptionTest, DataContentDescriptionCodecsCallableOnNull) {
+  auto shim_description = absl::make_unique<DataContentDescription>();
+  auto codec_list = shim_description->codecs();
+  EXPECT_EQ(0UL, codec_list.size());
+}
+
+TEST(SessionDescriptionTest, DataContentDescriptionSctpConferenceMode) {
+  auto description = absl::make_unique<SctpDataContentDescription>();
+  auto shim_description = description->as_data();
+  EXPECT_FALSE(shim_description->conference_mode());
+  shim_description->set_conference_mode(true);
+  EXPECT_TRUE(shim_description->conference_mode());
+}
+
+TEST(SessionDescriptionTest, DataContentDesriptionInSessionIsUnwrapped) {
+  auto description = absl::make_unique<DataContentDescription>();
+  // Create a DTLS object behind the shim.
+  description->set_protocol(kMediaProtocolUdpDtlsSctp);
+  SessionDescription session;
+  session.AddContent("name", MediaProtocolType::kSctp, description.release());
+  ContentInfo* content = &(session.contents()[0]);
+  ASSERT_TRUE(content);
+  ASSERT_TRUE(content->media_description()->type() == MEDIA_TYPE_DATA);
+  ASSERT_TRUE(content->media_description()->as_sctp());
+}
+
+TEST(SessionDescriptionTest,
+     DataContentDescriptionInfoSurvivesInstantiationAsSctp) {
+  auto description = absl::make_unique<DataContentDescription>();
+  description->set_rtcp_mux(true);
+  description->set_protocol(kMediaProtocolUdpDtlsSctp);
+  EXPECT_TRUE(description->rtcp_mux());
+}
+
+TEST(SessionDescriptionTest,
+     DataContentDescriptionStreamInfoSurvivesInstantiationAsRtp) {
+  auto description = absl::make_unique<DataContentDescription>();
+  StreamParams stream;
+  description->AddLegacyStream(1234);
+  EXPECT_EQ(1UL, description->streams().size());
+  description->set_protocol(kMediaProtocolDtlsSavpf);
+  EXPECT_EQ(1UL, description->streams().size());
+}
+
 }  // namespace cricket
diff --git a/pc/webrtc_sdp.cc b/pc/webrtc_sdp.cc
index 984a1e1..d89bd78 100644
--- a/pc/webrtc_sdp.cc
+++ b/pc/webrtc_sdp.cc
@@ -54,29 +54,31 @@
 using cricket::ContentInfo;
 using cricket::CryptoParams;
 using cricket::DataContentDescription;
-using cricket::ICE_CANDIDATE_COMPONENT_RTP;
 using cricket::ICE_CANDIDATE_COMPONENT_RTCP;
+using cricket::ICE_CANDIDATE_COMPONENT_RTP;
+using cricket::kCodecParamAssociatedPayloadType;
+using cricket::kCodecParamMaxAverageBitrate;
 using cricket::kCodecParamMaxBitrate;
+using cricket::kCodecParamMaxPlaybackRate;
 using cricket::kCodecParamMaxPTime;
 using cricket::kCodecParamMaxQuantization;
 using cricket::kCodecParamMinBitrate;
 using cricket::kCodecParamMinPTime;
 using cricket::kCodecParamPTime;
+using cricket::kCodecParamSctpProtocol;
+using cricket::kCodecParamSctpStreams;
 using cricket::kCodecParamSPropStereo;
 using cricket::kCodecParamStartBitrate;
 using cricket::kCodecParamStereo;
-using cricket::kCodecParamUseInbandFec;
 using cricket::kCodecParamUseDtx;
-using cricket::kCodecParamSctpProtocol;
-using cricket::kCodecParamSctpStreams;
-using cricket::kCodecParamMaxAverageBitrate;
-using cricket::kCodecParamMaxPlaybackRate;
-using cricket::kCodecParamAssociatedPayloadType;
+using cricket::kCodecParamUseInbandFec;
 using cricket::MediaContentDescription;
-using cricket::MediaType;
-using cricket::RtpHeaderExtensions;
 using cricket::MediaProtocolType;
+using cricket::MediaType;
 using cricket::RidDescription;
+using cricket::RtpDataContentDescription;
+using cricket::RtpHeaderExtensions;
+using cricket::SctpDataContentDescription;
 using cricket::SimulcastDescription;
 using cricket::SimulcastLayer;
 using cricket::SimulcastLayerList;
@@ -1337,8 +1339,6 @@
   const MediaContentDescription* media_desc = content_info->media_description();
   RTC_DCHECK(media_desc);
 
-  int sctp_port = cricket::kSctpDefaultPort;
-
   // RFC 4566
   // m=<media> <port> <proto> <fmt>
   // fmt is a list of payload type numbers that MAY be used in the session.
@@ -1366,25 +1366,19 @@
       fmt.append(rtc::ToString(codec.id));
     }
   } else if (media_type == cricket::MEDIA_TYPE_DATA) {
-    const DataContentDescription* data_desc = media_desc->as_data();
     if (IsDtlsSctp(media_desc->protocol())) {
+      const cricket::SctpDataContentDescription* data_desc =
+          media_desc->as_sctp();
       fmt.append(" ");
 
       if (data_desc->use_sctpmap()) {
-        for (const cricket::DataCodec& codec : data_desc->codecs()) {
-          if (absl::EqualsIgnoreCase(codec.name,
-                                     cricket::kGoogleSctpDataCodecName) &&
-              codec.GetParam(cricket::kCodecParamPort, &sctp_port)) {
-            break;
-          }
-        }
-
-        fmt.append(rtc::ToString(sctp_port));
+        fmt.append(rtc::ToString(data_desc->port()));
       } else {
         fmt.append(kDefaultSctpmapProtocol);
       }
     } else {
-      for (const cricket::DataCodec& codec : data_desc->codecs()) {
+      const RtpDataContentDescription* data_desc = media_desc->as_rtp_data();
+      for (const cricket::RtpDataCodec& codec : data_desc->codecs()) {
         fmt.append(" ");
         fmt.append(rtc::ToString(codec.id));
       }
@@ -1523,9 +1517,10 @@
   AddLine(os.str(), message);
 
   if (IsDtlsSctp(media_desc->protocol())) {
-    const DataContentDescription* data_desc = media_desc->as_data();
+    const cricket::SctpDataContentDescription* data_desc =
+        media_desc->as_sctp();
     bool use_sctpmap = data_desc->use_sctpmap();
-    BuildSctpContentAttributes(message, sctp_port, use_sctpmap);
+    BuildSctpContentAttributes(message, data_desc->port(), use_sctpmap);
   } else if (IsRtp(media_desc->protocol())) {
     BuildRtpContentAttributes(media_desc, media_type, msid_signaling, message);
   }
@@ -1834,43 +1829,6 @@
   }
 }
 
-cricket::DataCodec FindOrMakeSctpDataCodec(DataContentDescription* media_desc) {
-  for (const auto& codec : media_desc->codecs()) {
-    if (absl::EqualsIgnoreCase(codec.name, cricket::kGoogleSctpDataCodecName)) {
-      return codec;
-    }
-  }
-  cricket::DataCodec codec_port(cricket::kGoogleSctpDataCodecPlType,
-                                cricket::kGoogleSctpDataCodecName);
-  return codec_port;
-}
-
-bool AddOrModifySctpDataCodecPort(DataContentDescription* media_desc,
-                                  int sctp_port) {
-  // Add the SCTP Port number as a pseudo-codec "port" parameter
-  auto codec = FindOrMakeSctpDataCodec(media_desc);
-  int dummy;
-  if (codec.GetParam(cricket::kCodecParamPort, &dummy)) {
-    return false;
-  }
-  codec.SetParam(cricket::kCodecParamPort, sctp_port);
-  media_desc->AddOrReplaceCodec(codec);
-  return true;
-}
-
-bool AddOrModifySctpDataMaxMessageSize(DataContentDescription* media_desc,
-                                       int max_message_size) {
-  // Add the SCTP Max Message Size as a pseudo-parameter to the codec
-  auto codec = FindOrMakeSctpDataCodec(media_desc);
-  int dummy;
-  if (codec.GetParam(cricket::kCodecParamMaxMessageSize, &dummy)) {
-    return false;
-  }
-  codec.SetParam(cricket::kCodecParamMaxMessageSize, max_message_size);
-  media_desc->AddOrReplaceCodec(codec);
-  return true;
-}
-
 bool GetMinValue(const std::vector<int>& values, int* value) {
   if (values.empty()) {
     return false;
@@ -1960,7 +1918,8 @@
       AddAttributeLine(kCodecParamPTime, ptime, message);
     }
   } else if (media_type == cricket::MEDIA_TYPE_DATA) {
-    for (const cricket::DataCodec& codec : media_desc->as_data()->codecs()) {
+    for (const cricket::RtpDataCodec& codec :
+         media_desc->as_rtp_data()->codecs()) {
       // RFC 4566
       // a=rtpmap:<payload type> <encoding name>/<clock rate>
       // [/<encodingparameters>]
@@ -2748,24 +2707,36 @@
           payload_types, pos, &content_name, &bundle_only,
           &section_msid_signaling, &transport, candidates, error);
     } else if (HasAttribute(line, kMediaTypeData)) {
-      std::unique_ptr<DataContentDescription> data_desc =
-          ParseContentDescription<DataContentDescription>(
-              message, cricket::MEDIA_TYPE_DATA, mline_index, protocol,
-              payload_types, pos, &content_name, &bundle_only,
-              &section_msid_signaling, &transport, candidates, error);
-
-      if (data_desc && IsDtlsSctp(protocol)) {
+      if (IsDtlsSctp(protocol)) {
+        // The draft-03 format is:
+        // m=application <port> DTLS/SCTP <sctp-port>...
+        // use_sctpmap should be false.
+        // The draft-26 format is:
+        // m=application <port> UDP/DTLS/SCTP webrtc-datachannel
+        // use_sctpmap should be false.
+        auto data_desc = absl::make_unique<SctpDataContentDescription>();
         int p;
         if (rtc::FromString(fields[3], &p)) {
-          if (!AddOrModifySctpDataCodecPort(data_desc.get(), p)) {
-            return false;
-          }
+          data_desc->set_port(p);
         } else if (fields[3] == kDefaultSctpmapProtocol) {
           data_desc->set_use_sctpmap(false);
         }
+        if (!ParseContent(message, cricket::MEDIA_TYPE_DATA, mline_index,
+                          protocol, payload_types, pos, &content_name,
+                          &bundle_only, &section_msid_signaling,
+                          data_desc.get(), &transport, candidates, error)) {
+          return false;
+        }
+        content = std::move(data_desc);
+      } else {
+        // RTP
+        std::unique_ptr<RtpDataContentDescription> data_desc =
+            ParseContentDescription<RtpDataContentDescription>(
+                message, cricket::MEDIA_TYPE_DATA, mline_index, protocol,
+                payload_types, pos, &content_name, &bundle_only,
+                &section_msid_signaling, &transport, candidates, error);
+        content = std::move(data_desc);
       }
-
-      content = std::move(data_desc);
     } else {
       RTC_LOG(LS_WARNING) << "Unsupported media type: " << line;
       continue;
@@ -3138,13 +3109,15 @@
             line, "sctp-port attribute found in non-data media description.",
             error);
       }
+      if (media_desc->as_sctp()->use_sctpmap()) {
+        return ParseFailed(
+            line, "sctp-port attribute can't be used with sctpmap.", error);
+      }
       int sctp_port;
       if (!ParseSctpPort(line, &sctp_port, error)) {
         return false;
       }
-      if (!AddOrModifySctpDataCodecPort(media_desc->as_data(), sctp_port)) {
-        return false;
-      }
+      media_desc->as_sctp()->set_port(sctp_port);
     } else if (IsDtlsSctp(protocol) &&
                HasAttribute(line, kAttributeMaxMessageSize)) {
       if (media_type != cricket::MEDIA_TYPE_DATA) {
@@ -3157,10 +3130,7 @@
       if (!ParseSctpMaxMessageSize(line, &max_message_size, error)) {
         return false;
       }
-      if (!AddOrModifySctpDataMaxMessageSize(media_desc->as_data(),
-                                             max_message_size)) {
-        return false;
-      }
+      media_desc->as_sctp()->set_max_message_size(max_message_size);
     } else if (IsRtp(protocol)) {
       //
       // RTP specific attrubtes
@@ -3621,8 +3591,8 @@
     UpdateCodec(payload_type, encoding_name, clock_rate, 0, channels,
                 audio_desc);
   } else if (media_type == cricket::MEDIA_TYPE_DATA) {
-    DataContentDescription* data_desc = media_desc->as_data();
-    data_desc->AddCodec(cricket::DataCodec(payload_type, encoding_name));
+    RtpDataContentDescription* data_desc = media_desc->as_rtp_data();
+    data_desc->AddCodec(cricket::RtpDataCodec(payload_type, encoding_name));
   }
   return true;
 }
diff --git a/pc/webrtc_sdp_unittest.cc b/pc/webrtc_sdp_unittest.cc
index 3de2b60..367fac8 100644
--- a/pc/webrtc_sdp_unittest.cc
+++ b/pc/webrtc_sdp_unittest.cc
@@ -56,7 +56,6 @@
 using cricket::ContentInfo;
 using cricket::CryptoParams;
 using cricket::DataCodec;
-using cricket::DataContentDescription;
 using cricket::ICE_CANDIDATE_COMPONENT_RTCP;
 using cricket::ICE_CANDIDATE_COMPONENT_RTP;
 using cricket::kFecSsrcGroupSemantics;
@@ -65,6 +64,8 @@
 using cricket::RELAY_PORT_TYPE;
 using cricket::RidDescription;
 using cricket::RidDirection;
+using cricket::RtpDataContentDescription;
+using cricket::SctpDataContentDescription;
 using cricket::SessionDescription;
 using cricket::SimulcastDescription;
 using cricket::SimulcastLayer;
@@ -275,6 +276,7 @@
     "a=ssrc:10 mslabel:data_channel\r\n"
     "a=ssrc:10 label:data_channeld0\r\n";
 
+// draft-ietf-mmusic-sctp-sdp-03
 static const char kSdpSctpDataChannelString[] =
     "m=application 9 DTLS/SCTP 5000\r\n"
     "c=IN IP4 0.0.0.0\r\n"
@@ -1443,10 +1445,17 @@
               simulcast2.receive_layers().size());
   }
 
-  void CompareDataContentDescription(const DataContentDescription* dcd1,
-                                     const DataContentDescription* dcd2) {
+  void CompareRtpDataContentDescription(const RtpDataContentDescription* dcd1,
+                                        const RtpDataContentDescription* dcd2) {
+    CompareMediaContentDescription<RtpDataContentDescription>(dcd1, dcd2);
+  }
+
+  void CompareSctpDataContentDescription(
+      const SctpDataContentDescription* dcd1,
+      const SctpDataContentDescription* dcd2) {
     EXPECT_EQ(dcd1->use_sctpmap(), dcd2->use_sctpmap());
-    CompareMediaContentDescription<DataContentDescription>(dcd1, dcd2);
+    EXPECT_EQ(dcd1->port(), dcd2->port());
+    EXPECT_EQ(dcd1->max_message_size(), dcd2->max_message_size());
   }
 
   void CompareSessionDescription(const SessionDescription& desc1,
@@ -1484,10 +1493,21 @@
       }
 
       ASSERT_EQ(IsDataContent(&c1), IsDataContent(&c2));
-      if (IsDataContent(&c1)) {
-        const DataContentDescription* dcd1 = c1.media_description()->as_data();
-        const DataContentDescription* dcd2 = c2.media_description()->as_data();
-        CompareDataContentDescription(dcd1, dcd2);
+      if (c1.media_description()->as_sctp()) {
+        ASSERT_TRUE(c2.media_description()->as_sctp());
+        const SctpDataContentDescription* scd1 =
+            c1.media_description()->as_sctp();
+        const SctpDataContentDescription* scd2 =
+            c2.media_description()->as_sctp();
+        CompareSctpDataContentDescription(scd1, scd2);
+      } else {
+        if (IsDataContent(&c1)) {
+          const RtpDataContentDescription* dcd1 =
+              c1.media_description()->as_rtp_data();
+          const RtpDataContentDescription* dcd2 =
+              c2.media_description()->as_rtp_data();
+          CompareRtpDataContentDescription(dcd1, dcd2);
+        }
       }
 
       CompareSimulcastDescription(
@@ -1760,14 +1780,12 @@
   }
 
   void AddSctpDataChannel(bool use_sctpmap) {
-    std::unique_ptr<DataContentDescription> data(new DataContentDescription());
-    data_desc_ = data.get();
-    data_desc_->set_use_sctpmap(use_sctpmap);
-    data_desc_->set_protocol(cricket::kMediaProtocolDtlsSctp);
-    DataCodec codec(cricket::kGoogleSctpDataCodecPlType,
-                    cricket::kGoogleSctpDataCodecName);
-    codec.SetParam(cricket::kCodecParamPort, kDefaultSctpPort);
-    data_desc_->AddCodec(codec);
+    std::unique_ptr<SctpDataContentDescription> data(
+        new SctpDataContentDescription());
+    sctp_desc_ = data.get();
+    sctp_desc_->set_use_sctpmap(use_sctpmap);
+    sctp_desc_->set_protocol(cricket::kMediaProtocolDtlsSctp);
+    sctp_desc_->set_port(kDefaultSctpPort);
     desc_.AddContent(kDataContentName, MediaProtocolType::kSctp,
                      data.release());
     desc_.AddTransportInfo(TransportInfo(
@@ -1775,7 +1793,8 @@
   }
 
   void AddRtpDataChannel() {
-    std::unique_ptr<DataContentDescription> data(new DataContentDescription());
+    std::unique_ptr<RtpDataContentDescription> data(
+        new RtpDataContentDescription());
     data_desc_ = data.get();
 
     data_desc_->AddCodec(DataCodec(101, "google-data"));
@@ -2043,7 +2062,8 @@
   SessionDescription desc_;
   AudioContentDescription* audio_desc_;
   VideoContentDescription* video_desc_;
-  DataContentDescription* data_desc_;
+  RtpDataContentDescription* data_desc_;
+  SctpDataContentDescription* sctp_desc_;
   Candidates candidates_;
   std::unique_ptr<IceCandidateInterface> jcandidate_;
   JsepSessionDescription jdesc_;
@@ -2215,21 +2235,26 @@
   EXPECT_EQ(message, expected_sdp);
 }
 
+void MutateJsepSctpPort(JsepSessionDescription* jdesc,
+                        const SessionDescription& desc,
+                        int port) {
+  // Take our pre-built session description and change the SCTP port.
+  cricket::SessionDescription* mutant = desc.Copy();
+  SctpDataContentDescription* dcdesc =
+      mutant->GetContentDescriptionByName(kDataContentName)->as_sctp();
+  dcdesc->set_port(port);
+  // Note: mutant's owned by jdesc now.
+  ASSERT_TRUE(jdesc->Initialize(mutant, kSessionId, kSessionVersion));
+}
+
 TEST_F(WebRtcSdpTest, SerializeWithSctpDataChannelAndNewPort) {
   bool use_sctpmap = true;
   AddSctpDataChannel(use_sctpmap);
   JsepSessionDescription jsep_desc(kDummyType);
   MakeDescriptionWithoutCandidates(&jsep_desc);
-  DataContentDescription* dcdesc =
-      jsep_desc.description()
-          ->GetContentDescriptionByName(kDataContentName)
-          ->as_data();
 
   const int kNewPort = 1234;
-  cricket::DataCodec codec(cricket::kGoogleSctpDataCodecPlType,
-                           cricket::kGoogleSctpDataCodecName);
-  codec.SetParam(cricket::kCodecParamPort, kNewPort);
-  dcdesc->AddOrReplaceCodec(codec);
+  MutateJsepSctpPort(&jsep_desc, desc_, kNewPort);
 
   std::string message = webrtc::SdpSerialize(jsep_desc);
 
@@ -2868,14 +2893,12 @@
 // Helper function to set the max-message-size parameter in the
 // SCTP data codec.
 void MutateJsepSctpMaxMessageSize(const SessionDescription& desc,
-                                  const std::string& new_value,
+                                  int new_value,
                                   JsepSessionDescription* jdesc) {
   cricket::SessionDescription* mutant = desc.Copy();
-  DataContentDescription* dcdesc =
-      mutant->GetContentDescriptionByName(kDataContentName)->as_data();
-  std::vector<cricket::DataCodec> codecs(dcdesc->codecs());
-  codecs[0].SetParam(cricket::kCodecParamMaxMessageSize, new_value);
-  dcdesc->set_codecs(codecs);
+  SctpDataContentDescription* dcdesc =
+      mutant->GetContentDescriptionByName(kDataContentName)->as_sctp();
+  dcdesc->set_max_message_size(new_value);
   jdesc->Initialize(mutant, kSessionId, kSessionVersion);
 }
 
@@ -2887,7 +2910,7 @@
 
   sdp_with_data.append(kSdpSctpDataChannelStringWithSctpColonPort);
   sdp_with_data.append("a=max-message-size:12345\r\n");
-  MutateJsepSctpMaxMessageSize(desc_, "12345", &jdesc);
+  MutateJsepSctpMaxMessageSize(desc_, 12345, &jdesc);
   JsepSessionDescription jdesc_output(kDummyType);
 
   // Verify with DTLS/SCTP.
@@ -2937,29 +2960,13 @@
   // No crash is a pass.
 }
 
-void MutateJsepSctpPort(JsepSessionDescription* jdesc,
-                        const SessionDescription& desc) {
-  // take our pre-built session description and change the SCTP port.
-  std::unique_ptr<cricket::SessionDescription> mutant = desc.Clone();
-  DataContentDescription* dcdesc =
-      mutant->GetContentDescriptionByName(kDataContentName)->as_data();
-  std::vector<cricket::DataCodec> codecs(dcdesc->codecs());
-  EXPECT_EQ(1U, codecs.size());
-  EXPECT_EQ(cricket::kGoogleSctpDataCodecPlType, codecs[0].id);
-  codecs[0].SetParam(cricket::kCodecParamPort, kUnusualSctpPort);
-  dcdesc->set_codecs(codecs);
-
-  ASSERT_TRUE(
-      jdesc->Initialize(std::move(mutant), kSessionId, kSessionVersion));
-}
-
 TEST_F(WebRtcSdpTest, DeserializeSdpWithSctpDataChannelAndUnusualPort) {
   bool use_sctpmap = true;
   AddSctpDataChannel(use_sctpmap);
 
   // First setup the expected JsepSessionDescription.
   JsepSessionDescription jdesc(kDummyType);
-  MutateJsepSctpPort(&jdesc, desc_);
+  MutateJsepSctpPort(&jdesc, desc_, kUnusualSctpPort);
 
   // Then get the deserialized JsepSessionDescription.
   std::string sdp_with_data = kSdpString;
@@ -2979,7 +2986,7 @@
   AddSctpDataChannel(use_sctpmap);
 
   JsepSessionDescription jdesc(kDummyType);
-  MutateJsepSctpPort(&jdesc, desc_);
+  MutateJsepSctpPort(&jdesc, desc_, kUnusualSctpPort);
 
   // We need to test the deserialized JsepSessionDescription from
   // kSdpSctpDataChannelStringWithSctpPort for
@@ -3015,7 +3022,7 @@
   bool use_sctpmap = true;
   AddSctpDataChannel(use_sctpmap);
   JsepSessionDescription jdesc(kDummyType);
-  DataContentDescription* dcd = GetFirstDataContentDescription(&desc_);
+  SctpDataContentDescription* dcd = GetFirstSctpDataContentDescription(&desc_);
   dcd->set_bandwidth(100 * 1000);
   ASSERT_TRUE(jdesc.Initialize(desc_.Clone(), kSessionId, kSessionVersion));