Bug 1249098: Support maxplaybackrate for opus. r=jesup draft
authorByron Campen [:bwc] <docfaraday@gmail.com>
Thu, 18 Feb 2016 14:13:35 -0600
changeset 334317 3ef372486a8c608c780e864412942e8819cc3f88
parent 334101 d848a5628d801a460a7244cbcdea22d328d8b310
child 334982 087114a9cbc24bdeb84b2540abf9578e57047d74
child 334983 559c69a40caf459362d861032b21c925331ea590
child 335079 bc94c0c18bb95af7e2f9eb27dcda6a5699264ab1
push id11505
push userbcampen@mozilla.com
push dateWed, 24 Feb 2016 22:02:19 +0000
reviewersjesup
bugs1249098
milestone47.0a1
Bug 1249098: Support maxplaybackrate for opus. r=jesup MozReview-Commit-ID: 7BKVFkbPgV2
media/webrtc/signaling/src/jsep/JsepCodecDescription.h
media/webrtc/signaling/src/jsep/JsepTrack.h
media/webrtc/signaling/src/media-conduit/AudioConduit.cpp
media/webrtc/signaling/src/media-conduit/CodecConfig.h
media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.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/sdp.h
media/webrtc/signaling/src/sdp/sipcc/sdp_attr.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
--- a/media/webrtc/signaling/src/jsep/JsepCodecDescription.h
+++ b/media/webrtc/signaling/src/jsep/JsepCodecDescription.h
@@ -125,24 +125,72 @@ class JsepAudioCodecDescription : public
                             uint32_t clock,
                             uint32_t channels,
                             uint32_t packetSize,
                             uint32_t bitRate,
                             bool enabled = true)
       : JsepCodecDescription(mozilla::SdpMediaSection::kAudio, defaultPt, name,
                              clock, channels, enabled),
         mPacketSize(packetSize),
-        mBitrate(bitRate)
+        mBitrate(bitRate),
+        mMaxPlaybackRate(0)
   {
   }
 
   JSEP_CODEC_CLONE(JsepAudioCodecDescription)
 
+  SdpFmtpAttributeList::OpusParameters
+  GetOpusParameters(const std::string& pt,
+                    const SdpMediaSection& msection) const
+  {
+    // Will contain defaults if nothing else
+    SdpFmtpAttributeList::OpusParameters result;
+    auto* params = msection.FindFmtp(pt);
+
+    if (params && params->codec_type == SdpRtpmapAttributeList::kOpus) {
+      result =
+        static_cast<const SdpFmtpAttributeList::OpusParameters&>(*params);
+    }
+
+    return result;
+  }
+
+  void
+  AddParametersToMSection(SdpMediaSection& msection) const override
+  {
+    if (mDirection == sdp::kSend) {
+      return;
+    }
+
+    if (mName == "opus" && mMaxPlaybackRate) {
+      SdpFmtpAttributeList::OpusParameters opusParams(
+          GetOpusParameters(mDefaultPt, msection));
+      opusParams.maxplaybackrate = mMaxPlaybackRate;
+      msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, opusParams));
+    }
+  }
+
+  bool
+  Negotiate(const std::string& pt,
+            const SdpMediaSection& remoteMsection) override
+  {
+    JsepCodecDescription::Negotiate(pt, remoteMsection);
+    if (mName == "opus" && mDirection == sdp::kSend) {
+      SdpFmtpAttributeList::OpusParameters opusParams(
+          GetOpusParameters(mDefaultPt, remoteMsection));
+
+      mMaxPlaybackRate = opusParams.maxplaybackrate;
+    }
+
+    return true;
+  }
+
   uint32_t mPacketSize;
   uint32_t mBitrate;
+  uint32_t mMaxPlaybackRate;
 };
 
 class JsepVideoCodecDescription : public JsepCodecDescription {
  public:
   JsepVideoCodecDescription(const std::string& defaultPt,
                             const std::string& name,
                             uint32_t clock,
                             bool enabled = true)
@@ -195,28 +243,26 @@ class JsepVideoCodecDescription : public
         h264Params.profile_level_id = mProfileLevelId;
       }
 
       // 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));
+      msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, h264Params));
     } 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;
-        msection.SetFmtp(
-            SdpFmtpAttributeList::Fmtp(mDefaultPt, "", vp8Params));
+        msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, vp8Params));
       }
     }
   }
 
   void
   AddRtcpFbsToMSection(SdpMediaSection& msection) const
   {
     SdpRtcpFbAttributeList rtcpfbs(msection.GetRtcpFbs());
--- a/media/webrtc/signaling/src/jsep/JsepTrack.h
+++ b/media/webrtc/signaling/src/jsep/JsepTrack.h
@@ -136,24 +136,24 @@ public:
   {
     mSsrcs.push_back(ssrc);
   }
 
   virtual void PopulateCodecs(
       const std::vector<JsepCodecDescription*>& prototype);
 
   template <class UnaryFunction>
-  void ForEachCodec(UnaryFunction& func)
+  void ForEachCodec(UnaryFunction func)
   {
     std::for_each(mPrototypeCodecs.values.begin(),
                   mPrototypeCodecs.values.end(), func);
   }
 
   template <class BinaryPredicate>
-  void SortCodecs(BinaryPredicate& sorter)
+  void SortCodecs(BinaryPredicate sorter)
   {
     std::stable_sort(mPrototypeCodecs.values.begin(),
                      mPrototypeCodecs.values.end(), sorter);
   }
 
   virtual void AddToOffer(SdpMediaSection* offer) const;
   virtual void AddToAnswer(const SdpMediaSection& offer,
                            SdpMediaSection* answer) const;
--- a/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp
+++ b/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp
@@ -407,16 +407,26 @@ WebrtcAudioConduit::ConfigureSendMediaCo
       CSFLogError(logTag, "%s Invalid Send Codec", __FUNCTION__);
       return kMediaConduitInvalidSendCodec;
     }
     CSFLogError(logTag, "%s SetSendCodec Failed %d ", __FUNCTION__,
                                          mPtrVoEBase->LastError());
     return kMediaConduitUnknownError;
   }
 
+  if (codecConfig->mName == "opus" && codecConfig->mMaxPlaybackRate) {
+    if (mPtrVoECodec->SetOpusMaxPlaybackRate(
+          mChannel,
+          codecConfig->mMaxPlaybackRate) == -1) {
+      CSFLogError(logTag, "%s SetOpusMaxPlaybackRate Failed %d ", __FUNCTION__,
+                  mPtrVoEBase->LastError());
+      return kMediaConduitUnknownError;
+    }
+  }
+
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
   // TEMPORARY - see bug 694814 comment 2
   nsresult rv;
   nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv);
   if (NS_SUCCEEDED(rv)) {
     nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs);
 
     if (branch) {
--- a/media/webrtc/signaling/src/media-conduit/CodecConfig.h
+++ b/media/webrtc/signaling/src/media-conduit/CodecConfig.h
@@ -24,29 +24,32 @@ struct AudioCodecConfig
    */
   int mType;
   std::string mName;
   int mFreq;
   int mPacSize;
   int mChannels;
   int mRate;
 
+  // OPUS-specific
+  int mMaxPlaybackRate;
+
   /* Default constructor is not provided since as a consumer, we
    * can't decide the default configuration for the codec
    */
   explicit AudioCodecConfig(int type, std::string name,
                             int freq,int pacSize,
                             int channels, int rate)
                                                    : mType(type),
                                                      mName(name),
                                                      mFreq(freq),
                                                      mPacSize(pacSize),
                                                      mChannels(channels),
-                                                     mRate(rate)
-
+                                                     mRate(rate),
+                                                     mMaxPlaybackRate(0)
   {
   }
 };
 
 /*
  * Minimalistic video codec configuration
  * More to be added later depending on the use-case
  */
--- a/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
+++ b/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
@@ -69,16 +69,17 @@ JsepCodecDescToCodecConfig(const JsepCod
   }
 
   *aConfig = new AudioCodecConfig(pt,
                                   desc.mName,
                                   desc.mClock,
                                   desc.mPacketSize,
                                   desc.mChannels,
                                   desc.mBitrate);
+  (*aConfig)->mMaxPlaybackRate = desc.mMaxPlaybackRate;
 
   return NS_OK;
 }
 
 static std::vector<JsepCodecDescription*>
 GetCodecs(const JsepTrackNegotiatedDetails& aDetails)
 {
   // We do not try to handle cases where a codec is not used on the primary
--- a/media/webrtc/signaling/src/sdp/SdpAttribute.cpp
+++ b/media/webrtc/signaling/src/sdp/SdpAttribute.cpp
@@ -108,23 +108,21 @@ SdpFingerprintAttributeList::ParseFinger
   }
   return fp;
 }
 
 void
 SdpFmtpAttributeList::Serialize(std::ostream& os) const
 {
   for (auto i = mFmtps.begin(); i != mFmtps.end(); ++i) {
-    os << "a=" << mType << ":" << i->format << " ";
     if (i->parameters) {
+      os << "a=" << mType << ":" << i->format << " ";
       i->parameters->Serialize(os);
-    } else {
-      os << i->parameters_string;
+      os << CRLF;
     }
-    os << CRLF;
   }
 }
 
 void
 SdpGroupAttributeList::Serialize(std::ostream& os) const
 {
   for (auto i = mGroups.begin(); i != mGroups.end(); ++i) {
     os << "a=" << mType << ":" << i->semantics;
--- a/media/webrtc/signaling/src/sdp/SdpAttribute.h
+++ b/media/webrtc/signaling/src/sdp/SdpAttribute.h
@@ -1231,70 +1231,84 @@ public:
       os << "max-fs=" << max_fs;
       os << ";max-fr=" << max_fr;
     }
 
     unsigned int max_fs;
     unsigned int max_fr;
   };
 
+  class OpusParameters : public Parameters
+  {
+  public:
+    enum { kDefaultMaxPlaybackRate = 48000 };
+    OpusParameters() :
+      Parameters(SdpRtpmapAttributeList::kOpus),
+      maxplaybackrate(kDefaultMaxPlaybackRate)
+    {}
+
+    Parameters*
+    Clone() const override
+    {
+      return new OpusParameters(*this);
+    }
+
+    void
+    Serialize(std::ostream& os) const override
+    {
+      os << "maxplaybackrate=" << maxplaybackrate;
+    }
+
+    unsigned int maxplaybackrate;
+  };
+
   class Fmtp
   {
   public:
-    Fmtp(const std::string& aFormat, const std::string& aParametersString,
-         UniquePtr<Parameters> aParameters)
+    Fmtp(const std::string& aFormat, UniquePtr<Parameters> aParameters)
         : format(aFormat),
-          parameters_string(aParametersString),
           parameters(Move(aParameters))
     {
     }
 
-    Fmtp(const std::string& aFormat, const std::string& aParametersString,
-         const Parameters& aParameters)
+    Fmtp(const std::string& aFormat, const Parameters& aParameters)
         : format(aFormat),
-          parameters_string(aParametersString),
           parameters(aParameters.Clone())
     {
     }
 
     // TODO: Rip all of this out when we have move semantics in the stl.
     Fmtp(const Fmtp& orig) { *this = orig; }
 
     Fmtp& operator=(const Fmtp& rhs)
     {
       if (this != &rhs) {
         format = rhs.format;
-        parameters_string = rhs.parameters_string;
         parameters.reset(rhs.parameters ? rhs.parameters->Clone() : nullptr);
       }
       return *this;
     }
 
     // The contract around these is as follows:
-    // * |format| and |parameters_string| are always set
     // * |parameters| is only set if we recognized the media type and had
     //   a subclass of Parameters to represent that type of parameters
     // * |parameters| is a best-effort representation; it might be missing
     //   stuff
-    // * if |parameters| is set, it determines the serialized form,
-    //   otherwise |parameters_string| is used
     // * Parameters::codec_type tells you the concrete class, eg
     //   kH264 -> H264Parameters
     std::string format;
-    std::string parameters_string;
     UniquePtr<Parameters> parameters;
   };
 
   virtual void Serialize(std::ostream& os) const override;
 
   void
-  PushEntry(const std::string& format, const std::string& parameters_string,
-            UniquePtr<Parameters> parameters)
+  PushEntry(const std::string& format, UniquePtr<Parameters> parameters)
   {
-    mFmtps.push_back(Fmtp(format, parameters_string, Move(parameters)));
+    mFmtps.push_back(Fmtp(format, Move(parameters)));
   }
 
   std::vector<Fmtp> mFmtps;
 };
 
 ///////////////////////////////////////////////////////////////////////////
 // a=sctpmap, draft-ietf-mmusic-sctp-sdp-05
 //-------------------------------------------------------------------------
--- a/media/webrtc/signaling/src/sdp/SipccSdpAttributeList.cpp
+++ b/media/webrtc/signaling/src/sdp/SipccSdpAttributeList.cpp
@@ -661,31 +661,16 @@ SipccSdpAttributeList::LoadFmtp(sdp_t* s
 
     sdp_fmtp_t* fmtp = &(attr->attr.fmtp);
 
     // Get the payload type
     std::stringstream osPayloadType;
     // payload_num is the number in the fmtp attribute, verbatim
     osPayloadType << fmtp->payload_num;
 
-    // Get the serialized form of the parameters
-    flex_string fs;
-    flex_string_init(&fs);
-
-    // Very lame, but we need direct access so we can get the serialized form
-    sdp_result_e sdpres = sdp_build_attr_fmtp_params(sdp, fmtp, &fs);
-
-    if (sdpres != SDP_SUCCESS) {
-      flex_string_free(&fs);
-      continue;
-    }
-
-    std::string paramsString(fs.buffer);
-    flex_string_free(&fs);
-
     // Get parsed form of parameters, if supported
     UniquePtr<SdpFmtpAttributeList::Parameters> parameters;
 
     rtp_ptype codec = sdp_get_known_payload_type(sdp, level, fmtp->payload_num);
 
     switch (codec) {
       case RTP_H264_P0:
       case RTP_H264_P1: {
@@ -730,21 +715,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_OPUS: {
+        SdpFmtpAttributeList::OpusParameters* opusParameters(
+            new SdpFmtpAttributeList::OpusParameters);
+        opusParameters->maxplaybackrate = fmtp->maxplaybackrate;
+        parameters.reset(opusParameters);
+      } break;
       default: {
       }
     }
 
-    fmtps->PushEntry(osPayloadType.str(), paramsString, Move(parameters));
+    fmtps->PushEntry(osPayloadType.str(), Move(parameters));
   }
 
   if (!fmtps->mFmtps.empty()) {
     SetAttribute(fmtps.release());
   }
 }
 
 void
--- a/media/webrtc/signaling/src/sdp/sipcc/sdp.h
+++ b/media/webrtc/signaling/src/sdp/sipcc/sdp.h
@@ -408,16 +408,17 @@ typedef enum {
     SDP_LEVEL_ASYMMETRY_ALLOWED,
     SDP_MAX_AVERAGE_BIT_RATE,
     SDP_USED_TX,
     SDP_STEREO,
     SDP_USE_IN_BAND_FEC,
     SDP_MAX_CODED_AUDIO_BW,
     SDP_CBR,
     SDP_MAX_FR,
+    SDP_MAX_PLAYBACK_RATE,
     SDP_MAX_FMTP_PARAM,
     SDP_FMTP_PARAM_UNKNOWN
 } sdp_fmtp_codec_param_e;
 
 /* Fmtp attribute parameters values for
    fmtp attribute parameters which convey codec
    information */
 
@@ -636,16 +637,17 @@ typedef struct sdp_fmtp {
     tinybool                  annexa_required;
 
     tinybool                  annexa;
     tinybool                  annexb;
     uint32_t                       bitrate;
     uint32_t                       mode;
 
     /* some OPUS specific fmtp params */
+    uint32_t                       maxplaybackrate;
     uint32_t                       maxaveragebitrate;
     uint16_t                       usedtx;
     uint16_t                       stereo;
     uint16_t                       useinbandfec;
     char                      maxcodedaudiobandwidth[SDP_MAX_STRING_LEN+1];
     uint16_t                       cbr;
 
     /* BEGIN - All Video related FMTP parameters */
--- a/media/webrtc/signaling/src/sdp/sipcc/sdp_attr.c
+++ b/media/webrtc/signaling/src/sdp/sipcc/sdp_attr.c
@@ -1723,16 +1723,41 @@ sdp_result_e sdp_parse_attr_fmtp (sdp_t 
                 strtoul_result > UINT_MAX) {
                 sdp_attr_fmtp_invalid_value(sdp_p, "max-fr", tok);
                 SDP_FREE(temp_ptr);
                 return SDP_INVALID_PARAMETER;
             }
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->max_fr = (uint32_t) strtoul_result;
             codec_info_found = TRUE;
+        } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[50].name,
+                                   sdp_fmtp_codec_param[50].strlen) == 0) {
+            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
+            if (result1 != SDP_SUCCESS) {
+                    fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
+                if (result1 != SDP_SUCCESS) {
+                    sdp_attr_fmtp_no_value(sdp_p, "maxplaybackrate");
+                    SDP_FREE(temp_ptr);
+                    return SDP_INVALID_PARAMETER;
+                }
+            }
+            tok = tmp;
+            tok++;
+            errno = 0;
+            strtoul_result = strtoul(tok, &strtoul_end, 10);
+
+            if (errno || tok == strtoul_end || strtoul_result == 0 || strtoul_result > UINT_MAX) {
+                sdp_attr_fmtp_invalid_value(sdp_p, "maxplaybackrate", tok);
+                SDP_FREE(temp_ptr);
+                return SDP_INVALID_PARAMETER;
+            }
+            fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
+            fmtp_p->maxplaybackrate = (uint32_t) strtoul_result;
+            codec_info_found = TRUE;
+
         } else if (fmtp_ptr != NULL && *fmtp_ptr == '\n') {
             temp=PL_strtok_r(tmp, ";", &strtok_state);
             if (temp) {
                 if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {
                     SDP_PRINT("%s Annexes are possibly there for this fmtp %s  tmp: %s line\n",
                               sdp_p->debug_str, fmtp_ptr, tmp);
                 }
                 while (temp != NULL) {
--- a/media/webrtc/signaling/src/sdp/sipcc/sdp_main.c
+++ b/media/webrtc/signaling/src/sdp/sipcc/sdp_main.c
@@ -425,17 +425,18 @@ const sdp_namearray_t sdp_fmtp_codec_par
      {"mode",                sizeof("mode")},  /* 41 */
     {"level-asymmetry-allowed",         sizeof("level-asymmetry-allowed")}, /* 42 */
     {"maxaveragebitrate",               sizeof("maxaveragebitrate")}, /* 43 */
     {"usedtx",                          sizeof("usedtx")}, /* 44 */
     {"stereo",                          sizeof("stereo")}, /* 45 */
     {"useinbandfec",                    sizeof("useinbandfec")}, /* 46 */
     {"maxcodedaudiobandwidth",          sizeof("maxcodedaudiobandwidth")}, /* 47 */
     {"cbr",                             sizeof("cbr")}, /* 48 */
-    {"max-fr",                          sizeof("max-fr")} /* 49 */
+    {"max-fr",                          sizeof("max-fr")}, /* 49 */
+    {"maxplaybackrate",                 sizeof("maxplaybackrate")} /* 50 */
 } ;
 
 /* Note: These *must* be in the same order as the enum type. */
 const sdp_namearray_t sdp_fmtp_codec_param_val[SDP_MAX_FMTP_PARAM_VAL] =
 {
     {"yes",                 sizeof("yes")},
     {"no",                  sizeof("no")}
 };
--- a/media/webrtc/signaling/test/jsep_track_unittest.cpp
+++ b/media/webrtc/signaling/test/jsep_track_unittest.cpp
@@ -460,16 +460,64 @@ TEST_F(JsepTrackTest, SimulcastAnswerer)
   ASSERT_EQ("foo", mSendAns->GetNegotiatedDetails()->GetEncoding(0).mRid);
   ASSERT_EQ(40000U,
       mSendAns->GetNegotiatedDetails()->GetEncoding(0).mConstraints.maxBr);
   ASSERT_EQ("bar", mSendAns->GetNegotiatedDetails()->GetEncoding(1).mRid);
   ASSERT_EQ(10000U,
       mSendAns->GetNegotiatedDetails()->GetEncoding(1).mConstraints.maxBr);
 }
 
+#define VERIFY_OPUS_MAX_PLAYBACK_RATE(track, expectedRate)  \
+{  \
+  JsepTrack& copy(track); \
+  ASSERT_TRUE(copy.GetNegotiatedDetails());  \
+  ASSERT_TRUE(copy.GetNegotiatedDetails()->GetEncodingCount());  \
+  for (auto codec : copy.GetNegotiatedDetails()->GetEncoding(0).GetCodecs()) {\
+    if (codec->mName == "opus") {  \
+      JsepAudioCodecDescription* audioCodec =  \
+        static_cast<JsepAudioCodecDescription*>(codec);  \
+      ASSERT_EQ((expectedRate), audioCodec->mMaxPlaybackRate);  \
+    }  \
+  };  \
+}
+
+TEST_F(JsepTrackTest, DefaultOpusParameters)
+{
+  Init(SdpMediaSection::kAudio);
+  OfferAnswer();
+
+  VERIFY_OPUS_MAX_PLAYBACK_RATE(*mSendOff,
+      SdpFmtpAttributeList::OpusParameters::kDefaultMaxPlaybackRate);
+  VERIFY_OPUS_MAX_PLAYBACK_RATE(*mSendAns,
+      SdpFmtpAttributeList::OpusParameters::kDefaultMaxPlaybackRate);
+  VERIFY_OPUS_MAX_PLAYBACK_RATE(*mRecvOff, 0U);
+  VERIFY_OPUS_MAX_PLAYBACK_RATE(*mRecvAns, 0U);
+}
+
+TEST_F(JsepTrackTest, NonDefaultOpusParameters)
+{
+  InitCodecs();
+  for (auto& codec : mAnsCodecs.values) {
+    if (codec->mName == "opus") {
+      JsepAudioCodecDescription* audioCodec =
+        static_cast<JsepAudioCodecDescription*>(codec);
+      audioCodec->mMaxPlaybackRate = 16000;
+    }
+  }
+  InitTracks(SdpMediaSection::kAudio);
+  InitSdp(SdpMediaSection::kAudio);
+  OfferAnswer();
+
+  VERIFY_OPUS_MAX_PLAYBACK_RATE(*mSendOff, 16000U);
+  VERIFY_OPUS_MAX_PLAYBACK_RATE(*mSendAns,
+      SdpFmtpAttributeList::OpusParameters::kDefaultMaxPlaybackRate);
+  VERIFY_OPUS_MAX_PLAYBACK_RATE(*mRecvOff, 0U);
+  VERIFY_OPUS_MAX_PLAYBACK_RATE(*mRecvAns, 16000U);
+}
+
 } // namespace mozilla
 
 int
 main(int argc, char** argv)
 {
   // Prevents some log spew
   ScopedXPCOM xpcom("jsep_track_unittest");
 
--- a/media/webrtc/signaling/test/sdp_unittests.cpp
+++ b/media/webrtc/signaling/test/sdp_unittests.cpp
@@ -1136,16 +1136,17 @@ const std::string kBasicAudioVideoOffer 
 "a=identity:blahblahblah foo;bar" CRLF
 "a=group:BUNDLE first second" CRLF
 "a=group:BUNDLE third" CRLF
 "a=group:LS first third" CRLF
 "m=audio 9 RTP/SAVPF 109 9 0 8 101" CRLF
 "c=IN IP4 0.0.0.0" CRLF
 "a=mid:first" CRLF
 "a=rtpmap:109 opus/48000/2" CRLF
+"a=fmtp:109 maxplaybackrate=32000" CRLF
 "a=ptime:20" CRLF
 "a=maxptime:20" CRLF
 "a=rtpmap:9 G722/8000" CRLF
 "a=rtpmap:0 PCMU/8000" CRLF
 "a=rtpmap:8 PCMA/8000" CRLF
 "a=rtpmap:101 telephone-event/8000" CRLF
 "a=fmtp:101 0-15" CRLF
 "a=ice-ufrag:00000000" CRLF
@@ -1497,17 +1498,17 @@ const std::string kH264AudioVideoOffer =
 "a=mid:first" CRLF
 "a=rtpmap:109 opus/48000/2" CRLF
 "a=ptime:20" CRLF
 "a=maxptime:20" CRLF
 "a=rtpmap:9 G722/8000" CRLF
 "a=rtpmap:0 PCMU/8000" CRLF
 "a=rtpmap:8 PCMA/8000" CRLF
 "a=rtpmap:101 telephone-event/8000" CRLF
-"a=fmtp:101 0-15" CRLF
+"a=fmtp:109 maxplaybackrate=32000" CRLF
 "a=ice-ufrag:00000000" CRLF
 "a=ice-pwd:0000000000000000000000000000000" CRLF
 "a=sendonly" CRLF
 "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level" CRLF
 "a=setup:actpass" CRLF
 "a=rtcp-mux" CRLF
 "a=msid:stream track" CRLF
 "a=candidate:0 1 UDP 2130379007 10.0.0.36 62453 typ host" CRLF
@@ -1552,18 +1553,22 @@ TEST_P(NewSdpTest, CheckFormatParameters
   ASSERT_EQ(3U, mSdp->GetMediaSectionCount())
     << "Wrong number of media sections";
 
   ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
       SdpAttribute::kFmtpAttribute));
   auto audio_format_params =
       mSdp->GetMediaSection(0).GetAttributeList().GetFmtp().mFmtps;
   ASSERT_EQ(1U, audio_format_params.size());
-  ASSERT_EQ("101", audio_format_params[0].format);
-  ASSERT_EQ("0-15", audio_format_params[0].parameters_string);
+  ASSERT_EQ("109", audio_format_params[0].format);
+  ASSERT_TRUE(!!audio_format_params[0].parameters);
+  const SdpFmtpAttributeList::OpusParameters* opus_parameters =
+    static_cast<SdpFmtpAttributeList::OpusParameters*>(
+        audio_format_params[0].parameters.get());
+  ASSERT_EQ(32000U, opus_parameters->maxplaybackrate);
 
   ASSERT_TRUE(mSdp->GetMediaSection(1).GetAttributeList().HasAttribute(
       SdpAttribute::kFmtpAttribute));
   auto video_format_params =
       mSdp->GetMediaSection(1).GetAttributeList().GetFmtp().mFmtps;
   ASSERT_EQ(3U, video_format_params.size());
   ASSERT_EQ("97", video_format_params[0].format);
   ASSERT_TRUE(!!video_format_params[0].parameters);