Bug 1155435 - add sdp RtcpFb for REMB, r=drno, r=jesup
authorMichael Froman <mfroman@mozilla.com>
Fri, 20 May 2016 16:38:29 -0500
changeset 298729 6fa9a68064b369d7c44053486b2a023cbc63c955
parent 298728 8e697c3542ffbe810d6ecc9912a510216471e99b
child 298730 0a63c85e816e70ce7aac7c7461ad661fa8b47aea
push id19379
push usercbook@mozilla.com
push dateWed, 25 May 2016 13:21:42 +0000
treeherderfx-team@ff6044ca8189 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdrno, jesup
bugs1155435
milestone49.0a1
Bug 1155435 - add sdp RtcpFb for REMB, r=drno, r=jesup MozReview-Commit-ID: Ga7so20UsMo
media/webrtc/signaling/src/jsep/JsepCodecDescription.h
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.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
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
@@ -200,27 +200,44 @@ class JsepAudioCodecDescription : public
 class JsepVideoCodecDescription : public JsepCodecDescription {
  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),
         mPacketizationMode(0)
   {
     // Add supported rtcp-fb types
     mNackFbTypes.push_back("");
     mNackFbTypes.push_back(SdpRtcpFbAttributeList::pli);
     mCcmFbTypes.push_back(SdpRtcpFbAttributeList::fir);
   }
 
   virtual void
   EnableTmmbr() {
-    mCcmFbTypes.push_back(SdpRtcpFbAttributeList::tmmbr);
+    // EnableTmmbr can be called multiple times due to multiple calls to
+    // PeerConnectionImpl::ConfigureJsepSessionCodecs
+    if (!mTmmbrEnabled) {
+      mTmmbrEnabled = true;
+      mCcmFbTypes.push_back(SdpRtcpFbAttributeList::tmmbr);
+    }
+  }
+
+  virtual void
+  EnableRemb() {
+    // EnableRemb can be called multiple times due to multiple calls to
+    // PeerConnectionImpl::ConfigureJsepSessionCodecs
+    if (!mRembEnabled) {
+      mRembEnabled = true;
+      mOtherFbTypes.push_back({ "", SdpRtcpFbAttributeList::kRemb, "", ""});
+    }
   }
 
   void
   AddParametersToMSection(SdpMediaSection& msection) const override
   {
     AddFmtpsToMSection(msection);
     AddRtcpFbsToMSection(msection);
   }
@@ -286,16 +303,19 @@ class JsepVideoCodecDescription : public
       rtcpfbs.PushEntry(mDefaultPt, SdpRtcpFbAttributeList::kAck, type);
     }
     for (const std::string& type : mNackFbTypes) {
       rtcpfbs.PushEntry(mDefaultPt, SdpRtcpFbAttributeList::kNack, type);
     }
     for (const std::string& type : mCcmFbTypes) {
       rtcpfbs.PushEntry(mDefaultPt, SdpRtcpFbAttributeList::kCcm, type);
     }
+    for (const auto& fb : mOtherFbTypes) {
+      rtcpfbs.PushEntry(mDefaultPt, fb.type, fb.parameter, fb.extra);
+    }
 
     msection.SetRtcpFbs(rtcpfbs);
   }
 
   SdpFmtpAttributeList::H264Parameters
   GetH264Parameters(const std::string& pt,
                     const SdpMediaSection& msection) const
   {
@@ -342,22 +362,35 @@ class JsepVideoCodecDescription : public
       if (remoteMsection.HasRtcpFb(mDefaultPt, type, subType)) {
         temp.push_back(subType);
       }
     }
     *supportedTypes = temp;
   }
 
   void
+  NegotiateRtcpFb(const SdpMediaSection& remoteMsection,
+                  std::vector<SdpRtcpFbAttributeList::Feedback>* supportedFbs) {
+    std::vector<SdpRtcpFbAttributeList::Feedback> temp;
+    for (auto& fb : *supportedFbs) {
+      if (remoteMsection.HasRtcpFb(mDefaultPt, fb.type, fb.parameter)) {
+        temp.push_back(fb);
+      }
+    }
+    *supportedFbs = temp;
+  }
+
+  void
   NegotiateRtcpFb(const SdpMediaSection& remote)
   {
     // Removes rtcp-fb types that the other side doesn't support
     NegotiateRtcpFb(remote, SdpRtcpFbAttributeList::kAck, &mAckFbTypes);
     NegotiateRtcpFb(remote, SdpRtcpFbAttributeList::kNack, &mNackFbTypes);
     NegotiateRtcpFb(remote, SdpRtcpFbAttributeList::kCcm, &mCcmFbTypes);
+    NegotiateRtcpFb(remote, &mOtherFbTypes);
   }
 
   virtual bool
   Negotiate(const std::string& pt,
             const SdpMediaSection& remoteMsection) override
   {
     JsepCodecDescription::Negotiate(pt, remoteMsection);
     if (mName == "H264") {
@@ -598,16 +631,19 @@ class JsepVideoCodecDescription : public
     return true;
   }
 
   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;
 
   // H264-specific stuff
   uint32_t mProfileLevelId;
   uint32_t mPacketizationMode;
   std::string mSpropParameterSets;
 };
 
 class JsepApplicationCodecDescription : public JsepCodecDescription {
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -865,17 +865,18 @@ class ConfigureCodec {
       mSoftwareH264Enabled(false),
       mH264Enabled(false),
       mVP9Enabled(false),
       mH264Level(13), // minimum suggested for WebRTC spec
       mH264MaxBr(0), // Unlimited
       mH264MaxMbps(0), // Unlimited
       mVP8MaxFs(0),
       mVP8MaxFr(0),
-      mUseTmmbr(false)
+      mUseTmmbr(false),
+      mUseRemb(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
@@ -931,16 +932,19 @@ class ConfigureCodec {
 
       branch->GetIntPref("media.navigator.video.max_fr", &mVP8MaxFr);
       if (mVP8MaxFr <= 0) {
         mVP8MaxFr = 60; // We must specify something other than 0
       }
 
       // 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);
     }
 
     void operator()(JsepCodecDescription* codec) const
     {
       switch (codec->mType) {
         case SdpMediaSection::kAudio:
           // Nothing to configure here, for now.
           break;
@@ -977,16 +981,19 @@ class ConfigureCodec {
               }
               videoCodec.mConstraints.maxFs = mVP8MaxFs;
               videoCodec.mConstraints.maxFps = mVP8MaxFr;
             }
 
             if (mUseTmmbr) {
               videoCodec.EnableTmmbr();
             }
+            if (mUseRemb) {
+              videoCodec.EnableRemb();
+            }
           }
           break;
         case SdpMediaSection::kText:
         case SdpMediaSection::kApplication:
         case SdpMediaSection::kMessage:
           {} // Nothing to configure for these.
       }
     }
@@ -998,16 +1005,17 @@ class ConfigureCodec {
     bool mH264Enabled;
     bool mVP9Enabled;
     int32_t mH264Level;
     int32_t mH264MaxBr;
     int32_t mH264MaxMbps;
     int32_t mVP8MaxFs;
     int32_t mVP8MaxFr;
     bool mUseTmmbr;
+    bool mUseRemb;
 };
 
 nsresult
 PeerConnectionImpl::ConfigureJsepSessionCodecs() {
   nsresult res;
   nsCOMPtr<nsIPrefService> prefs =
     do_GetService("@mozilla.org/preferences-service;1", &res);
 
--- a/media/webrtc/signaling/src/sdp/SdpAttribute.h
+++ b/media/webrtc/signaling/src/sdp/SdpAttribute.h
@@ -937,17 +937,17 @@ public:
 //                       / SP token [SP byte-string]
 //                       / ; empty
 //
 class SdpRtcpFbAttributeList : public SdpAttribute
 {
 public:
   SdpRtcpFbAttributeList() : SdpAttribute(kRtcpFbAttribute) {}
 
-  enum Type { kAck, kApp, kCcm, kNack, kTrrInt };
+  enum Type { kAck, kApp, kCcm, kNack, kTrrInt, kRemb };
 
   static const char* pli;
   static const char* sli;
   static const char* rpsi;
   static const char* app;
 
   static const char* fir;
   static const char* tmmbr;
@@ -988,16 +988,19 @@ inline std::ostream& operator<<(std::ost
       os << "ccm";
       break;
     case SdpRtcpFbAttributeList::kNack:
       os << "nack";
       break;
     case SdpRtcpFbAttributeList::kTrrInt:
       os << "trr-int";
       break;
+    case SdpRtcpFbAttributeList::kRemb:
+      os << "goog-remb";
+      break;
     default:
       MOZ_ASSERT(false);
       os << "?";
   }
   return os;
 }
 
 ///////////////////////////////////////////////////////////////////////////
--- a/media/webrtc/signaling/src/sdp/SipccSdpAttributeList.cpp
+++ b/media/webrtc/signaling/src/sdp/SipccSdpAttributeList.cpp
@@ -926,16 +926,19 @@ SipccSdpAttributeList::LoadRtcpFb(sdp_t*
         }
         break;
       case SDP_RTCP_FB_TRR_INT: {
         type = SdpRtcpFbAttributeList::kTrrInt;
         std::ostringstream os;
         os << rtcpfb->param.trr_int;
         parameter = os.str();
       } break;
+      case SDP_RTCP_FB_REMB: {
+        type = SdpRtcpFbAttributeList::kRemb;
+      } 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
@@ -8,16 +8,18 @@
 /* a=rtcp-fb enumerations */
 
 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,
     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,
@@ -35,16 +37,17 @@ typedef enum {
 typedef enum {
     SDP_RTCP_FB_ACK_NOT_FOUND = -1,
     SDP_RTCP_FB_ACK_RPSI = 0,
     SDP_RTCP_FB_ACK_APP,
     SDP_MAX_RTCP_FB_ACK,
     SDP_RTCP_FB_ACK_UNKNOWN
 } sdp_rtcp_fb_ack_type_e;
 
+// Codec Control Messages - defined by RFC 5104
 typedef enum {
     SDP_RTCP_FB_CCM_NOT_FOUND = -1,
     SDP_RTCP_FB_CCM_FIR = 0,
     SDP_RTCP_FB_CCM_TMMBR,
     SDP_RTCP_FB_CCM_TSTR,
     SDP_RTCP_FB_CCM_VBCM,
     SDP_MAX_RTCP_FB_CCM,
     SDP_RTCP_FB_CCM_UNKNOWN
--- a/media/webrtc/signaling/src/sdp/sipcc/sdp.h
+++ b/media/webrtc/signaling/src/sdp/sipcc/sdp.h
@@ -1736,30 +1736,39 @@ sdp_attr_get_rtcp_fb_ack(sdp_t *sdp_p, u
 
 sdp_rtcp_fb_nack_type_e
 sdp_attr_get_rtcp_fb_nack(sdp_t *sdp_p, uint16_t level, uint16_t payload_type, uint16_t inst);
 
 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);
+
 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
 sdp_attr_set_rtcp_fb_nack(sdp_t *sdp_p, uint16_t level, uint16_t payload_type, uint16_t inst,
                           sdp_rtcp_fb_nack_type_e);
 
 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_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
 sdp_attr_get_extmap_id(sdp_t *sdp_p, uint16_t level, uint16_t inst);
--- a/media/webrtc/signaling/src/sdp/sipcc/sdp_attr.c
+++ b/media/webrtc/signaling/src/sdp/sipcc/sdp_attr.c
@@ -4992,16 +4992,19 @@ sdp_result_e sdp_build_attr_rtcp_fb(sdp_
                 flex_string_sprintf(fs, " %s",
                     sdp_rtcp_fb_nack_type_val[attr_p->attr.rtcp_fb.param.nack]
                         .name);
             }
             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_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);
@@ -5135,16 +5138,20 @@ sdp_result_e sdp_parse_attr_rtcp_fb (sdp
                 sdp_parse_error(sdp_p,
                   "%s Warning: could not parse trr-int value for rtcp-fb "
                   "attribute", sdp_p->debug_str);
                 sdp_p->conf_p->num_invalid_param++;
                 return SDP_INVALID_PARAMETER;
             }
             break;
 
+        case SDP_RTCP_FB_REMB:
+            /* No additional tokens to parse after goog-remb */
+            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
@@ -6035,16 +6035,36 @@ sdp_attr_get_rtcp_fb_trr_int(sdp_t *sdp_
                       (unsigned)payload_type, (unsigned)inst);
         }
         sdp_p->conf_p->num_invalid_param++;
         return 0xFFFFFFFF;
     }
     return (attr_p->attr.rtcp_fb.param.trr_int);
 }
 
+/* Function:    sdp_attr_get_rtcp_fb_remb_enabled
+ * Description: Returns true if rtcp-fb:...goog-remb 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:...goog-remb exists
+ */
+tinybool
+sdp_attr_get_rtcp_fb_remb_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_REMB,
+                                   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)
  */
@@ -6168,16 +6188,48 @@ sdp_attr_set_rtcp_fb_trr_int(sdp_t *sdp_
 
     attr_p->attr.rtcp_fb.payload_num = payload_type;
     attr_p->attr.rtcp_fb.feedback_type = SDP_RTCP_FB_TRR_INT;
     attr_p->attr.rtcp_fb.param.trr_int = interval;
     attr_p->attr.rtcp_fb.extra[0] = '\0';
     return (SDP_SUCCESS);
 }
 
+/* Function:    sdp_attr_set_rtcp_fb_remb
+ * Description: Sets the value of an rtcp-fb:...goog-remb 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_remb(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 goog-remb 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_REMB;
+    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
@@ -480,17 +480,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("trr-int"),
+    SDP_NAME("goog-remb")
 };
 
 /* 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/media/webrtc/signaling/test/jsep_track_unittest.cpp
+++ b/media/webrtc/signaling/test/jsep_track_unittest.cpp
@@ -205,27 +205,77 @@ class JsepTrackTest : public ::testing::
       CheckEncodingCount(expected, mSendOff, mRecvAns);
     }
 
     void CheckAnsEncodingCount(size_t expected) const
     {
       CheckEncodingCount(expected, mSendAns, mRecvOff);
     }
 
+    const JsepVideoCodecDescription*
+    GetVideoCodec(const JsepTrack& track) 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) {
+        return nullptr;
+      }
+      return static_cast<const JsepVideoCodecDescription*>(codecs[0]);
+    }
+
+    void CheckOtherFbsSize(const JsepTrack& track, size_t expected) const
+    {
+      const JsepVideoCodecDescription* videoCodec = GetVideoCodec(track);
+      ASSERT_NE(videoCodec, nullptr);
+      ASSERT_EQ(videoCodec->mOtherFbTypes.size(), expected);
+    }
+
+    void CheckOtherFbExists(const JsepTrack& track,
+                            SdpRtcpFbAttributeList::Type type) const
+    {
+      const JsepVideoCodecDescription* videoCodec = GetVideoCodec(track);
+      ASSERT_NE(videoCodec, nullptr);
+      for (const auto& fb : videoCodec->mOtherFbTypes) {
+          if (fb.type == type) {
+            return; // found the RtcpFb type, so stop looking
+          }
+      }
+      FAIL();  // RtcpFb type not found
+    }
+
+    void SanityCheckRtcpFbs(const JsepVideoCodecDescription& a,
+                            const JsepVideoCodecDescription& b) const
+    {
+      ASSERT_EQ(a.mNackFbTypes.size(), b.mNackFbTypes.size());
+      ASSERT_EQ(a.mAckFbTypes.size(), b.mAckFbTypes.size());
+      ASSERT_EQ(a.mCcmFbTypes.size(), b.mCcmFbTypes.size());
+      ASSERT_EQ(a.mOtherFbTypes.size(), b.mOtherFbTypes.size());
+    }
+
     void SanityCheckCodecs(const JsepCodecDescription& a,
                            const JsepCodecDescription& b) const
     {
       ASSERT_EQ(a.mType, b.mType);
       ASSERT_EQ(a.mDefaultPt, b.mDefaultPt);
       ASSERT_EQ(a.mName, b.mName);
       ASSERT_EQ(a.mClock, b.mClock);
       ASSERT_EQ(a.mChannels, b.mChannels);
       ASSERT_NE(a.mDirection, b.mDirection);
       // These constraints are for fmtp and rid, which _are_ signaled
       ASSERT_EQ(a.mConstraints, b.mConstraints);
+
+      if (a.mType == SdpMediaSection::kVideo) {
+        SanityCheckRtcpFbs(static_cast<const JsepVideoCodecDescription&>(a),
+                           static_cast<const JsepVideoCodecDescription&>(b));
+      }
     }
 
     void SanityCheckEncodings(const JsepTrackEncoding& a,
                               const JsepTrackEncoding& b) const
     {
       ASSERT_EQ(a.GetCodecs().size(), b.GetCodecs().size());
       for (size_t i = 0; i < a.GetCodecs().size(); ++i) {
         SanityCheckCodecs(*a.GetCodecs()[i], *b.GetCodecs()[i]);
@@ -311,16 +361,93 @@ TEST_F(JsepTrackTest, AudioNegotiation)
 TEST_F(JsepTrackTest, VideoNegotiation)
 {
   Init(SdpMediaSection::kVideo);
   OfferAnswer();
   CheckOffEncodingCount(1);
   CheckAnsEncodingCount(1);
 }
 
+TEST_F(JsepTrackTest, VideoNegotiationOfferRemb)
+{
+  InitCodecs();
+  // enable remb on the offer codecs
+  ((JsepVideoCodecDescription*)mOffCodecs.values[2])->EnableRemb();
+  InitTracks(SdpMediaSection::kVideo);
+  InitSdp(SdpMediaSection::kVideo);
+  OfferAnswer();
+
+  // make sure REMB is on offer and not on answer
+  ASSERT_NE(mOffer->ToString().find("a=rtcp-fb:120 goog-remb"),
+            std::string::npos);
+  ASSERT_EQ(mAnswer->ToString().find("a=rtcp-fb:120 goog-remb"),
+            std::string::npos);
+  CheckOffEncodingCount(1);
+  CheckAnsEncodingCount(1);
+
+  CheckOtherFbsSize(*mSendOff, 0);
+  CheckOtherFbsSize(*mRecvAns, 0);
+
+  CheckOtherFbsSize(*mSendAns, 0);
+  CheckOtherFbsSize(*mRecvOff, 0);
+}
+
+TEST_F(JsepTrackTest, VideoNegotiationAnswerRemb)
+{
+  InitCodecs();
+  // enable remb on the answer codecs
+  ((JsepVideoCodecDescription*)mAnsCodecs.values[2])->EnableRemb();
+  InitTracks(SdpMediaSection::kVideo);
+  InitSdp(SdpMediaSection::kVideo);
+  OfferAnswer();
+
+  // make sure REMB is not on offer and not on answer
+  ASSERT_EQ(mOffer->ToString().find("a=rtcp-fb:120 goog-remb"),
+            std::string::npos);
+  ASSERT_EQ(mAnswer->ToString().find("a=rtcp-fb:120 goog-remb"),
+            std::string::npos);
+  CheckOffEncodingCount(1);
+  CheckAnsEncodingCount(1);
+
+  CheckOtherFbsSize(*mSendOff, 0);
+  CheckOtherFbsSize(*mRecvAns, 0);
+
+  CheckOtherFbsSize(*mSendAns, 0);
+  CheckOtherFbsSize(*mRecvOff, 0);
+}
+
+TEST_F(JsepTrackTest, VideoNegotiationOfferAnswerRemb)
+{
+  InitCodecs();
+  // enable remb on the offer and answer codecs
+  ((JsepVideoCodecDescription*)mOffCodecs.values[2])->EnableRemb();
+  ((JsepVideoCodecDescription*)mAnsCodecs.values[2])->EnableRemb();
+  InitTracks(SdpMediaSection::kVideo);
+  InitSdp(SdpMediaSection::kVideo);
+  OfferAnswer();
+
+  // make sure REMB is on offer and on answer
+  ASSERT_NE(mOffer->ToString().find("a=rtcp-fb:120 goog-remb"),
+            std::string::npos);
+  ASSERT_NE(mAnswer->ToString().find("a=rtcp-fb:120 goog-remb"),
+            std::string::npos);
+  CheckOffEncodingCount(1);
+  CheckAnsEncodingCount(1);
+
+  CheckOtherFbsSize(*mSendOff, 1);
+  CheckOtherFbsSize(*mRecvAns, 1);
+  CheckOtherFbExists(*mSendOff, SdpRtcpFbAttributeList::kRemb);
+  CheckOtherFbExists(*mRecvAns, SdpRtcpFbAttributeList::kRemb);
+
+  CheckOtherFbsSize(*mSendAns, 1);
+  CheckOtherFbsSize(*mRecvOff, 1);
+  CheckOtherFbExists(*mSendAns, SdpRtcpFbAttributeList::kRemb);
+  CheckOtherFbExists(*mRecvOff, SdpRtcpFbAttributeList::kRemb);
+}
+
 TEST_F(JsepTrackTest, AudioOffSendonlyAnsRecvonly)
 {
   Init(SdpMediaSection::kAudio);
   mRecvOff = nullptr;
   mSendAns = nullptr;
   OfferAnswer();
   CheckOffEncodingCount(1);
   CheckAnsEncodingCount(0);
--- a/media/webrtc/signaling/test/sdp_unittests.cpp
+++ b/media/webrtc/signaling/test/sdp_unittests.cpp
@@ -209,16 +209,26 @@ class SdpTest : public ::testing::Test {
       uint16_t inst_num = 0;
       EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_RTCP_FB,
                                  &inst_num), SDP_SUCCESS);
       EXPECT_EQ(sdp_attr_set_rtcp_fb_trr_int(sdp_ptr_, level, payload, inst_num,
                                              interval), SDP_SUCCESS);
       return inst_num;
     }
 
+    uint16_t AddNewRtcpFbRemb(int level,
+                              uint16_t payload = SDP_ALL_PAYLOADS) {
+      uint16_t inst_num = 0;
+      EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_RTCP_FB,
+                                 &inst_num), SDP_SUCCESS);
+      EXPECT_EQ(sdp_attr_set_rtcp_fb_remb(sdp_ptr_, level, payload, inst_num
+                                          ), SDP_SUCCESS);
+      return inst_num;
+    }
+
     uint16_t AddNewRtcpFbCcm(int level, sdp_rtcp_fb_ccm_type_e type,
                          uint16_t payload = SDP_ALL_PAYLOADS) {
       uint16_t inst_num = 0;
       EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_RTCP_FB,
                                  &inst_num), SDP_SUCCESS);
       EXPECT_EQ(sdp_attr_set_rtcp_fb_ccm(sdp_ptr_, level, payload, inst_num,
                                          type), SDP_SUCCESS);
       return inst_num;
@@ -350,16 +360,27 @@ TEST_F(SdpTest, parseRtcpFbNackAppFooBar
 }
 
 TEST_F(SdpTest, parseRtcpFbNackFooBarBaz) {
   ParseSdp(kVideoSdp + "a=rtcp-fb:120 nack foo bar baz\r\n");
   ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 1),
             SDP_RTCP_FB_NACK_UNKNOWN);
 }
 
+TEST_F(SdpTest, parseRtcpFbRemb) {
+  ParseSdp(kVideoSdp + "a=rtcp-fb:120 goog-remb\r\n");
+  ASSERT_EQ(sdp_attr_get_rtcp_fb_remb_enabled(sdp_ptr_, 1, 120), true);
+}
+
+TEST_F(SdpTest, parseRtcpRbRembAllPt) {
+  ParseSdp(kVideoSdp + "a=rtcp-fb:* goog-remb\r\n");
+  ASSERT_EQ(sdp_attr_get_rtcp_fb_remb_enabled(sdp_ptr_, 1, SDP_ALL_PAYLOADS),
+                                              true);
+}
+
 TEST_F(SdpTest, parseRtcpFbTrrInt0) {
   ParseSdp(kVideoSdp + "a=rtcp-fb:120 trr-int 0\r\n");
   ASSERT_EQ(sdp_attr_get_rtcp_fb_trr_int(sdp_ptr_, 1, 120, 1), 0U);
 }
 
 TEST_F(SdpTest, parseRtcpFbTrrInt123) {
   ParseSdp(kVideoSdp + "a=rtcp-fb:120 trr-int 123\r\n");
   ASSERT_EQ(sdp_attr_get_rtcp_fb_trr_int(sdp_ptr_, 1, 120, 1), 123U);
@@ -433,16 +454,17 @@ TEST_F(SdpTest, parseRtcpFbKitchenSink) 
     "a=rtcp-fb:120 nack sli\r\n"
     "a=rtcp-fb:120 nack rpsi\r\n"
     "a=rtcp-fb:120 nack app\r\n"
     "a=rtcp-fb:120 nack app foo\r\n"
     "a=rtcp-fb:120 nack app foo bar\r\n"
     "a=rtcp-fb:120 nack foo bar baz\r\n"
     "a=rtcp-fb:120 trr-int 0\r\n"
     "a=rtcp-fb:120 trr-int 123\r\n"
+    "a=rtcp-fb:120 goog-remb\r\n"
     "a=rtcp-fb:120 ccm fir\r\n"
     "a=rtcp-fb:120 ccm tmmbr\r\n"
     "a=rtcp-fb:120 ccm tmmbr smaxpr=456\r\n"
     "a=rtcp-fb:120 ccm tstr\r\n"
     "a=rtcp-fb:120 ccm vbcm 123 456 789\r\n"
     "a=rtcp-fb:120 ccm foo\r\n"
     "a=rtcp-fb:120 ccm foo bar baz\r\n"
     "a=rtcp-fb:120 foo\r\n"
@@ -477,16 +499,19 @@ TEST_F(SdpTest, parseRtcpFbKitchenSink) 
             SDP_RTCP_FB_NACK_UNKNOWN);
   ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 9),
             SDP_RTCP_FB_NACK_NOT_FOUND);
 
   ASSERT_EQ(sdp_attr_get_rtcp_fb_trr_int(sdp_ptr_, 1, 120, 1), 0U);
   ASSERT_EQ(sdp_attr_get_rtcp_fb_trr_int(sdp_ptr_, 1, 120, 2), 123U);
   ASSERT_EQ(sdp_attr_get_rtcp_fb_trr_int(sdp_ptr_, 1, 120, 3), 0xFFFFFFFF);
 
+  ASSERT_EQ(sdp_attr_get_rtcp_fb_remb_enabled(sdp_ptr_, 1, 120), true);
+  ASSERT_EQ(sdp_attr_get_rtcp_fb_remb_enabled(sdp_ptr_, 2, 120), false);
+
   ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 1), SDP_RTCP_FB_CCM_FIR);
   ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 2),
             SDP_RTCP_FB_CCM_TMMBR);
   ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 3),
             SDP_RTCP_FB_CCM_TMMBR);
   ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 4),
             SDP_RTCP_FB_CCM_TSTR);
   ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 5),
@@ -672,16 +697,32 @@ TEST_F(SdpTest, addRtcpFbNackEcn) {
 TEST_F(SdpTest, addRtcpFbNackEcnAllPt) {
   InitLocalSdp();
   int level = AddNewMedia(SDP_MEDIA_VIDEO);
   AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_ECN);
   std::string body = SerializeSdp();
   ASSERT_NE(body.find("a=rtcp-fb:* nack ecn\r\n"), std::string::npos);
 }
 
+TEST_F(SdpTest, addRtcpFbRemb) {
+  InitLocalSdp();
+  int level = AddNewMedia(SDP_MEDIA_VIDEO);
+  AddNewRtcpFbRemb(level, 120);
+  std::string body = SerializeSdp();
+  ASSERT_NE(body.find("a=rtcp-fb:120 goog-remb\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbRembAllPt) {
+  InitLocalSdp();
+  int level = AddNewMedia(SDP_MEDIA_VIDEO);
+  AddNewRtcpFbRemb(level);
+  std::string body = SerializeSdp();
+  ASSERT_NE(body.find("a=rtcp-fb:* goog-remb\r\n"), std::string::npos);
+}
+
 TEST_F(SdpTest, addRtcpFbTrrInt) {
   InitLocalSdp();
   int level = AddNewMedia(SDP_MEDIA_VIDEO);
   AddNewRtcpFbTrrInt(level, 12345, 120);
   std::string body = SerializeSdp();
   ASSERT_NE(body.find("a=rtcp-fb:120 trr-int 12345\r\n"), std::string::npos);
 }
 
@@ -1914,16 +1955,17 @@ const std::string kBasicAudioVideoDataOf
 "a=rtcp-fb:120 nack app foo" CRLF
 "a=rtcp-fb:120 nack foo" CRLF // Should be ignored
 "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
@@ -2008,36 +2050,37 @@ TEST_P(NewSdpTest, CheckExtmap) {
 TEST_P(NewSdpTest, CheckRtcpFb) {
   ParseSdp(kBasicAudioVideoDataOffer);
   ASSERT_TRUE(!!mSdp);
   ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) << "Wrong number of media sections";
 
   auto& video_attrs = mSdp->GetMediaSection(1).GetAttributeList();
   ASSERT_TRUE(video_attrs.HasAttribute(SdpAttribute::kRtcpFbAttribute));
   auto& rtcpfbs = video_attrs.GetRtcpFb().mFeedbacks;
-  ASSERT_EQ(19U, rtcpfbs.size());
+  ASSERT_EQ(20U, 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");
   CheckRtcpFb(rtcpfbs[5], "120", SdpRtcpFbAttributeList::kNack, "rpsi");
   CheckRtcpFb(rtcpfbs[6], "120", SdpRtcpFbAttributeList::kNack, "app", "foo");
   CheckRtcpFb(rtcpfbs[7], "120", SdpRtcpFbAttributeList::kCcm, "fir");
   CheckRtcpFb(rtcpfbs[8], "120", SdpRtcpFbAttributeList::kCcm, "tmmbr");
   CheckRtcpFb(rtcpfbs[9], "120", SdpRtcpFbAttributeList::kCcm, "tstr");
   CheckRtcpFb(rtcpfbs[10], "120", SdpRtcpFbAttributeList::kCcm, "vbcm");
   CheckRtcpFb(rtcpfbs[11], "120", SdpRtcpFbAttributeList::kTrrInt, "10");
-  CheckRtcpFb(rtcpfbs[12], "126", SdpRtcpFbAttributeList::kNack, "");
-  CheckRtcpFb(rtcpfbs[13], "126", SdpRtcpFbAttributeList::kNack, "pli");
-  CheckRtcpFb(rtcpfbs[14], "126", SdpRtcpFbAttributeList::kCcm, "fir");
-  CheckRtcpFb(rtcpfbs[15], "97",  SdpRtcpFbAttributeList::kNack, "");
-  CheckRtcpFb(rtcpfbs[16], "97",  SdpRtcpFbAttributeList::kNack, "pli");
-  CheckRtcpFb(rtcpfbs[17], "97", SdpRtcpFbAttributeList::kCcm, "fir");
-  CheckRtcpFb(rtcpfbs[18], "*", SdpRtcpFbAttributeList::kCcm, "tmmbr");
+  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");
 }
 
 TEST_P(NewSdpTest, CheckRtcp) {
   ParseSdp(kBasicAudioVideoOffer);
   ASSERT_TRUE(!!mSdp);
   ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) << "Wrong number of media sections";
 
   ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute(
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -374,16 +374,18 @@ pref("media.navigator.enabled", true);
 pref("media.navigator.video.enabled", true);
 pref("media.navigator.load_adapt", true);
 pref("media.navigator.load_adapt.measure_interval",1000);
 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.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