Bug 1606823 - Add support for WebRTC transport-cc extension. r=bwc,drno
authorJ. Ryan Stinnett <jryans@gmail.com>
Mon, 30 Mar 2020 22:32:43 +0000
changeset 521470 67c48f5aea11f2d5c0fc9eb1d405f1bad9f0b2f4
parent 521469 8ec2329bef743261097e558c6dfb07f6e2cd1d9b
child 521471 c5c8bfabe23927a193df1440f2d1459705c71e66
push id37271
push useropoprus@mozilla.com
push dateWed, 01 Apr 2020 09:51:34 +0000
treeherdermozilla-central@205d1b4e8f69 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbwc, drno
bugs1606823
milestone76.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1606823 - Add support for WebRTC transport-cc extension. r=bwc,drno This change includes support to negotiate the transport-wide-cc RTP extension needed to enable sender side bandwidth estimation in WebRTC. When this extension is supported in both sides during the Offer/Answer negotiation the transport_cc mode is enabled in the WebRTC engine so that this mode is used instead of the legacy receiver-side (REMB based) mechanism. The change is inspired on this fork by medooze team: https://github.com/medooze/gecko-dev/pull/2/files Co-authored-by: ggarber <gustavogb@gmail.com> Differential Revision: https://phabricator.services.mozilla.com/D68734
media/webrtc/signaling/gtest/jsep_session_unittest.cpp
media/webrtc/signaling/gtest/jsep_track_unittest.cpp
media/webrtc/signaling/gtest/sdp_unittests.cpp
media/webrtc/signaling/src/jsep/JsepCodecDescription.h
media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
media/webrtc/signaling/src/media-conduit/CodecConfig.h
media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
media/webrtc/signaling/src/peerconnection/TransceiverImpl.cpp
media/webrtc/signaling/src/sdp/SdpAttribute.h
media/webrtc/signaling/src/sdp/SipccSdpAttributeList.cpp
media/webrtc/signaling/src/sdp/sipcc/ccsdp_rtcp_fb.h
media/webrtc/signaling/src/sdp/sipcc/sdp.h
media/webrtc/signaling/src/sdp/sipcc/sdp_attr.c
media/webrtc/signaling/src/sdp/sipcc/sdp_attr_access.c
media/webrtc/signaling/src/sdp/sipcc/sdp_main.c
modules/libpref/init/all.js
--- a/media/webrtc/signaling/gtest/jsep_session_unittest.cpp
+++ b/media/webrtc/signaling/gtest/jsep_session_unittest.cpp
@@ -56,16 +56,18 @@ uint64_t FakeUuidGenerator::ctr = 1000;
 
 class JsepSessionTest : public JsepSessionTestBase,
                         public ::testing::WithParamInterface<std::string> {
  public:
   JsepSessionTest() : mSdpHelper(&mLastError) {
     Preferences::SetCString("media.peerconnection.sdp.parser", "legacy");
     Preferences::SetCString("media.peerconnection.sdp.alternate_parse_mode",
                             "never");
+    Preferences::SetBool("media.navigator.video.use_transport_cc", true);
+
     mSessionOff =
         MakeUnique<JsepSessionImpl>("Offerer", MakeUnique<FakeUuidGenerator>());
     mSessionAns = MakeUnique<JsepSessionImpl>("Answerer",
                                               MakeUnique<FakeUuidGenerator>());
 
     EXPECT_EQ(NS_OK, mSessionOff->Init());
     EXPECT_EQ(NS_OK, mSessionAns->Init());
 
@@ -4196,20 +4198,20 @@ TEST_F(JsepSessionTest, TestAnswererIndi
 }
 
 TEST_F(JsepSessionTest, TestExtmap) {
   AddTracks(*mSessionOff, "audio");
   AddTracks(*mSessionAns, "audio");
   // ssrc-audio-level will be extmap 1 for both
   // csrc-audio-level will be 2 for both
   // mid will be 3 for both
-  // video related extensions take 4 and 5
-  mSessionOff->AddAudioRtpExtension("foo");  // Default mapping of 7
-  mSessionOff->AddAudioRtpExtension("bar");  // Default mapping of 8
-  mSessionAns->AddAudioRtpExtension("bar");  // Default mapping of 7
+  // video related extensions take 4 - 7
+  mSessionOff->AddAudioRtpExtension("foo");  // Default mapping of 8
+  mSessionOff->AddAudioRtpExtension("bar");  // Default mapping of 9
+  mSessionAns->AddAudioRtpExtension("bar");  // Default mapping of 8
   std::string offer = CreateOffer();
   SetLocalOffer(offer, CHECK_SUCCESS);
   SetRemoteOffer(offer, CHECK_SUCCESS);
   std::string answer = CreateAnswer();
   SetLocalAnswer(answer, CHECK_SUCCESS);
   SetRemoteAnswer(answer, CHECK_SUCCESS);
 
   UniquePtr<Sdp> parsedOffer(Parse(offer));
@@ -4224,36 +4226,36 @@ TEST_F(JsepSessionTest, TestExtmap) {
   ASSERT_EQ(1U, offerExtmap[0].entry);
   ASSERT_EQ("urn:ietf:params:rtp-hdrext:csrc-audio-level",
             offerExtmap[1].extensionname);
   ASSERT_EQ(2U, offerExtmap[1].entry);
   ASSERT_EQ("urn:ietf:params:rtp-hdrext:sdes:mid",
             offerExtmap[2].extensionname);
   ASSERT_EQ(3U, offerExtmap[2].entry);
   ASSERT_EQ("foo", offerExtmap[3].extensionname);
-  ASSERT_EQ(7U, offerExtmap[3].entry);
+  ASSERT_EQ(8U, offerExtmap[3].entry);
   ASSERT_EQ("bar", offerExtmap[4].extensionname);
-  ASSERT_EQ(8U, offerExtmap[4].entry);
+  ASSERT_EQ(9U, offerExtmap[4].entry);
 
   UniquePtr<Sdp> parsedAnswer(Parse(answer));
   ASSERT_EQ(1U, parsedAnswer->GetMediaSectionCount());
 
   auto& answerMediaAttrs = parsedAnswer->GetMediaSection(0).GetAttributeList();
   ASSERT_TRUE(answerMediaAttrs.HasAttribute(SdpAttribute::kExtmapAttribute));
   auto& answerExtmap = answerMediaAttrs.GetExtmap().mExtmaps;
   ASSERT_EQ(3U, answerExtmap.size());
   ASSERT_EQ("urn:ietf:params:rtp-hdrext:ssrc-audio-level",
             answerExtmap[0].extensionname);
   ASSERT_EQ(1U, answerExtmap[0].entry);
   ASSERT_EQ("urn:ietf:params:rtp-hdrext:sdes:mid",
             answerExtmap[1].extensionname);
   ASSERT_EQ(3U, answerExtmap[1].entry);
   // We ensure that the entry for "bar" matches what was in the offer
   ASSERT_EQ("bar", answerExtmap[2].extensionname);
-  ASSERT_EQ(8U, answerExtmap[2].entry);
+  ASSERT_EQ(9U, answerExtmap[2].entry);
 }
 
 TEST_F(JsepSessionTest, TestExtmapDefaults) {
   types.push_back(SdpMediaSection::kAudio);
   types.push_back(SdpMediaSection::kVideo);
   AddTracks(*mSessionOff, "audio,video");
 
   std::string offer = CreateOffer();
@@ -4283,30 +4285,35 @@ TEST_F(JsepSessionTest, TestExtmapDefaul
   ASSERT_EQ("urn:ietf:params:rtp-hdrext:sdes:mid",
             offerAudioExtmap[2].extensionname);
 
   auto& offerVideoMediaAttrs =
       parsedOffer->GetMediaSection(1).GetAttributeList();
   ASSERT_TRUE(
       offerVideoMediaAttrs.HasAttribute(SdpAttribute::kExtmapAttribute));
   auto& offerVideoExtmap = offerVideoMediaAttrs.GetExtmap().mExtmaps;
-  ASSERT_EQ(4U, offerVideoExtmap.size());
+  ASSERT_EQ(5U, offerVideoExtmap.size());
 
   ASSERT_EQ(3U, offerVideoExtmap[0].entry);
   ASSERT_EQ("urn:ietf:params:rtp-hdrext:sdes:mid",
             offerVideoExtmap[0].extensionname);
   ASSERT_EQ("http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time",
             offerVideoExtmap[1].extensionname);
   ASSERT_EQ(4U, offerVideoExtmap[1].entry);
   ASSERT_EQ("urn:ietf:params:rtp-hdrext:toffset",
             offerVideoExtmap[2].extensionname);
   ASSERT_EQ(5U, offerVideoExtmap[2].entry);
   ASSERT_EQ("http://www.webrtc.org/experiments/rtp-hdrext/playout-delay",
             offerVideoExtmap[3].extensionname);
   ASSERT_EQ(6U, offerVideoExtmap[3].entry);
+  ASSERT_EQ(
+      "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-"
+      "extensions-01",
+      offerVideoExtmap[4].extensionname);
+  ASSERT_EQ(7U, offerVideoExtmap[4].entry);
 
   UniquePtr<Sdp> parsedAnswer(Parse(answer));
   ASSERT_EQ(2U, parsedAnswer->GetMediaSectionCount());
 
   auto& answerAudioMediaAttrs =
       parsedAnswer->GetMediaSection(0).GetAttributeList();
   ASSERT_TRUE(
       answerAudioMediaAttrs.HasAttribute(SdpAttribute::kExtmapAttribute));
@@ -4320,41 +4327,46 @@ TEST_F(JsepSessionTest, TestExtmapDefaul
             answerAudioExtmap[1].extensionname);
   ASSERT_EQ(3U, answerAudioExtmap[1].entry);
 
   auto& answerVideoMediaAttrs =
       parsedAnswer->GetMediaSection(1).GetAttributeList();
   ASSERT_TRUE(
       answerVideoMediaAttrs.HasAttribute(SdpAttribute::kExtmapAttribute));
   auto& answerVideoExtmap = answerVideoMediaAttrs.GetExtmap().mExtmaps;
-  ASSERT_EQ(3U, answerVideoExtmap.size());
+  ASSERT_EQ(4U, answerVideoExtmap.size());
 
   ASSERT_EQ(3U, answerVideoExtmap[0].entry);
   ASSERT_EQ("urn:ietf:params:rtp-hdrext:sdes:mid",
             answerVideoExtmap[0].extensionname);
   ASSERT_EQ("http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time",
             answerVideoExtmap[1].extensionname);
   ASSERT_EQ(4U, answerVideoExtmap[1].entry);
   ASSERT_EQ("urn:ietf:params:rtp-hdrext:toffset",
             answerVideoExtmap[2].extensionname);
   ASSERT_EQ(5U, answerVideoExtmap[2].entry);
+  ASSERT_EQ(
+      "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-"
+      "extensions-01",
+      answerVideoExtmap[3].extensionname);
+  ASSERT_EQ(7U, answerVideoExtmap[3].entry);
 }
 
 TEST_F(JsepSessionTest, TestExtmapWithDuplicates) {
   AddTracks(*mSessionOff, "audio");
   AddTracks(*mSessionAns, "audio");
   // ssrc-audio-level will be extmap 1 for both
   // csrc-audio-level will be 2 for both
   // mid will be 3 for both
-  // video related extensions take 4 and 5
-  mSessionOff->AddAudioRtpExtension("foo");  // Default mapping of 7
-  mSessionOff->AddAudioRtpExtension("bar");  // Default mapping of 8
+  // video related extensions take 4 - 7
+  mSessionOff->AddAudioRtpExtension("foo");  // Default mapping of 8
+  mSessionOff->AddAudioRtpExtension("bar");  // Default mapping of 9
   mSessionOff->AddAudioRtpExtension("bar");  // Should be ignored
   mSessionOff->AddAudioRtpExtension("bar");  // Should be ignored
-  mSessionOff->AddAudioRtpExtension("baz");  // Default mapping of 9
+  mSessionOff->AddAudioRtpExtension("baz");  // Default mapping of 10
   mSessionOff->AddAudioRtpExtension("bar");  // Should be ignored
 
   std::string offer = CreateOffer();
   UniquePtr<Sdp> parsedOffer(Parse(offer));
   ASSERT_EQ(1U, parsedOffer->GetMediaSectionCount());
 
   auto& offerMediaAttrs = parsedOffer->GetMediaSection(0).GetAttributeList();
   ASSERT_TRUE(offerMediaAttrs.HasAttribute(SdpAttribute::kExtmapAttribute));
@@ -4365,21 +4377,21 @@ TEST_F(JsepSessionTest, TestExtmapWithDu
   ASSERT_EQ(1U, offerExtmap[0].entry);
   ASSERT_EQ("urn:ietf:params:rtp-hdrext:csrc-audio-level",
             offerExtmap[1].extensionname);
   ASSERT_EQ(2U, offerExtmap[1].entry);
   ASSERT_EQ("urn:ietf:params:rtp-hdrext:sdes:mid",
             offerExtmap[2].extensionname);
   ASSERT_EQ(3U, offerExtmap[2].entry);
   ASSERT_EQ("foo", offerExtmap[3].extensionname);
-  ASSERT_EQ(7U, offerExtmap[3].entry);
+  ASSERT_EQ(8U, offerExtmap[3].entry);
   ASSERT_EQ("bar", offerExtmap[4].extensionname);
-  ASSERT_EQ(8U, offerExtmap[4].entry);
+  ASSERT_EQ(9U, offerExtmap[4].entry);
   ASSERT_EQ("baz", offerExtmap[5].extensionname);
-  ASSERT_EQ(9U, offerExtmap[5].entry);
+  ASSERT_EQ(10U, offerExtmap[5].entry);
 }
 
 TEST_F(JsepSessionTest, TestExtmapZeroId) {
   AddTracks(*mSessionOff, "video");
   AddTracks(*mSessionAns, "video");
 
   std::string sdp =
       "v=0\r\n"
--- a/media/webrtc/signaling/gtest/jsep_track_unittest.cpp
+++ b/media/webrtc/signaling/gtest/jsep_track_unittest.cpp
@@ -1005,16 +1005,102 @@ TEST_F(JsepTrackTest, VideoNegotiationOf
   ASSERT_TRUE((codec = GetVideoCodec(mSendAns, 2, 0)));
   ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
   CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kRemb);
   ASSERT_TRUE((codec = GetVideoCodec(mRecvOff, 2, 0)));
   ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
   CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kRemb);
 }
 
+TEST_F(JsepTrackTest, VideoNegotiationOfferTransportCC) {
+  InitCodecs();
+  // enable TransportCC on the offer codecs
+  ((JsepVideoCodecDescription&)*mOffCodecs[2]).EnableTransportCC();
+  InitTracks(SdpMediaSection::kVideo);
+  InitSdp(SdpMediaSection::kVideo);
+  OfferAnswer();
+
+  // make sure TransportCC is on offer and not on answer
+  ASSERT_NE(mOffer->ToString().find("a=rtcp-fb:120 transport-cc"),
+            std::string::npos);
+  ASSERT_EQ(mAnswer->ToString().find("a=rtcp-fb:120 transport-cc"),
+            std::string::npos);
+  CheckOffEncodingCount(1);
+  CheckAnsEncodingCount(1);
+
+  UniquePtr<JsepVideoCodecDescription> codec;
+  ASSERT_TRUE((codec = GetVideoCodec(mSendOff, 2, 0)));
+  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
+  ASSERT_TRUE((codec = GetVideoCodec(mRecvAns, 2, 0)));
+  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
+  ASSERT_TRUE((codec = GetVideoCodec(mSendAns, 2, 0)));
+  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
+  ASSERT_TRUE((codec = GetVideoCodec(mRecvOff, 2, 0)));
+  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
+}
+
+TEST_F(JsepTrackTest, VideoNegotiationAnswerTransportCC) {
+  InitCodecs();
+  // enable TransportCC on the answer codecs
+  ((JsepVideoCodecDescription&)*mAnsCodecs[2]).EnableTransportCC();
+  InitTracks(SdpMediaSection::kVideo);
+  InitSdp(SdpMediaSection::kVideo);
+  OfferAnswer();
+
+  // make sure TransportCC is not on offer and not on answer
+  ASSERT_EQ(mOffer->ToString().find("a=rtcp-fb:120 transport-cc"),
+            std::string::npos);
+  ASSERT_EQ(mAnswer->ToString().find("a=rtcp-fb:120 transport-cc"),
+            std::string::npos);
+  CheckOffEncodingCount(1);
+  CheckAnsEncodingCount(1);
+
+  UniquePtr<JsepVideoCodecDescription> codec;
+  ASSERT_TRUE((codec = GetVideoCodec(mSendOff, 2, 0)));
+  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
+  ASSERT_TRUE((codec = GetVideoCodec(mRecvAns, 2, 0)));
+  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
+  ASSERT_TRUE((codec = GetVideoCodec(mSendAns, 2, 0)));
+  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
+  ASSERT_TRUE((codec = GetVideoCodec(mRecvOff, 2, 0)));
+  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
+}
+
+TEST_F(JsepTrackTest, VideoNegotiationOfferAnswerTransportCC) {
+  InitCodecs();
+  // enable TransportCC on the offer and answer codecs
+  ((JsepVideoCodecDescription&)*mOffCodecs[2]).EnableTransportCC();
+  ((JsepVideoCodecDescription&)*mAnsCodecs[2]).EnableTransportCC();
+  InitTracks(SdpMediaSection::kVideo);
+  InitSdp(SdpMediaSection::kVideo);
+  OfferAnswer();
+
+  // make sure TransportCC is on offer and on answer
+  ASSERT_NE(mOffer->ToString().find("a=rtcp-fb:120 transport-cc"),
+            std::string::npos);
+  ASSERT_NE(mAnswer->ToString().find("a=rtcp-fb:120 transport-cc"),
+            std::string::npos);
+  CheckOffEncodingCount(1);
+  CheckAnsEncodingCount(1);
+
+  UniquePtr<JsepVideoCodecDescription> codec;
+  ASSERT_TRUE((codec = GetVideoCodec(mSendOff, 2, 0)));
+  ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
+  CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kTransportCC);
+  ASSERT_TRUE((codec = GetVideoCodec(mRecvAns, 2, 0)));
+  ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
+  CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kTransportCC);
+  ASSERT_TRUE((codec = GetVideoCodec(mSendAns, 2, 0)));
+  ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
+  CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kTransportCC);
+  ASSERT_TRUE((codec = GetVideoCodec(mRecvOff, 2, 0)));
+  ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
+  CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kTransportCC);
+}
+
 TEST_F(JsepTrackTest, AudioOffSendonlyAnsRecvonly) {
   Init(SdpMediaSection::kAudio);
   GetOffer().SetDirection(SdpDirectionAttribute::kSendonly);
   GetAnswer().SetDirection(SdpDirectionAttribute::kRecvonly);
   OfferAnswer();
   CheckOffEncodingCount(1);
   CheckAnsEncodingCount(0);
 }
--- a/media/webrtc/signaling/gtest/sdp_unittests.cpp
+++ b/media/webrtc/signaling/gtest/sdp_unittests.cpp
@@ -3115,18 +3115,17 @@ const std::string kBasicAudioVideoDataOf
     "a=rtcp-fb:120 ccm fir" CRLF "a=rtcp-fb:120 ccm tmmbr" CRLF
     "a=rtcp-fb:120 ccm tstr" CRLF "a=rtcp-fb:120 ccm vbcm" CRLF
     "a=rtcp-fb:120 ccm foo" CRLF  // Should be ignored
     "a=rtcp-fb:120 trr-int 10" CRLF "a=rtcp-fb:120 goog-remb" CRLF
     "a=rtcp-fb:120 foo" CRLF  // Should be ignored
     "a=rtcp-fb:126 nack" CRLF "a=rtcp-fb:126 nack pli" CRLF
     "a=rtcp-fb:126 ccm fir" CRLF "a=rtcp-fb:97 nack" CRLF
     "a=rtcp-fb:97 nack pli" CRLF "a=rtcp-fb:97 ccm fir" CRLF
-    "a=rtcp-fb:* ccm tmmbr" CRLF  // Bug 1597603 "a=rtcp-fb:120 transport-cc"
-                                  // CRLF
+    "a=rtcp-fb:* ccm tmmbr" CRLF "a=rtcp-fb:120 transport-cc" CRLF
     "a=setup:actpass" CRLF "a=rtcp-mux" CRLF
     "m=application 9 DTLS/SCTP 5000" CRLF "c=IN IP4 0.0.0.0" CRLF
     "a=sctpmap:5000 webrtc-datachannel 16" CRLF "a=setup:actpass" CRLF;
 
 TEST_P(NewSdpTest, BasicAudioVideoDataSdpParse) {
   ParseSdp(kBasicAudioVideoDataOffer, true);
   ASSERT_EQ(0U, ParseErrorCount())
       << "Got parse errors: " << SerializeParseErrors();
@@ -3200,26 +3199,26 @@ TEST_P(NewSdpTest, CheckRtcpFb) {
   ASSERT_EQ(3U, Sdp()->GetMediaSectionCount())
       << "Wrong number of media sections";
 
   auto& video_attrs = Sdp()->GetMediaSection(1).GetAttributeList();
   ASSERT_TRUE(video_attrs.HasAttribute(SdpAttribute::kRtcpFbAttribute));
   auto& rtcpfbs = video_attrs.GetRtcpFb().mFeedbacks;
 
   if (ResultsAreFromSipcc()) {
-    ASSERT_EQ(20U, rtcpfbs.size());
+    ASSERT_EQ(21U, rtcpfbs.size());
   } else {
     ASSERT_EQ("WEBRTCSDP", mResults->ParserName());
     std::stringstream os;
     Sdp()->Serialize(os);
-    // Reserialized resutst should be the same
+    // Reserialized results should be the same
     if (::testing::get<1>(GetParam())) {
       ASSERT_EQ(21U, rtcpfbs.size());
     } else {
-      ASSERT_EQ(20U, rtcpfbs.size());
+      ASSERT_EQ(21U, rtcpfbs.size());
     }
   }
 
   CheckRtcpFb(rtcpfbs[0], "120", SdpRtcpFbAttributeList::kAck, "rpsi");
   CheckRtcpFb(rtcpfbs[1], "120", SdpRtcpFbAttributeList::kAck, "app", "foo");
   CheckRtcpFb(rtcpfbs[2], "120", SdpRtcpFbAttributeList::kNack, "");
   CheckRtcpFb(rtcpfbs[3], "120", SdpRtcpFbAttributeList::kNack, "sli");
   CheckRtcpFb(rtcpfbs[4], "120", SdpRtcpFbAttributeList::kNack, "pli");
@@ -3233,22 +3232,17 @@ TEST_P(NewSdpTest, CheckRtcpFb) {
   CheckRtcpFb(rtcpfbs[12], "120", SdpRtcpFbAttributeList::kRemb, "");
   CheckRtcpFb(rtcpfbs[13], "126", SdpRtcpFbAttributeList::kNack, "");
   CheckRtcpFb(rtcpfbs[14], "126", SdpRtcpFbAttributeList::kNack, "pli");
   CheckRtcpFb(rtcpfbs[15], "126", SdpRtcpFbAttributeList::kCcm, "fir");
   CheckRtcpFb(rtcpfbs[16], "97", SdpRtcpFbAttributeList::kNack, "");
   CheckRtcpFb(rtcpfbs[17], "97", SdpRtcpFbAttributeList::kNack, "pli");
   CheckRtcpFb(rtcpfbs[18], "97", SdpRtcpFbAttributeList::kCcm, "fir");
   CheckRtcpFb(rtcpfbs[19], "*", SdpRtcpFbAttributeList::kCcm, "tmmbr");
-
-  // Bug 1597603 - this should be re-enabled to play nicely with
-  // re-serialization
-  // if (!ResultsAreFromSipcc()) {
-  //   CheckRtcpFb(rtcpfbs[20], "120", SdpRtcpFbAttributeList::kTransCC, "");
-  // }
+  CheckRtcpFb(rtcpfbs[20], "120", SdpRtcpFbAttributeList::kTransportCC, "");
 }
 
 TEST_P(NewSdpTest, CheckRtcp) {
   ParseSdp(kBasicAudioVideoOffer);
   ASSERT_TRUE(!!Sdp());
   ASSERT_EQ(3U, Sdp()->GetMediaSectionCount())
       << "Wrong number of media sections";
 
--- a/media/webrtc/signaling/src/jsep/JsepCodecDescription.h
+++ b/media/webrtc/signaling/src/jsep/JsepCodecDescription.h
@@ -197,16 +197,17 @@ class JsepVideoCodecDescription : public
   JsepVideoCodecDescription(const std::string& defaultPt,
                             const std::string& name, uint32_t clock,
                             bool enabled = true)
       : JsepCodecDescription(mozilla::SdpMediaSection::kVideo, defaultPt, name,
                              clock, 0, enabled),
         mTmmbrEnabled(false),
         mRembEnabled(false),
         mFECEnabled(false),
+        mTransportCCEnabled(false),
         mREDPayloadType(0),
         mULPFECPayloadType(0),
         mProfileLevelId(0),
         mPacketizationMode(0) {
     // Add supported rtcp-fb types
     mNackFbTypes.push_back("");
     mNackFbTypes.push_back(SdpRtcpFbAttributeList::pli);
     mCcmFbTypes.push_back(SdpRtcpFbAttributeList::fir);
@@ -246,16 +247,24 @@ class JsepVideoCodecDescription : public
       return;
     }
 
     mFECEnabled = true;
     mREDPayloadType = redPt;
     mULPFECPayloadType = ulpfecPt;
   }
 
+  virtual void EnableTransportCC() {
+    if (!mTransportCCEnabled) {
+      mTransportCCEnabled = true;
+      mOtherFbTypes.push_back(
+          {"", SdpRtcpFbAttributeList::kTransportCC, "", ""});
+    }
+  }
+
   void AddParametersToMSection(SdpMediaSection& msection) const override {
     AddFmtpsToMSection(msection);
     AddRtcpFbsToMSection(msection);
   }
 
   void AddFmtpsToMSection(SdpMediaSection& msection) const {
     if (mName == "H264") {
       SdpFmtpAttributeList::H264Parameters h264Params(
@@ -646,16 +655,25 @@ class JsepVideoCodecDescription : public
     for (const auto& fb : mOtherFbTypes) {
       if (fb.type == SdpRtcpFbAttributeList::kRemb) {
         return true;
       }
     }
     return false;
   }
 
+  virtual bool RtcpFbTransportCCIsSet() const {
+    for (const auto& fb : mOtherFbTypes) {
+      if (fb.type == SdpRtcpFbAttributeList::kTransportCC) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   virtual void UpdateRedundantEncodings(
       const std::vector<UniquePtr<JsepCodecDescription>>& codecs) {
     for (const auto& codec : codecs) {
       if (codec->mType == SdpMediaSection::kVideo && codec->mEnabled &&
           codec->mName != "red") {
         uint16_t pt;
         if (!SdpHelper::GetPtAsInt(codec->mDefaultPt, &pt)) {
           continue;
@@ -669,16 +687,17 @@ class JsepVideoCodecDescription : public
 
   std::vector<std::string> mAckFbTypes;
   std::vector<std::string> mNackFbTypes;
   std::vector<std::string> mCcmFbTypes;
   std::vector<SdpRtcpFbAttributeList::Feedback> mOtherFbTypes;
   bool mTmmbrEnabled;
   bool mRembEnabled;
   bool mFECEnabled;
+  bool mTransportCCEnabled;
   uint8_t mREDPayloadType;
   uint8_t mULPFECPayloadType;
   std::vector<uint8_t> mRedundantEncodings;
 
   // H264-specific stuff
   uint32_t mProfileLevelId;
   uint32_t mPacketizationMode;
   std::string mSpropParameterSets;
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
@@ -1994,16 +1994,20 @@ void JsepSessionImpl::SetupDefaultRtpExt
   AddAudioVideoRtpExtension(webrtc::RtpExtension::kMIdUri,
                             SdpDirectionAttribute::Direction::kSendrecv);
   AddVideoRtpExtension(webrtc::RtpExtension::kAbsSendTimeUri,
                        SdpDirectionAttribute::Direction::kSendrecv);
   AddVideoRtpExtension(webrtc::RtpExtension::kTimestampOffsetUri,
                        SdpDirectionAttribute::Direction::kSendrecv);
   AddVideoRtpExtension(webrtc::RtpExtension::kPlayoutDelayUri,
                        SdpDirectionAttribute::Direction::kRecvonly);
+  if (Preferences::GetBool("media.navigator.video.use_transport_cc", false)) {
+    AddVideoRtpExtension(webrtc::RtpExtension::kTransportSequenceNumberUri,
+                         SdpDirectionAttribute::Direction::kSendrecv);
+  }
 }
 
 void JsepSessionImpl::SetState(JsepSignalingState state) {
   if (state == mState) return;
 
   MOZ_MTLOG(ML_NOTICE, "[" << mName << "]: " << GetStateStr(mState) << " -> "
                            << GetStateStr(state));
   mState = state;
--- a/media/webrtc/signaling/src/media-conduit/CodecConfig.h
+++ b/media/webrtc/signaling/src/media-conduit/CodecConfig.h
@@ -70,16 +70,17 @@ class VideoCodecConfig {
 
   std::vector<std::string> mAckFbTypes;
   std::vector<std::string> mNackFbTypes;
   std::vector<std::string> mCcmFbTypes;
   // Don't pass mOtherFbTypes from JsepVideoCodecDescription because we'd have
   // to drag SdpRtcpFbAttributeList::Feedback along too.
   bool mRembFbSet;
   bool mFECFbSet;
+  bool mTransportCCFbSet;
 
   int mULPFECPayloadType;
   int mREDPayloadType;
   int mREDRTXPayloadType;
 
   uint32_t mTias;
   EncodingConstraints mEncodingConstraints;
   struct Encoding {
@@ -97,16 +98,17 @@ class VideoCodecConfig {
   uint8_t mPacketizationMode;
   // TODO: add external negotiated SPS/PPS
 
   bool operator==(const VideoCodecConfig& aRhs) const {
     if (mType != aRhs.mType || mName != aRhs.mName ||
         mAckFbTypes != aRhs.mAckFbTypes || mNackFbTypes != aRhs.mNackFbTypes ||
         mCcmFbTypes != aRhs.mCcmFbTypes || mRembFbSet != aRhs.mRembFbSet ||
         mFECFbSet != aRhs.mFECFbSet ||
+        mTransportCCFbSet != aRhs.mTransportCCFbSet ||
         mULPFECPayloadType != aRhs.mULPFECPayloadType ||
         mREDPayloadType != aRhs.mREDPayloadType ||
         mREDRTXPayloadType != aRhs.mREDRTXPayloadType || mTias != aRhs.mTias ||
         !(mEncodingConstraints == aRhs.mEncodingConstraints) ||
         !(mEncodings == aRhs.mEncodings) ||
         mSpropParameterSets != aRhs.mSpropParameterSets ||
         mProfile != aRhs.mProfile || mConstraints != aRhs.mConstraints ||
         mLevel != aRhs.mLevel ||
@@ -119,16 +121,17 @@ class VideoCodecConfig {
 
   VideoCodecConfig(int type, std::string name,
                    const EncodingConstraints& constraints,
                    const struct VideoCodecConfigH264* h264 = nullptr)
       : mType(type),
         mName(name),
         mRembFbSet(false),
         mFECFbSet(false),
+        mTransportCCFbSet(false),
         mULPFECPayloadType(123),
         mREDPayloadType(122),
         mREDRTXPayloadType(-1),
         mTias(0),
         mEncodingConstraints(constraints),
         mProfile(0x42),
         mConstraints(0xE0),
         mLevel(0x0C),
@@ -182,11 +185,13 @@ class VideoCodecConfig {
       }
     }
     return false;
   }
 
   bool RtcpFbRembIsSet() const { return mRembFbSet; }
 
   bool RtcpFbFECIsSet() const { return mFECFbSet; }
+
+  bool RtcpFbTransportCCIsSet() const { return mTransportCCFbSet; }
 };
 }  // namespace mozilla
 #endif
--- a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
+++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
@@ -1447,16 +1447,17 @@ MediaConduitErrorCode WebrtcVideoConduit
   }
 
   webrtc::KeyFrameRequestMethod kf_request_method = webrtc::kKeyFrameReqPliRtcp;
   bool kf_request_enabled = false;
   bool use_nack_basic = false;
   bool use_tmmbr = false;
   bool use_remb = false;
   bool use_fec = false;
+  bool use_transport_cc = false;
   int ulpfec_payload_type = kNullPayloadType;
   int red_payload_type = kNullPayloadType;
   bool configuredH264 = false;
   nsTArray<UniquePtr<VideoCodecConfig>> recv_codecs;
 
   // Try Applying the codecs in the list
   // we treat as success if at least one codec was applied and reception was
   // started successfully.
@@ -1503,46 +1504,49 @@ MediaConduitErrorCode WebrtcVideoConduit
     // What if codec A has Nack and REMB, and codec B has TMMBR, and codec C has
     // none? In practice, that's not a useful configuration, and
     // VideoReceiveStream::Config can't represent that, so simply union the
     // (boolean) settings
     use_nack_basic |= codec_config->RtcpFbNackIsSet("");
     use_tmmbr |= codec_config->RtcpFbCcmIsSet("tmmbr");
     use_remb |= codec_config->RtcpFbRembIsSet();
     use_fec |= codec_config->RtcpFbFECIsSet();
+    use_transport_cc |= codec_config->RtcpFbTransportCCIsSet();
 
     recv_codecs.AppendElement(new VideoCodecConfig(*codec_config));
   }
 
   if (!recv_codecs.Length()) {
     CSFLogError(LOGTAG, "%s Found no valid receive codecs", __FUNCTION__);
     return kMediaConduitMalformedArgument;
   }
 
   // Now decide if we need to recreate the receive stream, or can keep it
   if (!mRecvStream || CodecsDifferent(recv_codecs, mRecvCodecList) ||
       mRecvStreamConfig.rtp.nack.rtp_history_ms !=
           (use_nack_basic ? 1000 : 0) ||
       mRecvStreamConfig.rtp.remb != use_remb ||
+      mRecvStreamConfig.rtp.transport_cc != use_transport_cc ||
       mRecvStreamConfig.rtp.tmmbr != use_tmmbr ||
       mRecvStreamConfig.rtp.keyframe_method != kf_request_method ||
       (use_fec &&
        (mRecvStreamConfig.rtp.ulpfec_payload_type != ulpfec_payload_type ||
         mRecvStreamConfig.rtp.red_payload_type != red_payload_type))) {
     MutexAutoLock lock(mMutex);
 
     condError = StopReceivingLocked();
     if (condError != kMediaConduitNoError) {
       return condError;
     }
 
     // If we fail after here things get ugly
     mRecvStreamConfig.rtp.rtcp_mode = webrtc::RtcpMode::kCompound;
     mRecvStreamConfig.rtp.nack.rtp_history_ms = use_nack_basic ? 1000 : 0;
     mRecvStreamConfig.rtp.remb = use_remb;
+    mRecvStreamConfig.rtp.transport_cc = use_transport_cc;
     mRecvStreamConfig.rtp.tmmbr = use_tmmbr;
     mRecvStreamConfig.rtp.keyframe_method = kf_request_method;
 
     if (use_fec) {
       mRecvStreamConfig.rtp.ulpfec_payload_type = ulpfec_payload_type;
       mRecvStreamConfig.rtp.red_payload_type = red_payload_type;
     } else {
       // Reset to defaults
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -608,16 +608,17 @@ class ConfigureCodec {
         mVP9Preferred(false),
         mH264Level(13),   // minimum suggested for WebRTC spec
         mH264MaxBr(0),    // Unlimited
         mH264MaxMbps(0),  // Unlimited
         mVP8MaxFs(0),
         mVP8MaxFr(0),
         mUseTmmbr(false),
         mUseRemb(false),
+        mUseTransportCC(false),
         mUseAudioFec(false),
         mRedUlpfecEnabled(false),
         mDtmfEnabled(false) {
     mSoftwareH264Enabled = PeerConnectionCtx::GetInstance()->gmpHasH264();
 
     if (WebrtcVideoConduit::HasH264Hardware()) {
       branch->GetBoolPref("media.webrtc.hw.h264.enabled",
                           &mHardwareH264Enabled);
@@ -648,16 +649,19 @@ class ConfigureCodec {
     }
 
     // TMMBR is enabled from a pref in about:config
     branch->GetBoolPref("media.navigator.video.use_tmmbr", &mUseTmmbr);
 
     // REMB is enabled by default, but can be disabled from about:config
     branch->GetBoolPref("media.navigator.video.use_remb", &mUseRemb);
 
+    branch->GetBoolPref("media.navigator.video.use_transport_cc",
+                        &mUseTransportCC);
+
     branch->GetBoolPref("media.navigator.audio.use_fec", &mUseAudioFec);
 
     branch->GetBoolPref("media.navigator.video.red_ulpfec_enabled",
                         &mRedUlpfecEnabled);
 
     // media.peerconnection.dtmf.enabled controls both sdp generation for
     // DTMF support as well as DTMF exposure to DOM
     branch->GetBoolPref("media.peerconnection.dtmf.enabled", &mDtmfEnabled);
@@ -718,16 +722,19 @@ class ConfigureCodec {
         }
 
         if (mUseTmmbr) {
           videoCodec.EnableTmmbr();
         }
         if (mUseRemb) {
           videoCodec.EnableRemb();
         }
+        if (mUseTransportCC) {
+          videoCodec.EnableTransportCC();
+        }
       } break;
       case SdpMediaSection::kText:
       case SdpMediaSection::kApplication:
       case SdpMediaSection::kMessage: {
       }  // Nothing to configure for these.
     }
   }
 
@@ -739,16 +746,17 @@ class ConfigureCodec {
   bool mVP9Preferred;
   int32_t mH264Level;
   int32_t mH264MaxBr;
   int32_t mH264MaxMbps;
   int32_t mVP8MaxFs;
   int32_t mVP8MaxFr;
   bool mUseTmmbr;
   bool mUseRemb;
+  bool mUseTransportCC;
   bool mUseAudioFec;
   bool mRedUlpfecEnabled;
   bool mDtmfEnabled;
 };
 
 class ConfigureRedCodec {
  public:
   explicit ConfigureRedCodec(nsCOMPtr<nsIPrefBranch>& branch,
--- a/media/webrtc/signaling/src/peerconnection/TransceiverImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/TransceiverImpl.cpp
@@ -653,16 +653,17 @@ static nsresult JsepCodecDescToVideoCode
   aConfig->reset(new VideoCodecConfig(pt, desc.mName, desc.mConstraints,
                                       h264Config.get()));
 
   (*aConfig)->mAckFbTypes = desc.mAckFbTypes;
   (*aConfig)->mNackFbTypes = desc.mNackFbTypes;
   (*aConfig)->mCcmFbTypes = desc.mCcmFbTypes;
   (*aConfig)->mRembFbSet = desc.RtcpFbRembIsSet();
   (*aConfig)->mFECFbSet = desc.mFECEnabled;
+  (*aConfig)->mTransportCCFbSet = desc.RtcpFbTransportCCIsSet();
   if (desc.mFECEnabled) {
     (*aConfig)->mREDPayloadType = desc.mREDPayloadType;
     (*aConfig)->mULPFECPayloadType = desc.mULPFECPayloadType;
   }
 
   return NS_OK;
 }
 
--- a/media/webrtc/signaling/src/sdp/SdpAttribute.h
+++ b/media/webrtc/signaling/src/sdp/SdpAttribute.h
@@ -982,17 +982,17 @@ class SdpRtcpAttribute : public SdpAttri
 //                       / SP "app" [SP byte-string]
 //                       / SP token [SP byte-string]
 //                       / ; empty
 //
 class SdpRtcpFbAttributeList : public SdpAttribute {
  public:
   SdpRtcpFbAttributeList() : SdpAttribute(kRtcpFbAttribute) {}
 
-  enum Type { kAck, kApp, kCcm, kNack, kTrrInt, kRemb, kTransCC };
+  enum Type { kAck, kApp, kCcm, kNack, kTrrInt, kRemb, kTransportCC };
 
   static const char* pli;
   static const char* sli;
   static const char* rpsi;
   static const char* app;
 
   static const char* fir;
   static const char* tmmbr;
@@ -1034,17 +1034,17 @@ inline std::ostream& operator<<(std::ost
       os << "nack";
       break;
     case SdpRtcpFbAttributeList::kTrrInt:
       os << "trr-int";
       break;
     case SdpRtcpFbAttributeList::kRemb:
       os << "goog-remb";
       break;
-    case SdpRtcpFbAttributeList::kTransCC:
+    case SdpRtcpFbAttributeList::kTransportCC:
       os << "transport-cc";
       break;
     default:
       MOZ_ASSERT(false);
       os << "?";
   }
   return os;
 }
--- a/media/webrtc/signaling/src/sdp/SipccSdpAttributeList.cpp
+++ b/media/webrtc/signaling/src/sdp/SipccSdpAttributeList.cpp
@@ -905,16 +905,19 @@ void SipccSdpAttributeList::LoadRtcpFb(s
         type = SdpRtcpFbAttributeList::kTrrInt;
         std::ostringstream os;
         os << rtcpfb->param.trr_int;
         parameter = os.str();
       } break;
       case SDP_RTCP_FB_REMB: {
         type = SdpRtcpFbAttributeList::kRemb;
       } break;
+      case SDP_RTCP_FB_TRANSPORT_CC: {
+        type = SdpRtcpFbAttributeList::kTransportCC;
+      } break;
       default:
         // Type we don't care about, ignore.
         continue;
     }
 
     std::stringstream osPayloadType;
     if (rtcpfb->payload_num == UINT16_MAX) {
       osPayloadType << "*";
--- a/media/webrtc/signaling/src/sdp/sipcc/ccsdp_rtcp_fb.h
+++ b/media/webrtc/signaling/src/sdp/sipcc/ccsdp_rtcp_fb.h
@@ -10,16 +10,18 @@
 typedef enum {
     SDP_RTCP_FB_ANY = -1,
     SDP_RTCP_FB_ACK = 0,
     SDP_RTCP_FB_CCM,
     SDP_RTCP_FB_NACK,
     SDP_RTCP_FB_TRR_INT,
     // from https://www.ietf.org/archive/id/draft-alvestrand-rmcat-remb-03.txt
     SDP_RTCP_FB_REMB,
+    // from https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01
+    SDP_RTCP_FB_TRANSPORT_CC,
     SDP_MAX_RTCP_FB,
     SDP_RTCP_FB_UNKNOWN
 } sdp_rtcp_fb_type_e;
 
 typedef enum {
     SDP_RTCP_FB_NACK_NOT_FOUND = -1,
     SDP_RTCP_FB_NACK_BASIC = 0,
     SDP_RTCP_FB_NACK_SLI,
--- a/media/webrtc/signaling/src/sdp/sipcc/sdp.h
+++ b/media/webrtc/signaling/src/sdp/sipcc/sdp.h
@@ -1763,16 +1763,20 @@ sdp_attr_get_rtcp_fb_nack(sdp_t *sdp_p, 
 uint32_t
 sdp_attr_get_rtcp_fb_trr_int(sdp_t *sdp_p, uint16_t level, uint16_t payload_type,
                              uint16_t inst);
 
 tinybool
 sdp_attr_get_rtcp_fb_remb_enabled(sdp_t *sdp_p, uint16_t level,
                                   uint16_t payload_type);
 
+tinybool
+sdp_attr_get_rtcp_fb_transport_cc_enabled(sdp_t *sdp_p, uint16_t level,
+                                          uint16_t payload_type);
+
 sdp_rtcp_fb_ccm_type_e
 sdp_attr_get_rtcp_fb_ccm(sdp_t *sdp_p, uint16_t level, uint16_t payload_type, uint16_t inst);
 
 sdp_result_e
 sdp_attr_set_rtcp_fb_ack(sdp_t *sdp_p, uint16_t level, uint16_t payload_type, uint16_t inst,
                          sdp_rtcp_fb_ack_type_e type);
 
 sdp_result_e
@@ -1781,16 +1785,19 @@ sdp_attr_set_rtcp_fb_nack(sdp_t *sdp_p, 
 
 sdp_result_e
 sdp_attr_set_rtcp_fb_trr_int(sdp_t *sdp_p, uint16_t level, uint16_t payload_type,
                              uint16_t inst, uint32_t interval);
 
 sdp_result_e
 sdp_attr_set_rtcp_fb_remb(sdp_t *sdp_p, uint16_t level, uint16_t payload_type,
                           uint16_t inst);
+sdp_result_e
+sdp_attr_set_rtcp_fb_transport_cc(sdp_t *sdp_p, uint16_t level, uint16_t payload_type,
+                                  uint16_t inst);
 
 sdp_result_e
 sdp_attr_set_rtcp_fb_ccm(sdp_t *sdp_p, uint16_t level, uint16_t payload_type, uint16_t inst,
                          sdp_rtcp_fb_ccm_type_e);
 const char *
 sdp_attr_get_extmap_uri(sdp_t *sdp_p, uint16_t level, uint16_t inst);
 
 uint16_t
--- a/media/webrtc/signaling/src/sdp/sipcc/sdp_attr.c
+++ b/media/webrtc/signaling/src/sdp/sipcc/sdp_attr.c
@@ -4560,16 +4560,19 @@ sdp_result_e sdp_build_attr_rtcp_fb(sdp_
             }
             break;
         case SDP_RTCP_FB_TRR_INT:
             flex_string_sprintf(fs, " %u", attr_p->attr.rtcp_fb.param.trr_int);
             break;
         case SDP_RTCP_FB_REMB:
             /* No additional params after REMB */
             break;
+        case SDP_RTCP_FB_TRANSPORT_CC:
+            /* No additional params after Transport-CC */
+            break;
 
         case SDP_RTCP_FB_UNKNOWN:
             /* Contents are in the "extra" field */
             break;
 
         default:
             CSFLogError(logTag, "%s Error: Invalid rtcp-fb enum (%d)",
                         sdp_p->debug_str, attr_p->attr.rtcp_fb.feedback_type);
@@ -4707,16 +4710,20 @@ sdp_result_e sdp_parse_attr_rtcp_fb (sdp
                 return SDP_INVALID_PARAMETER;
             }
             break;
 
         case SDP_RTCP_FB_REMB:
             /* No additional tokens to parse after goog-remb */
             break;
 
+        case SDP_RTCP_FB_TRANSPORT_CC:
+            /* No additional tokens to parse after transport-cc */
+            break;
+
         case SDP_RTCP_FB_UNKNOWN:
             /* Handled by "extra", below */
             break;
 
         default:
             /* This is an internal error, not a parsing error */
             CSFLogError(logTag, "%s Error: Invalid rtcp-fb enum (%d)",
                         sdp_p->debug_str, attr_p->attr.rtcp_fb.feedback_type);
--- a/media/webrtc/signaling/src/sdp/sipcc/sdp_attr_access.c
+++ b/media/webrtc/signaling/src/sdp/sipcc/sdp_attr_access.c
@@ -6060,16 +6060,36 @@ sdp_attr_get_rtcp_fb_remb_enabled(sdp_t 
     sdp_attr_t  *attr_p;
 
     attr_p = sdp_find_rtcp_fb_attr(sdp_p, level, payload_type,
                                    SDP_RTCP_FB_REMB,
                                    1); // always check for 1st instance
     return (attr_p? TRUE : FALSE); // either exists or not
 }
 
+/* Function:    sdp_attr_get_rtcp_fb_transport_cc_enabled
+ * Description: Returns true if rtcp-fb:...transport-cc attribute exists
+ * Parameters:  sdp_p      The SDP handle returned by sdp_init_description.
+ *              level        The level to check for the attribute.
+ *              payload_type The payload to get the attribute for
+ * Returns:     true if rtcp-fb:...transport-cc exists
+ */
+tinybool
+sdp_attr_get_rtcp_fb_transport_cc_enabled(sdp_t *sdp_p,
+                                          uint16_t level,
+                                          uint16_t payload_type)
+{
+    sdp_attr_t  *attr_p;
+
+    attr_p = sdp_find_rtcp_fb_attr(sdp_p, level, payload_type,
+                                   SDP_RTCP_FB_TRANSPORT_CC,
+                                   1); // always check for 1st instance
+    return (attr_p? TRUE : FALSE); // either exists or not
+}
+
 /* Function:    sdp_attr_get_rtcp_fb_ccm
  * Description: Returns the value of the rtcp-fb:...ccm attribute
  * Parameters:  sdp_p      The SDP handle returned by sdp_init_description.
  *              level        The level to check for the attribute.
  *              payload_type The payload to get the attribute for
  *              inst_num    The attribute instance number to check.
  * Returns:     CCM type (SDP_RTCP_FB_CCM_NOT_FOUND if not present)
  */
@@ -6225,16 +6245,48 @@ sdp_attr_set_rtcp_fb_remb(sdp_t *sdp_p, 
         return (SDP_INVALID_PARAMETER);
     }
 
     attr_p->attr.rtcp_fb.payload_num = payload_type;
     attr_p->attr.rtcp_fb.feedback_type = SDP_RTCP_FB_REMB;
     return (SDP_SUCCESS);
 }
 
+/* Function:    sdp_attr_set_rtcp_fb_transport_cc
+ * Description: Sets the value of an rtcp-fb:...transport-cc attribute
+ * Parameters:  sdp_p        The SDP handle returned by sdp_init_description.
+ *              level          The level to set the attribute.
+ *              payload_type   The value to set the payload type to for
+ *                             this attribute. Can be SDP_ALL_PAYLOADS.
+ *              inst_num       The attribute instance number to check.
+ * Returns:     SDP_SUCCESS            Attribute param was set successfully.
+ *              SDP_INVALID_PARAMETER  Specified attribute is not defined.
+ */
+sdp_result_e
+sdp_attr_set_rtcp_fb_transport_cc(sdp_t *sdp_p, uint16_t level, uint16_t payload_type,
+                                  uint16_t inst)
+{
+    sdp_attr_t  *attr_p;
+
+    attr_p = sdp_find_attr(sdp_p, level, 0, SDP_ATTR_RTCP_FB, inst);
+    if (!attr_p) {
+        if (sdp_p->debug_flag[SDP_DEBUG_ERRORS]) {
+            CSFLogError(logTag, "%s rtcp_fb transport-cc attribute, level %u "
+                      "instance %u not found.", sdp_p->debug_str, (unsigned)level,
+                      (unsigned)inst);
+        }
+        sdp_p->conf_p->num_invalid_param++;
+        return (SDP_INVALID_PARAMETER);
+    }
+
+    attr_p->attr.rtcp_fb.payload_num = payload_type;
+    attr_p->attr.rtcp_fb.feedback_type = SDP_RTCP_FB_TRANSPORT_CC;
+    return (SDP_SUCCESS);
+}
+
 /* Function:    sdp_attr_set_rtcp_fb_ccm
  * Description: Sets the value of an rtcp-fb:...ccm attribute
  * Parameters:  sdp_p        The SDP handle returned by sdp_init_description.
  *              level          The level to set the attribute.
  *              payload_type   The value to set the payload type to for
  *                             this attribute. Can be SDP_ALL_PAYLOADS.
  *              inst_num       The attribute instance number to check.
  *              type           The ccm type to indicate
--- a/media/webrtc/signaling/src/sdp/sipcc/sdp_main.c
+++ b/media/webrtc/signaling/src/sdp/sipcc/sdp_main.c
@@ -489,17 +489,18 @@ const sdp_namearray_t sdp_rtcp_unicast_m
 #define SDP_NAME(x) {x, sizeof(x)}
 /* Maintain the same order as defined in typdef sdp_rtcp_fb_type_e */
 const sdp_namearray_t sdp_rtcp_fb_type_val[SDP_MAX_RTCP_FB] =
 {
     SDP_NAME("ack"),
     SDP_NAME("ccm"),
     SDP_NAME("nack"),
     SDP_NAME("trr-int"),
-    SDP_NAME("goog-remb")
+    SDP_NAME("goog-remb"),
+    SDP_NAME("transport-cc")
 };
 
 /* Maintain the same order as defined in typdef sdp_rtcp_fb_nack_type_e */
 const sdp_namearray_t sdp_rtcp_fb_nack_type_val[SDP_MAX_RTCP_FB_NACK] =
 {
     SDP_NAME(""),
     SDP_NAME("sli"),
     SDP_NAME("pli"),
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -423,16 +423,17 @@ pref("media.videocontrols.picture-in-pic
 pref("media.videocontrols.picture-in-picture.video-toggle.flyout-enabled", false);
 pref("media.videocontrols.picture-in-picture.video-toggle.flyout-wait-ms", 5000);
 pref("media.videocontrols.picture-in-picture.video-toggle.always-show", false);
 
 #ifdef MOZ_WEBRTC
   pref("media.navigator.video.enabled", true);
   pref("media.navigator.video.default_fps",30);
   pref("media.navigator.video.use_remb", true);
+  pref("media.navigator.video.use_transport_cc", false);
   pref("media.navigator.video.use_tmmbr", false);
   pref("media.navigator.audio.use_fec", true);
   pref("media.navigator.video.red_ulpfec_enabled", false);
 
   #ifdef NIGHTLY_BUILD
     pref("media.peerconnection.sdp.parser", "sipcc");
     pref("media.peerconnection.sdp.alternate_parse_mode", "parallel");
     pref("media.peerconnection.sdp.strict_success", false);