Bug 1275360 - Add sdp handling for video FEC (red/ulpfec). r=drno
authorMichael Froman <mfroman@mozilla.com>
Thu, 30 Jun 2016 00:40:40 -0500
changeset 346946 e7e10e7ea1b22d3dba378640b2ebe263be6550bf
parent 346945 5172702dcd32ea48a5046b059332925017593c0c
child 346947 c3c3bc22ff728c3988d137de63c5c048ded4a3eb
push id6389
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:38:22 +0000
treeherdermozilla-beta@01d67bfe6c81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdrno
bugs1275360
milestone50.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 1275360 - Add sdp handling for video FEC (red/ulpfec). r=drno MozReview-Commit-ID: BIt7SkSEquj
media/webrtc/signaling/src/jsep/JsepCodecDescription.h
media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
media/webrtc/signaling/src/jsep/JsepTrack.cpp
media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
media/webrtc/signaling/src/sdp/SdpAttribute.cpp
media/webrtc/signaling/src/sdp/SdpAttribute.h
media/webrtc/signaling/src/sdp/SipccSdpAttributeList.cpp
media/webrtc/signaling/src/sdp/sipcc/ccsdp.h
media/webrtc/signaling/src/sdp/sipcc/sdp.h
media/webrtc/signaling/src/sdp/sipcc/sdp_access.c
media/webrtc/signaling/src/sdp/sipcc/sdp_attr.c
media/webrtc/signaling/test/jsep_session_unittest.cpp
media/webrtc/signaling/test/jsep_track_unittest.cpp
media/webrtc/signaling/test/sdp_unittests.cpp
modules/libpref/init/all.js
--- a/media/webrtc/signaling/src/jsep/JsepCodecDescription.h
+++ b/media/webrtc/signaling/src/jsep/JsepCodecDescription.h
@@ -45,16 +45,17 @@ class JsepCodecDescription {
   GetPtAsInt(uint16_t* ptOutparam) const
   {
     return SdpHelper::GetPtAsInt(mDefaultPt, ptOutparam);
   }
 
   virtual bool
   Matches(const std::string& fmt, const SdpMediaSection& remoteMsection) const
   {
+    // note: fmt here is remote fmt (to go with remoteMsection)
     if (mType != remoteMsection.GetMediaType()) {
       return false;
     }
 
     const SdpRtpmapAttributeList::Rtpmap* entry(remoteMsection.FindRtpmap(fmt));
 
     if (entry) {
       if (!nsCRT::strcasecmp(mName.c_str(), entry->name.c_str())
@@ -209,16 +210,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),
         mPacketizationMode(0)
   {
     // Add supported rtcp-fb types
     mNackFbTypes.push_back("");
     mNackFbTypes.push_back(SdpRtcpFbAttributeList::pli);
     mCcmFbTypes.push_back(SdpRtcpFbAttributeList::fir);
   }
 
@@ -237,16 +239,26 @@ class JsepVideoCodecDescription : public
     // EnableRemb can be called multiple times due to multiple calls to
     // PeerConnectionImpl::ConfigureJsepSessionCodecs
     if (!mRembEnabled) {
       mRembEnabled = true;
       mOtherFbTypes.push_back({ "", SdpRtcpFbAttributeList::kRemb, "", ""});
     }
   }
 
+  virtual void
+  EnableFec() {
+    // Enabling FEC for video works a little differently than enabling
+    // REMB or TMMBR.  Support for FEC is indicated by the presence of
+    // particular codes (red and ulpfec) instead of using rtcpfb
+    // attributes on a given codec.  There is no rtcpfb to push for FEC
+    // as can be seen above when REMB or TMMBR are enabled.
+    mFECEnabled = true;
+  }
+
   void
   AddParametersToMSection(SdpMediaSection& msection) const override
   {
     AddFmtpsToMSection(msection);
     AddRtcpFbsToMSection(msection);
   }
 
   void
@@ -277,16 +289,21 @@ class JsepVideoCodecDescription : public
       }
 
       // Parameters that apply to both the send and recv directions
       h264Params.packetization_mode = mPacketizationMode;
       // Hard-coded, may need to change someday?
       h264Params.level_asymmetry_allowed = true;
 
       msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, h264Params));
+    } else if (mName == "red") {
+      SdpFmtpAttributeList::RedParameters redParams(
+          GetRedParameters(mDefaultPt, msection));
+      redParams.encodings = mRedundantEncodings;
+      msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, redParams));
     } else if (mName == "VP8" || mName == "VP9") {
       if (mDirection == sdp::kRecv) {
         // VP8 and VP9 share the same SDP parameters thus far
         SdpFmtpAttributeList::VP8Parameters vp8Params(
             GetVP8Parameters(mDefaultPt, msection));
 
         vp8Params.max_fs = mConstraints.maxFs;
         vp8Params.max_fr = mConstraints.maxFps;
@@ -333,16 +350,31 @@ class JsepVideoCodecDescription : public
     if (params && params->codec_type == SdpRtpmapAttributeList::kH264) {
       result =
         static_cast<const SdpFmtpAttributeList::H264Parameters&>(*params);
     }
 
     return result;
   }
 
+  SdpFmtpAttributeList::RedParameters
+  GetRedParameters(const std::string& pt,
+                   const SdpMediaSection& msection) const
+  {
+    SdpFmtpAttributeList::RedParameters result;
+    auto* params = msection.FindFmtp(pt);
+
+    if (params && params->codec_type == SdpRtpmapAttributeList::kRed) {
+      result =
+        static_cast<const SdpFmtpAttributeList::RedParameters&>(*params);
+    }
+
+    return result;
+  }
+
   SdpFmtpAttributeList::VP8Parameters
   GetVP8Parameters(const std::string& pt,
                    const SdpMediaSection& msection) const
   {
     SdpRtpmapAttributeList::CodecType expectedType(
         mName == "VP8" ?
         SdpRtpmapAttributeList::kVP8 :
         SdpRtpmapAttributeList::kVP9);
@@ -422,17 +454,20 @@ class JsepVideoCodecDescription : public
         // Only do this if we didn't symmetrically negotiate above
         if (h264Params.level_asymmetry_allowed) {
           SetSaneH264Level(GetSaneH264Level(h264Params.profile_level_id),
                            &mProfileLevelId);
         }
       } else {
         // TODO(bug 1143709): max-recv-level support
       }
-
+    } else if (mName == "red") {
+      SdpFmtpAttributeList::RedParameters redParams(
+          GetRedParameters(mDefaultPt, remoteMsection));
+      mRedundantEncodings = redParams.encodings;
     } else if (mName == "VP8" || mName == "VP9") {
       if (mDirection == sdp::kSend) {
         SdpFmtpAttributeList::VP8Parameters vp8Params(
             GetVP8Parameters(mDefaultPt, remoteMsection));
 
         mConstraints.maxFs = vp8Params.max_fs;
         mConstraints.maxFps = vp8Params.max_fr;
       }
@@ -644,24 +679,44 @@ class JsepVideoCodecDescription : public
     for (const auto& fb : mOtherFbTypes) {
       if (fb.type == SdpRtcpFbAttributeList::kRemb) {
         return true;
       }
     }
     return false;
   }
 
+  virtual void
+  UpdateRedundantEncodings(std::vector<JsepCodecDescription*> codecs)
+  {
+    for (const auto codec : codecs) {
+      if (codec->mType == SdpMediaSection::kVideo &&
+          codec->mEnabled &&
+          codec->mName != "red") {
+        uint8_t pt = (uint8_t)strtoul(codec->mDefaultPt.c_str(), nullptr, 10);
+        // returns 0 if failed to convert, and since zero could
+        // be valid, check the defaultPt for 0
+        if (pt == 0 && codec->mDefaultPt != "0") {
+          continue;
+        }
+        mRedundantEncodings.push_back(pt);
+      }
+    }
+  }
+
   JSEP_CODEC_CLONE(JsepVideoCodecDescription)
 
   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;
+  std::vector<uint8_t> mRedundantEncodings;
 
   // H264-specific stuff
   uint32_t mProfileLevelId;
   uint32_t mPacketizationMode;
   std::string mSpropParameterSets;
 };
 
 class JsepApplicationCodecDescription : public JsepCodecDescription {
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
@@ -2177,21 +2177,39 @@ JsepSessionImpl::SetupDefaultCodecs()
       "H264",
       90000
       );
   h264_0->mPacketizationMode = 0;
   // Defaults for mandatory params
   h264_0->mProfileLevelId = 0x42E00D;
   mSupportedCodecs.values.push_back(h264_0);
 
+  JsepVideoCodecDescription* red = new JsepVideoCodecDescription(
+      "122", // payload type
+      "red", // codec name
+      90000  // clock rate (match other video codecs)
+      );
+  mSupportedCodecs.values.push_back(red);
+
+  JsepVideoCodecDescription* ulpfec = new JsepVideoCodecDescription(
+      "123",    // payload type
+      "ulpfec", // codec name
+      90000     // clock rate (match other video codecs)
+      );
+  mSupportedCodecs.values.push_back(ulpfec);
+
   mSupportedCodecs.values.push_back(new JsepApplicationCodecDescription(
       "5000",
       "webrtc-datachannel",
       WEBRTC_DATACHANNEL_STREAMS_DEFAULT
       ));
+
+  // Update the redundant encodings for the RED codec with the supported
+  // codecs.  Note: only uses the video codecs.
+  red->UpdateRedundantEncodings(mSupportedCodecs.values);
 }
 
 void
 JsepSessionImpl::SetupDefaultRtpExtensions()
 {
   AddAudioRtpExtension("urn:ietf:params:rtp-hdrext:ssrc-audio-level");
 }
 
--- a/media/webrtc/signaling/src/jsep/JsepTrack.cpp
+++ b/media/webrtc/signaling/src/jsep/JsepTrack.cpp
@@ -339,23 +339,68 @@ JsepTrack::NegotiateCodecs(
         if (formatChanges) {
           (*formatChanges)[originalFormat] = codec->mDefaultPt;
         }
         break;
       }
     }
   }
 
+  // Find the (potential) red codec and ulpfec codec
+  JsepVideoCodecDescription* red = nullptr;
+  JsepVideoCodecDescription* ulpfec = nullptr;
+  for (auto codec : *codecs) {
+    if (codec->mName == "red") {
+      red = static_cast<JsepVideoCodecDescription*>(codec);
+      break;
+    }
+    if (codec->mName == "ulpfec") {
+      ulpfec = static_cast<JsepVideoCodecDescription*>(codec);
+      break;
+    }
+  }
+  // if we have a red codec remove redundant encodings that don't exist
+  if (red) {
+    // Since we could have an externally specified redundant endcodings
+    // list, we shouldn't simply rebuild the redundant encodings list
+    // based on the current list of codecs.
+    std::vector<uint8_t> unnegotiatedEncodings;
+    std::swap(unnegotiatedEncodings, red->mRedundantEncodings);
+    for (auto redundantPt : unnegotiatedEncodings) {
+      std::string pt = std::to_string(redundantPt);
+      for (auto codec : *codecs) {
+        if (pt == codec->mDefaultPt) {
+          red->mRedundantEncodings.push_back(redundantPt);
+          break;
+        }
+      }
+    }
+  }
+  // Video FEC is indicated by the existence of the red and ulpfec
+  // codecs and not an attribute on the particular video codec (like in
+  // a rtcpfb attr). If we see both red and ulpfec codecs, we enable FEC
+  // on all the other codecs.
+  if (red && ulpfec) {
+    for (auto codec : *codecs) {
+      if (codec->mName != "red" && codec->mName != "ulpfec") {
+        JsepVideoCodecDescription* videoCodec =
+            static_cast<JsepVideoCodecDescription*>(codec);
+        videoCodec->EnableFec();
+      }
+    }
+  }
+
   // Make sure strongly preferred codecs are up front, overriding the remote
   // side's preference.
   std::stable_sort(codecs->begin(), codecs->end(), CompareCodec);
 
   // TODO(bug 814227): Remove this once we're ready to put multiple codecs in an
-  // answer
-  if (!codecs->empty()) {
+  // answer.  For now, remove all but the first codec unless the red codec
+  // exists, and then we include the others per RFC 5109, section 14.2.
+  if (!codecs->empty() && !red) {
     for (size_t i = 1; i < codecs->size(); ++i) {
       delete (*codecs)[i];
       (*codecs)[i] = nullptr;
     }
     codecs->resize(1);
   }
 }
 
--- a/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
+++ b/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
@@ -144,16 +144,17 @@ JsepCodecDescToCodecConfig(const JsepCod
   VideoCodecConfig* configRaw;
   configRaw = new VideoCodecConfig(
       pt, desc.mName, desc.mConstraints, h264Config.get());
 
   configRaw->mAckFbTypes = desc.mAckFbTypes;
   configRaw->mNackFbTypes = desc.mNackFbTypes;
   configRaw->mCcmFbTypes = desc.mCcmFbTypes;
   configRaw->mRembFbSet = desc.RtcpFbRembIsSet();
+  configRaw->mFECFbSet = desc.mFECEnabled;
 
   *aConfig = configRaw;
   return NS_OK;
 }
 
 static nsresult
 NegotiatedDetailsToVideoCodecConfigs(const JsepTrackNegotiatedDetails& aDetails,
                                      PtrVector<VideoCodecConfig>* aConfigs)
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -881,17 +881,18 @@ class ConfigureCodec {
       mVP9Enabled(false),
       mH264Level(13), // minimum suggested for WebRTC spec
       mH264MaxBr(0), // Unlimited
       mH264MaxMbps(0), // Unlimited
       mVP8MaxFs(0),
       mVP8MaxFr(0),
       mUseTmmbr(false),
       mUseRemb(false),
-      mUseAudioFec(false)
+      mUseAudioFec(false),
+      mRedUlpfecEnabled(false)
     {
 #ifdef MOZ_WEBRTC_OMX
       // Check to see if what HW codecs are available (not in use) at this moment.
       // Note that streaming video decode can reserve a decoder
 
       // XXX See bug 1018791 Implement W3 codec reservation policy
       // Note that currently, OMXCodecReservation needs to be held by an sp<> because it puts
       // 'this' into an sp<EventListener> to talk to the resource reservation code
@@ -952,16 +953,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.audio.use_fec", &mUseAudioFec);
+
+      branch->GetBoolPref("media.navigator.video.red_ulpfec_enabled",
+                          &mRedUlpfecEnabled);
     }
 
     void operator()(JsepCodecDescription* codec) const
     {
       switch (codec->mType) {
         case SdpMediaSection::kAudio:
           {
             JsepAudioCodecDescription& audioCodec =
@@ -992,16 +996,20 @@ class ConfigureCodec {
                 // We're assuming packetization mode 0 is unsupported by
                 // hardware.
                 videoCodec.mEnabled = false;
               }
 
               if (mHardwareH264Supported) {
                 videoCodec.mStronglyPreferred = true;
               }
+            } else if (videoCodec.mName == "red") {
+              videoCodec.mEnabled = mRedUlpfecEnabled;
+            } else if (videoCodec.mName == "ulpfec") {
+              videoCodec.mEnabled = mRedUlpfecEnabled;
             } else if (videoCodec.mName == "VP8" || videoCodec.mName == "VP9") {
               if (videoCodec.mName == "VP9" && !mVP9Enabled) {
                 videoCodec.mEnabled = false;
                 break;
               }
               videoCodec.mConstraints.maxFs = mVP8MaxFs;
               videoCodec.mConstraints.maxFps = mVP8MaxFr;
             }
@@ -1030,16 +1038,51 @@ class ConfigureCodec {
     int32_t mH264Level;
     int32_t mH264MaxBr;
     int32_t mH264MaxMbps;
     int32_t mVP8MaxFs;
     int32_t mVP8MaxFr;
     bool mUseTmmbr;
     bool mUseRemb;
     bool mUseAudioFec;
+    bool mRedUlpfecEnabled;
+};
+
+class ConfigureRedCodec {
+  public:
+    explicit ConfigureRedCodec(nsCOMPtr<nsIPrefBranch>& branch,
+                               std::vector<uint8_t>* redundantEncodings) :
+      mRedundantEncodings(redundantEncodings)
+    {
+      // if we wanted to override or modify which encodings are considered
+      // for redundant encodings, we'd probably want to handle it here by
+      // checking prefs modifying the operator() code below
+    }
+
+    void operator()(JsepCodecDescription* codec) const
+    {
+      if (codec->mType == SdpMediaSection::kVideo &&
+          codec->mEnabled == false) {
+        uint8_t pt = (uint8_t)strtoul(codec->mDefaultPt.c_str(), nullptr, 10);
+        // don't search for the codec payload type unless we have a valid
+        // conversion (non-zero)
+        if (pt != 0) {
+          std::vector<uint8_t>::iterator it =
+            std::find(mRedundantEncodings->begin(),
+                      mRedundantEncodings->end(),
+                      pt);
+          if (it != mRedundantEncodings->end()) {
+            mRedundantEncodings->erase(it);
+          }
+        }
+      }
+    }
+
+  private:
+    std::vector<uint8_t>* mRedundantEncodings;
 };
 
 nsresult
 PeerConnectionImpl::ConfigureJsepSessionCodecs() {
   nsresult res;
   nsCOMPtr<nsIPrefService> prefs =
     do_GetService("@mozilla.org/preferences-service;1", &res);
 
@@ -1054,16 +1097,33 @@ PeerConnectionImpl::ConfigureJsepSession
   if (!branch) {
     CSFLogError(logTag, "%s: Couldn't get prefs branch", __FUNCTION__);
     return NS_ERROR_FAILURE;
   }
 
   ConfigureCodec configurer(branch);
   mJsepSession->ForEachCodec(configurer);
 
+  // first find the red codec description
+  std::vector<JsepCodecDescription*>& codecs = mJsepSession->Codecs();
+  JsepVideoCodecDescription* redCodec = nullptr;
+  for (auto codec : codecs) {
+    // we only really care about finding the RED codec if it is
+    // enabled
+    if (codec->mName == "red" && codec->mEnabled) {
+      redCodec = static_cast<JsepVideoCodecDescription*>(codec);
+      break;
+    }
+  }
+  // if red codec was found, configure it for the other enabled codecs
+  if (redCodec) {
+    ConfigureRedCodec configureRed(branch, &(redCodec->mRedundantEncodings));
+    mJsepSession->ForEachCodec(configureRed);
+  }
+
   // We use this to sort the list of codecs once everything is configured
   CompareCodecPriority comparator;
 
   // Sort by priority
   int32_t preferredCodec = 0;
   branch->GetIntPref("media.navigator.video.preferred_codec",
                      &preferredCodec);
 
--- a/media/webrtc/signaling/src/sdp/SdpAttribute.cpp
+++ b/media/webrtc/signaling/src/sdp/SdpAttribute.cpp
@@ -1097,16 +1097,18 @@ ShouldSerializeChannels(SdpRtpmapAttribu
       return true;
     case SdpRtpmapAttributeList::kPCMU:
     case SdpRtpmapAttributeList::kPCMA:
     case SdpRtpmapAttributeList::kVP8:
     case SdpRtpmapAttributeList::kVP9:
     case SdpRtpmapAttributeList::kiLBC:
     case SdpRtpmapAttributeList::kiSAC:
     case SdpRtpmapAttributeList::kH264:
+    case SdpRtpmapAttributeList::kRed:
+    case SdpRtpmapAttributeList::kUlpfec:
       return false;
     case SdpRtpmapAttributeList::kOtherCodec:
       return true;
   }
   MOZ_CRASH();
 }
 
 void
--- a/media/webrtc/signaling/src/sdp/SdpAttribute.h
+++ b/media/webrtc/signaling/src/sdp/SdpAttribute.h
@@ -1018,16 +1018,18 @@ public:
     kG722,
     kPCMU,
     kPCMA,
     kVP8,
     kVP9,
     kiLBC,
     kiSAC,
     kH264,
+    kRed,
+    kUlpfec,
     kOtherCodec
   };
 
   struct Rtpmap {
     std::string pt;
     CodecType codec;
     std::string name;
     uint32_t clock;
@@ -1097,16 +1099,22 @@ inline std::ostream& operator<<(std::ost
       os << "iLBC";
       break;
     case SdpRtpmapAttributeList::kiSAC:
       os << "iSAC";
       break;
     case SdpRtpmapAttributeList::kH264:
       os << "H264";
       break;
+    case SdpRtpmapAttributeList::kRed:
+      os << "red";
+      break;
+    case SdpRtpmapAttributeList::kUlpfec:
+      os << "ulpfec";
+      break;
     default:
       MOZ_ASSERT(false);
       os << "?";
   }
   return os;
 }
 
 ///////////////////////////////////////////////////////////////////////////
@@ -1130,16 +1138,42 @@ public:
 
     virtual ~Parameters() {}
     virtual Parameters* Clone() const = 0;
     virtual void Serialize(std::ostream& os) const = 0;
 
     SdpRtpmapAttributeList::CodecType codec_type;
   };
 
+  class RedParameters : public Parameters
+  {
+  public:
+    RedParameters()
+        : Parameters(SdpRtpmapAttributeList::kRed)
+    {
+    }
+
+    virtual Parameters*
+    Clone() const override
+    {
+      return new RedParameters(*this);
+    }
+
+    virtual void
+    Serialize(std::ostream& os) const override
+    {
+      for(size_t i = 0; i < encodings.size(); ++i) {
+        os << (i != 0 ? "/" : "")
+           << std::to_string(encodings[i]);
+      }
+    }
+
+    std::vector<uint8_t> encodings;
+  };
+
   class H264Parameters : public Parameters
   {
   public:
     static const uint32_t kDefaultProfileLevelId = 0x420010;
 
     H264Parameters()
         : Parameters(SdpRtpmapAttributeList::kH264),
           packetization_mode(0),
--- a/media/webrtc/signaling/src/sdp/SipccSdpAttributeList.cpp
+++ b/media/webrtc/signaling/src/sdp/SipccSdpAttributeList.cpp
@@ -354,16 +354,20 @@ SipccSdpAttributeList::GetCodecType(rtp_
     case RTP_H264_P1:
       return SdpRtpmapAttributeList::kH264;
     case RTP_OPUS:
       return SdpRtpmapAttributeList::kOpus;
     case RTP_VP8:
       return SdpRtpmapAttributeList::kVP8;
     case RTP_VP9:
       return SdpRtpmapAttributeList::kVP9;
+    case RTP_RED:
+      return SdpRtpmapAttributeList::kRed;
+    case RTP_ULPFEC:
+      return SdpRtpmapAttributeList::kUlpfec;
     case RTP_NONE:
     // Happens when sipcc doesn't know how to translate to the enum
     case RTP_CELP:
     case RTP_G726:
     case RTP_GSM:
     case RTP_G723:
     case RTP_DVI4:
     case RTP_DVI4_II:
@@ -716,16 +720,27 @@ SipccSdpAttributeList::LoadFmtp(sdp_t* s
             new SdpFmtpAttributeList::VP8Parameters(
               SdpRtpmapAttributeList::kVP8));
 
         vp8Parameters->max_fs = fmtp->max_fs;
         vp8Parameters->max_fr = fmtp->max_fr;
 
         parameters.reset(vp8Parameters);
       } break;
+      case RTP_RED: {
+        SdpFmtpAttributeList::RedParameters* redParameters(
+            new SdpFmtpAttributeList::RedParameters);
+        for (int i = 0;
+             i < SDP_FMTP_MAX_REDUNDANT_ENCODINGS && fmtp->redundant_encodings[i];
+             ++i) {
+          redParameters->encodings.push_back(fmtp->redundant_encodings[i]);
+        }
+
+        parameters.reset(redParameters);
+      } break;
       case RTP_OPUS: {
         SdpFmtpAttributeList::OpusParameters* opusParameters(
             new SdpFmtpAttributeList::OpusParameters);
         opusParameters->maxplaybackrate = fmtp->maxplaybackrate;
         opusParameters->stereo = fmtp->stereo;
         opusParameters->useInBandFec = fmtp->useinbandfec;
         parameters.reset(opusParameters);
       } break;
--- a/media/webrtc/signaling/src/sdp/sipcc/ccsdp.h
+++ b/media/webrtc/signaling/src/sdp/sipcc/ccsdp.h
@@ -35,16 +35,18 @@ typedef enum rtp_ptype_
     RTP_H264_P1      = 126,
     RTP_AVT          = 101,
     RTP_L16          = 102,
     RTP_H263         = 103,
     RTP_ILBC         = 116, /* used only to make an offer */
     RTP_OPUS         = 109,
     RTP_VP8          = 120,
     RTP_VP9          = 121,
+    RTP_RED          = 122,
+    RTP_ULPFEC       = 123,
     RTP_I420         = 124,
     RTP_ISAC         = 124
 } rtp_ptype;
 
 typedef struct {
     const char *name;
     int         value;
 } ccsdp_key_table_entry_t;
--- a/media/webrtc/signaling/src/sdp/sipcc/sdp.h
+++ b/media/webrtc/signaling/src/sdp/sipcc/sdp.h
@@ -44,16 +44,19 @@
 #define SDP_SRTP_MAX_MKI_SIZE_BYTES   4
 
 /* Max number of characters for Lifetime */
 #define SDP_SRTP_MAX_LIFETIME_BYTES 16
 
 #define SDP_SDESCRIPTIONS_KEY_SIZE_UNKNOWN      0
 #define SDP_SRTP_CRYPTO_SELECTION_FLAGS_UNKNOWN 0
 
+/* Max number of fmtp redundant encodings */
+#define SDP_FMTP_MAX_REDUNDANT_ENCODINGS 128
+
 /*
  * SRTP_CONTEXT_SET_*
  *  Set a SRTP Context field flag
  */
 #define SDP_SRTP_ENCRYPT_MASK           0x00000001
 #define SDP_SRTP_AUTHENTICATE_MASK      0x00000002
 #define SDP_SRTCP_ENCRYPT_MASK          0x00000004
 #define SDP_SRTCP_SSRC_MASK             0x20000000
@@ -705,16 +708,19 @@ typedef struct sdp_fmtp {
     tinybool                  annex_i;
     tinybool                  annex_j;
     tinybool                  annex_t;
 
     /* H.263 codec requires annex K,N and P to have values */
     uint16_t                       annex_k_val;
     uint16_t                       annex_n_val;
 
+    /* RFC 5109 Section 4.2 for specifying redundant encodings */
+    uint8_t              redundant_encodings[SDP_FMTP_MAX_REDUNDANT_ENCODINGS];
+
     /* Annex P can take one or more values in the range 1-4 . e.g P=1,3 */
     uint16_t                       annex_p_val_picture_resize; /* 1 = four; 2 = sixteenth */
     uint16_t                       annex_p_val_warp; /* 3 = half; 4=sixteenth */
 
     uint8_t                        flag;
 
   /* END - All Video related FMTP parameters */
 
--- a/media/webrtc/signaling/src/sdp/sipcc/sdp_access.c
+++ b/media/webrtc/signaling/src/sdp/sipcc/sdp_access.c
@@ -24,16 +24,18 @@ static const char* logTag = "sdp_access"
 #define SIPSDP_ATTR_ENCNAME_ILBC      "iLBC"
 #define SIPSDP_ATTR_ENCNAME_H263v2    "H263-1998"
 #define SIPSDP_ATTR_ENCNAME_H264      "H264"
 #define SIPSDP_ATTR_ENCNAME_VP8       "VP8"
 #define SIPSDP_ATTR_ENCNAME_VP9       "VP9"
 #define SIPSDP_ATTR_ENCNAME_L16_256K  "L16"
 #define SIPSDP_ATTR_ENCNAME_ISAC      "ISAC"
 #define SIPSDP_ATTR_ENCNAME_OPUS      "opus"
+#define SIPSDP_ATTR_ENCNAME_RED       "red"
+#define SIPSDP_ATTR_ENCNAME_ULPFEC    "ulpfec"
 
 /* Function:    sdp_find_media_level
  * Description: Find and return a pointer to the specified media level,
  *              if it exists.
  *              Note: This is not an API for the application but an internal
  *              routine used by the SDP library.
  * Parameters:  sdp_p     The SDP handle returned by sdp_init_description.
  *              level       The media level to find.
@@ -1372,16 +1374,22 @@ rtp_ptype sdp_get_known_payload_type(sdp
           }
         }
         if (cpr_strcasecmp(encname, SIPSDP_ATTR_ENCNAME_VP8) == 0) {
           return (RTP_VP8);
         }
         if (cpr_strcasecmp(encname, SIPSDP_ATTR_ENCNAME_VP9) == 0) {
           return (RTP_VP9);
         }
+        if (cpr_strcasecmp(encname, SIPSDP_ATTR_ENCNAME_RED) == 0) {
+          return (RTP_RED);
+        }
+        if (cpr_strcasecmp(encname, SIPSDP_ATTR_ENCNAME_ULPFEC) == 0) {
+          return (RTP_ULPFEC);
+        }
       }
     }
   }
 
   return (RTP_NONE);
 }
 
 /* Function:    sdp_get_media_payload_type
--- a/media/webrtc/signaling/src/sdp/sipcc/sdp_attr.c
+++ b/media/webrtc/signaling/src/sdp/sipcc/sdp_attr.c
@@ -1504,33 +1504,33 @@ sdp_result_e sdp_parse_attr_fmtp (sdp_t 
                     sdp_attr_fmtp_no_value(sdp_p, "annex_p");
                     SDP_FREE(temp_ptr);
                     return SDP_INVALID_PARAMETER;
                 }
             }
             fmtp_p->annex_p_val_picture_resize = 0;
             fmtp_p->annex_p_val_warp = 0;
             tok = tmp;
-            tok++; temp=PL_strtok_r(tok, ",", &strtok_state);
+            tok++; temp = PL_strtok_r(tok, ",", &strtok_state);
             if (temp) {
                 iter=1;
                 while (temp != NULL) {
                     errno = 0;
                     strtoul_result = strtoul(temp, &strtoul_end, 10);
 
                     if (errno || temp == strtoul_end || strtoul_result > USHRT_MAX) {
                         break;
                     }
 
                     if (iter == 1)
                         fmtp_p->annex_p_val_picture_resize = (uint16_t) strtoul_result;
                     else if (iter == 2)
                         fmtp_p->annex_p_val_warp = (uint16_t) strtoul_result;
 
-                    temp=PL_strtok_r(NULL, ",", &strtok_state);
+                    temp = PL_strtok_r(NULL, ",", &strtok_state);
                     iter++;
                 }
             }
 
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             codec_info_found = TRUE;
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[42].name,
                                sdp_fmtp_codec_param[42].strlen) == 0) {
@@ -1775,16 +1775,37 @@ sdp_result_e sdp_parse_attr_fmtp (sdp_t 
                     }
                     if (strchr(temp, 'T') !=NULL) {
                         attr_p->attr.fmtp.annex_t = TRUE;
                     }
                     temp=PL_strtok_r(NULL, ";", &strtok_state);
                 }
             } /* if (temp) */
             done = TRUE;
+        } else if (strchr(tmp, '/')) {
+            // XXX Note that because RFC 5109 so conveniently specified
+            // this fmtp with no param names, we hope that nothing else
+            // has a slash in the string because otherwise we won't know
+            // how to differentiate.
+            temp=PL_strtok_r(tmp, "/", &strtok_state);
+            if (temp) {
+                iter = 0;
+                while (temp != NULL) {
+                    errno = 0;
+                    strtoul_result = strtoul(temp, &strtoul_end, 10);
+
+                    if (errno ||
+                        temp == strtoul_end || strtoul_result > USHRT_MAX) {
+                        continue;
+                    }
+                    fmtp_p->redundant_encodings[iter++] =
+                        (uint8_t)strtoul_result;
+                    temp=PL_strtok_r(NULL, "/", &strtok_state);
+                }
+            } /* if (temp) */
         } else {
           // XXX Note that DTMF fmtp will fall into here:
           // a=fmtp:101 0-15 (or 0-15,NN,NN etc)
 
           // unknown parameter - eat chars until ';'
           CSFLogDebug(logTag, "%s Unknown fmtp type (%s) - ignoring", __FUNCTION__,
                       tmp);
           fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t",
--- a/media/webrtc/signaling/test/jsep_session_unittest.cpp
+++ b/media/webrtc/signaling/test/jsep_session_unittest.cpp
@@ -2737,45 +2737,53 @@ TEST_F(JsepSessionTest, ValidateOfferedC
   ASSERT_TRUE(!!outputSdp);
 
   ASSERT_EQ(2U, outputSdp->GetMediaSectionCount());
   auto& video_section = outputSdp->GetMediaSection(1);
   ASSERT_EQ(SdpMediaSection::kVideo, video_section.GetMediaType());
   auto& video_attrs = video_section.GetAttributeList();
   ASSERT_EQ(SdpDirectionAttribute::kSendrecv, video_attrs.GetDirection());
 
-  ASSERT_EQ(4U, video_section.GetFormats().size());
+  ASSERT_EQ(6U, video_section.GetFormats().size());
   ASSERT_EQ("121", video_section.GetFormats()[0]);
   ASSERT_EQ("120", video_section.GetFormats()[1]);
   ASSERT_EQ("126", video_section.GetFormats()[2]);
   ASSERT_EQ("97", video_section.GetFormats()[3]);
+  ASSERT_EQ("122", video_section.GetFormats()[4]);
+  ASSERT_EQ("123", video_section.GetFormats()[5]);
 
   // Validate rtpmap
   ASSERT_TRUE(video_attrs.HasAttribute(SdpAttribute::kRtpmapAttribute));
   auto& rtpmaps = video_attrs.GetRtpmap();
   ASSERT_TRUE(rtpmaps.HasEntry("120"));
   ASSERT_TRUE(rtpmaps.HasEntry("121"));
   ASSERT_TRUE(rtpmaps.HasEntry("126"));
   ASSERT_TRUE(rtpmaps.HasEntry("97"));
+  ASSERT_TRUE(rtpmaps.HasEntry("122"));
+  ASSERT_TRUE(rtpmaps.HasEntry("123"));
 
   auto& vp8_entry = rtpmaps.GetEntry("120");
   auto& vp9_entry = rtpmaps.GetEntry("121");
   auto& h264_1_entry = rtpmaps.GetEntry("126");
   auto& h264_0_entry = rtpmaps.GetEntry("97");
+  auto& red_0_entry = rtpmaps.GetEntry("122");
+  auto& ulpfec_0_entry = rtpmaps.GetEntry("123");
 
   ASSERT_EQ("VP8", vp8_entry.name);
   ASSERT_EQ("VP9", vp9_entry.name);
   ASSERT_EQ("H264", h264_1_entry.name);
   ASSERT_EQ("H264", h264_0_entry.name);
+  ASSERT_EQ("red", red_0_entry.name);
+  ASSERT_EQ("ulpfec", ulpfec_0_entry.name);
 
   // Validate fmtps
   ASSERT_TRUE(video_attrs.HasAttribute(SdpAttribute::kFmtpAttribute));
   auto& fmtps = video_attrs.GetFmtp().mFmtps;
 
-  ASSERT_EQ(4U, fmtps.size());
+  ASSERT_EQ(5U, fmtps.size());
 
   // VP8
   const SdpFmtpAttributeList::Parameters* vp8_params =
     video_section.FindFmtp("120");
   ASSERT_TRUE(vp8_params);
   ASSERT_EQ(SdpRtpmapAttributeList::kVP8, vp8_params->codec_type);
 
   auto& parsed_vp8_params =
@@ -2816,21 +2824,40 @@ TEST_F(JsepSessionTest, ValidateOfferedC
   ASSERT_EQ(SdpRtpmapAttributeList::kH264, h264_0_params->codec_type);
 
   auto& parsed_h264_0_params =
       *static_cast<const SdpFmtpAttributeList::H264Parameters*>(h264_0_params);
 
   ASSERT_EQ((uint32_t)0x42e00d, parsed_h264_0_params.profile_level_id);
   ASSERT_TRUE(parsed_h264_0_params.level_asymmetry_allowed);
   ASSERT_EQ(0U, parsed_h264_0_params.packetization_mode);
+
+  // red
+  const SdpFmtpAttributeList::Parameters* red_params =
+    video_section.FindFmtp("122");
+  ASSERT_TRUE(red_params);
+  ASSERT_EQ(SdpRtpmapAttributeList::kRed, red_params->codec_type);
+
+  auto& parsed_red_params =
+      *static_cast<const SdpFmtpAttributeList::RedParameters*>(red_params);
+  ASSERT_EQ(5U, parsed_red_params.encodings.size());
+  ASSERT_EQ(121, parsed_red_params.encodings[0]);
+  ASSERT_EQ(120, parsed_red_params.encodings[1]);
+  ASSERT_EQ(126, parsed_red_params.encodings[2]);
+  ASSERT_EQ(97, parsed_red_params.encodings[3]);
+  ASSERT_EQ(123, parsed_red_params.encodings[4]);
 }
 
 TEST_F(JsepSessionTest, ValidateAnsweredCodecParams)
 {
-
+  // TODO(bug 1099351): Once fixed, we can allow red in this offer,
+  // which will also cause multiple codecs in answer.  For now,
+  // red/ulpfec for video are behind a pref to mitigate potential for
+  // errors.
+  SetCodecEnabled(mSessionOff, "red", false);
   for (auto i = mSessionAns.Codecs().begin(); i != mSessionAns.Codecs().end();
        ++i) {
     auto* codec = *i;
     if (codec->mName == "H264") {
       JsepVideoCodecDescription* h264 =
           static_cast<JsepVideoCodecDescription*>(codec);
       h264->mProfileLevelId = 0x42a00d;
       // Switch up the pts
--- a/media/webrtc/signaling/test/jsep_track_unittest.cpp
+++ b/media/webrtc/signaling/test/jsep_track_unittest.cpp
@@ -27,43 +27,76 @@
 namespace mozilla {
 
 class JsepTrackTest : public ::testing::Test
 {
   public:
     JsepTrackTest() {}
 
     std::vector<JsepCodecDescription*>
-    MakeCodecs() const
+    MakeCodecs(bool addFecCodecs = false, bool preferRed = false) const
     {
       std::vector<JsepCodecDescription*> results;
       results.push_back(
           new JsepAudioCodecDescription("1", "opus", 48000, 2, 960, 40000));
       results.push_back(
           new JsepAudioCodecDescription("9", "G722", 8000, 1, 320, 64000));
 
+      JsepVideoCodecDescription* red = nullptr;
+      if (addFecCodecs && preferRed) {
+        red = new JsepVideoCodecDescription(
+            "122",
+            "red",
+            90000
+            );
+        results.push_back(red);
+      }
+
       JsepVideoCodecDescription* vp8 =
           new JsepVideoCodecDescription("120", "VP8", 90000);
       vp8->mConstraints.maxFs = 12288;
       vp8->mConstraints.maxFps = 60;
       results.push_back(vp8);
 
       JsepVideoCodecDescription* h264 =
           new JsepVideoCodecDescription("126", "H264", 90000);
       h264->mPacketizationMode = 1;
       h264->mProfileLevelId = 0x42E00D;
       results.push_back(h264);
 
+      if (addFecCodecs) {
+        if (!preferRed) {
+          red = new JsepVideoCodecDescription(
+              "122",
+              "red",
+              90000
+              );
+          results.push_back(red);
+        }
+        JsepVideoCodecDescription* ulpfec = new JsepVideoCodecDescription(
+            "123",
+            "ulpfec",
+            90000
+            );
+        results.push_back(ulpfec);
+      }
+
       results.push_back(
           new JsepApplicationCodecDescription(
             "5000",
             "webrtc-datachannel",
             16
             ));
 
+      // if we're doing something with red, it needs
+      // to update the redundant encodings list
+      if (red) {
+        red->UpdateRedundantEncodings(results);
+      }
+
       return results;
     }
 
     void Init(SdpMediaSection::MediaType type) {
       InitCodecs();
       InitTracks(type);
       InitSdp(type);
     }
@@ -206,26 +239,28 @@ class JsepTrackTest : public ::testing::
     }
 
     void CheckAnsEncodingCount(size_t expected) const
     {
       CheckEncodingCount(expected, mSendAns, mRecvOff);
     }
 
     const JsepVideoCodecDescription*
-    GetVideoCodec(const JsepTrack& track) const
+    GetVideoCodec(const JsepTrack& track,
+                  size_t expectedSize = 1,
+                  size_t codecIndex = 0) const
     {
       if (!track.GetNegotiatedDetails() ||
           track.GetNegotiatedDetails()->GetEncodingCount() != 1U) {
         return nullptr;
       }
       const std::vector<JsepCodecDescription*>& codecs =
         track.GetNegotiatedDetails()->GetEncoding(0).GetCodecs();
-      if (codecs.size() != 1U ||
-          codecs[0]->mType != SdpMediaSection::kVideo) {
+      if (codecs.size() != expectedSize ||
+          codecs[codecIndex]->mType != SdpMediaSection::kVideo) {
         return nullptr;
       }
       return static_cast<const JsepVideoCodecDescription*>(codecs[0]);
     }
 
     void CheckOtherFbsSize(const JsepTrack& track, size_t expected) const
     {
       const JsepVideoCodecDescription* videoCodec = GetVideoCodec(track);
@@ -361,16 +396,210 @@ TEST_F(JsepTrackTest, AudioNegotiation)
 TEST_F(JsepTrackTest, VideoNegotiation)
 {
   Init(SdpMediaSection::kVideo);
   OfferAnswer();
   CheckOffEncodingCount(1);
   CheckAnsEncodingCount(1);
 }
 
+TEST_F(JsepTrackTest, VideoNegotationOffererFEC)
+{
+  mOffCodecs.values = MakeCodecs(true);
+  mAnsCodecs.values = MakeCodecs(false);
+
+  InitTracks(SdpMediaSection::kVideo);
+  InitSdp(SdpMediaSection::kVideo);
+  OfferAnswer();
+
+  CheckOffEncodingCount(1);
+  CheckAnsEncodingCount(1);
+
+  ASSERT_NE(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+  ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+  ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+  ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+
+  ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123"), std::string::npos);
+  ASSERT_EQ(mAnswer->ToString().find("a=fmtp:122"), std::string::npos);
+
+  const JsepVideoCodecDescription* track = nullptr;
+  ASSERT_TRUE((track = GetVideoCodec(*mSendOff)));
+  ASSERT_EQ("120", track->mDefaultPt);
+  ASSERT_TRUE((track = GetVideoCodec(*mRecvOff)));
+  ASSERT_EQ("120", track->mDefaultPt);
+  ASSERT_TRUE((track = GetVideoCodec(*mSendAns)));
+  ASSERT_EQ("120", track->mDefaultPt);
+  ASSERT_TRUE((track = GetVideoCodec(*mRecvAns)));
+  ASSERT_EQ("120", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, VideoNegotationAnswererFEC)
+{
+  mOffCodecs.values = MakeCodecs(false);
+  mAnsCodecs.values = MakeCodecs(true);
+
+  InitTracks(SdpMediaSection::kVideo);
+  InitSdp(SdpMediaSection::kVideo);
+  OfferAnswer();
+
+  CheckOffEncodingCount(1);
+  CheckAnsEncodingCount(1);
+
+  ASSERT_EQ(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+  ASSERT_EQ(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+  ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+  ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+
+  ASSERT_EQ(mOffer->ToString().find("a=fmtp:122"), std::string::npos);
+  ASSERT_EQ(mAnswer->ToString().find("a=fmtp:122"), std::string::npos);
+
+  const JsepVideoCodecDescription* track = nullptr;
+  ASSERT_TRUE((track = GetVideoCodec(*mSendOff)));
+  ASSERT_EQ("120", track->mDefaultPt);
+  ASSERT_TRUE((track = GetVideoCodec(*mRecvOff)));
+  ASSERT_EQ("120", track->mDefaultPt);
+  ASSERT_TRUE((track = GetVideoCodec(*mSendAns)));
+  ASSERT_EQ("120", track->mDefaultPt);
+  ASSERT_TRUE((track = GetVideoCodec(*mRecvAns)));
+  ASSERT_EQ("120", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, VideoNegotationOffererAnswererFEC)
+{
+  mOffCodecs.values = MakeCodecs(true);
+  mAnsCodecs.values = MakeCodecs(true);
+
+  InitTracks(SdpMediaSection::kVideo);
+  InitSdp(SdpMediaSection::kVideo);
+  OfferAnswer();
+
+  CheckOffEncodingCount(1);
+  CheckAnsEncodingCount(1);
+
+  ASSERT_NE(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+  ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+  ASSERT_NE(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+  ASSERT_NE(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+
+  ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123"), std::string::npos);
+  ASSERT_NE(mAnswer->ToString().find("a=fmtp:122 120/126/123"), std::string::npos);
+
+  const JsepVideoCodecDescription* track = nullptr;
+  ASSERT_TRUE((track = GetVideoCodec(*mSendOff, 4)));
+  ASSERT_EQ("120", track->mDefaultPt);
+  ASSERT_TRUE((track = GetVideoCodec(*mRecvOff, 4)));
+  ASSERT_EQ("120", track->mDefaultPt);
+  ASSERT_TRUE((track = GetVideoCodec(*mSendAns, 4)));
+  ASSERT_EQ("120", track->mDefaultPt);
+  ASSERT_TRUE((track = GetVideoCodec(*mRecvAns, 4)));
+  ASSERT_EQ("120", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, VideoNegotationOffererAnswererFECPreferred)
+{
+  mOffCodecs.values = MakeCodecs(true, true);
+  mAnsCodecs.values = MakeCodecs(true);
+
+  InitTracks(SdpMediaSection::kVideo);
+  InitSdp(SdpMediaSection::kVideo);
+  OfferAnswer();
+
+  CheckOffEncodingCount(1);
+  CheckAnsEncodingCount(1);
+
+  ASSERT_NE(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+  ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+  ASSERT_NE(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+  ASSERT_NE(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+
+  ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123"), std::string::npos);
+  ASSERT_NE(mAnswer->ToString().find("a=fmtp:122 120/126/123"), std::string::npos);
+
+  const JsepVideoCodecDescription* track = nullptr;
+  ASSERT_TRUE((track = GetVideoCodec(*mSendOff, 4)));
+  ASSERT_EQ("122", track->mDefaultPt);
+  ASSERT_TRUE((track = GetVideoCodec(*mRecvOff, 4)));
+  ASSERT_EQ("122", track->mDefaultPt);
+  ASSERT_TRUE((track = GetVideoCodec(*mSendAns, 4)));
+  ASSERT_EQ("122", track->mDefaultPt);
+  ASSERT_TRUE((track = GetVideoCodec(*mRecvAns, 4)));
+  ASSERT_EQ("122", track->mDefaultPt);
+}
+
+// Make sure we only put the right things in the fmtp:122 120/.... line
+TEST_F(JsepTrackTest, VideoNegotationOffererAnswererFECMismatch)
+{
+  mOffCodecs.values = MakeCodecs(true, true);
+  mAnsCodecs.values = MakeCodecs(true);
+  // remove h264 from answer codecs
+  ASSERT_EQ("H264", mAnsCodecs.values[3]->mName);
+  mAnsCodecs.values.erase(mAnsCodecs.values.begin()+3);
+
+  InitTracks(SdpMediaSection::kVideo);
+  InitSdp(SdpMediaSection::kVideo);
+  OfferAnswer();
+
+  CheckOffEncodingCount(1);
+  CheckAnsEncodingCount(1);
+
+  ASSERT_NE(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+  ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+  ASSERT_NE(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+  ASSERT_NE(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+
+  ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123"), std::string::npos);
+  ASSERT_NE(mAnswer->ToString().find("a=fmtp:122 120/123"), std::string::npos);
+
+  const JsepVideoCodecDescription* track = nullptr;
+  ASSERT_TRUE((track = GetVideoCodec(*mSendOff, 3)));
+  ASSERT_EQ("122", track->mDefaultPt);
+  ASSERT_TRUE((track = GetVideoCodec(*mRecvOff, 3)));
+  ASSERT_EQ("122", track->mDefaultPt);
+  ASSERT_TRUE((track = GetVideoCodec(*mSendAns, 3)));
+  ASSERT_EQ("122", track->mDefaultPt);
+  ASSERT_TRUE((track = GetVideoCodec(*mRecvAns, 3)));
+  ASSERT_EQ("122", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, VideoNegotationOffererAnswererFECZeroVP9Codec)
+{
+  mOffCodecs.values = MakeCodecs(true);
+  JsepVideoCodecDescription* vp9 =
+    new JsepVideoCodecDescription("0", "VP9", 90000);
+  vp9->mConstraints.maxFs = 12288;
+  vp9->mConstraints.maxFps = 60;
+  mOffCodecs.values.push_back(vp9);
+
+  ASSERT_EQ(8U, mOffCodecs.values.size());
+  JsepVideoCodecDescription* red =
+      static_cast<JsepVideoCodecDescription*>(mOffCodecs.values[4]);
+  ASSERT_EQ("red", red->mName);
+  // rebuild the redundant encodings with our newly added "wacky" VP9
+  red->mRedundantEncodings.clear();
+  red->UpdateRedundantEncodings(mOffCodecs.values);
+
+  mAnsCodecs.values = MakeCodecs(true);
+
+  InitTracks(SdpMediaSection::kVideo);
+  InitSdp(SdpMediaSection::kVideo);
+  OfferAnswer();
+
+  CheckOffEncodingCount(1);
+  CheckAnsEncodingCount(1);
+
+  ASSERT_NE(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+  ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+  ASSERT_NE(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+  ASSERT_NE(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+
+  ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123/0"), std::string::npos);
+  ASSERT_NE(mAnswer->ToString().find("a=fmtp:122 120/126/123\r\n"), std::string::npos);
+}
+
 TEST_F(JsepTrackTest, VideoNegotiationOfferRemb)
 {
   InitCodecs();
   // enable remb on the offer codecs
   ((JsepVideoCodecDescription*)mOffCodecs.values[2])->EnableRemb();
   InitTracks(SdpMediaSection::kVideo);
   InitSdp(SdpMediaSection::kVideo);
   OfferAnswer();
--- a/media/webrtc/signaling/test/sdp_unittests.cpp
+++ b/media/webrtc/signaling/test/sdp_unittests.cpp
@@ -1220,24 +1220,26 @@ const std::string kBasicAudioVideoOffer 
 "a=candidate:6 1 UDP 16515071 162.222.183.171 51858 typ relay raddr 162.222.183.171 rport 51858" CRLF
 "a=candidate:3 2 UDP 100401150 162.222.183.171 62454 typ relay raddr 162.222.183.171 rport 62454" CRLF
 "a=candidate:2 2 UDP 1694236670 24.6.134.204 55428 typ srflx raddr 10.0.0.36 rport 55428" CRLF
 "a=candidate:6 2 UDP 16515070 162.222.183.171 50340 typ relay raddr 162.222.183.171 rport 50340" CRLF
 "a=candidate:0 2 UDP 2130379006 10.0.0.36 55428 typ host" CRLF
 "a=rtcp:62454 IN IP4 162.222.183.171" CRLF
 "a=end-of-candidates" CRLF
 "a=ssrc:5150" CRLF
-"m=video 9 RTP/SAVPF 120 121" CRLF
+"m=video 9 RTP/SAVPF 120 121 122 123" CRLF
 "c=IN IP6 ::1" CRLF
 "a=fingerprint:sha-1 DF:FA:FB:08:3B:3C:54:1D:D7:D4:05:77:A0:72:9B:14:08:6D:0F:4C:2E:AC:8A:FD:0A:8E:99:BF:5D:E8:3C:E7" CRLF
 "a=mid:second" CRLF
 "a=rtpmap:120 VP8/90000" CRLF
 "a=fmtp:120 max-fs=3600;max-fr=30" CRLF
 "a=rtpmap:121 VP9/90000" CRLF
 "a=fmtp:121 max-fs=3600;max-fr=30" CRLF
+"a=rtpmap:122 red/90000" CRLF
+"a=rtpmap:123 ulpfec/90000" CRLF
 "a=recvonly" CRLF
 "a=rtcp-fb:120 nack" CRLF
 "a=rtcp-fb:120 nack pli" CRLF
 "a=rtcp-fb:120 ccm fir" CRLF
 "a=rtcp-fb:121 nack" CRLF
 "a=rtcp-fb:121 nack pli" CRLF
 "a=rtcp-fb:121 ccm fir" CRLF
 "a=setup:active" CRLF
@@ -1413,19 +1415,21 @@ TEST_P(NewSdpTest, CheckMlines) {
   ASSERT_EQ("101", audio_formats[4]);
 
   ASSERT_EQ(SdpMediaSection::kVideo, mSdp->GetMediaSection(1).GetMediaType())
     << "Wrong type for second media section";
   ASSERT_EQ(SdpMediaSection::kRtpSavpf,
             mSdp->GetMediaSection(1).GetProtocol())
     << "Wrong protocol for video";
   auto video_formats = mSdp->GetMediaSection(1).GetFormats();
-  ASSERT_EQ(2U, video_formats.size()) << "Wrong number of formats for video";
+  ASSERT_EQ(4U, video_formats.size()) << "Wrong number of formats for video";
   ASSERT_EQ("120", video_formats[0]);
   ASSERT_EQ("121", video_formats[1]);
+  ASSERT_EQ("122", video_formats[2]);
+  ASSERT_EQ("123", video_formats[3]);
 
   ASSERT_EQ(SdpMediaSection::kAudio, mSdp->GetMediaSection(2).GetMediaType())
     << "Wrong type for third media section";
 }
 
 TEST_P(NewSdpTest, CheckSetup) {
   ParseSdp(kBasicAudioVideoOffer);
   ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
@@ -1513,33 +1517,138 @@ TEST_P(NewSdpTest, CheckRtpmap) {
   CheckRtpmap("101",
               SdpRtpmapAttributeList::kOtherCodec,
               "telephone-event",
               8000,
               1,
               audiosec.GetFormats()[4],
               rtpmap);
 
-  const SdpMediaSection& videosec1 = mSdp->GetMediaSection(1);
+  const SdpMediaSection& videosec = mSdp->GetMediaSection(1);
+  const SdpRtpmapAttributeList videoRtpmap =
+    videosec.GetAttributeList().GetRtpmap();
+  ASSERT_EQ(4U, videoRtpmap.mRtpmaps.size())
+    << "Wrong number of rtpmap attributes for video";
+
   CheckRtpmap("120",
               SdpRtpmapAttributeList::kVP8,
               "VP8",
               90000,
               0,
-              videosec1.GetFormats()[0],
-              videosec1.GetAttributeList().GetRtpmap());
-
-  const SdpMediaSection& videosec2 = mSdp->GetMediaSection(1);
+              videosec.GetFormats()[0],
+              videoRtpmap);
+
   CheckRtpmap("121",
               SdpRtpmapAttributeList::kVP9,
               "VP9",
               90000,
               0,
-              videosec2.GetFormats()[1],
-              videosec2.GetAttributeList().GetRtpmap());
+              videosec.GetFormats()[1],
+              videoRtpmap);
+
+  CheckRtpmap("122",
+              SdpRtpmapAttributeList::kRed,
+              "red",
+              90000,
+              0,
+              videosec.GetFormats()[2],
+              videoRtpmap);
+
+  CheckRtpmap("123",
+              SdpRtpmapAttributeList::kUlpfec,
+              "ulpfec",
+              90000,
+              0,
+              videosec.GetFormats()[3],
+              videoRtpmap);
+}
+
+static const std::string kVideoWithRedAndUlpfecSdp =
+  "v=0" CRLF
+  "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF
+  "s=SIP Call" CRLF
+  "c=IN IP4 198.51.100.7" CRLF
+  "t=0 0" CRLF
+  "m=video 9 RTP/SAVPF 97 120 121 122 123" CRLF
+  "c=IN IP6 ::1" CRLF
+  "a=fingerprint:sha-1 DF:FA:FB:08:3B:3C:54:1D:D7:D4:05:77:A0:72:9B:14:08:6D:0F:4C:2E:AC:8A:FD:0A:8E:99:BF:5D:E8:3C:E7" CRLF
+  "a=rtpmap:97 H264/90000" CRLF
+  "a=fmtp:97 profile-level-id=42a01e" CRLF
+  "a=rtpmap:120 VP8/90000" CRLF
+  "a=fmtp:120 max-fs=3600;max-fr=30" CRLF
+  "a=rtpmap:121 VP9/90000" CRLF
+  "a=fmtp:121 max-fs=3600;max-fr=30" CRLF
+  "a=rtpmap:122 red/90000" CRLF
+  "a=rtpmap:123 ulpfec/90000" CRLF;
+
+TEST_P(NewSdpTest, CheckRedNoFmtp) {
+  ParseSdp(kVideoWithRedAndUlpfecSdp);
+  ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+  ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+    << "Wrong number of media sections";
+
+  ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+              SdpAttribute::kFmtpAttribute));
+  auto video_format_params =
+      mSdp->GetMediaSection(0).GetAttributeList().GetFmtp().mFmtps;
+  ASSERT_EQ(3U, video_format_params.size());
+
+  // make sure we don't get a fmtp for codec 122
+  for (size_t i = 0; i < video_format_params.size(); ++i) {
+    ASSERT_NE("122", video_format_params[i].format);
+  }
+}
+
+TEST_P(NewSdpTest, CheckRedFmtpWith2Codecs) {
+  ParseSdp(kVideoWithRedAndUlpfecSdp + "a=fmtp:122 120/121" CRLF);
+  ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+  ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+    << "Wrong number of media sections";
+
+  ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+              SdpAttribute::kFmtpAttribute));
+  auto video_format_params =
+      mSdp->GetMediaSection(0).GetAttributeList().GetFmtp().mFmtps;
+  ASSERT_EQ(4U, video_format_params.size());
+
+  ASSERT_EQ("122", video_format_params[3].format);
+  ASSERT_TRUE(!!video_format_params[3].parameters);
+  ASSERT_EQ(SdpRtpmapAttributeList::kRed,
+            video_format_params[3].parameters->codec_type);
+  const SdpFmtpAttributeList::RedParameters* red_parameters(
+      static_cast<SdpFmtpAttributeList::RedParameters*>(
+        video_format_params[3].parameters.get()));
+  ASSERT_EQ(2U, red_parameters->encodings.size());
+  ASSERT_EQ(120U, red_parameters->encodings[0]);
+  ASSERT_EQ(121U, red_parameters->encodings[1]);
+}
+
+TEST_P(NewSdpTest, CheckRedFmtpWith3Codecs) {
+  ParseSdp(kVideoWithRedAndUlpfecSdp + "a=fmtp:122 120/121/123" CRLF);
+  ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+  ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+    << "Wrong number of media sections";
+
+  ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+              SdpAttribute::kFmtpAttribute));
+  auto video_format_params =
+      mSdp->GetMediaSection(0).GetAttributeList().GetFmtp().mFmtps;
+  ASSERT_EQ(4U, video_format_params.size());
+
+  ASSERT_EQ("122", video_format_params[3].format);
+  ASSERT_TRUE(!!video_format_params[3].parameters);
+  ASSERT_EQ(SdpRtpmapAttributeList::kRed,
+            video_format_params[3].parameters->codec_type);
+  const SdpFmtpAttributeList::RedParameters* red_parameters(
+      static_cast<SdpFmtpAttributeList::RedParameters*>(
+        video_format_params[3].parameters.get()));
+  ASSERT_EQ(3U, red_parameters->encodings.size());
+  ASSERT_EQ(120U, red_parameters->encodings[0]);
+  ASSERT_EQ(121U, red_parameters->encodings[1]);
+  ASSERT_EQ(123U, red_parameters->encodings[2]);
 }
 
 const std::string kH264AudioVideoOffer =
 "v=0" CRLF
 "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
 "s=SIP Call" CRLF
 "c=IN IP4 224.0.0.1/100/12" CRLF
 "t=0 0" CRLF
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -398,16 +398,17 @@ pref("media.navigator.load_adapt.measure
 pref("media.navigator.load_adapt.avg_seconds",3);
 pref("media.navigator.load_adapt.high_load","0.90");
 pref("media.navigator.load_adapt.low_load","0.40");
 pref("media.navigator.video.default_fps",30);
 pref("media.navigator.video.default_minfps",10);
 pref("media.navigator.video.use_remb", true);
 pref("media.navigator.video.use_tmmbr", false);
 pref("media.navigator.audio.use_fec", true);
+pref("media.navigator.video.red_ulpfec_enabled", false);
 
 pref("media.webrtc.debug.trace_mask", 0);
 pref("media.webrtc.debug.multi_log", false);
 pref("media.webrtc.debug.aec_log_dir", "");
 pref("media.webrtc.debug.log_file", "");
 pref("media.webrtc.debug.aec_dump_max_size", 4194304); // 4MB
 
 #ifdef MOZ_WIDGET_GONK